taler-mailbox

Service for asynchronous wallet-to-wallet payment messages
Log | Files | Refs | Submodules | README | LICENSE

helper.go (3769B)


      1 // This file is part of taldir, the Taler Directory implementation.
      2 // Copyright (C) 2025 Martin Schanzenbach
      3 //
      4 // Taldir is free software: you can redistribute it and/or modify it
      5 // under the terms of the GNU Affero General Public License as published
      6 // by the Free Software Foundation, either version 3 of the License,
      7 // or (at your option) any later version.
      8 //
      9 // Taldir is distributed in the hope that it will be useful, but
     10 // WITHOUT ANY WARRANTY; without even the implied warranty of
     11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12 // Affero General Public License for more details.
     13 //
     14 // You should have received a copy of the GNU Affero General Public License
     15 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16 //
     17 // SPDX-License-Identifier: AGPL3.0-or-later
     18 
     19 package util
     20 
     21 import (
     22   "errors"
     23   "strings"
     24 )
     25 
     26 //------------------------------------------------------------------------
     27 // Base32 conversion between binary data and string representation
     28 //------------------------------------------------------------------------
     29 //
     30 // A binary array of size m is viewed as a consecutive stream of bits
     31 // from left to right. Bytes are ordered with ascending address, while
     32 // bits (in a byte) are ordered MSB to LSB.
     33 
     34 // For encoding the stream is partitioned into 5-bit chunks; the last chunk
     35 // is right-padded with 0's if 8*m is not divisible by 5. Each chunk (value
     36 // between 0 and 31) is encoded into a character; the mapping for encoding
     37 // is the same as in [https://www.crockford.com/wrmg/base32.html].
     38 //
     39 // For decoding each character is converted to a 5-bit chunk based on the
     40 // encoder mapping (with one addition: the character 'U' maps to the value
     41 // 27). The chunks are concatenated to produce the bit stream to be stored
     42 // in the output array.
     43 
     44 // character set used for encoding/decoding
     45 const xlate = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
     46 
     47 var (
     48 	// ErrInvalidEncoding signals an invalid encoding
     49 	ErrInvalidEncoding = errors.New("invalid encoding")
     50 	// ErrBufferTooSmall signalsa too small buffer for decoding
     51 	ErrBufferTooSmall = errors.New("buffer to small")
     52 )
     53 
     54 // EncodeBinaryToString encodes a byte array into a string.
     55 func Base32CrockfordEncode(data []byte) string {
     56 	size, pos, bits, n := len(data), 0, 0, 0
     57 	out := ""
     58 	for {
     59 		if n < 5 {
     60 			if pos < size {
     61 				bits = (bits << 8) | (int(data[pos]) & 0xFF)
     62 				pos++
     63 				n += 8
     64 			} else if n > 0 {
     65 				bits <<= uint(5 - n)
     66 				n = 5
     67 			} else {
     68 				break
     69 			}
     70 		}
     71 		out += string(xlate[(bits>>uint(n-5))&0x1F])
     72 		n -= 5
     73 	}
     74 	return out
     75 }
     76 
     77 // Base32CrockfordDecode decodes a string into a byte array.
     78 // The function expects the size of the output buffer to be specified as an
     79 // argument ('num'); the function returns an error if the buffer is overrun
     80 // or if an invalid character is found in the encoded string. If the decoded
     81 // bit stream is smaller than the output buffer, it is padded with 0's.
     82 func Base32CrockfordDecode(s string, num int) ([]byte, error) {
     83 	size := len(s)
     84 	out := make([]byte, num)
     85 	rpos, wpos, n, bits := 0, 0, 0, 0
     86 	for {
     87 		if n < 8 {
     88 			if rpos < size {
     89 				c := rune(s[rpos])
     90 				rpos++
     91 				v := strings.IndexRune(xlate, c)
     92 				if v == -1 {
     93 					switch c {
     94 					case 'O':
     95 						v = 0
     96 					case 'I', 'L':
     97 						v = 1
     98 					case 'U':
     99 						v = 27
    100 					default:
    101 						return nil, ErrInvalidEncoding
    102 					}
    103 				}
    104 				bits = (bits << 5) | (v & 0x1F)
    105 				n += 5
    106 			} else {
    107 				if wpos < num {
    108 					out[wpos] = byte(bits & ((1 << uint(n+1)) - 1))
    109 					wpos++
    110 					for i := wpos; i < num; i++ {
    111 						out[i] = 0
    112 					}
    113 				}
    114 				break
    115 			}
    116 		} else {
    117 			if wpos < num {
    118 				out[wpos] = byte((bits >> uint(n-8)) & 0xFF)
    119 				wpos++
    120 				n -= 8
    121 			} else {
    122 				return nil, ErrBufferTooSmall
    123 			}
    124 		}
    125 	}
    126 	return out, nil
    127 }