- * This reference implementation stores objects as-is. It does not implement
- * encryption at rest. It also must not persist private key material; higher
- * layers must respect the SPI security requirements.
+ * This reference implementation stores domain objects through a closed,
+ * current-version schema. It does not implement encryption at rest. It also
+ * must not persist private key material; higher layers must respect the SPI
+ * security requirements. Earlier pre-release formats are rejected rather than
+ * migrated.
*
*
*
@@ -135,9 +137,9 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
private static final Logger LOG = Logger.getLogger(FilesystemPkiStore.class.getName());
- private static final String VERSION_V1 = "v1";
+ /* package */ static final String CURRENT_STORE_VERSION = "v2";
private static final int SIGN_RECORD_MAGIC = 0x5A455352;
- private static final int SIGN_RECORD_VERSION = 1;
+ private static final int CURRENT_SIGN_RECORD_VERSION = 2;
private static final int SIGN_RECORD_HEADER_BYTES = Integer.BYTES * 2;
private static final String SIGN_FINGERPRINT_PREFIX = "signfp:v1:";
private static final int SIGN_FINGERPRINT_HEX_LENGTH = 64;
@@ -248,21 +250,21 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
PkiId caId = record.caId();
Path current = this.paths.caCurrent(caId);
- writeWithHistory(this.paths.caHistoryDir(caId), current, FsCodec.encode(record), this.options.caHistoryPolicy(),
- "CA", FsUtil.safeId(caId));
+ writeWithHistory(this.paths.caHistoryDir(caId), current, FsCodec.encode(FsCodec.CA_RECORD, record),
+ this.options.caHistoryPolicy(), "CA", FsUtil.safeId(caId));
}
@Override
public Optional getCa(final PkiId caId) {
Objects.requireNonNull(caId, "caId");
Path p = this.paths.caCurrent(caId);
- return readOptional(p, CaRecord.class);
+ return readOptional(p, FsCodec.CA_RECORD);
}
@Override
public List listCas() {
Path casRoot = this.paths.root().resolve("cas").resolve("by-id");
- return listCurrentRecords(casRoot, CaRecord.class);
+ return listCurrentRecords(casRoot, FsCodec.CA_RECORD);
}
@Override
@@ -270,26 +272,27 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
Objects.requireNonNull(credential, "credential");
PkiId id = credential.credentialId();
Path p = this.paths.credentialPath(id);
- writeOnce(p, FsCodec.encode(credential), "CREDENTIAL", FsUtil.safeId(id));
+ writeOnce(p, FsCodec.encode(FsCodec.CREDENTIAL, credential), "CREDENTIAL", FsUtil.safeId(id));
}
@Override
public Optional getCredential(final PkiId credentialId) {
Objects.requireNonNull(credentialId, "credentialId");
- return readOptional(this.paths.credentialPath(credentialId), Credential.class);
+ return readOptional(this.paths.credentialPath(credentialId), FsCodec.CREDENTIAL);
}
@Override
public void putRequest(final ParsedCertificationRequest request) {
Objects.requireNonNull(request, "request");
PkiId id = request.requestId();
- writeOnce(this.paths.requestPath(id), FsCodec.encode(request), "REQUEST", FsUtil.safeId(id));
+ writeOnce(this.paths.requestPath(id), FsCodec.encode(FsCodec.PARSED_REQUEST, request), "REQUEST",
+ FsUtil.safeId(id));
}
@Override
public Optional getRequest(final PkiId requestId) {
Objects.requireNonNull(requestId, "requestId");
- return readOptional(this.paths.requestPath(requestId), ParsedCertificationRequest.class);
+ return readOptional(this.paths.requestPath(requestId), FsCodec.PARSED_REQUEST);
}
@Override
@@ -298,33 +301,34 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
PkiId credId = record.credentialId();
Path current = this.paths.revocationCurrent(credId);
- writeWithHistory(this.paths.revocationHistoryDir(credId), current, FsCodec.encode(record),
+ writeWithHistory(this.paths.revocationHistoryDir(credId), current, FsCodec.encode(FsCodec.REVOCATION, record),
this.options.revocationHistoryPolicy(), "REVOCATION", FsUtil.safeId(credId));
}
@Override
public Optional getRevocation(final PkiId credentialId) {
Objects.requireNonNull(credentialId, "credentialId");
- return readOptional(this.paths.revocationCurrent(credentialId), RevokedRecord.class);
+ return readOptional(this.paths.revocationCurrent(credentialId), FsCodec.REVOCATION);
}
@Override
public List listRevocations() {
Path root = this.paths.root().resolve("revocations").resolve("by-credential");
- return listCurrentRecords(root, RevokedRecord.class);
+ return listCurrentRecords(root, FsCodec.REVOCATION);
}
@Override
public void putStatusObject(final StatusObject object) {
Objects.requireNonNull(object, "object");
PkiId id = object.statusObjectId();
- writeOnce(this.paths.statusObjectPath(id), FsCodec.encode(object), "STATUS_OBJECT", FsUtil.safeId(id));
+ writeOnce(this.paths.statusObjectPath(id), FsCodec.encode(FsCodec.STATUS_OBJECT, object), "STATUS_OBJECT",
+ FsUtil.safeId(id));
}
@Override
public Optional getStatusObject(final PkiId statusObjectId) {
Objects.requireNonNull(statusObjectId, "statusObjectId");
- return readOptional(this.paths.statusObjectPath(statusObjectId), StatusObject.class);
+ return readOptional(this.paths.statusObjectPath(statusObjectId), FsCodec.STATUS_OBJECT);
}
@Override
@@ -335,7 +339,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
// This is acceptable for a reference implementation; indexes can be added
// later.
Path byId = this.paths.root().resolve("status").resolve("by-id");
- List all = listBinaryFiles(byId, StatusObject.class);
+ List all = listBinaryFiles(byId, FsCodec.STATUS_OBJECT);
List out = new ArrayList<>();
for (StatusObject o : all) {
if (issuerCaId.equals(o.issuerCaId())) {
@@ -349,13 +353,14 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
public void putPublicationRecord(final PublicationRecord record) {
Objects.requireNonNull(record, "record");
PkiId id = record.publicationId();
- writeOnce(this.paths.publicationPath(id), FsCodec.encode(record), "PUBLICATION", FsUtil.safeId(id));
+ writeOnce(this.paths.publicationPath(id), FsCodec.encode(FsCodec.PUBLICATION, record), "PUBLICATION",
+ FsUtil.safeId(id));
}
@Override
public List listPublicationRecords() {
Path byId = this.paths.root().resolve("publications").resolve("by-id");
- return listBinaryFiles(byId, PublicationRecord.class);
+ return listBinaryFiles(byId, FsCodec.PUBLICATION);
}
@Override
@@ -364,7 +369,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
String profileId = profile.profileId();
Path current = this.paths.profileCurrent(profileId);
- writeWithHistory(this.paths.profileHistoryDir(profileId), current, FsCodec.encode(profile),
+ writeWithHistory(this.paths.profileHistoryDir(profileId), current,
+ FsCodec.encode(FsCodec.CERTIFICATE_PROFILE, profile),
this.options.profileHistoryPolicy(), "PROFILE", FsUtil.safeSegment(profileId));
}
@@ -373,26 +379,27 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
if (profileId == null || profileId.isBlank()) {
throw new IllegalArgumentException("profileId must not be null/blank");
}
- return readOptional(this.paths.profileCurrent(profileId), CertificateProfile.class);
+ return readOptional(this.paths.profileCurrent(profileId), FsCodec.CERTIFICATE_PROFILE);
}
@Override
public List listProfiles() {
Path root = this.paths.root().resolve("profiles").resolve("by-id");
- return listCurrentRecords(root, CertificateProfile.class);
+ return listCurrentRecords(root, FsCodec.CERTIFICATE_PROFILE);
}
@Override
public void putPolicyTrace(final PolicyTrace trace) {
Objects.requireNonNull(trace, "trace");
PkiId id = trace.decisionId();
- writeOnce(this.paths.policyTracePath(id), FsCodec.encode(trace), "POLICY_TRACE", FsUtil.safeId(id));
+ writeOnce(this.paths.policyTracePath(id), FsCodec.encode(FsCodec.POLICY_TRACE, trace), "POLICY_TRACE",
+ FsUtil.safeId(id));
}
@Override
public Optional getPolicyTrace(final PkiId decisionId) {
Objects.requireNonNull(decisionId, "decisionId");
- return readOptional(this.paths.policyTracePath(decisionId), PolicyTrace.class);
+ return readOptional(this.paths.policyTracePath(decisionId), FsCodec.POLICY_TRACE);
}
// -------------------------------------------------------------------------
@@ -407,14 +414,14 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
Path current = this.paths.workflowCurrent(opId);
// Never log payload; writeWithHistory logs only type + safe identifiers.
- writeWithHistory(this.paths.workflowHistoryDir(opId), current, FsCodec.encode(record),
+ writeWithHistory(this.paths.workflowHistoryDir(opId), current, FsCodec.encode(FsCodec.WORKFLOW_STATE, record),
this.options.workflowHistoryPolicy(), "WORKFLOW", FsUtil.safeId(opId));
}
@Override
public Optional getWorkflowState(final PkiId opId) {
Objects.requireNonNull(opId, "opId");
- return readOptional(this.paths.workflowCurrent(opId), WorkflowStateRecord.class);
+ return readOptional(this.paths.workflowCurrent(opId), FsCodec.WORKFLOW_STATE);
}
@Override
@@ -432,7 +439,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
@Override
public List listWorkflowStates() {
// List by operation directory (workflows/by-op//current.bin)
- return listCurrentRecords(this.paths.workflowRoot(), WorkflowStateRecord.class);
+ return listCurrentRecords(this.paths.workflowRoot(), FsCodec.WORKFLOW_STATE);
}
@Override
@@ -710,12 +717,12 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
if (input.getInt() != SIGN_RECORD_MAGIC) {
throw new IllegalStateException("Invalid signing record envelope");
}
- if (input.getInt() != SIGN_RECORD_VERSION) {
+ if (input.getInt() != CURRENT_SIGN_RECORD_VERSION) {
throw new IllegalStateException("Unsupported signing record version");
}
byte[] payload = new byte[input.remaining()];
input.get(payload);
- return FsCodec.decode(payload, SignWorkflowStore.Record.class);
+ return FsCodec.decode(FsCodec.SIGN_WORKFLOW_RECORD, payload);
} catch (IOException ex) {
throw new IllegalStateException("Failed to read authoritative signing record", ex);
}
@@ -796,10 +803,10 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
private void writeSignRecord(SignWorkflowStore.Record record) {
validateSignRecord(record.submissionId(), record);
try {
- byte[] payload = FsCodec.encode(record);
+ byte[] payload = FsCodec.encode(FsCodec.SIGN_WORKFLOW_RECORD, record);
ByteBuffer envelope = ByteBuffer.allocate(SIGN_RECORD_HEADER_BYTES + payload.length);
envelope.putInt(SIGN_RECORD_MAGIC);
- envelope.putInt(SIGN_RECORD_VERSION);
+ envelope.putInt(CURRENT_SIGN_RECORD_VERSION);
envelope.put(payload);
FsOperations.writeAtomic(paths.signWorkflowPath(record.submissionId()), envelope.array());
} catch (IOException ex) {
@@ -970,7 +977,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
private static IllegalStateException invalidSignRecord(SignWorkflowStore.Record record, String code) {
return new IllegalStateException("Signing record corruption: type=sign-workflow version="
- + SIGN_RECORD_VERSION + " state=" + record.state() + " code=" + code);
+ + CURRENT_SIGN_RECORD_VERSION + " state=" + record.state() + " code=" + code);
}
private static void requirePositive(Duration value, String name) {
@@ -1145,28 +1152,28 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
private void ensureVersionFile() throws IOException {
Path vf = this.paths.versionFile();
if (!Files.exists(vf)) {
- FsOperations.writeAtomic(vf, VERSION_V1.getBytes());
+ FsOperations.writeAtomic(vf, CURRENT_STORE_VERSION.getBytes());
return;
}
String ver = Files.readString(vf).trim();
- if (!VERSION_V1.equals(ver)) {
+ if (!CURRENT_STORE_VERSION.equals(ver)) {
throw new IllegalStateException("unsupported store version: " + ver);
}
}
- private static Optional readOptional(final Path path, final Class type) {
+ private static Optional readOptional(final Path path, final FsCodec.Schema schema) {
try {
if (!Files.exists(path)) {
return Optional.empty();
}
byte[] data = FsOperations.readAll(path);
- return Optional.of(FsCodec.decode(data, type));
+ return Optional.of(FsCodec.decode(schema, data));
} catch (IOException e) {
throw new IllegalStateException("read failed: " + path, e);
}
}
- private static List listBinaryFiles(final Path byIdDir, final Class type) {
+ private static List listBinaryFiles(final Path byIdDir, final FsCodec.Schema schema) {
if (!Files.isDirectory(byIdDir)) {
return List.of();
}
@@ -1174,7 +1181,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
return Files.list(byIdDir).filter(Files::isRegularFile)
.sorted(Comparator.comparing(p -> p.getFileName().toString())).map(p -> {
try {
- return FsCodec.decode(FsOperations.readAll(p), type);
+ return FsCodec.decode(schema, FsOperations.readAll(p));
} catch (IOException e) {
throw new IllegalStateException("read failed: " + p, e);
}
@@ -1184,7 +1191,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
}
}
- private static List listCurrentRecords(final Path byIdDir, final Class type) {
+ private static List listCurrentRecords(final Path byIdDir, final FsCodec.Schema schema) {
if (!Files.isDirectory(byIdDir)) {
return List.of();
}
@@ -1196,7 +1203,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable {
for (Path entityDir : entityDirs) {
Path current = entityDir.resolve(FsPaths.CURRENT_FILE);
if (Files.exists(current)) {
- out.add(FsCodec.decode(FsOperations.readAll(current), type));
+ out.add(FsCodec.decode(schema, FsOperations.readAll(current)));
}
}
return out;
diff --git a/pki/src/main/java/zeroecho/pki/impl/fs/FsCodec.java b/pki/src/main/java/zeroecho/pki/impl/fs/FsCodec.java
index c7d918b..10d6e02 100644
--- a/pki/src/main/java/zeroecho/pki/impl/fs/FsCodec.java
+++ b/pki/src/main/java/zeroecho/pki/impl/fs/FsCodec.java
@@ -23,555 +23,1021 @@
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.pki.impl.fs;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.RecordComponent;
import java.time.Duration;
import java.time.Instant;
+import java.time.DateTimeException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import zeroecho.core.io.Util;
+import zeroecho.pki.api.EncodedObject;
+import zeroecho.pki.api.Encoding;
+import zeroecho.pki.api.FormatId;
+import zeroecho.pki.api.IssuerRef;
+import zeroecho.pki.api.KeyRef;
+import zeroecho.pki.api.PkiId;
+import zeroecho.pki.api.SubjectRef;
+import zeroecho.pki.api.Validity;
+import zeroecho.pki.api.attr.AttributeId;
+import zeroecho.pki.api.attr.AttributeSet;
+import zeroecho.pki.api.attr.AttributeValue;
+import zeroecho.pki.api.audit.Principal;
+import zeroecho.pki.api.ca.CaKind;
+import zeroecho.pki.api.ca.CaRecord;
+import zeroecho.pki.api.ca.CaState;
+import zeroecho.pki.api.credential.Credential;
+import zeroecho.pki.api.credential.CredentialStatus;
+import zeroecho.pki.api.orch.OrchestrationDurabilityPolicy;
+import zeroecho.pki.api.orch.WorkflowStateRecord;
+import zeroecho.pki.api.policy.PolicyTrace;
+import zeroecho.pki.api.policy.PolicyTraceStep;
+import zeroecho.pki.api.profile.CertificateProfile;
+import zeroecho.pki.api.publication.PublicationRecord;
+import zeroecho.pki.api.publication.PublicationStatus;
+import zeroecho.pki.api.publication.PublicationTarget;
+import zeroecho.pki.api.publication.PublicationTargetType;
+import zeroecho.pki.api.request.ParsedCertificationRequest;
+import zeroecho.pki.api.revocation.RevocationReason;
+import zeroecho.pki.api.revocation.RevokedRecord;
+import zeroecho.pki.api.status.StatusObject;
+import zeroecho.pki.api.status.StatusObjectType;
+import zeroecho.pki.impl.core.attr.SimpleAttributeSet;
import zeroecho.pki.spi.store.SignWorkflowStore;
/**
- * Compact binary codec for filesystem persistence.
- *
- *
Encoding format
- *
- * The codec supports two on-wire formats:
- *
- *
- *
Compact format (current): begins with a 1-byte MAGIC marker
- * {@code 0xFF}, followed by a 1-byte TAG and tag-specific payload.
- *
Legacy format (backward-compatible): begins with a UTF-8 encoded
- * Java class name (written by
- * {@link zeroecho.core.io.Util#writeUTF8(OutputStream, String)}), followed by
- * value payload based on that class.
- *
+ * Closed binary codec for the current filesystem persistence schema.
*
*
- * The compact format eliminates the pathological overhead of writing a full
- * Java class name for every scalar value (e.g., {@code boolean}). It also
- * substantially reduces class-loading activity during decode.
+ * Every payload carries a fixed magic value, one current codec version, and one
+ * of ten explicit top-level type identifiers. Nested values are decoded only
+ * through the schema selected by trusted store code. Java class names are never
+ * persisted or resolved, and this codec performs no runtime class loading,
+ * reflective construction, or factory discovery.
*
*
- *
Type compatibility
*
- * During decode, the codec enforces that the encoded value type is compatible
- * with the expected type. Primitive/wrapper pairs (e.g.,
- * {@code boolean}/{@link Boolean}) are treated as compatible because values are
- * boxed when represented as {@link Object}.
+ * Pre-release payloads written by earlier class-name-based codecs are rejected.
+ * No compatibility or migration decoder is provided. Lists and optional values
+ * carry their exact element schema, and attribute sets are represented
+ * structurally and decoded to {@link SimpleAttributeSet}.
*
*/
-@SuppressWarnings("PMD.CyclomaticComplexity")
+@SuppressWarnings("PMD.CouplingBetweenObjects")
final class FsCodec {
/* package */ static final int MAX_COMPONENT_BYTES = 256 * 1024;
+ /* package */ static final int CURRENT_CODEC_VERSION = 2;
- private static final Map, Class>> PRIMITIVE_TO_WRAPPER = Map.of(boolean.class, Boolean.class, byte.class,
- Byte.class, short.class, Short.class, int.class, Integer.class, long.class, Long.class, char.class,
- Character.class, float.class, Float.class, double.class, Double.class);
+ private static final int CODEC_MAGIC = 0x5A454346;
+ private static final int MAX_COLLECTION_ELEMENTS = MAX_COMPONENT_BYTES;
- /**
- * Magic byte that marks the compact format.
- *
- *
- * This value is chosen to be extremely unlikely as a first byte of the legacy
- * UTF-8 class-name length prefix. Class names are short; legacy length prefix
- * first byte will be < 0x80.
- *
- */
- private static final int MAGIC_COMPACT = 0xFF;
+ private static final int TOP_CA_RECORD = 1;
+ private static final int TOP_CREDENTIAL = 2;
+ private static final int TOP_PARSED_REQUEST = 3;
+ private static final int TOP_REVOCATION = 4;
+ private static final int TOP_STATUS_OBJECT = 5;
+ private static final int TOP_PUBLICATION = 6;
+ private static final int TOP_CERTIFICATE_PROFILE = 7;
+ private static final int TOP_POLICY_TRACE = 8;
+ private static final int TOP_WORKFLOW_STATE = 9;
+ private static final int TOP_SIGN_WORKFLOW_RECORD = 10;
- // Compact tags (1 byte). Keep stable.
- private static final int TAG_STRING = 1;
- private static final int TAG_INT = 2;
- private static final int TAG_LONG = 3;
- private static final int TAG_BOOL = 4;
- private static final int TAG_BYTES = 5;
- private static final int TAG_INSTANT = 6;
- private static final int TAG_DURATION = 7;
- private static final int TAG_ENUM = 8;
- private static final int TAG_RECORD = 9;
- private static final int TAG_FALLBACK_STRING = 10;
- private static final int TAG_LIST = 11;
- private static final int TAG_SET = 12;
- private static final int TAG_OPTIONAL = 13;
+ private static final int TYPE_STRING = 1;
+ private static final int TYPE_BOOLEAN = 2;
+ private static final int TYPE_LONG = 3;
+ private static final int TYPE_BYTES = 4;
+ private static final int TYPE_INSTANT = 5;
+ private static final int TYPE_DURATION = 6;
+ private static final int TYPE_LIST = 7;
+ private static final int TYPE_OPTIONAL = 8;
+ private static final int TYPE_PKI_ID = 20;
+ private static final int TYPE_KEY_REF = 21;
+ private static final int TYPE_SUBJECT_REF = 22;
+ private static final int TYPE_ISSUER_REF = 23;
+ private static final int TYPE_FORMAT_ID = 24;
+ private static final int TYPE_VALIDITY = 25;
+ private static final int TYPE_ENCODED_OBJECT = 26;
+ private static final int TYPE_PRINCIPAL = 27;
+ private static final int TYPE_ATTRIBUTE_ID = 28;
+ private static final int TYPE_POLICY_TRACE_STEP = 29;
+ private static final int TYPE_PUBLICATION_TARGET = 30;
+ private static final int TYPE_ATTRIBUTE_SET = 31;
+ private static final int TYPE_ATTRIBUTE_VALUE = 33;
+ private static final int TYPE_CREDENTIAL_RECORD = 34;
+ private static final int TYPE_ENCODING_ENUM = 50;
+ private static final int TYPE_CA_KIND_ENUM = 51;
+ private static final int TYPE_CA_STATE_ENUM = 52;
+ private static final int TYPE_CREDENTIAL_STATUS_ENUM = 53;
+ private static final int TYPE_REVOCATION_REASON_ENUM = 54;
+ private static final int TYPE_STATUS_OBJECT_TYPE_ENUM = 55;
+ private static final int TYPE_PUBLICATION_TARGET_TYPE_ENUM = 56;
+ private static final int TYPE_PUBLICATION_STATUS_ENUM = 57;
+ private static final int TYPE_DURABILITY_POLICY_ENUM = 58;
+ private static final int TYPE_SIGN_STATE_ENUM = 59;
+
+ private static final int ATTRIBUTE_STRING = 1;
+ private static final int ATTRIBUTE_BOOLEAN = 2;
+ private static final int ATTRIBUTE_INTEGER = 3;
+ private static final int ATTRIBUTE_INSTANT = 4;
+ private static final int ATTRIBUTE_BYTES = 5;
+ private static final int MARKER_ABSENT = 0;
+ private static final int MARKER_PRESENT = 1;
+
+ private static final ValueSchema STRING = valueSchema(TYPE_STRING, Writer::writeString,
+ Reader::readString);
+ private static final ValueSchema BOOLEAN = valueSchema(TYPE_BOOLEAN, Writer::writeBoolean,
+ Reader::readBoolean);
+ private static final ValueSchema LONG = valueSchema(TYPE_LONG, Writer::writeLong, Reader::readLong);
+ private static final ValueSchema BYTES = valueSchema(TYPE_BYTES, Writer::writeBytes, Reader::readBytes);
+ private static final ValueSchema INSTANT = valueSchema(TYPE_INSTANT, Writer::writeInstant,
+ Reader::readInstant);
+ private static final ValueSchema DURATION = valueSchema(TYPE_DURATION, Writer::writeDuration,
+ Reader::readDuration);
+
+ private static final ValueSchema ENCODING = enumSchema(TYPE_ENCODING_ENUM,
+ value -> switch (value) {
+ case DER -> 1;
+ case PEM -> 2;
+ case BINARY -> 3;
+ },
+ code -> switch (code) {
+ case 1 -> Encoding.DER;
+ case 2 -> Encoding.PEM;
+ case 3 -> Encoding.BINARY;
+ default -> throw unknownEnum("Encoding", code);
+ });
+ private static final ValueSchema CA_KIND = enumSchema(TYPE_CA_KIND_ENUM,
+ value -> switch (value) {
+ case ROOT -> 1;
+ case INTERMEDIATE -> 2;
+ },
+ code -> switch (code) {
+ case 1 -> CaKind.ROOT;
+ case 2 -> CaKind.INTERMEDIATE;
+ default -> throw unknownEnum("CaKind", code);
+ });
+ private static final ValueSchema CA_STATE = enumSchema(TYPE_CA_STATE_ENUM,
+ value -> switch (value) {
+ case ACTIVE -> 1;
+ case RETIRED -> 2;
+ case COMPROMISED -> 3;
+ case DISABLED -> 4;
+ },
+ code -> switch (code) {
+ case 1 -> CaState.ACTIVE;
+ case 2 -> CaState.RETIRED;
+ case 3 -> CaState.COMPROMISED;
+ case 4 -> CaState.DISABLED;
+ default -> throw unknownEnum("CaState", code);
+ });
+ private static final ValueSchema CREDENTIAL_STATUS = enumSchema(TYPE_CREDENTIAL_STATUS_ENUM,
+ value -> switch (value) {
+ case ISSUED -> 1;
+ case REVOKED -> 2;
+ case EXPIRED -> 3;
+ },
+ code -> switch (code) {
+ case 1 -> CredentialStatus.ISSUED;
+ case 2 -> CredentialStatus.REVOKED;
+ case 3 -> CredentialStatus.EXPIRED;
+ default -> throw unknownEnum("CredentialStatus", code);
+ });
+ private static final ValueSchema REVOCATION_REASON = enumSchema(TYPE_REVOCATION_REASON_ENUM,
+ value -> switch (value) {
+ case UNSPECIFIED -> 1;
+ case KEY_COMPROMISE -> 2;
+ case CA_COMPROMISE -> 3;
+ case AFFILIATION_CHANGED -> 4;
+ case SUPERSEDED -> 5;
+ case CESSATION_OF_OPERATION -> 6;
+ case CERTIFICATE_HOLD -> 7;
+ case REMOVE_FROM_CRL -> 8;
+ case PRIVILEGE_WITHDRAWN -> 9;
+ case AA_COMPROMISE -> 10;
+ },
+ code -> switch (code) {
+ case 1 -> RevocationReason.UNSPECIFIED;
+ case 2 -> RevocationReason.KEY_COMPROMISE;
+ case 3 -> RevocationReason.CA_COMPROMISE;
+ case 4 -> RevocationReason.AFFILIATION_CHANGED;
+ case 5 -> RevocationReason.SUPERSEDED;
+ case 6 -> RevocationReason.CESSATION_OF_OPERATION;
+ case 7 -> RevocationReason.CERTIFICATE_HOLD;
+ case 8 -> RevocationReason.REMOVE_FROM_CRL;
+ case 9 -> RevocationReason.PRIVILEGE_WITHDRAWN;
+ case 10 -> RevocationReason.AA_COMPROMISE;
+ default -> throw unknownEnum("RevocationReason", code);
+ });
+ private static final ValueSchema STATUS_OBJECT_TYPE = enumSchema(TYPE_STATUS_OBJECT_TYPE_ENUM,
+ value -> switch (value) {
+ case CRL -> 1;
+ case DELTA_CRL -> 2;
+ case OCSP -> 3;
+ case REVOCATION_LIST -> 4;
+ },
+ code -> switch (code) {
+ case 1 -> StatusObjectType.CRL;
+ case 2 -> StatusObjectType.DELTA_CRL;
+ case 3 -> StatusObjectType.OCSP;
+ case 4 -> StatusObjectType.REVOCATION_LIST;
+ default -> throw unknownEnum("StatusObjectType", code);
+ });
+ private static final ValueSchema PUBLICATION_TARGET_TYPE = enumSchema(
+ TYPE_PUBLICATION_TARGET_TYPE_ENUM,
+ value -> switch (value) {
+ case FILESYSTEM -> 1;
+ case LDAP -> 2;
+ case HTTP -> 3;
+ case OBJECT_STORE -> 4;
+ case CUSTOM -> 5;
+ },
+ code -> switch (code) {
+ case 1 -> PublicationTargetType.FILESYSTEM;
+ case 2 -> PublicationTargetType.LDAP;
+ case 3 -> PublicationTargetType.HTTP;
+ case 4 -> PublicationTargetType.OBJECT_STORE;
+ case 5 -> PublicationTargetType.CUSTOM;
+ default -> throw unknownEnum("PublicationTargetType", code);
+ });
+ private static final ValueSchema PUBLICATION_STATUS = enumSchema(TYPE_PUBLICATION_STATUS_ENUM,
+ value -> switch (value) {
+ case PUBLISHED -> 1;
+ case SKIPPED -> 2;
+ case FAILED -> 3;
+ },
+ code -> switch (code) {
+ case 1 -> PublicationStatus.PUBLISHED;
+ case 2 -> PublicationStatus.SKIPPED;
+ case 3 -> PublicationStatus.FAILED;
+ default -> throw unknownEnum("PublicationStatus", code);
+ });
+ private static final ValueSchema DURABILITY_POLICY = enumSchema(
+ TYPE_DURABILITY_POLICY_ENUM,
+ value -> switch (value) {
+ case STRICT_ABORT_ON_RESTART -> 1;
+ case DURABLE_MIN_STATE -> 2;
+ case DURABLE_ENCRYPTED_STATE -> 3;
+ },
+ code -> switch (code) {
+ case 1 -> OrchestrationDurabilityPolicy.STRICT_ABORT_ON_RESTART;
+ case 2 -> OrchestrationDurabilityPolicy.DURABLE_MIN_STATE;
+ case 3 -> OrchestrationDurabilityPolicy.DURABLE_ENCRYPTED_STATE;
+ default -> throw unknownEnum("OrchestrationDurabilityPolicy", code);
+ });
+ private static final ValueSchema SIGN_STATE = valueSchema(TYPE_SIGN_STATE_ENUM,
+ (writer, value) -> writer.writeUnsignedByte(value.persistentCode()),
+ reader -> {
+ int code = reader.readUnsignedByte();
+ try {
+ return SignWorkflowStore.State.fromPersistentCode(code);
+ } catch (IllegalArgumentException ex) {
+ throw new IOException("unknown SignWorkflowStore.State code " + code, ex);
+ }
+ });
+
+ private static final ValueSchema PKI_ID = valueSchema(TYPE_PKI_ID,
+ (writer, value) -> writer.writeValue(STRING, value.value()),
+ reader -> new PkiId(reader.readValue(STRING)));
+ private static final ValueSchema KEY_REF = valueSchema(TYPE_KEY_REF,
+ (writer, value) -> writer.writeValue(STRING, value.value()),
+ reader -> new KeyRef(reader.readValue(STRING)));
+ private static final ValueSchema SUBJECT_REF = valueSchema(TYPE_SUBJECT_REF,
+ (writer, value) -> writer.writeValue(STRING, value.value()),
+ reader -> new SubjectRef(reader.readValue(STRING)));
+ private static final ValueSchema ISSUER_REF = valueSchema(TYPE_ISSUER_REF,
+ (writer, value) -> writer.writeValue(PKI_ID, value.caId()),
+ reader -> new IssuerRef(reader.readValue(PKI_ID)));
+ private static final ValueSchema FORMAT_ID = valueSchema(TYPE_FORMAT_ID,
+ (writer, value) -> writer.writeValue(STRING, value.value()),
+ reader -> new FormatId(reader.readValue(STRING)));
+ private static final ValueSchema VALIDITY = valueSchema(TYPE_VALIDITY,
+ (writer, value) -> {
+ writer.writeValue(INSTANT, value.notBefore());
+ writer.writeValue(INSTANT, value.notAfter());
+ },
+ reader -> new Validity(reader.readValue(INSTANT), reader.readValue(INSTANT)));
+ private static final ValueSchema ENCODED_OBJECT = valueSchema(TYPE_ENCODED_OBJECT,
+ FsCodec::writeEncodedObject, FsCodec::readEncodedObject);
+ private static final ValueSchema PRINCIPAL = valueSchema(TYPE_PRINCIPAL,
+ (writer, value) -> {
+ writer.writeValue(STRING, value.type());
+ writer.writeValue(STRING, value.name());
+ },
+ reader -> new Principal(reader.readValue(STRING), reader.readValue(STRING)));
+ private static final ValueSchema ATTRIBUTE_ID = valueSchema(TYPE_ATTRIBUTE_ID,
+ (writer, value) -> writer.writeValue(STRING, value.value()),
+ reader -> new AttributeId(reader.readValue(STRING)));
+
+ private static final ValueSchema ATTRIBUTE_VALUE = valueSchema(TYPE_ATTRIBUTE_VALUE,
+ FsCodec::writeAttributeValue, FsCodec::readAttributeValue);
+ private static final ValueSchema> ATTRIBUTE_VALUES = listOf(ATTRIBUTE_VALUE);
+ private static final ValueSchema ATTRIBUTE_SET = valueSchema(TYPE_ATTRIBUTE_SET,
+ FsCodec::writeAttributeSet, FsCodec::readAttributeSet);
+
+ private static final ValueSchema> STRINGS = listOf(STRING);
+ private static final ValueSchema> ATTRIBUTE_IDS = listOf(ATTRIBUTE_ID);
+ private static final ValueSchema POLICY_TRACE_STEP = valueSchema(TYPE_POLICY_TRACE_STEP,
+ (writer, value) -> {
+ writer.writeValue(STRING, value.ruleId());
+ writer.writeValue(STRING, value.outcome());
+ writer.writeValue(STRINGS, value.notes());
+ },
+ reader -> new PolicyTraceStep(reader.readValue(STRING), reader.readValue(STRING),
+ reader.readValue(STRINGS)));
+ private static final ValueSchema> POLICY_TRACE_STEPS = listOf(POLICY_TRACE_STEP);
+ private static final ValueSchema PUBLICATION_TARGET = valueSchema(TYPE_PUBLICATION_TARGET,
+ (writer, value) -> {
+ writer.writeValue(PUBLICATION_TARGET_TYPE, value.type());
+ writer.writeValue(STRING, value.targetId());
+ writer.writeValue(ATTRIBUTE_SET, value.attributes());
+ },
+ reader -> new PublicationTarget(reader.readValue(PUBLICATION_TARGET_TYPE), reader.readValue(STRING),
+ reader.readValue(ATTRIBUTE_SET)));
+
+ private static final ValueSchema> OPTIONAL_VALIDITY = optionalOf(VALIDITY);
+ private static final ValueSchema> OPTIONAL_STRING = optionalOf(STRING);
+ private static final ValueSchema> OPTIONAL_INSTANT = optionalOf(INSTANT);
+ private static final ValueSchema> OPTIONAL_DURATION = optionalOf(DURATION);
+ private static final ValueSchema> OPTIONAL_ENCODED_OBJECT = optionalOf(ENCODED_OBJECT);
+
+ private static final ValueSchema CREDENTIAL_VALUE = valueSchema(TYPE_CREDENTIAL_RECORD,
+ FsCodec::writeCredential, FsCodec::readCredential);
+ private static final ValueSchema> CREDENTIALS = listOf(CREDENTIAL_VALUE);
+
+ /* package */ static final Schema CA_RECORD = topLevel(TOP_CA_RECORD, "CA_RECORD",
+ valueSchema(100, FsCodec::writeCaRecord, FsCodec::readCaRecord));
+ /* package */ static final Schema CREDENTIAL = topLevel(TOP_CREDENTIAL, "CREDENTIAL",
+ CREDENTIAL_VALUE);
+ /* package */ static final Schema PARSED_REQUEST = topLevel(TOP_PARSED_REQUEST,
+ "PARSED_REQUEST", valueSchema(101, FsCodec::writeParsedRequest, FsCodec::readParsedRequest));
+ /* package */ static final Schema REVOCATION = topLevel(TOP_REVOCATION, "REVOCATION",
+ valueSchema(102, FsCodec::writeRevocation, FsCodec::readRevocation));
+ /* package */ static final Schema STATUS_OBJECT = topLevel(TOP_STATUS_OBJECT, "STATUS_OBJECT",
+ valueSchema(103, FsCodec::writeStatusObject, FsCodec::readStatusObject));
+ /* package */ static final Schema PUBLICATION = topLevel(TOP_PUBLICATION, "PUBLICATION",
+ valueSchema(104, FsCodec::writePublication, FsCodec::readPublication));
+ /* package */ static final Schema CERTIFICATE_PROFILE = topLevel(TOP_CERTIFICATE_PROFILE,
+ "CERTIFICATE_PROFILE", valueSchema(105, FsCodec::writeCertificateProfile,
+ FsCodec::readCertificateProfile));
+ /* package */ static final Schema POLICY_TRACE = topLevel(TOP_POLICY_TRACE, "POLICY_TRACE",
+ valueSchema(106, FsCodec::writePolicyTrace, FsCodec::readPolicyTrace));
+ /* package */ static final Schema WORKFLOW_STATE = topLevel(TOP_WORKFLOW_STATE,
+ "WORKFLOW_STATE", valueSchema(107, FsCodec::writeWorkflowState, FsCodec::readWorkflowState));
+ /* package */ static final Schema SIGN_WORKFLOW_RECORD = topLevel(
+ TOP_SIGN_WORKFLOW_RECORD, "SIGN_WORKFLOW_RECORD",
+ valueSchema(108, FsCodec::writeSignWorkflowRecord, FsCodec::readSignWorkflowRecord));
+
+ private static final Map> TOP_LEVEL_SCHEMAS = Map.ofEntries(
+ Map.entry(TOP_CA_RECORD, CA_RECORD),
+ Map.entry(TOP_CREDENTIAL, CREDENTIAL),
+ Map.entry(TOP_PARSED_REQUEST, PARSED_REQUEST),
+ Map.entry(TOP_REVOCATION, REVOCATION),
+ Map.entry(TOP_STATUS_OBJECT, STATUS_OBJECT),
+ Map.entry(TOP_PUBLICATION, PUBLICATION),
+ Map.entry(TOP_CERTIFICATE_PROFILE, CERTIFICATE_PROFILE),
+ Map.entry(TOP_POLICY_TRACE, POLICY_TRACE),
+ Map.entry(TOP_WORKFLOW_STATE, WORKFLOW_STATE),
+ Map.entry(TOP_SIGN_WORKFLOW_RECORD, SIGN_WORKFLOW_RECORD));
private FsCodec() {
// utility
}
- /* default */ static byte[] encode(final T value) {
+ /* package */ static byte[] encode(final Schema schema, final T value) {
+ Objects.requireNonNull(schema, "schema");
Objects.requireNonNull(value, "value");
try {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- writeAnyCompact(bos, value);
- return bos.toByteArray();
- } catch (IOException e) {
- throw new IllegalStateException("encoding failed: " + value.getClass().getName(), e);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ Writer writer = new Writer(output);
+ writer.writeInt(CODEC_MAGIC);
+ writer.writeUnsignedByte(CURRENT_CODEC_VERSION);
+ writer.writeUnsignedByte(schema.typeId);
+ schema.valueSchema.encoder.encode(writer, value);
+ return output.toByteArray();
+ } catch (IOException ex) {
+ throw new IllegalStateException("Encoding failed: schema=" + schema.name + " code=ENCODE_FAILED", ex);
}
}
- /* default */ static T decode(final byte[] data, final Class expectedType) {
- Objects.requireNonNull(data, "data");
- Objects.requireNonNull(expectedType, "expectedType");
+ /* package */ static T decode(final Schema schema, final byte[] encoded) {
+ Objects.requireNonNull(schema, "schema");
+ Objects.requireNonNull(encoded, "encoded");
try {
- ByteArrayInputStream bis = new ByteArrayInputStream(data);
- Object decoded = readAny(bis, expectedType);
- if (bis.available() != 0) {
- throw new IllegalStateException("trailing data after " + expectedType.getName());
- }
- return expectedType.cast(decoded);
- } catch (IOException e) {
- throw new IllegalStateException("decoding failed: " + expectedType.getName(), e);
+ return decodeCurrentPayload(schema, encoded);
+ } catch (IOException | IllegalArgumentException ex) {
+ throw new IllegalStateException(
+ "Decoding failed: schema=" + schema.name + " code=INVALID_CURRENT_PAYLOAD", ex);
}
}
- private static void writeAnyCompact(final OutputStream out, final Object value) throws IOException {
- Objects.requireNonNull(out, "out");
- Objects.requireNonNull(value, "value");
+ private static T decodeCurrentPayload(Schema schema, byte[] encoded) throws IOException {
+ ByteArrayInputStream input = new ByteArrayInputStream(encoded);
+ Reader reader = new Reader(input);
+ if (reader.readInt() != CODEC_MAGIC) {
+ throw new IOException("codec magic mismatch");
+ }
+ int version = reader.readUnsignedByte();
+ if (version != CURRENT_CODEC_VERSION) {
+ throw new IOException("unsupported codec version");
+ }
+ int typeId = reader.readUnsignedByte();
+ Schema> encodedSchema = TOP_LEVEL_SCHEMAS.get(typeId);
+ if (encodedSchema == null) {
+ throw new IOException("unknown top-level type");
+ }
+ if (encodedSchema.typeId != schema.typeId) {
+ throw new IOException("top-level type mismatch");
+ }
+ T decoded = schema.valueSchema.decoder.decode(reader);
+ if (input.available() != 0) {
+ throw new IOException("trailing payload data");
+ }
+ return decoded;
+ }
- out.write(MAGIC_COMPACT);
+ private static void writeEncodedObject(Writer writer, EncodedObject value) throws IOException {
+ writer.writeValue(ENCODING, value.encoding());
+ byte[] bytes = value.bytes();
+ try {
+ writer.writeValue(BYTES, bytes);
+ } finally {
+ Arrays.fill(bytes, (byte) 0);
+ }
+ }
+ private static EncodedObject readEncodedObject(Reader reader) throws IOException {
+ Encoding encoding = reader.readValue(ENCODING);
+ byte[] bytes = reader.readValue(BYTES);
+ try {
+ return new EncodedObject(encoding, bytes);
+ } finally {
+ Arrays.fill(bytes, (byte) 0);
+ }
+ }
+
+ private static void writeAttributeValue(Writer writer, AttributeValue value) throws IOException {
switch (value) {
- case String s -> writeStringCompact(out, s);
- case Integer i -> writeIntegerCompact(out, i);
- case Long l -> writeLongCompact(out, l);
- case Boolean b -> writeBooleanCompact(out, b);
- case byte[] bytes -> writeBytesCompact(out, bytes);
- case Instant instant -> writeInstantCompact(out, instant);
- case Duration duration -> writeDurationCompact(out, duration);
- case java.util.List> list -> writeListCompact(out, list);
- case java.util.Set> set -> writeSetCompact(out, set);
- case Optional> optional -> writeOptionalCompact(out, optional);
- default -> writeComplexCompact(out, value);
+ case AttributeValue.StringValue stringValue -> {
+ writer.writeUnsignedByte(ATTRIBUTE_STRING);
+ writer.writeValue(STRING, stringValue.value());
+ }
+ case AttributeValue.BooleanValue booleanValue -> {
+ writer.writeUnsignedByte(ATTRIBUTE_BOOLEAN);
+ writer.writeValue(BOOLEAN, booleanValue.value());
+ }
+ case AttributeValue.IntegerValue integerValue -> {
+ writer.writeUnsignedByte(ATTRIBUTE_INTEGER);
+ writer.writeValue(LONG, integerValue.value());
+ }
+ case AttributeValue.InstantValue instantValue -> {
+ writer.writeUnsignedByte(ATTRIBUTE_INSTANT);
+ writer.writeValue(INSTANT, instantValue.value());
+ }
+ case AttributeValue.BytesValue bytesValue -> {
+ writer.writeUnsignedByte(ATTRIBUTE_BYTES);
+ byte[] copy = bytesValue.value().clone();
+ try {
+ writer.writeValue(BYTES, copy);
+ } finally {
+ Arrays.fill(copy, (byte) 0);
+ }
+ }
}
}
- private static void writeStringCompact(final OutputStream out, final String value) throws IOException {
- out.write(TAG_STRING);
- Util.writeUTF8(out, value);
+ private static AttributeValue readAttributeValue(Reader reader) throws IOException {
+ return switch (reader.readUnsignedByte()) {
+ case ATTRIBUTE_STRING -> new AttributeValue.StringValue(reader.readValue(STRING));
+ case ATTRIBUTE_BOOLEAN -> new AttributeValue.BooleanValue(reader.readValue(BOOLEAN));
+ case ATTRIBUTE_INTEGER -> new AttributeValue.IntegerValue(reader.readValue(LONG));
+ case ATTRIBUTE_INSTANT -> new AttributeValue.InstantValue(reader.readValue(INSTANT));
+ case ATTRIBUTE_BYTES -> readAttributeBytes(reader);
+ default -> throw new IOException("unknown attribute value type");
+ };
}
- private static void writeIntegerCompact(final OutputStream out, final Integer value) throws IOException {
- out.write(TAG_INT);
- Util.writePack7I(out, value);
- }
-
- private static void writeLongCompact(final OutputStream out, final Long value) throws IOException {
- out.write(TAG_LONG);
- Util.writePack7L(out, value);
- }
-
- private static void writeBooleanCompact(final OutputStream out, final Boolean value) throws IOException {
- out.write(TAG_BOOL);
- out.write(value ? 1 : 0);
- }
-
- private static void writeBytesCompact(final OutputStream out, final byte[] value) throws IOException {
- out.write(TAG_BYTES);
- Util.write(out, value);
- }
-
- private static void writeInstantCompact(final OutputStream out, final Instant value) throws IOException {
- out.write(TAG_INSTANT);
- Util.writeLong(out, value.getEpochSecond());
- Util.writePack7I(out, value.getNano());
- }
-
- private static void writeDurationCompact(final OutputStream out, final Duration value) throws IOException {
- out.write(TAG_DURATION);
- Util.writeLong(out, value.getSeconds());
- Util.writePack7I(out, value.getNano());
- }
-
- private static void writeListCompact(final OutputStream out, final java.util.List> list) throws IOException {
- out.write(TAG_LIST);
- Util.writePack7I(out, list.size());
- for (Object element : list) {
- Objects.requireNonNull(element, "list element must not be null");
- writeAnyCompact(out, element);
+ private static AttributeValue readAttributeBytes(Reader reader) throws IOException {
+ byte[] bytes = reader.readValue(BYTES);
+ try {
+ return new AttributeValue.BytesValue(bytes.clone());
+ } finally {
+ Arrays.fill(bytes, (byte) 0);
}
}
- private static void writeSetCompact(final OutputStream out, final java.util.Set> set) throws IOException {
- out.write(TAG_SET);
-
- java.util.List