@@ -17,13 +17,6 @@ import ResourceLocatorIdentifierEnum from '../enum/ResourceLocatorIdentifierEnum
17
17
* @link https://github.com/virtru/nanotdf/blob/master/spec/index.md#341-resource-locator
18
18
*/
19
19
export default class ResourceLocator {
20
- readonly protocol : ProtocolEnum ;
21
- readonly lengthOfBody : number ;
22
- readonly body : string ;
23
- readonly identifier : string ;
24
- readonly identifierType : ResourceLocatorIdentifierEnum = ResourceLocatorIdentifierEnum . None ;
25
- readonly offset : number = 0 ;
26
-
27
20
static readonly PROTOCOL_OFFSET = 0 ;
28
21
static readonly PROTOCOL_LENGTH = 1 ;
29
22
static readonly LENGTH_OFFSET = 1 ;
@@ -34,100 +27,121 @@ export default class ResourceLocator {
34
27
static readonly IDENTIFIER_8_BYTE : number = 2 << 4 ; // 32
35
28
static readonly IDENTIFIER_32_BYTE : number = 3 << 4 ; // 48
36
29
37
- static parse ( url : string , identifier : string = '' ) : ResourceLocator {
38
- const [ protocol , body ] = url . split ( '://' ) ;
30
+ constructor (
31
+ readonly protocol : ProtocolEnum ,
32
+ readonly lengthOfBody : number ,
33
+ readonly body : string ,
34
+ readonly offset : number ,
35
+ readonly id ?: string ,
36
+ readonly idType : ResourceLocatorIdentifierEnum = ResourceLocatorIdentifierEnum . None
37
+ ) { }
38
+
39
+ static fromURL ( url : string , identifier ?: string ) : ResourceLocator {
40
+ const [ protocolStr , body ] = url . split ( '://' ) ;
41
+
42
+ let protocol : ProtocolEnum ;
39
43
40
44
// Validate and set protocol identifier byte
41
- const protocolIdentifierByte = new Uint8Array ( 1 ) ;
42
- switch ( protocol . toLowerCase ( ) ) {
45
+ switch ( protocolStr . toLowerCase ( ) ) {
43
46
case 'http' :
44
- protocolIdentifierByte [ 0 ] = 0x00 ;
47
+ protocol = ProtocolEnum . Http ;
45
48
break ;
46
49
case 'https' :
47
- protocolIdentifierByte [ 0 ] = 0x01 ;
50
+ protocol = ProtocolEnum . Https ;
48
51
break ;
49
52
default :
50
- throw new Error ( ' resource locator protocol unsupported' ) ;
53
+ throw new Error ( ` resource locator protocol [ ${ protocolStr } ] unsupported` ) ;
51
54
}
52
55
53
56
// Set identifier padded length and protocol identifier byte
54
- const identifierPaddedLength = ( ( ) => {
55
- switch ( identifier . length ) {
56
- case 0 :
57
- protocolIdentifierByte [ 0 ] |= ResourceLocator . IDENTIFIER_0_BYTE ;
58
- return ResourceLocatorIdentifierEnum . None . valueOf ( ) ;
59
- case 2 :
60
- protocolIdentifierByte [ 0 ] |= ResourceLocator . IDENTIFIER_2_BYTE ;
61
- return ResourceLocatorIdentifierEnum . TwoBytes . valueOf ( ) ;
62
- case 8 :
63
- protocolIdentifierByte [ 0 ] |= ResourceLocator . IDENTIFIER_8_BYTE ;
64
- return ResourceLocatorIdentifierEnum . EightBytes . valueOf ( ) ;
65
- case 32 :
66
- protocolIdentifierByte [ 0 ] |= ResourceLocator . IDENTIFIER_32_BYTE ;
67
- return ResourceLocatorIdentifierEnum . ThirtyTwoBytes . valueOf ( ) ;
68
- default :
69
- throw new Error ( `Unsupported identifier length: ${ identifier . length } ` ) ;
57
+ const identifierType = ( ( ) => {
58
+ if ( ! identifier ) {
59
+ return ResourceLocatorIdentifierEnum . None ;
60
+ }
61
+ const identifierLength = new TextEncoder ( ) . encode ( identifier ) . length ;
62
+ if ( identifierLength <= 2 ) {
63
+ return ResourceLocatorIdentifierEnum . TwoBytes ;
64
+ } else if ( identifierLength <= 8 ) {
65
+ return ResourceLocatorIdentifierEnum . EightBytes ;
66
+ } else if ( identifierLength <= 32 ) {
67
+ return ResourceLocatorIdentifierEnum . ThirtyTwoBytes ;
70
68
}
69
+ throw new Error ( `unsupported identifier length: ${ identifier . length } ` ) ;
71
70
} ) ( ) ;
72
71
73
72
// Create buffer to hold protocol, body length, body, and identifier
74
- const bodyBytes = new TextEncoder ( ) . encode ( body ) ;
75
- const buffer = new Uint8Array ( 1 + 1 + bodyBytes . length + identifierPaddedLength ) ;
76
-
77
- // Set the protocol, body length, body and identifier into buffer
78
- buffer . set ( protocolIdentifierByte , 0 ) ;
79
- buffer . set ( [ bodyBytes . length ] , 1 ) ;
80
- buffer . set ( bodyBytes , 2 ) ;
81
-
82
- if ( identifierPaddedLength > 0 ) {
83
- const identifierBytes = new TextEncoder ( )
84
- . encode ( identifier )
85
- . subarray ( 0 , identifierPaddedLength ) ;
86
- buffer . set ( identifierBytes , 2 + bodyBytes . length ) ;
73
+ const lengthOfBody = new TextEncoder ( ) . encode ( body ) . length ;
74
+ if ( lengthOfBody == 0 ) {
75
+ throw new Error ( 'url body empty' ) ;
87
76
}
88
-
89
- return new ResourceLocator ( buffer ) ;
77
+ const identifierLength = identifierType . valueOf ( ) ;
78
+ const offset = ResourceLocator . BODY_OFFSET + lengthOfBody + identifierLength ;
79
+ return new ResourceLocator ( protocol , lengthOfBody , body , offset , identifier , identifierType ) ;
90
80
}
91
81
92
- constructor ( buff : Uint8Array ) {
82
+ static parse ( buff : Uint8Array ) {
93
83
// Protocol
94
- this . protocol = buff [ ResourceLocator . PROTOCOL_OFFSET ] ;
84
+ const protocolAndIdentifierType = buff [ ResourceLocator . PROTOCOL_OFFSET ] ;
95
85
// Length of body
96
- this . lengthOfBody = buff [ ResourceLocator . LENGTH_OFFSET ] ;
86
+ const lengthOfBody = buff [ ResourceLocator . LENGTH_OFFSET ] ;
87
+ if ( lengthOfBody == 0 ) {
88
+ throw new Error ( 'url body empty' ) ;
89
+ }
97
90
// Body as utf8 string
98
91
const decoder = new TextDecoder ( ) ;
99
- this . body = decoder . decode (
100
- buff . subarray ( ResourceLocator . BODY_OFFSET , ResourceLocator . BODY_OFFSET + this . lengthOfBody )
101
- ) ;
92
+ let offset = ResourceLocator . BODY_OFFSET + lengthOfBody ;
93
+ if ( offset > buff . length ) {
94
+ throw new Error ( 'parse out of bounds error' ) ;
95
+ }
96
+ const body = decoder . decode ( buff . subarray ( ResourceLocator . BODY_OFFSET , offset ) ) ;
97
+ const protocol = protocolAndIdentifierType & 0xf ;
98
+ switch ( protocol ) {
99
+ case ProtocolEnum . Http :
100
+ case ProtocolEnum . Https :
101
+ break ;
102
+ default :
103
+ throw new Error ( `unsupported protocol type [${ protocol } ]` ) ;
104
+ }
102
105
// identifier
103
- const identifierTypeNibble = this . protocol & 0xf0 ;
106
+ const identifierTypeNibble = protocolAndIdentifierType & 0xf0 ;
107
+ let identifierType = ResourceLocatorIdentifierEnum . None ;
104
108
if ( identifierTypeNibble === ResourceLocator . IDENTIFIER_2_BYTE ) {
105
- this . identifierType = ResourceLocatorIdentifierEnum . TwoBytes ;
109
+ identifierType = ResourceLocatorIdentifierEnum . TwoBytes ;
106
110
} else if ( identifierTypeNibble === ResourceLocator . IDENTIFIER_8_BYTE ) {
107
- this . identifierType = ResourceLocatorIdentifierEnum . EightBytes ;
111
+ identifierType = ResourceLocatorIdentifierEnum . EightBytes ;
108
112
} else if ( identifierTypeNibble === ResourceLocator . IDENTIFIER_32_BYTE ) {
109
- this . identifierType = ResourceLocatorIdentifierEnum . ThirtyTwoBytes ;
113
+ identifierType = ResourceLocatorIdentifierEnum . ThirtyTwoBytes ;
114
+ } else if ( identifierTypeNibble !== ResourceLocator . IDENTIFIER_0_BYTE ) {
115
+ throw new Error ( `unsupported key identifier type [${ identifierTypeNibble } ]` ) ;
110
116
}
111
- switch ( this . identifierType ) {
117
+
118
+ let identifier : string | undefined = undefined ;
119
+
120
+ switch ( identifierType ) {
112
121
case ResourceLocatorIdentifierEnum . None :
113
122
// noop
114
123
break ;
115
124
case ResourceLocatorIdentifierEnum . TwoBytes :
116
125
case ResourceLocatorIdentifierEnum . EightBytes :
117
- case ResourceLocatorIdentifierEnum . ThirtyTwoBytes :
118
- const start = ResourceLocator . BODY_OFFSET + this . lengthOfBody ;
119
- const end = start + this . identifierType . valueOf ( ) ;
120
- const subarray = buff . subarray ( start , end ) ;
126
+ case ResourceLocatorIdentifierEnum . ThirtyTwoBytes : {
127
+ const kidStart = offset ;
128
+ offset = kidStart + identifierType . valueOf ( ) ;
129
+ if ( offset > buff . length ) {
130
+ throw new Error ( 'parse out of bounds error' ) ;
131
+ }
132
+ const kidSubarray = buff . subarray ( kidStart , offset ) ;
121
133
// Remove padding (assuming the padding is null bytes, 0x00)
122
- const trimmedSubarray = subarray . filter ( ( byte ) => byte !== 0x00 ) ;
123
- this . identifier = decoder . decode ( trimmedSubarray ) ;
134
+ const zeroIndex = kidSubarray . indexOf ( 0 ) ;
135
+ if ( zeroIndex >= 0 ) {
136
+ const trimmedSubarray = kidSubarray . subarray ( 0 , zeroIndex ) ;
137
+ identifier = decoder . decode ( trimmedSubarray ) ;
138
+ } else {
139
+ identifier = decoder . decode ( kidSubarray ) ;
140
+ }
124
141
break ;
142
+ }
125
143
}
126
- this . offset =
127
- ResourceLocator . PROTOCOL_LENGTH +
128
- ResourceLocator . LENGTH_LENGTH +
129
- this . lengthOfBody +
130
- this . identifierType . valueOf ( ) ;
144
+ return new ResourceLocator ( protocol , lengthOfBody , body , offset , identifier , identifierType ) ;
131
145
}
132
146
133
147
/**
@@ -136,20 +150,11 @@ export default class ResourceLocator {
136
150
* @returns { number } Length of resource locator
137
151
*/
138
152
get length ( ) : number {
139
- return (
140
- // Protocol
141
- 1 +
142
- // Length of the body( 1 byte)
143
- 1 +
144
- // Content length
145
- this . body . length +
146
- // Identifier length
147
- this . identifierType . valueOf ( )
148
- ) ;
153
+ return this . offset ;
149
154
}
150
155
151
156
get url ( ) : string | never {
152
- switch ( this . protocol & 0xf ) {
157
+ switch ( this . protocol ) {
153
158
case ProtocolEnum . Http :
154
159
return 'http://' + this . body ;
155
160
case ProtocolEnum . Https :
@@ -163,33 +168,26 @@ export default class ResourceLocator {
163
168
* Return the contents of the Resource Locator in buffer
164
169
*/
165
170
toBuffer ( ) : Uint8Array {
166
- const buffer = new Uint8Array ( 2 + this . body . length + this . identifierType . valueOf ( ) ) ;
167
- buffer . set ( [ this . protocol ] , 0 ) ;
168
- buffer . set ( [ this . lengthOfBody ] , 1 ) ;
169
- buffer . set ( new TextEncoder ( ) . encode ( this . body ) , 2 ) ;
170
- if ( this . identifier ) {
171
- buffer . set ( new TextEncoder ( ) . encode ( this . identifier ) , 2 + this . body . length ) ;
171
+ const buffer = new Uint8Array ( ResourceLocator . BODY_OFFSET + this . body . length + this . idType ) ;
172
+ let idTypeNibble = 0 ;
173
+ switch ( this . idType ) {
174
+ case ResourceLocatorIdentifierEnum . TwoBytes :
175
+ idTypeNibble = ResourceLocator . IDENTIFIER_2_BYTE ;
176
+ break ;
177
+ case ResourceLocatorIdentifierEnum . EightBytes :
178
+ idTypeNibble = ResourceLocator . IDENTIFIER_8_BYTE ;
179
+ break ;
180
+ case ResourceLocatorIdentifierEnum . ThirtyTwoBytes :
181
+ idTypeNibble = ResourceLocator . IDENTIFIER_32_BYTE ;
182
+ break ;
172
183
}
173
- return buffer ;
174
- }
175
-
176
- /**
177
- * Get URL
178
- *
179
- * Construct URL from ResourceLocator or throw error
180
- */
181
- getUrl ( ) : string | never {
182
- let protocol : string ;
183
- // protocolIndex get the first four bits
184
- const protocolIndex : number = this . protocol & 0xf ;
185
- if ( protocolIndex === ProtocolEnum . Http ) {
186
- protocol = 'http' ;
187
- } else if ( protocolIndex === ProtocolEnum . Https ) {
188
- protocol = 'https' ;
189
- } else {
190
- throw new Error ( `Cannot create URL from protocol, "${ ProtocolEnum [ this . protocol ] } "` ) ;
184
+ buffer . set ( [ this . protocol | idTypeNibble ] , ResourceLocator . PROTOCOL_OFFSET ) ;
185
+ buffer . set ( [ this . lengthOfBody ] , ResourceLocator . LENGTH_OFFSET ) ;
186
+ buffer . set ( new TextEncoder ( ) . encode ( this . body ) , ResourceLocator . BODY_OFFSET ) ;
187
+ if ( this . id ) {
188
+ buffer . set ( new TextEncoder ( ) . encode ( this . id ) , ResourceLocator . BODY_OFFSET + this . body . length ) ;
191
189
}
192
- return ` ${ protocol } :// ${ this . body } ` ;
190
+ return buffer ;
193
191
}
194
192
195
193
/**
@@ -198,7 +196,7 @@ export default class ResourceLocator {
198
196
* Returns the identifier of the ResourceLocator or an empty string if no identifier is present.
199
197
* @returns { string } Identifier of the resource locator.
200
198
*/
201
- getIdentifier ( ) : string {
202
- return this . identifier || '' ;
199
+ get identifier ( ) : string {
200
+ return this . id ?? '' ;
203
201
}
204
202
}
0 commit comments