Win32-dhcp-server-0.3.1: src/System/Win32/DHCP/DhcpStructure.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
module System.Win32.DHCP.DhcpStructure where
import Control.Monad (when)
import Import
-- |Function dictionary for objects used with the DHCP api.
-- * Ability to peek from a pointer to that object.
-- * Ability to properly free an object using Win32's rpcFreeMemory
-- created by the DHCP api.
-- * Ability to use the with* metaphor to temporarily poke an
-- object into C memory, call a continuation on it, and then
-- free the memory from Haskell's heap.
--
-- Extra features made possible by the typeclass
-- * Ability to turn any Storable instance into a DhpcStructure instance
-- by wrapping it into a 'storableDhcpStructure'.
-- * Ability to peek an array of DHCP structures into a list.
-- * Ability to poke a list of objects into contiguous memory, then
-- call a continuation on that block of memory.
data DhcpStructure a = DhcpStructure
{ peekDhcp :: Ptr a -> IO a
-- |Cleaning up memory is the responsibility of the client of this
-- library. Most out parameters return compound structures which need to
-- be recursively navigated, freeing all children, before freeing the main
-- pointer.
--
-- This function only frees child objects without freeing the pointer
-- itself. It's necessary because some structures contained inline
-- structures instead of the usual pointer. A separate 'freeDhcp' function
-- will call this one before freeing its supplied pointer.
, freeDhcpChildren :: (forall x. Ptr x -> IO ()) -> Ptr a -> IO ()
-- |Like `withDhcp`, but without any allocation or cleanup of memory.
-- The continuation is not automatically given a pointer because
-- the caller should already have it.
, withDhcp' :: forall r. a -> Ptr a -> IO r -> IO r
, sizeDhcp :: Int
}
freeDhcp :: DhcpStructure a -> (forall x. Ptr x -> IO ()) -> Ptr a -> IO ()
freeDhcp dict free ptr = do
freeDhcpChildren dict free ptr
free ptr
-- |Allocate memory for a structure, poke it into memory, apply
-- a function, and then clean up the memory.
withDhcp :: DhcpStructure a -> a -> (Ptr a -> IO r) -> IO r
withDhcp dict a f = allocaBytes (sizeDhcp dict) $ \ptr ->
withDhcp' dict a ptr $ f ptr
-- |Convert a DhcpStructure so that it can be used with a newtype
-- wrapper.
newtypeDhcpStructure :: (a -> nt) -> (nt -> a)
-> DhcpStructure a -> DhcpStructure nt
newtypeDhcpStructure wrap unwrap dict =
DhcpStructure peekNt freeNt withNt' (sizeDhcp dict)
where
peekNt ptr = wrap <$> peekDhcp dict (castPtr ptr)
freeNt :: (forall x. Ptr x -> IO ()) -> Ptr a -> IO ()
freeNt f ptr = freeDhcpChildren dict f (castPtr ptr)
withNt' a ptr f = withDhcp' dict (unwrap a) (castPtr ptr) f
-- |This is used in cases like 'CLIENT_UID' where we want to treat it like
-- a 'LengthArray' but individual elements of the array are simple values that
-- do not need to be freed individually. An example of this (and the only
-- place where it is used) is 'CLIENT_UID'. We reuse the 'Storable' instances
-- peek and poke functions.
storableDhcpStructure :: forall a. (Storable a) => DhcpStructure a
storableDhcpStructure = DhcpStructure
{ peekDhcp = peek
, freeDhcpChildren = \freeFunc ptr -> freeFunc ptr
, withDhcp' = withStorable'
, sizeDhcp = sizeOf (undefined :: a)
}
where
withStorable' x ptr f = poke ptr x >> f
data DhcpArray a = DhcpArray
{ peekDhcpArray :: Int -> Ptr a -> IO [a]
, freeDhcpArray :: (forall x. Ptr x -> IO ()) -> Int -> Ptr a -> IO ()
, withDhcpArray' :: forall r. [a] -> Ptr a -> IO r -> IO r
, dhcpStructure :: DhcpStructure a
}
-- |Allocate enough contiguous memory for each element. Recursively
-- free all memory once the supplied function returns.
-- The continuation receives a length argument. This is because
-- the length must be calculated in the course of execution, and will
-- likely be needed again by the caller.
withDhcpArray :: DhcpArray a -> [a] -> (Int -> Ptr a -> IO r) -> IO r
withDhcpArray dict elems f =
allocaBytes (stride * size) $ \ptr ->
-- `f` is meant to be called on the array as a whole; not individual elements.
-- It's supplied its pointer here because we want it called on position 0.
withDhcpArray' dict elems ptr $ f size ptr
where
size = length elems
stride = sizeDhcp . dhcpStructure $ dict
-- |This dictionary is a default set to "base" other versions on.
-- Scanning through the buffer happens dhcpSize bytes at a time. Memory
-- is freed by calling the freeing function on every element in the buffer.
baseDhcpArray :: DhcpStructure a -> DhcpArray a
baseDhcpArray s = DhcpArray
{ peekDhcpArray = basePeekArray s
, freeDhcpArray = baseFreeArray s
, withDhcpArray' = baseWithArray' s
, dhcpStructure = s
}
-- |This differs from `baseDhcpArray` in that the entire buffer
-- is freed with a single call to the freeing function.
basicDhcpArray :: DhcpStructure a -> DhcpArray a
basicDhcpArray dict = (baseDhcpArray dict)
{ freeDhcpArray = basicFreeArray dict
}
ptrDhcpArray :: DhcpStructure a -> DhcpArray a
ptrDhcpArray dict = (baseDhcpArray dict)
{ peekDhcpArray = ptrPeekArray dict
, freeDhcpArray = ptrFreeArray dict
, withDhcpArray' = ptrWithArray' dict
}
basePeekArray :: DhcpStructure a -> Int -> Ptr a -> IO [a]
basePeekArray dict len ptr0 = mapM (peekDhcp dict) ptrs
where
ptrs = take len . iterate (`plusPtr` sizeDhcp dict) $ ptr0
-- |Elements are arranged end to end in a buffer. The buffer is freed
-- at once after each element's children are freed.
baseFreeArray :: DhcpStructure a
-> (forall x. Ptr x -> IO ()) -> Int -> Ptr a -> IO ()
baseFreeArray dict freefunc len ptr
| len <= 0 = return ()
| otherwise = do
f (len - 1)
freefunc ptr
where
f 0 = freeDhcpChildren dict freefunc ptr
f n = do
freeDhcpChildren dict freefunc $ ptr `plusPtr` (n * sizeDhcp dict)
f (n - 1)
baseWithArray' :: DhcpStructure a -> [a] -> Ptr a -> IO r -> IO r
baseWithArray' _ [] _ f = f
baseWithArray' dict (e:es) ptr f =
-- We're not concerned with the individual element.
withDhcp' dict e ptr
$ baseWithArray' dict es (ptr `plusPtr` sizeDhcp dict) f
basicFreeArray :: DhcpStructure a -> (forall x. Ptr x -> IO ())
-> Int -> Ptr a -> IO ()
basicFreeArray dict freefunc _ ptr = freeDhcp dict freefunc ptr
ptrPeekArray :: DhcpStructure a -> Int -> Ptr a -> IO [a]
ptrPeekArray dict len ptr = mapM peekElement pptrs
where
--Each element is a pointer to the real data
pptrs = take len . iterate (`plusPtr` sizeOf nullPtr) $ castPtr ptr
peekElement pptr = peek pptr >>= peekDhcp dict
ptrFreeArray :: DhcpStructure a
-> (forall x. Ptr x -> IO ()) -> Int -> Ptr a -> IO ()
ptrFreeArray dict freefunc len ptr = do
mapM (freeDhcp dict freefunc `scrubbing_`) pptrs
-- Len may very well be 0 in which case there's really nothing to free.
when (len > 0) $ freefunc ptr
where
--Each element is a pointer to the real data
pptrs = take len . iterate (`plusPtr` sizeOf nullPtr) $ castPtr ptr
ptrWithArray' :: DhcpStructure a -> [a] -> Ptr a -> IO r -> IO r
ptrWithArray' _ [] _ f = f
ptrWithArray' dict (e:es) ptr f =
-- We're not concerned with the individual element.
withDhcp dict e $ \pe -> do
poke pptr pe
ptrWithArray' dict es (ptr `plusPtr` sizeOf nullPtr) f
where
--Each element is a pointer to the real data
pptr = castPtr ptr