Incremental binary stream decoder, generalized from hit-network
This commit is contained in:
parent
de730cf573
commit
e72284e182
2 changed files with 113 additions and 1 deletions
109
src/Data/Binary/Local.hs
Normal file
109
src/Data/Binary/Local.hs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | This module provides incremental binary data decoding. The data stream
|
||||||
|
-- from which bytes are read is represented as a monadic action which returns a
|
||||||
|
-- strict ByteString. If the ByteString returned is empty, it means there is no
|
||||||
|
-- more input. The intention is that the ByteStrings returned are of constant
|
||||||
|
-- size, e.g. the lazy ByteString chunk size, or some network-specific chunk or
|
||||||
|
-- packet size.
|
||||||
|
module Data.Binary.Local
|
||||||
|
( DecodeFail (..)
|
||||||
|
, DecodeBuffer
|
||||||
|
, decodeIncremental
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Monad.Logger
|
||||||
|
import Data.Binary.Get
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Foldable (for_)
|
||||||
|
import Data.Int (Int64)
|
||||||
|
import Data.Monoid ((<>))
|
||||||
|
import Formatting
|
||||||
|
|
||||||
|
import qualified Data.ByteString as B (null)
|
||||||
|
import qualified Data.Text as T (pack)
|
||||||
|
import qualified Data.Text.Encoding as TE (decodeLatin1)
|
||||||
|
|
||||||
|
-- | Description of decoding error.
|
||||||
|
data DecodeFail = DecodeFail
|
||||||
|
{ -- | Unused bytes left in the buffer.
|
||||||
|
dfRemainder :: ByteString
|
||||||
|
-- | Number of bytes consumed before the error.
|
||||||
|
, dfConsumed :: Int64
|
||||||
|
-- | Error description.
|
||||||
|
, dfReason :: String
|
||||||
|
}
|
||||||
|
deriving Show
|
||||||
|
|
||||||
|
-- | A buffer used during the decoding process.
|
||||||
|
type DecodeBuffer = Maybe ByteString
|
||||||
|
|
||||||
|
-- | Read bytes from a stream until a single value is successfuly decoded from
|
||||||
|
-- it (or until decoding fails).
|
||||||
|
decodeIncremental
|
||||||
|
:: MonadLogger m
|
||||||
|
=> LogSource
|
||||||
|
-- ^ Log source name for logging via 'MonadLogger'. If you don't want to
|
||||||
|
-- specify a source, pass empty text here.
|
||||||
|
-> m ByteString
|
||||||
|
-- ^ The stream reader action. Reads a chunk of bytes and returns it. If
|
||||||
|
-- there are no more bytes to read, returns an empty bytestring.
|
||||||
|
-> DecodeBuffer
|
||||||
|
-- ^ Decoder buffer. If you're starting to decode, pass 'Nothing' here. If
|
||||||
|
-- you already decode a value and want to continue, pass here the buffer
|
||||||
|
-- you got from the previous call to this function.
|
||||||
|
-> Get a
|
||||||
|
-- ^ Decoder definition.
|
||||||
|
-> m (Either DecodeFail (DecodeBuffer, a))
|
||||||
|
-- ^ If decoding fails, 'Left' an error is returned. If it succeeds, you
|
||||||
|
-- get the buffer state and the decoded value. If you intend to decode a
|
||||||
|
-- value again from the same stream, pass the buffer to the next call of
|
||||||
|
-- this function.
|
||||||
|
decodeIncremental src readBytes buf decode = go buf $ runGetIncremental decode
|
||||||
|
--readBytes <- requestBody <$> waiRequest
|
||||||
|
where
|
||||||
|
go _ (Fail remains consumed err) = do
|
||||||
|
let df = DecodeFail
|
||||||
|
{ dfRemainder = remains
|
||||||
|
, dfConsumed = consumed
|
||||||
|
, dfReason = err
|
||||||
|
}
|
||||||
|
$logErrorS src $ T.pack $ show df
|
||||||
|
return $ Left df
|
||||||
|
go mbuffer (Done unused consumed result) = do
|
||||||
|
$logDebugS src $ sformat
|
||||||
|
("Decoding done, consumed " % int % " bytes") consumed
|
||||||
|
for_ mbuffer $ \ b -> $logWarnS src $
|
||||||
|
"Done decoding with nonempty buffer: " <> (TE.decodeLatin1 b)
|
||||||
|
let mbuffer' = if B.null unused
|
||||||
|
then mbuffer
|
||||||
|
else Just $ maybe unused (<> unused) mbuffer
|
||||||
|
return $ Right (mbuffer', result)
|
||||||
|
go mbuffer (Partial k) = do
|
||||||
|
chunk <- case mbuffer of
|
||||||
|
Nothing -> do
|
||||||
|
bytes <- readBytes
|
||||||
|
$logDebugS src $ "Received " <> TE.decodeLatin1 bytes
|
||||||
|
return bytes
|
||||||
|
Just b -> do
|
||||||
|
$logDebugS src $ "Reading buffer: " <> TE.decodeLatin1 b
|
||||||
|
return b
|
||||||
|
if B.null chunk
|
||||||
|
then go Nothing $ k Nothing
|
||||||
|
else go Nothing $ k $ Just chunk
|
|
@ -34,7 +34,8 @@ flag library-only
|
||||||
default: False
|
default: False
|
||||||
|
|
||||||
library
|
library
|
||||||
exposed-modules: Data.ByteString.Char8.Local
|
exposed-modules: Data.Binary.Local
|
||||||
|
Data.ByteString.Char8.Local
|
||||||
Data.ByteString.Local
|
Data.ByteString.Local
|
||||||
Data.Char.Local
|
Data.Char.Local
|
||||||
Data.List.Local
|
Data.List.Local
|
||||||
|
@ -91,6 +92,8 @@ library
|
||||||
build-depends: aeson
|
build-depends: aeson
|
||||||
, attoparsec
|
, attoparsec
|
||||||
, base
|
, base
|
||||||
|
-- for Data.Binary.Local
|
||||||
|
, binary
|
||||||
, base64-bytestring
|
, base64-bytestring
|
||||||
, blaze-html
|
, blaze-html
|
||||||
, byteable
|
, byteable
|
||||||
|
|
Loading…
Reference in a new issue