我遇到了一个关于 scodec 的有趣问题。我有一个特殊的编码方案,当当前位指针 mod 8 不为零(未与最近的字节对齐)时,它需要流字节对齐。
现在通常,这将由byteAligned
编解码器处理,但这种情况似乎需要整个解码操作的全局上下文。
这是问题的最小示例
case class Baz(abc : Int, qux : Int)
case class Foo(bar : Int,
items : Vector[Baz])
object Foo {
implicit val codec : Codec[Foo] = (
("bar" | uint8L) ::
("items" | vectorOfN(uint8L, (
("abc" | uint8L) ::
("qux" | uint2L)
).as[Baz]
))).as[Foo]
}
items
将被编码和解码为一个Vector
项目Baz
。请注意,qux
成员 inBaz
表示为 2 位无符号整数。我们希望abc
成员是字节对齐的,这意味着在它的解码过程中,它将解码,如果字节流未对齐,则必须在解码位之后添加填充(案例 C)。
这意味着对于大小为 1 的向量(情况 A),输出 BitVector 将错位 6 位,这很好,因为没有更多项目(abc
在向量解码循环中再也不会到达)。
没有字节对齐的大小为 2 的向量显示在案例 B 中。具有字节对齐的向量显示在案例 C 中。
每个案例下方是分解比特流的注释。0x0a
十进制为十六进制,十进制0b01
为二进制。当需要更多细节并且流不是字节对齐时,我将在两者之间切换。
案例A
Codec.encode(Foo(10, Vector(Baz(4,1))))
res43: Attempt[BitVector] = Successful(BitVector(26 bits, 0x0a01044))
// bar N items abc(0) qux(0)
// 0x0a 0x01 0x04 0b01
案例B
Codec.encode(Foo(10, Vector(Baz(4,1), Baz(15,3))))
res42: Attempt[BitVector] = Successful(BitVector(36 bits, 0x0a020443f))
// bar N items abc(0) qux(0) abc(1) qux(1)
// 0x0a 0x02 0x04 0b01 0b00001111 0b11
案例C
Codec.encode(Foo(10, Vector(Baz(4,1), Baz(15,3))))
res42: Attempt[BitVector] = Successful(BitVector(42 bits, 0x0a020443c0c))
// bar N items abc(0) qux(0) abc(1) padding qux(1)
// 0x0a 0x02 0x04 0b01 0b00001111 0b000000 0b11
查看案例 C 的另一种方法是展开编解码器
// bar N items abc(0) qux(0) abc(1) padding qux(1)
(uint8L :: uint8L :: uint8L :: uint2L :: uint8L :: ignore(6) :: uint2L).
dropUnits.encode(10 :: 2 :: 4 :: 1 :: 15 :: 3 :: HNil)
res47: Attempt[BitVector] = Successful(BitVector(42 bits, 0x0a020443c0c))
请注意,之后没有任何填充,abc(0)
因为此时流是字节对齐的。abc(1)
由于解码,它没有对齐qux(0)
。对齐后,qux
将在对齐的边界上解码。我们总是希望qux
在字节对齐的位置上解码。
我需要一些方法来获取全局上下文以确定整个 BitVector 中的当前位指针是否对齐。如果不是,我需要一个编解码器来智能地知道它何时不在对齐的边界上解码,并在解码它的当前值后纠正流。
我知道编解码器可以获取当前比特流进行解码,但他们不知道它们在整个比特流中的位置。
任何方向或代码将不胜感激。
附言
如果你想在 Ammonite 中进行测试,你需要这个来开始
load.ivy("org.scodec" %% "scodec-core" % "1.8.3")
import scodec.codecs._
import scodec._
import shapeless._
复制和粘贴时,您需要以 a 开头,{
以确保同时定义案例类和案例对象。