Skip to content

Commit

Permalink
feat: add support for a:ssrc and a:ssrc-group (#3)
Browse files Browse the repository at this point in the history
* feat: add support for a:ssrc line

* feat: add support for a:ssrc-group lines

* chore: export new line types

* chore: fixes from pr comments
  • Loading branch information
bbaldino committed Oct 13, 2022
1 parent 306c392 commit 9d50ef2
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 1 deletion.
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"sandboxed",
"sdp",
"sdps",
"ssrcs",
"TIAS",
"tfjs",
"toffset",
Expand Down
2 changes: 2 additions & 0 deletions src/lines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ export * from './session-information-line';
export * from './session-name-line';
export * from './setup-line';
export * from './simulcast-line';
export * from './ssrc-line';
export * from './ssrc-group-line';
export * from './timing-line';
export * from './version-line';
40 changes: 40 additions & 0 deletions src/lines/ssrc-group-line.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { SsrcGroupLine } from './ssrc-group-line';

describe('ssrc group line', () => {
it('should parse valid lines correctly', () => {
expect.hasAssertions();
{
const line = 'ssrc-group:SIM 123 456 789';
const result = SsrcGroupLine.fromSdpLine(line);

expect(result).toBeTruthy();
expect(result?.semantics).toBe('SIM');
expect(result?.ssrcs).toStrictEqual([123, 456, 789]);
}
{
const line = 'ssrc-group:FID 123 456';
const result = SsrcGroupLine.fromSdpLine(line);

expect(result).toBeTruthy();
expect(result?.semantics).toBe('FID');
expect(result?.ssrcs).toStrictEqual([123, 456]);
}
});

it('should fail to parse invalid lines', () => {
expect.hasAssertions();

expect(SsrcGroupLine.fromSdpLine('foo:FID 123 456')).toBeFalsy();
expect(SsrcGroupLine.fromSdpLine('ssrc-group:BAR 123 456')).toBeFalsy();
expect(SsrcGroupLine.fromSdpLine('ssrc-group:FID abc def')).toBeFalsy();
});

it('should serialize correctly', () => {
expect.hasAssertions();

expect(new SsrcGroupLine('SIM', [123, 456, 789]).toSdpLine()).toBe(
'a=ssrc-group:SIM 123 456 789'
);
expect(new SsrcGroupLine('FID', [123, 456]).toSdpLine()).toBe('a=ssrc-group:FID 123 456');
});
});
54 changes: 54 additions & 0 deletions src/lines/ssrc-group-line.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NUM, SP } from '../regex-helpers';
import { Line } from './line';

type SsrcGroupSemantics = 'SIM' | 'FID' | 'FEC';

/**
* Model an SSRC group line as define by https://datatracker.ietf.org/doc/html/rfc5576#section-4.2.
*
* @example
* a=ssrc-group: FID 13579 24680
*/
export class SsrcGroupLine extends Line {
semantics: SsrcGroupSemantics;

ssrcs: number[];

private static regex = new RegExp(`^ssrc-group:(SIM|FID|FEC) ((?:${NUM}${SP}*)+)`);

/**
* Create an SsrcGroupLine from the given values.
*
* @param semantics - The semantics of this SSRC group.
* @param ssrcs - The SSRCs in this SSRC group.
*/
constructor(semantics: SsrcGroupSemantics, ssrcs: number[]) {
super();
this.semantics = semantics;
this.ssrcs = ssrcs;
}

/**
* Create an SsrcGroupLine from the given string.
*
* @param line - The line to parse.
* @returns An SsrcGroupLine instance or undefined if parsing failed.
*/
static fromSdpLine(line: string): SsrcGroupLine | undefined {
if (!SsrcGroupLine.regex.test(line)) {
return undefined;
}
const tokens = line.match(SsrcGroupLine.regex) as RegExpMatchArray;
const semantics = tokens[1];
const ssrcs = tokens[2].split(' ').map((ssrcStr) => parseInt(ssrcStr, 10));

return new SsrcGroupLine(semantics as SsrcGroupSemantics, ssrcs);
}

/**
* @inheritdoc
*/
toSdpLine(): string {
return `a=ssrc-group:${this.semantics} ${this.ssrcs.join(' ')}`;
}
}
55 changes: 55 additions & 0 deletions src/lines/ssrc-line.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { SsrcLine } from './ssrc-line';

