| /* |
| * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "media/base/rtp_utils.h" |
| |
| #include <string.h> |
| |
| #include <vector> |
| |
| // PacketTimeUpdateParams is defined in asyncpacketsocket.h. |
| // TODO(sergeyu): Find more appropriate place for PacketTimeUpdateParams. |
| #include "media/base/turn_utils.h" |
| #include "rtc_base/async_packet_socket.h" |
| #include "rtc_base/byte_order.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/message_digest.h" |
| |
| namespace cricket { |
| |
| static const uint8_t kRtpVersion = 2; |
| static const size_t kRtpFlagsOffset = 0; |
| static const size_t kRtpPayloadTypeOffset = 1; |
| static const size_t kRtpSeqNumOffset = 2; |
| static const size_t kRtpTimestampOffset = 4; |
| static const size_t kRtpSsrcOffset = 8; |
| static const size_t kRtcpPayloadTypeOffset = 1; |
| static const size_t kRtpExtensionHeaderLen = 4; |
| static const size_t kAbsSendTimeExtensionLen = 3; |
| static const size_t kOneByteExtensionHeaderLen = 1; |
| static const size_t kTwoByteExtensionHeaderLen = 2; |
| |
| namespace { |
| |
| // Fake auth tag written by the sender when external authentication is enabled. |
| // HMAC in packet will be compared against this value before updating packet |
| // with actual HMAC value. |
| static const uint8_t kFakeAuthTag[10] = {0xba, 0xdd, 0xba, 0xdd, 0xba, |
| 0xdd, 0xba, 0xdd, 0xba, 0xdd}; |
| |
| void UpdateAbsSendTimeExtensionValue(uint8_t* extension_data, |
| size_t length, |
| uint64_t time_us) { |
| // Absolute send time in RTP streams. |
| // |
| // The absolute send time is signaled to the receiver in-band using the |
| // general mechanism for RTP header extensions [RFC5285]. The payload |
| // of this extension (the transmitted value) is a 24-bit unsigned integer |
| // containing the sender's current time in seconds as a fixed point number |
| // with 18 bits fractional part. |
| // |
| // The form of the absolute send time extension block: |
| // |
| // 0 1 2 3 |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | ID | len=2 | absolute send time | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| if (length != kAbsSendTimeExtensionLen) { |
| RTC_NOTREACHED(); |
| return; |
| } |
| |
| // Convert microseconds to a 6.18 fixed point value in seconds. |
| uint32_t send_time = ((time_us << 18) / 1000000) & 0x00FFFFFF; |
| extension_data[0] = static_cast<uint8_t>(send_time >> 16); |
| extension_data[1] = static_cast<uint8_t>(send_time >> 8); |
| extension_data[2] = static_cast<uint8_t>(send_time); |
| } |
| |
| // Assumes |length| is actual packet length + tag length. Updates HMAC at end of |
| // the RTP packet. |
| void UpdateRtpAuthTag(uint8_t* rtp, |
| size_t length, |
| const rtc::PacketTimeUpdateParams& packet_time_params) { |
| // If there is no key, return. |
| if (packet_time_params.srtp_auth_key.empty()) { |
| return; |
| } |
| |
| size_t tag_length = packet_time_params.srtp_auth_tag_len; |
| |
| // ROC (rollover counter) is at the beginning of the auth tag. |
| const size_t kRocLength = 4; |
| if (tag_length < kRocLength || tag_length > length) { |
| RTC_NOTREACHED(); |
| return; |
| } |
| |
| uint8_t* auth_tag = rtp + (length - tag_length); |
| |
| // We should have a fake HMAC value @ auth_tag. |
| RTC_DCHECK_EQ(0, memcmp(auth_tag, kFakeAuthTag, tag_length)); |
| |
| // Copy ROC after end of rtp packet. |
| memcpy(auth_tag, &packet_time_params.srtp_packet_index, kRocLength); |
| // Authentication of a RTP packet will have RTP packet + ROC size. |
| size_t auth_required_length = length - tag_length + kRocLength; |
| |
| uint8_t output[64]; |
| size_t result = |
| rtc::ComputeHmac(rtc::DIGEST_SHA_1, &packet_time_params.srtp_auth_key[0], |
| packet_time_params.srtp_auth_key.size(), rtp, |
| auth_required_length, output, sizeof(output)); |
| |
| if (result < tag_length) { |
| RTC_NOTREACHED(); |
| return; |
| } |
| |
| // Copy HMAC from output to packet. This is required as auth tag length |
| // may not be equal to the actual HMAC length. |
| memcpy(auth_tag, output, tag_length); |
| } |
| |
| } // namespace |
| |
| bool GetUint8(const void* data, size_t offset, int* value) { |
| if (!data || !value) { |
| return false; |
| } |
| *value = *(static_cast<const uint8_t*>(data) + offset); |
| return true; |
| } |
| |
| bool GetUint16(const void* data, size_t offset, int* value) { |
| if (!data || !value) { |
| return false; |
| } |
| *value = static_cast<int>( |
| rtc::GetBE16(static_cast<const uint8_t*>(data) + offset)); |
| return true; |
| } |
| |
| bool GetUint32(const void* data, size_t offset, uint32_t* value) { |
| if (!data || !value) { |
| return false; |
| } |
| *value = rtc::GetBE32(static_cast<const uint8_t*>(data) + offset); |
| return true; |
| } |
| |
| bool SetUint8(void* data, size_t offset, uint8_t value) { |
| if (!data) { |
| return false; |
| } |
| rtc::Set8(data, offset, value); |
| return true; |
| } |
| |
| bool SetUint16(void* data, size_t offset, uint16_t value) { |
| if (!data) { |
| return false; |
| } |
| rtc::SetBE16(static_cast<uint8_t*>(data) + offset, value); |
| return true; |
| } |
| |
| bool SetUint32(void* data, size_t offset, uint32_t value) { |
| if (!data) { |
| return false; |
| } |
| rtc::SetBE32(static_cast<uint8_t*>(data) + offset, value); |
| return true; |
| } |
| |
| bool GetRtpFlags(const void* data, size_t len, int* value) { |
| if (len < kMinRtpPacketLen) { |
| return false; |
| } |
| return GetUint8(data, kRtpFlagsOffset, value); |
| } |
| |
| bool GetRtpPayloadType(const void* data, size_t len, int* value) { |
| if (len < kMinRtpPacketLen) { |
| return false; |
| } |
| if (!GetUint8(data, kRtpPayloadTypeOffset, value)) { |
| return false; |
| } |
| *value &= 0x7F; |
| return true; |
| } |
| |
| bool GetRtpSeqNum(const void* data, size_t len, int* value) { |
| if (len < kMinRtpPacketLen) { |
| return false; |
| } |
| return GetUint16(data, kRtpSeqNumOffset, value); |
| } |
| |
| bool GetRtpTimestamp(const void* data, size_t len, uint32_t* value) { |
| if (len < kMinRtpPacketLen) { |
| return false; |
| } |
| return GetUint32(data, kRtpTimestampOffset, value); |
| } |
| |
| bool GetRtpSsrc(const void* data, size_t len, uint32_t* value) { |
| if (len < kMinRtpPacketLen) { |
| return false; |
| } |
| return GetUint32(data, kRtpSsrcOffset, value); |
| } |
| |
| bool GetRtpHeaderLen(const void* data, size_t len, size_t* value) { |
| if (!data || len < kMinRtpPacketLen || !value) |
| return false; |
| const uint8_t* header = static_cast<const uint8_t*>(data); |
| // Get base header size + length of CSRCs (not counting extension yet). |
| size_t header_size = kMinRtpPacketLen + (header[0] & 0xF) * sizeof(uint32_t); |
| if (len < header_size) |
| return false; |
| // If there's an extension, read and add in the extension size. |
| if (header[0] & 0x10) { |
| if (len < header_size + sizeof(uint32_t)) |
| return false; |
| header_size += |
| ((rtc::GetBE16(header + header_size + 2) + 1) * sizeof(uint32_t)); |
| if (len < header_size) |
| return false; |
| } |
| *value = header_size; |
| return true; |
| } |
| |
| bool GetRtpHeader(const void* data, size_t len, RtpHeader* header) { |
| return (GetRtpPayloadType(data, len, &(header->payload_type)) && |
| GetRtpSeqNum(data, len, &(header->seq_num)) && |
| GetRtpTimestamp(data, len, &(header->timestamp)) && |
| GetRtpSsrc(data, len, &(header->ssrc))); |
| } |
| |
| bool GetRtcpType(const void* data, size_t len, int* value) { |
| if (len < kMinRtcpPacketLen) { |
| return false; |
| } |
| return GetUint8(data, kRtcpPayloadTypeOffset, value); |
| } |
| |
| // This method returns SSRC first of RTCP packet, except if packet is SDES. |
| // TODO(mallinath) - Fully implement RFC 5506. This standard doesn't restrict |
| // to send non-compound packets only to feedback messages. |
| bool GetRtcpSsrc(const void* data, size_t len, uint32_t* value) { |
| // Packet should be at least of 8 bytes, to get SSRC from a RTCP packet. |
| if (!data || len < kMinRtcpPacketLen + 4 || !value) |
| return false; |
| int pl_type; |
| if (!GetRtcpType(data, len, &pl_type)) |
| return false; |
| // SDES packet parsing is not supported. |
| if (pl_type == kRtcpTypeSDES) |
| return false; |
| *value = rtc::GetBE32(static_cast<const uint8_t*>(data) + 4); |
| return true; |
| } |
| |
| bool SetRtpSsrc(void* data, size_t len, uint32_t value) { |
| return SetUint32(data, kRtpSsrcOffset, value); |
| } |
| |
| // Assumes version 2, no padding, no extensions, no csrcs. |
| bool SetRtpHeader(void* data, size_t len, const RtpHeader& header) { |
| if (!IsValidRtpPayloadType(header.payload_type) || header.seq_num < 0 || |
| header.seq_num > static_cast<int>(UINT16_MAX)) { |
| return false; |
| } |
| return (SetUint8(data, kRtpFlagsOffset, kRtpVersion << 6) && |
| SetUint8(data, kRtpPayloadTypeOffset, header.payload_type & 0x7F) && |
| SetUint16(data, kRtpSeqNumOffset, |
| static_cast<uint16_t>(header.seq_num)) && |
| SetUint32(data, kRtpTimestampOffset, header.timestamp) && |
| SetRtpSsrc(data, len, header.ssrc)); |
| } |
| |
| static bool HasCorrectRtpVersion(rtc::ArrayView<const uint8_t> packet) { |
| return packet.data()[0] >> 6 == kRtpVersion; |
| } |
| |
| bool IsRtpPacket(rtc::ArrayView<const char> packet) { |
| return packet.size() >= kMinRtpPacketLen && |
| HasCorrectRtpVersion( |
| rtc::reinterpret_array_view<const uint8_t>(packet)); |
| } |
| |
| // Check the RTP payload type. If 63 < payload type < 96, it's RTCP. |
| // For additional details, see http://tools.ietf.org/html/rfc5761. |
| bool IsRtcpPacket(rtc::ArrayView<const char> packet) { |
| if (packet.size() < kMinRtcpPacketLen || |
| !HasCorrectRtpVersion( |
| rtc::reinterpret_array_view<const uint8_t>(packet))) { |
| return false; |
| } |
| |
| char pt = packet[1] & 0x7F; |
| return (63 < pt) && (pt < 96); |
| } |
| |
| bool IsValidRtpPayloadType(int payload_type) { |
| return payload_type >= 0 && payload_type <= 127; |
| } |
| |
| bool IsValidRtpPacketSize(RtpPacketType packet_type, size_t size) { |
| RTC_DCHECK_NE(RtpPacketType::kUnknown, packet_type); |
| size_t min_packet_length = packet_type == RtpPacketType::kRtcp |
| ? kMinRtcpPacketLen |
| : kMinRtpPacketLen; |
| return size >= min_packet_length && size <= kMaxRtpPacketLen; |
| } |
| |
| absl::string_view RtpPacketTypeToString(RtpPacketType packet_type) { |
| switch (packet_type) { |
| case RtpPacketType::kRtp: |
| return "RTP"; |
| case RtpPacketType::kRtcp: |
| return "RTCP"; |
| case RtpPacketType::kUnknown: |
| return "Unknown"; |
| } |
| } |
| |
| RtpPacketType InferRtpPacketType(rtc::ArrayView<const char> packet) { |
| // RTCP packets are RTP packets so must check that first. |
| if (IsRtcpPacket(packet)) { |
| return RtpPacketType::kRtcp; |
| } |
| if (IsRtpPacket(packet)) { |
| return RtpPacketType::kRtp; |
| } |
| return RtpPacketType::kUnknown; |
| } |
| |
| bool ValidateRtpHeader(const uint8_t* rtp, |
| size_t length, |
| size_t* header_length) { |
| if (header_length) { |
| *header_length = 0; |
| } |
| |
| if (length < kMinRtpPacketLen) { |
| return false; |
| } |
| |
| size_t cc_count = rtp[0] & 0x0F; |
| size_t header_length_without_extension = kMinRtpPacketLen + 4 * cc_count; |
| if (header_length_without_extension > length) { |
| return false; |
| } |
| |
| // If extension bit is not set, we are done with header processing, as input |
| // length is verified above. |
| if (!(rtp[0] & 0x10)) { |
| if (header_length) |
| *header_length = header_length_without_extension; |
| |
| return true; |
| } |
| |
| rtp += header_length_without_extension; |
| |
| if (header_length_without_extension + kRtpExtensionHeaderLen > length) { |
| return false; |
| } |
| |
| // Getting extension profile length. |
| // Length is in 32 bit words. |
| uint16_t extension_length_in_32bits = rtc::GetBE16(rtp + 2); |
| size_t extension_length = extension_length_in_32bits * 4; |
| |
| size_t rtp_header_length = extension_length + |
| header_length_without_extension + |
| kRtpExtensionHeaderLen; |
| |
| // Verify input length against total header size. |
| if (rtp_header_length > length) { |
| return false; |
| } |
| |
| if (header_length) { |
| *header_length = rtp_header_length; |
| } |
| return true; |
| } |
| |
| // ValidateRtpHeader() must be called before this method to make sure, we have |
| // a sane rtp packet. |
| bool UpdateRtpAbsSendTimeExtension(uint8_t* rtp, |
| size_t length, |
| int extension_id, |
| uint64_t time_us) { |
| // 0 1 2 3 |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // |V=2|P|X| CC |M| PT | sequence number | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | timestamp | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | synchronization source (SSRC) identifier | |
| // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ |
| // | contributing source (CSRC) identifiers | |
| // | .... | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| // Return if extension bit is not set. |
| if (!(rtp[0] & 0x10)) { |
| return true; |
| } |
| |
| size_t cc_count = rtp[0] & 0x0F; |
| size_t header_length_without_extension = kMinRtpPacketLen + 4 * cc_count; |
| |
| rtp += header_length_without_extension; |
| |
| // Getting extension profile ID and length. |
| uint16_t profile_id = rtc::GetBE16(rtp); |
| // Length is in 32 bit words. |
| uint16_t extension_length_in_32bits = rtc::GetBE16(rtp + 2); |
| size_t extension_length = extension_length_in_32bits * 4; |
| |
| rtp += kRtpExtensionHeaderLen; // Moving past extension header. |
| |
| constexpr uint16_t kOneByteExtensionProfileId = 0xBEDE; |
| constexpr uint16_t kTwoByteExtensionProfileId = 0x1000; |
| |
| bool found = false; |
| if (profile_id == kOneByteExtensionProfileId || |
| profile_id == kTwoByteExtensionProfileId) { |
| // OneByte extension header |
| // 0 |
| // 0 1 2 3 4 5 6 7 |
| // +-+-+-+-+-+-+-+-+ |
| // | ID |length | |
| // +-+-+-+-+-+-+-+-+ |
| |
| // 0 1 2 3 |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | 0xBE | 0xDE | length=3 | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | ID | L=0 | data | ID | L=1 | data... |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // ...data | 0 (pad) | 0 (pad) | ID | L=3 | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | data | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| // TwoByte extension header |
| // 0 |
| // 0 1 2 3 4 5 6 7 |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | ID | length | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| // 0 1 2 3 |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | 0x10 | 0x00 | length=3 | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | ID | L=1 | data | ID | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | L=2 | data | 0 (pad) | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | ID | L=2 | data | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| size_t extension_header_length = profile_id == kOneByteExtensionProfileId |
| ? kOneByteExtensionHeaderLen |
| : kTwoByteExtensionHeaderLen; |
| |
| const uint8_t* extension_start = rtp; |
| const uint8_t* extension_end = extension_start + extension_length; |
| |
| // rtp + 1 since the minimum size per header extension is two bytes for both |
| // one- and two-byte header extensions. |
| while (rtp + 1 < extension_end) { |
| // See RFC8285 Section 4.2-4.3 for more information about one- and |
| // two-byte header extensions. |
| const int id = |
| profile_id == kOneByteExtensionProfileId ? (*rtp & 0xF0) >> 4 : *rtp; |
| const size_t length = profile_id == kOneByteExtensionProfileId |
| ? (*rtp & 0x0F) + 1 |
| : *(rtp + 1); |
| if (rtp + extension_header_length + length > extension_end) { |
| return false; |
| } |
| if (id == extension_id) { |
| UpdateAbsSendTimeExtensionValue(rtp + extension_header_length, length, |
| time_us); |
| found = true; |
| break; |
| } |
| rtp += extension_header_length + length; |
| // Counting padding bytes. |
| while ((rtp < extension_end) && (*rtp == 0)) { |
| ++rtp; |
| } |
| } |
| } |
| return found; |
| } |
| |
| bool ApplyPacketOptions(uint8_t* data, |
| size_t length, |
| const rtc::PacketTimeUpdateParams& packet_time_params, |
| uint64_t time_us) { |
| RTC_DCHECK(data); |
| RTC_DCHECK(length); |
| |
| // if there is no valid |rtp_sendtime_extension_id| and |srtp_auth_key| in |
| // PacketOptions, nothing to be updated in this packet. |
| if (packet_time_params.rtp_sendtime_extension_id == -1 && |
| packet_time_params.srtp_auth_key.empty()) { |
| return true; |
| } |
| |
| // If there is a srtp auth key present then the packet must be an RTP packet. |
| // RTP packet may have been wrapped in a TURN Channel Data or TURN send |
| // indication. |
| size_t rtp_start_pos; |
| size_t rtp_length; |
| if (!UnwrapTurnPacket(data, length, &rtp_start_pos, &rtp_length)) { |
| RTC_NOTREACHED(); |
| return false; |
| } |
| |
| // Making sure we have a valid RTP packet at the end. |
| auto packet = rtc::MakeArrayView(data + rtp_start_pos, rtp_length); |
| if (!IsRtpPacket(rtc::reinterpret_array_view<const char>(packet)) || |
| !ValidateRtpHeader(data + rtp_start_pos, rtp_length, nullptr)) { |
| RTC_NOTREACHED(); |
| return false; |
| } |
| |
| uint8_t* start = data + rtp_start_pos; |
| // If packet option has non default value (-1) for sendtime extension id, |
| // then we should parse the rtp packet to update the timestamp. Otherwise |
| // just calculate HMAC and update packet with it. |
| if (packet_time_params.rtp_sendtime_extension_id != -1) { |
| UpdateRtpAbsSendTimeExtension(start, rtp_length, |
| packet_time_params.rtp_sendtime_extension_id, |
| time_us); |
| } |
| |
| UpdateRtpAuthTag(start, rtp_length, packet_time_params); |
| return true; |
| } |
| |
| } // namespace cricket |