added base45 encoding/decoding

This commit is contained in:
Stefan Friese 2024-05-23 22:32:15 +02:00
parent 5d1bd6abc2
commit 87089a314b
4 changed files with 117 additions and 0 deletions

View File

@ -13,6 +13,7 @@ import Encoding.Base8 (enc8, dec8)
import Encoding.Base10 (enc10, dec10) import Encoding.Base10 (enc10, dec10)
import Encoding.Base16 (enc16, dec16) import Encoding.Base16 (enc16, dec16)
import Encoding.Base32 (enc32, dec32) import Encoding.Base32 (enc32, dec32)
import Encoding.Base45 (enc45, dec45)
import Encoding.Base58 (enc58, dec58) import Encoding.Base58 (enc58, dec58)
import Encoding.Base62 (enc62, dec62) import Encoding.Base62 (enc62, dec62)
import Encoding.Base64 (enc64, dec64, enc64url, dec64url) import Encoding.Base64 (enc64, dec64, enc64url, dec64url)
@ -34,6 +35,7 @@ data Based = Decode {
url :: Bool, url :: Bool,
b62 :: Bool, b62 :: Bool,
b58 :: Bool, b58 :: Bool,
b45 :: Bool,
b32 :: Bool, b32 :: Bool,
b16 :: Bool, b16 :: Bool,
b10 :: Bool, b10 :: Bool,
@ -54,6 +56,7 @@ data Based = Decode {
url :: Bool, url :: Bool,
b62 :: Bool, b62 :: Bool,
b58 :: Bool, b58 :: Bool,
b45 :: Bool,
b32 :: Bool, b32 :: Bool,
b16 :: Bool, b16 :: Bool,
b10 :: Bool, b10 :: Bool,
@ -160,6 +163,8 @@ optionHandler Decode{b62=True} = dec62
optionHandler Encode{b62=True} = enc62 optionHandler Encode{b62=True} = enc62
optionHandler Decode{b58=True} = dec58 optionHandler Decode{b58=True} = dec58
optionHandler Encode{b58=True} = enc58 optionHandler Encode{b58=True} = enc58
optionHandler Decode{b45=True} = dec45
optionHandler Encode{b45=True} = enc45
optionHandler Decode{b32=True} = dec32 optionHandler Decode{b32=True} = dec32
optionHandler Encode{b32=True} = enc32 optionHandler Encode{b32=True} = enc32
optionHandler Decode{b16=True} = dec16 optionHandler Decode{b16=True} = dec16
@ -191,6 +196,7 @@ decodeMode = Decode {
url = def &= help "decode URI", url = def &= help "decode URI",
b62 = def &= help "decode base62", b62 = def &= help "decode base62",
b58 = def &= help "decode base58", b58 = def &= help "decode base58",
b45 = def &= help "decode base45",
b32 = def &= help "decode base32", b32 = def &= help "decode base32",
b16 = def &= help "decode base16", b16 = def &= help "decode base16",
b10 = def &= help "decode decimal from hex", b10 = def &= help "decode decimal from hex",
@ -213,6 +219,7 @@ encodeMode = Encode {
url = def &= help "encode URI", url = def &= help "encode URI",
b62 = def &= help "encode base62", b62 = def &= help "encode base62",
b58 = def &= help "encode base58", b58 = def &= help "encode base58",
b45 = def &= help "encode base45",
b32 = def &= help "encode base32", b32 = def &= help "encode base32",
b16 = def &= help "encode base16", b16 = def &= help "encode base16",
b10 = def &= help "encode base10 to hex", b10 = def &= help "encode base10 to hex",

1
based Symbolic link
View File

@ -0,0 +1 @@
./dist-newstyle/build/aarch64-linux/ghc-9.4.8/based-0.4.3.0/x/based/build/based/based

View File

@ -13,6 +13,7 @@ library
Encoding.Base10 Encoding.Base10
Encoding.Base16 Encoding.Base16
Encoding.Base32 Encoding.Base32
Encoding.Base45
Encoding.Base58 Encoding.Base58
Encoding.Base62 Encoding.Base62
Encoding.Base64 Encoding.Base64

108
src/Encoding/Base45.hs Normal file
View File

@ -0,0 +1,108 @@
module Encoding.Base45
( enc45
, dec45
) where
import Data.Char (chr, ord)
import Data.List (elemIndex)
import Data.Maybe (fromJust)
base45Alphabet :: String
base45Alphabet = ['0'..'9'] ++ ['A'..'Z'] ++ " " ++ "$%*+-./:"
enc45 :: String -> String
enc45 = concatMap (reverse . encodeChunk) . chunkBy 2 . map ord
chunkBy :: Int -> [a] -> [[a]]
chunkBy _ [] = []
chunkBy n xs = take n xs : chunkBy n (drop n xs)
encodeChunk :: [Int] -> String
encodeChunk [x1, x2] = map (base45Alphabet !!) [c, b, a]
where
n = x1 * 256 + x2
a = n `mod` 45
b = (n `div` 45) `mod` 45
c = n `div` 2025
encodeChunk [x1] = map (base45Alphabet !!) [b, a]
where
a = x1 `mod` 45
b = x1 `div` 45
encodeChunk _ = error "Invalid chunk length"
-- Decode
dec45 :: String -> String
dec45 encoded = bytesToChars decodedBytes
where
numericValues = map charToNumeric encoded
groups = groupIntoThrees numericValues
base45Values = map toBase45 groups
decodedBytes = concatMap fromBase45 base45Values
charToNumeric :: Char -> Int
charToNumeric c = case c of
'0' -> 0
'1' -> 1
'2' -> 2
'3' -> 3
'4' -> 4
'5' -> 5
'6' -> 6
'7' -> 7
'8' -> 8
'9' -> 9
'A' -> 10
'B' -> 11
'C' -> 12
'D' -> 13
'E' -> 14
'F' -> 15
'G' -> 16
'H' -> 17
'I' -> 18
'J' -> 19
'K' -> 20
'L' -> 21
'M' -> 22
'N' -> 23
'O' -> 24
'P' -> 25
'Q' -> 26
'R' -> 27
'S' -> 28
'T' -> 29
'U' -> 30
'V' -> 31
'W' -> 32
'X' -> 33
'Y' -> 34
'Z' -> 35
' ' -> 36
'$' -> 37
'%' -> 38
'*' -> 39
'+' -> 40
'-' -> 41
'.' -> 42
'/' -> 43
':' -> 44
_ -> error "Invalid Base45 character"
groupIntoThrees :: [Int] -> [[Int]]
groupIntoThrees [] = []
groupIntoThrees xs
| length xs < 3 = [xs ++ replicate (3 - length xs) 0]
| otherwise = take 3 xs : groupIntoThrees (drop 3 xs)
toBase45 :: [Int] -> Int
toBase45 [c, d, e] = c + d * 45 + e * (45 * 45)
fromBase45 :: Int -> [Int]
fromBase45 n
| n >= 256 = fromBase45 (n `div` 256) ++ [n `mod` 256]
| otherwise = [n]
bytesToChars :: [Int] -> String
bytesToChars = map chr