wireform-thrift-0.1.0.0: src/Thrift/Transport.hs
{-# LANGUAGE BangPatterns #-}
{- | Thrift framed transport: 4-byte big-endian length prefix framing.
Many Thrift servers (e.g. TNonblockingServer, TThreadedSelectorServer)
require framed transport where each message is preceded by a 4-byte
big-endian length prefix.
-}
module Thrift.Transport (
frameMessage,
unframeMessage,
unframeMessages,
) where
import Data.Bits (shiftL, shiftR, (.&.), (.|.))
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Unsafe qualified as BSU
import Data.Word (Word32, Word8)
-- | Frame a message: 4-byte big-endian length prefix + payload.
frameMessage :: ByteString -> ByteString
frameMessage !payload =
let !len = BS.length payload
!b0 = fromIntegral ((len `shiftR` 24) .&. 0xFF) :: Word8
!b1 = fromIntegral ((len `shiftR` 16) .&. 0xFF) :: Word8
!b2 = fromIntegral ((len `shiftR` 8) .&. 0xFF) :: Word8
!b3 = fromIntegral (len .&. 0xFF) :: Word8
in BS.pack [b0, b1, b2, b3] <> payload
-- | Unframe: read 4-byte length, extract payload.
unframeMessage :: ByteString -> Either String ByteString
unframeMessage !bs
| BS.length bs < 4 = Left "Thrift.Transport: input too short for frame header"
| otherwise =
let !len = readBE32 bs 0
in if fromIntegral len + 4 > BS.length bs
then
Left $
"Thrift.Transport: frame claims "
++ show len
++ " bytes but only "
++ show (BS.length bs - 4)
++ " available"
else Right (BSU.unsafeTake (fromIntegral len) (BSU.unsafeDrop 4 bs))
-- | Unframe multiple messages from a stream.
unframeMessages :: ByteString -> Either String [ByteString]
unframeMessages !bs = go 0 []
where
!bsLen = BS.length bs
go !off !acc
| off >= bsLen = Right (reverse acc)
| off + 4 > bsLen = Left "Thrift.Transport: truncated frame header"
| otherwise =
let !len = fromIntegral (readBE32 bs off) :: Int
!payOff = off + 4
in if payOff + len > bsLen
then Left $ "Thrift.Transport: truncated frame payload"
else
let !payload = BSU.unsafeTake len (BSU.unsafeDrop payOff bs)
in go (payOff + len) (payload : acc)
readBE32 :: ByteString -> Int -> Word32
readBE32 !bs !off =
let !b0 = fromIntegral (BSU.unsafeIndex bs off) :: Word32
!b1 = fromIntegral (BSU.unsafeIndex bs (off + 1)) :: Word32
!b2 = fromIntegral (BSU.unsafeIndex bs (off + 2)) :: Word32
!b3 = fromIntegral (BSU.unsafeIndex bs (off + 3)) :: Word32
in (b0 `shiftL` 24) .|. (b1 `shiftL` 16) .|. (b2 `shiftL` 8) .|. b3
{-# INLINE readBE32 #-}