-- | The module is implementing the LINX signal concept, which is the -- user level payload exchanged between two services. module Network.Linx.Gateway.Signal ( Signal (..) , SignalSelector (..) , SigNo (..) , PayloadSize (..) , encode , decode ) where import Control.Applicative ((<$>), (<*>)) import Data.Binary import Data.Binary.Get (getLazyByteString) import Data.Binary.Put (putLazyByteString) import qualified Data.ByteString.Lazy as LBS import Network.Linx.Gateway.Types import Network.Linx.Gateway.BinaryInt32 import Network.Linx.Gateway.BinaryList -- | Type class to determine the size of a signal. class PayloadSize a where payloadSize :: a -> Length -- | A signal - user lever payload data - is coded into three -- different fields in the gateway protocol: Signal length in bytes, -- including signal number. Signal number. Array of signal data. Even -- in the case of 'NoSignal' the binary encoding is eight bytes. data Signal = -- | A full bodied signal with number and data. Signal { sigNo :: !SigNo , sigData :: !LBS.ByteString } -- | A signal data only is carrying a signal number, but no data. | NumericSignal { sigNo :: !SigNo } -- | An 'empty' signal. | NoSignal deriving (Eq, Show) -- | A signal selector is used to filter which signals to expect when -- calling receive. It can either be one of two specific filters - -- AnySignal or Cancel - or a generic filter. data SignalSelector = -- | Accept any kind of signal. AnySignal -- | Cancel the last receive. | Cancel -- | Accept any of the specified signals. The list must not be -- empty. | Sel { selection :: ![SigNo] } deriving (Eq, Show) -- | PayloadSize instances. instance PayloadSize Signal where payloadSize NoSignal = Length 8 payloadSize NumericSignal {} = Length 8 payloadSize sig@Signal {} = let len = LBS.length $ sigData sig in toLength $ 8 + len instance PayloadSize SignalSelector where payloadSize AnySignal = Length 8 payloadSize Cancel = Length 8 payloadSize sel@Sel {} = let len = length $ selection sel in toLength $ 8 + len * 4 -- | Binary instances. instance Binary Signal where get = do len <- asInt <$> get case len of 0 -> getInt32 >> return NoSignal 4 -> NumericSignal <$> get _ -> Signal <$> get <*> getLazyByteString (len - 4) put NoSignal = putInt32 0 >> putInt32 0 put (NumericSignal sigNo') = putInt32 4 >> put sigNo' put (Signal sigNo' sigData') = let len = toLength $ LBS.length sigData' + 4 in put len >> put sigNo' >> putLazyByteString sigData' instance Binary SignalSelector where get = do len <- asInt <$> get case len :: Int32 of 0 -> getInt32 >> return Cancel 1 -> getInt32 >> return AnySignal _ -> get >>= \n -> Sel <$> getList n put AnySignal = putInt32 1 >> putInt32 0 put Cancel = putInt32 0 >> putInt32 0 put sel@Sel {} | selection sel == [] = error "Cannot encode empty selector list" | otherwise = do let len = length (selection sel) len' = toLength (len + 1) len'' = toLength len put (len') >> put len'' >> putList (selection sel)