@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,447 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for session-validator.ts
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const session_validator_1 = require("../../../src/validator/session-validator");
7
+ const session_1 = require("../../../src/types/session");
8
+ const fields_1 = require("../../../src/types/fields");
9
+ const time_1 = require("../../../src/types/time");
10
+ const network_1 = require("../../../src/types/network");
11
+ const attributes_1 = require("../../../src/types/attributes");
12
+ describe('session-validator', () => {
13
+ describe('validateSessionDescription', () => {
14
+ describe('valid sessions', () => {
15
+ it('should validate minimal valid session', () => {
16
+ const session = (0, session_1.createSessionDescription)({
17
+ version: 0,
18
+ origin: (0, fields_1.createOrigin)('jdoe', '2890844526', '2890842807', 'IN', 'IP4', (0, network_1.createIP4Address)('10.47.16.5')),
19
+ sessionName: 'Test Session',
20
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
21
+ });
22
+ const result = (0, session_validator_1.validateSessionDescription)(session);
23
+ expect(result.valid).toBe(true);
24
+ expect(result.errors).toHaveLength(0);
25
+ });
26
+ it('should validate session with connection', () => {
27
+ const session = (0, session_1.createSessionDescription)({
28
+ version: 0,
29
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
30
+ sessionName: 'Test',
31
+ connection: (0, fields_1.createConnection)('IN', 'IP4', (0, network_1.createIP4MulticastAddress)((0, network_1.createIP4Address)('224.2.17.12'), 127)),
32
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
33
+ });
34
+ const result = (0, session_validator_1.validateSessionDescription)(session);
35
+ expect(result.valid).toBe(true);
36
+ });
37
+ it('should validate session with multiple time descriptions', () => {
38
+ const session = (0, session_1.createSessionDescription)({
39
+ version: 0,
40
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
41
+ sessionName: 'Test',
42
+ timeDescriptions: [
43
+ (0, time_1.createTimeDescription)((0, time_1.createTiming)(2873397496, 2873404696)),
44
+ (0, time_1.createTimeDescription)((0, time_1.createTiming)(3034423619, 3042462419)),
45
+ ],
46
+ });
47
+ const result = (0, session_validator_1.validateSessionDescription)(session);
48
+ expect(result.valid).toBe(true);
49
+ });
50
+ it('should validate session with bandwidth', () => {
51
+ const session = (0, session_1.createSessionDescription)({
52
+ version: 0,
53
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
54
+ sessionName: 'Test',
55
+ bandwidths: [(0, fields_1.createBandwidth)('AS', 128)],
56
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
57
+ });
58
+ const result = (0, session_validator_1.validateSessionDescription)(session);
59
+ expect(result.valid).toBe(true);
60
+ });
61
+ it('should validate session with single direction attribute', () => {
62
+ const session = (0, session_1.createSessionDescription)({
63
+ version: 0,
64
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
65
+ sessionName: 'Test',
66
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
67
+ attributes: [(0, attributes_1.createRecvonly)()],
68
+ });
69
+ const result = (0, session_validator_1.validateSessionDescription)(session);
70
+ expect(result.valid).toBe(true);
71
+ });
72
+ });
73
+ describe('invalid sessions', () => {
74
+ it('should reject empty session name', () => {
75
+ const session = {
76
+ version: 0,
77
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
78
+ sessionName: '',
79
+ emails: [],
80
+ phones: [],
81
+ bandwidths: [],
82
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
83
+ attributes: [],
84
+ mediaDescriptions: [],
85
+ };
86
+ const result = (0, session_validator_1.validateSessionDescription)(session);
87
+ expect(result.valid).toBe(false);
88
+ expect(result.errors.some((e) => e.field === 'sessionName')).toBe(true);
89
+ });
90
+ it('should reject missing time descriptions', () => {
91
+ const session = {
92
+ version: 0,
93
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
94
+ sessionName: 'Test',
95
+ emails: [],
96
+ phones: [],
97
+ bandwidths: [],
98
+ timeDescriptions: [],
99
+ attributes: [],
100
+ mediaDescriptions: [],
101
+ };
102
+ const result = (0, session_validator_1.validateSessionDescription)(session);
103
+ expect(result.valid).toBe(false);
104
+ expect(result.errors.some((e) => e.constraint === 'RFC8866:5.9')).toBe(true);
105
+ });
106
+ it('should reject start time > stop time', () => {
107
+ const session = {
108
+ version: 0,
109
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
110
+ sessionName: 'Test',
111
+ emails: [],
112
+ phones: [],
113
+ bandwidths: [],
114
+ timeDescriptions: [
115
+ {
116
+ timing: { startTime: 2000, stopTime: 1000 },
117
+ repeats: [],
118
+ },
119
+ ],
120
+ attributes: [],
121
+ mediaDescriptions: [],
122
+ };
123
+ const result = (0, session_validator_1.validateSessionDescription)(session);
124
+ expect(result.valid).toBe(false);
125
+ expect(result.errors.some((e) => e.message.includes('start time'))).toBe(true);
126
+ });
127
+ it('should reject multiple direction attributes', () => {
128
+ const session = {
129
+ version: 0,
130
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
131
+ sessionName: 'Test',
132
+ emails: [],
133
+ phones: [],
134
+ bandwidths: [],
135
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
136
+ attributes: [(0, attributes_1.createRecvonly)(), (0, attributes_1.createSendrecv)()],
137
+ mediaDescriptions: [],
138
+ };
139
+ const result = (0, session_validator_1.validateSessionDescription)(session);
140
+ expect(result.valid).toBe(false);
141
+ expect(result.errors.some((e) => e.constraint === 'RFC8866:6.7')).toBe(true);
142
+ });
143
+ it('should reject negative bandwidth', () => {
144
+ const session = {
145
+ version: 0,
146
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
147
+ sessionName: 'Test',
148
+ emails: [],
149
+ phones: [],
150
+ bandwidths: [{ type: 'AS', value: -1 }],
151
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
152
+ attributes: [],
153
+ mediaDescriptions: [],
154
+ };
155
+ const result = (0, session_validator_1.validateSessionDescription)(session);
156
+ expect(result.valid).toBe(false);
157
+ expect(result.errors.some((e) => { var _a; return (_a = e.field) === null || _a === void 0 ? void 0 : _a.includes('bandwidth'); })).toBe(true);
158
+ });
159
+ it('should reject media-only attribute ptime at session level (RFC 8866 Section 6.4)', () => {
160
+ const session = {
161
+ version: 0,
162
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
163
+ sessionName: 'Test',
164
+ emails: [],
165
+ phones: [],
166
+ bandwidths: [],
167
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
168
+ attributes: [(0, attributes_1.createValueAttribute)('ptime', '20')],
169
+ mediaDescriptions: [],
170
+ };
171
+ const result = (0, session_validator_1.validateSessionDescription)(session);
172
+ expect(result.valid).toBe(false);
173
+ expect(result.errors.some((e) => e.message.includes('ptime') && e.message.includes('media level'))).toBe(true);
174
+ });
175
+ it('should reject media-only attribute rtpmap at session level (RFC 8866 Section 6.6)', () => {
176
+ const session = {
177
+ version: 0,
178
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
179
+ sessionName: 'Test',
180
+ emails: [],
181
+ phones: [],
182
+ bandwidths: [],
183
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
184
+ attributes: [(0, attributes_1.createValueAttribute)('rtpmap', '0 PCMU/8000')],
185
+ mediaDescriptions: [],
186
+ };
187
+ const result = (0, session_validator_1.validateSessionDescription)(session);
188
+ expect(result.valid).toBe(false);
189
+ expect(result.errors.some((e) => e.message.includes('rtpmap') && e.message.includes('media level'))).toBe(true);
190
+ });
191
+ it('should reject media-only attribute fmtp at session level (RFC 8866 Section 6.15)', () => {
192
+ const session = {
193
+ version: 0,
194
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
195
+ sessionName: 'Test',
196
+ emails: [],
197
+ phones: [],
198
+ bandwidths: [],
199
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
200
+ attributes: [(0, attributes_1.createValueAttribute)('fmtp', '96 profile-level-id=42e01f')],
201
+ mediaDescriptions: [],
202
+ };
203
+ const result = (0, session_validator_1.validateSessionDescription)(session);
204
+ expect(result.valid).toBe(false);
205
+ expect(result.errors.some((e) => e.message.includes('fmtp') && e.message.includes('media level'))).toBe(true);
206
+ });
207
+ it('should reject all media-only attributes at session level', () => {
208
+ // Test all media-only attributes: ptime, maxptime, rtpmap, fmtp, orient, framerate, quality
209
+ const mediaOnlyAttrs = ['ptime', 'maxptime', 'rtpmap', 'fmtp', 'orient', 'framerate', 'quality'];
210
+ for (const attrName of mediaOnlyAttrs) {
211
+ const session = {
212
+ version: 0,
213
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
214
+ sessionName: 'Test',
215
+ emails: [],
216
+ phones: [],
217
+ bandwidths: [],
218
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
219
+ attributes: [(0, attributes_1.createValueAttribute)(attrName, 'test')],
220
+ mediaDescriptions: [],
221
+ };
222
+ const result = (0, session_validator_1.validateSessionDescription)(session);
223
+ expect(result.valid).toBe(false);
224
+ expect(result.errors.some((e) => e.message.includes(attrName) && e.message.includes('media level'))).toBe(true);
225
+ }
226
+ });
227
+ it('should reject non-numeric session ID (RFC 8866 Section 5.2)', () => {
228
+ const session = {
229
+ version: 0,
230
+ origin: {
231
+ username: 'jdoe',
232
+ sessId: 'abc123', // Non-numeric session ID
233
+ sessVersion: '456',
234
+ netType: 'IN',
235
+ addrType: 'IP4',
236
+ unicastAddress: (0, network_1.createIP4Address)('10.0.0.1'),
237
+ },
238
+ sessionName: 'Test',
239
+ emails: [],
240
+ phones: [],
241
+ bandwidths: [],
242
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
243
+ attributes: [],
244
+ mediaDescriptions: [],
245
+ };
246
+ const result = (0, session_validator_1.validateSessionDescription)(session);
247
+ expect(result.valid).toBe(false);
248
+ expect(result.errors.some((e) => e.field === 'origin.sessId' &&
249
+ e.message.includes('only digits'))).toBe(true);
250
+ });
251
+ it('should reject non-numeric session version (RFC 8866 Section 5.2)', () => {
252
+ const session = {
253
+ version: 0,
254
+ origin: {
255
+ username: 'jdoe',
256
+ sessId: '123',
257
+ sessVersion: '456abc', // Non-numeric session version
258
+ netType: 'IN',
259
+ addrType: 'IP4',
260
+ unicastAddress: (0, network_1.createIP4Address)('10.0.0.1'),
261
+ },
262
+ sessionName: 'Test',
263
+ emails: [],
264
+ phones: [],
265
+ bandwidths: [],
266
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
267
+ attributes: [],
268
+ mediaDescriptions: [],
269
+ };
270
+ const result = (0, session_validator_1.validateSessionDescription)(session);
271
+ expect(result.valid).toBe(false);
272
+ expect(result.errors.some((e) => e.field === 'origin.sessVersion' &&
273
+ e.message.includes('only digits'))).toBe(true);
274
+ });
275
+ it('should reject session ID with special characters', () => {
276
+ const session = {
277
+ version: 0,
278
+ origin: {
279
+ username: 'jdoe',
280
+ sessId: '123-456', // Contains hyphen
281
+ sessVersion: '789',
282
+ netType: 'IN',
283
+ addrType: 'IP4',
284
+ unicastAddress: (0, network_1.createIP4Address)('10.0.0.1'),
285
+ },
286
+ sessionName: 'Test',
287
+ emails: [],
288
+ phones: [],
289
+ bandwidths: [],
290
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
291
+ attributes: [],
292
+ mediaDescriptions: [],
293
+ };
294
+ const result = (0, session_validator_1.validateSessionDescription)(session);
295
+ expect(result.valid).toBe(false);
296
+ expect(result.errors.some((e) => e.field === 'origin.sessId' &&
297
+ e.message.includes('only digits'))).toBe(true);
298
+ });
299
+ it('should accept valid numeric session ID and version', () => {
300
+ const session = (0, session_1.createSessionDescription)({
301
+ version: 0,
302
+ origin: (0, fields_1.createOrigin)('jdoe', '2890844526', // Valid numeric session ID
303
+ '2890842807', // Valid numeric session version
304
+ 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
305
+ sessionName: 'Test',
306
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
307
+ });
308
+ const result = (0, session_validator_1.validateSessionDescription)(session);
309
+ expect(result.valid).toBe(true);
310
+ });
311
+ it('should reject invalid email format (RFC 8866 Section 5.6)', () => {
312
+ const session = {
313
+ version: 0,
314
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
315
+ sessionName: 'Test',
316
+ emails: ['invalid-email'],
317
+ phones: [],
318
+ bandwidths: [],
319
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
320
+ attributes: [],
321
+ mediaDescriptions: [],
322
+ };
323
+ const result = (0, session_validator_1.validateSessionDescription)(session);
324
+ expect(result.valid).toBe(false);
325
+ expect(result.errors.some((e) => e.field === 'emails[0]' &&
326
+ e.constraint === 'RFC8866:5.6')).toBe(true);
327
+ });
328
+ it('should accept valid email formats (RFC 8866 Section 5.6)', () => {
329
+ const session = {
330
+ version: 0,
331
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
332
+ sessionName: 'Test',
333
+ emails: [
334
+ 'j.doe@example.com',
335
+ 'j.doe@example.com (Jane Doe)',
336
+ 'Jane Doe <j.doe@example.com>',
337
+ ],
338
+ phones: [],
339
+ bandwidths: [],
340
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
341
+ attributes: [],
342
+ mediaDescriptions: [],
343
+ };
344
+ const result = (0, session_validator_1.validateSessionDescription)(session);
345
+ expect(result.valid).toBe(true);
346
+ });
347
+ it('should reject invalid phone format (RFC 8866 Section 5.6)', () => {
348
+ const session = {
349
+ version: 0,
350
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
351
+ sessionName: 'Test',
352
+ emails: [],
353
+ phones: ['invalid-phone'],
354
+ bandwidths: [],
355
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
356
+ attributes: [],
357
+ mediaDescriptions: [],
358
+ };
359
+ const result = (0, session_validator_1.validateSessionDescription)(session);
360
+ expect(result.valid).toBe(false);
361
+ expect(result.errors.some((e) => e.field === 'phones[0]' &&
362
+ e.constraint === 'RFC8866:5.6')).toBe(true);
363
+ });
364
+ it('should accept valid phone formats (RFC 8866 Section 5.6)', () => {
365
+ const session = {
366
+ version: 0,
367
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
368
+ sessionName: 'Test',
369
+ emails: [],
370
+ phones: [
371
+ '+1 617 555-6011',
372
+ '+1-617-555-6011 (Jane Doe)',
373
+ 'Jane Doe <+1 617 555-6011>',
374
+ ],
375
+ bandwidths: [],
376
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
377
+ attributes: [],
378
+ mediaDescriptions: [],
379
+ };
380
+ const result = (0, session_validator_1.validateSessionDescription)(session);
381
+ expect(result.valid).toBe(true);
382
+ });
383
+ it('should reject invalid URI format (RFC 8866 Section 5.5)', () => {
384
+ const session = {
385
+ version: 0,
386
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
387
+ sessionName: 'Test',
388
+ uri: 'http://example.com/path with spaces',
389
+ emails: [],
390
+ phones: [],
391
+ bandwidths: [],
392
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
393
+ attributes: [],
394
+ mediaDescriptions: [],
395
+ };
396
+ const result = (0, session_validator_1.validateSessionDescription)(session);
397
+ expect(result.valid).toBe(false);
398
+ expect(result.errors.some((e) => e.field === 'uri' &&
399
+ e.constraint === 'RFC8866:5.5')).toBe(true);
400
+ });
401
+ it('should accept valid URI formats (RFC 8866 Section 5.5)', () => {
402
+ const session = {
403
+ version: 0,
404
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
405
+ sessionName: 'Test',
406
+ uri: 'http://www.example.com/session',
407
+ emails: [],
408
+ phones: [],
409
+ bandwidths: [],
410
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
411
+ attributes: [],
412
+ mediaDescriptions: [],
413
+ };
414
+ const result = (0, session_validator_1.validateSessionDescription)(session);
415
+ expect(result.valid).toBe(true);
416
+ });
417
+ it('should accept URI with query string and fragment (RFC 8866 Section 5.5)', () => {
418
+ const session = {
419
+ version: 0,
420
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
421
+ sessionName: 'Test',
422
+ uri: 'https://conference.example.org/meeting?id=123#info',
423
+ emails: [],
424
+ phones: [],
425
+ bandwidths: [],
426
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
427
+ attributes: [],
428
+ mediaDescriptions: [],
429
+ };
430
+ const result = (0, session_validator_1.validateSessionDescription)(session);
431
+ expect(result.valid).toBe(true);
432
+ });
433
+ });
434
+ describe('connection coverage', () => {
435
+ it('should allow session without media and no connection', () => {
436
+ const session = (0, session_1.createSessionDescription)({
437
+ version: 0,
438
+ origin: (0, fields_1.createOrigin)('jdoe', '123', '456', 'IN', 'IP4', (0, network_1.createIP4Address)('10.0.0.1')),
439
+ sessionName: 'Test',
440
+ timeDescriptions: [(0, time_1.createTimeDescription)((0, time_1.createTiming)(0, 0))],
441
+ });
442
+ const result = (0, session_validator_1.validateSessionDescription)(session);
443
+ expect(result.valid).toBe(true);
444
+ });
445
+ });
446
+ });
447
+ });
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "https://www.schemastore.org/package.json",
3
+ "name": "@tomgiee/tsdp",
4
+ "version": "1.0.0",
5
+ "description": "RFC 8866-compliant SDP parser, serializer, and validator for TypeScript",
6
+ "author": "Thomas Giesler <tgiesler@brassnode.net>",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/tomgie/tsdp.git"
11
+ },
12
+ "homepage": "https://github.com/tomgie/tsdp#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/tomgie/tsdp/issues"
15
+ },
16
+ "keywords": [
17
+ "parser",
18
+ "typescript",
19
+ "serializer",
20
+ "sip",
21
+ "webrtc",
22
+ "sdp",
23
+ "voip",
24
+ "rfc8866"
25
+ ],
26
+ "main": "dist/src/index.js",
27
+ "types": "dist/src/index.d.ts",
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "test": "jest",
39
+ "test:watch": "jest --watch",
40
+ "test:coverage": "jest --coverage"
41
+ },
42
+ "devDependencies": {
43
+ "@types/jest": "^30.0.0",
44
+ "@types/node": "^25.0.6",
45
+ "jest": "^30.2.0",
46
+ "ts-jest": "^29.4.6",
47
+ "ts-node": "^10.9.2",
48
+ "typescript": "^5.5.3"
49
+ }
50
+ }