Skip to content

Commit

Permalink
Merge pull request #454 from gnieh/fix/bignum-decoding
Browse files Browse the repository at this point in the history
Decode bignums properly
  • Loading branch information
ybasket authored Mar 22, 2023
2 parents 4a17f03 + d536f51 commit a4ab8cc
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 48 deletions.
2 changes: 2 additions & 0 deletions cbor/shared/src/main/scala/fs2/data/cbor/CborException.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ sealed abstract class CborException(msg: String, inner: Throwable) extends Excep

class CborParsingException(msg: String, inner: Throwable = null) extends CborException(msg, inner)

class CborTagDecodingException(msg: String, inner: Throwable = null) extends CborException(msg, inner)

class CborValidationException(msg: String, inner: Throwable = null) extends CborException(msg, inner)
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ package internal
import low.CborItem

import scala.collection.mutable

import java.lang.{Float => JFloat, Double => JDouble, Long => JLong}
import java.lang.{Double => JDouble, Float => JFloat, Long => JLong}
import scodec.bits.ByteVector

object ValueParser {
Expand Down Expand Up @@ -209,16 +208,31 @@ object ValueParser {
private def parseTags[F[_]](chunk: Chunk[CborItem],
idx: Int,
rest: Stream[F, CborItem],
tags: CborValue => CborValue,
tags: CborValue => Pull[F, Nothing, CborValue],
chunkAcc: List[CborValue])(implicit
F: RaiseThrowable[F]): Pull[F, CborValue, Result[F, CborValue => CborValue]] =
F: RaiseThrowable[F]): Pull[F, CborValue, Result[F, CborValue => Pull[F, Nothing, CborValue]]] =
if (idx >= chunk.size) {
Pull.output(Chunk.seq(chunkAcc.reverse)) >> rest.pull.uncons.flatMap {
case Some((hd, tl)) => parseTags(hd, 0, tl, tags, Nil)
case None => Pull.raiseError(new CborParsingException("unexpected end of input"))
}
} else {
chunk(idx) match {
case CborItem.Tag(Tags.PositiveBigNum) =>
def f(recurse: Boolean): CborValue => Pull[F, Nothing, CborValue] = {
case CborValue.ByteString(bytes) => tags(CborValue.Integer(BigInt(bytes.toArrayUnsafe)))
case CborValue.Tagged(tag2, v) if recurse => tags(CborValue.Tagged(tag2, v)).flatMap(f(false))
case _ => Pull.raiseError(new CborTagDecodingException("A bignum must have a byte string as value"))
}
parseTags(chunk, idx + 1, rest, f(true), chunkAcc)
case CborItem.Tag(Tags.NegativeBigNum) =>
def f(recurse: Boolean): CborValue => Pull[F, Nothing, CborValue] = {
case CborValue.ByteString(bytes) =>
tags(CborValue.Integer(minusOne + minusOne * BigInt(bytes.toArrayUnsafe)))
case CborValue.Tagged(tag2, v) if recurse => tags(CborValue.Tagged(tag2, v)).flatMap(f(false))
case _ => Pull.raiseError(new CborTagDecodingException("A bignum must have a byte string as value"))
}
parseTags(chunk, idx + 1, rest, f(true), chunkAcc)
case CborItem.Tag(tag) => parseTags(chunk, idx + 1, rest, v => tags(CborValue.Tagged(tag, v)), chunkAcc)
case _ => Pull.pure((chunk, idx, rest, chunkAcc, tags))
}
Expand All @@ -228,76 +242,68 @@ object ValueParser {

private def parseValue[F[_]](chunk: Chunk[CborItem], idx: Int, rest: Stream[F, CborItem], chunkAcc: List[CborValue])(
implicit F: RaiseThrowable[F]): Pull[F, CborValue, Result[F, CborValue]] =
parseTags(chunk, idx, rest, identity, chunkAcc).flatMap { case (chunk, idx, rest, chunkAcc, tags) =>
parseTags(chunk, idx, rest, Pull.pure, chunkAcc).flatMap { case (chunk, idx, rest, chunkAcc, tags) =>
if (idx >= chunk.size) {
Pull.output(Chunk.seq(chunkAcc.reverse)) >> rest.pull.uncons.flatMap {
case Some((hd, tl)) => parseValue(hd, 0, tl, Nil)
case None => Pull.raiseError(new CborParsingException("unexpected end of input"))
}
} else {
chunk(idx) match {
case CborItem.False => Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.False)))
case CborItem.True => Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.True)))
case CborItem.Null => Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.Null)))
case CborItem.Undefined => Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.Undefined)))
case CborItem.False =>
tags(CborValue.False).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.True =>
tags(CborValue.True).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.Null =>
tags(CborValue.Null).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.Undefined =>
tags(CborValue.Undefined).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.SimpleValue(value) =>
Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.SimpleValue(value))))
tags(CborValue.SimpleValue(value)).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.PositiveInt(bytes) =>
val value = BigInt(bytes.toHex, 16)
Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.Integer(value))))
val value = BigInt(bytes.toArrayUnsafe)
tags(CborValue.Integer(value)).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.NegativeInt(bytes) =>
val value = minusOne - BigInt(bytes.toHex, 16)
Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.Integer(value))))
val value = minusOne - BigInt(bytes.toArrayUnsafe)
tags(CborValue.Integer(value)).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.Float16(raw) =>
Pull.pure(
(chunk,
idx + 1,
rest,
chunkAcc,
tags(CborValue.Float32(fs2.data.cbor.HalfFloat.toFloat(raw.toShort(signed = false))))))
tags(CborValue.Float32(fs2.data.cbor.HalfFloat.toFloat(raw.toShort(signed = false)))).map(v =>
(chunk, idx + 1, rest, chunkAcc, v))
case CborItem.Float32(raw) =>
Pull.pure(
(chunk,
idx + 1,
rest,
chunkAcc,
tags(CborValue.Float32(JFloat.intBitsToFloat(raw.toInt(signed = false))))))
tags(CborValue.Float32(JFloat.intBitsToFloat(raw.toInt(signed = false)))).map(v =>
(chunk, idx + 1, rest, chunkAcc, v))
case CborItem.Float64(raw) =>
Pull.pure(
(chunk,
idx + 1,
rest,
chunkAcc,
tags(CborValue.Float64(JDouble.longBitsToDouble(raw.toLong(signed = false))))))
tags(CborValue.Float64(JDouble.longBitsToDouble(raw.toLong(signed = false)))).map(v =>
(chunk, idx + 1, rest, chunkAcc, v))
case CborItem.ByteString(bytes) =>
Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.ByteString(bytes))))
tags(CborValue.ByteString(bytes)).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.StartIndefiniteByteString =>
parseByteStrings(chunk, idx + 1, rest, ByteVector.empty, chunkAcc).map {
parseByteStrings(chunk, idx + 1, rest, ByteVector.empty, chunkAcc).flatMap {
case (chunk, idx, rest, chunkAcc, bs) =>
(chunk, idx, rest, chunkAcc, tags(bs))
tags(bs).map(v => (chunk, idx, rest, chunkAcc, v))
}
case CborItem.TextString(string) =>
Pull.pure((chunk, idx + 1, rest, chunkAcc, tags(CborValue.TextString(string))))
tags(CborValue.TextString(string)).map(v => (chunk, idx + 1, rest, chunkAcc, v))
case CborItem.StartIndefiniteTextString =>
parseTextStrings(chunk, idx + 1, rest, new StringBuilder, chunkAcc).map {
parseTextStrings(chunk, idx + 1, rest, new StringBuilder, chunkAcc).flatMap {
case (chunk, idx, rest, chunkAcc, ts) =>
(chunk, idx, rest, chunkAcc, tags(ts))
tags(ts).map(v => (chunk, idx, rest, chunkAcc, v))
}
case CborItem.StartArray(size) =>
parseArray(chunk, idx + 1, rest, size, new mutable.ListBuffer, chunkAcc).map {
case (chunk, idx, rest, chunkAcc, array) => (chunk, idx, rest, chunkAcc, tags(array))
parseArray(chunk, idx + 1, rest, size, new mutable.ListBuffer, chunkAcc).flatMap {
case (chunk, idx, rest, chunkAcc, array) => tags(array).map(v => (chunk, idx, rest, chunkAcc, v))
}
case CborItem.StartIndefiniteArray =>
parseIndefiniteArray(chunk, idx + 1, rest, new mutable.ListBuffer, chunkAcc).map {
case (chunk, idx, rest, chunkAcc, array) => (chunk, idx, rest, chunkAcc, tags(array))
parseIndefiniteArray(chunk, idx + 1, rest, new mutable.ListBuffer, chunkAcc).flatMap {
case (chunk, idx, rest, chunkAcc, array) => tags(array).map(v => (chunk, idx, rest, chunkAcc, v))
}
case CborItem.StartMap(size) =>
parseMap(chunk, idx + 1, rest, size, mutable.Map.empty, chunkAcc).map {
case (chunk, idx, rest, chunkAcc, array) => (chunk, idx, rest, chunkAcc, tags(array))
parseMap(chunk, idx + 1, rest, size, mutable.Map.empty, chunkAcc).flatMap {
case (chunk, idx, rest, chunkAcc, array) => tags(array).map(v => (chunk, idx, rest, chunkAcc, v))
}
case CborItem.StartIndefiniteMap =>
parseIndefiniteMap(chunk, idx + 1, rest, mutable.Map.empty, chunkAcc).map {
case (chunk, idx, rest, chunkAcc, array) => (chunk, idx, rest, chunkAcc, tags(array))
parseIndefiniteMap(chunk, idx + 1, rest, mutable.Map.empty, chunkAcc).flatMap {
case (chunk, idx, rest, chunkAcc, array) => tags(array).map(v => (chunk, idx, rest, chunkAcc, v))
}
case item =>
raise(new CborParsingException(s"unknown item $item"), chunkAcc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ private[cbor] object ValueSerializer {
go(Chunk.singleton(value), 0, Stream.empty, CborItem.Tag(tag) :: acc) >>
go(chunk, idx + 1, rest, Nil)
case CborValue.Float32(value) =>
go(chunk, idx + 1, rest, CborItem.Float32(ByteVector.fromInt(JFloat.floatToRawIntBits(value))) :: acc)
go(chunk, idx + 1, rest, CborItem.Float32(ByteVector.fromInt(JFloat.floatToIntBits(value))) :: acc)
case CborValue.Float64(value) =>
go(chunk, idx + 1, rest, CborItem.Float64(ByteVector.fromLong(JDouble.doubleToRawLongBits(value))) :: acc)
go(chunk, idx + 1, rest, CborItem.Float64(ByteVector.fromLong(JDouble.doubleToLongBits(value))) :: acc)
case CborValue.False =>
go(chunk, idx + 1, rest, CborItem.False :: acc)
case CborValue.True =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2022 Lucas Satabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fs2.data.cbor.high

import cats.effect.IO
import fs2._
import weaver.SimpleIOSuite

object ValueParserTest extends SimpleIOSuite {

test("roundtrips bignums") {
val in = CborValue.Integer(BigInt("-739421513997118914047232662242593364"))
Stream(in).covary[IO].through(toBinary).through(values).compile.onlyOrError.map { out =>
expect.same(in, out)
}
}

}

0 comments on commit a4ab8cc

Please sign in to comment.