packages feed

automotive-cse 0.0.1.1 → 0.0.2.0

raw patch · 6 files changed

+347/−153 lines, 6 filesdep +QuickCheckdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: QuickCheck

Dependency ranges changed: base

API changes (from Hackage documentation)

- Codec.Automotive.CSE: KeyAuthUse :: ByteString -> KeyAuthUse k
- Codec.Automotive.CSE: M1 :: ByteString -> M1
- Codec.Automotive.CSE: M2 :: ByteString -> M2
- Codec.Automotive.CSE: M3 :: ByteString -> M3
- Codec.Automotive.CSE: M4 :: ByteString -> M4
- Codec.Automotive.CSE: M5 :: ByteString -> M5
- Codec.Automotive.CSE: newtype KeyAuthUse k
- Codec.Automotive.CSE: newtype M1
- Codec.Automotive.CSE: newtype M2
- Codec.Automotive.CSE: newtype M3
- Codec.Automotive.CSE: newtype M4
- Codec.Automotive.CSE: newtype M5
+ Codec.Automotive.CSE: data Enc
+ Codec.Automotive.CSE: data KeyAuthUse k
+ Codec.Automotive.CSE: data M1
+ Codec.Automotive.CSE: data M2
+ Codec.Automotive.CSE: data M3
+ Codec.Automotive.CSE: data M4
+ Codec.Automotive.CSE: data M5
+ Codec.Automotive.CSE: data Mac
+ Codec.Automotive.CSE: data UID
+ Codec.Automotive.CSE: data UpdateC c
+ Codec.Automotive.CSE: extractM1 :: M1 -> (UID, Word8, Word8)
+ Codec.Automotive.CSE: extractM4 :: K3 -> M4 -> ((UID, Word8, Word8), Word32)
+ Codec.Automotive.CSE: extractM4' :: K3 -> M4 -> (M1, Word32)
+ Codec.Automotive.CSE: instance GHC.Classes.Eq Codec.Automotive.CSE.UID
+ Codec.Automotive.CSE: instance GHC.Show.Show (Codec.Automotive.CSE.Derived k c)
+ Codec.Automotive.CSE: instance GHC.Show.Show (Codec.Automotive.CSE.UpdateC c)
+ Codec.Automotive.CSE: instance GHC.Show.Show Codec.Automotive.CSE.M1
+ Codec.Automotive.CSE: instance GHC.Show.Show Codec.Automotive.CSE.M2
+ Codec.Automotive.CSE: instance GHC.Show.Show Codec.Automotive.CSE.M3
+ Codec.Automotive.CSE: instance GHC.Show.Show Codec.Automotive.CSE.M4
+ Codec.Automotive.CSE: instance GHC.Show.Show Codec.Automotive.CSE.M5
+ Codec.Automotive.CSE: instance GHC.Show.Show Codec.Automotive.CSE.UID
+ Codec.Automotive.CSE: keyUpdateEncC :: UpdateC Enc
+ Codec.Automotive.CSE: keyUpdateMacC :: UpdateC Mac
+ Codec.Automotive.CSE: makeKeyAuthUse :: MonadPlus m => ByteString -> m (KeyAuthUse k)
+ Codec.Automotive.CSE: makeM4' :: K3 -> M1 -> Word32 -> M4
+ Codec.Automotive.CSE: makeUID :: MonadPlus m => ByteString -> m UID
+ Codec.Automotive.CSE: type K1' = Derived Auth Enc
+ Codec.Automotive.CSE: type K2' = Derived Auth Mac
+ Codec.Automotive.CSE: type K3' = Derived NotAuth Enc
+ Codec.Automotive.CSE: type K4' = Derived NotAuth Mac
+ Codec.Automotive.CSE: unDerived :: Derived k c -> ByteString
+ Codec.Automotive.CSE: unKeyAuthUse :: KeyAuthUse k -> ByteString
+ Codec.Automotive.CSE: unM1 :: M1 -> ByteString
+ Codec.Automotive.CSE: unM2 :: M2 -> ByteString
+ Codec.Automotive.CSE: unM3 :: M3 -> ByteString
+ Codec.Automotive.CSE: unM4 :: M4 -> ByteString
+ Codec.Automotive.CSE: unM5 :: M5 -> ByteString
+ Codec.Automotive.CSE: unUID :: UID -> ByteString
- Codec.Automotive.CSE: derivedCipher :: Derived k c -> Either CryptoError (DerivedCipher k c)
+ Codec.Automotive.CSE: derivedCipher :: Derived k c -> DerivedCipher k c
- Codec.Automotive.CSE: makeM1 :: ByteString -> Word8 -> Word8 -> M1
+ Codec.Automotive.CSE: makeM1 :: UID -> Word8 -> Word8 -> M1
- Codec.Automotive.CSE: makeM4 :: ByteString -> Word8 -> Word8 -> K3 -> Word32 -> M4
+ Codec.Automotive.CSE: makeM4 :: K3 -> UID -> Word8 -> Word8 -> Word32 -> M4

