From 9044eb31fe7431ca607a35e0ef80ba728e43aa4f Mon Sep 17 00:00:00 2001 From: Jozef Koval Date: Wed, 19 Feb 2025 18:15:48 +0100 Subject: [PATCH] Implement Reader#getUIntExact(). --- .../src/main/scala/scorex/util/Extensions.scala | 3 ++- .../scala/scorex/util/serialization/Reader.scala | 7 +++++++ .../scorex/util/serialization/VLQReader.scala | 15 ++++++++++++++- .../scorex/util/serialization/VLQWriter.scala | 6 +++--- .../scala/scorex/util/serialization/Writer.scala | 2 +- .../VLQReaderWriterSpecification.scala | 12 ++++++++++++ 6 files changed, 39 insertions(+), 6 deletions(-) diff --git a/shared/src/main/scala/scorex/util/Extensions.scala b/shared/src/main/scala/scorex/util/Extensions.scala index 050faf6..d746028 100644 --- a/shared/src/main/scala/scorex/util/Extensions.scala +++ b/shared/src/main/scala/scorex/util/Extensions.scala @@ -74,6 +74,7 @@ object Extensions { * If `Long` value is out of the possible range for a [[scala.Int]] result, * then a `java.lang.ArithmeticException` is thrown. */ + @deprecated("Use Reader#getUIntExact() instead") def toIntExact: Int = { if (x < Int.MinValue || x > Int.MaxValue) throw new ArithmeticException("Int overflow") @@ -86,7 +87,7 @@ object Extensions { /** * Safely casting each element of collection to be type of `B`. - * If element can not to be cast to `B` then `AssertionError` is thrown + * If element can not to be cast to `B` then `IllegalArgumentException` is thrown */ def cast[B:ClassTag]: Source[B] = { diff --git a/shared/src/main/scala/scorex/util/serialization/Reader.scala b/shared/src/main/scala/scorex/util/serialization/Reader.scala index c44dd2a..410d365 100644 --- a/shared/src/main/scala/scorex/util/serialization/Reader.scala +++ b/shared/src/main/scala/scorex/util/serialization/Reader.scala @@ -68,8 +68,15 @@ abstract class Reader { * Decode positive Int. * @return signed Long */ + @deprecated("Use getUIntExact() instead") def getUInt(): Long + /** + * Decode positive Int. + * @return unsigned 31-bit Int + */ + def getUIntExact(): Int + /** * Decode signed Long. * @return signed Long diff --git a/shared/src/main/scala/scorex/util/serialization/VLQReader.scala b/shared/src/main/scala/scorex/util/serialization/VLQReader.scala index 84bd942..becbf1d 100644 --- a/shared/src/main/scala/scorex/util/serialization/VLQReader.scala +++ b/shared/src/main/scala/scorex/util/serialization/VLQReader.scala @@ -25,7 +25,7 @@ trait VLQReader extends Reader { * Decode Short previously encoded with [[VLQWriter.putUShort]] using VLQ. * @see [[https://en.wikipedia.org/wiki/Variable-length_quantity]] * @return Int - * @throws AssertionError for deserialized values not in unsigned Short range + * @throws IllegalArgumentException for deserialized values not in unsigned Short range */ @inline override def getUShort(): Int = { val x = getULong().toInt @@ -50,6 +50,7 @@ trait VLQReader extends Reader { * Decode Int previously encoded with [[VLQWriter.putUInt]] using VLQ. * @see [[https://en.wikipedia.org/wiki/Variable-length_quantity]] * @return Long + * @throws IllegalArgumentException for deserialized values not in unsigned Int range */ @inline override def getUInt(): Long = { val x = getULong() @@ -57,6 +58,18 @@ trait VLQReader extends Reader { x } + /** + * Decode Int previously encoded with [[VLQWriter.putUInt]] using VLQ. + * @see [[https://en.wikipedia.org/wiki/Variable-length_quantity]] + * @return Int + * @throws IllegalArgumentException for deserialized values not in unsigned 31-bit Int range + */ + @inline override def getUIntExact(): Int = { + val x = getULong() + require(x >= 0L && x <= Int.MaxValue.toLong, s"$x is out of unsigned 31-bit int range") + x.toInt + } + /** * Decode signed Long previously encoded with [[VLQWriter.putLong]] using VLQ with ZigZag. * diff --git a/shared/src/main/scala/scorex/util/serialization/VLQWriter.scala b/shared/src/main/scala/scorex/util/serialization/VLQWriter.scala index 82f26e7..666ac7a 100644 --- a/shared/src/main/scala/scorex/util/serialization/VLQWriter.scala +++ b/shared/src/main/scala/scorex/util/serialization/VLQWriter.scala @@ -31,7 +31,7 @@ trait VLQWriter extends Writer { * * @see [[https://en.wikipedia.org/wiki/Variable-length_quantity]] * @param x unsigned Short in a range 0 <= x <= 0xFFFF represented as Int - * @throws AssertionError for values not in unsigned Short range + * @throws IllegalArgumentException for values not in unsigned Short range */ @inline override def putUShort(x: Int): this.type = { require(x >= 0 && x <= 0xFFFF, s"Value $x is out of unsigned short range") @@ -59,7 +59,7 @@ trait VLQWriter extends Writer { * * @see [[https://en.wikipedia.org/wiki/Variable-length_quantity]] * @param x unsigned Int - * @throws AssertionError for values not in unsigned Int range + * @throws IllegalArgumentException for values not in unsigned Int range */ @inline override def putUInt(x: Long): this.type = { require(x >= 0 && x <= 0xFFFFFFFFL, s"$x is out of unsigned int range") @@ -151,7 +151,7 @@ trait VLQWriter extends Writer { * Encode String is shorter than 256 bytes * * @param s String - * @return + * @throws IllegalArgumentException for strings longer than 255 bytes */ override def putShortString(s: String): this.type = { val bytes = s.getBytes diff --git a/shared/src/main/scala/scorex/util/serialization/Writer.scala b/shared/src/main/scala/scorex/util/serialization/Writer.scala index 8dc9fa3..b2b6fb2 100644 --- a/shared/src/main/scala/scorex/util/serialization/Writer.scala +++ b/shared/src/main/scala/scorex/util/serialization/Writer.scala @@ -46,7 +46,7 @@ abstract class Writer { * Encode integer as an unsigned byte asserting the range check * @param x integer value to encode * @return - * @throws AssertionError if x is outside of the unsigned byte range + * @throws IllegalArgumentException if x is outside of the unsigned byte range */ def putUByte(x: Int): this.type = { require(x >= 0 && x <= 0xFF, s"$x is out of unsigned byte range") diff --git a/shared/src/test/scala/scorex/util/serialization/VLQReaderWriterSpecification.scala b/shared/src/test/scala/scorex/util/serialization/VLQReaderWriterSpecification.scala index 01ff74a..1a39a08 100644 --- a/shared/src/test/scala/scorex/util/serialization/VLQReaderWriterSpecification.scala +++ b/shared/src/test/scala/scorex/util/serialization/VLQReaderWriterSpecification.scala @@ -338,6 +338,18 @@ trait VLQReaderWriterSpecification extends AnyPropSpec } } + property("unsigned Int roundtrip exact"){ + forAll(Gen.chooseNum(0, Int.MaxValue)) { x: Int => + byteBufReader(byteArrayWriter().putUInt(x.toLong).toBytes).getUIntExact() shouldBe x + } + forAll(Gen.chooseNum(Long.MinValue, -1L)) { x: Long => + an[IllegalArgumentException] should be thrownBy byteBufReader(byteArrayWriter().putULong(x).toBytes).getUIntExact() + } + forAll(Gen.chooseNum(Int.MaxValue.toLong + 1L, Long.MaxValue)) { x: Long => + an[IllegalArgumentException] should be thrownBy byteBufReader(byteArrayWriter().putULong(x).toBytes).getUIntExact() + } + } + property("Long roundtrip") { forAll { (v: Long) => byteBufReader(byteArrayWriter().putLong(v).toBytes).getLong() shouldBe v } }