--- /dev/null
+! Copyright (C) 2022 John Benediktsson
+! See http://factorcode.org/license.txt for BSD license
+
+USING: base32hex byte-arrays kernel sequences tools.test ;
+
+{ t } [ 256 <iota> >byte-array dup >base32hex base32hex> = ] unit-test
+
+{ B{ } } [ f >base32hex ] unit-test
+{ B{ } } [ B{ } >base32hex ] unit-test
+{ "00======" } [ "\0" >base32hex "" like ] unit-test
+{ "C4======" } [ "a" >base32hex "" like ] unit-test
+{ "C5H0====" } [ "ab" >base32hex "" like ] unit-test
+{ "C5H66===" } [ "abc" >base32hex "" like ] unit-test
+{ "C5H66P0=" } [ "abcd" >base32hex "" like ] unit-test
+{ "C5H66P35" } [ "abcde" >base32hex "" like ] unit-test
+
+{ B{ } } [ f base32hex> ] unit-test
+{ B{ } } [ B{ } base32hex> ] unit-test
+{ "\0" } [ "00======" base32hex> "" like ] unit-test
+{ "a" } [ "C4======" base32hex> "" like ] unit-test
+{ "ab" } [ "C5H0====" base32hex> "" like ] unit-test
+{ "abc" } [ "C5H66===" base32hex> "" like ] unit-test
+{ "abcd" } [ "C5H66P0=" base32hex> "" like ] unit-test
+{ "abcde" } [ "C5H66P35" base32hex> "" like ] unit-test
--- /dev/null
+! Copyright (C) 2022 John Benediktsson
+! See http://factorcode.org/license.txt for BSD license
+USING: base64.private byte-arrays combinators endian io
+io.encodings.binary io.streams.byte-array kernel kernel.private
+literals math namespaces sequences ;
+IN: base32hex
+
+ERROR: malformed-base32hex ;
+
+! XXX: Optional map 0 as O
+! XXX: Optional map 1 as L or I
+! XXX: Optional handle lower-case input
+
+<PRIVATE
+
+<<
+CONSTANT: alphabet $[ "0123456789ABCDEFGHIJKLMNOPQRSTUV" >byte-array ]
+>>
+
+: ch>base32hex ( ch -- ch )
+ alphabet nth ; inline
+
+: base32hex>ch ( ch -- ch )
+ $[ alphabet alphabet-inverse 0 CHAR: = pick set-nth ] nth
+ [ malformed-base32hex ] unless* { fixnum } declare ; inline
+
+: encode5 ( seq -- byte-array )
+ be> { -35 -30 -25 -20 -15 -10 -5 0 } '[
+ shift 0x1f bitand ch>base32hex
+ ] with B{ } map-as ; inline
+
+: encode-pad ( seq n -- byte-array )
+ [ 5 0 pad-tail encode5 ] [ B{ 0 2 4 5 7 } nth ] bi* head-slice
+ 8 CHAR: = pad-tail ; inline
+
+: (encode-base32hex) ( stream column -- )
+ 5 pick stream-read dup length {
+ { 0 [ 3drop ] }
+ { 5 [ encode5 write-lines (encode-base32hex) ] }
+ [ encode-pad write-lines (encode-base32hex) ]
+ } case ;
+
+PRIVATE>
+
+: encode-base32hex ( -- )
+ input-stream get f (encode-base32hex) ;
+
+: encode-base32hex-lines ( -- )
+ input-stream get 0 (encode-base32hex) ;
+
+<PRIVATE
+
+: decode8 ( seq -- )
+ [ 0 [ base32hex>ch swap 5 shift bitor ] reduce 5 >be ]
+ [ [ CHAR: = = ] count ] bi
+ [ write ] [ B{ 0 4 0 3 2 0 1 } nth head-slice write ] if-zero ; inline
+
+: (decode-base32hex) ( stream -- )
+ 8 "\n\r" pick read-ignoring dup length {
+ { 0 [ 2drop ] }
+ { 8 [ decode8 (decode-base32hex) ] }
+ [ drop 8 CHAR: = pad-tail decode8 (decode-base32hex) ]
+ } case ;
+
+PRIVATE>
+
+: decode-base32hex ( -- )
+ input-stream get (decode-base32hex) ;
+
+: >base32hex ( seq -- base32 )
+ binary [ binary [ encode-base32hex ] with-byte-reader ] with-byte-writer ;
+
+: base32hex> ( base32 -- seq )
+ binary [ binary [ decode-base32hex ] with-byte-reader ] with-byte-writer ;
+
+: >base32hex-lines ( seq -- base32 )
+ binary [ binary [ encode-base32hex-lines ] with-byte-reader ] with-byte-writer ;