1
- import * as bytes from 'multiformats/bytes.js'
2
-
3
- const readonly = ( object , key , value ) => {
4
- Object . defineProperty ( object , key , {
5
- value,
6
- writable : false ,
7
- enumerable : true
8
- } )
9
- }
1
+ import * as Bytes from 'multiformats/bytes.js'
2
+
3
+ const property = ( value , { writable = false , enumerable = true , configurable = false } = { } ) => ( {
4
+ value,
5
+ writable,
6
+ enumerable,
7
+ configurable
8
+ } )
10
9
11
10
// ESM does not support importing package.json where this version info
12
11
// should come from. To workaround it version is copied here.
@@ -38,86 +37,180 @@ if (cid) {
38
37
}
39
38
`
40
39
40
+ /**
41
+ * @param {import('./index').Multiformats } multiformats
42
+ */
41
43
export default multiformats => {
42
44
const { multibase, varint, multihash } = multiformats
43
- const parse = buff => {
44
- const [ code , length ] = varint . decode ( buff )
45
- return [ code , buff . slice ( length ) ]
45
+
46
+ /**
47
+ * @param {number } version
48
+ * @param {number } codec
49
+ * @param {Uint8Array } multihash
50
+ * @returns {Uint8Array }
51
+ */
52
+ const encodeCID = ( version , codec , multihash ) => {
53
+ const versionBytes = varint . encode ( version )
54
+ const codecBytes = varint . encode ( codec )
55
+ const bytes = new Uint8Array ( versionBytes . byteLength + codecBytes . byteLength + multihash . byteLength )
56
+ bytes . set ( versionBytes , 0 )
57
+ bytes . set ( codecBytes , versionBytes . byteLength )
58
+ bytes . set ( multihash , versionBytes . byteLength + codecBytes . byteLength )
59
+ return bytes
46
60
}
47
- const encode = ( version , codec , multihash ) => {
48
- return Uint8Array . from ( [
49
- ...varint . encode ( version ) ,
50
- ...varint . encode ( codec ) ,
51
- ...multihash
52
- ] )
61
+
62
+ /**
63
+ * Takes `Uint8Array` representation of `CID` and returns
64
+ * `[version, codec, multihash]`. Throws error if bytes passed do not
65
+ * correspond to vaild `CID`.
66
+ * @param {Uint8Array } bytes
67
+ * @returns {[number, number, Uint8Array] }
68
+ */
69
+ const decodeCID = ( bytes ) => {
70
+ const [ version , offset ] = varint . decode ( bytes )
71
+ switch ( version ) {
72
+ // CIDv0
73
+ case 18 : {
74
+ return [ 0 , 0x70 , bytes ]
75
+ }
76
+ // CIDv1
77
+ case 1 : {
78
+ const [ code , length ] = varint . decode ( bytes . subarray ( offset ) )
79
+ return [ 1 , code , decodeMultihash ( bytes . subarray ( offset + length ) ) ]
80
+ }
81
+ default : {
82
+ throw new RangeError ( `Invalid CID version ${ version } ` )
83
+ }
84
+ }
53
85
}
54
86
55
87
const cidSymbol = Symbol . for ( '@ipld/js-cid/CID' )
56
88
57
- class CID {
58
- constructor ( cid , ...args ) {
59
- Object . defineProperty ( this , '_baseCache' , {
60
- value : new Map ( ) ,
61
- writable : false ,
62
- enumerable : false
63
- } )
64
- readonly ( this , 'asCID' , this )
65
- if ( cid != null && cid [ cidSymbol ] === true ) {
66
- readonly ( this , 'version' , cid . version )
67
- readonly ( this , 'multihash' , bytes . coerce ( cid . multihash ) )
68
- readonly ( this , 'buffer' , bytes . coerce ( cid . buffer ) )
69
- if ( cid . code ) readonly ( this , 'code' , cid . code )
70
- else readonly ( this , 'code' , multiformats . get ( cid . codec ) . code )
71
- return
89
+ /**
90
+ * Create CID from the string encoded CID.
91
+ * @param {string } string
92
+ * @returns {CID }
93
+ */
94
+ const fromString = ( string ) => {
95
+ switch ( string [ 0 ] ) {
96
+ // V0
97
+ case 'Q' : {
98
+ const cid = new CID ( multibase . get ( 'base58btc' ) . decode ( string ) )
99
+ cid . _baseCache . set ( 'base58btc' , string )
100
+ return cid
72
101
}
73
- if ( args . length > 0 ) {
74
- if ( typeof args [ 0 ] !== 'number' ) throw new Error ( 'String codecs are no longer supported' )
75
- readonly ( this , 'version' , cid )
76
- readonly ( this , 'code' , args . shift ( ) )
77
- if ( this . version === 0 && this . code !== 112 ) {
78
- throw new Error ( 'Version 0 CID must be 112 codec (dag-cbor)' )
79
- }
80
- this . _multihash = args . shift ( )
81
- if ( args . length ) throw new Error ( 'No longer supported, cannot specify base encoding in instantiation' )
82
- if ( this . version === 0 ) readonly ( this , 'buffer' , this . multihash )
83
- else readonly ( this , 'buffer' , encode ( this . version , this . code , this . multihash ) )
84
- return
102
+ default : {
103
+ // CID v1
104
+ const cid = new CID ( multibase . decode ( string ) )
105
+ cid . _baseCache . set ( multibase . encoding ( string ) . name , string )
106
+ return cid
85
107
}
86
- if ( typeof cid === 'string' ) {
87
- if ( cid . startsWith ( 'Q' ) ) {
88
- readonly ( this , 'version' , 0 )
89
- readonly ( this , 'code' , 0x70 )
90
- const { decode } = multibase . get ( 'base58btc' )
91
- this . _multihash = decode ( cid )
92
- readonly ( this , 'buffer' , this . multihash )
93
- return
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Takes a hashCID multihash and validates the digest. Returns it back if
113
+ * all good otherwise throws error.
114
+ * @param {Uint8Array } hash
115
+ * @returns {Uint8Array }
116
+ */
117
+ const decodeMultihash = ( hash ) => {
118
+ const { digest, length } = multihash . decode ( hash )
119
+ if ( digest . length !== length ) {
120
+ throw new Error ( 'Given multihash has incorrect length' )
121
+ }
122
+
123
+ return hash
124
+ }
125
+
126
+ /**
127
+ * @implements {ArrayBufferView}
128
+ */
129
+ class CID {
130
+ /**
131
+ * Creates new CID from the given value that is either CID, string or an
132
+ * Uint8Array.
133
+ * @param {CID|string|Uint8Array } value
134
+ */
135
+ static from ( value ) {
136
+ if ( typeof value === 'string' ) {
137
+ return fromString ( value )
138
+ } else if ( value instanceof Uint8Array ) {
139
+ return new CID ( value )
140
+ } else {
141
+ const cid = CID . asCID ( value )
142
+ if ( cid ) {
143
+ // If we got the same CID back we create a copy.
144
+ if ( cid === value ) {
145
+ return new CID ( cid . bytes )
146
+ } else {
147
+ return cid
148
+ }
149
+ } else {
150
+ throw new TypeError ( `Can not create CID from given value ${ value } ` )
94
151
}
95
- const { name } = multibase . encoding ( cid )
96
- this . _baseCache . set ( name , cid )
97
- cid = multibase . decode ( cid )
98
152
}
99
- cid = bytes . coerce ( cid )
100
- readonly ( this , 'buffer' , cid )
101
- let code
102
- ; [ code , cid ] = parse ( cid )
103
- if ( code === 18 ) {
104
- // CIDv0
105
- readonly ( this , 'version' , 0 )
106
- readonly ( this , 'code' , 0x70 )
107
- this . _multihash = this . buffer
108
- return
153
+ }
154
+
155
+ /**
156
+ * Creates new CID with a given version, codec and a multihash.
157
+ * @param {number } version
158
+ * @param {number } code
159
+ * @param {Uint8Array } multihash
160
+ */
161
+ static create ( version , code , multihash ) {
162
+ if ( typeof code !== 'number' ) {
163
+ throw new Error ( 'String codecs are no longer supported' )
164
+ }
165
+
166
+ switch ( version ) {
167
+ case 0 : {
168
+ if ( code !== 112 ) {
169
+ throw new Error ( 'Version 0 CID must be 112 codec (dag-cbor)' )
170
+ } else {
171
+ return new CID ( multihash )
172
+ }
173
+ }
174
+ case 1 : {
175
+ // TODO: Figure out why we check digest here but not in v 0
176
+ return new CID ( encodeCID ( version , code , decodeMultihash ( multihash ) ) )
177
+ }
178
+ default : {
179
+ throw new Error ( 'Invalid version' )
180
+ }
109
181
}
110
- if ( code > 1 ) throw new Error ( `Invalid CID version ${ code } ` )
111
- readonly ( this , 'version' , code )
112
- ; [ code , cid ] = parse ( cid )
113
- readonly ( this , 'code' , code )
114
- this . _multihash = cid
115
182
}
116
183
117
- set _multihash ( hash ) {
118
- const { length, digest } = multihash . decode ( hash )
119
- if ( digest . length !== length ) throw new Error ( 'Incorrect length' )
120
- readonly ( this , 'multihash' , hash )
184
+ /**
185
+ *
186
+ * @param {ArrayBuffer|Uint8Array } buffer
187
+ * @param {number } [byteOffset=0]
188
+ * @param {number } [byteLength=buffer.byteLength]
189
+ */
190
+ constructor ( buffer , byteOffset = 0 , byteLength = buffer . byteLength ) {
191
+ const bytes = buffer instanceof Uint8Array
192
+ ? Bytes . coerce ( buffer ) // Just in case it's a node Buffer
193
+ : new Uint8Array ( buffer , byteOffset , byteLength )
194
+
195
+ const [ version , code , multihash ] = decodeCID ( bytes )
196
+ Object . defineProperties ( this , {
197
+ // ArrayBufferView
198
+ buffer : property ( bytes . buffer , { enumerable : false } ) ,
199
+ byteOffset : property ( bytes . byteOffset , { enumerable : false } ) ,
200
+ byteLength : property ( bytes . byteLength , { enumerable : false } ) ,
201
+
202
+ // CID fields
203
+ version : property ( version ) ,
204
+ code : property ( code ) ,
205
+ multihash : property ( multihash ) ,
206
+ asCID : property ( this ) ,
207
+
208
+ // Legacy
209
+ bytes : property ( bytes , { enumerable : false } ) ,
210
+
211
+ // Internal
212
+ _baseCache : property ( new Map ( ) , { enumerable : false } )
213
+ } )
121
214
}
122
215
123
216
get codec ( ) {
@@ -143,11 +236,11 @@ export default multiformats => {
143
236
throw new Error ( 'Cannot convert non sha2-256 multihash CID to CIDv0' )
144
237
}
145
238
146
- return new CID ( 0 , this . code , this . multihash )
239
+ return CID . create ( 0 , this . code , this . multihash )
147
240
}
148
241
149
242
toV1 ( ) {
150
- return new CID ( 1 , this . code , this . multihash )
243
+ return CID . create ( 1 , this . code , this . multihash )
151
244
}
152
245
153
246
get toBaseEncodedString ( ) {
@@ -159,17 +252,25 @@ export default multiformats => {
159
252
}
160
253
161
254
toString ( base ) {
162
- if ( this . version === 0 ) {
255
+ const { version, bytes } = this
256
+ if ( version === 0 ) {
163
257
if ( base && base !== 'base58btc' ) {
164
258
throw new Error ( `Cannot string encode V0 in ${ base } encoding` )
165
259
}
166
260
const { encode } = multibase . get ( 'base58btc' )
167
- return encode ( this . buffer )
261
+ return encode ( bytes )
262
+ }
263
+
264
+ base = base || 'base32'
265
+ const { _baseCache } = this
266
+ const string = _baseCache . get ( base )
267
+ if ( string == null ) {
268
+ const string = multibase . encode ( bytes , base )
269
+ _baseCache . set ( base , string )
270
+ return string
271
+ } else {
272
+ return string
168
273
}
169
- if ( ! base ) base = 'base32'
170
- if ( this . _baseCache . has ( base ) ) return this . _baseCache . get ( base )
171
- this . _baseCache . set ( base , multibase . encode ( this . buffer , base ) )
172
- return this . _baseCache . get ( base )
173
274
}
174
275
175
276
toJSON ( ) {
@@ -183,17 +284,13 @@ export default multiformats => {
183
284
equals ( other ) {
184
285
return this . code === other . code &&
185
286
this . version === other . version &&
186
- bytes . equals ( this . multihash , other . multihash )
287
+ Bytes . equals ( this . multihash , other . multihash )
187
288
}
188
289
189
290
get [ Symbol . toStringTag ] ( ) {
190
291
return 'CID'
191
292
}
192
293
193
- get [ cidSymbol ] ( ) {
194
- return true
195
- }
196
-
197
294
/**
198
295
* Takes any input `value` and returns a `CID` instance if it was
199
296
* a `CID` otherwise returns `null`. If `value` is instanceof `CID`
@@ -217,12 +314,14 @@ export default multiformats => {
217
314
// API.
218
315
} else if ( value != null && value . asCID === value ) {
219
316
const { version, code, multihash } = value
220
- return new CID ( version , code , multihash )
317
+ return CID . create ( version , code , multihash )
221
318
// If value is a CID from older implementation that used to be tagged via
222
319
// symbol we still rebase it to the this `CID` implementation by
223
320
// delegating that to a constructor.
224
321
} else if ( value != null && value [ cidSymbol ] === true ) {
225
- return new CID ( value )
322
+ const { version, multihash } = value
323
+ const code = value . code || multiformats . get ( value . codec ) . code
324
+ return new CID ( encodeCID ( version , code , multihash ) )
226
325
// Otherwise value is not a CID (or an incompatible version of it) in
227
326
// which case we return `null`.
228
327
} else {
@@ -232,7 +331,7 @@ export default multiformats => {
232
331
233
332
static isCID ( value ) {
234
333
deprecate ( / ^ 0 \. 0 / , IS_CID_DEPRECATION )
235
- return ! ! ( value && value [ cidSymbol ] )
334
+ return ! ! ( value && ( value [ cidSymbol ] || value . asCID === value ) )
236
335
}
237
336
}
238
337
0 commit comments