Skip to content

Commit e816a9c

Browse files
committed
Implement base32
1 parent 3d81de9 commit e816a9c

File tree

3 files changed

+78
-131
lines changed

3 files changed

+78
-131
lines changed

src/Base32.php

Lines changed: 22 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -3,132 +3,35 @@
33

44
class Base32{
55

6-
protected const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
7-
8-
protected const BASE32HEX_PATTERN = '/[^A-Z2-7]/';
9-
10-
// protected const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUV=';
11-
//
12-
// protected const BASE32HEX_PATTERN = '/[^0-9A-V]/';
13-
14-
protected const MAPPING = [
15-
'=' => 0b00000,
16-
'A' => 0b00000,
17-
'B' => 0b00001,
18-
'C' => 0b00010,
19-
'D' => 0b00011,
20-
'E' => 0b00100,
21-
'F' => 0b00101,
22-
'G' => 0b00110,
23-
'H' => 0b00111,
24-
'I' => 0b01000,
25-
'J' => 0b01001,
26-
'K' => 0b01010,
27-
'L' => 0b01011,
28-
'M' => 0b01100,
29-
'N' => 0b01101,
30-
'O' => 0b01110,
31-
'P' => 0b01111,
32-
'Q' => 0b10000,
33-
'R' => 0b10001,
34-
'S' => 0b10010,
35-
'T' => 0b10011,
36-
'U' => 0b10100,
37-
'V' => 0b10101,
38-
'W' => 0b10110,
39-
'X' => 0b10111,
40-
'Y' => 0b11000,
41-
'Z' => 0b11001,
42-
'2' => 0b11010,
43-
'3' => 0b11011,
44-
'4' => 0b11100,
45-
'5' => 0b11101,
46-
'6' => 0b11110,
47-
'7' => 0b11111,
48-
];
49-
50-
protected const MAPPING_HEX = [
51-
'=' => 0b00000,
52-
'0' => 0b00000,
53-
'1' => 0b00001,
54-
'2' => 0b00010,
55-
'3' => 0b00011,
56-
'4' => 0b00100,
57-
'5' => 0b00101,
58-
'6' => 0b00110,
59-
'7' => 0b00111,
60-
'8' => 0b01000,
61-
'9' => 0b01001,
62-
'A' => 0b01010,
63-
'B' => 0b01011,
64-
'C' => 0b01100,
65-
'D' => 0b01101,
66-
'E' => 0b01110,
67-
'F' => 0b01111,
68-
'G' => 0b10000,
69-
'H' => 0b10001,
70-
'I' => 0b10010,
71-
'J' => 0b10011,
72-
'K' => 0b10100,
73-
'L' => 0b10101,
74-
'M' => 0b10110,
75-
'N' => 0b10111,
76-
'O' => 0b11000,
77-
'P' => 0b11001,
78-
'Q' => 0b11010,
79-
'R' => 0b11011,
80-
'S' => 0b11100,
81-
'T' => 0b11101,
82-
'U' => 0b11110,
83-
'V' => 0b11111,
84-
];
85-
86-
public static function decode(string $data): string{
87-
// Only work in upper cases
88-
$base32String = strtoupper($data);
89-
90-
// Remove anything that is not base32 alphabet
91-
$base32String = preg_replace(static::BASE32HEX_PATTERN, '', $base32String);
92-
93-
// Empty string results in empty string
94-
if ('' === $base32String || null === $base32String) {
6+
public static function decode(string $data,string $alphabet): string{
7+
if (empty($data)) {
958
return '';
969
}
9710

98-
$decoded = '';
99-
100-
//Set the initial values
101-
$len = strlen($base32String);
102-
$n = 0;
103-
$bitLen = 5;
104-
$val = static::MAPPING[$base32String[0]];
105-
106-
while ($n < $len) {
107-
//If the bit length has fallen below 8, shift left 5 and add the next pentet.
108-
if ($bitLen < 8) {
109-
$val = $val << 5;
110-
$bitLen += 5;
111-
$n++;
112-
$pentet = $base32String[$n] ?? '=';
11+
$data = str_split($data);
12+
$data = array_map(static function ($character) use($alphabet) {
13+
if ($character !== $alphabet[strlen($alphabet)-1]) {
14+
$index = strpos($alphabet, $character);
15+
return sprintf('%05b', $index);
16+
}
17+
}, $data);
18+
$binary = implode('', $data);
11319

114-
//If the new pentet is padding, make this the last iteration.
115-
if ('=' === $pentet) {
116-
$n = $len;
117-
}
118-
$val += static::MAPPING[$pentet];
119-
} else {
120-
$shift = $bitLen - 8;
20+
/* Split to eight bit chunks. */
21+
$data = str_split($binary, 8);
12122

122-
$decoded .= \chr($val >> $shift);
123-
$val = $val & ((1 << $shift) - 1);
124-
$bitLen -= 8;
125-
}
23+
/* Make sure binary is divisible by eight by ignoring the incomplete byte. */
24+
$last = array_pop($data);
25+
if ((null !== $last) && (8 === strlen($last))) {
26+
$data[] = $last;
12627
}
12728

128-
return $decoded;
29+
return implode('', array_map(function ($byte) {
30+
return chr((int)bindec($byte));
31+
}, $data));
12932
}
13033

131-
public static function encode(string $data): string{
34+
public static function encode(string $data,string $alphabet): string{
13235
// Empty string results in empty string
13336
if ('' === $data) {
13437
return '';
@@ -155,12 +58,12 @@ public static function encode(string $data): string{
15558
$val += $chars[$n];
15659
}
15760
$shift = $bitLen - 5;
158-
$encoded .= ($n - (int)($bitLen > 8) > $len && 0 == $val) ? '=' : static::ALPHABET[$val >> $shift];
61+
$encoded .= ($n - (int)($bitLen > 8) > $len && 0 == $val) ? '=' : $alphabet[$val >> $shift];
15962
$val = $val & ((1 << $shift) - 1);
16063
$bitLen -= 5;
16164
}
16265

163-
return $encoded;
66+
return strtolower($encoded);
16467
}
16568

16669
}

src/Multibase.php

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ class Multibase{
3030
public const BASE64URLPAD = 'U';
3131
/*DRAFT*/public const PROQUINT = 'p';
3232

33-
private const BITCOIN = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
34-
private const FLICKR = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
33+
private const ALPHABET32 = 'abcdefghijklmnopqrstuvwxyz234567=';
34+
private const ALPHABET32_HEX = '0123456789abcdefghijklmnopqrstuv=';
35+
private const ALPHABET32_ZOOKO = 'ybndrfg8ejkmcpqxot1uwisza345h769=';
36+
37+
private const ALPHABET58_BITCOIN = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
38+
private const ALPHABET58_FLICKR = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
3539

3640
public static function decode(string $data): string{
3741
$b = substr($data,0,1);
@@ -55,18 +59,36 @@ public static function decode(string $data): string{
5559
case self::BASE16UPPER:{
5660
return Base16::decode(strtolower($rest));
5761
}
58-
//TODO base 32
62+
case self::BASE32HEX:
63+
case self::BASE32HEXPAD:{
64+
return Base32::decode($rest,self::ALPHABET32_HEX);
65+
}
66+
case self::BASE32HEXUPPER:
67+
case self::BASE32HEXPADUPPER:{
68+
return Base32::decode(strtolower($rest),self::ALPHABET32_HEX);
69+
}
70+
case self::BASE32:
71+
case self::BASE32PAD:{
72+
return Base32::decode($rest,self::ALPHABET32);
73+
}
74+
case self::BASE32UPPER:
75+
case self::BASE32PADUPPER:{
76+
return Base32::decode(strtolower($rest),self::ALPHABET32);
77+
}
78+
case self::BASE32Z:{
79+
return Base32::decode($rest,self::ALPHABET32_ZOOKO);
80+
}
5981
case self::BASE36:{
6082
return Base36::decode($rest);
6183
}
6284
case self::BASE36UPPER:{
6385
return Base36::decode(strtolower($rest));
6486
}
6587
case self::BASE58BTC:{
66-
return Base58::decode($rest,self::BITCOIN);
88+
return Base58::decode($rest,self::ALPHABET58_BITCOIN);
6789
}
6890
case self::BASE58FLICKR:{
69-
return Base58::decode($rest,self::FLICKR);
91+
return Base58::decode($rest,self::ALPHABET58_FLICKR);
7092
}
7193
case self::BASE64:
7294
case self::BASE64PAD:{
@@ -102,18 +124,44 @@ public static function encode(string $b,string $data){
102124
case self::BASE16UPPER:{
103125
return $b.strtoupper(Base16::encode($data));
104126
}
105-
//TODO base 32
127+
case self::BASE32HEX:{
128+
return $b.str_replace('=','',Base32::encode($data,self::ALPHABET32_HEX));
129+
}
130+
case self::BASE32HEXUPPER:{
131+
return $b.str_replace('=','',strtoupper(Base32::encode($data,self::ALPHABET32_HEX)));
132+
}
133+
case self::BASE32HEXPAD:{
134+
return $b.Base32::encode($data,self::ALPHABET32_HEX);
135+
}
136+
case self::BASE32HEXPADUPPER:{
137+
return $b.strtoupper(Base32::encode($data,self::ALPHABET32_HEX));
138+
}
139+
case self::BASE32:{
140+
return $b.str_replace('=','',Base32::encode($data,self::ALPHABET32));
141+
}
142+
case self::BASE32UPPER:{
143+
return $b.str_replace('=','',strtoupper(Base32::encode($data,self::ALPHABET32)));
144+
}
145+
case self::BASE32PAD:{
146+
return $b.Base32::encode($data,self::ALPHABET32);
147+
}
148+
case self::BASE32PADUPPER:{
149+
return $b.strtoupper(Base32::encode($data,self::ALPHABET32));
150+
}
151+
case self::BASE32Z:{
152+
return $b.str_replace('=','',Base32::encode($data,self::ALPHABET32_ZOOKO));
153+
}
106154
case self::BASE36:{
107155
return $b.Base36::encode($data);
108156
}
109157
case self::BASE36UPPER:{
110158
return $b.strtoupper(Base36::encode($data));
111159
}
112160
case self::BASE58BTC:{
113-
return $b.Base58::encode($data,self::BITCOIN);
161+
return $b.Base58::encode($data,self::ALPHABET58_BITCOIN);
114162
}
115163
case self::BASE58FLICKR:{
116-
return $b.Base58::encode($data,self::FLICKR);
164+
return $b.Base58::encode($data,self::ALPHABET58_FLICKR);
117165
}
118166
case self::BASE64:{
119167
return $b.str_replace('=','',Base64::encode($data));

tests/TestUtil.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ public static function runTest($test,$csv,$encode=true,$decode=true){
2323
$vector = $csv[$i][1];
2424

2525
$constantName = Multibase::class.'::'.strtoupper($encoding);
26-
if(strpos($constantName,'32')){
27-
//SKIP base32 for now
28-
continue;
29-
}
3026

3127
if($encode){
3228
$test->assertEquals($vector,Multibase::encode(constant($constantName),$data),'Encoding '.$encoding);

0 commit comments

Comments
 (0)