Skip to content
base32.lisp 10 KiB
Newer Older
Nathan Froyd's avatar
Nathan Froyd committed
;;;; base32.lisp -- The base32 encoding, defined in RFC 3548 and 4648.

(cl:in-package :binascii)

(defvar *base32-encode-table*
  #.(coerce "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" 'simple-base-string))
(defvar *base32hex-encode-table*
  #.(coerce "0123456789ABCDEFGHIJKLMNOPQRSTUV" 'simple-base-string))

(defstruct (base32-encode-state
             (:include encode-state)
             (:copier nil)
             (:constructor make-base32-encode-state
                           (&aux (descriptor (base32-format-descriptor))
                                 (table *base32-encode-table*)))
             (:constructor make-base32hex-encode-state
                           (&aux (descriptor (base32-format-descriptor))
                                 (table *base32hex-encode-table*))))
  (bits 0 :type (unsigned-byte 16))
  (n-bits 0 :type (unsigned-byte 8))
  (table *base32-encode-table* :read-only t
         :type (simple-array base-char (32)))
  (padding-remaining 0 :type (integer 0 6)))

(declaim (inline base32-encoder))
(defun base32-encoder (state output input
                       output-index output-end
                       input-index input-end lastp converter)
  (declare (type base32-encode-state state))
  (declare (type simple-octet-vector input))
  (declare (type index output-index output-end input-index input-end))
  (declare (type function converter))
  (let ((bits (base32-encode-state-bits state))
        (n-bits (base32-encode-state-n-bits state))
        (table (base32-encode-state-table state))
        (n-pad-chars #.(make-array 5 :initial-contents '(0 4 1 6 3)
                                   :element-type 'fixnum)))
                      
    (declare (type index input-index output-index))
    (declare (type (unsigned-byte 16) bits))
    (declare (type (unsigned-byte 8) n-bits))
    (declare (type (simple-array fixnum (5)) n-pad-chars))
    (tagbody
     PAD-CHECK
       (when (base32-encode-state-finished-input-p state)
     INPUT-CHECK
       (when (>= input-index input-end)
         (go DONE))
     DO-INPUT
       (when (< n-bits 5)
         (setf bits (ldb (byte 16 0)
                         (logior (ash bits 8) (aref input input-index))))
         (incf input-index)
         (incf n-bits 8))
     OUTPUT-CHECK
       (when (>= output-index output-end)
         (go DONE))
     DO-OUTPUT
       (decf n-bits 5)
       (setf (aref output output-index)
             (funcall converter (aref table (ldb (byte 5 n-bits) bits))))
       (incf output-index)
       (if (>= n-bits 5)
           (go OUTPUT-CHECK)
           (go INPUT-CHECK))
     DONE
       (unless lastp
         (go RESTORE-STATE))
       (setf (base32-encode-state-finished-input-p state) t)
       (setf (base32-encode-state-padding-remaining state)
             (aref n-pad-chars n-bits))
     PAD
       (locally (declare (type (integer 0 4) n-bits))
         (let ((padding-remaining (base32-encode-state-padding-remaining state))
               (max-pad-chars (aref n-pad-chars n-bits)))
           (declare (type (integer 0 6) padding-remaining))
           (declare (type (integer 0 6) max-pad-chars))
           (when (and (= padding-remaining max-pad-chars)
                      (< output-index output-end))
             (setf (aref output output-index)
                   (funcall converter
                            (aref table (ash (ldb (byte n-bits 0) bits)
                                             (- 5 n-bits)))))
Nathan Froyd's avatar
Nathan Froyd committed
             (incf output-index))
           (loop while (and (> padding-remaining 0)
                            (< output-index output-end))
              do (setf (aref output output-index) (funcall converter #\=))
              (incf output-index)
              (decf padding-remaining))
           (when (zerop padding-remaining)
             (setf n-bits 0))
           (setf (base32-encode-state-padding-remaining state) padding-remaining)))
     RESTORE-STATE
       (setf (base32-encode-state-bits state) bits
             (base32-encode-state-n-bits state) n-bits))
    (values input-index output-index)))
(defvar *base32-decode-table* (make-decode-table *base32-encode-table*))
(defvar *base32hex-decode-table* (make-decode-table *base32hex-encode-table*))
(declaim (type decode-table *base32-decode-table* *base32hex-decode-table*))

(defun base32-decode-table (case-fold map01)
  (let ((table *base32-decode-table*))
    (when map01
      (setf table (copy-seq table))
      (setf (aref table (char-code #\0)) (aref table (char-code #\O)))
      (case map01
        ((#\I #\L) (setf (aref table (char-code #\1))
                         (aref table (char-code map01))))))
    (when case-fold
      (setf table (case-fold-decode-table table *base32-encode-table*)))
    table))

(defstruct (base32-decode-state
             (:include decode-state)
             (:copier nil)
             (:constructor %make-base32-decode-state
                           (table
                            &aux (descriptor (base32-format-descriptor)))))
  (bits 0 :type (unsigned-byte 16))
  (n-bits 0 :type (unsigned-byte 8))
  (padding-remaining 0 :type (integer 0 6))
  (table *base32-decode-table* :read-only t :type decode-table))

(defun make-base32-decode-state (case-fold map01)
  (%make-base32-decode-state (base32-decode-table case-fold map01)))
(defun make-base32hex-decode-state (case-fold map01)
  (declare (ignore case-fold map01))
  (%make-base32-decode-state *base32hex-decode-table*))

(defun base32-decoder (state output input
                       output-index output-end
                       input-index input-end lastp converter)
  (declare (type base32-decode-state state))
  (declare (type simple-octet-vector output))
  (declare (type index output-index output-end input-index input-end))
  (declare (type function converter))
  (let ((bits (base32-decode-state-bits state))
        (n-bits (base32-decode-state-n-bits state))
        (padding-remaining (base32-decode-state-padding-remaining state))
        (table (base32-decode-state-table state)))
    (declare (type (unsigned-byte 16) bits))
    (declare (type fixnum n-bits))
    (declare (type (integer 0 6) padding-remaining))
    (tagbody
     PAD-CHECK
       (when (base32-decode-state-finished-input-p state)
         (go EAT-EQUAL-CHECK-PAD))
     OUTPUT-AVAILABLE-CHECK
       (when (< n-bits 8)
         (go INPUT-AVAILABLE-CHECK))
     OUTPUT-SPACE-CHECK
       (when (>= output-index output-end)
         (go DONE))
     DO-OUTPUT
       (decf n-bits 8)
       (setf (aref output output-index) (logand (ash bits (- n-bits)) #xff)
             bits (logand bits #xff))
       (incf output-index)
       (go INPUT-AVAILABLE-CHECK)
     INPUT-AVAILABLE-CHECK
       (when (>= input-index input-end)
         (go DONE))
     DO-INPUT
       (let* ((c (aref input input-index))
              (v (funcall converter c))
              (d (dtref table v)))
         (when (= v (if (typep input 'simple-octet-vector)
                        (char-code #\=)
                        (funcall converter #\=)))
           (go SAW-EQUAL))
         (when (= d +dt-invalid+)
           (error "invalid base32 character ~A at position ~D" c input-index))
         (incf input-index)
         (setf bits (ldb (byte 16 0) (logior (ash bits 5) d)))
         (incf n-bits 5)
         (go OUTPUT-AVAILABLE-CHECK))
     DONE
       (unless lastp
         (go RESTORE-STATE))
     SAW-EQUAL
       (setf (base32-decode-state-finished-input-p state) t)
       ;; A complete base32 group is:
       ;;
       ;; vvvvvvvv wwwwwwww xxxxxxxx yyyyyyyy zzzzzzzz
       ;;
       ;; which gets encoded by:
       ;;
       ;; vvvvv vvvww wwwww wxxxx xxxxy yyyyy yyzzz zzzzz
       ;;
       ;; so the intermediate bits left are: 3 1 4 2 0
       ;; corresponding to padding amounts : 6 4 3 1 0 (in characters)
       ;;
       ;; but we also have to handle cases where we start padding too
       ;; soon: we can't handle padding after seeing 1 group of 5, 3
       ;; groups of 5, 4 groups of 5 or 6 groups of five.  those
       ;; correspond to 5 bits remaining (having not seen the 3 v's), 7
       ;; bits remaining (having not seen the 1 w), 4 bits remaining
       ;; (having not seen the 4 x's), and 6 bits remaining (having not
       ;; seen the 2 y's).
       (let ((n-pad-chars #.(make-array 5 :initial-contents '(0 4 1 6 3)
                                        :element-type 'fixnum)))
         (if (<= n-bits 4)
             (setf padding-remaining (aref n-pad-chars n-bits))
             (error "invalid base32 input")))
     EAT-EQUAL-CHECK-PAD
       (when (zerop padding-remaining)
         (go RESTORE-STATE))
     EAT-EQUAL-CHECK-INPUT
       (when (>= input-index input-end)
         (go RESTORE-STATE))
     EAT-EQUAL
       (let ((v (aref input input-index)))
         (unless (= (funcall converter v)
                    (if (typep input 'simple-octet-vector)
                        (char-code #\=)
                        (funcall converter #\=)))
           (error "invalid base32 input ~A at position ~D" v input-index))
         (incf input-index)
         (decf padding-remaining)
         (go EAT-EQUAL-CHECK-PAD))
     RESTORE-STATE
       (setf (base32-decode-state-n-bits state) n-bits
             (base32-decode-state-bits state) bits
             (base32-decode-state-padding-remaining state) padding-remaining))
    (values input-index output-index)))

(defun encoded-length-base32 (count)
Nathan Froyd's avatar
Nathan Froyd committed
  "Return the number of characters required to encode COUNT octets in Base32."
  (* (ceiling count 5) 8))

(defun decoded-length-base32 (length)
  (* (ceiling length 8) 5))

(define-format :base32
  :encode-state-maker make-base32-encode-state
  :decode-state-maker make-base32-decode-state
  :encode-length-fun encoded-length-base32
  :decode-length-fun decoded-length-base32
  :encoder-fun base32-encoder
  :decoder-fun base32-decoder)
(define-format :base32hex
  :encode-state-maker make-base32hex-encode-state
  :decode-state-maker make-base32hex-decode-state
  :encode-length-fun encoded-length-base32
  :decode-length-fun decoded-length-base32
  :encoder-fun base32-encoder
  :decoder-fun base32-decoder)