/* * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "zm_stream_socket_protocol.h" #include "zm_ffmpeg.h" #include "zm_logger.h" #include namespace zm { namespace stream_socket { namespace { void put_u16(uint8_t *out, uint16_t value) { out[0] = value & 0xff; out[1] = (value >> 8) & 0xff; } void put_u32(uint8_t *out, uint32_t value) { out[0] = value & 0xff; out[1] = (value >> 8) & 0xff; out[2] = (value >> 16) & 0xff; out[3] = (value >> 24) & 0xff; } void put_u64(uint8_t *out, uint64_t value) { put_u32(out, value & 0xffffffff); put_u32(out + 4, value >> 32); } uint16_t get_u16(const uint8_t *in) { return static_cast(in[0]) | (static_cast(in[1]) << 8); } uint32_t get_u32(const uint8_t *in) { return static_cast(in[0]) | (static_cast(in[1]) << 8) | (static_cast(in[2]) << 16) | (static_cast(in[3]) << 24); } uint64_t get_u64(const uint8_t *in) { return static_cast(get_u32(in)) | (static_cast(get_u32(in + 4)) << 32); } void append_tlv(std::vector &out, uint8_t tag, const uint8_t *value, uint16_t len) { out.push_back(tag); uint8_t len_le[2]; put_u16(len_le, len); out.insert(out.end(), len_le, len_le + 2); out.insert(out.end(), value, value + len); } void append_tlv_u32(std::vector &out, uint8_t tag, uint32_t value) { uint8_t value_le[4]; put_u32(value_le, value); append_tlv(out, tag, value_le, sizeof(value_le)); } void append_tlv_u64(std::vector &out, uint8_t tag, uint64_t value) { uint8_t value_le[8]; put_u64(value_le, value); append_tlv(out, tag, value_le, sizeof(value_le)); } void append_tlv_str(std::vector &out, uint8_t tag, const std::string &value) { // TLV length is u16; clamp pathologically long strings rather than overflow. uint16_t len = value.size() > kMaxTlvValueSize ? kMaxTlvValueSize : static_cast(value.size()); append_tlv(out, tag, reinterpret_cast(value.data()), len); } } // namespace void SerializeHeader(const Header &header, uint8_t out[kHeaderSize]) { put_u32(out, header.length); out[4] = header.version; out[5] = header.type; out[6] = header.stream; out[7] = header.flags; put_u32(out + 8, header.sequence); put_u32(out + 12, header.generation); put_u64(out + 16, static_cast(header.pts_us)); // two's-complement } bool ParseHeader(const uint8_t in[kHeaderSize], Header &header) { header.length = get_u32(in); header.version = in[4]; header.type = in[5]; header.stream = in[6]; header.flags = in[7]; header.sequence = get_u32(in + 8); header.generation = get_u32(in + 12); header.pts_us = static_cast(get_u64(in + 16)); // two's-complement if (header.version != kProtocolVersion) return false; if (header.length < kHeaderLengthBytes or header.length > kMaxMessageLength) return false; return true; } std::vector BuildHello(const AVCodecParameters *par, AVRational frame_rate) { std::vector out; out.reserve(64 + (par->extradata ? par->extradata_size : 0)); append_tlv_u32(out, kTlvCodecId, static_cast(par->codec_id)); if (par->extradata and par->extradata_size > 0) { if (static_cast(par->extradata_size) > kMaxTlvValueSize) { // A TLV value is at most 64 KiB; parameter sets are a few hundred bytes, // so anything bigger is not something a consumer could use anyway. // Omit it rather than send a silently truncated blob. Warning("StreamSocket: extradata of %d bytes exceeds the HELLO TLV limit" " of %zu bytes, omitting it", par->extradata_size, kMaxTlvValueSize); } else { append_tlv(out, kTlvExtradata, par->extradata, static_cast(par->extradata_size)); } } if (par->codec_type == AVMEDIA_TYPE_VIDEO) { if (par->width > 0) append_tlv_u32(out, kTlvWidth, par->width); if (par->height > 0) append_tlv_u32(out, kTlvHeight, par->height); if (frame_rate.num > 0 and frame_rate.den > 0) { append_tlv_u32(out, kTlvFpsNum, frame_rate.num); append_tlv_u32(out, kTlvFpsDen, frame_rate.den); } } else if (par->codec_type == AVMEDIA_TYPE_AUDIO) { if (par->sample_rate > 0) append_tlv_u32(out, kTlvSampleRate, par->sample_rate); #if LIBAVUTIL_VERSION_CHECK(57, 28, 100, 28, 0) int channels = par->ch_layout.nb_channels; #else int channels = par->channels; #endif if (channels > 0) append_tlv_u32(out, kTlvChannels, channels); } if (par->profile >= 0) append_tlv_u32(out, kTlvProfile, par->profile); if (par->level >= 0) append_tlv_u32(out, kTlvLevel, par->level); return out; } bool ParseHello(const uint8_t *data, size_t len, HelloInfo &info) { info = HelloInfo(); size_t pos = 0; while (pos < len) { if (len - pos < 3) return false; uint8_t tag = data[pos]; uint16_t value_len = get_u16(data + pos + 1); pos += 3; if (len - pos < value_len) return false; const uint8_t *value = data + pos; pos += value_len; bool known_numeric = tag >= kTlvCodecId and tag <= kTlvLevel and tag != kTlvExtradata; if (known_numeric and value_len != 4) return false; switch (tag) { case kTlvCodecId: info.codec_id = static_cast(get_u32(value)); break; case kTlvExtradata: info.extradata.assign(value, value + value_len); break; case kTlvWidth: info.width = get_u32(value); break; case kTlvHeight: info.height = get_u32(value); break; case kTlvFpsNum: info.fps_num = get_u32(value); break; case kTlvFpsDen: info.fps_den = get_u32(value); break; case kTlvSampleRate: info.sample_rate = get_u32(value); break; case kTlvChannels: info.channels = get_u32(value); break; case kTlvProfile: info.profile = get_u32(value); break; case kTlvLevel: info.level = get_u32(value); break; default: break; // unknown tag: skip } } return info.codec_id != AV_CODEC_ID_NONE; } std::vector BuildStats(uint64_t sent, uint64_t dropped) { std::vector out(16); put_u64(out.data(), sent); put_u64(out.data() + 8, dropped); return out; } bool ParseStats(const uint8_t *data, size_t len, uint64_t &sent, uint64_t &dropped) { if (len < 16) return false; sent = get_u64(data); dropped = get_u64(data + 8); return true; } std::vector BuildEvent(const MonitorEvent &ev) { std::vector out; out.reserve(16 + ev.message.size() + ev.state_name.size()); uint8_t code_le[2]; put_u16(code_le, ev.code); out.insert(out.end(), code_le, code_le + 2); if (ev.has_wall_clock) append_tlv_u64(out, kTlvWallClockUs, ev.wall_clock_us); if (!ev.message.empty()) append_tlv_str(out, kTlvMessage, ev.message); if (ev.has_state_id) append_tlv_u32(out, kTlvStateId, ev.state_id); if (ev.has_prev_state_id) append_tlv_u32(out, kTlvPrevStateId, ev.prev_state_id); if (ev.has_detail) append_tlv_u32(out, kTlvDetail, ev.detail); if (!ev.state_name.empty()) append_tlv_str(out, kTlvStateName, ev.state_name); if (ev.has_health_code) { uint8_t code_le[2]; put_u16(code_le, ev.health_code); append_tlv(out, kTlvHealthCode, code_le, sizeof(code_le)); } return out; } bool ParseEvent(const uint8_t *data, size_t len, MonitorEvent &ev) { ev = MonitorEvent(); if (len < 2) return false; ev.code = get_u16(data); size_t pos = 2; while (pos < len) { if (len - pos < 3) return false; uint8_t tag = data[pos]; uint16_t value_len = get_u16(data + pos + 1); pos += 3; if (len - pos < value_len) return false; const uint8_t *value = data + pos; pos += value_len; switch (tag) { case kTlvWallClockUs: if (value_len != 8) return false; ev.wall_clock_us = get_u64(value); ev.has_wall_clock = true; break; case kTlvMessage: ev.message.assign(reinterpret_cast(value), value_len); break; case kTlvStateId: if (value_len != 4) return false; ev.state_id = get_u32(value); ev.has_state_id = true; break; case kTlvPrevStateId: if (value_len != 4) return false; ev.prev_state_id = get_u32(value); ev.has_prev_state_id = true; break; case kTlvDetail: if (value_len != 4) return false; ev.detail = get_u32(value); ev.has_detail = true; break; case kTlvStateName: ev.state_name.assign(reinterpret_cast(value), value_len); break; case kTlvHealthCode: if (value_len != 2) return false; ev.health_code = get_u16(value); ev.has_health_code = true; break; default: break; // unknown tag: skip } } return true; } } // namespace stream_socket } // namespace zm