From 1121cbff7482542cd1551eac479124923bac4ea7 Mon Sep 17 00:00:00 2001 From: Oleg Koretsky Date: Wed, 9 Jul 2025 13:10:17 +0300 Subject: [PATCH 1/4] Add unsigned number methods to CBORGenerator --- .../dataformat/cbor/CBORGenerator.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java index 0863ec0ec..dd1f993f2 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java @@ -1142,6 +1142,20 @@ public void writeNull() throws IOException { _writeByte(BYTE_NULL); } + /** + * Method for outputting given value as an unsigned integer. + * Can be called in any context where a value is expected + * (Array value, Object field value, root-level value). + * + * @param i Number value to write + * @throws IOException if there is either an underlying I/O problem or encoding + * issue at format layer + */ + public void writeNumberUnsigned(int i) throws IOException { + _verifyValueWrite("write number unsigned"); + _writeIntMinimal(PREFIX_TYPE_INT_POS, i); + } + @Override public void writeNumber(int i) throws IOException { _verifyValueWrite("write number"); @@ -1183,6 +1197,33 @@ public void writeNumber(int i) throws IOException { _outputBuffer[_outputTail++] = b0; } + /** + * Method for outputting given value as an unsigned integer. + * Can be called in any context where a value is expected + * (Array value, Object field value, root-level value). + * + * @param l Number value to write + * @throws IOException if there is either an underlying I/O problem or encoding + * issue at format layer + */ + public void writeNumberUnsigned(long l) throws IOException { + if (_cfgMinimalInts && l >= 0 && l < 0x100000000L) { + writeNumberUnsigned((int) l); + return; + } + _verifyValueWrite("write number unsigned"); + _ensureRoomForOutput(9); + _outputBuffer[_outputTail++] = (byte) (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); + _outputBuffer[_outputTail++] = (byte) (l >> 56); + _outputBuffer[_outputTail++] = (byte) (l >> 48); + _outputBuffer[_outputTail++] = (byte) (l >> 40); + _outputBuffer[_outputTail++] = (byte) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (l >> 24); + _outputBuffer[_outputTail++] = (byte) (l >> 16); + _outputBuffer[_outputTail++] = (byte) (l >> 8); + _outputBuffer[_outputTail++] = (byte) l; + } + @Override public void writeNumber(long l) throws IOException { _verifyValueWrite("write number"); From 5abb8c2c6b5c9db4472daed2a5699b0c7857f21a Mon Sep 17 00:00:00 2001 From: Oleg Koretsky Date: Wed, 9 Jul 2025 15:01:29 +0300 Subject: [PATCH 2/4] Add unsigned number generator test --- .../cbor/gen/GeneratorSimpleTest.java | 216 +++++++++++++++++- 1 file changed, 210 insertions(+), 6 deletions(-) diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/GeneratorSimpleTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/GeneratorSimpleTest.java index e40f052cf..c9b4ffed9 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/GeneratorSimpleTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/GeneratorSimpleTest.java @@ -1,17 +1,16 @@ package com.fasterxml.jackson.dataformat.cbor.gen; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.*; +import org.junit.jupiter.api.Test; + import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.util.LinkedHashMap; import java.util.Map; -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.cbor.*; - import static org.junit.jupiter.api.Assertions.*; public class GeneratorSimpleTest extends CBORTestBase @@ -181,6 +180,98 @@ public void testIntValues() throws Exception (byte) 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF); } + @Test + public void testUnsignedIntValues() throws Exception + { + // uint32 max + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGenerator gen = cborGenerator(out); + gen.writeNumberUnsigned(-1); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT32_ELEMENTS), + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF); + + // Min int + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(Integer.MIN_VALUE); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT32_ELEMENTS), + (byte) 0x80, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00); + + // Truncated to 2 bytes + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(1000); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT16_ELEMENTS), + (byte) 0x03, + (byte) 0xE8); + + // Truncated to 1 byte + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(100); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT8_ELEMENTS), + (byte) 0x64); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(25); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT8_ELEMENTS), + (byte) 0x19); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(24); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT8_ELEMENTS), + (byte) 0x18); + + // Truncated to not take any extra bytes + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(23); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) 0x17); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(10); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) 0x0A); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(1); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) 0x01); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(0); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) 0x00); + } + @Test public void testLongValues() throws Exception { @@ -201,6 +292,119 @@ public void testLongValues() throws Exception assertEquals(0, b[3]); } + @Test + public void testUnsignedLongValues() throws Exception + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGenerator gen = cborGenerator(out); + + // uint64 max + gen.writeNumberUnsigned(-1L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT64_ELEMENTS), + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF); + + // Min long + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(Long.MIN_VALUE); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT64_ELEMENTS), + (byte) 0x80, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00); + + // Truncated to 4 bytes + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(1000000L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT32_ELEMENTS), + (byte) 0x00, + (byte) 0x0F, + (byte) 0x42, + (byte) 0x40); + + // Truncated to 2 bytes + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(1000L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT16_ELEMENTS), + (byte) 0x03, + (byte) 0xE8); + + // Truncated to 1 byte + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(100L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT8_ELEMENTS), + (byte) 0x64); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(25L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT8_ELEMENTS), + (byte) 0x19); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(24L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) (CBORConstants.PREFIX_TYPE_INT_POS + CBORConstants.SUFFIX_UINT8_ELEMENTS), + (byte) 0x18); + + // Truncated to not take any extra bytes + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(23L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) 0x17); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(10L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) 0x0A); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(1L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) 0x01); + + out = new ByteArrayOutputStream(); + gen = cborGenerator(out); + gen.writeNumberUnsigned(0L); + gen.close(); + _verifyBytes(out.toByteArray(), + (byte) 0x00); + } + @Test public void testFloatValues() throws Exception { From f19cbd4e8f601cdb1f059f9b23ddfcd02b1558f7 Mon Sep 17 00:00:00 2001 From: Oleg Koretsky Date: Wed, 9 Jul 2025 21:08:21 +0300 Subject: [PATCH 3/4] Restore the import order in the test --- .../dataformat/cbor/gen/GeneratorSimpleTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/GeneratorSimpleTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/GeneratorSimpleTest.java index c9b4ffed9..401dd06b0 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/GeneratorSimpleTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/GeneratorSimpleTest.java @@ -1,16 +1,17 @@ package com.fasterxml.jackson.dataformat.cbor.gen; -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.cbor.*; -import org.junit.jupiter.api.Test; - import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.util.LinkedHashMap; import java.util.Map; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.*; + import static org.junit.jupiter.api.Assertions.*; public class GeneratorSimpleTest extends CBORTestBase From e5d2f98485e92619f2d1850fee36f0d428b99c4c Mon Sep 17 00:00:00 2001 From: Oleg Koretsky Date: Thu, 10 Jul 2025 11:31:01 +0300 Subject: [PATCH 4/4] Add since version to methods javadoc --- .../com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java index dd1f993f2..e9c30891e 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java @@ -1150,6 +1150,7 @@ public void writeNull() throws IOException { * @param i Number value to write * @throws IOException if there is either an underlying I/O problem or encoding * issue at format layer + * @since 2.20 */ public void writeNumberUnsigned(int i) throws IOException { _verifyValueWrite("write number unsigned"); @@ -1205,6 +1206,7 @@ public void writeNumber(int i) throws IOException { * @param l Number value to write * @throws IOException if there is either an underlying I/O problem or encoding * issue at format layer + * @since 2.20 */ public void writeNumberUnsigned(long l) throws IOException { if (_cfgMinimalInts && l >= 0 && l < 0x100000000L) {