Сериализация битовых полей Scala

Я очень новичок в Scala, и меня смущают функции манипулирования битами. Я надеюсь, что кто-то может указать мне в правильном направлении?

У меня есть массив байтов, определенный со следующими битовыми полями:

0-3 - magic number
  4 - version
5-7 - payload length in bytes
8-X - payload, of variable length, as indicated in bits 5-7

Я хотел бы сериализовать это туда и обратно в такую ​​​​структуру, как:

MagicNumber: Integer
Version: Integer
Length: Integer
payload: Array[Byte]

Как оптимально поступить с битами в этой ситуации? Большинство примеров, которые я видел, имеют дело с сериализацией более высокого уровня, такой как JSON. В этом случае я пытаюсь сериализовать и десериализовать двоичные данные TCP.


person Will I Am    schedule 19.09.2014    source источник


Ответы (2)


Вы можете использовать Scala Pickling, POF или Google Protobuf, но если ваш формат настолько ограничен, самый простой способ - написать свой собственный сериализатор:

case class Data(magicNumber: Int, version: Int, payload: Array[Byte])

def serialize(data: Stream[Data]): Stream[Byte] = 
   data.flatMap(x => 
     Array((x.magicNumber << 4 | x.version << 3 | x.payload.length).toByte) ++ x.payload)

@scala.annotation.tailrec
def deserialize(binary: Stream[Byte], acc: Stream[Data] = Stream[Data]()): Stream[Data] =   
   if(binary.nonEmpty) {
     val magicNumber = binary.head >> 4 
     val version = (binary.head & 0x08) >>3 
     val size = binary.head & 0x07
     val data = Data(magicNumber, version, ByteVector(binary.tail.take(size).toArray)) 
     deserialize(binary.drop(size + 1), acc ++ Stream(data)) 
   } else acc

Или вы можете использовать библиотеку Scodec (этот вариант лучше, потому что у вас будет автоматическая проверка диапазона значений):

СБТ:

  libraryDependencies += "org.typelevel" %% "scodec-core" % "1.3.0"

Кодек:

  case class Data(magicNumber: Int, version: Int, payload: ByteVector)
  val codec = (uint(4) :: uint(1) :: variableSizeBytes(uint(3), bytes)).as[Data]

Использовать:

  val encoded = codec.encode(Data(2, 1, bin"01010101".bytes)).fold(sys.error, _.toByteArray)
  val decoded = codec.decode(BitVector(encoded)).fold(sys.error, _._2)
person dk14    schedule 19.09.2014
comment
Спасибо, это помогает. Я также пытаюсь посмотреть на травление scala, но, к сожалению, ему не хватает примеров. я еще не понимаю, как бы я сделал это с травлением / рассолом. Пока мне этого достаточно! - person Will I Am; 19.09.2014
comment
scodec более бит-ориентирован, чем травление, я обновил ответ рабочим примером для него. Но это зависит от тяжелых библиотек типа scalaz, shapeless. - person dk14; 20.09.2014
comment
Я делаю это правильно? Кажется, я не могу совершить поездку туда и обратно: val input2 = new Data(0,3,Array[Byte]('h','e','l','l','o')) val output2 = сериализовать (поток (input2)) val output3 = десериализовать (output2) - person Will I Am; 20.09.2014
comment
Поэтому я рекомендую использовать scodec (у него есть автоматическая проверка диапазона). В вашем случае версия = 3. Максимально возможное значение для версии (1 бит) равно 1. Вы также можете добавить утверждение (для магии ‹ 2 ^ 4, версия ‹ 2, data.size ‹ 2 ^ 3) для сериализации функции, чтобы избежать таких ситуация не туда и обратно. - person dk14; 20.09.2014
comment
Спасибо. В итоге я использовал манипуляции с битовыми полями просто потому, что не хотел добавлять дополнительные большие зависимости. Это было не так сложно, но могло бы быть намного проще, если бы была поддержка беззнакового байта. - person Will I Am; 22.09.2014

Я бы посмотрел на scodec. На основе примера UDP, это должно быть что-то вроде (не проверено):

import scodec.bits.{ BitVector, ByteVector }
import scodec.codecs._

case class Datagram(
  magicNumber: Int,
  version: Byte,
  payload: ByteVector)

object Datagram {
  implicit val codec: Codec[Datagram] = {
    ("magic_number" | int32 ) ::
    ("version" | byte ) ::
    variableSizeBytes(int(3),
      ("payload" | bytes ))
  }.as[Datagram]
}
person Alexey Romanov    schedule 19.09.2014