I have a use case where a header can contain 7 bytes plus an optional 0-15 Bytes of information where the size information is in the lower 4 bits of the 5th Byte so the format is:
4 bytes | 4 bits | 4 bits <- length of extra bytes | 2 bytes | 0-15 extra Bytes
And I modelled that in the following case class
case class FHDR(DevAddr:Long, ADR:Boolean, ADRACKReq:Boolean, ACK:Boolean, FPending:Boolean, FCnt:Int, FOpts:ByteVector)
implicit val FHDRCodec = {
("dev_addr" | uint32L) ::
("adr" | bool) ::
("adr_ack_req" | bool) ::
("ack" | bool) ::
("f_pending" | bool) ::
variableSizePrefixedBytes(uint4,
"f_cnt" | uint16L,
"f_opts" | bytes)
}.as[FHDR]
According to the scala docs in this case i can use the variableSizePrefixedBytes
method to model the 2 extra bytes between the size and the extra bytes.
But i'm doing something wrong as the compiler cannot prove this codec can be converted to/from the FHDR
class, i've been staring at this for a while now, but am clueless
The error in this case is:
error: Could not prove that shapeless.::[Long,shapeless.::[Boolean,shapeless.::[Boolean,shapeless.::[Boolean,shapeless.::[Boolean,shapeless.::[(Int, scodec.bits.ByteVector),shapeless.HNil]]]]]] can be converted to/from FHDR.
}.as[FHDR]
^
Reformatting the error to use ::
in infix position gives:
error: Could not prove that Long :: Boolean :: Boolean :: Boolean :: Boolean :: (Int, ByteVector) :: HNil can be converted to/from FHDR.
}.as[FHDR]
^
The generic representation of FHDR
is Long :: Boolean :: Boolean :: Boolean :: Boolean :: Int :: ByteVector :: HNil
. The issue here is that the last type in the HList
is (Int, ByteVector)
, caused by variableSizePrefixedBytes
, which has this signature:
def variableSizePrefixedBytes[A, B](size: Codec[Int], prefix: Codec[A], value: Codec[B], sizePadding: Int = 0): Codec[(A, B)] = ...
We need to flatten that tuple in to the outer HList
structure, which will result in the codec's shape matching the shape of the generic representation of FHDR
.
import scodec.bits._
import scodec.codecs._
import shapeless.syntax.std.product._
case class FHDR(DevAddr:Long, ADR:Boolean, ADRACKReq:Boolean, ACK:Boolean, FPending:Boolean, FCnt:Int, FOpts:ByteVector)
implicit val FHDRCodec = {
("dev_addr" | uint32L) ::
("adr" | bool) ::
("adr_ack_req" | bool) ::
("ack" | bool) ::
("f_pending" | bool) ::
variableSizePrefixedBytes(uint4,
"f_cnt" | uint16L,
"f_opts" | bytes
).xmapc(_.toHList)(_.tupled)
}.as[FHDR]
Here, we use .toHList
on the Tuple2
, which comes from the shapeless.syntax.std.product._
import. We also use .tupled
to reverse that transformation.