【问题标题】:Decoding ByteString Using Encoding使用编码解码 ByteString
【发布时间】:2021-05-19 06:43:35
【问题描述】:

我正在构建一个从文件中读取 381 个字节并尝试解码输入的脚本。我对我标记为“预设”的那些字节中的 348 个感兴趣。预设的 ByteString 的 3 字节块可以解码成单个 Int16,下面的“值”是我感兴趣的 116 Int16...

decodeFile :: FilePath -> IO [Maybe PresetValue]
decodeFile filename =
  do h <- openFile (dir ++ filename) ReadMode
     header  <- h `BL.hGet` 32
     presets <- h `BL.hGet` 348
     f7      <- h `BL.hGet` 1
     let values = Bin.runGet getPresets presets
     hClose h
     return values

getPresets = do
  empty <- Bin.isEmpty
  if empty
    then return []
    else do p  <- getAndDecodeTriple
            ps <- getPresets
            return (p:ps)

getAndDecodeTriple = do
  b1 <- Bin.getWord8
  b2 <- Bin.getWord8
  b3 <- Bin.getWord8
  return $ decode (b1,b2,b3)

我遇到的问题是解码一个 3 字节的块,因为我知道它是如何在 C++ 中编码的

这里是 C++ 编码

void SysexReader::sx_encode(int val, char* dest)
{
    char encode;
    
    // Encode Byte 1 (4 bits of payload)
    encode = 0x40 | ((val >> 12) & 0x000F);
    *dest++ = encode;
    
    // Encode Byte 2 (6 bits of payload)
    encode = (val >> 6) & 0x003F;
    *dest++ = encode;
    
    // Encode Byte 3 (6 bits of payload)
    encode = val & 0x003F;
    *dest = encode;
}

这是翻译成 Haskell 的 C++ 编码...

type Encoding a  = (a,a,a)
type PresetValue = Int16

encode :: Integral a => PresetValue -> Encoding a
encode val =
  let f = fromIntegral
  in (f $ enc1 val, f $ enc2 val, f $ enc3 val)
  where
    enc1 = or40 . and000F . (flip shiftR 12)
      where and000F = (0x000F .&.)
            or40    = (0x40 .|.)
    enc2 = enc3 . flip shiftR 6
    enc3 = (0x003F .&.)

我的解码尝试使用了我有编码程序并且我知道 PresetValue 只能在 (0,127) 范围内的事实

--    (3 Sysex Bytes) -> (Preset Value)   --
-------------------------------------------------------
decode :: Integral a => (a,a,a) -> Maybe PresetValue
decode encoded =
  case match of
    [value] -> Just value
    []      -> Nothing  --error "encode not surjective"
    many    -> error "encode not injective"
  where
    match = filter (\x -> encode x == encoded) [0..127]

很遗憾,我无法解码所有值,正如您从下面的 116 条目列表中看到的那样,很多地方都没有包含任何内容。

[Just 14,Just 84,Just 97,Just 117,Just 114,Just 117,Just 115,Just 32,Just 73,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Nothing,Nothing,Just 0,Nothing,Nothing,Nothing,Just 0,Nothing,Nothing,Just 0,Just 0,Nothing,Nothing,Just 0,Just 1,Nothing,Just 0,Nothing,Nothing,Just 0,Just 0,Just 0,Just 1,
Just 0,Just 0,Nothing,Just 5,Just 0,Just 1,Just 0,Just 0,Just 0,Nothing,Nothing,
Just 3,Just 2,Just 0,Just 0,Nothing,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Nothing,Nothing,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Just 0,Nothing]

我做错了什么?我觉得它一定是我用来表示传入文件中每个块的类型。或者也许我正在使用 fromIntegral 丢失信息。

我已经做了一段时间的开发人员,从来没有在这里发布过问题,并且一直在努力寻找答案,但我真的迷失了这个问题。谢谢。

【问题讨论】:

  • 在某处插入`mod` 256
  • 我刚试过。不工作;我得到了一堆什么都没有。为什么这行得通?这可能会让我找到答案。

标签: haskell


【解决方案1】:

使用openBinaryFile 代替openFile 可能会更好。这在这里应该没什么区别,因为我相信hGet 会忽略文件是否以文本或二进制模式打开,但这是一种很好的做法。

此外,最好使用Word16 代替您的Int16。 C 代码使用int,因此任何 16 位整数值都将是无符号的。同样,如果您真的只处理 [0..127] 范围内的预设,那也没关系,但这似乎是一种好习惯。

我可以看到您的代码没有任何明显错误,但是如果不访问输入文件,几乎不可能复制您的问题。我可能会建议使用decode 的更好实现:

decode :: (Word8, Word8, Word8) -> Maybe PresetValue
decode (a,b,c)
  |  0x40 <= a && a <= 0x4f
  && b <= 0x3f && c <= 0x3f
  = Just $ (fromIntegral a .&. 0xf) `shiftL` 12 .|. fromIntegral b `shiftL` 6 .|. fromIntegral c
decode _ = Nothing

它处理从 0 到 65535 的所有可能的编码预设值。如果您仍然在解码中获得 Nothing 值,则编码文件可能已损坏。

看起来第一个错误值位于偏移量 19,对应于字节 57-59 (0x39-0x41),或者占 32 字节标头,字节 89-91 (0x59-0x61)。在十六进制编辑器中打开文件并查看给您带来麻烦的偏移量的三个字节可能会有所帮助。

【讨论】:

  • 更好的实现(解码)是一种轻描淡写。我唯一改变的是添加一个 where f = fromIntegral 以使格式正确。事实证明,有些值确实远高于 127。
  • 上述解决方案中假设的字节序是否有可能与 C++ 实现中使用的字节序不同,它会影响结果吗?如果是这样,如何修改上述解决方案以考虑字节顺序?
  • 字节序不应该在这里起作用。 C++ 实现写出三个连续字节,包含从最高有效位开始的大小为 4、6 和 6 的位字段,Haskell 代码使用无字节序的getWord8 函数以相同的方式读回它。如果您是get-ting 直接多字节整数,则可能存在潜在的字节序问题,但getWord8 没有。
猜你喜欢
  • 1970-01-01
  • 2020-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多