{- | Module: Codec.Binary.Coldwidow Description: Base45 encoding/decoding QR Code alphanumeric mode accepts a set of 45 characters. This module offers functions to encode/decode binary data to/from text representation using only the 45 characters allowed by the qr-code alphanumeric mode. -} module Codec.Binary.Coldwidow (encode, decode, packInteger, unpackInteger) where import Data.Bits (shiftL, (.|.), shiftR) import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy as B import Data.Word (Word8) import Numeric (showIntAtBase, readInt) import Data.List (elemIndex) import Data.Maybe (fromJust, isJust) chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-', '.', '/', ':'] base = 45 -- external inteface -- |Encodes binary data into a String. The resulting string will contain -- only the 45 characters allowed by the qr-code alphanumeric mode. The -- binary data to be encoded should be represented as a Haskell Integer. To -- convert a ByteString to a Haskell Integer you can use the 'packInteger' -- function defined bellow. encode :: Integer -> String encode x = showIntAtBase base toDigit x "" -- |Converts a BinaryString to an Integer. This is used to represent binary -- data as a Haskell Integer tha can be passed to 'encode' function defined above. packInteger :: ByteString -> Integer packInteger s = packInteger' (B.unpack s) 0 -- |Decodes binary data from its text representation. The text representation of -- data, obtained with the 'encode' function defined above, will contain only -- characters allowed in the qr-code alphanumeric mode. The decoded binary data -- returned by this function will be represented as a Haskell Integer. To convert -- it into a BinaryString you can use the 'unpackInteger' function defined bellow. decode :: String -> Integer decode encoded = let parsed = readEncodedInt encoded in case parsed of [(val, [])] -> val otherwise -> error $ "Error parsing encoded value."++parseMessages where parseMessages = concatMap parseMessage parsed -- |Converts a Haskell Integer into a ByteString. The Integer holds the binary -- represantation of the data you obtain by using the `decode` function defined -- above. You can use the ByteString represantation to, for example, save the -- binary data into a file. unpackInteger :: Integer -> ByteString unpackInteger 0 = B.singleton 0 unpackInteger x = B.pack $ unpackInteger' x [] -- internal functions toDigit :: Int -> Char toDigit i = chars !! i -- TODO: make a more efficient implementation readEncodedInt :: ReadS Integer readEncodedInt = readInt base isDigit fromDigit isDigit :: Char -> Bool isDigit digit = isJust $ elemIndex digit chars fromDigit :: Char -> Int fromDigit digit = fromJust $ elemIndex digit chars packInteger' :: [Word8] -> Integer -> Integer packInteger' [] result = result packInteger' (d:ds) result = packInteger' ds ((fromIntegral d) .|. (result `shiftL` 8)) unpackInteger' :: Integer -> [Word8] -> [Word8] unpackInteger' 0 result = result unpackInteger' value result = unpackInteger' (value `shiftR` 8) $ (fromInteger value :: Word8) : result parseMessage :: (Integer, String) -> String parseMessage (parsed, remainder) = "\n\tStopped at: " ++ (take 20 remainder)