-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMulticodec.java
More file actions
323 lines (287 loc) · 10.1 KB
/
Multicodec.java
File metadata and controls
323 lines (287 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
package com.apicatalog.multicodec;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.IntStream;
import com.apicatalog.uvarint.UVarInt;
/**
* Represents a
* <a href="https://github.com/multiformats/multicodec">multicodec</a>
* definition and provides encoding/decoding operations using its varint code.
*
* <p>
* A multicodec is a self-describing format where the encoded data is prefixed
* with a varint code identifying the format. This class stores metadata about
* the codec, including its name, tag, status, and binary varint code, and can
* be used to:
* <ul>
* <li>Encode raw byte arrays with the codec prefix</li>
* <li>Check if a value is encoded with this codec</li>
* <li>Decode a previously encoded value</li>
* </ul>
*
* <p>
* Instances of this class are immutable and thread-safe.
*/
public class Multicodec {
/**
* Categories of recognized multicodec tags.
*/
public enum Tag {
Key,
Multihash,
Multiaddr,
Hash,
Cid,
Namespace,
Multiformat,
Serialization,
Transport,
Varsig,
}
/**
* Registration status of the codec.
*/
public enum Status {
Deprecated,
Draft,
Permanent,
}
protected final String name;
protected final byte[] codeVarint;
protected final long code;
protected final Tag tag;
protected final Status status;
/**
* Constructs a new {@code Multicodec}.
*
* @param name the codec name
* @param tag the codec category tag
* @param code the codec numeric code
* @param uvarint the varint-encoded form of the code
* @param status the registration status
*/
protected Multicodec(String name, Tag tag, long code, byte[] uvarint, Status status) {
this.tag = tag;
this.name = name;
this.code = code;
this.codeVarint = uvarint;
this.status = status;
}
/**
* Creates a new {@code Multicodec} with no explicit status.
*
* @param name the codec name
* @param tag the codec category tag
* @param code the codec numeric code
* @return a new {@code Multicodec} instance
*/
public static Multicodec of(String name, Tag tag, long code) {
return of(name, tag, code, null);
}
/**
* Creates a new {@code Multicodec}.
*
* @param name the codec name
* @param tag the codec category tag
* @param code the codec numeric code
* @param status the codec registration status
* @return a new {@code Multicodec} instance
*/
public static Multicodec of(String name, Tag tag, long code, Status status) {
return new Multicodec(name, tag, code, UVarInt.encode(code), status);
}
/**
* Encodes the given value by prefixing it with this codec's varint code.
*
* @param value the non-empty byte array to encode
* @return the encoded byte array (varint prefix + original value)
* @throws NullPointerException if {@code value} is {@code null}
* @throws IllegalArgumentException if {@code value} is empty
*/
public byte[] encode(final byte[] value) {
return encode(value, 0, value.length);
}
/**
* Encodes a value into this codec starting from the given index.
*
* <p>
* Prepends this codec's varint prefix to the specified slice of the input and
* returns the encoded byte array.
* </p>
*
* @param value the input byte array to encode
* @param index the starting index (inclusive)
* @return the encoded value
* @throws NullPointerException if {@code value} is {@code null}
* @throws IllegalArgumentException if {@code index} is out of range or if the
* remaining length is invalid
*/
public byte[] encode(final byte[] value, int index) {
return encode(value, index, value.length - index);
}
/**
* Encodes a value into this codec from a given subrange.
*
* <p>
* Prepends this codec's varint prefix to the specified range of the input and
* returns the encoded byte array.
* </p>
*
* @param value the input byte array to encode
* @param index the starting index (inclusive)
* @param length the number of bytes to include from {@code index}
* @return the encoded value
* @throws NullPointerException if {@code value} is {@code null}
* @throws IllegalArgumentException if {@code index} is out of range or if
* {@code length} exceeds the available bytes
*/
public byte[] encode(final byte[] value, int index, int length) {
Objects.requireNonNull(value);
if (index >= value.length) {
throw new IllegalArgumentException(
"Index " + index + " is out of range for array length " + value.length + ".");
}
if (length > (value.length - index)) {
throw new IllegalArgumentException(
"Requested length (" + length + ") exceeds available bytes (" + (value.length - index) + ").");
}
final byte[] encoded = new byte[codeVarint.length + length];
System.arraycopy(codeVarint, 0, encoded, 0, codeVarint.length);
System.arraycopy(value, index, encoded, codeVarint.length, length);
return encoded;
}
/**
* Checks if the given encoded byte array starts with this codec's varint code.
*
* @param encoded the byte array to test
* @return {@code true} if {@code encoded} starts with this codec's varint
* prefix, {@code false} otherwise
*/
public boolean isEncoded(final byte[] encoded) {
return encoded != null
&& encoded.length >= codeVarint.length
&& IntStream.range(0, codeVarint.length).allMatch(i -> codeVarint[i] == encoded[i]);
}
/**
* Decodes an encoded value by removing this codec's varint prefix.
*
* @param encoded the encoded value
* @return the decoded (original) byte array
* @throws NullPointerException if {@code encoded} is {@code null}
* @throws IllegalArgumentException if {@code encoded} is too short or does not
* begin with this codec's varint prefix
*/
public byte[] decode(final byte[] encoded) {
return decode(encoded, 0, encoded.length);
}
/**
* Decodes an encoded value starting from a given index.
*
* @param encoded the encoded byte array
* @param index the starting index (inclusive)
* @return the decoded (original) payload
* @throws NullPointerException if {@code encoded} is {@code null}
* @throws IllegalArgumentException if {@code index} or {@code length} are
* invalid, if the data is too short, or if it
* does not start with this codec's prefix
*/
public byte[] decode(final byte[] encoded, final int index) {
return decode(encoded, index, encoded.length - index);
}
/**
* Decodes an encoded value from a subrange of the array.
*
* @param encoded the encoded byte array
* @param index the starting index (inclusive)
* @param length the number of bytes to read from {@code index}
* @return the decoded payload
* @throws NullPointerException if {@code encoded} is {@code null}
* @throws IllegalArgumentException if the index/length are invalid, the range
* is too short, or the data does not start
* with this codec's prefix
*/
public byte[] decode(final byte[] encoded, final int index, final int length) {
Objects.requireNonNull(encoded);
if (length > (encoded.length - index)) {
throw new IllegalArgumentException(
"The requested decode length (" + length + ") is greater than the available bytes (" + (encoded.length - index) + ").");
}
if (length < (codeVarint.length + 1)) {
throw new IllegalArgumentException(
"The value to decode must be a non-empty byte array with a minimum length of "
+ (codeVarint.length + 1) + " bytes, but the actual length is " + length + " bytes.");
}
if (!IntStream.range(0, codeVarint.length).allMatch(i -> codeVarint[i] == encoded[i + index])) {
throw new IllegalArgumentException(
"The provided value is not encoded with this codec: " + toString() + ".");
}
return Arrays.copyOfRange(encoded, index + codeVarint.length, length + index);
}
@Override
public int hashCode() {
return Objects.hash(code);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Multicodec other = (Multicodec) obj;
return code == other.code;
}
@Override
public String toString() {
return "Multicodec [name=" + name + ", tag=" + tag + ", code=" + code + "]";
}
/**
* Returns the length of this codec's varint code in bytes.
*
* @return varint length
*/
public int length() {
return codeVarint.length;
}
/**
* Returns the varint-encoded form of this codec's code.
*
* @return varint byte array
*/
public byte[] varint() {
return codeVarint;
}
/**
* Returns this codec's category tag.
*
* @return the tag
*/
public Tag tag() {
return tag;
}
/**
* Returns this codec's name.
*
* @return the name
*/
public String name() {
return name;
}
/**
* Returns this codec's numeric code.
*
* @return the numeric code
*/
public long code() {
return code;
}
/**
* Returns this codec's registration status.
*
* @return the status, or {@code null} if unspecified
*/
public Status status() {
return status;
}
}