@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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/src/builder/media-builder.d.ts +221 -0
- package/dist/src/builder/media-builder.d.ts.map +1 -0
- package/dist/src/builder/media-builder.js +385 -0
- package/dist/src/builder/session-builder.d.ts +195 -0
- package/dist/src/builder/session-builder.d.ts.map +1 -0
- package/dist/src/builder/session-builder.js +366 -0
- package/dist/src/index.d.ts +67 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +250 -0
- package/dist/src/parser/attribute-parser.d.ts +100 -0
- package/dist/src/parser/attribute-parser.d.ts.map +1 -0
- package/dist/src/parser/attribute-parser.js +217 -0
- package/dist/src/parser/field-parser.d.ts +124 -0
- package/dist/src/parser/field-parser.d.ts.map +1 -0
- package/dist/src/parser/field-parser.js +335 -0
- package/dist/src/parser/media-parser.d.ts +45 -0
- package/dist/src/parser/media-parser.d.ts.map +1 -0
- package/dist/src/parser/media-parser.js +157 -0
- package/dist/src/parser/primitive-parser.d.ts +138 -0
- package/dist/src/parser/primitive-parser.d.ts.map +1 -0
- package/dist/src/parser/primitive-parser.js +316 -0
- package/dist/src/parser/scanner.d.ts +142 -0
- package/dist/src/parser/scanner.d.ts.map +1 -0
- package/dist/src/parser/scanner.js +284 -0
- package/dist/src/parser/session-parser.d.ts +35 -0
- package/dist/src/parser/session-parser.d.ts.map +1 -0
- package/dist/src/parser/session-parser.js +207 -0
- package/dist/src/parser/time-parser.d.ts +74 -0
- package/dist/src/parser/time-parser.d.ts.map +1 -0
- package/dist/src/parser/time-parser.js +168 -0
- package/dist/src/serializer/attribute-serializer.d.ts +18 -0
- package/dist/src/serializer/attribute-serializer.d.ts.map +1 -0
- package/dist/src/serializer/attribute-serializer.js +34 -0
- package/dist/src/serializer/field-serializer.d.ts +112 -0
- package/dist/src/serializer/field-serializer.d.ts.map +1 -0
- package/dist/src/serializer/field-serializer.js +212 -0
- package/dist/src/serializer/media-serializer.d.ts +31 -0
- package/dist/src/serializer/media-serializer.d.ts.map +1 -0
- package/dist/src/serializer/media-serializer.js +83 -0
- package/dist/src/serializer/session-serializer.d.ts +29 -0
- package/dist/src/serializer/session-serializer.d.ts.map +1 -0
- package/dist/src/serializer/session-serializer.js +99 -0
- package/dist/src/serializer/time-serializer.d.ts +46 -0
- package/dist/src/serializer/time-serializer.d.ts.map +1 -0
- package/dist/src/serializer/time-serializer.js +86 -0
- package/dist/src/types/attributes.d.ts +318 -0
- package/dist/src/types/attributes.d.ts.map +1 -0
- package/dist/src/types/attributes.js +225 -0
- package/dist/src/types/errors.d.ts +129 -0
- package/dist/src/types/errors.d.ts.map +1 -0
- package/dist/src/types/errors.js +186 -0
- package/dist/src/types/fields.d.ts +100 -0
- package/dist/src/types/fields.d.ts.map +1 -0
- package/dist/src/types/fields.js +48 -0
- package/dist/src/types/media.d.ts +148 -0
- package/dist/src/types/media.d.ts.map +1 -0
- package/dist/src/types/media.js +137 -0
- package/dist/src/types/network.d.ts +136 -0
- package/dist/src/types/network.d.ts.map +1 -0
- package/dist/src/types/network.js +130 -0
- package/dist/src/types/primitives.d.ts +193 -0
- package/dist/src/types/primitives.d.ts.map +1 -0
- package/dist/src/types/primitives.js +195 -0
- package/dist/src/types/session.d.ts +122 -0
- package/dist/src/types/session.d.ts.map +1 -0
- package/dist/src/types/session.js +81 -0
- package/dist/src/types/time.d.ts +129 -0
- package/dist/src/types/time.d.ts.map +1 -0
- package/dist/src/types/time.js +84 -0
- package/dist/src/utils/address-parser.d.ts +100 -0
- package/dist/src/utils/address-parser.d.ts.map +1 -0
- package/dist/src/utils/address-parser.js +338 -0
- package/dist/src/utils/format-validators.d.ts +77 -0
- package/dist/src/utils/format-validators.d.ts.map +1 -0
- package/dist/src/utils/format-validators.js +504 -0
- package/dist/src/utils/line-reader.d.ts +84 -0
- package/dist/src/utils/line-reader.d.ts.map +1 -0
- package/dist/src/utils/line-reader.js +169 -0
- package/dist/src/utils/time-converter.d.ts +99 -0
- package/dist/src/utils/time-converter.d.ts.map +1 -0
- package/dist/src/utils/time-converter.js +195 -0
- package/dist/src/validator/media-validator.d.ts +27 -0
- package/dist/src/validator/media-validator.d.ts.map +1 -0
- package/dist/src/validator/media-validator.js +241 -0
- package/dist/src/validator/semantic-validator.d.ts +47 -0
- package/dist/src/validator/semantic-validator.d.ts.map +1 -0
- package/dist/src/validator/semantic-validator.js +207 -0
- package/dist/src/validator/session-validator.d.ts +36 -0
- package/dist/src/validator/session-validator.d.ts.map +1 -0
- package/dist/src/validator/session-validator.js +280 -0
- package/dist/tests/integration/round-trip.test.d.ts +5 -0
- package/dist/tests/integration/round-trip.test.d.ts.map +1 -0
- package/dist/tests/integration/round-trip.test.js +320 -0
- package/dist/tests/integration/voip-examples.test.d.ts +5 -0
- package/dist/tests/integration/voip-examples.test.d.ts.map +1 -0
- package/dist/tests/integration/voip-examples.test.js +361 -0
- package/dist/tests/unit/builder/media-builder.test.d.ts +5 -0
- package/dist/tests/unit/builder/media-builder.test.d.ts.map +1 -0
- package/dist/tests/unit/builder/media-builder.test.js +524 -0
- package/dist/tests/unit/builder/session-builder.test.d.ts +5 -0
- package/dist/tests/unit/builder/session-builder.test.d.ts.map +1 -0
- package/dist/tests/unit/builder/session-builder.test.js +367 -0
- package/dist/tests/unit/parser/attribute-parser.test.d.ts +5 -0
- package/dist/tests/unit/parser/attribute-parser.test.d.ts.map +1 -0
- package/dist/tests/unit/parser/attribute-parser.test.js +319 -0
- package/dist/tests/unit/parser/field-parser.test.d.ts +5 -0
- package/dist/tests/unit/parser/field-parser.test.d.ts.map +1 -0
- package/dist/tests/unit/parser/field-parser.test.js +355 -0
- package/dist/tests/unit/parser/media-parser.test.d.ts +5 -0
- package/dist/tests/unit/parser/media-parser.test.d.ts.map +1 -0
- package/dist/tests/unit/parser/media-parser.test.js +241 -0
- package/dist/tests/unit/parser/primitive-parser.test.d.ts +5 -0
- package/dist/tests/unit/parser/primitive-parser.test.d.ts.map +1 -0
- package/dist/tests/unit/parser/primitive-parser.test.js +261 -0
- package/dist/tests/unit/parser/scanner.test.d.ts +5 -0
- package/dist/tests/unit/parser/scanner.test.d.ts.map +1 -0
- package/dist/tests/unit/parser/scanner.test.js +241 -0
- package/dist/tests/unit/parser/session-parser.test.d.ts +5 -0
- package/dist/tests/unit/parser/session-parser.test.d.ts.map +1 -0
- package/dist/tests/unit/parser/session-parser.test.js +346 -0
- package/dist/tests/unit/parser/time-parser.test.d.ts +5 -0
- package/dist/tests/unit/parser/time-parser.test.d.ts.map +1 -0
- package/dist/tests/unit/parser/time-parser.test.js +173 -0
- package/dist/tests/unit/serializer/attribute-serializer.test.d.ts +5 -0
- package/dist/tests/unit/serializer/attribute-serializer.test.d.ts.map +1 -0
- package/dist/tests/unit/serializer/attribute-serializer.test.js +78 -0
- package/dist/tests/unit/serializer/field-serializer.test.d.ts +5 -0
- package/dist/tests/unit/serializer/field-serializer.test.d.ts.map +1 -0
- package/dist/tests/unit/serializer/field-serializer.test.js +159 -0
- package/dist/tests/unit/serializer/media-serializer.test.d.ts +5 -0
- package/dist/tests/unit/serializer/media-serializer.test.d.ts.map +1 -0
- package/dist/tests/unit/serializer/media-serializer.test.js +155 -0
- package/dist/tests/unit/serializer/session-serializer.test.d.ts +5 -0
- package/dist/tests/unit/serializer/session-serializer.test.d.ts.map +1 -0
- package/dist/tests/unit/serializer/session-serializer.test.js +317 -0
- package/dist/tests/unit/serializer/time-serializer.test.d.ts +5 -0
- package/dist/tests/unit/serializer/time-serializer.test.d.ts.map +1 -0
- package/dist/tests/unit/serializer/time-serializer.test.js +115 -0
- package/dist/tests/unit/types/errors.test.d.ts +5 -0
- package/dist/tests/unit/types/errors.test.d.ts.map +1 -0
- package/dist/tests/unit/types/errors.test.js +127 -0
- package/dist/tests/unit/types/network.test.d.ts +5 -0
- package/dist/tests/unit/types/network.test.d.ts.map +1 -0
- package/dist/tests/unit/types/network.test.js +132 -0
- package/dist/tests/unit/types/primitives.test.d.ts +5 -0
- package/dist/tests/unit/types/primitives.test.d.ts.map +1 -0
- package/dist/tests/unit/types/primitives.test.js +108 -0
- package/dist/tests/unit/utils/address-parser.test.d.ts +5 -0
- package/dist/tests/unit/utils/address-parser.test.d.ts.map +1 -0
- package/dist/tests/unit/utils/address-parser.test.js +203 -0
- package/dist/tests/unit/utils/format-validators.test.d.ts +5 -0
- package/dist/tests/unit/utils/format-validators.test.d.ts.map +1 -0
- package/dist/tests/unit/utils/format-validators.test.js +224 -0
- package/dist/tests/unit/utils/line-reader.test.d.ts +5 -0
- package/dist/tests/unit/utils/line-reader.test.d.ts.map +1 -0
- package/dist/tests/unit/utils/line-reader.test.js +157 -0
- package/dist/tests/unit/utils/time-converter.test.d.ts +5 -0
- package/dist/tests/unit/utils/time-converter.test.d.ts.map +1 -0
- package/dist/tests/unit/utils/time-converter.test.js +190 -0
- package/dist/tests/unit/validator/media-validator.test.d.ts +5 -0
- package/dist/tests/unit/validator/media-validator.test.d.ts.map +1 -0
- package/dist/tests/unit/validator/media-validator.test.js +313 -0
- package/dist/tests/unit/validator/semantic-validator.test.d.ts +5 -0
- package/dist/tests/unit/validator/semantic-validator.test.d.ts.map +1 -0
- package/dist/tests/unit/validator/semantic-validator.test.js +262 -0
- package/dist/tests/unit/validator/session-validator.test.d.ts +5 -0
- package/dist/tests/unit/validator/session-validator.test.d.ts.map +1 -0
- package/dist/tests/unit/validator/session-validator.test.js +447 -0
- package/package.json +50 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitive parsers for SDP (RFC 8866)
|
|
3
|
+
*
|
|
4
|
+
* Parsers for basic grammar elements: tokens, integers, addresses, times, etc.
|
|
5
|
+
*/
|
|
6
|
+
import { Scanner } from './scanner';
|
|
7
|
+
import { NtpTime, TypedTime } from '../types/primitives';
|
|
8
|
+
import { IP4Address, IP6Address, FQDN } from '../types/network';
|
|
9
|
+
/**
|
|
10
|
+
* Parse a token (RFC 8866 Section 9)
|
|
11
|
+
*
|
|
12
|
+
* token = 1*token-char
|
|
13
|
+
* token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E
|
|
14
|
+
*
|
|
15
|
+
* Essentially: any visible ASCII character except space and separators like ()[]{}:;,<>?=@\"
|
|
16
|
+
*
|
|
17
|
+
* @param scanner - Scanner instance
|
|
18
|
+
* @returns Parsed token string
|
|
19
|
+
* @throws ParseError if no valid token found
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseToken(scanner: Scanner): string;
|
|
22
|
+
/**
|
|
23
|
+
* Parse an integer
|
|
24
|
+
*
|
|
25
|
+
* @param scanner - Scanner instance
|
|
26
|
+
* @returns Parsed integer
|
|
27
|
+
* @throws ParseError if no valid integer found
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseInteger(scanner: Scanner): number;
|
|
30
|
+
/**
|
|
31
|
+
* Parse a byte (0-255)
|
|
32
|
+
*
|
|
33
|
+
* @param scanner - Scanner instance
|
|
34
|
+
* @returns Parsed byte value
|
|
35
|
+
* @throws ParseError if not a valid byte
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseByte(scanner: Scanner): number;
|
|
38
|
+
/**
|
|
39
|
+
* Parse a non-whitespace string
|
|
40
|
+
*
|
|
41
|
+
* Reads characters until whitespace or end of line.
|
|
42
|
+
*
|
|
43
|
+
* @param scanner - Scanner instance
|
|
44
|
+
* @returns Parsed string
|
|
45
|
+
* @throws ParseError if empty
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseNonWSString(scanner: Scanner): string;
|
|
48
|
+
/**
|
|
49
|
+
* Parse a byte-string (RFC 8866 Section 9)
|
|
50
|
+
*
|
|
51
|
+
* byte-string = 1*(%x01-09 / %x0B-0C / %x0E-FF)
|
|
52
|
+
* (any byte except NUL, CR, LF)
|
|
53
|
+
*
|
|
54
|
+
* @param scanner - Scanner instance
|
|
55
|
+
* @returns Parsed byte-string
|
|
56
|
+
* @throws ParseError if invalid characters found
|
|
57
|
+
*/
|
|
58
|
+
export declare function parseByteString(scanner: Scanner): string;
|
|
59
|
+
/**
|
|
60
|
+
* Parse an IPv4 address
|
|
61
|
+
*
|
|
62
|
+
* Format: a.b.c.d where each octet is 0-255
|
|
63
|
+
*
|
|
64
|
+
* @param scanner - Scanner instance
|
|
65
|
+
* @returns Parsed IPv4 address
|
|
66
|
+
* @throws ParseError if invalid IPv4 address
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseIP4Address(scanner: Scanner): IP4Address;
|
|
69
|
+
/**
|
|
70
|
+
* Parse an IPv6 address
|
|
71
|
+
*
|
|
72
|
+
* Supports all IPv6 formats including compression (::) and mixed IPv4
|
|
73
|
+
*
|
|
74
|
+
* @param scanner - Scanner instance
|
|
75
|
+
* @returns Parsed IPv6 address
|
|
76
|
+
* @throws ParseError if invalid IPv6 address
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseIP6Address(scanner: Scanner): IP6Address;
|
|
79
|
+
/**
|
|
80
|
+
* Parse an FQDN (Fully Qualified Domain Name)
|
|
81
|
+
*
|
|
82
|
+
* @param scanner - Scanner instance
|
|
83
|
+
* @returns Parsed FQDN
|
|
84
|
+
* @throws ParseError if invalid FQDN
|
|
85
|
+
*/
|
|
86
|
+
export declare function parseFQDN(scanner: Scanner): FQDN;
|
|
87
|
+
/**
|
|
88
|
+
* Parse an NTP timestamp
|
|
89
|
+
*
|
|
90
|
+
* NTP time is seconds since January 1, 1900 00:00:00 UTC
|
|
91
|
+
* 0 means unbounded/permanent
|
|
92
|
+
*
|
|
93
|
+
* @param scanner - Scanner instance
|
|
94
|
+
* @returns Parsed NTP time
|
|
95
|
+
* @throws ParseError if invalid
|
|
96
|
+
*/
|
|
97
|
+
export declare function parseNtpTime(scanner: Scanner): NtpTime;
|
|
98
|
+
/**
|
|
99
|
+
* Parse a typed time value
|
|
100
|
+
*
|
|
101
|
+
* Format: [-]<integer>[d|h|m|s]
|
|
102
|
+
* - d = days
|
|
103
|
+
* - h = hours
|
|
104
|
+
* - m = minutes
|
|
105
|
+
* - s or no suffix = seconds
|
|
106
|
+
* - Can be negative (for timezone offsets)
|
|
107
|
+
*
|
|
108
|
+
* @param scanner - Scanner instance
|
|
109
|
+
* @returns Parsed typed time
|
|
110
|
+
* @throws ParseError if invalid
|
|
111
|
+
*/
|
|
112
|
+
export declare function parseTypedTime(scanner: Scanner): TypedTime;
|
|
113
|
+
/**
|
|
114
|
+
* Parse an email address or display name with email
|
|
115
|
+
*
|
|
116
|
+
* Format: email-address / displayable-name <email-address>
|
|
117
|
+
*
|
|
118
|
+
* @param scanner - Scanner instance
|
|
119
|
+
* @returns Parsed email string
|
|
120
|
+
*/
|
|
121
|
+
export declare function parseEmail(scanner: Scanner): string;
|
|
122
|
+
/**
|
|
123
|
+
* Parse a phone number or display name with phone
|
|
124
|
+
*
|
|
125
|
+
* Format: phone-number / displayable-name <phone-number>
|
|
126
|
+
*
|
|
127
|
+
* @param scanner - Scanner instance
|
|
128
|
+
* @returns Parsed phone string
|
|
129
|
+
*/
|
|
130
|
+
export declare function parsePhone(scanner: Scanner): string;
|
|
131
|
+
/**
|
|
132
|
+
* Parse a URI
|
|
133
|
+
*
|
|
134
|
+
* @param scanner - Scanner instance
|
|
135
|
+
* @returns Parsed URI string
|
|
136
|
+
*/
|
|
137
|
+
export declare function parseURI(scanner: Scanner): string;
|
|
138
|
+
//# sourceMappingURL=primitive-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitive-parser.d.ts","sourceRoot":"","sources":["../../../src/parser/primitive-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAiB,MAAM,qBAAqB,CAAC;AACxE,OAAO,EACL,UAAU,EACV,UAAU,EACV,IAAI,EAIL,MAAM,kBAAkB,CAAC;AAY1B;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAanD;AAwBD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAuBrD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAalD;AAaD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAazD;AASD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAaxD;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAe5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAiB5D;AAaD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiBhD;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAatD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAiB1D;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAGnD;AAMD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAGjD"}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Primitive parsers for SDP (RFC 8866)
|
|
4
|
+
*
|
|
5
|
+
* Parsers for basic grammar elements: tokens, integers, addresses, times, etc.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.parseToken = parseToken;
|
|
9
|
+
exports.parseInteger = parseInteger;
|
|
10
|
+
exports.parseByte = parseByte;
|
|
11
|
+
exports.parseNonWSString = parseNonWSString;
|
|
12
|
+
exports.parseByteString = parseByteString;
|
|
13
|
+
exports.parseIP4Address = parseIP4Address;
|
|
14
|
+
exports.parseIP6Address = parseIP6Address;
|
|
15
|
+
exports.parseFQDN = parseFQDN;
|
|
16
|
+
exports.parseNtpTime = parseNtpTime;
|
|
17
|
+
exports.parseTypedTime = parseTypedTime;
|
|
18
|
+
exports.parseEmail = parseEmail;
|
|
19
|
+
exports.parsePhone = parsePhone;
|
|
20
|
+
exports.parseURI = parseURI;
|
|
21
|
+
const errors_1 = require("../types/errors");
|
|
22
|
+
const primitives_1 = require("../types/primitives");
|
|
23
|
+
const network_1 = require("../types/network");
|
|
24
|
+
const address_parser_1 = require("../utils/address-parser");
|
|
25
|
+
const time_converter_1 = require("../utils/time-converter");
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Token Parsing
|
|
28
|
+
// ============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Parse a token (RFC 8866 Section 9)
|
|
31
|
+
*
|
|
32
|
+
* token = 1*token-char
|
|
33
|
+
* token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E
|
|
34
|
+
*
|
|
35
|
+
* Essentially: any visible ASCII character except space and separators like ()[]{}:;,<>?=@\"
|
|
36
|
+
*
|
|
37
|
+
* @param scanner - Scanner instance
|
|
38
|
+
* @returns Parsed token string
|
|
39
|
+
* @throws ParseError if no valid token found
|
|
40
|
+
*/
|
|
41
|
+
function parseToken(scanner) {
|
|
42
|
+
const start = scanner.getPosition();
|
|
43
|
+
const token = scanner.readWhile(isTokenChar);
|
|
44
|
+
if (token.length === 0) {
|
|
45
|
+
throw new errors_1.ParseError('Expected token', start.line, start.column);
|
|
46
|
+
}
|
|
47
|
+
return token;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if a character is a valid token character
|
|
51
|
+
*/
|
|
52
|
+
function isTokenChar(ch) {
|
|
53
|
+
const code = ch.charCodeAt(0);
|
|
54
|
+
// token-char per RFC 8866
|
|
55
|
+
return (code === 0x21 || // !
|
|
56
|
+
(code >= 0x23 && code <= 0x27) || // # $ % & '
|
|
57
|
+
(code >= 0x2a && code <= 0x2b) || // * +
|
|
58
|
+
(code >= 0x2d && code <= 0x2e) || // - .
|
|
59
|
+
(code >= 0x30 && code <= 0x39) || // 0-9
|
|
60
|
+
(code >= 0x41 && code <= 0x5a) || // A-Z
|
|
61
|
+
(code >= 0x5e && code <= 0x7e) // ^ _ ` a-z { | } ~
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Integer Parsing
|
|
66
|
+
// ============================================================================
|
|
67
|
+
/**
|
|
68
|
+
* Parse an integer
|
|
69
|
+
*
|
|
70
|
+
* @param scanner - Scanner instance
|
|
71
|
+
* @returns Parsed integer
|
|
72
|
+
* @throws ParseError if no valid integer found
|
|
73
|
+
*/
|
|
74
|
+
function parseInteger(scanner) {
|
|
75
|
+
const start = scanner.getPosition();
|
|
76
|
+
const digits = scanner.readWhile(isDigit);
|
|
77
|
+
if (digits.length === 0) {
|
|
78
|
+
throw new errors_1.ParseError('Expected integer', start.line, start.column);
|
|
79
|
+
}
|
|
80
|
+
const value = parseInt(digits, 10);
|
|
81
|
+
if (isNaN(value)) {
|
|
82
|
+
throw new errors_1.ParseError(`Invalid integer: ${digits}`, start.line, start.column);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parse a byte (0-255)
|
|
88
|
+
*
|
|
89
|
+
* @param scanner - Scanner instance
|
|
90
|
+
* @returns Parsed byte value
|
|
91
|
+
* @throws ParseError if not a valid byte
|
|
92
|
+
*/
|
|
93
|
+
function parseByte(scanner) {
|
|
94
|
+
const start = scanner.getPosition();
|
|
95
|
+
const value = parseInteger(scanner);
|
|
96
|
+
if (value < 0 || value > 255) {
|
|
97
|
+
throw new errors_1.ParseError(`Byte value out of range: ${value} (expected 0-255)`, start.line, start.column);
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if character is a digit
|
|
103
|
+
*/
|
|
104
|
+
function isDigit(ch) {
|
|
105
|
+
return ch >= '0' && ch <= '9';
|
|
106
|
+
}
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// String Parsing
|
|
109
|
+
// ============================================================================
|
|
110
|
+
/**
|
|
111
|
+
* Parse a non-whitespace string
|
|
112
|
+
*
|
|
113
|
+
* Reads characters until whitespace or end of line.
|
|
114
|
+
*
|
|
115
|
+
* @param scanner - Scanner instance
|
|
116
|
+
* @returns Parsed string
|
|
117
|
+
* @throws ParseError if empty
|
|
118
|
+
*/
|
|
119
|
+
function parseNonWSString(scanner) {
|
|
120
|
+
const start = scanner.getPosition();
|
|
121
|
+
const str = scanner.readWhile(isNonWhitespace);
|
|
122
|
+
if (str.length === 0) {
|
|
123
|
+
throw new errors_1.ParseError('Expected non-whitespace string', start.line, start.column);
|
|
124
|
+
}
|
|
125
|
+
return str;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if character is non-whitespace
|
|
129
|
+
*/
|
|
130
|
+
function isNonWhitespace(ch) {
|
|
131
|
+
return ch !== ' ' && ch !== '\t' && ch !== '\r' && ch !== '\n';
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse a byte-string (RFC 8866 Section 9)
|
|
135
|
+
*
|
|
136
|
+
* byte-string = 1*(%x01-09 / %x0B-0C / %x0E-FF)
|
|
137
|
+
* (any byte except NUL, CR, LF)
|
|
138
|
+
*
|
|
139
|
+
* @param scanner - Scanner instance
|
|
140
|
+
* @returns Parsed byte-string
|
|
141
|
+
* @throws ParseError if invalid characters found
|
|
142
|
+
*/
|
|
143
|
+
function parseByteString(scanner) {
|
|
144
|
+
const start = scanner.getPosition();
|
|
145
|
+
const str = scanner.readWhile(isByteStringChar);
|
|
146
|
+
if (str.length === 0) {
|
|
147
|
+
throw new errors_1.ParseError('Expected byte-string', start.line, start.column);
|
|
148
|
+
}
|
|
149
|
+
return str;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if character is valid in byte-string
|
|
153
|
+
*/
|
|
154
|
+
function isByteStringChar(ch) {
|
|
155
|
+
const code = ch.charCodeAt(0);
|
|
156
|
+
return ((code >= 0x01 && code <= 0x09) ||
|
|
157
|
+
(code >= 0x0b && code <= 0x0c) ||
|
|
158
|
+
(code >= 0x0e && code <= 0xff));
|
|
159
|
+
}
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// IP Address Parsing
|
|
162
|
+
// ============================================================================
|
|
163
|
+
/**
|
|
164
|
+
* Parse an IPv4 address
|
|
165
|
+
*
|
|
166
|
+
* Format: a.b.c.d where each octet is 0-255
|
|
167
|
+
*
|
|
168
|
+
* @param scanner - Scanner instance
|
|
169
|
+
* @returns Parsed IPv4 address
|
|
170
|
+
* @throws ParseError if invalid IPv4 address
|
|
171
|
+
*/
|
|
172
|
+
function parseIP4Address(scanner) {
|
|
173
|
+
const start = scanner.getPosition();
|
|
174
|
+
// Read until whitespace or special characters
|
|
175
|
+
const addr = scanner.readWhile(ch => isDigit(ch) || ch === '.');
|
|
176
|
+
if (!(0, address_parser_1.isIP4Address)(addr)) {
|
|
177
|
+
throw new errors_1.ParseError(`Invalid IPv4 address: ${addr}`, start.line, start.column);
|
|
178
|
+
}
|
|
179
|
+
return (0, network_1.createIP4Address)(addr);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Parse an IPv6 address
|
|
183
|
+
*
|
|
184
|
+
* Supports all IPv6 formats including compression (::) and mixed IPv4
|
|
185
|
+
*
|
|
186
|
+
* @param scanner - Scanner instance
|
|
187
|
+
* @returns Parsed IPv6 address
|
|
188
|
+
* @throws ParseError if invalid IPv6 address
|
|
189
|
+
*/
|
|
190
|
+
function parseIP6Address(scanner) {
|
|
191
|
+
const start = scanner.getPosition();
|
|
192
|
+
// Read until whitespace (IPv6 can contain : and . and hex digits)
|
|
193
|
+
const addr = scanner.readWhile(ch => isHexDigit(ch) || ch === ':' || ch === '.');
|
|
194
|
+
if (!(0, address_parser_1.isIP6Address)(addr)) {
|
|
195
|
+
throw new errors_1.ParseError(`Invalid IPv6 address: ${addr}`, start.line, start.column);
|
|
196
|
+
}
|
|
197
|
+
return (0, network_1.createIP6Address)(addr);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Check if character is a hexadecimal digit
|
|
201
|
+
*/
|
|
202
|
+
function isHexDigit(ch) {
|
|
203
|
+
return ((ch >= '0' && ch <= '9') ||
|
|
204
|
+
(ch >= 'a' && ch <= 'f') ||
|
|
205
|
+
(ch >= 'A' && ch <= 'F'));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Parse an FQDN (Fully Qualified Domain Name)
|
|
209
|
+
*
|
|
210
|
+
* @param scanner - Scanner instance
|
|
211
|
+
* @returns Parsed FQDN
|
|
212
|
+
* @throws ParseError if invalid FQDN
|
|
213
|
+
*/
|
|
214
|
+
function parseFQDN(scanner) {
|
|
215
|
+
const start = scanner.getPosition();
|
|
216
|
+
// Read until whitespace (FQDN can contain alphanumeric, -, and .)
|
|
217
|
+
const fqdn = scanner.readWhile(ch => isAlphanumeric(ch) || ch === '-' || ch === '.');
|
|
218
|
+
if (!(0, address_parser_1.isFQDN)(fqdn)) {
|
|
219
|
+
throw new errors_1.ParseError(`Invalid FQDN: ${fqdn}`, start.line, start.column);
|
|
220
|
+
}
|
|
221
|
+
return (0, network_1.createFQDN)(fqdn);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check if character is alphanumeric
|
|
225
|
+
*/
|
|
226
|
+
function isAlphanumeric(ch) {
|
|
227
|
+
return ((ch >= '0' && ch <= '9') ||
|
|
228
|
+
(ch >= 'a' && ch <= 'z') ||
|
|
229
|
+
(ch >= 'A' && ch <= 'Z'));
|
|
230
|
+
}
|
|
231
|
+
// ============================================================================
|
|
232
|
+
// Time Parsing
|
|
233
|
+
// ============================================================================
|
|
234
|
+
/**
|
|
235
|
+
* Parse an NTP timestamp
|
|
236
|
+
*
|
|
237
|
+
* NTP time is seconds since January 1, 1900 00:00:00 UTC
|
|
238
|
+
* 0 means unbounded/permanent
|
|
239
|
+
*
|
|
240
|
+
* @param scanner - Scanner instance
|
|
241
|
+
* @returns Parsed NTP time
|
|
242
|
+
* @throws ParseError if invalid
|
|
243
|
+
*/
|
|
244
|
+
function parseNtpTime(scanner) {
|
|
245
|
+
const start = scanner.getPosition();
|
|
246
|
+
const value = parseInteger(scanner);
|
|
247
|
+
if (value < 0) {
|
|
248
|
+
throw new errors_1.ParseError(`NTP time cannot be negative: ${value}`, start.line, start.column);
|
|
249
|
+
}
|
|
250
|
+
return (0, primitives_1.createNtpTime)(value);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Parse a typed time value
|
|
254
|
+
*
|
|
255
|
+
* Format: [-]<integer>[d|h|m|s]
|
|
256
|
+
* - d = days
|
|
257
|
+
* - h = hours
|
|
258
|
+
* - m = minutes
|
|
259
|
+
* - s or no suffix = seconds
|
|
260
|
+
* - Can be negative (for timezone offsets)
|
|
261
|
+
*
|
|
262
|
+
* @param scanner - Scanner instance
|
|
263
|
+
* @returns Parsed typed time
|
|
264
|
+
* @throws ParseError if invalid
|
|
265
|
+
*/
|
|
266
|
+
function parseTypedTime(scanner) {
|
|
267
|
+
const start = scanner.getPosition();
|
|
268
|
+
// Read optional minus sign, digits, and optional unit suffix
|
|
269
|
+
const timeStr = scanner.readWhile(ch => ch === '-' || isDigit(ch) || ch === 'd' || ch === 'h' || ch === 'm' || ch === 's');
|
|
270
|
+
try {
|
|
271
|
+
return (0, time_converter_1.parseTypedTime)(timeStr);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
throw new errors_1.ParseError(`Invalid typed time: ${timeStr}`, start.line, start.column);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// Email and Phone Parsing
|
|
279
|
+
// ============================================================================
|
|
280
|
+
/**
|
|
281
|
+
* Parse an email address or display name with email
|
|
282
|
+
*
|
|
283
|
+
* Format: email-address / displayable-name <email-address>
|
|
284
|
+
*
|
|
285
|
+
* @param scanner - Scanner instance
|
|
286
|
+
* @returns Parsed email string
|
|
287
|
+
*/
|
|
288
|
+
function parseEmail(scanner) {
|
|
289
|
+
// Email can contain most characters, read until end of line
|
|
290
|
+
return scanner.readUntil(/[\r\n]/);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Parse a phone number or display name with phone
|
|
294
|
+
*
|
|
295
|
+
* Format: phone-number / displayable-name <phone-number>
|
|
296
|
+
*
|
|
297
|
+
* @param scanner - Scanner instance
|
|
298
|
+
* @returns Parsed phone string
|
|
299
|
+
*/
|
|
300
|
+
function parsePhone(scanner) {
|
|
301
|
+
// Phone can contain most characters, read until end of line
|
|
302
|
+
return scanner.readUntil(/[\r\n]/);
|
|
303
|
+
}
|
|
304
|
+
// ============================================================================
|
|
305
|
+
// URI Parsing
|
|
306
|
+
// ============================================================================
|
|
307
|
+
/**
|
|
308
|
+
* Parse a URI
|
|
309
|
+
*
|
|
310
|
+
* @param scanner - Scanner instance
|
|
311
|
+
* @returns Parsed URI string
|
|
312
|
+
*/
|
|
313
|
+
function parseURI(scanner) {
|
|
314
|
+
// URI can contain many characters, read until end of line
|
|
315
|
+
return scanner.readUntil(/[\r\n]/);
|
|
316
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scanner for SDP parsing (RFC 8866)
|
|
3
|
+
*
|
|
4
|
+
* Provides low-level tokenization and character-level operations
|
|
5
|
+
* with position tracking for detailed error reporting.
|
|
6
|
+
*/
|
|
7
|
+
import { Position } from '../types/primitives';
|
|
8
|
+
/**
|
|
9
|
+
* Scanner class for parsing SDP text
|
|
10
|
+
*
|
|
11
|
+
* Maintains position information and provides character-level operations
|
|
12
|
+
* for parsing SDP fields according to RFC 8866 grammar.
|
|
13
|
+
*/
|
|
14
|
+
export declare class Scanner {
|
|
15
|
+
private readonly input;
|
|
16
|
+
private position;
|
|
17
|
+
private line;
|
|
18
|
+
private column;
|
|
19
|
+
/**
|
|
20
|
+
* Create a new Scanner
|
|
21
|
+
*
|
|
22
|
+
* @param input - SDP text to scan
|
|
23
|
+
*/
|
|
24
|
+
constructor(input: string);
|
|
25
|
+
/**
|
|
26
|
+
* Peek at the current character without consuming it
|
|
27
|
+
*
|
|
28
|
+
* @returns Current character or null if at EOF
|
|
29
|
+
*/
|
|
30
|
+
peek(): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Peek ahead n characters without consuming
|
|
33
|
+
*
|
|
34
|
+
* @param n - Number of characters to peek ahead
|
|
35
|
+
* @returns Character at position+n or null if beyond EOF
|
|
36
|
+
*/
|
|
37
|
+
peekAhead(n: number): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Advance to the next character and return the current one
|
|
40
|
+
*
|
|
41
|
+
* Updates line and column tracking when encountering newlines.
|
|
42
|
+
*
|
|
43
|
+
* @returns Current character or null if at EOF
|
|
44
|
+
*/
|
|
45
|
+
advance(): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Read characters until a delimiter is found
|
|
48
|
+
*
|
|
49
|
+
* Does NOT consume the delimiter.
|
|
50
|
+
*
|
|
51
|
+
* @param delimiter - String or RegExp to match
|
|
52
|
+
* @returns String of characters read (may be empty)
|
|
53
|
+
*/
|
|
54
|
+
readUntil(delimiter: string | RegExp): string;
|
|
55
|
+
/**
|
|
56
|
+
* Read while a condition is true
|
|
57
|
+
*
|
|
58
|
+
* @param predicate - Function that returns true to continue reading
|
|
59
|
+
* @returns String of characters read
|
|
60
|
+
*/
|
|
61
|
+
readWhile(predicate: (ch: string) => boolean): string;
|
|
62
|
+
/**
|
|
63
|
+
* Skip whitespace characters (space and tab)
|
|
64
|
+
*
|
|
65
|
+
* Does NOT skip newlines (CRLF/LF/CR).
|
|
66
|
+
*/
|
|
67
|
+
skipWhitespace(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Skip to the next line
|
|
70
|
+
*
|
|
71
|
+
* Consumes all characters until and including the next line ending (CRLF/LF/CR).
|
|
72
|
+
*/
|
|
73
|
+
skipToNextLine(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Get current position in the input
|
|
76
|
+
*
|
|
77
|
+
* @returns Position object with offset, line, and column
|
|
78
|
+
*/
|
|
79
|
+
getPosition(): Position;
|
|
80
|
+
/**
|
|
81
|
+
* Check if at end of file
|
|
82
|
+
*
|
|
83
|
+
* @returns true if no more characters to read
|
|
84
|
+
*/
|
|
85
|
+
isEOF(): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Expect a specific string at the current position
|
|
88
|
+
*
|
|
89
|
+
* Consumes the string if it matches, throws otherwise.
|
|
90
|
+
*
|
|
91
|
+
* @param expected - String to expect
|
|
92
|
+
* @throws ParseError if string doesn't match
|
|
93
|
+
*/
|
|
94
|
+
expect(expected: string): void;
|
|
95
|
+
/**
|
|
96
|
+
* Expect a character that matches a condition
|
|
97
|
+
*
|
|
98
|
+
* Consumes the character if it matches, throws otherwise.
|
|
99
|
+
*
|
|
100
|
+
* @param predicate - Function that returns true for valid character
|
|
101
|
+
* @param description - Description of expected character for error messages
|
|
102
|
+
* @returns The matched character
|
|
103
|
+
* @throws ParseError if character doesn't match
|
|
104
|
+
*/
|
|
105
|
+
expectChar(predicate: (ch: string) => boolean, description: string): string;
|
|
106
|
+
/**
|
|
107
|
+
* Expect end of line (CRLF, LF, or CR)
|
|
108
|
+
*
|
|
109
|
+
* @throws ParseError if not at end of line
|
|
110
|
+
*/
|
|
111
|
+
expectEOL(): void;
|
|
112
|
+
/**
|
|
113
|
+
* Expect space character
|
|
114
|
+
*
|
|
115
|
+
* @throws ParseError if not a space
|
|
116
|
+
*/
|
|
117
|
+
expectSpace(): void;
|
|
118
|
+
/**
|
|
119
|
+
* Get context around a position for error messages
|
|
120
|
+
*
|
|
121
|
+
* Returns a snippet of the input around the position.
|
|
122
|
+
*
|
|
123
|
+
* @param pos - Position to get context for
|
|
124
|
+
* @returns Context string (may be empty)
|
|
125
|
+
*/
|
|
126
|
+
private getContext;
|
|
127
|
+
/**
|
|
128
|
+
* Get remaining input from current position
|
|
129
|
+
*
|
|
130
|
+
* @returns Remaining input string
|
|
131
|
+
*/
|
|
132
|
+
getRemainingInput(): string;
|
|
133
|
+
/**
|
|
134
|
+
* Get a substring of the input
|
|
135
|
+
*
|
|
136
|
+
* @param start - Start position
|
|
137
|
+
* @param end - End position (optional, defaults to current position)
|
|
138
|
+
* @returns Substring
|
|
139
|
+
*/
|
|
140
|
+
getSubstring(start: number, end?: number): string;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../../src/parser/scanner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C;;;;;GAKG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAAS;IAEvB;;;;OAIG;gBACS,KAAK,EAAE,MAAM;IAOzB;;;;OAIG;IACH,IAAI,IAAI,MAAM,GAAG,IAAI;IAOrB;;;;;OAKG;IACH,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQnC;;;;;;OAMG;IACH,OAAO,IAAI,MAAM,GAAG,IAAI;IA2BxB;;;;;;;OAOG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM;IAwB7C;;;;;OAKG;IACH,SAAS,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM;IAcrD;;;;OAIG;IACH,cAAc,IAAI,IAAI;IAUtB;;;;OAIG;IACH,cAAc,IAAI,IAAI;IAStB;;;;OAIG;IACH,WAAW,IAAI,QAAQ;IAQvB;;;;OAIG;IACH,KAAK,IAAI,OAAO;IAIhB;;;;;;;OAOG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IA4B9B;;;;;;;;;OASG;IACH,UAAU,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IA0B3E;;;;OAIG;IACH,SAAS,IAAI,IAAI;IAqBjB;;;;OAIG;IACH,WAAW,IAAI,IAAI;IAgBnB;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAQlB;;;;OAIG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;;;;;OAMG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM;CAGlD"}
|