Как мы можем сериализовать и десериализовать структуру данных Haskell в Bytestring? - PullRequest
0 голосов
/ 09 января 2019

У меня есть TCP-сервер, написанный на C ++, который ожидает запрос в следующем формате:

struct Header {
   int headerField1;
   int headerField2;
}

struct Request {
   Header header;
   char[14] uniqueID;
   char[12] password;
}

Я хочу реализовать клиент для отправки этого запроса на мой сервер в Haskell

Я пробовал Data.Binary.encode, который не справляется с задачей. Меня больше смущает, как я могу использовать произвольный размер шрифта в haskell. то есть char [12];

Код Haskell:

data Header = Header 
  {
     headerField1   :: Word32
  ,  headerField2   :: Word32
  } deriving (Generic)
instance Binary Header

data Request = Request 
  {
     header         :: Header
  ,  uniqueID       :: ByteString -- I am not sure which data type to use here.
  ,  password       :: ByteString -- Same as above, as length is defined 12 bytes which is arbitrary.
  } deriving (Generic)
instance Binary Request

Я написал пользовательскую строку для парсера данных, которая отлично работает для заголовка, поскольку нет произвольного размера

parseHeader = do
    Header <$> 
        getWord32le <*>
        getWord32le

Я ищу способ Haskell для сериализации и десериализации структуры пакета, определенной для ByteString (и наоборот), а также для создания типа данных произвольного размера - char [12]

1 Ответ

0 голосов
/ 09 января 2019

Чтобы сначала ответить на главный вопрос, вы можете разобрать строки байтов известной длины с помощью getByteString (или getLazyByteString). Таким образом, двоичный парсер для Request может быть:

parseRequest :: Get Request
parseRequest =
  Request
    <$> parseHeader
    <*> getByteString 14
    <*> getByteString 12

Если у вас также есть сериализаторы, скажем, putRequest, вы можете поместить его в экземпляр Binary с анализатором, что позволит вам использовать некоторые дополнительные функции библиотеки для удобства (но вам не нужно) .

instance Binary Request where
  get = parseRequest
  put = putRequest

Чтобы не перепутать пароль и идентификатор, неплохо было бы обернуть их в новые типы:

newtype UniqueID = MkUniqueID ByteString  -- length 14
newtype Password = MkPassword ByteString  -- length 12

При реализации операций над ними убедитесь, что они не создают значения неправильной длины. Затем вы можете скрыть конструкторы при экспорте типов, чтобы пользователи не могли сломать эти инварианты.

В синтаксических анализаторах для этих типов вы указываете желаемую длину:

parseUniqueID :: Get UniqueID
parseUniqueID = MkUniqueID <$> getByteString 14

parsePassword :: Get Password
parsePassword = MkPassword <$> getByteString 12

Теперь это делает определение Request более описательным, единственный способ смешать пароль и идентификатор в коде на Haskell - это ошибиться при сериализации / десериализации, что снижает вероятность ошибок в других местах.

data Request = Request
  { header   :: Header
  , uniqueID :: UniqueID
  , password :: Password
  }

parseRequest :: Get Request
parseRequest =
  Request
    <$> parseHeader
    <*> parseUniqueID
    <*> parsePassword
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...