From 7dc41e3957ba7f9777fb26df503be6856bbbbee1 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 26 Aug 2024 21:41:38 +0200 Subject: [PATCH] Fixed encoding CBOR in canonical format --- mcumgr-core/build.gradle | 3 + .../java/io/runtime/mcumgr/util/CBOR.java | 2 +- .../mcumgr/util/CanonicalCBORFactory.java | 53 ++++++++++++ .../mcumgr/util/CanonicalCBORGenerator.java | 83 +++++++++++++++++++ 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 mcumgr-core/src/main/java/io/runtime/mcumgr/util/CanonicalCBORFactory.java create mode 100644 mcumgr-core/src/main/java/io/runtime/mcumgr/util/CanonicalCBORGenerator.java diff --git a/mcumgr-core/build.gradle b/mcumgr-core/build.gradle index b477fe66..7dcd1db3 100644 --- a/mcumgr-core/build.gradle +++ b/mcumgr-core/build.gradle @@ -53,8 +53,11 @@ dependencies { // Import CBOR parser - version 2.14+ requires Android 8 // See: https://github.com/NordicSemiconductor/Android-nRF-Connect-Device-Manager/issues/135 + //noinspection GradleDependency implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.13.5' // don't update + //noinspection GradleDependency implementation 'com.fasterxml.jackson.core:jackson-core:2.13.5' // don't update + //noinspection GradleDependency implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.5' // don't update // Test diff --git a/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CBOR.java b/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CBOR.java index 9907b29a..d2756726 100644 --- a/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CBOR.java +++ b/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CBOR.java @@ -20,7 +20,7 @@ @SuppressWarnings("unused") public class CBOR { - private final static CBORFactory sFactory = new CBORFactory(); + private final static CBORFactory sFactory = new CanonicalCBORFactory(); public static byte[] toBytes(Object obj) throws IOException { ObjectMapper mapper = new ObjectMapper(sFactory); diff --git a/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CanonicalCBORFactory.java b/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CanonicalCBORFactory.java new file mode 100644 index 00000000..26713f4c --- /dev/null +++ b/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CanonicalCBORFactory.java @@ -0,0 +1,53 @@ +package io.runtime.mcumgr.util; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.io.IOContext; +import com.fasterxml.jackson.dataformat.cbor.CBORConstants; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * This class is a copy of {@link CBORFactory} with the only difference + * that it returns {@link CanonicalCBORGenerator} instead of {@link CBORGenerator}. + */ +public class CanonicalCBORFactory extends CBORFactory { + + @Override + public CBORGenerator createGenerator(OutputStream out, JsonEncoding enc) throws IOException { + final IOContext ctxt = _createContext(_createContentReference(out), false); + return _createCBORGenerator(ctxt, + _generatorFeatures, _formatGeneratorFeatures, _objectCodec, + _decorate(out, ctxt)); + } + + @Override + public CBORGenerator createGenerator(OutputStream out) throws IOException { + final IOContext ctxt = _createContext(_createContentReference(out), false); + return _createCBORGenerator(ctxt, + _generatorFeatures, _formatGeneratorFeatures, _objectCodec, + _decorate(out, ctxt)); + } + + @Override + protected CBORGenerator _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException { + return _createCBORGenerator(ctxt, + _generatorFeatures, _formatGeneratorFeatures, _objectCodec, out); + } + + // These methods are required to make the overriding class to work: + + private CBORGenerator _createCBORGenerator(IOContext ctxt, + int stdFeat, int formatFeat, ObjectCodec codec, OutputStream out) throws IOException + { + // false -> we won't manage the stream unless explicitly directed to + CanonicalCBORGenerator gen = new CanonicalCBORGenerator(ctxt, stdFeat, formatFeat, _objectCodec, out); + if (CBORGenerator.Feature.WRITE_TYPE_HEADER.enabledIn(formatFeat)) { + gen.writeTag(CBORConstants.TAG_ID_SELF_DESCRIBE); + } + return gen; + } +} diff --git a/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CanonicalCBORGenerator.java b/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CanonicalCBORGenerator.java new file mode 100644 index 00000000..d3b36eb4 --- /dev/null +++ b/mcumgr-core/src/main/java/io/runtime/mcumgr/util/CanonicalCBORGenerator.java @@ -0,0 +1,83 @@ +package io.runtime.mcumgr.util; + +import static com.fasterxml.jackson.dataformat.cbor.CBORConstants.PREFIX_TYPE_OBJECT; +import static com.fasterxml.jackson.dataformat.cbor.CBORConstants.SUFFIX_UINT16_ELEMENTS; +import static com.fasterxml.jackson.dataformat.cbor.CBORConstants.SUFFIX_UINT32_ELEMENTS; +import static com.fasterxml.jackson.dataformat.cbor.CBORConstants.SUFFIX_UINT8_ELEMENTS; + +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.io.IOContext; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * This class is a copy of {@link CBORGenerator} with the only difference + * that it writes maps in canonical form. + *

+ * The implementation is copied from {@link CBORGenerator} at version 2.17.1, which cannot be used + * as 2.14+ support only Android 8+. + */ +public class CanonicalCBORGenerator extends CBORGenerator { + + public CanonicalCBORGenerator(IOContext ctxt, int stdFeatures, int formatFeatures, ObjectCodec codec, OutputStream out) { + super(ctxt, stdFeatures, formatFeatures, codec, out); + } + + public CanonicalCBORGenerator(IOContext ctxt, int stdFeatures, int formatFeatures, ObjectCodec codec, OutputStream out, byte[] outputBuffer, int offset, boolean bufferRecyclable) { + super(ctxt, stdFeatures, formatFeatures, codec, out, outputBuffer, offset, bufferRecyclable); + } + + @Override + public void writeStartObject(Object forValue, int elementsToWrite) throws IOException { + _verifyValueWrite("start an object"); + _streamWriteContext = _streamWriteContext.createChildObjectContext(forValue); + _pushRemainingElements(); + _currentRemainingElements = elementsToWrite; + _writeLengthMarker(PREFIX_TYPE_OBJECT, elementsToWrite); + } + + // These methods are required to make the overriding class to work: + + private void _pushRemainingElements() { + if (_elementCounts.length == _elementCountsPtr) { // initially, as well as if full + _elementCounts = Arrays.copyOf(_elementCounts, _elementCounts.length+10); + } + _elementCounts[_elementCountsPtr++] = _currentRemainingElements; + } + + private void _writeLengthMarker(int majorType, int i) + throws IOException { + _ensureRoomForOutput(5); + if (i < 24) { + _outputBuffer[_outputTail++] = (byte) (majorType + i); + return; + } + if (i <= 0xFF) { + _outputBuffer[_outputTail++] = (byte) (majorType + SUFFIX_UINT8_ELEMENTS); + _outputBuffer[_outputTail++] = (byte) i; + return; + } + final byte b0 = (byte) i; + i >>= 8; + if (i <= 0xFF) { + _outputBuffer[_outputTail++] = (byte) (majorType + SUFFIX_UINT16_ELEMENTS); + _outputBuffer[_outputTail++] = (byte) i; + _outputBuffer[_outputTail++] = b0; + return; + } + _outputBuffer[_outputTail++] = (byte) (majorType + SUFFIX_UINT32_ELEMENTS); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + _outputBuffer[_outputTail++] = b0; + } + + private void _ensureRoomForOutput(int needed) throws IOException { + if ((_outputTail + needed) >= _outputEnd) { + _flushBuffer(); + } + } +}