scodec декодирует / кодирует разделенное поле длины
Я использую scodec: https://github.com/scodec/scodec для декодирования / кодирования двоичного протокола.
Я борюсь с частью спецификации, где поле "length" разделено на две части "moreflag". Флаг more указывает, нужно ли полю длины больше места.
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 ответ
Один из способов сделать это - использовать предопределенные комбинаторы для определения структуры двоичного формата, а не логики комбинации. Затем поместите комбинационную логику в функцию, переданную 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)
}
}