scodec解码/编码分割长度字段

时间:2014-11-13 23:18:20

标签: scala scalaz shapeless scodec

我正在使用scodec:https://github.com/scodec/scodec来解码/编码二进制协议。

我正在努力使用规范的一部分,其中包括"长度"字段由" moreflag"分成两部分。 moreflag表示长度字段是否需要更多空间。

Example:
Byte 1:  identifier bits 8-7, moreFlag bit 6, length bits 5-0    // first length field filled with 0's if moreFlag is false
Byte 2:  moreFlag bit 8, length bits 7-0  
Byte 3:  otherJunk bits 8-0

我的问题是我想将这两个长度字段编码/解码为单个案例类字段:

case class Header(totalLength: Int, otherJunk: Int)

我尝试了一些不同的东西,但到目前为止还没有任何结果:

implicit val headerCodec: Codec[Header] = (
  ("identifier" | uint2) :~>:
  ("moreFlag" | bool).compact >>:~ { meta => 
    if (meta.last) {
      // more flag enabled, combine lengths somehow
      ("first length part" | uint(5)) :: ("moreFlag2DontCare" | uint(1) :~>: ("second length part - how to combine?" | uint(7)) :: ("otherJunk" | uint8) 
    }
    else {
      ("first length part always 0s" | constant(bin"00000")) :: ("moreFlag2DontCare" | uint(1) :~>: ("fullLength" | uint(7)) :: ("otherJunk" | uint8)
    }
  }
).as[Header]

我在这里走在正确的轨道上吗?谢谢!

1 个答案:

答案 0 :(得分:4)

实现此目的的一种方法是使用预定义的组合器来定义二进制格式的结构 - 而不是组合逻辑。然后将组合逻辑放在传递给xmap的函数中。

import scodec._
import bits._
import codecs._
import shapeless._

case class Header(totalLength: Int, otherJunk: Int)

object Header {
  implicit val codec: Codec[Header] = {
    type Struct = Int :: Boolean :: BitVector :: Boolean :: BitVector :: Int :: HNil
    val struct: Codec[Struct] = ("identifier" | uint2) :: ("moreFlag" | bool) :: ("upper length bits" | codecs.bits(5)) :: ("moreFlag2" | bool) :: ("lower length bits" | codecs.bits(7)) :: ("other junk" | uint8)
    def to(header: Header): Struct = {
      val lengthBits = uint(12).encodeValid(header.totalLength)
      val more = !(lengthBits startsWith bin"00000")
      0 :: more :: lengthBits.take(5) :: false :: lengthBits.drop(5) :: header.otherJunk :: HNil
    }
    def from(struct: Struct): Header = struct match {
      case id :: moreFlag :: upperLengthBits :: moreFlag2 :: lowerLengthBits :: otherJunk :: HNil =>
        val length =
          if (moreFlag) uint(12).decodeValidValue(upperLengthBits ++ lowerLengthBits)
          else uint(7).decodeValidValue(lowerLengthBits)
        Header(length, otherJunk)
    }
    struct.xmap[Header](from, to)
  }
}

请注意,上部和下部长度位编码为bits(5)bits(7),然后在from函数中手动合并并解码。尽管使用了不安全的decodeValidValue,但解码是安全的,因为uint(12)编解码器在12位输入上是完全的 - 它不可能返回左侧。

但是,to函数使用encodeValid,这显然不安全 - 例如,Int.MaxValue会导致异常。我们可以使用稍微复杂的版本修复此问题,其中to函数返回Err \/ Struct,我们调用widen而不是xmap

object Header {
  implicit val codec: Codec[Header] = {
    type Struct = Int :: Boolean :: BitVector :: Boolean :: BitVector :: Int :: HNil
    val struct: Codec[Struct] = ("identifier" | uint2) :: ("moreFlag" | bool) :: ("upper length bits" | codecs.bits(5)) :: ("moreFlag2" | bool) :: ("lower length bits" | codecs.bits(7)) :: ("other junk" | uint8)
    def to(header: Header): Err \/ Struct = {
      uint(12).encode(header.totalLength) map { lengthBits =>
        val more = !(lengthBits startsWith bin"00000")
        0 :: more :: lengthBits.take(5) :: false :: lengthBits.drop(5) :: header.otherJunk :: HNil
      }
    }
    def from(struct: Struct): Header = struct match {
      case id :: moreFlag :: upperLengthBits :: moreFlag2 :: lowerLengthBits :: otherJunk :: HNil =>
        val length =
          if (moreFlag) uint(12).decodeValidValue(upperLengthBits ++ lowerLengthBits)
          else uint(7).decodeValidValue(lowerLengthBits)
        Header(length, otherJunk)
    }
    struct.widen[Header](from, to)
  }
}