3

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

Gertjan Assies
  • 1,890
  • 13
  • 23

1 Answers1

4

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.

mpilquist
  • 3,855
  • 21
  • 22
  • Kewl, that works and I must say I love the way I can read a specification and almost one to one translate that into a bunch of codecs, great work! – Gertjan Assies Jan 05 '16 at 21:11