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 099ebf670..e76060823 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 @@ -738,10 +738,13 @@ private final void _writeIntFull(int markerBase, int i) throws IOException // Helper method that works like `writeNumber(long)` but DOES NOT // check internal output state. It does, however, check need for minimization - private final void _writeLongNoCheck(long l) throws IOException { + private final void _writeLongNoCheck(long l) throws IOException + { if (_cfgMinimalInts) { if (l >= 0) { - if (l <= 0x100000000L) { + // 31-Mar-2021, tatu: [dataformats-cbor#269] Incorrect boundary check, + // was off by one, resulting in truncation to 0 + if (l < 0x100000000L) { _writeIntMinimal(PREFIX_TYPE_INT_POS, (int) l); return; } @@ -1010,7 +1013,9 @@ public void writeNumber(long l) throws IOException { _verifyValueWrite("write number"); if (_cfgMinimalInts) { // maybe 32 bits is enough? if (l >= 0) { - if (l <= 0x100000000L) { + // 31-Mar-2021, tatu: [dataformats-cbor#269] Incorrect boundary check, + // was off by one, resulting in truncation to 0 + if (l < 0x100000000L) { _writeIntMinimal(PREFIX_TYPE_INT_POS, (int) l); return; } diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java index a77842a2b..eb7eef96e 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java @@ -815,6 +815,19 @@ protected String _numberToName(int ch, boolean neg) throws IOException break; case 26: i = _decode32Bits(); + // [dataformats-binary#269] (and earlier [dataformats-binary#30]), + // got some edge case to consider + if (i < 0) { + long l; + if (neg) { + long unsignedBase = (long) i & 0xFFFFFFFFL; + l = -unsignedBase - 1L; + } else { + l = (long) i; + l = l & 0xFFFFFFFFL; + } + return String.valueOf(l); + } break; case 27: { diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/NumberBeanTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/NumberBeanTest.java index d4475dc60..5095bf2c8 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/NumberBeanTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/NumberBeanTest.java @@ -5,8 +5,10 @@ import java.math.BigInteger; import com.fasterxml.jackson.annotation.JsonUnwrapped; + import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonParser.NumberType; + import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.cbor.CBORFactory; import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; @@ -73,15 +75,28 @@ public void testLongRoundTrip() throws Exception 100L, -200L, 5000L, -3600L, Integer.MIN_VALUE, Integer.MAX_VALUE, - 1L + Integer.MAX_VALUE, -1L + Integer.MIN_VALUE, - 2330462449L, // from [dataformats-binary#30] - Long.MIN_VALUE, Long.MAX_VALUE - }) { - LongBean input = new LongBean(v); - byte[] b = MAPPER.writeValueAsBytes(input); - LongBean result = MAPPER.readValue(b, LongBean.class); - assertEquals(input.value, result.value); + 1L + Integer.MAX_VALUE, -1L + Integer.MIN_VALUE + }) { + _testLongRoundTrip(v); } + + _testLongRoundTrip(2330462449L); // from [dataformats-binary#30] + _testLongRoundTrip(0xFFFFFFFFL); // max positive uint32 + _testLongRoundTrip(-0xFFFFFFFFL); + _testLongRoundTrip(0x100000000L); + _testLongRoundTrip(-0x100000000L); + _testLongRoundTrip(0x100000001L); + _testLongRoundTrip(-0x100000001L); + _testLongRoundTrip(Long.MIN_VALUE); + _testLongRoundTrip(Long.MAX_VALUE); + } + + private void _testLongRoundTrip(long v) throws Exception + { + LongBean input = new LongBean(v); + byte[] b = MAPPER.writeValueAsBytes(input); + LongBean result = MAPPER.readValue(b, LongBean.class); + assertEquals(input.value, result.value); } // for [dataformats-binary#32] coercion of Float into Double @@ -218,7 +233,7 @@ public void testNumberTypeRetainingBuffering() throws Exception assertToken(JsonToken.END_ARRAY, p.nextToken()); } } - + // [databind#2784] public void testBigDecimalWithBuffering() throws Exception { diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/NumberMap269Test.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/NumberMap269Test.java new file mode 100644 index 000000000..e203f8cd2 --- /dev/null +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/NumberMap269Test.java @@ -0,0 +1,63 @@ +package com.fasterxml.jackson.dataformat.cbor.mapper; + +import java.util.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORTestBase; + +// [dataformats-binary#269] +public class NumberMap269Test extends CBORTestBase +{ + static class TestData269 { + Map map; + + public TestData269 setMap(Map map) { + this.map = map; + return this; + } + + public Map getMap() { + return map; + } + } + + /* + /********************************************************** + /* Test methods + /********************************************************** + */ + + private final ObjectMapper MAPPER = cborMapper(); + + // [dataformats-binary#269] + public void testInt32BoundaryWithMapKey() throws Exception + { + // First, with specific reported combo: + _testInt32BoundaryWithMapKey(4294967296L, -4294967296L); + + // and then systematically couple of others (actually overlapping but...) + final long MAX_POS_UINT32 = 0xFFFFFFFFL; + final long MAX_POS_UINT32_PLUS_1 = MAX_POS_UINT32 + 1L; + + _testInt32BoundaryWithMapKey(MAX_POS_UINT32, -MAX_POS_UINT32); + _testInt32BoundaryWithMapKey(MAX_POS_UINT32_PLUS_1, + -MAX_POS_UINT32_PLUS_1); + + _testInt32BoundaryWithMapKey(MAX_POS_UINT32_PLUS_1 + 1L, + -MAX_POS_UINT32_PLUS_1 - 1L); + } + + private void _testInt32BoundaryWithMapKey(long key1, long key2) throws Exception + { + Map map = new LinkedHashMap<>(); + map.put(key1, "hello"); + map.put(key2, "world"); + TestData269 input = new TestData269().setMap(map); + + byte[] cborDoc = MAPPER.writeValueAsBytes(input); + + TestData269 result = MAPPER.readValue(cborDoc, TestData269.class); + + assertEquals(input.map, result.map); + } +} diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 75c85a105..e802963bb 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -141,6 +141,10 @@ Jonas Konrad (yawkat@github) most compact form for all integers (2.11.0) +Dylan (Quantum64@github) +* Reported #269: CBOR loses `Map` entries with specific `long` Map key values (32-bit boundary) + (2.11.5 / 2.12.3) + Michael Liedtke (mcliedtke@github) * Contributed fix for #212: (ion) Optimize `IonParser.getNumberType()` using diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 45d675c3c..fb7bb0004 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -30,6 +30,8 @@ Modules: (reported by Fabian M) #268: (smile) Handle sequence of Smile header markers without recursion (reported by Fabian M) +#269: CBOR loses `Map` entries with specific `long` Map key values (32-bit boundary) + (reported by Quantum64@github) 2.12.2 (03-Mar-2021)