@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,241 @@
1
+ "use strict";
2
+ /**
3
+ * Media validators for SDP (RFC 8866)
4
+ *
5
+ * Validates media descriptions and media-level constraints per RFC 8866.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.validateMediaDescription = validateMediaDescription;
9
+ exports.validateAllMediaDescriptions = validateAllMediaDescriptions;
10
+ const errors_1 = require("../types/errors");
11
+ const session_validator_1 = require("./session-validator");
12
+ const attributes_1 = require("../types/attributes");
13
+ // ============================================================================
14
+ // Media Description Validator
15
+ // ============================================================================
16
+ /**
17
+ * Validate a media description
18
+ *
19
+ * Checks RFC 8866 Section 5.14 (m=) and media-level field requirements.
20
+ *
21
+ * @param media - Media description to validate
22
+ * @param index - Index of media description (for error messages)
23
+ * @param hasSessionConnection - Whether session-level connection exists
24
+ * @returns ValidationResult with any errors found
25
+ */
26
+ function validateMediaDescription(media, index, hasSessionConnection) {
27
+ const errors = [];
28
+ const prefix = `mediaDescriptions[${index}]`;
29
+ // 1. Validate media field (RFC 8866 Section 5.14)
30
+ validateMediaField(media, prefix, errors);
31
+ // 2. Validate port (RFC 8866 Section 5.14)
32
+ validatePort(media, prefix, errors);
33
+ // 3. Validate formats (RFC 8866 Section 5.14)
34
+ validateFormats(media, prefix, errors);
35
+ // 4. Validate connection coverage (RFC 8866 Section 5.7)
36
+ if (!hasSessionConnection) {
37
+ validateMediaConnection(media, prefix, errors);
38
+ }
39
+ // 5. Validate bandwidths (RFC 8866 Section 5.8)
40
+ validateMediaBandwidths(media, prefix, errors);
41
+ // 6. Validate attributes (RFC 8866 Section 6)
42
+ validateMediaAttributes(media, prefix, errors);
43
+ return errors.length === 0 ? (0, session_validator_1.validResult)() : (0, session_validator_1.invalidResult)(errors);
44
+ }
45
+ /**
46
+ * Validate media field
47
+ *
48
+ * RFC 8866 Section 5.14: Media type and protocol are required
49
+ */
50
+ function validateMediaField(media, prefix, errors) {
51
+ // Validate media type
52
+ if (!media.media.type || media.media.type.length === 0) {
53
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.media.type`, media.media.type, 'Media type cannot be empty', 'RFC8866:5.14'));
54
+ }
55
+ // Validate protocol
56
+ if (!media.media.proto || media.media.proto.length === 0) {
57
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.media.proto`, media.media.proto, 'Protocol cannot be empty', 'RFC8866:5.14'));
58
+ }
59
+ }
60
+ /**
61
+ * Validate port
62
+ *
63
+ * RFC 8866 Section 5.14: Port must be 0-65535
64
+ */
65
+ function validatePort(media, prefix, errors) {
66
+ const port = media.media.port;
67
+ if (port.kind === 'simple') {
68
+ if (port.value < 0 || port.value > 65535) {
69
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.media.port`, port.value, 'Port must be 0-65535', 'RFC8866:5.14'));
70
+ }
71
+ }
72
+ else if (port.kind === 'range') {
73
+ if (port.base < 0 || port.base > 65535) {
74
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.media.port.base`, port.base, 'Port must be 0-65535', 'RFC8866:5.14'));
75
+ }
76
+ if (port.count < 1) {
77
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.media.port.count`, port.count, 'Port count must be at least 1', 'RFC8866:5.14'));
78
+ }
79
+ // Check that port range doesn't exceed 65535
80
+ if (port.base + port.count - 1 > 65535) {
81
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: port range ${port.base}/${port.count} exceeds 65535`, 'RFC8866:5.14'));
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Validate formats
87
+ *
88
+ * RFC 8866 Section 5.14: At least one format is required
89
+ */
90
+ function validateFormats(media, prefix, errors) {
91
+ if (!media.media.formats || media.media.formats.length === 0) {
92
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: at least one media format is required`, 'RFC8866:5.14'));
93
+ }
94
+ }
95
+ /**
96
+ * Validate media-level connection
97
+ *
98
+ * RFC 8866 Section 5.7: Connection required if no session-level connection
99
+ */
100
+ function validateMediaConnection(media, prefix, errors) {
101
+ if (!media.connections || media.connections.length === 0) {
102
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: connection information required when no session-level connection`, 'RFC8866:5.7'));
103
+ }
104
+ }
105
+ /**
106
+ * Validate media-level bandwidths
107
+ *
108
+ * RFC 8866 Section 5.8: Bandwidth values must be non-negative
109
+ */
110
+ function validateMediaBandwidths(media, prefix, errors) {
111
+ for (let i = 0; i < media.bandwidths.length; i++) {
112
+ const bw = media.bandwidths[i];
113
+ if (bw.value < 0) {
114
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.bandwidths[${i}].value`, bw.value, 'Bandwidth value must be non-negative', 'RFC8866:5.8'));
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * Validate media-level attributes
120
+ *
121
+ * RFC 8866 Section 6: Various attribute constraints including:
122
+ * - Only one direction attribute allowed
123
+ * - Session-only attributes cannot appear at media level
124
+ * - At most one rtpmap per payload type (RFC 8866 Section 6.6)
125
+ * - At most one fmtp per format (RFC 8866 Section 6.15)
126
+ * - ptime, maxptime, quality, framerate value constraints
127
+ */
128
+ function validateMediaAttributes(media, prefix, errors) {
129
+ const directionAttrs = ['sendrecv', 'sendonly', 'recvonly', 'inactive'];
130
+ let directionCount = 0;
131
+ // Track rtpmap payload types for duplicate detection and format validation
132
+ const rtpmapPayloadTypes = new Set();
133
+ const rtpmapPayloadTypeDuplicates = new Set();
134
+ // Track fmtp formats for duplicate detection and format validation
135
+ const fmtpFormats = new Set();
136
+ const fmtpFormatDuplicates = new Set();
137
+ for (const attr of media.attributes) {
138
+ // Check for multiple direction attributes (only one allowed)
139
+ if (attr.kind === 'property' && directionAttrs.includes(attr.name)) {
140
+ directionCount++;
141
+ }
142
+ // Check for session-only attributes at media level (RFC 8866 Section 6)
143
+ if (attributes_1.SESSION_ONLY_ATTRIBUTES.includes(attr.name)) {
144
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: attribute '${attr.name}' is only valid at session level, not media level`, 'RFC8866:6'));
145
+ }
146
+ // Check rtpmap payload types (should match format list, at most one per payload type)
147
+ if (attr.kind === 'value' && attr.name === 'rtpmap') {
148
+ const match = attr.value.match(/^(\d+)\s/);
149
+ if (match) {
150
+ const payloadType = match[1];
151
+ if (rtpmapPayloadTypes.has(payloadType)) {
152
+ rtpmapPayloadTypeDuplicates.add(payloadType);
153
+ }
154
+ else {
155
+ rtpmapPayloadTypes.add(payloadType);
156
+ }
157
+ }
158
+ }
159
+ // Check fmtp formats (should have corresponding rtpmap or be in format list, at most one per format)
160
+ if (attr.kind === 'value' && attr.name === 'fmtp') {
161
+ const match = attr.value.match(/^(\S+)\s/);
162
+ if (match) {
163
+ const format = match[1];
164
+ if (fmtpFormats.has(format)) {
165
+ fmtpFormatDuplicates.add(format);
166
+ }
167
+ else {
168
+ fmtpFormats.add(format);
169
+ }
170
+ }
171
+ }
172
+ // Validate ptime is positive
173
+ if (attr.kind === 'value' && attr.name === 'ptime') {
174
+ const ptime = parseFloat(attr.value);
175
+ if (isNaN(ptime) || ptime <= 0) {
176
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.attributes.ptime`, attr.value, 'ptime must be a positive number', 'RFC8866:6.4'));
177
+ }
178
+ }
179
+ // Validate maxptime is positive
180
+ if (attr.kind === 'value' && attr.name === 'maxptime') {
181
+ const maxptime = parseFloat(attr.value);
182
+ if (isNaN(maxptime) || maxptime <= 0) {
183
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.attributes.maxptime`, attr.value, 'maxptime must be a positive number', 'RFC8866:6.5'));
184
+ }
185
+ }
186
+ // Validate quality is 0-10
187
+ if (attr.kind === 'value' && attr.name === 'quality') {
188
+ const quality = parseInt(attr.value, 10);
189
+ if (isNaN(quality) || quality < 0 || quality > 10) {
190
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.attributes.quality`, attr.value, 'quality must be 0-10', 'RFC8866:6.14'));
191
+ }
192
+ }
193
+ // Validate framerate is positive
194
+ if (attr.kind === 'value' && attr.name === 'framerate') {
195
+ const framerate = parseFloat(attr.value);
196
+ if (isNaN(framerate) || framerate <= 0) {
197
+ errors.push(errors_1.ValidationError.invalidValue(`${prefix}.attributes.framerate`, attr.value, 'framerate must be a positive number', 'RFC8866:6.13'));
198
+ }
199
+ }
200
+ }
201
+ if (directionCount > 1) {
202
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: only one direction attribute (sendrecv/sendonly/recvonly/inactive) allowed`, 'RFC8866:6.7'));
203
+ }
204
+ // Validate no duplicate rtpmap entries for same payload type (RFC 8866 Section 6.6)
205
+ for (const pt of rtpmapPayloadTypeDuplicates) {
206
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: duplicate rtpmap for payload type ${pt} (at most one rtpmap per format allowed)`, 'RFC8866:6.6'));
207
+ }
208
+ // Validate no duplicate fmtp entries for same format (RFC 8866 Section 6.15)
209
+ for (const fmt of fmtpFormatDuplicates) {
210
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: duplicate fmtp for format ${fmt} (at most one fmtp per format allowed)`, 'RFC8866:6.15'));
211
+ }
212
+ // Validate rtpmap payload types are in format list
213
+ // Cast formats to strings for comparison since Format is a branded string
214
+ const formatStrings = media.media.formats.map((f) => f);
215
+ for (const pt of rtpmapPayloadTypes) {
216
+ if (!formatStrings.includes(pt)) {
217
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: rtpmap payload type ${pt} not in format list`, 'RFC8866:6.6'));
218
+ }
219
+ }
220
+ // Validate fmtp formats are in format list or have rtpmap
221
+ for (const fmt of fmtpFormats) {
222
+ if (!formatStrings.includes(fmt) && !rtpmapPayloadTypes.has(fmt)) {
223
+ errors.push(errors_1.ValidationError.constraintViolation(`${prefix}: fmtp format ${fmt} not in format list`, 'RFC8866:6.15'));
224
+ }
225
+ }
226
+ }
227
+ /**
228
+ * Validate all media descriptions in a session
229
+ *
230
+ * @param mediaDescriptions - Array of media descriptions
231
+ * @param hasSessionConnection - Whether session-level connection exists
232
+ * @returns ValidationResult with any errors found
233
+ */
234
+ function validateAllMediaDescriptions(mediaDescriptions, hasSessionConnection) {
235
+ const allErrors = [];
236
+ for (let i = 0; i < mediaDescriptions.length; i++) {
237
+ const result = validateMediaDescription(mediaDescriptions[i], i, hasSessionConnection);
238
+ allErrors.push(...result.errors);
239
+ }
240
+ return allErrors.length === 0 ? (0, session_validator_1.validResult)() : (0, session_validator_1.invalidResult)(allErrors);
241
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Semantic validators for SDP (RFC 8866)
3
+ *
4
+ * Cross-field semantic validation that checks relationships between fields.
5
+ */
6
+ import { SessionDescription } from '../types/session';
7
+ import { ValidationResult } from './session-validator';
8
+ /**
9
+ * Options for customizing validation behavior
10
+ */
11
+ export interface ValidationOptions {
12
+ /**
13
+ * Whether to skip semantic validation (cross-field checks)
14
+ */
15
+ skipSemanticValidation?: boolean;
16
+ /**
17
+ * Whether to allow deprecated k= field without warning
18
+ */
19
+ allowDeprecatedKeyField?: boolean;
20
+ /**
21
+ * Whether to require connection at session level
22
+ */
23
+ requireSessionConnection?: boolean;
24
+ }
25
+ /**
26
+ * Perform complete semantic validation of a session description
27
+ *
28
+ * This is the main validation entry point that checks:
29
+ * 1. Session-level validation
30
+ * 2. Media-level validation
31
+ * 3. Cross-field semantic constraints
32
+ *
33
+ * @param session - Session description to validate
34
+ * @param options - Optional validation options
35
+ * @returns ValidationResult with all errors found
36
+ */
37
+ export declare function validateSdp(session: SessionDescription, options?: ValidationOptions): ValidationResult;
38
+ /**
39
+ * Validate cross-field semantic constraints
40
+ *
41
+ * Checks relationships between different parts of the SDP.
42
+ *
43
+ * @param session - Session description to validate
44
+ * @returns ValidationResult with any errors found
45
+ */
46
+ export declare function validateSemanticConstraints(session: SessionDescription): ValidationResult;
47
+ //# sourceMappingURL=semantic-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-validator.d.ts","sourceRoot":"","sources":["../../../src/validator/semantic-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAIjB,MAAM,qBAAqB,CAAC;AAO7B;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;OAEG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC;;OAEG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CA0ClB;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,kBAAkB,GAAG,gBAAgB,CAmBzF"}
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ /**
3
+ * Semantic validators for SDP (RFC 8866)
4
+ *
5
+ * Cross-field semantic validation that checks relationships between fields.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.validateSdp = validateSdp;
9
+ exports.validateSemanticConstraints = validateSemanticConstraints;
10
+ const errors_1 = require("../types/errors");
11
+ const session_validator_1 = require("./session-validator");
12
+ const media_validator_1 = require("./media-validator");
13
+ /**
14
+ * Perform complete semantic validation of a session description
15
+ *
16
+ * This is the main validation entry point that checks:
17
+ * 1. Session-level validation
18
+ * 2. Media-level validation
19
+ * 3. Cross-field semantic constraints
20
+ *
21
+ * @param session - Session description to validate
22
+ * @param options - Optional validation options
23
+ * @returns ValidationResult with all errors found
24
+ */
25
+ function validateSdp(session, options = {}) {
26
+ const allErrors = [];
27
+ // 1. Session-level validation
28
+ const sessionResult = (0, session_validator_1.validateSessionDescription)(session);
29
+ allErrors.push(...sessionResult.errors);
30
+ // 2. Check for deprecated key field
31
+ if (!options.allowDeprecatedKeyField && session.key !== undefined) {
32
+ allErrors.push(errors_1.ValidationError.constraintViolation('The k= field is deprecated per RFC 8866', 'RFC8866:5.12'));
33
+ }
34
+ // 3. Require session-level connection if specified
35
+ if (options.requireSessionConnection && session.connection === undefined) {
36
+ allErrors.push(errors_1.ValidationError.constraintViolation('Session-level connection information is required', 'RFC8866:5.7'));
37
+ }
38
+ // 4. Media-level validation
39
+ const hasSessionConnection = session.connection !== undefined;
40
+ const mediaResult = (0, media_validator_1.validateAllMediaDescriptions)(session.mediaDescriptions, hasSessionConnection);
41
+ allErrors.push(...mediaResult.errors);
42
+ // 5. Cross-field semantic validation (if not skipped)
43
+ if (!options.skipSemanticValidation) {
44
+ const semanticResult = validateSemanticConstraints(session);
45
+ allErrors.push(...semanticResult.errors);
46
+ }
47
+ return allErrors.length === 0 ? (0, session_validator_1.validResult)() : (0, session_validator_1.invalidResult)(allErrors);
48
+ }
49
+ /**
50
+ * Validate cross-field semantic constraints
51
+ *
52
+ * Checks relationships between different parts of the SDP.
53
+ *
54
+ * @param session - Session description to validate
55
+ * @returns ValidationResult with any errors found
56
+ */
57
+ function validateSemanticConstraints(session) {
58
+ const errors = [];
59
+ // 1. Validate session vs media direction consistency
60
+ validateDirectionConsistency(session, errors);
61
+ // 2. Validate bandwidth constraints
62
+ validateBandwidthConsistency(session, errors);
63
+ // 3. Validate address type consistency
64
+ validateAddressTypeConsistency(session, errors);
65
+ // 4. Validate ptime/maxptime relationship
66
+ validatePtimeConsistency(session, errors);
67
+ // 5. Validate time description constraints
68
+ validateTimeConsistency(session, errors);
69
+ return errors.length === 0 ? (0, session_validator_1.validResult)() : (0, session_validator_1.invalidResult)(errors);
70
+ }
71
+ /**
72
+ * Validate direction attribute consistency
73
+ *
74
+ * If session has a direction attribute, media can override but should be compatible.
75
+ */
76
+ function validateDirectionConsistency(session, errors) {
77
+ const directionAttrs = ['sendrecv', 'sendonly', 'recvonly', 'inactive'];
78
+ // Find session-level direction
79
+ let sessionDirection;
80
+ for (const attr of session.attributes) {
81
+ if (attr.kind === 'property' && directionAttrs.includes(attr.name)) {
82
+ sessionDirection = attr.name;
83
+ break;
84
+ }
85
+ }
86
+ // If session is inactive, warn if any media has active direction
87
+ if (sessionDirection === 'inactive') {
88
+ for (let i = 0; i < session.mediaDescriptions.length; i++) {
89
+ const md = session.mediaDescriptions[i];
90
+ for (const attr of md.attributes) {
91
+ if (attr.kind === 'property' &&
92
+ (attr.name === 'sendrecv' || attr.name === 'sendonly' || attr.name === 'recvonly')) {
93
+ // This is a warning, not an error - media can override
94
+ // But we'll note it for completeness
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * Validate bandwidth consistency
102
+ *
103
+ * CT (Conference Total) should be >= sum of AS (Application Specific) for all media.
104
+ */
105
+ function validateBandwidthConsistency(session, errors) {
106
+ // Find session-level CT bandwidth
107
+ let sessionCT;
108
+ for (const bw of session.bandwidths) {
109
+ if (bw.type === 'CT') {
110
+ sessionCT = bw.value;
111
+ break;
112
+ }
113
+ }
114
+ if (sessionCT === undefined) {
115
+ return; // No CT to validate against
116
+ }
117
+ // Sum all AS bandwidths from media
118
+ let totalAS = 0;
119
+ for (const md of session.mediaDescriptions) {
120
+ for (const bw of md.bandwidths) {
121
+ if (bw.type === 'AS') {
122
+ totalAS += bw.value;
123
+ }
124
+ }
125
+ }
126
+ // Also add session-level AS
127
+ for (const bw of session.bandwidths) {
128
+ if (bw.type === 'AS') {
129
+ totalAS += bw.value;
130
+ }
131
+ }
132
+ if (totalAS > sessionCT) {
133
+ errors.push(errors_1.ValidationError.constraintViolation(`Sum of AS bandwidths (${totalAS}) exceeds CT bandwidth (${sessionCT})`, 'RFC8866:5.8'));
134
+ }
135
+ }
136
+ /**
137
+ * Validate address type consistency
138
+ *
139
+ * Origin address type should be compatible with connection address types.
140
+ */
141
+ function validateAddressTypeConsistency(session, errors) {
142
+ const originAddrType = session.origin.addrType;
143
+ // Check session-level connection
144
+ if (session.connection) {
145
+ const connAddrType = session.connection.addrType;
146
+ // Different address types are allowed, but we could warn
147
+ // For now, we don't enforce strict consistency
148
+ }
149
+ // Check media-level connections
150
+ for (let i = 0; i < session.mediaDescriptions.length; i++) {
151
+ const md = session.mediaDescriptions[i];
152
+ for (let j = 0; j < md.connections.length; j++) {
153
+ const conn = md.connections[j];
154
+ // Different address types are allowed per RFC
155
+ }
156
+ }
157
+ }
158
+ /**
159
+ * Validate ptime/maxptime relationship
160
+ *
161
+ * If both ptime and maxptime are specified, ptime should be <= maxptime.
162
+ *
163
+ * RFC 8866 Sections 6.4-6.5: Both values use non-zero-int-or-real,
164
+ * meaning they can be decimal values.
165
+ */
166
+ function validatePtimeConsistency(session, errors) {
167
+ for (let i = 0; i < session.mediaDescriptions.length; i++) {
168
+ const md = session.mediaDescriptions[i];
169
+ let ptime;
170
+ let maxptime;
171
+ for (const attr of md.attributes) {
172
+ if (attr.kind === 'value') {
173
+ if (attr.name === 'ptime') {
174
+ ptime = parseFloat(attr.value);
175
+ }
176
+ else if (attr.name === 'maxptime') {
177
+ maxptime = parseFloat(attr.value);
178
+ }
179
+ }
180
+ }
181
+ if (ptime !== undefined && maxptime !== undefined && ptime > maxptime) {
182
+ errors.push(errors_1.ValidationError.constraintViolation(`Media ${i}: ptime (${ptime}) exceeds maxptime (${maxptime})`, 'RFC8866:6.4-6.5'));
183
+ }
184
+ }
185
+ }
186
+ /**
187
+ * Validate time description constraints
188
+ *
189
+ * Check that repeat times and timezone adjustments are consistent.
190
+ */
191
+ function validateTimeConsistency(session, errors) {
192
+ for (let i = 0; i < session.timeDescriptions.length; i++) {
193
+ const td = session.timeDescriptions[i];
194
+ // Repeat times only make sense for bounded sessions
195
+ if (td.repeats.length > 0) {
196
+ const { startTime, stopTime } = td.timing;
197
+ if (startTime === 0 && stopTime === 0) {
198
+ // Repeat with unbounded session - technically valid but unusual
199
+ // Could add a warning here
200
+ }
201
+ }
202
+ // Timezone adjustments only make sense for bounded sessions with repeats
203
+ if (td.timezone && td.repeats.length === 0) {
204
+ errors.push(errors_1.ValidationError.constraintViolation(`Time description ${i}: timezone adjustment specified without repeat times`, 'RFC8866:5.11'));
205
+ }
206
+ }
207
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Session validators for SDP (RFC 8866)
3
+ *
4
+ * Validates session-level fields and constraints per RFC 8866.
5
+ */
6
+ import { SessionDescription } from '../types/session';
7
+ import { ValidationError } from '../types/errors';
8
+ /**
9
+ * Result of validation - either valid or contains errors
10
+ */
11
+ export interface ValidationResult {
12
+ readonly valid: boolean;
13
+ readonly errors: readonly ValidationError[];
14
+ }
15
+ /**
16
+ * Create a valid result
17
+ */
18
+ export declare function validResult(): ValidationResult;
19
+ /**
20
+ * Create an invalid result with errors
21
+ */
22
+ export declare function invalidResult(errors: ValidationError[]): ValidationResult;
23
+ /**
24
+ * Validate a complete session description
25
+ *
26
+ * Checks RFC 8866 Section 5 requirements:
27
+ * - Required fields are present (v, o, s, t)
28
+ * - Version is 0
29
+ * - At least one time description
30
+ * - Connection coverage (session or all media)
31
+ *
32
+ * @param session - Session description to validate
33
+ * @returns ValidationResult with any errors found
34
+ */
35
+ export declare function validateSessionDescription(session: SessionDescription): ValidationResult;
36
+ //# sourceMappingURL=session-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-validator.d.ts","sourceRoot":"","sources":["../../../src/validator/session-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AASlD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;CAC7C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAE9C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,gBAAgB,CAEzE;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,kBAAkB,GAAG,gBAAgB,CAkCxF"}