---------------------------------------------------------------------------- -- | -- Module : WSJTX.UDP.EncodeQt -- Copyright : (c) Marc Fontaine 2017-2018 -- License : BSD3 -- -- Maintainer : Marc.Fontaine@gmx.de -- Stability : experimental -- Portability : GHC-only -- -- Generic Serializing and deserializng of WSJT-X UDP packages. -- WSJT-X uses QT the framework and proprietary QT binary -- format for serialization. -- Also see NetworkMessage.hpp in WSJTX sources. -- This module only supports the schema 2 protocol. {-# 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