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 +238/−0
- Data/Binary/ISO8583/TH.hs +314/−0
- LICENSE +30/−0
- Setup.hs +2/−0
- Test.hs +76/−0
- iso8583-bitmaps.cabal +41/−0
+ 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