0 | module Oracle.Internal.Hex
  1 |
  2 | import Data.Bits
  3 |
  4 | %default total
  5 |
  6 | ||| Convert a four-bit hexadecimal digit into its uppercase character
  7 | ||| representation.
  8 | |||
  9 | ||| Values 0 through 15 are mapped to `'0'` through `'9'` and `'A'`
 10 | ||| through `'F'`.
 11 | |||
 12 | ||| Values greater than 15 are treated as 15 and therefore map to `'F'`.
 13 | |||
 14 | export
 15 | hexDigit : Bits8 -> Char
 16 | hexDigit n =
 17 |   case n of
 18 |     0  => '0'
 19 |     1  => '1'
 20 |     2  => '2'
 21 |     3  => '3'
 22 |     4  => '4'
 23 |     5  => '5'
 24 |     6  => '6'
 25 |     7  => '7'
 26 |     8  => '8'
 27 |     9  => '9'
 28 |     10 => 'A'
 29 |     11 => 'B'
 30 |     12 => 'C'
 31 |     13 => 'D'
 32 |     14 => 'E'
 33 |     _  => 'F'
 34 |
 35 | ||| Encode a single byte as a two-character uppercase hexadecimal String.
 36 | |||
 37 | ||| The high four bits are encoded first, followed by the low four bits.
 38 | |||
 39 | ||| For example:
 40 | |||
 41 | ||| ```text
 42 | ||| 0x00 -> "00"
 43 | ||| 0x2A -> "2A"
 44 | ||| 0xFF -> "FF"
 45 | ||| ```
 46 | |||
 47 | export
 48 | hexByte : Bits8 -> String
 49 | hexByte byte =
 50 |   pack
 51 |     [ hexDigit (byte `shiftR` 4)
 52 |     , hexDigit (byte .&. 0x0F)
 53 |     ]
 54 |
 55 | ||| Encode a list of bytes as an uppercase hexadecimal String.
 56 | |||
 57 | ||| Each byte is represented by exactly two hexadecimal characters, so the
 58 | ||| resulting String has twice as many characters as the input has bytes.
 59 | |||
 60 | ||| This representation is safe for transporting arbitrary binary data across
 61 | ||| String-based FFI boundaries because the resulting value contains only
 62 | ||| ASCII hexadecimal characters.
 63 | |||
 64 | ||| For example:
 65 | |||
 66 | ||| ```text
 67 | ||| [0x00, 0xFF, 0x01, 0x80, 0x41, 0x42]
 68 | |||     -> "00FF01804142"
 69 | ||| ```
 70 | |||
 71 | export
 72 | hexEncode : List Bits8 -> String
 73 | hexEncode =
 74 |   concatMap hexByte
 75 |
 76 | ||| Decode a hexadecimal character into its four-bit numeric value.
 77 | |||
 78 | ||| Both uppercase and lowercase hexadecimal characters are accepted.
 79 | |||
 80 | export
 81 | hexValue : Char -> Maybe Bits8
 82 | hexValue '0' = Just 0
 83 | hexValue '1' = Just 1
 84 | hexValue '2' = Just 2
 85 | hexValue '3' = Just 3
 86 | hexValue '4' = Just 4
 87 | hexValue '5' = Just 5
 88 | hexValue '6' = Just 6
 89 | hexValue '7' = Just 7
 90 | hexValue '8' = Just 8
 91 | hexValue '9' = Just 9
 92 | hexValue 'A' = Just 10
 93 | hexValue 'B' = Just 11
 94 | hexValue 'C' = Just 12
 95 | hexValue 'D' = Just 13
 96 | hexValue 'E' = Just 14
 97 | hexValue 'F' = Just 15
 98 | hexValue 'a' = Just 10
 99 | hexValue 'b' = Just 11
100 | hexValue 'c' = Just 12
101 | hexValue 'd' = Just 13
102 | hexValue 'e' = Just 14
103 | hexValue 'f' = Just 15
104 | hexValue _   = Nothing
105 |
106 | ||| Decode two hexadecimal characters into a single byte.
107 | |||
108 | export
109 | hexPair : Char -> Char -> Maybe Bits8
110 | hexPair hi lo = do
111 |   hi' <- hexValue hi
112 |   lo' <- hexValue lo
113 |   pure $ (hi' `shiftL` 4) .|. lo'
114 |
115 | ||| Decode an uppercase or lowercase hexadecimal String into bytes.
116 | |||
117 | ||| Returns Nothing if the String has an odd number of characters or contains
118 | ||| any non-hexadecimal character.
119 | |||
120 | export
121 | hexDecode : String -> Maybe (List Bits8)
122 | hexDecode str =
123 |   go (unpack str)
124 |   where
125 |     go : List Char -> Maybe (List Bits8)
126 |     go [] = Just []
127 |     go (hi :: lo :: rest) = do
128 |       byte <- hexPair hi lo
129 |       bytes <- go rest
130 |       pure (byte :: bytes)
131 |     go [_] = Nothing
132 |