-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add decode lts-23.4
- Loading branch information
Showing
5 changed files
with
207 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
## Contributing as a Developer | ||
|
||
- When creating a bug report: Please follow the template and provide detailed information. | ||
- When fixing a feature: Create a Pull Request (PR) with accompanying test code. | ||
- When adding a feature: First, propose the feature in an Issue. | ||
|
||
## Contributing Outside of Coding | ||
|
||
The following actions help boost my motivation: | ||
|
||
- Giving a GitHub Star | ||
- Promoting the application | ||
- Becoming a GitHub Sponsor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,88 @@ | ||
module ClockworkBase32 | ||
( maxInList | ||
( encode, | ||
decode | ||
) where | ||
|
||
-- returns the maximum element in a list, if it exists | ||
maxInList :: (Ord a) => [a] -> Maybe a | ||
maxInList [] = Nothing | ||
maxInList xs = Just (maximum xs) | ||
import Data.Bits (shiftL, shiftR, (.&.), (.|.)) | ||
import qualified Data.ByteString as BS | ||
import qualified Data.ByteString.Char8 as BSC | ||
import Data.Word (Word8) | ||
|
||
-- clockwork base32 symbols | ||
base32Symbols :: String | ||
base32Symbols = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" | ||
|
||
-- | Encode a string into Clockwork Base32 format. | ||
-- This function encodes a given input string into the Clockwork Base32 encoding | ||
-- by processing it in chunks of 5 bytes. The input is converted into a sequence | ||
-- of Base32 symbols using the Clockwork Base32 alphabet. | ||
-- | ||
-- For example: | ||
-- encode "foobar" => "CSQPYRK1E8" | ||
encode :: String -> String | ||
encode input = processChunks inputBytes | ||
where | ||
inputBytes = BS.unpack $ BSC.pack input | ||
processChunks [] = "" | ||
processChunks bytes = encodeChunk (take 5 bytes) ++ processChunks (drop 5 bytes) | ||
|
||
-- | Encode a chunk of 5 bytes into Base32 symbols. | ||
-- This function takes a chunk of 1 to 5 bytes and converts it into the corresponding | ||
-- Base32 symbols. If the chunk is smaller than 5 bytes, the number of output symbols | ||
-- will be adjusted accordingly. | ||
-- | ||
-- For example: | ||
-- encodeChunk [102] => "CR" | ||
encodeChunk :: [Word8] -> String | ||
encodeChunk chunk = take outputLength $ map (base32Symbols !!) indices | ||
where | ||
-- 5 bytes -> 40 bits | ||
val :: Integer | ||
val = foldl (\acc (b, i) -> acc .|. (fromIntegral b `shiftL` (32 - 8 * i))) 0 (zip chunk [0..]) | ||
-- split 40 bits into 8 5-bit indices | ||
indices = [fromIntegral ((val `shiftR` (35 - 5 * i)) .&. 0x1F) | i <- [0..7]] | ||
-- determine the number of symbols to output based on the input size | ||
outputLength = case length chunk of | ||
1 -> 2 | ||
2 -> 4 | ||
3 -> 5 | ||
4 -> 7 | ||
5 -> 8 | ||
_ -> 0 | ||
|
||
-- | Decode a Clockwork Base32 encoded string into the original string. | ||
decode :: String -> Either String String | ||
decode input = case mapM decodeChar input of | ||
Left err -> Left err | ||
Right symbols -> processChunks symbols [] | ||
where | ||
-- convert a Base32 symbol into a value | ||
decodeChar :: Char -> Either String Word8 | ||
decodeChar c = case lookup c decodeMap of | ||
Just val -> Right val | ||
Nothing -> Left ("Invalid character: " ++ [c]) | ||
|
||
-- process the input symbols in chunks of 8 characters | ||
processChunks :: [Word8] -> [Word8] -> Either String String | ||
processChunks [] acc = Right (BSC.unpack (BS.pack (reverse acc))) | ||
processChunks symbols acc = case decodeChunk (take 8 symbols) of | ||
[] -> processChunks (drop 8 symbols) acc | ||
chunk -> processChunks (drop 8 symbols) (reverse chunk ++ acc) | ||
|
||
-- decode a chunk of 8 Base32 symbols into 5 bytes | ||
decodeChunk :: [Word8] -> [Word8] | ||
decodeChunk chunk = [fromIntegral byte | byte <- [v1, v2, v3, v4, v5], byte /= 0] | ||
where | ||
-- 8 symbols -> 40 bits | ||
val :: Integer | ||
val = foldl (\acc (b, i) -> acc .|. (fromIntegral b `shiftL` (35 - 5 * i))) 0 (zip chunk [0..]) | ||
-- split 40 bits into 5 8-bit values | ||
v1 = (val `shiftR` 32) .&. 0xFF | ||
v2 = (val `shiftR` 24) .&. 0xFF | ||
v3 = (val `shiftR` 16) .&. 0xFF | ||
v4 = (val `shiftR` 8) .&. 0xFF | ||
v5 = val .&. 0xFF | ||
|
||
-- create a map for decoding Base32 symbols | ||
decodeMap :: [(Char, Word8)] | ||
decodeMap = zip base32Symbols [0..] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,80 @@ | ||
module Main (main) where | ||
|
||
import ClockworkBase32 (maxInList) | ||
import ClockworkBase32 (decode, encode) | ||
import Test.Hspec | ||
|
||
|
||
main :: IO () | ||
main = hspec $ do | ||
describe "maxInList" $ do | ||
it "returns Nothing for an empty list" $ do | ||
maxInList ([] :: [Int]) `shouldBe` Nothing | ||
-- testcase from | ||
-- https://github.com/szktty/go-clockwork-base32/blob/c2cac4daa7ad2045089b943b377b12ac57e3254e/base32_test.go#L36-L44 | ||
-- https://github.com/shiguredo/erlang-base32/blob/0cc88a702ce1d8ca345e516a05a9a85f7f23a718/test/base32_clockwork_test.erl#L7-L18 | ||
describe "encode" $ do | ||
it "returns '' when input value is ''" $ do | ||
encode "" `shouldBe` "" | ||
|
||
it "returns 'CR' when input value is 'f'" $ do | ||
encode "f" `shouldBe` "CR" | ||
|
||
it "returns 'CSQG' when input value is 'fo'" $ do | ||
encode "fo" `shouldBe` "CSQG" | ||
|
||
it "returns 'CSQPY' when input value is 'foo'" $ do | ||
encode "foo" `shouldBe` "CSQPY" | ||
|
||
it "returns 'CSQPYRG' when input value is 'foob'" $ do | ||
encode "foob" `shouldBe` "CSQPYRG" | ||
|
||
it "returns 'CSQPYRK1' when input value is 'fooba'" $ do | ||
encode "fooba" `shouldBe` "CSQPYRK1" | ||
|
||
it "returns 'CSQPYRK1E8' when input value is 'foobar'" $ do | ||
encode "foobar" `shouldBe` "CSQPYRK1E8" | ||
|
||
it "returns '91JPRV3F5GG7EVVJDHJ22' when input value is 'Hello, world!'" $ do | ||
encode "Hello, world!" `shouldBe` "91JPRV3F5GG7EVVJDHJ22" | ||
|
||
it "returns 'AHM6A83HENMP6TS0C9S6YXVE41K6YY10D9TPTW3K41QQCSBJ41T6GS90DHGQMY90CHQPEBG' when input value is 'The quick brown fox jumps over the lazy dog.'" $ do | ||
encode "The quick brown fox jumps over the lazy dog." `shouldBe` "AHM6A83HENMP6TS0C9S6YXVE41K6YY10D9TPTW3K41QQCSBJ41T6GS90DHGQMY90CHQPEBG" | ||
|
||
it "returns '07EKWRQY2N7DEAVD5MJ3JX36KM' when input value is '\x01\xdd\x3e\x62\xfe\x15\x4e\xd7\x2b\x6d\x2d\x24\x39\x74\x66\x9d'" $ do | ||
encode "\x01\xdd\x3e\x62\xfe\x15\x4e\xd7\x2b\x6d\x2d\x24\x39\x74\x66\x9d" `shouldBe` "07EKWRQY2N7DEAVD5MJ3JX36KM" | ||
|
||
it "returns 'AXQQEB10D5T20WK5C5P6RY90EXQQ4TVK44' when input value is 'Wow, it really works!'" $ do | ||
encode "Wow, it really works!" `shouldBe` "AXQQEB10D5T20WK5C5P6RY90EXQQ4TVK44" | ||
|
||
describe "decode" $ do | ||
it "returns Right '' when input value is ''" $ do | ||
decode "" `shouldBe` Right "" | ||
|
||
it "returns Right 'f' when input value is 'CR'" $ do | ||
decode "CR" `shouldBe` Right "f" | ||
|
||
it "returns Right 'fo' when input value is 'CSQG'" $ do | ||
decode "CSQG" `shouldBe` Right "fo" | ||
|
||
it "returns Right 'foo' when input value is 'CSQPY'" $ do | ||
decode "CSQPY" `shouldBe` Right "foo" | ||
|
||
it "returns Right 'foob' when input value is 'CSQPYRG'" $ do | ||
decode "CSQPYRG" `shouldBe` Right "foob" | ||
|
||
it "returns Right 'fooba' when input value is 'CSQPYRK1'" $ do | ||
decode "CSQPYRK1" `shouldBe` Right "fooba" | ||
|
||
it "returns Right 'foobar' when input value is 'CSQPYRK1E8'" $ do | ||
decode "CSQPYRK1E8" `shouldBe` Right "foobar" | ||
|
||
it "returns Right 'Hello, world!' when input value is '91JPRV3F5GG7EVVJDHJ22'" $ do | ||
decode "91JPRV3F5GG7EVVJDHJ22" `shouldBe` Right "Hello, world!" | ||
|
||
it "returns Right 'The quick brown fox jumps over the lazy dog.' when input value is 'AHM6A83HENMP6TS0C9S6YXVE41K6YY10D9TPTW3K41QQCSBJ41T6GS90DHGQMY90CHQPEBG'" $ do | ||
decode "AHM6A83HENMP6TS0C9S6YXVE41K6YY10D9TPTW3K41QQCSBJ41T6GS90DHGQMY90CHQPEBG" `shouldBe` Right "The quick brown fox jumps over the lazy dog." | ||
|
||
it "returns Right '\x01\xdd\x3e\x62\xfe\x15\x4e\xd7\x2b\x6d\x2d\x24\x39\x74\x66\x9d' when input value is '07EKWRQY2N7DEAVD5MJ3JX36KM'" $ do | ||
decode "07EKWRQY2N7DEAVD5MJ3JX36KM" `shouldBe` Right "\x01\xdd\x3e\x62\xfe\x15\x4e\xd7\x2b\x6d\x2d\x24\x39\x74\x66\x9d" | ||
|
||
it "returns Right 'Wow, it really works!' when input value is 'AXQQEB10D5T20WK5C5P6RY90EXQQ4TVK44'" $ do | ||
decode "AXQQEB10D5T20WK5C5P6RY90EXQQ4TVK44" `shouldBe` Right "Wow, it really works!" | ||
|
||
it "returns the maximum element for a non-empty list" $ do | ||
maxInList ([1, 2, 3] :: [Int]) `shouldBe` Just 3 | ||
it "returns Left 'Invalid character: ~' when input value is '~'" $ do | ||
decode "~" `shouldBe` Left "Invalid character: ~" |