@tomgiee/tsdp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/dist/src/builder/media-builder.d.ts +221 -0
  4. package/dist/src/builder/media-builder.d.ts.map +1 -0
  5. package/dist/src/builder/media-builder.js +385 -0
  6. package/dist/src/builder/session-builder.d.ts +195 -0
  7. package/dist/src/builder/session-builder.d.ts.map +1 -0
  8. package/dist/src/builder/session-builder.js +366 -0
  9. package/dist/src/index.d.ts +67 -0
  10. package/dist/src/index.d.ts.map +1 -0
  11. package/dist/src/index.js +250 -0
  12. package/dist/src/parser/attribute-parser.d.ts +100 -0
  13. package/dist/src/parser/attribute-parser.d.ts.map +1 -0
  14. package/dist/src/parser/attribute-parser.js +217 -0
  15. package/dist/src/parser/field-parser.d.ts +124 -0
  16. package/dist/src/parser/field-parser.d.ts.map +1 -0
  17. package/dist/src/parser/field-parser.js +335 -0
  18. package/dist/src/parser/media-parser.d.ts +45 -0
  19. package/dist/src/parser/media-parser.d.ts.map +1 -0
  20. package/dist/src/parser/media-parser.js +157 -0
  21. package/dist/src/parser/primitive-parser.d.ts +138 -0
  22. package/dist/src/parser/primitive-parser.d.ts.map +1 -0
  23. package/dist/src/parser/primitive-parser.js +316 -0
  24. package/dist/src/parser/scanner.d.ts +142 -0
  25. package/dist/src/parser/scanner.d.ts.map +1 -0
  26. package/dist/src/parser/scanner.js +284 -0
  27. package/dist/src/parser/session-parser.d.ts +35 -0
  28. package/dist/src/parser/session-parser.d.ts.map +1 -0
  29. package/dist/src/parser/session-parser.js +207 -0
  30. package/dist/src/parser/time-parser.d.ts +74 -0
  31. package/dist/src/parser/time-parser.d.ts.map +1 -0
  32. package/dist/src/parser/time-parser.js +168 -0
  33. package/dist/src/serializer/attribute-serializer.d.ts +18 -0
  34. package/dist/src/serializer/attribute-serializer.d.ts.map +1 -0
  35. package/dist/src/serializer/attribute-serializer.js +34 -0
  36. package/dist/src/serializer/field-serializer.d.ts +112 -0
  37. package/dist/src/serializer/field-serializer.d.ts.map +1 -0
  38. package/dist/src/serializer/field-serializer.js +212 -0
  39. package/dist/src/serializer/media-serializer.d.ts +31 -0
  40. package/dist/src/serializer/media-serializer.d.ts.map +1 -0
  41. package/dist/src/serializer/media-serializer.js +83 -0
  42. package/dist/src/serializer/session-serializer.d.ts +29 -0
  43. package/dist/src/serializer/session-serializer.d.ts.map +1 -0
  44. package/dist/src/serializer/session-serializer.js +99 -0
  45. package/dist/src/serializer/time-serializer.d.ts +46 -0
  46. package/dist/src/serializer/time-serializer.d.ts.map +1 -0
  47. package/dist/src/serializer/time-serializer.js +86 -0
  48. package/dist/src/types/attributes.d.ts +318 -0
  49. package/dist/src/types/attributes.d.ts.map +1 -0
  50. package/dist/src/types/attributes.js +225 -0
  51. package/dist/src/types/errors.d.ts +129 -0
  52. package/dist/src/types/errors.d.ts.map +1 -0
  53. package/dist/src/types/errors.js +186 -0
  54. package/dist/src/types/fields.d.ts +100 -0
  55. package/dist/src/types/fields.d.ts.map +1 -0
  56. package/dist/src/types/fields.js +48 -0
  57. package/dist/src/types/media.d.ts +148 -0
  58. package/dist/src/types/media.d.ts.map +1 -0
  59. package/dist/src/types/media.js +137 -0
  60. package/dist/src/types/network.d.ts +136 -0
  61. package/dist/src/types/network.d.ts.map +1 -0
  62. package/dist/src/types/network.js +130 -0
  63. package/dist/src/types/primitives.d.ts +193 -0
  64. package/dist/src/types/primitives.d.ts.map +1 -0
  65. package/dist/src/types/primitives.js +195 -0
  66. package/dist/src/types/session.d.ts +122 -0
  67. package/dist/src/types/session.d.ts.map +1 -0
  68. package/dist/src/types/session.js +81 -0
  69. package/dist/src/types/time.d.ts +129 -0
  70. package/dist/src/types/time.d.ts.map +1 -0
  71. package/dist/src/types/time.js +84 -0
  72. package/dist/src/utils/address-parser.d.ts +100 -0
  73. package/dist/src/utils/address-parser.d.ts.map +1 -0
  74. package/dist/src/utils/address-parser.js +338 -0
  75. package/dist/src/utils/format-validators.d.ts +77 -0
  76. package/dist/src/utils/format-validators.d.ts.map +1 -0
  77. package/dist/src/utils/format-validators.js +504 -0
  78. package/dist/src/utils/line-reader.d.ts +84 -0
  79. package/dist/src/utils/line-reader.d.ts.map +1 -0
  80. package/dist/src/utils/line-reader.js +169 -0
  81. package/dist/src/utils/time-converter.d.ts +99 -0
  82. package/dist/src/utils/time-converter.d.ts.map +1 -0
  83. package/dist/src/utils/time-converter.js +195 -0
  84. package/dist/src/validator/media-validator.d.ts +27 -0
  85. package/dist/src/validator/media-validator.d.ts.map +1 -0
  86. package/dist/src/validator/media-validator.js +241 -0
  87. package/dist/src/validator/semantic-validator.d.ts +47 -0
  88. package/dist/src/validator/semantic-validator.d.ts.map +1 -0
  89. package/dist/src/validator/semantic-validator.js +207 -0
  90. package/dist/src/validator/session-validator.d.ts +36 -0
  91. package/dist/src/validator/session-validator.d.ts.map +1 -0
  92. package/dist/src/validator/session-validator.js +280 -0
  93. package/dist/tests/integration/round-trip.test.d.ts +5 -0
  94. package/dist/tests/integration/round-trip.test.d.ts.map +1 -0
  95. package/dist/tests/integration/round-trip.test.js +320 -0
  96. package/dist/tests/integration/voip-examples.test.d.ts +5 -0
  97. package/dist/tests/integration/voip-examples.test.d.ts.map +1 -0
  98. package/dist/tests/integration/voip-examples.test.js +361 -0
  99. package/dist/tests/unit/builder/media-builder.test.d.ts +5 -0
  100. package/dist/tests/unit/builder/media-builder.test.d.ts.map +1 -0
  101. package/dist/tests/unit/builder/media-builder.test.js +524 -0
  102. package/dist/tests/unit/builder/session-builder.test.d.ts +5 -0
  103. package/dist/tests/unit/builder/session-builder.test.d.ts.map +1 -0
  104. package/dist/tests/unit/builder/session-builder.test.js +367 -0
  105. package/dist/tests/unit/parser/attribute-parser.test.d.ts +5 -0
  106. package/dist/tests/unit/parser/attribute-parser.test.d.ts.map +1 -0
  107. package/dist/tests/unit/parser/attribute-parser.test.js +319 -0
  108. package/dist/tests/unit/parser/field-parser.test.d.ts +5 -0
  109. package/dist/tests/unit/parser/field-parser.test.d.ts.map +1 -0
  110. package/dist/tests/unit/parser/field-parser.test.js +355 -0
  111. package/dist/tests/unit/parser/media-parser.test.d.ts +5 -0
  112. package/dist/tests/unit/parser/media-parser.test.d.ts.map +1 -0
  113. package/dist/tests/unit/parser/media-parser.test.js +241 -0
  114. package/dist/tests/unit/parser/primitive-parser.test.d.ts +5 -0
  115. package/dist/tests/unit/parser/primitive-parser.test.d.ts.map +1 -0
  116. package/dist/tests/unit/parser/primitive-parser.test.js +261 -0
  117. package/dist/tests/unit/parser/scanner.test.d.ts +5 -0
  118. package/dist/tests/unit/parser/scanner.test.d.ts.map +1 -0
  119. package/dist/tests/unit/parser/scanner.test.js +241 -0
  120. package/dist/tests/unit/parser/session-parser.test.d.ts +5 -0
  121. package/dist/tests/unit/parser/session-parser.test.d.ts.map +1 -0
  122. package/dist/tests/unit/parser/session-parser.test.js +346 -0
  123. package/dist/tests/unit/parser/time-parser.test.d.ts +5 -0
  124. package/dist/tests/unit/parser/time-parser.test.d.ts.map +1 -0
  125. package/dist/tests/unit/parser/time-parser.test.js +173 -0
  126. package/dist/tests/unit/serializer/attribute-serializer.test.d.ts +5 -0
  127. package/dist/tests/unit/serializer/attribute-serializer.test.d.ts.map +1 -0
  128. package/dist/tests/unit/serializer/attribute-serializer.test.js +78 -0
  129. package/dist/tests/unit/serializer/field-serializer.test.d.ts +5 -0
  130. package/dist/tests/unit/serializer/field-serializer.test.d.ts.map +1 -0
  131. package/dist/tests/unit/serializer/field-serializer.test.js +159 -0
  132. package/dist/tests/unit/serializer/media-serializer.test.d.ts +5 -0
  133. package/dist/tests/unit/serializer/media-serializer.test.d.ts.map +1 -0
  134. package/dist/tests/unit/serializer/media-serializer.test.js +155 -0
  135. package/dist/tests/unit/serializer/session-serializer.test.d.ts +5 -0
  136. package/dist/tests/unit/serializer/session-serializer.test.d.ts.map +1 -0
  137. package/dist/tests/unit/serializer/session-serializer.test.js +317 -0
  138. package/dist/tests/unit/serializer/time-serializer.test.d.ts +5 -0
  139. package/dist/tests/unit/serializer/time-serializer.test.d.ts.map +1 -0
  140. package/dist/tests/unit/serializer/time-serializer.test.js +115 -0
  141. package/dist/tests/unit/types/errors.test.d.ts +5 -0
  142. package/dist/tests/unit/types/errors.test.d.ts.map +1 -0
  143. package/dist/tests/unit/types/errors.test.js +127 -0
  144. package/dist/tests/unit/types/network.test.d.ts +5 -0
  145. package/dist/tests/unit/types/network.test.d.ts.map +1 -0
  146. package/dist/tests/unit/types/network.test.js +132 -0
  147. package/dist/tests/unit/types/primitives.test.d.ts +5 -0
  148. package/dist/tests/unit/types/primitives.test.d.ts.map +1 -0
  149. package/dist/tests/unit/types/primitives.test.js +108 -0
  150. package/dist/tests/unit/utils/address-parser.test.d.ts +5 -0
  151. package/dist/tests/unit/utils/address-parser.test.d.ts.map +1 -0
  152. package/dist/tests/unit/utils/address-parser.test.js +203 -0
  153. package/dist/tests/unit/utils/format-validators.test.d.ts +5 -0
  154. package/dist/tests/unit/utils/format-validators.test.d.ts.map +1 -0
  155. package/dist/tests/unit/utils/format-validators.test.js +224 -0
  156. package/dist/tests/unit/utils/line-reader.test.d.ts +5 -0
  157. package/dist/tests/unit/utils/line-reader.test.d.ts.map +1 -0
  158. package/dist/tests/unit/utils/line-reader.test.js +157 -0
  159. package/dist/tests/unit/utils/time-converter.test.d.ts +5 -0
  160. package/dist/tests/unit/utils/time-converter.test.d.ts.map +1 -0
  161. package/dist/tests/unit/utils/time-converter.test.js +190 -0
  162. package/dist/tests/unit/validator/media-validator.test.d.ts +5 -0
  163. package/dist/tests/unit/validator/media-validator.test.d.ts.map +1 -0
  164. package/dist/tests/unit/validator/media-validator.test.js +313 -0
  165. package/dist/tests/unit/validator/semantic-validator.test.d.ts +5 -0
  166. package/dist/tests/unit/validator/semantic-validator.test.d.ts.map +1 -0
  167. package/dist/tests/unit/validator/semantic-validator.test.js +262 -0
  168. package/dist/tests/unit/validator/session-validator.test.d.ts +5 -0
  169. package/dist/tests/unit/validator/session-validator.test.d.ts.map +1 -0
  170. package/dist/tests/unit/validator/session-validator.test.js +447 -0
  171. package/package.json +50 -0
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Field parsers for SDP (RFC 8866)
3
+ *
4
+ * Parsers for individual SDP field types (v=, o=, s=, i=, u=, e=, p=, c=, b=, k=)
5
+ */
6
+ import { Scanner } from './scanner';
7
+ import { Version, SessionName, Information, URI, Email, Phone, Key } from '../types/primitives';
8
+ import { Origin, Connection, Bandwidth } from '../types/fields';
9
+ /**
10
+ * Parse version field
11
+ *
12
+ * Format: v=0
13
+ *
14
+ * @param scanner - Scanner instance
15
+ * @returns Version (currently always 0)
16
+ * @throws ParseError if version is not 0
17
+ */
18
+ export declare function parseVersionField(scanner: Scanner): Version;
19
+ /**
20
+ * Parse origin field
21
+ *
22
+ * Format: o=<username> SP <sess-id> SP <sess-version> SP <nettype> SP <addrtype> SP <unicast-address>
23
+ *
24
+ * @param scanner - Scanner instance
25
+ * @returns Origin object
26
+ * @throws ParseError if invalid format
27
+ */
28
+ export declare function parseOriginField(scanner: Scanner): Origin;
29
+ /**
30
+ * Parse session name field
31
+ *
32
+ * Format: s=<session name>
33
+ *
34
+ * Session name can be a single space (" ") if there is no meaningful name.
35
+ *
36
+ * @param scanner - Scanner instance
37
+ * @returns SessionName
38
+ * @throws ParseError if empty
39
+ */
40
+ export declare function parseSessionNameField(scanner: Scanner): SessionName;
41
+ /**
42
+ * Parse information field
43
+ *
44
+ * Format: i=<session information>
45
+ *
46
+ * @param scanner - Scanner instance
47
+ * @returns Information string
48
+ */
49
+ export declare function parseInformationField(scanner: Scanner): Information;
50
+ /**
51
+ * Parse URI field
52
+ *
53
+ * Format: u=<uri>
54
+ *
55
+ * @param scanner - Scanner instance
56
+ * @returns URI string
57
+ */
58
+ export declare function parseUriField(scanner: Scanner): URI;
59
+ /**
60
+ * Parse email field
61
+ *
62
+ * Format: e=<email-address> or e=<display-name> <email-address>
63
+ *
64
+ * @param scanner - Scanner instance
65
+ * @returns Email string
66
+ */
67
+ export declare function parseEmailField(scanner: Scanner): Email;
68
+ /**
69
+ * Parse phone field
70
+ *
71
+ * Format: p=<phone-number> or p=<display-name> <phone-number>
72
+ *
73
+ * @param scanner - Scanner instance
74
+ * @returns Phone string
75
+ */
76
+ export declare function parsePhoneField(scanner: Scanner): Phone;
77
+ /**
78
+ * Parse connection field
79
+ *
80
+ * Format: c=<nettype> SP <addrtype> SP <connection-address>
81
+ *
82
+ * connection-address can be:
83
+ * - unicast-address
84
+ * - multicast-address (with optional TTL and number of addresses)
85
+ *
86
+ * @param scanner - Scanner instance
87
+ * @returns Connection object
88
+ * @throws ParseError if invalid format
89
+ */
90
+ export declare function parseConnectionField(scanner: Scanner): Connection;
91
+ /**
92
+ * Parse bandwidth field
93
+ *
94
+ * Format: b=<bwtype>:<bandwidth>
95
+ *
96
+ * bwtype can be:
97
+ * - CT (Conference Total)
98
+ * - AS (Application Specific)
99
+ * - Or any registered extension type
100
+ *
101
+ * bandwidth is in kilobits per second
102
+ *
103
+ * @param scanner - Scanner instance
104
+ * @returns Bandwidth object
105
+ * @throws ParseError if invalid format
106
+ */
107
+ export declare function parseBandwidthField(scanner: Scanner): Bandwidth;
108
+ /**
109
+ * Parse key field (OBSOLETE per RFC 8866)
110
+ *
111
+ * Format: k=<method>:<encryption-key>
112
+ * Or: k=<method> (if no key data)
113
+ *
114
+ * Common methods:
115
+ * - clear:<encryption-key> - key is included untransformed
116
+ * - base64:<encoded-encryption-key> - key is base64 encoded
117
+ * - uri:<uri-to-obtain-key> - URI where key can be obtained
118
+ * - prompt - no key included, user will be prompted
119
+ *
120
+ * @param scanner - Scanner instance
121
+ * @returns Key string
122
+ */
123
+ export declare function parseKeyField(scanner: Scanner): Key;
124
+ //# sourceMappingURL=field-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-parser.d.ts","sourceRoot":"","sources":["../../../src/parser/field-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,OAAO,EACL,OAAO,EACP,WAAW,EAMX,WAAW,EACX,GAAG,EACH,KAAK,EACL,KAAK,EACL,GAAG,EAGJ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAmD,MAAM,iBAAiB,CAAC;AA0BjH;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAa3D;AAMD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAmCzD;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAenE;AAMD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAGnE;AAMD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,GAAG,CAEnD;AAMD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,KAAK,CAEvD;AAMD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,KAAK,CAEvD;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAiBjE;AAyHD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAqB/D;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,GAAG,CAGnD"}
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ /**
3
+ * Field parsers for SDP (RFC 8866)
4
+ *
5
+ * Parsers for individual SDP field types (v=, o=, s=, i=, u=, e=, p=, c=, b=, k=)
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseVersionField = parseVersionField;
9
+ exports.parseOriginField = parseOriginField;
10
+ exports.parseSessionNameField = parseSessionNameField;
11
+ exports.parseInformationField = parseInformationField;
12
+ exports.parseUriField = parseUriField;
13
+ exports.parseEmailField = parseEmailField;
14
+ exports.parsePhoneField = parsePhoneField;
15
+ exports.parseConnectionField = parseConnectionField;
16
+ exports.parseBandwidthField = parseBandwidthField;
17
+ exports.parseKeyField = parseKeyField;
18
+ const primitive_parser_1 = require("./primitive-parser");
19
+ const errors_1 = require("../types/errors");
20
+ const primitives_1 = require("../types/primitives");
21
+ const fields_1 = require("../types/fields");
22
+ const network_1 = require("../types/network");
23
+ const address_parser_1 = require("../utils/address-parser");
24
+ const address_parser_2 = require("../utils/address-parser");
25
+ // ============================================================================
26
+ // Version Field Parser (v=)
27
+ // ============================================================================
28
+ /**
29
+ * Parse version field
30
+ *
31
+ * Format: v=0
32
+ *
33
+ * @param scanner - Scanner instance
34
+ * @returns Version (currently always 0)
35
+ * @throws ParseError if version is not 0
36
+ */
37
+ function parseVersionField(scanner) {
38
+ const start = scanner.getPosition();
39
+ const version = (0, primitive_parser_1.parseInteger)(scanner);
40
+ if (version !== 0) {
41
+ throw new errors_1.ParseError(`Unsupported SDP version: ${version} (expected 0)`, start.line, start.column);
42
+ }
43
+ return (0, primitives_1.createVersion)(version);
44
+ }
45
+ // ============================================================================
46
+ // Origin Field Parser (o=)
47
+ // ============================================================================
48
+ /**
49
+ * Parse origin field
50
+ *
51
+ * Format: o=<username> SP <sess-id> SP <sess-version> SP <nettype> SP <addrtype> SP <unicast-address>
52
+ *
53
+ * @param scanner - Scanner instance
54
+ * @returns Origin object
55
+ * @throws ParseError if invalid format
56
+ */
57
+ function parseOriginField(scanner) {
58
+ const start = scanner.getPosition();
59
+ // Parse username (non-whitespace string)
60
+ const username = (0, primitive_parser_1.parseNonWSString)(scanner);
61
+ scanner.expectSpace();
62
+ // Parse session ID (non-whitespace string)
63
+ const sessId = (0, primitive_parser_1.parseNonWSString)(scanner);
64
+ scanner.expectSpace();
65
+ // Parse session version (non-whitespace string)
66
+ const sessVersion = (0, primitive_parser_1.parseNonWSString)(scanner);
67
+ scanner.expectSpace();
68
+ // Parse network type (token)
69
+ const netType = (0, primitive_parser_1.parseToken)(scanner);
70
+ scanner.expectSpace();
71
+ // Parse address type (token)
72
+ const addrType = (0, primitive_parser_1.parseToken)(scanner);
73
+ scanner.expectSpace();
74
+ // Parse unicast address (depends on addrType)
75
+ const unicastAddress = (0, address_parser_1.parseUnicastAddress)(scanner.readUntil(/[\r\n]/), addrType);
76
+ return (0, fields_1.createOrigin)(username, sessId, sessVersion, netType, addrType, unicastAddress);
77
+ }
78
+ // ============================================================================
79
+ // Session Name Field Parser (s=)
80
+ // ============================================================================
81
+ /**
82
+ * Parse session name field
83
+ *
84
+ * Format: s=<session name>
85
+ *
86
+ * Session name can be a single space (" ") if there is no meaningful name.
87
+ *
88
+ * @param scanner - Scanner instance
89
+ * @returns SessionName
90
+ * @throws ParseError if empty
91
+ */
92
+ function parseSessionNameField(scanner) {
93
+ const start = scanner.getPosition();
94
+ // Read until end of line
95
+ const name = scanner.readUntil(/[\r\n]/);
96
+ if (name.length === 0) {
97
+ throw new errors_1.ParseError('Session name cannot be empty', start.line, start.column);
98
+ }
99
+ return name;
100
+ }
101
+ // ============================================================================
102
+ // Information Field Parser (i=)
103
+ // ============================================================================
104
+ /**
105
+ * Parse information field
106
+ *
107
+ * Format: i=<session information>
108
+ *
109
+ * @param scanner - Scanner instance
110
+ * @returns Information string
111
+ */
112
+ function parseInformationField(scanner) {
113
+ // Read until end of line (can be empty)
114
+ return scanner.readUntil(/[\r\n]/);
115
+ }
116
+ // ============================================================================
117
+ // URI Field Parser (u=)
118
+ // ============================================================================
119
+ /**
120
+ * Parse URI field
121
+ *
122
+ * Format: u=<uri>
123
+ *
124
+ * @param scanner - Scanner instance
125
+ * @returns URI string
126
+ */
127
+ function parseUriField(scanner) {
128
+ return (0, primitive_parser_1.parseURI)(scanner);
129
+ }
130
+ // ============================================================================
131
+ // Email Field Parser (e=)
132
+ // ============================================================================
133
+ /**
134
+ * Parse email field
135
+ *
136
+ * Format: e=<email-address> or e=<display-name> <email-address>
137
+ *
138
+ * @param scanner - Scanner instance
139
+ * @returns Email string
140
+ */
141
+ function parseEmailField(scanner) {
142
+ return (0, primitive_parser_1.parseEmail)(scanner);
143
+ }
144
+ // ============================================================================
145
+ // Phone Field Parser (p=)
146
+ // ============================================================================
147
+ /**
148
+ * Parse phone field
149
+ *
150
+ * Format: p=<phone-number> or p=<display-name> <phone-number>
151
+ *
152
+ * @param scanner - Scanner instance
153
+ * @returns Phone string
154
+ */
155
+ function parsePhoneField(scanner) {
156
+ return (0, primitive_parser_1.parsePhone)(scanner);
157
+ }
158
+ // ============================================================================
159
+ // Connection Field Parser (c=)
160
+ // ============================================================================
161
+ /**
162
+ * Parse connection field
163
+ *
164
+ * Format: c=<nettype> SP <addrtype> SP <connection-address>
165
+ *
166
+ * connection-address can be:
167
+ * - unicast-address
168
+ * - multicast-address (with optional TTL and number of addresses)
169
+ *
170
+ * @param scanner - Scanner instance
171
+ * @returns Connection object
172
+ * @throws ParseError if invalid format
173
+ */
174
+ function parseConnectionField(scanner) {
175
+ const start = scanner.getPosition();
176
+ // Parse network type (token)
177
+ const netType = (0, primitive_parser_1.parseToken)(scanner);
178
+ scanner.expectSpace();
179
+ // Parse address type (token)
180
+ const addrType = (0, primitive_parser_1.parseToken)(scanner);
181
+ scanner.expectSpace();
182
+ // Parse connection address
183
+ const connectionAddress = parseConnectionAddress(scanner, addrType);
184
+ return (0, fields_1.createConnection)(netType, addrType, connectionAddress);
185
+ }
186
+ /**
187
+ * Parse connection address (unicast or multicast)
188
+ *
189
+ * Multicast format:
190
+ * - IPv4: <base-multicast-address>/<ttl>[/<number of addresses>]
191
+ * - IPv6: <base-multicast-address>[/<number of addresses>]
192
+ * - FQDN: <base-multicast-address>[/<ttl>][/<number of addresses>]
193
+ *
194
+ * RFC 8866 Section 5.7: When TTL or number of addresses are present,
195
+ * the address MUST be a multicast address.
196
+ *
197
+ * @param scanner - Scanner instance
198
+ * @param addrType - Address type (IP4, IP6, etc.)
199
+ * @returns ConnectionAddress (unicast or multicast)
200
+ */
201
+ function parseConnectionAddress(scanner, addrType) {
202
+ const start = scanner.getPosition();
203
+ // Read the address part (until / or end of line)
204
+ const addressPart = scanner.readUntil(/[\/\r\n]/);
205
+ if (addressPart.length === 0) {
206
+ throw new errors_1.ParseError('Expected connection address', start.line, start.column);
207
+ }
208
+ // Check if there's a / indicating multicast parameters
209
+ if (scanner.peek() !== '/') {
210
+ // Unicast address
211
+ return (0, address_parser_1.parseUnicastAddress)(addressPart, addrType);
212
+ }
213
+ // Multicast address - parse TTL and/or number of addresses
214
+ scanner.advance(); // consume '/'
215
+ const firstParam = (0, primitive_parser_1.parseInteger)(scanner);
216
+ let ttl;
217
+ let numAddresses;
218
+ // Check for second parameter
219
+ if (scanner.peek() === '/') {
220
+ scanner.advance(); // consume second '/'
221
+ const secondParam = (0, primitive_parser_1.parseInteger)(scanner);
222
+ // For IPv4: first is TTL, second is numAddresses
223
+ // For FQDN: first is TTL, second is numAddresses
224
+ // For IPv6: TTL is forbidden, so first is numAddresses
225
+ if (addrType === 'IP6') {
226
+ throw new errors_1.ParseError('IPv6 multicast addresses cannot have TTL parameter', start.line, start.column);
227
+ }
228
+ ttl = firstParam;
229
+ numAddresses = secondParam;
230
+ }
231
+ else {
232
+ // Only one parameter
233
+ if (addrType === 'IP4') {
234
+ // For IPv4, TTL is required
235
+ ttl = firstParam;
236
+ }
237
+ else if (addrType === 'IP6') {
238
+ // For IPv6, it's number of addresses
239
+ numAddresses = firstParam;
240
+ }
241
+ else {
242
+ // For FQDN or other, assume it's TTL
243
+ ttl = firstParam;
244
+ }
245
+ }
246
+ // Create appropriate multicast address based on address type
247
+ // RFC 8866 Section 5.7: Validate that address is actually multicast when parameters are present
248
+ if (addrType === 'IP4') {
249
+ // For IP4, could be IPv4 address or FQDN
250
+ if ((0, address_parser_2.isIP4Address)(addressPart)) {
251
+ // Validate that IPv4 address is in multicast range (224.0.0.0 - 239.255.255.255)
252
+ if (!(0, address_parser_2.isIP4MulticastAddress)(addressPart)) {
253
+ throw new errors_1.ParseError(`IPv4 address '${addressPart}' with TTL/numAddresses must be a multicast address (224.0.0.0 - 239.255.255.255)`, start.line, start.column);
254
+ }
255
+ return (0, network_1.createIP4MulticastAddress)((0, network_1.createIP4Address)(addressPart), ttl, numAddresses);
256
+ }
257
+ else {
258
+ // Assume FQDN - cannot validate multicast range for FQDNs
259
+ return (0, network_1.createFQDNMulticastAddress)((0, network_1.createFQDN)(addressPart), ttl, numAddresses);
260
+ }
261
+ }
262
+ else if (addrType === 'IP6') {
263
+ // For IP6, could be IPv6 address or FQDN
264
+ if ((0, address_parser_2.isIP6Address)(addressPart)) {
265
+ // Validate that IPv6 address is multicast (starts with FF)
266
+ if (!(0, address_parser_2.isIP6MulticastAddress)(addressPart)) {
267
+ throw new errors_1.ParseError(`IPv6 address '${addressPart}' with numAddresses must be a multicast address (must start with FF)`, start.line, start.column);
268
+ }
269
+ return (0, network_1.createIP6MulticastAddress)((0, network_1.createIP6Address)(addressPart), numAddresses);
270
+ }
271
+ else {
272
+ // Assume FQDN - cannot validate multicast range for FQDNs
273
+ return (0, network_1.createFQDNMulticastAddress)((0, network_1.createFQDN)(addressPart), ttl, numAddresses);
274
+ }
275
+ }
276
+ else {
277
+ // Extension address type
278
+ return (0, network_1.createFQDNMulticastAddress)((0, network_1.createFQDN)(addressPart), ttl, numAddresses);
279
+ }
280
+ }
281
+ // ============================================================================
282
+ // Bandwidth Field Parser (b=)
283
+ // ============================================================================
284
+ /**
285
+ * Parse bandwidth field
286
+ *
287
+ * Format: b=<bwtype>:<bandwidth>
288
+ *
289
+ * bwtype can be:
290
+ * - CT (Conference Total)
291
+ * - AS (Application Specific)
292
+ * - Or any registered extension type
293
+ *
294
+ * bandwidth is in kilobits per second
295
+ *
296
+ * @param scanner - Scanner instance
297
+ * @returns Bandwidth object
298
+ * @throws ParseError if invalid format
299
+ */
300
+ function parseBandwidthField(scanner) {
301
+ const start = scanner.getPosition();
302
+ // Parse bandwidth type (token before ':')
303
+ const bwtype = (0, primitive_parser_1.parseToken)(scanner);
304
+ // Expect ':'
305
+ const ch = scanner.peek();
306
+ if (ch !== ':') {
307
+ throw new errors_1.ParseError(`Expected ':' after bandwidth type, found "${ch}"`, start.line, start.column);
308
+ }
309
+ scanner.advance();
310
+ // Parse bandwidth value (integer)
311
+ const bandwidth = (0, primitive_parser_1.parseInteger)(scanner);
312
+ return (0, fields_1.createBandwidth)(bwtype, bandwidth);
313
+ }
314
+ // ============================================================================
315
+ // Key Field Parser (k=)
316
+ // ============================================================================
317
+ /**
318
+ * Parse key field (OBSOLETE per RFC 8866)
319
+ *
320
+ * Format: k=<method>:<encryption-key>
321
+ * Or: k=<method> (if no key data)
322
+ *
323
+ * Common methods:
324
+ * - clear:<encryption-key> - key is included untransformed
325
+ * - base64:<encoded-encryption-key> - key is base64 encoded
326
+ * - uri:<uri-to-obtain-key> - URI where key can be obtained
327
+ * - prompt - no key included, user will be prompted
328
+ *
329
+ * @param scanner - Scanner instance
330
+ * @returns Key string
331
+ */
332
+ function parseKeyField(scanner) {
333
+ // Read entire line
334
+ return scanner.readUntil(/[\r\n]/);
335
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Media description parser for SDP (RFC 8866)
3
+ *
4
+ * Parses media descriptions (m= and associated fields)
5
+ */
6
+ import { Scanner } from './scanner';
7
+ import { Media, MediaDescription } from '../types/media';
8
+ /**
9
+ * Parse media field
10
+ *
11
+ * Format: m=<media> <port> <proto> <fmt> ...
12
+ *
13
+ * Examples:
14
+ * - m=audio 49170 RTP/AVP 0 8 101
15
+ * - m=video 51372 RTP/AVP 99
16
+ * - m=audio 49170/2 RTP/AVP 0 (port range for hierarchical encoding)
17
+ *
18
+ * @param scanner - Scanner instance
19
+ * @returns Media object
20
+ * @throws ParseError if invalid format
21
+ */
22
+ export declare function parseMediaField(scanner: Scanner): Media;
23
+ /**
24
+ * Parse media description
25
+ *
26
+ * A media description consists of:
27
+ * - One media field (m=) [required]
28
+ * - Optional information field (i=)
29
+ * - Zero or more connection fields (c=)
30
+ * - Zero or more bandwidth fields (b=)
31
+ * - Optional encryption key field (k=)
32
+ * - Zero or more attribute fields (a=)
33
+ *
34
+ * The media field has already been identified, but not yet parsed.
35
+ *
36
+ * @param scanner - Scanner positioned at the start of media field value (after "m=")
37
+ * @param additionalLines - Additional lines that might belong to this media description
38
+ * @returns MediaDescription object
39
+ * @throws ParseError if invalid format
40
+ */
41
+ export declare function parseMediaDescription(mediaFieldScanner: Scanner, additionalLines: {
42
+ type: string;
43
+ value: string;
44
+ }[]): MediaDescription;
45
+ //# sourceMappingURL=media-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-parser.d.ts","sourceRoot":"","sources":["../../../src/parser/media-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,OAAO,EACL,KAAK,EACL,gBAAgB,EAMjB,MAAM,gBAAgB,CAAC;AAWxB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,KAAK,CA6FvD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,iBAAiB,EAAE,OAAO,EAC1B,eAAe,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,GACjD,gBAAgB,CAmElB"}
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * Media description parser for SDP (RFC 8866)
4
+ *
5
+ * Parses media descriptions (m= and associated fields)
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseMediaField = parseMediaField;
9
+ exports.parseMediaDescription = parseMediaDescription;
10
+ const scanner_1 = require("./scanner");
11
+ const primitive_parser_1 = require("./primitive-parser");
12
+ const field_parser_1 = require("./field-parser");
13
+ const attribute_parser_1 = require("./attribute-parser");
14
+ const errors_1 = require("../types/errors");
15
+ const media_1 = require("../types/media");
16
+ const primitives_1 = require("../types/primitives");
17
+ // ============================================================================
18
+ // Media Field Parser (m=)
19
+ // ============================================================================
20
+ /**
21
+ * Parse media field
22
+ *
23
+ * Format: m=<media> <port> <proto> <fmt> ...
24
+ *
25
+ * Examples:
26
+ * - m=audio 49170 RTP/AVP 0 8 101
27
+ * - m=video 51372 RTP/AVP 99
28
+ * - m=audio 49170/2 RTP/AVP 0 (port range for hierarchical encoding)
29
+ *
30
+ * @param scanner - Scanner instance
31
+ * @returns Media object
32
+ * @throws ParseError if invalid format
33
+ */
34
+ function parseMediaField(scanner) {
35
+ const start = scanner.getPosition();
36
+ // Parse media type (token)
37
+ const mediaTypeStr = (0, primitive_parser_1.parseToken)(scanner);
38
+ const mediaType = (0, primitives_1.createMediaType)(mediaTypeStr);
39
+ // Parse port or port range
40
+ scanner.skipWhitespace();
41
+ const portStart = scanner.getPosition();
42
+ // Read until whitespace (to capture '/' for port ranges)
43
+ const portStr = scanner.readWhile(ch => ch !== ' ' && ch !== '\t' && ch !== '\r' && ch !== '\n');
44
+ if (portStr.length === 0) {
45
+ throw new errors_1.ParseError('Expected port number', portStart.line, portStart.column);
46
+ }
47
+ let port;
48
+ // Check if port is a range (contains '/')
49
+ if (portStr.includes('/')) {
50
+ const parts = portStr.split('/');
51
+ if (parts.length !== 2) {
52
+ throw new errors_1.ParseError(`Invalid port range format: expected "<port>/<count>", got "${portStr}"`, portStart.line, portStart.column);
53
+ }
54
+ const base = parseInt(parts[0], 10);
55
+ const count = parseInt(parts[1], 10);
56
+ if (isNaN(base) || isNaN(count)) {
57
+ throw new errors_1.ParseError(`Invalid port range: "${portStr}"`, portStart.line, portStart.column);
58
+ }
59
+ port = (0, media_1.createPortRange)(base, count);
60
+ }
61
+ else {
62
+ // Simple port
63
+ const portNum = parseInt(portStr, 10);
64
+ if (isNaN(portNum)) {
65
+ throw new errors_1.ParseError(`Invalid port number: "${portStr}"`, portStart.line, portStart.column);
66
+ }
67
+ port = (0, media_1.createSimplePort)(portNum);
68
+ }
69
+ // Parse protocol (read until whitespace, protocols can contain '/' like RTP/AVP)
70
+ scanner.skipWhitespace();
71
+ const protoStart = scanner.getPosition();
72
+ const protoStr = scanner.readWhile(ch => ch !== ' ' && ch !== '\t' && ch !== '\r' && ch !== '\n');
73
+ if (protoStr.length === 0) {
74
+ throw new errors_1.ParseError('Expected protocol', protoStart.line, protoStart.column);
75
+ }
76
+ const proto = (0, primitives_1.createProtocol)(protoStr);
77
+ // Parse formats (one or more)
78
+ const formats = [];
79
+ scanner.skipWhitespace();
80
+ while (!scanner.isEOF() && scanner.peek() !== '\r' && scanner.peek() !== '\n') {
81
+ const formatStr = (0, primitive_parser_1.parseToken)(scanner);
82
+ formats.push((0, primitives_1.createFormat)(formatStr));
83
+ scanner.skipWhitespace();
84
+ }
85
+ if (formats.length === 0) {
86
+ throw new errors_1.ParseError('at least one media format is required', start.line, start.column);
87
+ }
88
+ return (0, media_1.createMedia)(mediaType, port, proto, formats);
89
+ }
90
+ // ============================================================================
91
+ // Media Description Parser
92
+ // ============================================================================
93
+ /**
94
+ * Parse media description
95
+ *
96
+ * A media description consists of:
97
+ * - One media field (m=) [required]
98
+ * - Optional information field (i=)
99
+ * - Zero or more connection fields (c=)
100
+ * - Zero or more bandwidth fields (b=)
101
+ * - Optional encryption key field (k=)
102
+ * - Zero or more attribute fields (a=)
103
+ *
104
+ * The media field has already been identified, but not yet parsed.
105
+ *
106
+ * @param scanner - Scanner positioned at the start of media field value (after "m=")
107
+ * @param additionalLines - Additional lines that might belong to this media description
108
+ * @returns MediaDescription object
109
+ * @throws ParseError if invalid format
110
+ */
111
+ function parseMediaDescription(mediaFieldScanner, additionalLines) {
112
+ // Parse the media field (m=)
113
+ const media = parseMediaField(mediaFieldScanner);
114
+ // Parse optional fields
115
+ let information;
116
+ const connections = [];
117
+ const bandwidths = [];
118
+ let key;
119
+ const attributes = [];
120
+ for (const line of additionalLines) {
121
+ const scanner = new scanner_1.Scanner(line.value);
122
+ switch (line.type) {
123
+ case 'i':
124
+ if (information !== undefined) {
125
+ throw new errors_1.ParseError('Duplicate information field in media description', 0, 0);
126
+ }
127
+ information = (0, field_parser_1.parseInformationField)(scanner);
128
+ break;
129
+ case 'c':
130
+ connections.push((0, field_parser_1.parseConnectionField)(scanner));
131
+ break;
132
+ case 'b':
133
+ bandwidths.push((0, field_parser_1.parseBandwidthField)(scanner));
134
+ break;
135
+ case 'k':
136
+ if (key !== undefined) {
137
+ throw new errors_1.ParseError('Duplicate key field in media description', 0, 0);
138
+ }
139
+ key = (0, field_parser_1.parseKeyField)(scanner);
140
+ break;
141
+ case 'a':
142
+ attributes.push((0, attribute_parser_1.parseAttributeField)(scanner));
143
+ break;
144
+ default:
145
+ // Unknown field type - this shouldn't happen if called correctly
146
+ // Media description ends when we encounter another m= or end of input
147
+ throw new errors_1.ParseError(`Unexpected field type in media description: ${line.type}=`, 0, 0);
148
+ }
149
+ }
150
+ return (0, media_1.createMediaDescription)(media, {
151
+ information,
152
+ connections,
153
+ bandwidths,
154
+ key,
155
+ attributes,
156
+ });
157
+ }