Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public class CBORGenerator extends GeneratorBase
{
private final static int[] NO_INTS = new int[0];

// @since 2.20
private final static BigInteger BI_MINUS_ONE = BigInteger.ONE.negate();

/**
* Let's ensure that we have big enough output buffer because of safety
* margins we need for UTF-8 encoding.
Expand Down Expand Up @@ -115,6 +118,25 @@ public enum Feature implements FormatFeature {
* @since 2.15
*/
WRITE_MINIMAL_DOUBLES(false),

/**
* Feature that determines how binary tagged negative BigInteger values are
* encoded: either using CBOR standard encoding logic (as per spec),
* or using legacy Jackson encoding logic (encoding up to Jackson 2.19).
* When enabled, uses CBOR standard specified encoding of negative values
* (e.g., -1 is encoded {@code [0xC3, 0x41, 0x00]}).
* When disabled, maintains backwards compatibility with existing implementations
* (e.g., -1 is encoded {@code [0xC3, 0x41, 0x01]}) and uses legacy Jackson encoding.
*<p>
* Note that there is the counterpart
* {@link CBORParser.Feature#DECODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING}
* for encoding.
*<p>
* Default value is {@code false} for backwards-compatibility.
*
* @since 2.20
*/
ENCODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING(false)
;

protected final boolean _defaultState;
Expand Down Expand Up @@ -1210,14 +1232,17 @@ public void writeNumber(BigInteger v) throws IOException {
// Main write method isolated so that it can be called directly
// in cases where that is needed (to encode BigDecimal)
protected void _write(BigInteger v) throws IOException {
/*
* Supported by using type tags, as per spec: major type for tag '6'; 5
/* Supported by using type tags, as per spec: major type for tag '6'; 5
* LSB either 2 for positive bignum or 3 for negative bignum. And then
* byte sequence that encode variable length integer.
*/
if (v.signum() < 0) {
_writeByte(BYTE_TAG_BIGNUM_NEG);
v = v.negate();
if (isEnabled(CBORGenerator.Feature.ENCODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING)) {
v = BI_MINUS_ONE.subtract(v);
} else {
v = v.negate();
}
} else {
_writeByte(BYTE_TAG_BIGNUM_POS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,24 @@ public class CBORParser extends ParserMinimalBase
*/
public enum Feature implements FormatFeature
{
// BOGUS(false)
/**
* Feature that determines how binary tagged negative BigInteger values are
* decoded: either assuming CBOR standard encoding logic (as per spec),
* or the legacy Jackson encoding logic (encoding up to Jackson 2.19).
* When enabled, ensures proper encoding of negative values
* (e.g., {@code [0xC3, 0x41, 0x00]} is decoded as -1)
* When disabled, maintains backwards compatibility with existing implementations
* (e.g., {@code [0xC3, 0x41, 0x00]} is decoded as 0).
*<p>
* Note that there is the counterpart
* {@link CBORGenerator.Feature#ENCODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING}
* for encoding.
*<p>
* The default value is {@code false} for backwards compatibility.
*
* @since 2.20
*/
DECODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING(false)
;

final boolean _defaultState;
Expand Down Expand Up @@ -147,6 +164,9 @@ public int getFirstTag() {

private final static int[] UTF8_UNIT_CODES = CBORConstants.sUtf8UnitLengths;

// @since 2.20
private final static BigInteger BI_MINUS_ONE = BigInteger.ONE.negate();

// Constants for handling of 16-bit "mini-floats"
private final static double MATH_POW_2_10 = Math.pow(2, 10);
private final static double MATH_POW_2_NEG14 = Math.pow(2, -14);
Expand All @@ -165,6 +185,14 @@ public int getFirstTag() {
/**********************************************************
*/

/**
* Bit flag composed of bits that indicate which
* {@link CBORParser.Feature}s are enabled.
*<p>
* @since 2.20
*/
protected int _formatFeatures;

/**
* Codec used for data binding when (if) requested.
*/
Expand Down Expand Up @@ -515,6 +543,7 @@ public CBORParser(IOContext ctxt, int parserFeatures, int cborFeatures,
boolean bufferRecyclable)
{
super(parserFeatures, ctxt.streamReadConstraints());
_formatFeatures = cborFeatures;
_ioContext = ctxt;
_objectCodec = codec;
_symbols = sym;
Expand Down Expand Up @@ -561,12 +590,15 @@ public Version version() {
/**********************************************************
*/

// public JsonParser overrideStdFeatures(int values, int mask)
@Override
public final JsonParser overrideFormatFeatures(int values, int mask) {
_formatFeatures = (_formatFeatures & ~mask) | (values & mask);
return this;
}

@Override
public int getFormatFeatures() {
// No parser features, yet
return 0;
public final int getFormatFeatures() {
return _formatFeatures;
}

@Override // since 2.12
Expand Down Expand Up @@ -1123,9 +1155,15 @@ protected JsonToken _handleTaggedBinary(TagList tags) throws IOException
_numberBigInt = BigInteger.ZERO;
} else {
_streamReadConstraints.validateIntegerLength(_binaryValue.length);
BigInteger nr = new BigInteger(_binaryValue);
final BigInteger nr;
if (neg) {
nr = nr.negate();
if (Feature.DECODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING.enabledIn(_formatFeatures)) {
nr = BI_MINUS_ONE.subtract(new BigInteger(1, _binaryValue));
} else {
nr = new BigInteger(_binaryValue).negate();
}
} else {
nr = new BigInteger(_binaryValue);
}
_numberBigInt = nr;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,40 @@ public Builder(CBORMapper m) {
/******************************************************************
*/

/**
* @since 2.20.0
*/
public Builder enable(CBORParser.Feature... features) {
for (CBORParser.Feature f : features) {
_streamFactory.enable(f);
}
return this;
}

/**
* @since 2.20.0
*/
public Builder disable(CBORParser.Feature... features) {
for (CBORParser.Feature f : features) {
_streamFactory.disable(f);
}
return this;
}

/**
* @since 2.20.0
*/
public Builder configure(CBORParser.Feature f, boolean state)
{
if (state) {
_streamFactory.enable(f);
} else {
_streamFactory.disable(f);
}
return this;
}


/**
* @since 2.14
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,147 @@ public void testBigDecimalValues() throws Exception
assertArrayEquals(spec, b);
}

// [dataformats-binary#431]
// [https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.3]
@Test
public void testSimpleBigIntegerEncoding() throws Exception
{
BigInteger minusOne = BigInteger.valueOf(-1);
byte[] expectedBytes = {
(byte) 0xC3, // tag 3 (negative bignum)
(byte) 0x41 // byte string, length 1
};

// Test correct encoding
CBORFactory factory = CBORFactory.builder()
.enable(CBORGenerator.Feature.ENCODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING)
.build();
ByteArrayOutputStream correctOut = new ByteArrayOutputStream();
try (CBORGenerator gen1 = factory.createGenerator(correctOut)) {
gen1.writeNumber(minusOne);
}

byte[] result1 = correctOut.toByteArray();
assertEquals(3, result1.length);
assertEquals(expectedBytes[0], result1[0]);
assertEquals(expectedBytes[1], result1[1]);
assertEquals(0x00, result1[2]);

// Test incorrect encoding for compatibility
ByteArrayOutputStream incorrectOut = new ByteArrayOutputStream();
factory = CBORFactory.builder()
.disable(CBORGenerator.Feature.ENCODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING)
.build();
try (CBORGenerator gen2 = factory.createGenerator(incorrectOut)) {
gen2.writeNumber(minusOne);
}

byte[] result2 = incorrectOut.toByteArray();
assertEquals(3, result2.length);
assertEquals(expectedBytes[0], result2[0]);
assertEquals(expectedBytes[1], result2[1]);
assertEquals(0x01, result2[2]);
}

// [dataformats-binary#431]
// [https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.3]
@Test
public void testZeroBigIntegerEncoding() throws Exception {
BigInteger zero = BigInteger.valueOf(0);
byte[] expectedBytes = {
(byte) 0xC2, // tag 2 (positive bignum)
(byte) 0x41, // byte string, 1 byte
(byte) 0x00, // 0
};

ByteArrayOutputStream correctOut = new ByteArrayOutputStream();
CBORFactory factory = CBORFactory.builder()
.enable(CBORGenerator.Feature.ENCODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING)
.build();
try (CBORGenerator gen1 = factory.createGenerator(correctOut)) {
gen1.writeNumber(zero);
}

byte[] result = correctOut.toByteArray();
assertEquals(3, result.length);
assertArrayEquals(expectedBytes, result);
}

// [dataformats-binary#431]
// [https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.3]
@Test
public void testNegativeBigIntegerEncoding() throws Exception {
BigInteger negativeBigInteger = new BigInteger("-340282366920938463463374607431768211456");
// correct encoding: https://cbor.me/?bytes=c35100ffffffffffffffffffffffffffffffff
byte[] expectedBytes = {
(byte) 0xC3,
(byte) 0x51,
(byte) 0x00,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF
};

// Test correct encoding
ByteArrayOutputStream correctOut = new ByteArrayOutputStream();
CBORFactory factory = CBORFactory.builder()
.enable(CBORGenerator.Feature.ENCODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING)
.build();
try (CBORGenerator gen1 = factory.createGenerator(correctOut)) {
gen1.writeNumber(negativeBigInteger);
}
byte[] result1 = correctOut.toByteArray();
assertArrayEquals(expectedBytes, result1);

// Test incorrect encoding for compatibility
// incorrect encoding: https://cbor.me/?bytes=c3510100000000000000000000000000000000
byte[] legacyExpectedBytes = {
(byte) 0xC3,
(byte) 0x51,
(byte) 0x01,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
};
ByteArrayOutputStream incorrectOut = new ByteArrayOutputStream();
factory = CBORFactory.builder()
.disable(CBORGenerator.Feature.ENCODE_USING_STANDARD_NEGATIVE_BIGINT_ENCODING)
.build();
try (CBORGenerator gen2 = factory.createGenerator(incorrectOut)) {
gen2.writeNumber(negativeBigInteger);
}

byte[] result2 = incorrectOut.toByteArray();
assertEquals(19, result2.length);
assertArrayEquals(legacyExpectedBytes, result2);
}

@Test
public void testEmptyArray() throws Exception
{
Expand Down
Loading