Files

automotive-cse.cabal view
@@ -1,6 +1,6 @@  name:                automotive-cse-version:             0.0.1.1+version:             0.0.2.0 synopsis:            Automotive CSE emulation description:         This package includes Cryptography Security Engine (CSE)                      codec emulation for automotive things.@@ -27,7 +27,7 @@                        Backport.Crypto.ConstructHash.MiyaguchiPreneel    other-extensions:    GeneralizedNewtypeDeriving-  build-depends:         base >=4.6 && <5+  build-depends:         base >=4.5 && <5                        , bytestring                        , cereal                        , memory@@ -36,8 +36,9 @@   default-language:    Haskell2010   ghc-options:         -Wall -test-suite exTrue+test-suite tests   build-depends:         base <5+                       , QuickCheck                        , quickcheck-simple                        , automotive-cse @@ -45,8 +46,10 @@                        , cryptonite    type:                exitcode-stdio-1.0-  main-is:             expectTrue.hs-  -- other-modules:+  main-is:             runTests.hs+  other-modules:+                       TestVector+                       Iso   hs-source-dirs:      test   default-language:     Haskell2010   ghc-options:         -Wall
src/Codec/Automotive/CSE.hs view
@@ -1,35 +1,57 @@  -- CSE (Cryptographic Service Engine) emulation implementation module Codec.Automotive.CSE (-  M1 (M1), M2 (M2), M3 (M3), M4 (M4), M5 (M5),-  makeM1, makeM2, makeM3, makeM4, makeM5,+  M1, unM1, makeM1, extractM1,+  M2, unM2, makeM2,+  M3, unM3, makeM3,+  M4, unM4, makeM4, extractM4, makeM4', extractM4',+  M5, unM5, makeM5, -  K1, K2, K3, K4,-  makeK1, makeK2, makeK3, makeK4,+  K1, K1', makeK1,+  K2, K2', makeK2,+  K3, K3', makeK3,+  K4, K4', makeK4, -  Derived, kdf,+  UID, unUID, makeUID, +  Derived, unDerived,+  kdf, keyUpdateEncC, keyUpdateMacC,+   DerivedCipher, derivedCipher, -  KeyAuthUse (..), Auth, NotAuth,+  KeyAuthUse, Auth, NotAuth,+  makeKeyAuthUse, unKeyAuthUse,++  UpdateC, Enc, Mac,   ) where -import Control.Applicative ((<$>))-import Data.Monoid ((<>))-import Data.Bits (shiftL, (.|.))+import Control.Monad (MonadPlus, guard)+import Data.Monoid ((<>), mconcat, Endo (..))+import Data.Bits (shiftL, shiftR, (.&.), (.|.)) import Data.Word (Word8, Word32) import Data.ByteString (ByteString) import qualified Data.ByteString as BS+import Data.Serialize.Get (runGet, getWord64be) import Data.Serialize.Put (runPut, putWord64be)+import Numeric (showHex)  import qualified Data.ByteArray as B-import Crypto.Cipher.Types (cipherInit, ecbEncrypt, cbcEncrypt, nullIV)+import Crypto.Cipher.Types (cipherInit, ecbEncrypt, cbcEncrypt, nullIV, ecbDecrypt) import Crypto.Cipher.AES (AES128)-import Crypto.Error (CryptoError, eitherCryptoError)+import Crypto.Error (eitherCryptoError)+ import Backport.Crypto.MAC.CMAC (CMAC(..), cmac) import Backport.Crypto.ConstructHash.MiyaguchiPreneel (MiyaguchiPreneel(..), mp)  +hdump :: ByteString -> String+hdump = (`appEndo` "") . mconcat . map (Endo . showW8) . BS.unpack+  where+    showW8 w+      | w < 16     =  ('0' :) . showHex w+      | otherwise  =  showHex w++ data Enc data Mac @@ -37,6 +59,9 @@   UpdateC ByteString   deriving Eq +instance Show (UpdateC c) where+  show (UpdateC c) = unwords ["UpdateC", hdump c]+ keyUpdateEncC :: UpdateC Enc keyUpdateEncC =   UpdateC . runPut $@@ -58,10 +83,24 @@   KeyAuthUse ByteString   deriving Eq +makeKeyAuthUse :: MonadPlus m => ByteString -> m (KeyAuthUse k)+makeKeyAuthUse k = do+  guard $ BS.length k == 16+  return $ KeyAuthUse k++unKeyAuthUse :: KeyAuthUse k -> ByteString+unKeyAuthUse (KeyAuthUse bs) = bs+ newtype Derived k c =   Derived ByteString   deriving Eq +instance Show (Derived k c) where+  show (Derived k) = unwords ["DerivedCipher", hdump k]++unDerived :: Derived k c -> ByteString+unDerived (Derived k) = k+ kdf :: KeyAuthUse k -> UpdateC c -> Derived k c kdf (KeyAuthUse k) (UpdateC c) = Derived . B.convert $ chashGetBytes (mp $ k <> c :: MiyaguchiPreneel AES128) @@ -73,8 +112,12 @@  newtype DerivedCipher k c = DerivedCipher AES128 -derivedCipher :: Derived k c -> Either CryptoError (DerivedCipher k c)-derivedCipher (Derived k) = DerivedCipher <$> (eitherCryptoError $ cipherInit k)+derivedCipher :: Derived k c -> DerivedCipher k c+derivedCipher (Derived k) =+  DerivedCipher+  . either (error . ("Codec.Automotive.CSE.derivedCipher: internal error: " ++) . show) id+  -- assume refined length (16 byte) of miyaguchi-preneel AES128 result+  . eitherCryptoError $ cipherInit k  type K1' = Derived Auth Enc type K1  = DerivedCipher Auth Enc@@ -105,16 +148,45 @@ makeK4 = kdfMac  +newtype UID = UID ByteString deriving Eq++instance Show UID where+  show (UID s) = unwords ["UID", hdump s]++unUID :: UID -> ByteString+unUID (UID u) = u++makeUID :: MonadPlus m => ByteString -> m UID+makeUID s = do+  guard $ BS.length s == 15+  return $ UID s+ newtype M1 = M1 ByteString deriving Eq -makeM1 :: ByteString -- ^ UID          - 15 octet+instance Show M1 where+  show (M1 s) = unwords ["M1", hdump s]++makeM1 :: UID        -- ^ UID          - 15 octet        -> Word8      -- ^ Key ID       -  4 bit        -> Word8      -- ^ Auth key ID  -  4 bit        -> M1-makeM1 uid kid akid = M1 $ uid <> BS.singleton (kid `shiftL` 4 .|. akid)+makeM1 (UID uid) kid akid = M1 $ uid <> BS.singleton (kid `shiftL` 4 .|. akid) +unM1 :: M1 -> ByteString+unM1 (M1 m1) = m1++extractM1 :: M1 -> (UID, Word8, Word8)+extractM1 (M1 m1) = (UID uid, lw `shiftR` 4, lw .&. 0x0F)+  where+    (uid, x) = BS.splitAt 15 m1+    lw = head $ BS.unpack x+    -- assume refined M1+ newtype M2 = M2 ByteString deriving Eq +instance Show M2 where+  show (M2 s) = unwords ["M2", hdump s]+ makeM2 :: K1                 -- ^ K1 value        -> Word32             -- ^ Counter   - 28 bit        -> Word8              -- ^ Key Flag  -  6 bit@@ -131,35 +203,73 @@                 putWord64be 0)             <> keyData +unM2 :: M2 -> ByteString+unM2 (M2 m2) = m2+ newtype M3 = M3 ByteString deriving Eq +instance Show M3 where+  show (M3 s) = unwords ["M3", hdump s]+ makeM3 :: K2        -> M1        -> M2        -> M3 makeM3 (DerivedCipher k2) (M1 m1) (M2 m2) = M3 . B.convert . cmacGetBytes . cmac k2 $ m1 <> m2 +unM3 :: M3 -> ByteString+unM3 (M3 m3) = m3+ newtype M4 = M4 ByteString deriving Eq -makeM4 :: ByteString-       -> Word8-       -> Word8-       -> K3-       -> Word32-       -> M4-makeM4 uid kid akid (DerivedCipher k3) counter =-    M4 $ p1 <> ecbEncrypt k3 p2+instance Show M4 where+  show (M4 s) = unwords ["M4", hdump s]++makeM4' :: K3+        -> M1+        -> Word32+        -> M4+makeM4' (DerivedCipher k3) (M1 m1) counter =+    M4 $ m1 <> ecbEncrypt k3 p2   where-    M1 p1 = makeM1 uid kid akid     p2 = runPut $ do       putWord64be $         fromIntegral counter `shiftL` 36 .|.         1                    `shiftL` 35       putWord64be 0 +makeM4 :: K3+       -> UID+       -> Word8+       -> Word8+       -> Word32+       -> M4+makeM4 k3 uid kid akid counter =+  makeM4' k3 (makeM1 uid kid akid) counter++unM4 :: M4 -> ByteString+unM4 (M4 m4) = m4++extractM4' :: K3 -> M4 -> (M1, Word32)+extractM4' (DerivedCipher k3) (M4 m4) = (M1 m1, fromIntegral $ w64 `shiftR` 36)+  where+    (m1, m4') = BS.splitAt 16 m4+    w64 = either (error . ("Codec.Automotive.CSE.extractM4: internal error: " ++)) id+          . runGet getWord64be $ ecbDecrypt k3 m4'++extractM4 :: K3 -> M4 -> ((UID, Word8, Word8), Word32)+extractM4 k3 m4 = (extractM1 m1, counter)+  where (m1, counter) = extractM4' k3 m4+ newtype M5 = M5 ByteString deriving Eq +instance Show M5 where+  show (M5 s) = unwords ["M5", hdump s]+ makeM5 :: K4        -> M4        -> M5 makeM5 (DerivedCipher k4) (M4 m4) = M5 . B.convert . cmacGetBytes $ cmac k4 m4++unM5 :: M5 -> ByteString+unM5 (M5 m4) = m4
+ test/Iso.hs view
@@ -0,0 +1,68 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE FlexibleInstances #-}++module Iso (tests) where++import Test.QuickCheck.Simple (Test, qcTest)+import Test.QuickCheck (Arbitrary (..), choose)++import Codec.Automotive.CSE+  (UID, makeUID, M1, makeM1, extractM1, M4, makeM4, extractM4,+   KeyAuthUse, makeKeyAuthUse, derivedCipher, K3, K3', makeK3)++import Control.Applicative ((<$>), (<*>))+import Control.Monad (replicateM)+import Data.Maybe (fromJust)+import Data.Word (Word8)+import qualified Data.ByteString as BS+++newtype KeyID = KeyID { unKeyID :: Word8 }++instance Arbitrary KeyID where+  arbitrary = KeyID <$> choose (0, 15)++instance Arbitrary (KeyAuthUse k) where+  arbitrary = fromJust . makeKeyAuthUse . BS.pack <$> replicateM 16 arbitrary++instance Arbitrary UID where+  arbitrary = fromJust . makeUID . BS.pack <$> replicateM 15 arbitrary++instance Arbitrary K3' where+  arbitrary = makeK3 <$> arbitrary++instance Arbitrary M1 where+  arbitrary =+    makeM1+    <$> arbitrary+    <*> (unKeyID <$> arbitrary)+    <*> (unKeyID <$> arbitrary)++newtype GenM4 = GenM4 (K3 -> M4)++instance Show GenM4 where+  show = const "GenM4"++instance Arbitrary GenM4 where+  arbitrary = do+    uid   <-  arbitrary+    kid   <-  unKeyID <$> arbitrary+    akid  <-  unKeyID <$> arbitrary+    c     <-  choose (0, 2^(28 :: Int) - 1)+    return . GenM4 $ \k3 -> makeM4 k3 uid kid akid c++isoM1 :: M1 -> Bool+isoM1 m1 = makeM1 uid kid akid == m1+  where (uid, kid, akid) = extractM1 m1++isoM4 :: K3' -> GenM4 -> Bool+isoM4 k3' (GenM4 g) = makeM4 k3 uid kid akid c == m4+  where ((uid, kid, akid), c) = extractM4 k3 m4+        k3 = derivedCipher k3'+        m4 = g k3++tests :: [Test]+tests =+  [ qcTest "iso - M1 extract make" isoM1+  , qcTest "iso - M4 extract make" isoM4+  ]
+ test/TestVector.hs view
@@ -0,0 +1,128 @@+module TestVector (tests) where++import Test.QuickCheck.Simple (Test, boolTest)++import Codec.Automotive.CSE+  (M1, unM1, makeM1, extractM1,+   M2, unM2, makeM2,+   M3, unM3, makeM3,+   M4, unM4, makeM4, extractM4,+   M5, unM5, makeM5,+   UID, makeUID,+   KeyAuthUse, Auth, NotAuth, makeKeyAuthUse, derivedCipher,+   K3, makeK1, makeK2, makeK3, makeK4)++import Numeric (showHex, showInt)+import Data.Maybe (fromJust)+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.Char (digitToInt)+import Data.Word (Word8, Word32)+++{-+ Following test vectors are calculated using the implementation:+   https://github.com/naohaq/CSE_KeyGen/tree/master/C+ with input params:+   https://github.com/naohaq/CSE_KeyGen/blob/master/param_sample.txt+ -}++testAuthKey :: KeyAuthUse Auth+testAuthKey = fromJust . makeKeyAuthUse $ hxs "00010203_04050607_08090a0b_0c0d0e0f"++testKey :: KeyAuthUse NotAuth+testKey = fromJust . makeKeyAuthUse $ hxs "0f0e0d0c_0b0a0908_07060504_03020100"++testAuthKeyID :: Word8+testAuthKeyID = 1++testKeyID :: Word8+testKeyID = 4++testUID :: UID+testUID = fromJust . makeUID $ hxs "000000000000000000000000000001"++testCounter :: Word32+testCounter = 1++testFlags :: Word8+testFlags = 0+++testM1 :: M1+testM1 = makeM1 testUID testKeyID testAuthKeyID++expectM1 :: Bool+expectM1 =+  unM1 testM1 == hxs "00000000000000000000000000000141"++expectE1 :: Bool+expectE1 = extractM1 testM1 == (testUID, testKeyID, testAuthKeyID)++testM2 :: M2+testM2 =+  makeM2 (derivedCipher $ makeK1 testAuthKey) testCounter testFlags testKey++expectM2 :: Bool+expectM2 =+  unM2 testM2 == hxs "2b111e2d93f486566bcbba1d7f7a9797_c94643b050fc5d4d7de14cff682203c3"++testM3 :: M3+testM3 =+  makeM3 (derivedCipher $ makeK2 testAuthKey) testM1 testM2++expectM3 :: Bool+expectM3 =+  unM3 testM3 == hxs "b9d745e5ace7d41860bc63c2b9f5bb46"++testK3 :: K3+testK3 = derivedCipher $ makeK3 testKey++testM4 :: M4+testM4 =+  makeM4 testK3 testUID testKeyID testAuthKeyID testCounter++expectM4 :: Bool+expectM4 =+  unM4 testM4 == hxs "00000000000000000000000000000141_b472e8d8727d70d57295e74849a27917"++expectE4 :: Bool+expectE4 = extractM4 testK3 testM4 == ((testUID, testKeyID, testAuthKeyID), testCounter)++testM5 :: M5+testM5 =+  makeM5 (derivedCipher $ makeK4 testKey) testM4++expectM5 :: Bool+expectM5 =+  unM5 testM5 == hxs "820d8d95dc11b4668878160cb2a4e23e"++hxs :: String -> ByteString+hxs = BS.pack . rec' where+    dtoW8 = fromIntegral . digitToInt+    rec' (' ':xs)  =  rec' xs+    rec' ('_':xs)  =  rec' xs+    rec' (x:y:xs)  =  dtoW8 x * 16 + dtoW8 y : rec' xs+    rec' [_]       =  error "hxs: invalid hex pattern."+    rec' []        =  []++_dump :: ByteString -> String+_dump = (`rec'` []) . BS.unpack  where+  rec'  []     =  id+  rec' (w:ws)  =  (if w < 16 then ('0' :) . showHex w else showHex w) . rec' ws++_dumpD :: ByteString -> String+_dumpD = (`rec'` []) . BS.unpack  where+  rec'  []     =  id+  rec' (w:ws)  =  showInt w . ("," ++) . rec' ws+++tests :: [Test]+tests = [ boolTest "M1" expectM1+        , boolTest "extract M1" expectE1+        , boolTest "M2" expectM2+        , boolTest "M3" expectM3+        , boolTest "M4" expectM4+        , boolTest "extract M4" expectE4+        , boolTest "M5" expectM5+        ]
− test/expectTrue.hs
@@ -1,124 +0,0 @@--import Test.QuickCheck.Simple (Test, boolTest, defaultMain)--import Codec.Automotive.CSE-  (M1(M1), makeM1,-   M2(M2), makeM2,-   M3(M3), makeM3,-   M4(M4), makeM4,-   M5(M5), makeM5,-   KeyAuthUse (..), Auth, NotAuth, derivedCipher,-   makeK1, makeK2, makeK3, makeK4)--import Numeric (showHex, showInt)-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import Data.Char (digitToInt)-import Data.Word (Word8, Word32)-import Crypto.Error (CryptoError)---{-- Following test vectors are calculated using the implementation:-   https://github.com/naohaq/CSE_KeyGen/tree/master/C- with input params:-   https://github.com/naohaq/CSE_KeyGen/blob/master/param_sample.txt- -}--testAuthKey :: KeyAuthUse Auth-testAuthKey = KeyAuthUse $ hxs "00010203_04050607_08090a0b_0c0d0e0f"--testKey :: KeyAuthUse NotAuth-testKey = KeyAuthUse $ hxs "0f0e0d0c_0b0a0908_07060504_03020100"--testAuthKeyID :: Word8-testAuthKeyID = 1--testKeyID :: Word8-testKeyID = 4--testUID :: ByteString-testUID = hxs "000000000000000000000000000001"--testCounter :: Word32-testCounter = 1--testFlags :: Word8-testFlags = 0---testM1 :: M1-testM1 = makeM1 testUID testKeyID testAuthKeyID--expectM1 :: Bool-expectM1 =-  testM1 == M1 (hxs "00000000000000000000000000000141")--testM2 :: Either CryptoError M2-testM2 = do-    k1  <-  derivedCipher $ makeK1 testAuthKey-    return $ makeM2 k1 testCounter testFlags testKey--expectM2 :: Bool-expectM2 =-  testM2 == Right (M2 $ hxs "2b111e2d93f486566bcbba1d7f7a9797_c94643b050fc5d4d7de14cff682203c3")--testM3 :: Either CryptoError M3-testM3 = do-  k2  <-  derivedCipher $ makeK2 testAuthKey-  m2  <-  testM2-  return $ makeM3 k2 testM1 m2--expectM3 :: Bool-expectM3 =-  testM3 == Right (M3 $ hxs "b9d745e5ace7d41860bc63c2b9f5bb46")--testM4 :: Either CryptoError M4-testM4 = do-  k3  <-  derivedCipher $ makeK3 testKey-  return $ makeM4 testUID testKeyID testAuthKeyID k3 testCounter--expectM4 :: Bool-expectM4 =-  testM4 == Right (M4 $ hxs "00000000000000000000000000000141_b472e8d8727d70d57295e74849a27917")--testM5 :: Either CryptoError M5-testM5 = do-  k4  <-  derivedCipher $ makeK4 testKey-  m4  <-  testM4-  return $  makeM5 k4 m4--expectM5 :: Bool-expectM5 =-  testM5 == Right (M5 $ hxs "820d8d95dc11b4668878160cb2a4e23e")--hxs :: String -> ByteString-hxs = BS.pack . rec' where-    dtoW8 = fromIntegral . digitToInt-    rec' (' ':xs)  =  rec' xs-    rec' ('_':xs)  =  rec' xs-    rec' (x:y:xs)  =  dtoW8 x * 16 + dtoW8 y : rec' xs-    rec' [_]       =  error "hxs: invalid hex pattern."-    rec' []        =  []--_dump :: ByteString -> String-_dump = (`rec'` []) . BS.unpack  where-  rec'  []     =  id-  rec' (w:ws)  =  (if w < 16 then ('0' :) . showHex w else showHex w) . rec' ws--_dumpD :: ByteString -> String-_dumpD = (`rec'` []) . BS.unpack  where-  rec'  []     =  id-  rec' (w:ws)  =  showInt w . ("," ++) . rec' ws---tests :: [Test]-tests = [ boolTest "M1" expectM1-        , boolTest "M2" expectM2-        , boolTest "M3" expectM3-        , boolTest "M4" expectM4-        , boolTest "M5" expectM5-        ]--main :: IO ()-main = defaultMain tests
+ test/runTests.hs view
@@ -0,0 +1,9 @@+import Test.QuickCheck.Simple (defaultMain)++import qualified TestVector+import qualified Iso+++main :: IO ()+main =+  defaultMain $ TestVector.tests ++ Iso.tests