Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ public enum Feature implements FormatFeature {
* @since 2.15
*/
WRITE_MINIMAL_DOUBLES(false),

/**
* Feature that determines how binary tagged negative BigInteger values are
* encoded.
*
* When enabled, Ensures proper encoding of negative values
* (e.g., -1 is encoded [0xC3, 0x41, 0x00])
*
* When disabled, Maintains backwards compatibility with existing implementations
* (e.g., -1 is encoded [0xC3, 0x41, 0x01])
*
* The default value is false for backwards compatibility.
*
* @since 2.20.0
*/

CORRECT_CBOR_NEGATIVE_BIGINT_ENCODING(false)
;

protected final boolean _defaultState;
Expand Down Expand Up @@ -1217,7 +1234,11 @@ protected void _write(BigInteger v) throws IOException {
*/
if (v.signum() < 0) {
_writeByte(BYTE_TAG_BIGNUM_NEG);
v = v.negate();
if (isEnabled(CBORGenerator.Feature.CORRECT_CBOR_NEGATIVE_BIGINT_ENCODING)) {
v = BigInteger.ONE.negate().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 @@ -29,6 +29,22 @@ public class CBORParser extends ParserMinimalBase
public enum Feature implements FormatFeature
{
// BOGUS(false)

/**
* Feature that determines how binary tagged negative BigInteger values are
* decoded.
*
* When enabled, Ensures proper encoding of negative values
* (e.g., [0xC3, 0x41, 0x00] is decoded -1)
*
* When disabled, Maintains backwards compatibility with existing implementations
* (e.g., [0xC3, 0x41, 0x00] is decoded 0)
*
* The default value is false for backwards compatibility.
*
* @since 2.20.0
*/
CORRECT_CBOR_NEGATIVE_BIGINT_DECODING(false)
;

final boolean _defaultState;
Expand Down Expand Up @@ -515,6 +531,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 +578,22 @@ public Version version() {
/**********************************************************
*/

// public JsonParser overrideStdFeatures(int values, int mask)
/**
* Bit flag composed of bits that indicate which
* {@link CBORParser.Feature}s are enabled.
*<p>
*/
protected int _formatFeatures;

@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,11 +1150,16 @@ protected JsonToken _handleTaggedBinary(TagList tags) throws IOException
_numberBigInt = BigInteger.ZERO;
} else {
_streamReadConstraints.validateIntegerLength(_binaryValue.length);
BigInteger nr = new BigInteger(_binaryValue);
if (neg) {
nr = nr.negate();
}
_numberBigInt = nr;

BigInteger nr = new BigInteger(_binaryValue);
if (neg) {
if (Feature.CORRECT_CBOR_NEGATIVE_BIGINT_DECODING.enabledIn(_formatFeatures)) {
nr = BigInteger.ONE.negate().subtract(new BigInteger(1, _binaryValue));
} else {
nr = nr.negate();
}
}
_numberBigInt = nr;
}
_numTypesValid = NR_BIGINT;
_tagValues.clear();
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,137 @@ 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
ByteArrayOutputStream correctOut = new ByteArrayOutputStream();
CBORFactory factory = new CBORFactory();
CBORGenerator gen1 = factory.createGenerator(correctOut);
gen1.enable(CBORGenerator.Feature.CORRECT_CBOR_NEGATIVE_BIGINT_ENCODING);
gen1.writeNumber(minusOne);
gen1.close();

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();
CBORGenerator gen2 = cborGenerator(incorrectOut);
gen2.writeNumber(minusOne);
gen2.close();

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

@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 = new CBORFactory();
CBORGenerator gen1 = factory.createGenerator(correctOut);
gen1.enable(CBORGenerator.Feature.CORRECT_CBOR_NEGATIVE_BIGINT_ENCODING);
gen1.writeNumber(zero);
gen1.close();

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 testNgeativeBigIntegerEncoding() 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 = new CBORFactory();
CBORGenerator gen1 = factory.createGenerator(correctOut);
gen1.enable(CBORGenerator.Feature.CORRECT_CBOR_NEGATIVE_BIGINT_ENCODING);
gen1.writeNumber(negativeBigInteger);
gen1.close();

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();
CBORGenerator gen2 = cborGenerator(incorrectOut);
gen2.writeNumber(negativeBigInteger);
gen2.close();

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

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