describe('ssrc line', () => {
it('should parse a line with just an attribute correctly', () => {
expect.hasAssertions();
const line = 'ssrc:1234567 foo';
const result = SsrcLine.fromSdpLine(line);

expect(result).toBeTruthy();
expect(result?.ssrcId).toBe(1234567);
expect(result?.attribute).toBe('foo');
expect(result?.attributeValue).toBeFalsy();
expect(result?.attributeData).toBeFalsy();
});

it('should parse a line with an attribute and attribute value correctly', () => {
expect.hasAssertions();
const line = 'ssrc:1234567 foo:bar';
const result = SsrcLine.fromSdpLine(line);

expect(result).toBeTruthy();
expect(result?.ssrcId).toBe(1234567);
expect(result?.attribute).toBe('foo');
expect(result?.attributeValue).toBe('bar');
expect(result?.attributeData).toBeFalsy();
});

it('should parse a line with an attribute, attribute value, and attribute data correctly', () => {
expect.hasAssertions();
const line = 'ssrc:1234567 foo:bar baz';
const result = SsrcLine.fromSdpLine(line);

expect(result).toBeTruthy();
expect(result?.ssrcId).toBe(1234567);
expect(result?.attribute).toBe('foo');
expect(result?.attributeValue).toBe('bar');
expect(result?.attributeData).toBe('baz');
});

it('should fail to parse invalid lines', () => {
expect.hasAssertions();

expect(SsrcLine.fromSdpLine('foo:1234567 foo')).toBeFalsy();
expect(SsrcLine.fromSdpLine('ssrc:1234567 foo=bar')).toBeFalsy();
});

it('should serialize correctly', () => {
expect.hasAssertions();
expect(new SsrcLine(1234567, 'foo').toSdpLine()).toBe('a=ssrc:1234567 foo');
expect(new SsrcLine(1234567, 'foo', 'bar').toSdpLine()).toBe('a=ssrc:1234567 foo:bar');
expect(new SsrcLine(1234567, 'foo', 'bar', 'baz').toSdpLine()).toBe(
'a=ssrc:1234567 foo:bar baz'
);
});
});
77 changes: 77 additions & 0 deletions src/lines/ssrc-line.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { NUM, SDP_TOKEN, TOKEN } from '../regex-helpers';
import { Line } from './line';

/**
* Model an SSRC line in SDP as defined by https://datatracker.ietf.org/doc/html/rfc5576#section-4.1.
*
* @example
* a=ssrc:1234567 cname:foo
*/
export class SsrcLine extends Line {
ssrcId: number;

attribute: string;

attributeValue: string | undefined;

attributeData: string | undefined;

private static regex = new RegExp(
`^ssrc:(${NUM}) (${SDP_TOKEN})(?::(${SDP_TOKEN})?(?: (${TOKEN}))?)?$`
);

/**
* Create an SsrcLine from the given values.
*
* @param ssrcId - The SSRC ID.
* @param attribute - The attribute specific to this SSRC.
* @param attributeValue - An optional attribute value, if applicable.
* @param attributeData - Optional attribute data, if applicable.
*/
constructor(
ssrcId: number,
attribute: string,
attributeValue: string | undefined = undefined,
attributeData: string | undefined = undefined
) {
super();
this.ssrcId = ssrcId;
this.attribute = attribute;
this.attributeValue = attributeValue;
this.attributeData = attributeData;
}

/**
* Create an SsrcLine from the given string.
*
* @param line - The line to parse.
* @returns An SsrcLine instance or undefined if parsing failed.
*/
static fromSdpLine(line: string): SsrcLine | undefined {
if (!SsrcLine.regex.test(line)) {
return undefined;
}
const tokens = line.match(SsrcLine.regex) as RegExpMatchArray;
const ssrcId = parseInt(tokens[1], 10);
const attribute = tokens[2];
const attributeValue = tokens[3];
const attributeData = tokens[4];

return new SsrcLine(ssrcId, attribute, attributeValue, attributeData);
}

/**
* @inheritdoc
*/
toSdpLine(): string {
let str = `a=ssrc:${this.ssrcId} ${this.attribute}`;
if (this.attributeValue) {
str += `:${this.attributeValue}`;
}
if (this.attributeData) {
str += ` ${this.attributeData}`;
}

return str;
}
}
4 changes: 3 additions & 1 deletion src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable max-classes-per-file */
import { IceOptionsLine } from './lines';
import { IceOptionsLine, SsrcGroupLine, SsrcLine } from './lines';
import { BandwidthLine } from './lines/bandwidth-line';
import { BundleGroupLine } from './lines/bundle-group-line';
import { CandidateLine } from './lines/candidate-line';
Expand Down Expand Up @@ -133,6 +133,8 @@ class SdpGrammar extends Grammar {
this.addParser('a', RidLine.fromSdpLine);
this.addParser('a', CandidateLine.fromSdpLine);
this.addParser('a', SimulcastLine.fromSdpLine);
this.addParser('a', SsrcLine.fromSdpLine);
this.addParser('a', SsrcGroupLine.fromSdpLine);
}
}

Expand Down

0 comments on commit 9d50ef2

Please sign in to comment.