{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
module WSJTX.UDP.EncodeQt
where
import Data.Word
import Data.Text as Text
import Data.Text.Encoding as Text
import Data.Time
import Data.ByteString as BS
import Data.ByteString.Lazy as BSL (fromStrict, toStrict)
import GHC.Generics
import Data.Binary.Get
import Data.Binary.Put (Put, putWord8, putByteString, runPut, putWord64be, putInt32be, putWord32be, putDoublebe)
import Data.Binary.Parser.Word8 (word8)
import Control.Monad
import WSJTX.UDP.NetworkMessage as NetworkMessage
class ToQt a where
toQt :: a -> Put
default toQt :: (Generic a, ToQt' (Rep a)) => a -> Put
toQt x = toQt' (from x)
class ToQt' f where
toQt' :: f p -> Put
instance ToQt Word32 where toQt = putWord32be
instance ToQt Int where toQt = putInt32be . fromIntegral
instance ToQt Word64 where toQt = putWord64be
instance ToQt Double where toQt = putDoublebe
instance ToQt Bool where
toQt True = putWord8 1
toQt False = putWord8 0
instance ToQt DiffTime where
toQt t = putWord32be $ fromIntegral (diffTimeToPicoseconds t `div` 1000000000)
instance ToQt Text where
toQt txt = do
let bs = Text.encodeUtf8 txt
putWord32be $ fromIntegral $ BS.length bs
putByteString bs
instance ToQt' V1 where toQt' _ = undefined
instance ToQt' U1 where toQt' U1 = return ()
instance (ToQt' f) => ToQt' (M1 i t f) where toQt' (M1 x) = toQt' x
instance (ToQt c) => ToQt' (K1 i c) where toQt' (K1 x) = toQt x
instance (ToQt' f, ToQt' g) => ToQt' (f :*: g)
where
toQt' (x :*: y) = toQt' x >> toQt' y
class FromQt' f where
fromQt' :: Get (f p)
instance FromQt' U1 where fromQt' = return U1
instance (FromQt' f) => FromQt' (M1 i t f) where fromQt' = M1 <$> fromQt'
instance (FromQt c) => FromQt' (K1 i c) where fromQt' = K1 <$> fromQt
instance (FromQt' f, FromQt' g) => FromQt' (f :*: g)
where
fromQt' = do
a <- fromQt'
b <- fromQt'
return (a :*: b)
class FromQt a where
fromQt :: Get a
default fromQt :: (Generic a, FromQt' (Rep a)) => Get a
fromQt = fmap to fromQt'
instance FromQt Text where
fromQt = do
len <- getWord32be
if len == 0xffffffff then return Text.empty
else do
bs <- getByteString $ fromIntegral len
return $ Text.decodeUtf8With (\_ _ -> Just '_') bs
instance FromQt Word32 where fromQt = getWord32be
instance FromQt Int where fromQt = fmap fromIntegral getInt32be
instance FromQt Word64 where fromQt = getWord64be
instance FromQt Double where fromQt = getDoublebe
instance FromQt Bool where
fromQt = do
f <- getWord8
if f ==0 then return False else return True
instance FromQt DiffTime where
fromQt = do
t <- getWord32be
return $ picosecondsToDiffTime (fromIntegral t * 1000000000)
parseUDPPackage :: BS.ByteString -> Package
parseUDPPackage bs
= case runGetOrFail package $ BSL.fromStrict bs of
Left _x -> OtherPackage $ BS.unpack bs
Right (_,_,res) -> res
where
package :: Get Package
package = do
qtMagicWord
schema <- getWord32be
when (schema /= 2) mzero
getWord32be >>= \case
0 -> pc PHeartbeat
1 -> pc PStatus
2 -> pc PDecode
3 -> pc PClear
4 -> pc PReply
5 -> pc PLogged
6 -> pc PClose
7 -> pc PReplay
8 -> pc PHaltTx
9 -> pc PFreeText
_ -> mzero
pc :: (Generic b1, FromQt' (Rep b1)) => (b1 -> Package) -> Get Package
pc constr = constr . to <$> fromQt'
qtMagicWord :: Get ()
qtMagicWord = do
word8 0xAD
word8 0xBC
word8 0xCB
word8 0xDA
packageToUDP :: Package -> BS.ByteString
packageToUDP p
= BSL.toStrict $ runPut package
where
package = case p of
PHeartbeat x -> pt 0 x
PStatus x -> pt 1 x
PDecode x -> pt 2 x
PClear x -> pt 3 x
PReply x -> pt 4 x
PLogged x -> pt 5 x
PClose x -> pt 6 x
PReplay x -> pt 7 x
PHaltTx x -> pt 8 x
PFreeText x -> pt 9 x
OtherPackage l -> putByteString $ BS.pack l
pt :: (Generic b1, ToQt' (Rep b1)) => Word32 -> b1 -> Put
pt tag x = do
qtMagicWord
putWord32be 2
putWord32be tag
toQt' $ from x
qtMagicWord :: Put
qtMagicWord = do
putWord8 0xAD
putWord8 0xBC
putWord8 0xCB
putWord8 0xDA