netw-0.1.0.0: src/Netw/Ancillary.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE DataKinds #-}
{-|
Module : Netw.Ancillary
Description : Ancillary data
This module contains ancillary data types. Ancillary data can be send and received via the sendmsg and recvmsg functions.
-}
module Netw.Ancillary where
import Netw.Internal.Ancillary
import qualified Netw.Internal.Cmsg as I
import Control.Monad.Primitive
import Control.Monad
import Control.Exception
import Data.Dynamic
import Data.Foldable
import Data.Function
import Data.Primitive hiding (indexByteArray, writeByteArray, readByteArray)
import Data.Primitive.ByteArray.Unaligned
import System.Posix.Types
import GHC.TypeLits
class (Typeable a, KnownNatPair (Anci a)) => Ancillary a where
-- | This type family enforces an unique mapping between cmsg_level and cmsg_type pair and the ancillary data type
type Anci a = (r :: (Nat, Nat)) | r -> a
-- ^ the order is (cmsg_level, cmsg_type)
-- | Get the size of the cmsg payload (just cmsg_data[])
cmsgDataSize :: a -> Int
-- | Write the payload onto the data section. Take the offset of the data section.
cmsgPokeData :: PrimMonad m => a -> MutableByteArray (PrimState m) -> Int -> m ()
-- ^ This function assumes enough space has been allocated
-- | Read the payload from the data section. Take the offset and size of the payload section.
cmsgPeekData :: ByteArray -> Int -> Int -> a
cmsgLevel :: forall a. Ancillary a => I.CmsghdrLevel
cmsgLevel = fromIntegral (fstNatVal @(Anci a))
cmsgType :: forall a. Ancillary a => I.CmsghdrType
cmsgType = fromIntegral (sndNatVal @(Anci a))
-- | Find the space a control message element occupies in cmsg buffer given the size of its payload section.
cmsgSpace :: Ancillary a => a -> Int
cmsgSpace = I.cmsgSpace . cmsgDataSize
-- | Read a control message element, assuming type a. Return Nothing if a is not the correct type.
peekAncillary :: forall a. Ancillary a => ByteArray -> Int -> Maybe a
peekAncillary cmsgs hdroffs = I.peekCmsg cmsgs hdroffs reader
where reader l t b o s
| l == cmsgLevel @a && t == cmsgType @a =
Just (cmsgPeekData b o s)
| otherwise = Nothing
-- | Store a control message element at offset in buffer
pokeAncillary :: forall a m. (Ancillary a, PrimMonad m) => MutableByteArray (PrimState m) -> Int -> a -> m ()
pokeAncillary cmsgs hdroffs andat = I.pokeCmsg cmsgs hdroffs l t (cmsgDataSize andat) (cmsgPokeData andat)
where l = cmsgLevel @a
t = cmsgType @a
-- ^ Warning: The buffer must be big enough to store the control message element
-- | This data type holds an abitrary ancillary data type.
data AncillaryData = AncillaryData
{ ancillarySpace :: !Int
, ancillaryData :: !(forall a. Ancillary a => Maybe a)
, ancillaryWrite :: !(forall m. PrimMonad m => MutableByteArray (PrimState m) -> Int -> m ())
}
mkAncillaryData :: Ancillary a => a -> AncillaryData
mkAncillaryData a = AncillaryData { ancillarySpace = cmsgSpace a
, ancillaryData = fromDynamic (toDyn a)
, ancillaryWrite = \ buffer offset -> pokeAncillary buffer offset a
}
-- | ancillaryWrite but without the impredicative type
writeAncillary :: PrimMonad m => AncillaryData -> MutableByteArray (PrimState m) -> Int -> m ()
writeAncillary a = ancillaryWrite a
-- | ancillaryData but without the impredicative type
recoverAncillary :: Ancillary a => AncillaryData -> Maybe a
recoverAncillary a = ancillaryData a
{-# INLINE encodeAncillaryData #-}
-- | Encode a list of control message elements
encodeAncillaryData :: [AncillaryData] -> ByteArray
encodeAncillaryData cmsgs = runByteArray $ do
buffer <- newByteArray size
zipWithM_ (`writeAncillary` buffer) cmsgs offsets
return buffer
where (offsets, size) = (\ a -> (init a, last a)) (scanl (+) 0 (map ancillarySpace cmsgs))
-- ^ Used internally
{-# INLINE decodeAncillaryData #-}
-- | Decode a buffer of control message elements. NOTE: The bytearray must be shrunk to `msg_controllen` bytes before being passed to this function.
decodeAncillaryData :: ByteArray -> [AncillaryData]
decodeAncillaryData cmsgs = fix (\ as offs ->
if offs < size then
let space = I.peekCmsg cmsgs offs (\ _ _ _ _ -> I.cmsgSpace)
andat = AncillaryData { ancillarySpace = space
, ancillaryData = peekAncillary cmsgs offs
, ancillaryWrite = \ dest destOffs -> copyByteArray dest destOffs cmsgs offs space
}
in andat:as (offs + space)
else []) 0
where size = sizeofByteArray cmsgs
-- ^ Used internally. Every AncillaryData element holds a reference to the bytearray and keeps it alive.
-- | Transfer file descriptors between sockets
newtype ScmRights = ScmRights [Fd]
instance Ancillary ScmRights where
type Anci ScmRights = UNIX_CONTROL_MESSAGE_RIGHTS
cmsgDataSize (ScmRights fds) = sizeOfType @Fd * length fds
cmsgPokeData (ScmRights fds) buffer initOffs = void (foldlM (\ offs fd -> writeUnalignedByteArray buffer offs fd >> return (offs + sizeOfType @Fd)) initOffs fds)
cmsgPeekData buffer o s = assert (s `mod` sizeOfType @Fd == 0) $ ScmRights $ fix (\ as offs size ->
if size > 0 then
indexUnalignedByteArray buffer offs:as (offs + sizeOfType @Fd) (size - sizeOfType @Fd)
else
[]) o s