packages feed

iso8583-bitmaps (empty) → 0.1.0.0

raw patch · 6 files changed

+701/−0 lines, 6 filesdep +basedep +binarydep +bytestringsetup-changed

Dependencies added: base, binary, bytestring, containers, parsec, syb, template-haskell, th-lift

Files

+ Data/Binary/ISO8583.hs view
@@ -0,0 +1,238 @@+----------------------------------------------------------------------------+-- |+-- Module      :  Data.Binary.Bitmap+-- Copyright   :  (c) Ilya Portnov 2014+-- License     :  BSD3-style (see LICENSE)+--+-- Maintainer  :  portnov84@rambler.ru+-- Stability   :  unstable+-- Portability :  not tested+--+-- This module contains Get and Put method implementations for ISO 8583 style+-- bitmaps, and also utility methods for getting/putting fields in formats+-- commonly used in ISO 8583 specification.+--+----------------------------------------------------------------------------++module Data.Binary.ISO8583+ (-- * Usage+  -- $usage+  getBitmapFieldNumbers, +  getBitmap,+  mergeFieldNumbers,+  putBitmap, putBitmap',+  embeddedLen,+  asciiNumber, asciiNumberF,+  putAsciiNumber, putEmbeddedLen,+  putByteStringPad, putLazyByteStringPad,+  toBS, fromBS ) where++import Control.Monad+import Data.Binary+import Data.Binary.Get+import Data.Binary.Put+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Char8 as C8+import Data.Int+import Data.Word+import Data.Bits+import Data.Char+import Data.List+import qualified Data.Map as M+import Data.Maybe+import Text.Printf++--import Debug.Trace++-- $usage+--+-- Typical usage is:+--+-- > data Message = Message {pan :: B.ByteString, stan :: Integer}+-- > deriving (Eq, Show)+--+-- > data FieldValue = String B.ByteString | Int Integer+-- > deriving (Eq, Show)+--+-- > getField :: Int -> Maybe (Get FieldValue)+-- > getField 2 = Just $ String `fmap` embeddedLen 2+-- > getField 11 = Just $ Int `fmap` asciiNumberF 11 6+-- > getField _ = Nothing+--+-- > getMessage :: Get Message+-- > getMessage = do+-- >   m <- getBitmap getField+-- >   let Just (String pan) = M.lookup 2 m+-- >   let Just (Int stan) = M.lookup 11 m+-- >   return $ Message pan stan+--+-- > putMessage :: Message -> Put+-- > putMessage (Message pan stan) = do+-- >   putBitmap [(2, putEmbeddedLen 2 pan),+-- >              (11, putAsciiNumber 6 stan)] +--++bytes :: [(Word8, Word8)]+bytes = let digits = "0123456789ABCDEF"+        in  [(fromIntegral (ord digit), n) | (digit, n) <- zip digits [0..]]++bits :: [(Word8, [Int])]+bits = [(digit, [i | i <- [1..4] , testBit n (4-i)]) | (digit, n) <- bytes]++bitsSet :: Int -> Word8 -> [Int]+bitsSet byteNr b = +  let fs = case lookup b bits of+             Just x -> x+             Nothing -> error $ "Unknown byte: " ++ [chr (fromIntegral b)]+  in [4*byteNr + n | n <- fs]++-- | Parse bitmap. Return numbers of fields present.+-- NB: only two bitmaps are supported as for now (Primary and Secondary bitmaps in ISO 8583 notation).+getBitmapFieldNumbers :: Get [Int]+getBitmapFieldNumbers = do+  bm <- getByteString 16+  let xs = [bitsSet i b | (i,b) <- zip [0..] (B.unpack bm)]+  return $ concat xs++mergeFieldNumbersW :: [Int] -> [Word64]+mergeFieldNumbersW fs =+  let (fs1,fs2) = partition (<65) fs+      go b f = setBit b (63-f)+      w1 = foldl go 0 $ map (\f -> f - 1) fs1+      w2 = foldl go 0 $ map (\f -> f - 65) fs2+  in  if w2 == 0+        then [w1]+        else [setBit w1 63, w2]++-- | Merge numbers of fields present into ISO bitmap.+mergeFieldNumbers :: [Int] -> B.ByteString+mergeFieldNumbers fs =+  -- TODO: usage of toUpper . printf for hexadecimal number is pretty slow.+  let hex n = toBS $ map toUpper $ printf "%016x" n+  in case mergeFieldNumbersW fs of+       [w1] -> hex w1+       [w1,w2] -> hex w1 `B.append` hex w2++-- | Parse ISO 8583-style bitmap.+-- Fails if unsupported field is present in message.+getBitmap :: (Int -> Maybe (Get f))  -- ^ Parser for n'th field, or Nothing if field is not supported+          -> Get (M.Map Int f)+getBitmap getter = do+  fs1 <- getBitmapFieldNumbers+  fs2 <- if 1 `elem` fs1+          then getBitmapFieldNumbers+          else return []+  let fs = fs1 ++ map (64+) fs2+  res <- forM fs $ \f ->+            if f == 1+              then return []+              else+                case getter f of+                  Nothing -> fail $ "Unsupported field #" ++ show f+                  Just fn -> do+                    offset <- bytesRead+                    --trace ("Parsing field #" ++ show f ++ " at " ++ show offset) $ return ()+                    fn >>= (\x -> return [(f, x)])+  return $ M.fromList $ concat res+               +-- | Put ISO 8583-style bitmap.+putBitmap :: [(Int, Put)] -- ^ (Field number, field putter)+          -> Put+putBitmap fs = do+  let fieldNumbers = map fst fs+  --trace ("Putting fields: " ++ show fieldNumbers) $ return ()+  putByteString $ mergeFieldNumbers fieldNumbers+  mapM_ snd fs++-- | Put ISO 8583-style bitmap.+putBitmap' :: [(Int, Maybe Put)] -- ^ (Field number, field putter or Nothing if field is not present)+           -> Put+putBitmap' fs = do+  let fs' = [(f, p) | (f, Just p) <- fs]+  let fieldNumbers = map fst fs'+  --trace ("Putting fields: " ++ show fieldNumbers) $ return ()+  putByteString $ mergeFieldNumbers fieldNumbers+  mapM_ snd fs'++-- | Parse string with embedded length (LLVAR/LLLVAR in ISO 8583 notation)+embeddedLen :: Int              -- ^ Field number (to be used in error message)+            -> Int              -- ^ Number of bytes used for length (2 for LLVAR, 3 for LLLVAR and so on)+            -> Get B.ByteString+embeddedLen f n = do+  sz <- asciiNumberF f n+  getByteString (fromIntegral sz)++-- | Parse number of given length in ASCII notation; +-- Report bitmap field number in case of error.+asciiNumberF :: Int          -- ^ Field number, to be used in error message+             -> Int          -- ^ Number length+             -> Get Integer+asciiNumberF f n = do+  bs <- getByteString n+  case C8.readInteger bs of+    Just (res, s)+      | C8.null s -> return res+    _ -> fail $ "Cannot parse number: <" ++ fromBS bs ++ "> in field #" ++ show f++-- | Parse number of given length in ASCII notation+asciiNumber :: Int      -- ^ Number length+            -> Get Int+asciiNumber n = do+  bs <- getByteString n+  case reads (fromBS bs) of+    [(res, "")] -> return res+    _ -> fail $ "Cannot parse number: " ++ fromBS bs++-- | Put number of given length in ASCII notation+putAsciiNumber :: Int      -- ^ Number length+               -> Integer  -- ^ Number+               -> Put+putAsciiNumber sz n = do+  let s = show n+      m = length s+      s' = if m < sz+             then replicate (sz-m) '0' ++ s+             else if m == sz+                    then s+                    else drop (m-sz) s+      bs = toBS s'+  putByteString bs++-- | Put string with embedded length (LLVAR/LLLVAR in ISO 8583 notation)+putEmbeddedLen :: Int          -- ^ Number of bytes used for length (2 for LLVAR, 3 for LLLVAR and so on)+               -> B.ByteString -- ^ String to put+               -> Put+putEmbeddedLen sz bstr = do+  let len = fromIntegral $ B.length bstr+  putAsciiNumber sz len+  putByteString bstr++-- | Put space-padded string of given length+putByteStringPad :: Int           -- ^ Field length+                 -> B.ByteString  -- ^ String to put+                 -> Put+putByteStringPad sz bstr = do+  let len = B.length bstr+  let bstr' = if len < sz+                then B.replicate (sz-len) 0x20 `B.append` bstr+                else bstr+  putByteString bstr'++-- | Put space-padded string of given length+putLazyByteStringPad :: Int64           -- ^ Field length+                 -> L.ByteString  -- ^ String to put+                 -> Put+putLazyByteStringPad sz bstr = do+  let len = L.length bstr+  let bstr' = if len < sz+                then L.replicate (sz-len) 0x20 `L.append` bstr+                else bstr+  putLazyByteString bstr'++toBS :: String -> B.ByteString+toBS str = B.pack $ map (fromIntegral . ord) str++fromBS :: B.ByteString -> String+fromBS bstr = map (chr . fromIntegral) $ B.unpack bstr+
+ Data/Binary/ISO8583/TH.hs view
@@ -0,0 +1,314 @@+{-# LANGUAGE TemplateHaskell, ExistentialQuantification #-}+----------------------------------------------------------------------------+-- |+-- Module      :  Data.Binary.Bitmap.TH+-- Copyright   :  (c) Ilya Portnov 2014+-- License     :  BSD3-style (see LICENSE)+--+-- Maintainer  :  portnov84@rambler.ru+-- Stability   :  unstable+-- Portability :  not tested+--+-- This module contains QuasiQuoter for declarative description of+-- ISO 8583-based message formats.+--+----------------------------------------------------------------------------++module Data.Binary.ISO8583.TH+  (-- * Usage+   -- $usage+   FieldType (..),+   FieldValue (..),+   Field (..),+   pData,+   string2data,+   binaryQ, binary+  ) where++import Control.Monad+import Text.Parsec+import qualified Text.Parsec.Token as P+import Text.Parsec.Language (haskellDef)+import Text.Parsec.String+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.ByteString (ByteString)+import qualified Data.Map as M+import Data.Generics++import Language.Haskell.TH.Syntax+import Language.Haskell.TH.Quote+import Language.Haskell.TH.Lift++import Data.Binary+import Data.Binary.Get+import Data.Binary.Put++import Data.Binary.ISO8583++-- $usage+--+-- Typical usage is:+--+-- > [binary|+-- >   Message+-- >   2 pan embedded 2+-- >   4 amount int 12+-- >   11 stan int 6+-- >   43 termAddress TermAddress 222+-- > |]+--+-- > data TermAddress = TermAddress ...+--+-- > instance Binary TermAddress where ...+--+-- Quasi-quote format is:+--+--  - First line - name of data type to generate+--+--  - Each of other lines describes one field, in following format:+--  {Field number} {Field name} {data type} {type parameter}+--+--  Internally supported data types are: +--+--  - int - integer field. Parameter defines size of field.+--+--  - str - fixed-length string field. Parameter defines size of field.+--  +--  - embedded - embedded-length string field. Parameter defines number of bytes used to store field length (2 for LLVAR, 3 for LLLVAR and so on).+--+--  Any other data type, instance of Binary class, may be used as well.+--+--  Quasi-quoter generates data type definition, and also following functions:+--+--  - get[Message] :: Int -> Maybe (Get FieldValue)+--+--  - put[Message] :: Message -> [(Int, Maybe Put)]+--+--  - construct[Message] :: M.Map Int FieldValue -> Message+--+-- Concrete ISO 8583-based formats usually use some kind of message header, or +-- use bitmap only as small part of overall format. So, it's usually no point of+-- generating instance Binary Message - it's better to write instance for your+-- format by using functions mentioned above.+--+-- > instance Binary Message where+-- >   get = do+-- >     -- parse some kind of header here, then:+-- >     m <- getBitmap getMessage+-- >     return $ constructMessage m+-- >   +-- >   put msg = do+-- >     -- write some kind of header here, then:+-- >     putBitmap' (putMessage msg)+--++-- | Supported field types+data FieldType =+       TInt Int          -- ^ Integer field of given size+     | TString Int       -- ^ Fixed-length string field+     | TEmbeddedLen Int  -- ^ Variable-length string field with embedded length (LLVAR and so on)+     | TOther String Int -- ^ User-defined field - any data type with instance Binary. NB: second parameter is not used currently.+  deriving (Eq, Show)++deriveLift ''FieldType++-- | Field format description+data Field =+      Field {+        fNumber :: Int      -- ^ Field number+      , fName :: String     -- ^ Field name+      , fType :: FieldType  -- ^ Field type description+    }+  deriving (Eq, Show)++-- | Supported field values+data FieldValue =+       FInt Integer         -- ^ Integer field+     | FString ByteString   -- ^ String fields+     | forall t. (Binary t, Show t, Typeable t) => FOther t -- ^ User-defined field++instance Eq FieldValue where+  (FInt x) == (FInt y) = x == y+  (FString x) == (FString y) = x == y+  (FOther x) == (FOther y) = runPut (put x) == runPut (put y)+  _ == _ = False++instance Show FieldValue where+  show (FInt x) = show x+  show (FString x) = show x+  show (FOther x) = show $ runPut (put x)++unpackFInt :: FieldValue -> Integer+unpackFInt (FInt x) = x+unpackFInt x = error $ "Internal error: not an integer: " ++ show x++unpackFString :: FieldValue -> ByteString+unpackFString (FString str) = str+unpackFString x = error $ "Internal error: not a string: " ++ show x++toStrict :: L.ByteString -> B.ByteString+toStrict s = B.concat $ L.toChunks s++fromStrict :: B.ByteString -> L.ByteString+fromStrict s = L.fromChunks [s]++unpackOther :: FieldValue -> ByteString+unpackOther (FOther x) = toStrict $ runPut (put x)+unpackOther _ = error $ "Internal error: not a FOther"++lexer = P.makeTokenParser haskellDef+identifier = P.identifier lexer+number = P.natural lexer++pField :: Parser Field+pField = do+  skipMany (space <|> newline)+  n <- number+  skipMany space+  name <- identifier+  skipMany space+  t <- identifier+  skipMany space+  param <- number+  ft <- case t of+          "int" -> return $ TInt (fromIntegral param)+          "str" -> return $ TString (fromIntegral param)+          "embedded" -> return $ TEmbeddedLen (fromIntegral param)+          _ -> return $ TOther t (fromIntegral param)+  return $ Field (fromIntegral n) name ft++pFields :: Parser [Field]+pFields = do+  fs <- pField `sepEndBy1` many newline+  eof+  return fs++-- | Parse data format definition+pData :: Parser (String, [Field])+pData = do+  skipMany (space <|> newline)+  name <- identifier+  skipMany newline+  fs <- pFields+  return (name, fs)++-- | Generate `data Message = Message {...'+generateData :: String    -- ^ Data type name+             -> [Field]   -- ^ Fields description+             -> Q [Dec]+generateData nameStr fields = do+    let name = mkName nameStr+    fields' <- mapM convertField fields+    let constructor = RecC name fields'+    return [ DataD [] name [] [constructor] [] ]+  where+    convertField (Field _ fname ftype) = do+      t <- convertType ftype+      return (mkName fname, NotStrict, t)++    convertType (TInt _) = [t| Maybe Integer |]+    convertType (TString _) = [t| Maybe B.ByteString |]+    convertType (TEmbeddedLen _) = [t| Maybe B.ByteString |]+    convertType (TOther n _) = do+        let name = mkName n+        return $ ConT (mkName "Maybe") `AppT` ConT name++-- | Generate `getMessage 2 = Just $ ...'+mkGetter :: String -> [Field] -> Q [Dec]+mkGetter nameStr fields = do+    let name = mkName nameStr+    clauses <- forM fields $ \(Field f _ ft) -> mkGetClause f ft+    unsupportedClause <- mkUnsupportedClause +    return [FunD name (clauses ++ [unsupportedClause])]+  where+    mkGetClause :: Int -> FieldType -> Q Clause+    mkGetClause n ft = do+      let pats = [LitP (IntegerL (fromIntegral n))]+      let int x = return $ LitE $ IntegerL (fromIntegral x)+      body <- case ft of +                TInt sz -> [| Just $ FInt `fmap` asciiNumberF $(int n) $(int sz) |]+                TString sz -> [| Just $ FString `fmap` getByteString $(int sz) |]+                TEmbeddedLen sz -> [| Just $ FString `fmap` embeddedLen $(int n) $(int sz) |]+                TOther name _ -> [| Just $ FOther `fmap` (get :: Get $(return $ ConT $ mkName name) ) |]+      return $ Clause pats (NormalB body) []++    mkUnsupportedClause :: Q Clause+    mkUnsupportedClause = do+      body <- [| Nothing |]+      let pats = [ WildP ]+      return $ Clause pats (NormalB body) []++-- | Generate `putMessage'+mkPutter :: String -> [Field] -> Q [Dec]+mkPutter nameStr fields = do+    msg <- newName "msg"+    let name = mkName nameStr+        pat = VarP msg+    body <- ListE `fmap` mapM (mkBody msg) fields+    let clause = Clause [pat] (NormalB body) []+    return [FunD name [clause]]+  where+    mkBody msg (Field f fname ftype) = do+      let getter = return $ VarE $ mkName fname+      let msgvar = return $ VarE msg+      let wrapper = case ftype of+                     TInt _ -> return $ ConE $ mkName "FInt"+                     TString _    -> return $ ConE $ mkName "FString"+                     TEmbeddedLen _    -> return $ ConE $ mkName "FString"+                     TOther name _ -> return $ ConE $ mkName "FOther"+      let putter = [| putField $(lift ftype) ( $(wrapper) `fmap` $(getter) $(msgvar) ) |]+      [| ( $(lift f) , $(putter) ) |]++putField :: FieldType -> Maybe FieldValue -> Maybe Put+putField _ Nothing = Nothing+putField (TInt sz) (Just (FInt n)) = Just $ putAsciiNumber sz n+putField (TString sz) (Just (FString s)) = Just $ putByteStringPad sz s+putField (TEmbeddedLen sz) (Just (FString s)) = Just $ putEmbeddedLen sz s+putField (TOther _ _) (Just (FOther x)) = Just $ put x+putField t (Just v) = fail $ "Internal error: field value " ++ show v ++ " does not match type " ++ show t++mkConstructor :: String -> String -> [Field] -> Q [Dec]+mkConstructor prefix nameStr fields = do+    msg <- newName "msg"+    let name = mkName nameStr+        pat = VarP msg+    body <- RecConE name `fmap` mapM (mkBody msg) fields+    let clause = Clause [pat] (NormalB body) []+    return [FunD (mkName $ prefix ++ nameStr) [clause]]+  where+    mkBody msg (Field f fname ftype) = do+      let msgvar = return $ VarE msg+      let unpack = case ftype of+                     TInt _ -> [| unpackFInt |]+                     TString _ -> [| unpackFString |]+                     TEmbeddedLen _ -> [| unpackFString |]+                     TOther _ _ -> [| runGet get . fromStrict . unpackOther |]+      rhs <- [| $(unpack) `fmap` M.lookup $(lift f) $(msgvar) |]+      return (mkName fname, rhs)++-- | Generate only data type definition+string2data :: String -> Q [Dec]+string2data str = do+  case parse pData "<input>" str of+    Right (name, fields) -> generateData name fields+    Left err -> fail $ show err++binaryQ :: String -> Q [Dec]+binaryQ str = do+  case parse pData "<input>" str of+    Right (name, fields) -> do+        dtype <- generateData name fields+        getter <- mkGetter ("get" ++ name) fields+        putter <- mkPutter ("put" ++ name) fields+        cons <- mkConstructor "construct" name fields+        return $ dtype ++ getter ++ putter ++ cons+    Left err -> fail $ show err++dataOnly :: QuasiQuoter+dataOnly = QuasiQuoter {quoteDec = string2data, quoteExp=undefined, quotePat=undefined, quoteType=undefined}++-- | Main function here.+binary :: QuasiQuoter+binary = QuasiQuoter {quoteDec = binaryQ,  quoteExp=undefined, quotePat=undefined, quoteType=undefined}+
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, IlyaPortnov++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of IlyaPortnov nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Test.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE TemplateHaskell, QuasiQuotes, StandaloneDeriving, DeriveDataTypeable #-}++module Test where++import Control.Applicative+import Control.Monad+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.Binary+import Data.Binary.Get+import Data.Binary.Put+import qualified Data.Map as M+import Data.Generics++import Data.Binary.ISO8583+import Data.Binary.ISO8583.TH++[binary|+  Message+  2 pan embedded 2+  4 amount int 12+  11 stan int 6+  43 termAddress TermAddress 222+|]++deriving instance Eq Message+deriving instance Show Message++data TermAddress = TermAddress {+      tOwner :: B.ByteString,+      tCity :: B.ByteString,+      tOther :: L.ByteString }+  deriving (Eq, Show, Typeable)++instance Binary TermAddress where+  -- NB: this implementation is smth odd and usable only for this testcase.+  get =+    TermAddress+      <$> B.filter (/= 0x20) `fmap` getByteString 30+      <*> B.filter (/= 0x20) `fmap` getByteString 30+      <*> L.filter (/= 0x20) `fmap` getRemainingLazyByteString++  put (TermAddress owner city other) = do+    putByteStringPad 30 owner+    putByteStringPad 30 city+    putLazyByteStringPad 162 other++instance Binary Message where+  get = do+    m <- getBitmap getMessage+    return $ constructMessage m+  +  put msg = do+    putBitmap' (putMessage msg)+    +testMsg :: Message+testMsg = Message {+  pan = Just $ toBS "12345678",+  amount = Just $ 100500,+  stan = Just $ 123456,+  termAddress = Just $ TermAddress {+                  tOwner = toBS "TestBank",+                  tCity = toBS "Magnitogorsk",+                  tOther = L.empty }+}++test :: IO ()+test = do+  let bstr = encode testMsg+      msg = decode bstr+  if msg /= testMsg+    then fail $ "Encode/decode mismatch:\n" +++           show testMsg ++ "\n /= \n" +++           show msg+    else putStrLn "passed."+
+ iso8583-bitmaps.cabal view
@@ -0,0 +1,41 @@+-- Initial bitmaps.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                iso8583-bitmaps+version:             0.1.0.0+synopsis:            Parse and merge ISO 8583-style bitmaps+description:         This package provides utility methods for writing+                     Get and Put methods (in terms of Data.Binary) for parsing and+                     merging ISO 8583-style bitmaps, and also parsing and merging+                     most commonly used field formats (LLVAR/LLLVAR, ASCII numbers+                     and so on).+                     Moreover, this package provides TH quasiquoter for declarative+                     specification of ISO 8583-based message formats.++license:             BSD3+license-file:        LICENSE+author:              IlyaPortnov+maintainer:          portnov84@rambler.ru+-- copyright:           +category:            Data+build-type:          Simple+cabal-version:       >=1.8++extra-source-files: Test.hs++library+  exposed-modules:     Data.Binary.ISO8583,+                       Data.Binary.ISO8583.TH+  -- other-modules:       +  build-depends:       base > 4 && < 5,+                       binary >=0.5,+                       bytestring >=0.9,+                       containers >=0.4,+                       parsec >=3.1,+                       template-haskell >=2.7,+                       th-lift >=0.6,+                       syb >= 0.3.6++source-repository head+  type: git+  location: https://github.com/portnov/iso8583-bitmaps.git