aws-transcribe-ws-0.0.1.0: src/AWS/Transcribe/EventStream.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{- |Event stream encoding provides bidirectional communication using messages between a client and a server.
Data frames sent to the Amazon Transcribe streaming service are encoded in this format. The response from Amazon Transcribe also uses this encoding.
Each message consists of two sections: the prelude and the data. The prelude consists of:
The total byte length of the message
The combined byte length of all of the headers
The data section consists of:
The headers
A payload
Each section ends with a 4-byte big-endian integer CRC checksum.
The message CRC checksum is for both the prelude section and the data section.
Amazon Transcribe uses CRC32 (often referred to as GZIP CRC32) to calculate both CRCs.
For more information about CRC32, see GZIP file format specification version 4.3
.
Total message overhead, including the prelude and both checksums, is 16 bytes.
documentation source: https://docs.aws.amazon.com/transcribe/latest/dg/event-stream.html
-}
module AWS.Transcribe.EventStream where
import Control.Lens (makeLenses, (^.))
import Data.Binary (Binary (get, put), getWord8)
import Data.Binary.Get (Get, getByteString, getInt16be, getInt32be, skip)
import Data.Binary.Put (putBuilder)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.Digest.CRC32 as CRC32
import Data.Int (Int16, Int32)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Word (Word8)
-- A message used in event stream encoding
data Message = MkMessage
{ _hContentType :: !Header
, _hEventType :: !Header
, _hMessageType :: !Header
, _payload :: !BS.ByteString
}
deriving (Show, Eq)
-- | Event stram encoding header
data Header = MkHeader
{ _hNameLength :: !Word8
, _hName :: !T.Text
, _hValueType :: !Word8
, _hValueStringLength :: !Int16
, _hValueString :: !T.Text
}
deriving (Show, Eq)
makeLenses ''Message
makeLenses ''Header
instance Binary Header where
put = putBuilder . headerBuilder
get = do
hnl <- get
hn <- getByteString (fromIntegral hnl)
hvt <- get
hvsl <- getInt16be
hvs <- getByteString (fromIntegral hvsl)
pure $ MkHeader hnl (T.decodeUtf8 hn) hvt hvsl (T.decodeUtf8 hvs)
instance Binary Message where
put msg = do
putBuilder totalMsg
putBuilder msgCrc
where
prelude = tbl' <> hbl'
tbl' = BS.int32BE $ tbl msg
hbl' = BS.int32BE hbl
preludeCrc = crcBuilder prelude
pb = BS.byteString (msg ^. payload)
totalMsg = prelude <> preludeCrc <> requestMessageHeaders <> pb
msgCrc = crcBuilder totalMsg
get = do
tbl' <- getInt32be
hbl' <- getInt32be
-- Skip crc check
skip 4
h1 <- get
h2 <- get
h3 <- get
let (h1', h2', h3') = classifyHeaders h2 h1 h3
pl <- getByteString (fromIntegral tbl' - fromIntegral hbl' - 16)
-- Skip crc check
pure $ MkMessage h1' h2' h3' pl
classifyHeaders :: Header -> Header -> Header -> (Header, Header, Header)
classifyHeaders h1 h2 h3
| h1 ^. hNameLength == 13 = classifyHeaders h2 h3 h1
| h2 ^. hName == "content-type" = (h2, h1, h3)
| otherwise = (h3, h1, h2)
-- The total byte length of a message
-- tbl: 16 Bytes overhead + headers length + payload length
tbl :: Message -> Int32
tbl msg = 16 + hbl + pbl msg
-- A message's headers byte length
hbl :: Int32
hbl = hContentTypeRequestBL + hEventTypeRequestBL + hMessageTypeRequestBL
-- A message's payload byte length
pbl :: Message -> Int32
pbl msg = fromIntegral (BS.length $ msg ^. payload)
-- A strict ByteString builder for `Header`
headerBuilder :: Header -> BS.Builder
headerBuilder (MkHeader nl n vt vsl vl) =
BS.word8 nl
<> BS.byteString (T.encodeUtf8 n)
<> BS.word8 vt
<> BS.int16BE vsl
<> BS.byteString (T.encodeUtf8 vl)
getHeader :: Get Header
getHeader = do
hnbl <- getWord8
hn <- getByteString (fromIntegral hnbl)
hvt <- getWord8
vsbl <- getInt16be
vs <- getByteString (fromIntegral vsbl)
pure $ MkHeader hnbl (T.decodeUtf8 hn) hvt vsbl (T.decodeUtf8 vs)
-- A CRC32 builder for strict ByteStrings
crcBuilder :: BS.Builder -> BS.Builder
crcBuilder = BS.word32BE . CRC32.crc32 . CRC32.digest . BL.toStrict . BS.toLazyByteString
-- The standard content-type request header
hContentTypeRequest :: Header
hContentTypeRequest = MkHeader 13 ":content-type" 7 24 "application/octet-stream"
-- Request content-type header byte length
hContentTypeRequestBL :: Int32
hContentTypeRequestBL = 41
-- The standard event-type request header
hEventTypeRequest :: Header
hEventTypeRequest = MkHeader 11 ":event-type" 7 10 "AudioEvent"
-- Request event-type header byte length
hEventTypeRequestBL :: Int32
hEventTypeRequestBL = 25
-- The standard message-type request header
hMessageTypeRequest :: Header
hMessageTypeRequest = MkHeader 13 ":message-type" 7 5 "event"
-- Request message-type header byte length
hMessageTypeRequestBL :: Int32
hMessageTypeRequestBL = 22
requestMessageHeaders :: BS.Builder
requestMessageHeaders =
headerBuilder hContentTypeRequest
<> headerBuilder hEventTypeRequest
<> headerBuilder hMessageTypeRequest
-- | Make a streaming message, using the default request headers
mkStreamingMessage :: BS.ByteString -> Message
mkStreamingMessage pl = MkMessage hContentTypeRequest hEventTypeRequest hMessageTypeRequest pl