From d8f82c788c4d8c41c59ca143caf2f989d0e9a9ae Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 15 Oct 2016 16:11:41 -0700 Subject: [PATCH] Fix #30 --- .../jackson/dataformat/cbor/CBORParser.java | 135 +++++++++++++++--- .../cbor/parse/ParserNumbersTest.java | 84 +++++++++-- release-notes/VERSION | 2 + 3 files changed, 188 insertions(+), 33 deletions(-) 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 911b03379..fc77e8568 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 @@ -20,7 +20,7 @@ public final class CBORParser extends ParserMinimalBase { private final static byte[] NO_BYTES = new byte[0]; - + /** * Enumeration that defines all togglable features for CBOR generators. */ @@ -66,13 +66,13 @@ private Feature(boolean defaultState) { // 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); - + /* /********************************************************** /* Configuration /********************************************************** */ - + /** * Codec used for data binding when (if) requested. */ @@ -167,7 +167,7 @@ private Feature(boolean defaultState) { * in the end it'll be converted to 1-based) */ protected int _tokenInputCol = 0; - + /* /********************************************************** /* Parsing state @@ -179,6 +179,7 @@ private Feature(boolean defaultState) { * the next token is to be parsed (root, array, object). */ protected CBORReadContext _parsingContext; + /** * Buffer that contains contents of String values, including * field names if necessary (name split across boundary, @@ -199,7 +200,7 @@ private Feature(boolean defaultState) { * representation being available via read context) */ protected boolean _nameCopied = false; - + /** * ByteArrayBuilder is needed if 'getBinaryValue' is called. If so, * we better reuse it for remainder of content. @@ -247,7 +248,7 @@ private Feature(boolean defaultState) { * buffer. */ protected boolean _bufferRecyclable; - + /* /********************************************************** /* Additional parsing state @@ -372,7 +373,7 @@ private Feature(boolean defaultState) { /* Life-cycle /********************************************************** */ - + public CBORParser(IOContext ctxt, int parserFeatures, int cborFeatures, ObjectCodec codec, ByteQuadsCanonicalizer sym, InputStream in, byte[] inputBuffer, int start, int end, @@ -423,7 +424,7 @@ public Version version() { /* Configuration /********************************************************** */ - + // public JsonParser overrideStdFeatures(int values, int mask) @Override @@ -675,11 +676,30 @@ public JsonToken nextToken() throws IOException _numberInt = _decode16Bits(); break; case 2: - _numberInt = _decode32Bits(); + // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here + { + int v = _decode32Bits(); + if (v >= 0) { + _numberInt = v; + } else { + long l = (long) v; + _numberLong = l & 0xFFFFFFFFL; + _numTypesValid = NR_LONG; + } + } break; case 3: - _numberLong = _decode64Bits(); - _numTypesValid = NR_LONG; + // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here + { + long l = _decode64Bits(); + if (l >= 0L) { + _numberLong = l; + _numTypesValid = NR_LONG; + } else { + _numberBigInt = _bigPositive(l); + _numTypesValid = NR_BIGINT; + } + } break; default: _invalidToken(ch); @@ -699,11 +719,29 @@ public JsonToken nextToken() throws IOException _numberInt = -_decode16Bits() - 1; break; case 2: - _numberInt = -_decode32Bits() - 1; + // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here + { + int v = _decode32Bits(); + if (v < 0) { + _numberLong = ((long) v) + -1L; + _numTypesValid = NR_LONG; + } else { + _numberInt = -v - 1; + } + } break; case 3: - _numberLong = -_decode64Bits() - 1L; - _numTypesValid = NR_LONG; + // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here + { + long l = _decode64Bits(); + if (l >= 0L) { + _numberLong = -l - 1L; + _numTypesValid = NR_LONG; + } else { + _numberBigInt = _bigNegative(l); + _numTypesValid = NR_BIGINT; + } + } break; default: _invalidToken(ch); @@ -784,7 +822,7 @@ public JsonToken nextToken() throws IOException } return null; } - + protected String _numberToName(int ch, boolean neg) throws IOException { final int lowBits = ch & 0x1F; @@ -1109,11 +1147,30 @@ public String nextTextValue() throws IOException _numberInt = _decode16Bits(); break; case 2: - _numberInt = _decode32Bits(); + // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here + { + int v = _decode32Bits(); + if (v < 0) { + long l = (long) v; + _numberLong = l & 0xFFFFFFFFL; + _numTypesValid = NR_LONG; + } else{ + _numberInt = v; + } + } break; case 3: - _numberLong = _decode64Bits(); - _numTypesValid = NR_LONG; + // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here + { + long l = _decode64Bits(); + if (l >= 0L) { + _numberLong = l; + _numTypesValid = NR_LONG; + } else { + _numberBigInt = _bigPositive(l); + _numTypesValid = NR_BIGINT; + } + } break; default: _invalidToken(ch); @@ -1134,11 +1191,29 @@ public String nextTextValue() throws IOException _numberInt = -_decode16Bits() - 1; break; case 2: - _numberInt = -_decode32Bits() - 1; + // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here + { + int v = _decode32Bits(); + if (v < 0) { + _numberLong = ((long) v) + -1L; + _numTypesValid = NR_LONG; + } else { + _numberInt = -v - 1; + } + } break; case 3: - _numberLong = -_decode64Bits() - 1L; - _numTypesValid = NR_LONG; + // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here + { + long l = _decode64Bits(); + if (l >= 0L) { + _numberLong = l; + _numTypesValid = NR_LONG; + } else { + _numberBigInt = _bigNegative(l); + _numTypesValid = NR_BIGINT; + } + } break; default: _invalidToken(ch); @@ -3081,5 +3156,21 @@ protected void _reportInvalidOther(int mask, int ptr) throws JsonParseException _inputPtr = ptr; _reportInvalidOther(mask); } + + /* + /********************************************************** + /* Internal methods, other + /********************************************************** + */ + + private final static BigInteger BIT_63 = BigInteger.ONE.shiftLeft(63); + + private final BigInteger _bigPositive(long l) { + BigInteger biggie = BigInteger.valueOf((l << 1) >>> 1); + return biggie.or(BIT_63); + } + + private final BigInteger _bigNegative(long l) { + return BigInteger.valueOf(l).subtract(BigInteger.ONE); + } } - diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/ParserNumbersTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/ParserNumbersTest.java index 204dcbc9f..a0f80eb4c 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/ParserNumbersTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/ParserNumbersTest.java @@ -20,6 +20,8 @@ @SuppressWarnings("resource") public class ParserNumbersTest extends CBORTestBase { + private final CBORFactory CBOR_F = cborFactory(); + public void testIntValues() throws Exception { // first, single-byte @@ -71,13 +73,42 @@ private void _verifyInt(CBORFactory f, int value) throws Exception p.close(); } + // Special tests for "gray area" for uint32 values that do not fit + // in Java int; from [dataformats-binary#30] + public void testInt32Overflow() throws Exception + { + // feed in max uint32, which is 2x+1 as big as Integer.MAX_VALUE + byte[] input = new byte[] { + (byte) CBORConstants.PREFIX_TYPE_INT_POS + 26, // uint32, that is, 4 more bytes + -1, -1, -1, -1 + }; + CBORParser p = CBOR_F.createParser(input); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + // should be exposed as `long` because these uint32 values do not fit in Java `int` + assertEquals(0xFFFFFFFFL, p.getLongValue()); + assertEquals(NumberType.LONG, p.getNumberType()); + p.close(); + + // and then the reverse; something that ought to be negative + input = new byte[] { + (byte) CBORConstants.PREFIX_TYPE_INT_NEG + 26, // int32, that is, 4 more bytes + (byte) 0x80, 0, 0, 0 + }; + p = CBOR_F.createParser(input); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + // should be exposed as `long` because this value won't fit in `int` either + long exp = -1L + Integer.MIN_VALUE; + assertEquals(exp, p.getLongValue()); + assertEquals(NumberType.LONG, p.getNumberType()); + p.close(); + } + public void testLongValues() throws Exception { - CBORFactory f = cborFactory(); - _verifyLong(f, 1L + Integer.MAX_VALUE); - _verifyLong(f, Long.MIN_VALUE); - _verifyLong(f, Long.MAX_VALUE); - _verifyLong(f, -1L + Integer.MIN_VALUE); + _verifyLong(CBOR_F, 1L + Integer.MAX_VALUE); + _verifyLong(CBOR_F, Long.MIN_VALUE); + _verifyLong(CBOR_F, Long.MAX_VALUE); + _verifyLong(CBOR_F, -1L + Integer.MIN_VALUE); } private void _verifyLong(CBORFactory f, long value) throws Exception @@ -107,14 +138,45 @@ private void _verifyLong(CBORFactory f, long value) throws Exception p.close(); } + + // Special tests for "gray area" for uint64 values that do not fit + // in Java long; from [dataformats-binary#30] + public void testInt64Overflow() throws Exception + { + // feed in max uint64, which is 2x+1 as big as Long.MAX_VALUE + byte[] input = new byte[] { + (byte) CBORConstants.PREFIX_TYPE_INT_POS + 27, // uint64, that is, 8 more bytes + -1, -1, -1, -1, -1, -1, -1, -1 + }; + CBORParser p = CBOR_F.createParser(input); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + // should be exposed as BigInteger + assertEquals(NumberType.BIG_INTEGER, p.getNumberType()); + BigInteger exp = BigInteger.valueOf(Long.MAX_VALUE).shiftLeft(1) + .add(BigInteger.ONE); + assertEquals(exp, p.getBigIntegerValue()); + p.close(); + + // and then the reverse; something that ought to be negative + input = new byte[] { + (byte) CBORConstants.PREFIX_TYPE_INT_NEG + 27, + (byte) 0x80, 0, 0, 0, + 0, 0, 0, 0 + }; + p = CBOR_F.createParser(input); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + // should be exposed as `long` because this value won't fit in `int` either + exp = BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE); + assertEquals(exp, p.getBigIntegerValue()); + assertEquals(NumberType.BIG_INTEGER, p.getNumberType()); + p.close(); + } + public void testDoubleValues() throws Exception { - // first, single-byte - CBORFactory f = cborFactory(); - // single byte - _verifyDouble(f, 0.25); - _verifyDouble(f, 20.5); - _verifyDouble(f, -5000.25); + _verifyDouble(CBOR_F, 0.25); + _verifyDouble(CBOR_F, 20.5); + _verifyDouble(CBOR_F, -5000.25); } private void _verifyDouble(CBORFactory f, double value) throws Exception diff --git a/release-notes/VERSION b/release-notes/VERSION index 054db071a..238d1ab42 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -13,6 +13,8 @@ Modules: 2.8.5 (not yet released) +#30 (cbor): Overflow when decoding uint32 for Major type 0 + (reported by TianlinZhou@github) #31 (cbor): Exception serializing double[][] 2.8.4 (14-Oct-2016)