-- Convert hex to b64 --

The first challenge on https://cryptopals.com is to convert a hexidecimal string into a b64 string.

First of all, how do we want this to work from a high level?

I figure converting types from one to another is best able to be done in reverse too. So we will want both a hex->b64 and a b64->hex. We can, because it will be handy to have later, convert things into and out of numbers. CL has bignums so that will be easy even with large inputs.

Let’s assume we will use a number for now. So, converting to hex will basically look like this, ignoring details:

(defun hex-to-b64 (hex)
  (print-b64 (parse-hex hex)))

The list of functions we will want, to be able to make a conversion loop, will be:

  • #'parse-hex
  • #'print-hex
  • #'parse-b64
  • #'print-b64

In a loop, as a test, something like this should return the input, without errors given a valid input:

(defun test-hex-b64-loop (number)
  (assert (= number (parse-b64 (print-b64 number))))
  (assert (= number (parse-hex (print-hex number))))
  (assert (= number (parse-b64 (print-b64 (parse-hex (print-hex number))))))

When it comes to printing numbers of various types we will look at CL for clues as to how printing is currently handled. That way our system can be as close to factory as possible. It looks like the printer calls write-string to print a string. Write string loops along the string sending characters out to the stream.

Following that convention, we will have: print-hex which calls write-hex to do the printing, which calls hex-char for each chunk it needs to write.

This means we need to add to our list:

  • #'write-hex
  • #'hex-char
  • #'char-hex
  • #'write-b64
  • #'b64-char
  • #'char-b64

I am not 100% sure I like the naming here, with CL the ‘character to code and back again’ functions are char-code and code-char. Since hex is basically in place of the char the substitute functions would be code-hex and hex-code. But since they aren’t character-codes, but rather chunks of a binary number, I don’t think it quite fits either. One option would be to use the length of the bits, eg nibble or quartet for the 4-bit hex, and sextet for the 6-bit b64.

Anyways, I think CL has the division of operations right. Print-hex will take in large numbers, do print stuff, and call write-hex. Write-hex is to loop across the number, placing the characters onto the stream, calling hex-char for each. It may seem like all three of these could be done in one function, and they could, but by exposing them individually we empower future users, who can later use just write-hex, for instance, if they don’t need the options and overhead print-hex adds. It also means if we go to make something for printing byte vectors in hex, we can re-use some of this.

We can make similar functions for printing and parsing binary. That adds to our list:

  • #'print-bin
  • #'write-bin
  • #'bin-char
  • #'char-bin
  • #'parse-bin

So as usual for me, I will start by building this from the inside and work my way back out. In that case the first thing we should write are hex-char and char-hex for converting single hex characters to 4-bit integers and back again. These match code-char and char-code in both design and purpose.

Then we can work outwards till the loop of print-hex and parse-hex is complete. And finally, the b64 and bin functions should be somewhat simple as they will mostly be laid out already.