diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7533de2..6fb017d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [3.3.1, 2.13.12, 2.12.18, 2.11.12] + scala: [3.3.5, 2.13.16, 2.12.20, 2.11.12] java: [adopt@1.8] runs-on: ${{ matrix.os }} steps: @@ -64,7 +64,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [3.3.1, 2.13.12] + scala: [3.3.5, 2.13.16] java: [adopt@1.8] runs-on: ${{ matrix.os }} steps: diff --git a/build.sbt b/build.sbt index c1576ab..ac5c7e1 100644 --- a/build.sbt +++ b/build.sbt @@ -103,10 +103,11 @@ lazy val scalac3: Seq[String] = Seq( "-source:3.0-migration" // makes the compiler forgiving on most of the dropped features, printing warnings in place of errors ) -lazy val scala213 = "2.13.12" -lazy val scala212 = "2.12.18" +lazy val scala213 = "2.13.16" +lazy val scala212 = "2.12.20" lazy val scala211 = "2.11.12" -lazy val scala3 = "3.3.1" +lazy val scala3 = "3.3.5" +lazy val scalatestVersion = "3.2.19" crossScalaVersions := Seq(scala211, scala212, scala213, scala3) scalaVersion := scala213 @@ -125,30 +126,27 @@ lazy val utilSettings = Seq( resolvers ++= Resolver.sonatypeOssRepos("snapshots"), libraryDependencies ++= Seq( - ("org.rudogma" %%% "supertagged" % "2.0-RC2").cross(CrossVersion.for3Use2_13), + "org.scalatest" %%% "scalatest" % scalatestVersion % Test, + "org.scalatest" %%% "scalatest-propspec" % scalatestVersion % Test, + "org.scalatest" %%% "scalatest-shouldmatchers" % scalatestVersion % Test ) ++ { if (scalaVersion.value == scala3) Seq( - "org.scalatest" %%% "scalatest" % "3.3.0-alpha.1" % Test, - "org.scalatest" %%% "scalatest-propspec" % "3.3.0-alpha.1" % Test, - "org.scalatest" %%% "scalatest-shouldmatchers" % "3.3.0-alpha.1" % Test, - "org.scalacheck" %%% "scalacheck" % "1.15.3" % Test, - "org.scalatestplus" %%% "scalacheck-1-17" % "3.3.0.0-alpha.1" % Test + "org.scalacheck" %%% "scalacheck" % "1.18.1" % Test, + "org.scalatestplus" %%% "scalacheck-1-18" % "3.2.19.0" % Test ) else // use last versions with Scala 2.11 support Seq( - "org.scalatest" %%% "scalatest" % "3.3.0-SNAP3" % Test, - "org.scalatest" %%% "scalatest-propspec" % "3.3.0-SNAP3" % Test, - "org.scalatest" %%% "scalatest-shouldmatchers" % "3.3.0-SNAP3" % Test, + "org.rudogma" %%% "supertagged" % "2.0-RC2", "org.scalacheck" %%% "scalacheck" % "1.15.2" % Test, "org.scalatestplus" %%% "scalacheck-1-15" % "3.3.0.0-SNAP3" % Test ) }, scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, n)) if n == 13 => + case Some((2, 13)) => scalac ++ scalac213 - case Some((2, n)) if n == 12 => + case Some((2, 12)) => scalac ++ scalac212 case Some((2, 11)) => scalac ++ scalac211 diff --git a/project/build.properties b/project/build.properties index 3c0b78a..73df629 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.1 +sbt.version=1.10.7 diff --git a/project/plugins.sbt b/project/plugins.sbt index 8b1c598..b4912c1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,8 @@ -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") -addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7") -addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.8") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.0") -addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.1") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.2") +addDependencyTreePlugin +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.0") +addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.15") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.12.2") +addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") +addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.18.2") diff --git a/shared/src/main/scala/scorex/util/package.scala b/shared/src/main/scala-2/scorex/util/package.scala similarity index 100% rename from shared/src/main/scala/scorex/util/package.scala rename to shared/src/main/scala-2/scorex/util/package.scala diff --git a/shared/src/main/scala-3/scorex/util/package.scala b/shared/src/main/scala-3/scorex/util/package.scala new file mode 100644 index 0000000..9f291a5 --- /dev/null +++ b/shared/src/main/scala-3/scorex/util/package.scala @@ -0,0 +1,20 @@ +package scorex + +import scorex.util.encode.Base16 + +opaque type ModifierId <: String = String +object ModifierId { + def apply(s: String): ModifierId = s +} + +def bytesToId(bytes: Array[Byte]): ModifierId = ModifierId(Base16.encode(bytes)) + +def idToBytes(id: ModifierId): Array[Byte] = Base16.decode(id).get + +extension (m: ModifierId) { + @inline def toBytes: Array[Byte] = idToBytes(m) +} + +extension (bytes: Array[Byte]) { + @inline def toModifierId: ModifierId = bytesToId(bytes) +} \ No newline at end of file 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 } }