2

我正在使用 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]

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

4

1 回答 1

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函数中手动组合并解码。尽管使用了 unsafe decodeValidValue,但解码是安全的,因为uint(12)编解码器是 12 位输入的总和——它不可能返回左。

但是,to函数 uses 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)
  }
}
于 2014-11-14T00:54:01.570 回答