rtp.js
Version:
RTP stack for Node.js and browser written in TypeScript
518 lines (517 loc) • 18.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const SdesPacket_1 = require("../../../packets/RTCP/SdesPacket");
const RtcpPacket_1 = require("../../../packets/RTCP/RtcpPacket");
const helpers_1 = require("../../../utils/helpers");
const sdesPacketDump = {
byteLength: 28,
padding: 0,
packetType: RtcpPacket_1.RtcpPacketType.SDES,
count: 1,
chunks: [
{
byteLength: 24,
ssrc: 0x9f65e742,
items: [{ type: SdesPacket_1.SdesItemType.CNAME, text: 't7mkYnCm46OcINy/' }],
},
],
};
const array = new Uint8Array([
0x81,
0xca,
0x00,
0x06, // Type: 202 (SDES), Count: 1, Length: 6
// Chunk
0x9f,
0x65,
0xe7,
0x42, // SSRC: 0x9f65e742
0x01,
0x10,
0x74,
0x37, // Item Type: 1 (CNAME), Length: 16,
0x6d,
0x6b,
0x59,
0x6e, // Text: "t7mkYnCm46OcINy/"
0x43,
0x6d,
0x34,
0x36,
0x4f,
0x63,
0x49,
0x4e,
0x79,
0x2f,
0x00,
0x00, // 2 null octets
]);
const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
describe('parse RTCP SDES packet', () => {
test('buffer view is RTCP', () => {
expect((0, RtcpPacket_1.isRtcp)(view)).toBe(true);
});
test('packet processing succeeds', () => {
const packet = new SdesPacket_1.SdesPacket(view);
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view)).toBe(true);
packet.serialize();
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view)).toBe(true);
const clonedPacket = packet.clone();
expect(clonedPacket.needsSerialization()).toBe(false);
expect(clonedPacket.dump()).toEqual(sdesPacketDump);
expect((0, helpers_1.areDataViewsEqual)(clonedPacket.getView(), view)).toBe(true);
});
});
describe('create RTCP SDES packet', () => {
const packet = new SdesPacket_1.SdesPacket();
test('packet view is RTCP', () => {
expect((0, RtcpPacket_1.isRtcp)(packet.getView())).toBe(true);
});
test('packet processing succeeds', () => {
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual({
...sdesPacketDump,
byteLength: 4,
count: 0,
chunks: [],
});
// Fill optional fields so serialization should be needed.
const chunk1 = new SdesPacket_1.SdesChunk();
const chunk1Dump = sdesPacketDump.chunks[0];
chunk1.setSsrc(chunk1Dump.ssrc);
chunk1.setItems(chunk1Dump.items);
packet.addChunk(chunk1);
expect(packet.needsSerialization()).toBe(true);
expect(packet.dump()).toEqual(sdesPacketDump);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view)).toBe(true);
packet.serialize();
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view)).toBe(true);
const clonedPacket = packet.clone();
expect(clonedPacket.needsSerialization()).toBe(false);
expect(clonedPacket.dump()).toEqual(sdesPacketDump);
expect((0, helpers_1.areDataViewsEqual)(clonedPacket.getView(), view)).toBe(true);
// If a change is done in a chunk, the SDES packet must need serialization.
chunk1.setSsrc(1234);
expect(chunk1.needsSerialization()).toBe(true);
expect(packet.needsSerialization()).toBe(true);
// And if we serialize the packet, it should unset the serialization needed
// flag.
packet.serialize();
expect(chunk1.needsSerialization()).toBe(false);
expect(packet.needsSerialization()).toBe(false);
});
});
describe('parse RTCP SDES packet with padding', () => {
const sdesPacketDump2 = {
byteLength: 76,
padding: 4,
packetType: RtcpPacket_1.RtcpPacketType.SDES,
count: 2,
chunks: [
{
byteLength: 24,
ssrc: 0x9f65e742,
items: [{ type: SdesPacket_1.SdesItemType.CNAME, text: 't7mkYnCm46OcINy/' }],
},
{
byteLength: 44,
ssrc: 0x9f65e743,
items: [
{ type: SdesPacket_1.SdesItemType.NAME, text: 't7mkYnCm46OcINy/' },
{ type: SdesPacket_1.SdesItemType.NOTE, text: 't7mkYnCm46OcINy/' },
],
},
],
};
const array2 = new Uint8Array([
0xa2,
0xca,
0x00,
0x12, // Padding, Type: 202 (SDES), Count: 2, Length: 18
// Chunk
0x9f,
0x65,
0xe7,
0x42, // SSRC: 0x9f65e742
0x01,
0x10,
0x74,
0x37, // Item Type: 1 (CNAME), Length: 16,
0x6d,
0x6b,
0x59,
0x6e, // Text: "t7mkYnCm46OcINy/"
0x43,
0x6d,
0x34,
0x36,
0x4f,
0x63,
0x49,
0x4e,
0x79,
0x2f,
0x00,
0x00, // 2 null octets
// Chunk
0x9f,
0x65,
0xe7,
0x43, // SSRC: 0x9f65e743
0x02,
0x10,
0x74,
0x37, // Item Type: 2 (NAME), Length: 16,
0x6d,
0x6b,
0x59,
0x6e, // Text: "t7mkYnCm46OcINy/"
0x43,
0x6d,
0x34,
0x36,
0x4f,
0x63,
0x49,
0x4e,
0x79,
0x2f,
0x07,
0x10,
0x74,
0x37, // Item Type: 7 (NOTE), Length: 16,
0x6d,
0x6b,
0x59,
0x6e, // Text: "t7mkYnCm46OcINy/"
0x43,
0x6d,
0x34,
0x36,
0x4f,
0x63,
0x49,
0x4e,
0x79,
0x2f,
0x00,
0x00,
0x00,
0x00, // 4 null octets
0x00,
0x00,
0x00,
0x04, // Padding (4 bytes)
]);
const view2 = new DataView(array2.buffer, array2.byteOffset, array2.byteLength);
const packet = new SdesPacket_1.SdesPacket(view2);
test('buffer view is RTCP', () => {
expect((0, RtcpPacket_1.isRtcp)(view)).toBe(true);
});
test('packet processing succeeds', () => {
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump2);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view2)).toBe(true);
packet.serialize();
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump2);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view2)).toBe(true);
const clonedPacket = packet.clone();
expect(clonedPacket.needsSerialization()).toBe(false);
expect(clonedPacket.dump()).toEqual(sdesPacketDump2);
expect((0, helpers_1.areDataViewsEqual)(clonedPacket.getView(), view2)).toBe(true);
});
});
describe('parse RTCP SDES packet with padded item', () => {
const sdesPacketDump3 = {
byteLength: 16,
padding: 0,
packetType: RtcpPacket_1.RtcpPacketType.SDES,
count: 1,
chunks: [
{
byteLength: 12,
ssrc: 0x11223344,
items: [{ type: SdesPacket_1.SdesItemType.LOC, text: 'ab' }],
},
],
};
const array3 = new Uint8Array([
0x81,
0xca,
0x00,
0x03, // Type: 202 (SDES), Count: 1, Length: 3
// Chunk
0x11,
0x22,
0x33,
0x44, // SSRC: 0x11223344
0x05,
0x02,
0x61,
0x62, // Item Type: 5 (LOC), Length: 2, Text: "ab"
0x00,
0x00,
0x00,
0x00, // 4 null octets
]);
const view3 = new DataView(array3.buffer, array3.byteOffset, array3.byteLength);
const packet = new SdesPacket_1.SdesPacket(view3);
test('buffer view is RTCP', () => {
expect((0, RtcpPacket_1.isRtcp)(view)).toBe(true);
});
test('packet processing succeeds', () => {
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump3);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view3)).toBe(true);
packet.serialize();
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump3);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view3)).toBe(true);
const clonedPacket = packet.clone();
expect(clonedPacket.needsSerialization()).toBe(false);
expect(clonedPacket.dump()).toEqual(sdesPacketDump3);
expect((0, helpers_1.areDataViewsEqual)(clonedPacket.getView(), view3)).toBe(true);
});
});
describe('parse another RTCP SDES packet', () => {
const sdesPacketDump4 = {
packetType: RtcpPacket_1.RtcpPacketType.SDES,
byteLength: 52,
padding: 0,
count: 2,
chunks: [
{
byteLength: 24,
ssrc: 1234,
items: [
{ type: SdesPacket_1.SdesItemType.CNAME, text: 'qwerty' },
{ type: SdesPacket_1.SdesItemType.TOOL, text: 'iñaki' },
],
},
{
byteLength: 24,
ssrc: 5678,
items: [{ type: SdesPacket_1.SdesItemType.LOC, text: 'somewhere œæ€' }],
},
],
};
const array4 = new Uint8Array([
0x82,
0xca,
0x00,
0x0c, // Type: 202 (SDES), Count: 2, Length: 12
// Chunk
0x00,
0x00,
0x04,
0xd2, // SSRC: 1234
0x01,
0x06,
0x71,
0x77, // Item Type: 1 (CNAME), Length: 6, Text: "qwerty"
0x65,
0x72,
0x74,
0x79,
0x06,
0x06,
0x69,
0xc3, // Item Type: 6 (TOOL), Length: 6, Text: "iñaki"
0xb1,
0x61,
0x6b,
0x69,
0x00,
0x00,
0x00,
0x00, // 4 null octets
// Chunk
0x00,
0x00,
0x16,
0x2e, // SSRC: 5678
0x05,
0x11,
0x73,
0x6f, // Item Type: 5 (LOC), Length: 17, Text: "somewhere œæ€"
0x6d,
0x65,
0x77,
0x68,
0x65,
0x72,
0x65,
0x20,
0xc5,
0x93,
0xc3,
0xa6,
0xe2,
0x82,
0xac,
0x00, // 1 null octet
]);
const view4 = new DataView(array4.buffer, array4.byteOffset, array4.byteLength);
const packet = new SdesPacket_1.SdesPacket(view4);
test('buffer view is RTCP', () => {
expect((0, RtcpPacket_1.isRtcp)(view)).toBe(true);
});
test('packet processing succeeds', () => {
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump4);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view4)).toBe(true);
packet.serialize();
expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sdesPacketDump4);
expect((0, helpers_1.areDataViewsEqual)(packet.getView(), view4)).toBe(true);
const clonedPacket = packet.clone();
expect(clonedPacket.needsSerialization()).toBe(false);
expect(clonedPacket.dump()).toEqual(sdesPacketDump4);
expect((0, helpers_1.areDataViewsEqual)(clonedPacket.getView(), view4)).toBe(true);
});
});
describe('parse invalid RTCP SDES packet', () => {
test('parsing a buffer view which length does not fit the indicated count throws', () => {
// Parse just the first 26 bytes of the buffer.
const view5 = new DataView(array.buffer, array.byteOffset, 26);
expect(() => new SdesPacket_1.SdesPacket(view5)).toThrow(RangeError);
});
test('parsing a packet with missing null octets in the chunk throws', () => {
const array6 = new Uint8Array([
0x81,
0xca,
0x00,
0x02, // Type: 202 (SDES), Count: 1, Length: 2
// Chunk
0x11,
0x22,
0x33,
0x44, // SSRC: 0x11223344
0x08,
0x02,
0x61,
0x62, // Item Type: 8 (PRIV), Length: 2, Text: "ab"
]);
const view6 = new DataView(array6.buffer, array6.byteOffset, array6.byteLength);
expect(() => new SdesPacket_1.SdesPacket(view6)).toThrow(RangeError);
});
});
describe('create another RTCP SDES packet', () => {
test('packet processing succeeds', () => {
const packet = new SdesPacket_1.SdesPacket();
expect((0, RtcpPacket_1.isRtcp)(packet.getView())).toBe(true);
expect(packet.needsSerialization()).toBe(false);
// Byte length must be 4 (common header).
expect(packet.getByteLength()).toBe(4);
expect(packet.getPacketType()).toBe(RtcpPacket_1.RtcpPacketType.SDES);
expect(packet.getCount()).toBe(0);
expect(packet.getPadding()).toBe(0);
expect(packet.needsSerialization()).toBe(false);
packet.padTo4Bytes();
// After padding to 4 bytes, nothing should change since the rest of the
// packet always fits into groups of 4 bytes.
expect(packet.getByteLength()).toBe(4);
expect(packet.getPacketType()).toBe(RtcpPacket_1.RtcpPacketType.SDES);
expect(packet.getCount()).toBe(0);
expect(packet.getPadding()).toBe(0);
expect(packet.needsSerialization()).toBe(false);
packet.serialize();
expect(packet.getByteLength()).toBe(4);
expect(packet.getPacketType()).toBe(RtcpPacket_1.RtcpPacketType.SDES);
expect(packet.getCount()).toBe(0);
expect(packet.getPadding()).toBe(0);
expect(packet.needsSerialization()).toBe(false);
expect((0, RtcpPacket_1.isRtcp)(packet.getView())).toBe(true);
const chunk1 = new SdesPacket_1.SdesChunk();
chunk1.setSsrc(1234);
chunk1.addItem(SdesPacket_1.SdesItemType.CNAME, 'qwerty');
chunk1.addItem(SdesPacket_1.SdesItemType.TOOL, 'iñaki');
packet.addChunk(chunk1);
const chunk2 = new SdesPacket_1.SdesChunk();
chunk2.setSsrc(5678);
chunk2.addItem(SdesPacket_1.SdesItemType.LOC, 'somewhere œæ€');
packet.addChunk(chunk2);
expect((0, RtcpPacket_1.isRtcp)(packet.getView())).toBe(true);
const clonedPacket = packet.clone();
expect((0, RtcpPacket_1.isRtcp)(clonedPacket.getView())).toBe(true);
expect(clonedPacket.dump()).toEqual(packet.dump());
expect((0, helpers_1.areDataViewsEqual)(clonedPacket.getView(), packet.getView())).toBe(true);
const clonedChunk1 = clonedPacket.getChunks()[0];
expect(clonedChunk1.dump()).toEqual(chunk1.dump());
const chunk3 = new SdesPacket_1.SdesChunk();
chunk3.setSsrc(0x66666666);
chunk3.addItem(SdesPacket_1.SdesItemType.PRIV, 'ab');
packet.addChunk(chunk3);
packet.serialize();
expect(packet.getPacketType()).toBe(RtcpPacket_1.RtcpPacketType.SDES);
expect(packet.getCount()).toBe(3);
expect(packet.getPadding()).toBe(0);
expect(packet.needsSerialization()).toBe(false);
expect((0, RtcpPacket_1.isRtcp)(packet.getView())).toBe(true);
const chunk1B = packet.getChunks()[0];
expect(chunk1B.needsSerialization()).toBe(false);
expect(chunk1B.dump()).toEqual({
byteLength: 24,
ssrc: 1234,
items: [
{ type: SdesPacket_1.SdesItemType.CNAME, text: 'qwerty' },
{ type: SdesPacket_1.SdesItemType.TOOL, text: 'iñaki' },
],
});
const chunk2B = packet.getChunks()[1];
expect(chunk2B.needsSerialization()).toBe(false);
expect(chunk2B.dump()).toEqual({
byteLength: 24,
ssrc: 5678,
items: [{ type: SdesPacket_1.SdesItemType.LOC, text: 'somewhere œæ€' }],
});
const chunk3B = packet.getChunks()[2];
expect(chunk3B.needsSerialization()).toBe(false);
expect(chunk3B.dump()).toEqual({
byteLength: 12,
ssrc: 0x66666666,
items: [{ type: SdesPacket_1.SdesItemType.PRIV, text: 'ab' }],
});
});
});
describe('create SDES Chunk', () => {
test('creating a Chunk succeeds', () => {
const sdesChunkDump = {
byteLength: 44,
ssrc: 0x9f65e743,
items: [
{ type: SdesPacket_1.SdesItemType.NAME, text: 't7mkYnCm46OcINy/' },
{ type: SdesPacket_1.SdesItemType.NOTE, text: 't7mkYnCm46OcINy/' },
],
};
const chunk = new SdesPacket_1.SdesChunk();
expect(chunk.needsSerialization()).toBe(false);
chunk.setSsrc(sdesChunkDump.ssrc);
chunk.setItems(sdesChunkDump.items);
expect(chunk.needsSerialization()).toBe(true);
expect(chunk.dump()).toEqual(sdesChunkDump);
const serializationBuffer = new ArrayBuffer(2000);
const serializationByteOffset = 234;
const cloningBuffer = new ArrayBuffer(1000);
const cloningByteOffset = 123;
// Clone the chunk instead of just serializing it since clone() will
// serialize it anyway.
const clonedChunk = chunk.clone(cloningBuffer, cloningByteOffset, serializationBuffer, serializationByteOffset);
expect(chunk.needsSerialization()).toBe(false);
expect(chunk.getView().buffer === serializationBuffer).toBe(true);
expect(chunk.getView().byteOffset).toBe(serializationByteOffset);
expect(chunk.dump()).toEqual(sdesChunkDump);
expect(clonedChunk.needsSerialization()).toBe(false);
expect(clonedChunk.getView().buffer === cloningBuffer).toBe(true);
expect(clonedChunk.getView().byteOffset).toBe(cloningByteOffset);
expect(clonedChunk.dump()).toEqual(sdesChunkDump);
});
});