简体   繁体   English

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

[英]scodec decode/encode split length field

I am using scodec: https://github.com/scodec/scodec to decode/encode a binary protocol. 我正在使用scodec: https : //github.com/scodec/scodec解码/编码二进制协议。

I am struggling with a part of the spec where a "length" field is split into two parts by a "moreflag". 我在规范的一部分中苦苦挣扎,其中“ length”字段被“ moreflag”分为两部分。 The moreflag indicates if the length field needs more space. 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

My problem is I want to encode/decode both of these length fields into a single case class field: 我的问题是我想将这两个长度字段都编码/解码为单个case类字段:

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

I have tried a few different things, but nothing has worked out so far: 我尝试了几种不同的方法,但是到目前为止,仍然没有任何结果:

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]

Am I on the right track here? 我在正确的轨道上吗? Thanks! 谢谢!

One way to accomplish this is to use the predefined combinators to define the structure of the binary format -- not the combination logic. 实现此目的的一种方法是使用预定义的组合器来定义二进制格式的结构,而不是组合逻辑。 Then put the combination logic inside a function passed to xmap . 然后将组合逻辑放在传递给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)
  }
}

Note that the upper and lower length bits are encoded as bits(5) and bits(7) and then manually combined in the from function and decoded. 注意,将上下长度的位编码为bits(5)bits(7) ,然后在from函数中手动组合并解码。 The decoding is safe, despite using the unsafe decodeValidValue , because the uint(12) codec is total on 12-bit inputs -- it is impossible for it to return a left. 尽管使用了不安全的decodeValidValue ,但解码是安全的,因为uint(12)编解码器是12位输入上的总计-不可能返回左。

However, the to function uses encodeValid , which is clearly unsafe -- eg, Int.MaxValue will cause an exception. 但是, to函数使用encodeValid ,这显然是不安全的-例如, Int.MaxValue将导致异常。 We can fix this with a slightly more complicated version, where the to function returns an Err \\/ Struct , and we call widen instead of xmap : 我们可以使用稍微复杂些的版本来解决此问题,其中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)
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM