diff --git a/README.md b/README.md index 830d537..aa4453a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Copper Multicodec is a Java library that implements [Multicodec](https://github. [![Java 8 CI](https://github.com/filip26/copper-multicodec/actions/workflows/java8-build.yml/badge.svg)](https://github.com/filip26/copper-multicodec/actions/workflows/java8-build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=filip26_copper-multicodec&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=filip26_copper-multicodec) +[![javadoc](https://javadoc.io/badge2/com.apicatalog/copper-multicodec/javadoc.svg)](https://javadoc.io/doc/com.apicatalog/copper-multicodec) [![Maven Central](https://img.shields.io/maven-central/v/com.apicatalog/copper-multicodec.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:com.apicatalog%20AND%20a:copper-multicodec) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) @@ -16,6 +17,8 @@ Copper Multicodec is a Java library that implements [Multicodec](https://github. - **Unsigned VarInt Support:** Handles unsigned variable-length integers. - **Zero Third-Party Dependencies:** Ensures a lightweight and self-contained implementation. +[Supported Codecs](https://github.com/filip26/copper-multicodec/tree/main/src/main/java/com/apicatalog/multicodec/codec) + ## Examples ```java @@ -110,15 +113,10 @@ To include Copper Multicodec in your project, add the following dependency to yo com.apicatalog copper-multicodec - 2.1.0 + 2.2.0 ``` -## Documentation - -* [![javadoc](https://javadoc.io/badge2/com.apicatalog/copper-multicodec/javadoc.svg)](https://javadoc.io/doc/com.apicatalog/copper-multicodec) -* [Supported Codecs](https://github.com/filip26/copper-multicodec/tree/main/src/main/java/com/apicatalog/multicodec/codec) - ## Contributing All PR's welcome! diff --git a/pom.xml b/pom.xml index dd06502..6dda83e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.apicatalog copper-multicodec - 2.2.0-SNAPSHOT + 2.2.0 jar https://github.com/filip26/copper-multicodec diff --git a/src/main/java/com/apicatalog/multicodec/MulticodecDecoder.java b/src/main/java/com/apicatalog/multicodec/MulticodecDecoder.java index 2ea55ac..1a1ba60 100644 --- a/src/main/java/com/apicatalog/multicodec/MulticodecDecoder.java +++ b/src/main/java/com/apicatalog/multicodec/MulticodecDecoder.java @@ -55,6 +55,12 @@ public static MulticodecDecoder getInstance() { * @return a new decoder instance containing only codecs with matching tags */ public static MulticodecDecoder getInstance(Tag... tags) { + Objects.requireNonNull(tags); + + if (tags.length == 0) { + throw new IllegalArgumentException("At least one tag must be provided."); + } + return new MulticodecDecoder(MulticodecRegistry.getInstance(tags)); } @@ -66,6 +72,12 @@ public static MulticodecDecoder getInstance(Tag... tags) { * @return a new decoder instance containing only the provided codecs */ public static MulticodecDecoder getInstance(Multicodec... codecs) { + Objects.requireNonNull(codecs); + + if (codecs.length == 0) { + throw new IllegalArgumentException("At least one codec must be provided."); + } + return new MulticodecDecoder(MulticodecRegistry.getInstance(codecs)); } diff --git a/src/main/java/com/apicatalog/multicodec/codec/MulticodecRegistry.java b/src/main/java/com/apicatalog/multicodec/codec/MulticodecRegistry.java index ec28232..a5d3a89 100644 --- a/src/main/java/com/apicatalog/multicodec/codec/MulticodecRegistry.java +++ b/src/main/java/com/apicatalog/multicodec/codec/MulticodecRegistry.java @@ -3,6 +3,7 @@ import java.util.Arrays; import java.util.EnumMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -91,6 +92,7 @@ public static final MulticodecRegistry getInstance() { * @return a new registry instance containing only matching codecs */ public static final MulticodecRegistry getInstance(final Tag... tags) { + Objects.requireNonNull(tags); return new MulticodecRegistry(provided(tags)); } @@ -101,6 +103,12 @@ public static final MulticodecRegistry getInstance(final Tag... tags) { * @return a new registry instance containing only the provided codecs */ public static final MulticodecRegistry getInstance(final Multicodec... codecs) { + Objects.requireNonNull(codecs); + + if (codecs.length == 0) { + throw new IllegalArgumentException("At least one codec must be provided."); + } + return new MulticodecRegistry(Arrays.stream(codecs) .collect(Collectors.toMap(Multicodec::code, Function.identity()))); } @@ -144,6 +152,23 @@ public final Optional getCodec(final long code) { return Optional.ofNullable(codecs.get(code)); } + /** + * Finds a registered {@link Multicodec} by its name. + *

+ * This method searches through all registered codecs and returns the first + * match whose {@link Multicodec#name()} equals the given name. + *

+ * + * @param name the name of the codec to look up (must not be {@code null}) + * @return an {@link Optional} containing the matching {@link Multicodec}, or an + * empty {@link Optional} if no codec with the given name is found + */ + public final Optional findCodec(final String name) { + return codecs.values().stream() + .filter(codec -> codec.name().equals(name)) + .findFirst(); + } + /** * Returns the map of codecs contained in this registry instance. * @@ -152,4 +177,14 @@ public final Optional getCodec(final long code) { public Map codecs() { return codecs; } + + /** + * Returns the number of registered codecs in this registry. + * + * @return the total count of registered codecs + */ + public long size() { + return codecs.size(); + } + } diff --git a/src/main/java/com/apicatalog/multicodec/codec/package-info.java b/src/main/java/com/apicatalog/multicodec/codec/package-info.java new file mode 100644 index 0000000..c0a7677 --- /dev/null +++ b/src/main/java/com/apicatalog/multicodec/codec/package-info.java @@ -0,0 +1,30 @@ +/** + * Provides the codec registry and predefined codec sets for the + * multicodec format. + * + *

+ * The {@code codec} subpackage contains the + * {@link com.apicatalog.multicodec.codec.MulticodecRegistry}, which maintains + * mappings between multicodec codes and their + * {@link com.apicatalog.multicodec.Multicodec} definitions. It supports + * creating registry instances containing: + *

+ * + *
    + *
  • All known codecs ({@code MulticodecRegistry.getInstance()})
  • + *
  • Only codecs matching specific + * {@link com.apicatalog.multicodec.Multicodec.Tag}s
  • + *
  • Only explicitly provided codecs
  • + *
+ * + *

Usage Example

+ * + *
{@code
+ * // registry with all known codecs
+ * MulticodecRegistry registry = MulticodecRegistry.getInstance();
+ *
+ * // registry with only a subset of codecs by tag
+ * MulticodecRegistry hashes = MulticodecRegistry.getInstance(Tag.Hash);
+ * }
+ */ +package com.apicatalog.multicodec.codec; diff --git a/src/main/java/com/apicatalog/multicodec/package-info.java b/src/main/java/com/apicatalog/multicodec/package-info.java new file mode 100644 index 0000000..e452d2c --- /dev/null +++ b/src/main/java/com/apicatalog/multicodec/package-info.java @@ -0,0 +1,35 @@ +/** + * Core classes for working with + * multicodec + * identifiers and encoded values. + * + *

+ * A multicodec is a self-describing format in which the data is + * prefixed by a varint code identifying the codec. This package provides: + *

+ * + *
    + *
  • {@link com.apicatalog.multicodec.Multicodec} – immutable definition of a + * codec including its name, tag, numeric code, varint representation, and + * status.
  • + *
  • {@link com.apicatalog.multicodec.MulticodecDecoder} – decoder that uses a + * {@link com.apicatalog.multicodec.codec.MulticodecRegistry} to resolve and + * decode multicodec-encoded values.
  • + *
+ * + *

Usage Example

+ * + *
{@code
+ * // obtain a decoder with all known codecs
+ * MulticodecDecoder decoder = MulticodecDecoder.getInstance();
+ *
+ * byte[] encoded = ... ;   // some multicodec-encoded data
+ * byte[] decoded = decoder.decode(encoded);
+ * }
+ * + *

+ * For additional codecs, see the + * {@link com.apicatalog.multicodec.codec} subpackage. + *

+ */ +package com.apicatalog.multicodec; diff --git a/src/main/java/com/apicatalog/multihash/package-info.java b/src/main/java/com/apicatalog/multihash/package-info.java new file mode 100644 index 0000000..ace677d --- /dev/null +++ b/src/main/java/com/apicatalog/multihash/package-info.java @@ -0,0 +1,20 @@ +/** + * Support for multihash. + * + *

+ * A multihash is a self-describing cryptographic hash format. It + * encodes a digest as: + *

+ *
    + *
  1. a multicodec code identifying the hash algorithm,
  2. + *
  3. a varint indicating the length of the digest,
  4. + *
  5. the digest bytes themselves.
  6. + *
+ * + *

+ * This package provides {@link com.apicatalog.multihash.Multihash}, a + * specialization of {@link com.apicatalog.multicodec.Multicodec} that + * implements the multihash format. Instances are immutable and thread-safe. + *

+ */ +package com.apicatalog.multihash; \ No newline at end of file diff --git a/src/main/java/com/apicatalog/uvarint/UVarInt.java b/src/main/java/com/apicatalog/uvarint/UVarInt.java index 16ae622..60396ec 100644 --- a/src/main/java/com/apicatalog/uvarint/UVarInt.java +++ b/src/main/java/com/apicatalog/uvarint/UVarInt.java @@ -1,14 +1,37 @@ package com.apicatalog.uvarint; +/** + * Utility class for encoding and decoding + * unsigned variable-length + * integers (UVarInt). + * + *

+ * A UVarInt encodes a non-negative integer into one or more bytes. Each byte + * contributes 7 bits of information, and the most significant bit (MSB) + * indicates whether more bytes follow. This encoding is commonly used in + * multiformats specifications, including + * multicodec and multihash. + *

+ * + *

+ * This class is {@code final} and contains only static methods. + *

+ */ public final class UVarInt { - /** Maximum encoded var length in bytes */ + /** Maximum encoded varint length in bytes. */ public static final int MAX_VAR_LENGTH = 9; + /** Mask for the 7 value bits within a varint byte. */ public static final int SEGMENT_BITS = 0x7F; + + /** Continuation bit (MSB) indicating more bytes follow. */ public static final int CONTINUE_BIT = 0x80; + + /** Mask used for detecting overflow conditions. */ public static final int INT_OVERFLOW_BITS = 0x70; + /** Precomputed maximum values for each encoded length. */ protected static long[] MAX_VALUES = { 0x7FL, 0x3FFFL, @@ -21,12 +44,20 @@ public final class UVarInt { 0x7FFFFFFFFFFFFFFFL }; + /** Prevent instantiation. */ protected UVarInt() { - /* protected */ + /* no-op */ } + /** + * Encodes the given value into a UVarInt byte array. + * + * @param value the value to encode (must be non-negative) + * @return a newly allocated byte array containing the encoded value + * @throws IllegalArgumentException if {@code value} cannot be encoded within + * {@link #MAX_VAR_LENGTH} bytes + */ public static final byte[] encode(final long value) { - final int length = byteLength(value); if (length == 1) { @@ -34,67 +65,92 @@ public static final byte[] encode(final long value) { } byte[] uvarint = new byte[length]; - write(value, uvarint, 0); - return uvarint; } + /** + * Writes the UVarInt encoding of the given value into the target array starting + * at the given index. + * + * @param value the value to encode + * @param uvarint the target byte array + * @param index the starting index + * @throws IllegalArgumentException if {@code value} cannot be encoded within + * {@link #MAX_VAR_LENGTH} bytes + * @throws NullPointerException if {@code uvarint} is {@code null} + */ public static final void write(final long value, final byte[] uvarint, final int index) { - int offset = 0; long bytes = value; - boolean next = false; + do { if (next) { - uvarint[offset + index - 1] |= UVarInt.CONTINUE_BIT; + uvarint[offset + index - 1] |= CONTINUE_BIT; } - uvarint[offset + index] = (byte) (bytes & UVarInt.SEGMENT_BITS); + uvarint[offset + index] = (byte) (bytes & SEGMENT_BITS); bytes >>>= 7; - next = bytes != 0; - offset++; - } while (next); - } + /** + * Decodes a UVarInt from the given byte array starting at index {@code 0}. + * + * @param uvarint the encoded byte array + * @return the decoded value + * @throws IllegalArgumentException if the encoding is invalid or too long + * @throws NullPointerException if {@code uvarint} is {@code null} + */ public static final long decode(final byte[] uvarint) { return decode(uvarint, 0); } + /** + * Decodes a UVarInt from the given byte array starting at the specified index. + * + * @param uvarint the encoded byte array + * @param index the starting index + * @return the decoded value + * @throws IllegalArgumentException if the encoding is invalid or too long + * @throws NullPointerException if {@code uvarint} is {@code null} + */ public static final long decode(final byte[] uvarint, int index) { - int offset = 0; - boolean next = false; long value = 0; do { - if (offset >= UVarInt.MAX_VAR_LENGTH) { - throw new IllegalArgumentException("uintvar longer than " + UVarInt.MAX_VAR_LENGTH + " has been found. Only uintvar up to " + UVarInt.MAX_VAR_LENGTH + " are supported."); + if (offset >= MAX_VAR_LENGTH) { + throw new IllegalArgumentException( + "UVarInt longer than " + MAX_VAR_LENGTH + " bytes is not supported."); } if (offset >= uvarint.length) { - throw new IllegalArgumentException("The input stream has ended unexpectedly, a next byte is expected."); + throw new IllegalArgumentException( + "Unexpected end of input: next byte is required."); } int b = uvarint[offset + index]; - - value |= (long) (b & UVarInt.SEGMENT_BITS) << (offset * 7); - - next = ((b & UVarInt.CONTINUE_BIT) != 0); - + value |= (long) (b & SEGMENT_BITS) << (offset * 7); + next = ((b & CONTINUE_BIT) != 0); offset++; - } while (next); return value; } + /** + * Returns the number of bytes required to encode the given value as a UVarInt. + * + * @param value the value to measure + * @return the number of bytes required + * @throws IllegalArgumentException if {@code value} cannot be encoded within + * {@link #MAX_VAR_LENGTH} bytes + */ public static final int byteLength(long value) { if (value <= MAX_VALUES[0]) { return 1; @@ -112,6 +168,7 @@ public static final int byteLength(long value) { return (value <= MAX_VALUES[7]) ? 8 : MAX_VAR_LENGTH; } - throw new IllegalArgumentException("A var longer than " + UVarInt.MAX_VAR_LENGTH + " has been found. Only vars up to " + UVarInt.MAX_VAR_LENGTH + " are supported."); + throw new IllegalArgumentException( + "Value exceeds maximum encodable length of " + MAX_VAR_LENGTH + " bytes."); } } diff --git a/src/main/java/com/apicatalog/uvarint/package-info.java b/src/main/java/com/apicatalog/uvarint/package-info.java new file mode 100644 index 0000000..2aa2cc1 --- /dev/null +++ b/src/main/java/com/apicatalog/uvarint/package-info.java @@ -0,0 +1,26 @@ +/** + * Utilities for working with unsigned variable-length integers (UVarInt). + * + *

+ * A UVarInt is an encoding of an unsigned integer into one or more + * bytes using a variable-length scheme. Each byte contributes 7 bits of data, + * and the highest bit indicates whether more bytes follow. This encoding is + * widely used in multiformats + * specifications such as multicodec and multihash. + *

+ * + *

+ * The {@link com.apicatalog.uvarint.UVarInt} class provides static methods for: + *

+ *
    + *
  • Encoding a {@code long} into UVarInt form
  • + *
  • Decoding a UVarInt-encoded byte array back into a {@code long}
  • + *
  • Determining the number of bytes required to encode a given value
  • + *
+ * + *

+ * Encoded values are limited to at most + * {@link com.apicatalog.uvarint.UVarInt#MAX_VAR_LENGTH} bytes. + *

+ */ +package com.apicatalog.uvarint;