packer 0.1.1 → 0.1.3
raw patch · 7 files changed
+158/−43 lines, 7 files
Files
- Data/Packer.hs +24/−3
- Data/Packer/Endian.hs +27/−5
- Data/Packer/IO.hs +3/−3
- Data/Packer/Internal.hs +86/−25
- Data/Packer/Unsafe.hs +10/−6
- Tests/Tests.hs +7/−0
- packer.cabal +1/−1
Data/Packer.hs view
@@ -18,11 +18,13 @@ , Unpacking , OutOfBoundUnpacking(..) , OutOfBoundPacking(..)+ , IsolationNotFullyConsumed(..) , Hole -- * Main methods , runUnpacking , tryUnpacking , runPacking+ , runPackingRes -- * Unpacking functions , unpackSkip , unpackSetPosition@@ -43,6 +45,7 @@ , getRemaining , getRemainingCopy , getStorable+ , isolate -- * Packing functions , packGetPosition , putWord8@@ -194,11 +197,25 @@ else return $ Just i -- | Get an arbitrary type with the Storable class constraint.+--+-- The Storage method for sizeOf need to be constant size related+-- to the type. It cannot use any fields to define its size.+--+-- The sizeOf method is always going to be called with undefined,+-- so make sure sizeOf doesn't need the value of the type. getStorable :: Storable a => Unpacking a getStorable = get_ undefined where get_ :: Storable a => a -> Unpacking a get_ undefA = unpackCheckAct (sizeOf undefA) (peek . castPtr) +-- | Isolate N bytes from the unpacking, and create an isolated+-- context where only those N bytes are available.+--+-- If the sub unpacker doesn't consume all the bytes available,+-- this function will raises an exception+isolate :: Int -> Unpacking a -> Unpacking a+isolate n subUnpacker = unpackIsolate n subUnpacker+ -- | Put a Word8 putWord8 :: Word8 -> Packing () putWord8 w = packCheckAct 1 (\ptr -> poke (castPtr ptr) w)@@ -285,7 +302,7 @@ putBytes bs = packCheckAct len $ \ptr -> withForeignPtr fptr $ \ptr2 ->- B.memcpy ptr (ptr2 `plusPtr` o) len+ B.memcpy ptr (ptr2 `plusPtr` o) (fromIntegral len) where (fptr,o,len) = B.toForeignPtr bs -- | Put an arbitrary type with the Storable class constraint.@@ -301,5 +318,9 @@ tryUnpacking action bs = unsafeDoIO $ tryUnpackingIO bs action -- | Run packing with a buffer created internally with a monadic action and return the bytestring-runPacking :: Int -> Packing () -> ByteString-runPacking sz action = unsafeDoIO $ runPackingIO sz action+runPackingRes :: Int -> Packing a -> (a, ByteString)+runPackingRes sz action = unsafeDoIO $ runPackingIO sz action++-- | Run packing with a buffer created internally with a monadic action and return the bytestring+runPacking :: Int -> Packing a -> ByteString+runPacking sz action = snd $ runPackingRes sz action
Data/Packer/Endian.hs view
@@ -22,6 +22,20 @@ import Data.Bits import Data.Word +#if MIN_VERSION_base(4,7,0)+-- | swap endianness on a Word16+swap16 :: Word16 -> Word16+swap16 = byteSwap16++-- | Transform a 32 bit value bytes from a.b.c.d to d.c.b.a+swap32 :: Word32 -> Word32+swap32 = byteSwap32++-- | Transform a 64 bit value bytes from a.b.c.d.e.f.g.h to h.g.f.e.d.c.b.a+swap64 :: Word64 -> Word64+swap64 = byteSwap64++#else #if BITS_IS_OLD shr :: Bits a => a -> Int -> a shr = shiftR@@ -44,11 +58,6 @@ .|. ((w `shr` 40) .&. 0xff00) .|. ((w .&. 0xff00) `shl` 40) .|. ((w `shr` 24) .&. 0xff0000) .|. ((w .&. 0xff0000) `shl` 24) .|. ((w `shr` 8) .&. 0xff000000) .|. ((w .&. 0xff000000) `shl` 8)-{-- .|. ((w `shr` 48) .&. 0xff00) .|. ((w .&. 0xff00) `shl` 48)- .|. ((w `shr` 40) .&. 0xff0000) .|. ((w .&. 0xff0000) `shl` 40)- .|. ((w `shr` 32) .&. 0xff000000) .|. ((w .&. 0xff000000) `shl` 32)--} -- | swap endianness on a Word32 swap32 :: Word32 -> Word32@@ -59,37 +68,50 @@ -- | swap endianness on a Word16 swap16 :: Word16 -> Word16 swap16 w = (w `shr` 8) .|. (w `shl` 8)+#endif #ifdef CPU_BIG_ENDIAN+-- | 16 bit big endian to host endian {-# INLINE be16Host #-} be16Host :: Word16 -> Word16 be16Host = id+-- | 32 bit big endian to host endian {-# INLINE be32Host #-} be32Host :: Word32 -> Word32 be32Host = id+-- | 64 bit big endian to host endian {-# INLINE be64Host #-} be64Host :: Word64 -> Word64 be64Host = id+-- | 16 bit little endian to host endian le16Host :: Word16 -> Word16 le16Host w = swap16 w+-- | 32 bit little endian to host endian le32Host :: Word32 -> Word32 le32Host w = swap32 w+-- | 64 bit little endian to host endian le64Host :: Word64 -> Word64 le64Host w = swap64 w #else+-- | 16 bit little endian to host endian {-# INLINE le16Host #-} le16Host :: Word16 -> Word16 le16Host = id+-- | 32 bit little endian to host endian {-# INLINE le32Host #-} le32Host :: Word32 -> Word32 le32Host = id+-- | 64 bit little endian to host endian {-# INLINE le64Host #-} le64Host :: Word64 -> Word64 le64Host = id+-- | 16 bit big endian to host endian be16Host :: Word16 -> Word16 be16Host w = swap16 w+-- | 32 bit big endian to host endian be32Host :: Word32 -> Word32 be32Host w = swap32 w+-- | 64 bit big endian to host endian be64Host :: Word64 -> Word64 be64Host w = swap64 w #endif
Data/Packer/IO.hs view
@@ -28,10 +28,10 @@ tryUnpackingIO bs action = E.try $ runUnpackingIO bs action -- | Run packing with a buffer created internally with a monadic action and return the bytestring-runPackingIO :: Int -> Packing () -> IO ByteString+runPackingIO :: Int -> Packing a -> IO (a, ByteString) runPackingIO sz action = createUptoN sz $ \ptr -> runPackingAt ptr sz action where -- copy of bytestring createUptoN as it's only been added 2012-09 createUptoN l f = do fp <- B.mallocByteString l- l' <- withForeignPtr fp $ \p -> f p- return $! B.PS fp 0 l'+ (a, l') <- withForeignPtr fp $ \p -> f p+ return $! (,) a $! B.PS fp 0 l'
Data/Packer/Internal.hs view
@@ -15,18 +15,22 @@ , Hole , Unpacking(..) , Memory(..)- , PackSt(..)+ -- * exceptions , OutOfBoundUnpacking(..) , OutOfBoundPacking(..) , HoleInPacking(..)+ , IsolationNotFullyConsumed(..)+ -- * unpack methods , unpackUnsafeActRef , unpackCheckActRef , unpackUnsafeAct , unpackCheckAct+ , unpackIsolate , unpackLookahead , unpackSetPosition , unpackGetPosition , unpackGetNbRemaining+ -- * pack methods , packCheckAct , packHole , packGetPosition@@ -37,21 +41,50 @@ import Foreign.ForeignPtr import Data.Data import Data.Word-import Control.Exception (Exception, throw, throwIO)-import Control.Monad.State+import Control.Exception (Exception, throwIO)+import Control.Monad.Trans import Control.Applicative (Applicative(..), (<$>), (<*>))+import Control.Concurrent.MVar+import Control.Monad (when) -- | Represent a memory block with a ptr as beginning data Memory = Memory {-# UNPACK #-} !(Ptr Word8) {-# UNPACK #-} !Int --- | Packing state-data PackSt = PackSt (Ptr Word8) !Int !Memory- -- | Packing monad-newtype Packing a = Packing { runPacking_ :: StateT PackSt IO a }- deriving (Functor,Applicative,Monad,MonadIO)+newtype Packing a = Packing { runPacking_ :: (Ptr Word8, MVar Int) -> Memory -> IO (a, Memory) } +instance Monad Packing where+ return = returnPacking+ (>>=) = bindPacking++instance MonadIO Packing where+ liftIO f = Packing $ \_ st -> f >>= \a -> return (a,st)++instance Functor Packing where+ fmap = fmapPacking++instance Applicative Packing where+ pure = returnPacking+ (<*>) = apPacking++bindPacking m1 m2 = Packing $ \cst st -> do+ (a, st2) <- runPacking_ m1 cst st+ runPacking_ (m2 a) cst st2+{-# INLINE bindPacking #-}++fmapPacking :: (a -> b) -> Packing a -> Packing b+fmapPacking f m = Packing $ \cst st -> runPacking_ m cst st >>= \(a, st2) -> return (f a, st2)+{-# INLINE fmapPacking #-}++returnPacking :: a -> Packing a+returnPacking a = Packing $ \_ st -> return (a,st)+{-# INLINE [0] returnPacking #-}++apPacking :: Packing (a -> b) -> Packing a -> Packing b+apPacking fm m = fm >>= \p -> m >>= \r2 -> return (p r2)+{-# INLINE [0] apPacking #-}+ -- | Unpacking monad newtype Unpacking a = Unpacking { runUnpacking_ :: (ForeignPtr Word8, Memory) -> Memory -> IO (a, Memory) } @@ -59,6 +92,9 @@ return = returnUnpacking (>>=) = bindUnpacking +instance MonadIO Unpacking where+ liftIO f = Unpacking $ \_ st -> f >>= \a -> return (a,st)+ instance Functor Unpacking where fmap = fmapUnpacking @@ -97,25 +133,52 @@ Int -- number of bytes requested deriving (Show,Eq,Data,Typeable) +-- | Exception when isolate doesn't consume all the bytes passed in the sub unpacker+data IsolationNotFullyConsumed = IsolationNotFullyConsumed Int -- number of bytes isolated+ Int -- number of bytes not consumed+ deriving (Show,Eq,Data,Typeable)+ instance Exception OutOfBoundPacking instance Exception HoleInPacking instance Exception OutOfBoundUnpacking+instance Exception IsolationNotFullyConsumed -unpackUnsafeActRef :: Int -> (ForeignPtr Word8 -> Ptr Word8 -> IO a) -> Unpacking a+-- | run an action to transform a number of bytes into a 'a'+-- and increment the pointer by number of bytes.+unpackUnsafeActRef :: Int -- ^ number of bytes+ -> (ForeignPtr Word8 -> Ptr Word8 -> IO a)+ -> Unpacking a unpackUnsafeActRef n act = Unpacking $ \(fptr, iniBlock) st@(Memory ptr sz) -> do r <- act fptr ptr return (r, Memory (ptr `plusPtr` n) (sz - n)) -unpackCheckActRef :: Int -> (ForeignPtr Word8 -> Ptr Word8 -> IO a) -> Unpacking a+-- | similar 'unpackUnsafeActRef' but does boundary checking.+unpackCheckActRef :: Int+ -> (ForeignPtr Word8 -> Ptr Word8 -> IO a)+ -> Unpacking a unpackCheckActRef n act = Unpacking $ \(fptr, iniBlock@(Memory iniPtr _)) (Memory ptr sz) -> do when (sz < n) (throwIO $ OutOfBoundUnpacking (ptr `minusPtr` iniPtr) n) r <- act fptr ptr return (r, Memory (ptr `plusPtr` n) (sz - n)) {-# INLINE [0] unpackCheckActRef #-} +-- | Isolate a number of bytes to run an unpacking operation.+--+-- If the unpacking doesn't consume all the bytes, an exception is raised.+unpackIsolate :: Int+ -> Unpacking a+ -> Unpacking a+unpackIsolate n sub = Unpacking $ \(fptr, iniBlock@(Memory iniPtr _)) (Memory ptr sz) -> do+ when (sz < n) (throwIO $ OutOfBoundUnpacking (ptr `minusPtr` iniPtr) n)+ (r, Memory newPtr subLeft) <- (runUnpacking_ sub) (fptr,iniBlock) (Memory ptr n)+ when (subLeft > 0) $ (throwIO $ IsolationNotFullyConsumed n subLeft)+ return (r, Memory newPtr (sz - n))++-- | Similar to unpackUnsafeActRef except that it throw the foreign ptr. unpackUnsafeAct :: Int -> (Ptr Word8 -> IO a) -> Unpacking a unpackUnsafeAct n act = unpackUnsafeActRef n (\_ -> act) +-- | Similar to unpackCheckActRef except that it throw the foreign ptr. unpackCheckAct :: Int -> (Ptr Word8 -> IO a) -> Unpacking a unpackCheckAct n act = unpackCheckActRef n (\_ -> act) {-# INLINE [0] unpackCheckAct #-}@@ -132,9 +195,9 @@ unpackGetPosition = Unpacking $ \(_, (Memory iniPtr _)) st@(Memory ptr _) -> return (ptr `minusPtr` iniPtr, st) +-- | Return the number of remaining bytes unpackGetNbRemaining :: Unpacking Int-unpackGetNbRemaining = Unpacking $- \_ st@(Memory _ sz) -> return (sz,st)+unpackGetNbRemaining = Unpacking $ \_ st@(Memory _ sz) -> return (sz,st) -- | Allow to look into the memory. -- This is inherently unsafe@@ -143,23 +206,21 @@ unpackLookahead f = Unpacking $ \_ st@(Memory ptr sz) -> f ptr sz >>= \a -> return (a, st) -withPackMemory :: Int -> (Ptr Word8 -> IO a) -> StateT PackSt IO a-withPackMemory n act = do- (PackSt iPos holes (Memory ptr sz)) <- get- when (sz < n) (lift $ throw $ OutOfBoundPacking sz n)- r <- lift (act ptr)- put $ PackSt iPos holes (Memory (ptr `plusPtr` n) (sz - n))- return r+-- | run a pack action on the internal packed buffer.+packCheckAct :: Int -> (Ptr Word8 -> IO a) -> Packing a+packCheckAct n act = Packing $ \_ (Memory ptr sz) -> do+ when (sz < n) (throwIO $ OutOfBoundPacking sz n)+ r <- act ptr+ return (r, Memory (ptr `plusPtr` n) (sz - n))+{-# INLINE [0] packCheckAct #-} +-- | modify holes modifyHoles :: (Int -> Int) -> Packing ()-modifyHoles f = Packing $ modify (\(PackSt iPos holes mem) -> PackSt iPos (f holes) mem)--packCheckAct :: Int -> (Ptr Word8 -> IO a) -> Packing a-packCheckAct n act = Packing (withPackMemory n act)+modifyHoles f = Packing $ \(_, holesMVar) mem -> modifyMVar_ holesMVar (\v -> return $! f v) >> return ((), mem) -- | Get the position in the memory block. packGetPosition :: Packing Int-packGetPosition = Packing $ gets (\(PackSt iniPtr _ (Memory ptr _)) -> ptr `minusPtr` iniPtr)+packGetPosition = Packing $ \(iniPtr, _) mem@(Memory ptr _) -> return (ptr `minusPtr` iniPtr, mem) -- | A Hole represent something that need to be filled -- later, for example a CRC, a prefixed size, etc.@@ -179,4 +240,4 @@ -- -- TODO: user can use one hole many times leading to wrong counting. fillHole :: Hole a -> a -> Packing ()-fillHole (Hole closure) a = modifyHoles (\i -> i - 1) >> Packing (lift $ closure a)+fillHole (Hole closure) a = modifyHoles (\i -> i - 1) >> liftIO (closure a)
Data/Packer/Unsafe.hs view
@@ -20,19 +20,23 @@ import Foreign.ForeignPtr import Control.Monad.State import Control.Exception+import Control.Concurrent.MVar (takeMVar, newMVar) -- | Run packing on an arbitrary buffer with a size. -- -- This is available, for example to run on mmap typed memory, and this is highly unsafe, -- as the user need to make sure the pointer and size passed to this function are correct.-runPackingAt :: Ptr Word8 -- ^ Pointer to the beginning of the memory- -> Int -- ^ Size of the memory- -> Packing () -- ^ Packing action- -> IO Int -- ^ Number of bytes filled+runPackingAt :: Ptr Word8 -- ^ Pointer to the beginning of the memory+ -> Int -- ^ Size of the memory+ -> Packing a -- ^ Packing action+ -> IO (a, Int) -- ^ Number of bytes filled runPackingAt ptr sz action = do- (PackSt _ holes (Memory _ leftSz)) <- execStateT (runPacking_ action) (PackSt ptr 0 $ Memory ptr sz)+ mvar <- newMVar 0+ (a, (Memory _ leftSz)) <- (runPacking_ action) (ptr,mvar) (Memory ptr sz)+ --(PackSt _ holes (Memory _ leftSz)) <- execStateT (runPacking_ action) (PackSt ptr 0 $ Memory ptr sz)+ holes <- takeMVar mvar when (holes > 0) (throwIO $ HoleInPacking holes)- return $ sz - leftSz+ return (a, sz - leftSz) -- | Run unpacking on an arbitrary buffer with a size. --
Tests/Tests.hs view
@@ -131,6 +131,13 @@ (runUnpacking (unpackSetPosition 2) (B.singleton 1))) , testCase "unpacking set pos after" (assertException "unpacking position" (\(OutOfBoundUnpacking _ _) -> Just ()) (runUnpacking (unpackSetPosition (-1)) (B.singleton 1)))+ , testCase "unpacking isolate" (runUnpacking (isolate 2 (getBytes 2) >>= \i -> getWord8 >>= \r -> return (i,r)) (B.pack [1,2,3]) @=? (B.pack [1,2], 3))+ , testCase "unpacking isolate out of bounds" $+ assertException "unpacking isolate" (\(OutOfBoundUnpacking _ _) -> Just ())+ (runUnpacking (isolate 2 (getBytes 3)) (B.pack [1,2,3]))+ , testCase "unpacking isolate not consumed" $+ assertException "unpacking isolate" (\(IsolationNotFullyConsumed _ _) -> Just ())+ (runUnpacking (isolate 3 (getBytes 2)) (B.pack [1,2,3])) ] , testGroup "endianness cases" $ concatMap toEndianCase endiannessCases , testProperty "unpacking.packing=id" (\ds -> unpackDataStream ds (packDataStream ds) == ds)
packer.cabal view
@@ -1,5 +1,5 @@ Name: packer-Version: 0.1.1+Version: 0.1.3 Description: Fast byte serializer and unserializer License: BSD3 License-file: LICENSE