session-ios/Signal/src/network/rtp/RtpPacket.m

370 lines
16 KiB
Objective-C

#import "RtpPacket.h"
#import "Util.h"
#import "Constraints.h"
#import "FunctionalUtil.h"
#import "Environment.h"
const uint8_t PACKET_VERSION = 2;
#define HAS_EXTENSION_HEADER_BIT_INDEX 4
#define HAS_PADDING_BIT_INDEX 5
#define VERSION_INDEX 6
#define MARKER_BIT_INDEX 7
#define EXTENSION_HEADER_IDENTIFIER_LENGTH 2
#define EXTENSION_HEADER_LENGTH_LENGTH 2
#define MINIMUM_RTP_HEADER_LENGTH 12
#define SEQUENCE_NUMBER_OFFSET 2
#define TIME_STAMP_OFFSET 4
#define SYNCHRONIZATION_SOURCE_IDENTIFIER_OFFSET 8
#define CONTRIBUTING_SOURCE_ID_LENGTH 4
#define VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET 0
#define PAYLOAD_TYPE_AND_MARKER_BIT_FLAG_BYTE_OFFSET 1
#define SUPPORTED_RTP_VERSION 2
#define RTP_VERSION_FOR_ZRTP 0
#define PAYLOAD_TYPE_MASK 0x7F
#define MAX_EXTENSION_HEADER_LENGTH (0x10000 - 0x1)
@implementation RtpPacket
@synthesize payload;
@synthesize contributingSourceIdentifiers;
@synthesize hasExtensionHeader;
@synthesize isMarkerBitSet;
@synthesize padding;
@synthesize payloadType;
@synthesize sequenceNumber;
@synthesize synchronizationSourceIdentifier;
@synthesize timeStamp;
@synthesize version;
@synthesize wasAdjustedDueToInteropIssues;
+(RtpPacket*) rtpPacketWithDefaultsAndSequenceNumber:(uint16_t)sequenceNumber andPayload:(NSData *)payload {
require(payload != nil);
return [RtpPacket rtpPacketWithVersion:PACKET_VERSION
andPadding:0
andContributingSourceIdentifiers:@[]
andSynchronizationSourceIdentifier:0
andMarkerBit:false
andPayloadtype:0
andSequenceNumber:sequenceNumber
andTimeStamp:0
andPayload:payload];
}
+(RtpPacket*) rtpPacketWithVersion:(uint8_t)version
andPadding:(uint8_t)padding
andContributingSourceIdentifiers:(NSArray*)contributingSourceIdentifiers
andSynchronizationSourceIdentifier:(uint32_t)synchronizedSourceIdentifier
andExtensionIdentifier:(uint16_t)extensionHeaderIdentifier
andExtensionData:(NSData*)extensionData
andMarkerBit:(bool)isMarkerBitSet
andPayloadtype:(uint8_t)payloadType
andSequenceNumber:(uint16_t)sequenceNumber
andTimeStamp:(uint32_t)timeStamp
andPayload:(NSData*)payload {
require((version & ~0x3) == 0);
require((payloadType & ~0x7F) == 0);
require(extensionData != nil);
require([extensionData length] < 0x10000);
require(contributingSourceIdentifiers != nil);
require([contributingSourceIdentifiers count] < 0x10);
require(payload != nil);
RtpPacket* p = [RtpPacket new];
p->version = version;
p->isMarkerBitSet = isMarkerBitSet;
p->hasExtensionHeader = true;
p->extensionHeaderIdentifier = extensionHeaderIdentifier;
p->extensionHeaderData = extensionData;
p->padding = padding;
p->contributingSourceIdentifiers = contributingSourceIdentifiers;
p->payloadType = payloadType;
p->synchronizationSourceIdentifier = synchronizedSourceIdentifier;
p->timeStamp = timeStamp;
p->sequenceNumber = sequenceNumber;
p->payload = payload;
return p;
}
+(RtpPacket*) rtpPacketWithVersion:(uint8_t)version
andPadding:(uint8_t)padding
andContributingSourceIdentifiers:(NSArray*)contributingSourceIdentifiers
andSynchronizationSourceIdentifier:(uint32_t)synchronizedSourceIdentifier
andMarkerBit:(bool)isMarkerBitSet
andPayloadtype:(uint8_t)payloadType
andSequenceNumber:(uint16_t)sequenceNumber
andTimeStamp:(uint32_t)timeStamp
andPayload:(NSData*)payload {
require((version & ~0x3) == 0);
require((payloadType & ~0x7F) == 0);
require(contributingSourceIdentifiers != nil);
require([contributingSourceIdentifiers count] < 0x10);
require(payload != nil);
RtpPacket* p = [RtpPacket new];
p->version = version;
p->isMarkerBitSet = isMarkerBitSet;
p->padding = padding;
p->contributingSourceIdentifiers = contributingSourceIdentifiers;
p->payloadType = payloadType;
p->synchronizationSourceIdentifier = synchronizedSourceIdentifier;
p->timeStamp = timeStamp;
p->sequenceNumber = sequenceNumber;
p->payload = payload;
return p;
}
-(RtpPacket*) withPayload:(NSData*)newPayload {
if (hasExtensionHeader) {
return [RtpPacket rtpPacketWithVersion:version
andPadding:padding
andContributingSourceIdentifiers:contributingSourceIdentifiers
andSynchronizationSourceIdentifier:synchronizationSourceIdentifier
andExtensionIdentifier:extensionHeaderIdentifier
andExtensionData:extensionHeaderData
andMarkerBit:isMarkerBitSet
andPayloadtype:payloadType
andSequenceNumber:sequenceNumber
andTimeStamp:timeStamp
andPayload:newPayload];
}
return [RtpPacket rtpPacketWithVersion:version
andPadding:padding
andContributingSourceIdentifiers:contributingSourceIdentifiers
andSynchronizationSourceIdentifier:synchronizationSourceIdentifier
andMarkerBit:isMarkerBitSet
andPayloadtype:payloadType
andSequenceNumber:sequenceNumber
andTimeStamp:timeStamp
andPayload:newPayload];
}
+(uint16_t) getSequenceNumberFromPacketData:(NSData*)packetData {
return [packetData bigEndianUInt16At:SEQUENCE_NUMBER_OFFSET];
}
+(uint32_t) getTimeStampFromPacketData:(NSData*)packetData {
return [packetData bigEndianUInt32At:TIME_STAMP_OFFSET];
}
+(uint32_t) getSynchronizationSourceIdentifierFromPacketData:(NSData*)packetData {
return [packetData bigEndianUInt32At:SYNCHRONIZATION_SOURCE_IDENTIFIER_OFFSET];
}
-(void) readContributingSourcesFromPacketData:(NSData*)packetData trackingOffset:(NSUInteger*)offset andMinSize:(NSUInteger*)minSize {
uint8_t contributingSourceCount = [NumberUtil lowUInt4OfUint8:[packetData uint8At:VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET]];
*minSize += contributingSourceCount * CONTRIBUTING_SOURCE_ID_LENGTH;
checkOperationDescribe([packetData length] >= *minSize, @"Rtp packet ends before header finished.");
NSMutableArray* contributingSources = [NSMutableArray array];
for (NSUInteger i = 0; i < contributingSourceCount; i++) {
uint32_t ccsrc = [packetData bigEndianUInt32At:*offset];
[contributingSources addObject:@(ccsrc)];
*offset += CONTRIBUTING_SOURCE_ID_LENGTH;
}
contributingSourceIdentifiers = contributingSources;
}
-(void) readExtensionHeaderFromPacketData:(NSData*)packetData trackingOffset:(NSUInteger*)offset andMinSize:(NSUInteger*)minSize {
hasExtensionHeader = (([packetData uint8At:VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET] >> HAS_EXTENSION_HEADER_BIT_INDEX) & 1) != 0;
// legacy compatibility with android redphone
if ([Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) {
bool hasPadding = (([packetData uint8At:0] >> HAS_PADDING_BIT_INDEX) & 1) != 0;
if (hasPadding) {
wasAdjustedDueToInteropIssues = true;
*offset += 12;
hasExtensionHeader = true;
}
}
if (!hasExtensionHeader) return;
*minSize += EXTENSION_HEADER_IDENTIFIER_LENGTH + EXTENSION_HEADER_LENGTH_LENGTH;
checkOperationDescribe([packetData length] >= *minSize, @"Rtp packet ends before header of extension header finished.");
extensionHeaderIdentifier = [packetData bigEndianUInt16At:*offset];
*offset += EXTENSION_HEADER_IDENTIFIER_LENGTH;
uint16_t extensionLength = [packetData bigEndianUInt16At:*offset];
*offset += EXTENSION_HEADER_LENGTH_LENGTH;
*minSize += extensionLength;
checkOperationDescribe([packetData length] >= *minSize, @"Rtp packet ends before payload of extension header finished.");
extensionHeaderData = [packetData subdataWithRange:NSMakeRange(*offset, extensionLength)];
*offset += extensionLength;
}
-(void) readPaddingFromPacketData:(NSData*)packetData andMinSize:(NSUInteger*)minSize {
bool hasPadding = (([packetData uint8At:0] >> HAS_PADDING_BIT_INDEX) & 1) != 0;
// legacy compatibility with android redphone
if (hasPadding) {
if ([Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) {
wasAdjustedDueToInteropIssues = true;
hasPadding = false;
}
}
if (!hasPadding) return;
padding = [packetData uint8At:[packetData length] - 1];
checkOperationDescribe(padding > 0, @"Padding length must be at least 1 because it includes the suffix byte specifying the length.");
*minSize += padding;
checkOperationDescribe([packetData length] >= *minSize, @"Rtp packet overlaps header and padding.");
}
+(RtpPacket*) rtpPacketParsedFromPacketData:(NSData*)packetData {
require(packetData != nil);
NSUInteger minSize = MINIMUM_RTP_HEADER_LENGTH;
checkOperationDescribe([packetData length] >= minSize, @"Rtp packet ends before header finished.");
RtpPacket* p = [RtpPacket new];
p->rawPacketData = packetData;
p->version = [packetData uint8At:VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET] >> VERSION_INDEX;
checkOperation(p->version == SUPPORTED_RTP_VERSION || p->version == RTP_VERSION_FOR_ZRTP);
p->isMarkerBitSet = (([packetData uint8At:PAYLOAD_TYPE_AND_MARKER_BIT_FLAG_BYTE_OFFSET] >> MARKER_BIT_INDEX) & 1) != 0;;
p->payloadType = [packetData uint8At:PAYLOAD_TYPE_AND_MARKER_BIT_FLAG_BYTE_OFFSET] & PAYLOAD_TYPE_MASK;
NSUInteger offset = MINIMUM_RTP_HEADER_LENGTH;
[p readContributingSourcesFromPacketData:packetData trackingOffset:&offset andMinSize:&minSize];
[p readExtensionHeaderFromPacketData:packetData trackingOffset:&offset andMinSize:&minSize];
[p readPaddingFromPacketData:packetData andMinSize:&minSize];
p->payload = [packetData subdataWithRange:NSMakeRange(offset, [packetData length] - p->padding - offset)];
p->sequenceNumber = [self getSequenceNumberFromPacketData:packetData];
p->timeStamp = [self getTimeStampFromPacketData:packetData];
p->synchronizationSourceIdentifier = [self getSynchronizationSourceIdentifierFromPacketData:packetData];
return p;
}
-(NSData*) generateFlags {
requireState((version & ~0x3) == 0);
requireState((payloadType & ~0x7F) == 0);
requireState([contributingSourceIdentifiers count] < 0x10);
NSMutableData* flags = [NSMutableData dataWithLength:2];
uint8_t versionMask = (uint8_t)(version << VERSION_INDEX);
uint8_t paddingBit = padding > 0 ? (uint8_t)(1<<HAS_PADDING_BIT_INDEX) : 0;
uint8_t extensionBit = hasExtensionHeader ? (uint8_t)(1<<HAS_EXTENSION_HEADER_BIT_INDEX) : 0;
uint8_t ccsrcCount = (uint8_t)[contributingSourceIdentifiers count];
[flags setUint8At:VERSION_AND_PADDING_AND_EXTENSION_AND_CCSRC_FLAG_BYTE_OFFSET
to:versionMask | paddingBit | extensionBit | ccsrcCount];
uint8_t markerMask = isMarkerBitSet ? (uint8_t)(1 << MARKER_BIT_INDEX) : 0;
[flags setUint8At:PAYLOAD_TYPE_AND_MARKER_BIT_FLAG_BYTE_OFFSET
to:markerMask | payloadType];
return flags;
}
-(NSData*) generateCcrcData {
return [[contributingSourceIdentifiers map:^id(NSNumber* ccsrc) {
return [NSData dataWithBigEndianBytesOfUInt32:[ccsrc unsignedIntValue]];
}] concatDatas];
}
-(NSData*) generateExtensionHeaderData {
if (!hasExtensionHeader) return [NSData data];
return [@[
[NSData dataWithBigEndianBytesOfUInt16:extensionHeaderIdentifier],
[NSData dataWithBigEndianBytesOfUInt16:(uint16_t)[extensionHeaderData length]],
extensionHeaderData
] concatDatas];
}
-(NSData*) generatePaddingData {
NSMutableData* paddingData = [NSMutableData dataWithLength:padding];
if (padding > 0) {
[paddingData setUint8At:[paddingData length] - 1 to:padding];
}
return paddingData;
}
-(NSData*) generateSerializedPacketDataUsingInteropOptions:(NSArray*)interopOptions {
requireState(hasExtensionHeader == (extensionHeaderData != nil));
requireState(extensionHeaderData == nil || [extensionHeaderData length] <= MAX_EXTENSION_HEADER_LENGTH);
NSData* shouldBeEmpty = [NSData data];
// legacy compatibility with android redphone
if ([Environment hasEnabledTestingOrLegacyOption:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) {
if ([interopOptions containsObject:ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]) {
if (hasExtensionHeader) {
shouldBeEmpty = [NSData dataWithLength:12];
}
}
}
return [@[
[self generateFlags],
[NSData dataWithBigEndianBytesOfUInt16:sequenceNumber],
[NSData dataWithBigEndianBytesOfUInt32:timeStamp],
[NSData dataWithBigEndianBytesOfUInt32:synchronizationSourceIdentifier],
[self generateCcrcData],
shouldBeEmpty,
[self generateExtensionHeaderData],
payload,
[self generatePaddingData]
] concatDatas];
}
-(RtpPacket*) withSequenceNumber:(uint16_t)newSequenceNumber {
RtpPacket* p = [RtpPacket new];
p->version = version;
p->padding = padding;
p->hasExtensionHeader = hasExtensionHeader;
p->contributingSourceIdentifiers = contributingSourceIdentifiers;
p->isMarkerBitSet = isMarkerBitSet;
p->payloadType = payloadType;
p->timeStamp = timeStamp;
p->synchronizationSourceIdentifier = synchronizationSourceIdentifier;
p->extensionHeaderIdentifier = extensionHeaderIdentifier;
p->extensionHeaderData = extensionHeaderData;
p->payload = payload;
p->sequenceNumber = newSequenceNumber;
return p;
}
-(uint16_t) extensionHeaderIdentifier {
requireState(hasExtensionHeader);
return extensionHeaderIdentifier;
}
-(NSData*) extensionHeaderData {
requireState(hasExtensionHeader);
return extensionHeaderData;
}
-(NSData*) rawPacketDataUsingInteropOptions:(NSArray*)interopOptions {
if (rawPacketData == nil) rawPacketData = [self generateSerializedPacketDataUsingInteropOptions:interopOptions];
return rawPacketData;
}
-(bool) isEqualToRtpPacket:(RtpPacket*)other {
if (other == nil) return false;
if (version != [other version]) return false;
if (padding != [other padding]) return false;
if (hasExtensionHeader != [other hasExtensionHeader]) return false;
if (isMarkerBitSet != [other isMarkerBitSet]) return false;
if (payloadType != [other payloadType]) return false;
if (timeStamp != [other timeStamp]) return false;
if (synchronizationSourceIdentifier != [other synchronizationSourceIdentifier]) return false;
if (sequenceNumber != [other sequenceNumber]) return false;
if (![payload isEqualToData:[other payload]]) return false;
if (![contributingSourceIdentifiers isEqualToArray:[other contributingSourceIdentifiers]]) return false;
if (hasExtensionHeader) {
if (extensionHeaderIdentifier != [other extensionHeaderIdentifier]) return false;
if (![extensionHeaderData isEqualToData:[other extensionHeaderData]]) return false;
}
if (![[self rawPacketDataUsingInteropOptions:@[]] isEqualToData:[other rawPacketDataUsingInteropOptions:@[]]]) return false;
return true;
}
@end