Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2026 Project CHIP Authors
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 :
19 : /**
20 : * @file
21 : * Base-85 utility functions (RFC 1924 alphabet, git/Python compatible).
22 : */
23 :
24 : #include "Base85.h"
25 :
26 : #include <string.h>
27 :
28 : namespace chip {
29 :
30 : // RFC 1924 alphabet: 0-9 A-Z a-z !#$%&()*+-;<=>?@^_`{|}~
31 : // Values 0-9 map to '0'-'9', 10-35 to 'A'-'Z', 36-61 to 'a'-'z',
32 : // and values 62-84 map to the 23 punctuation characters below.
33 :
34 : // Convert a value in the range 0..84 to its RFC 1924 base85 character.
35 133 : static char Base85ValToChar(uint8_t val)
36 : {
37 133 : switch (val)
38 : {
39 : // clang-format off
40 1 : case 62: return '!';
41 1 : case 63: return '#';
42 1 : case 64: return '$';
43 3 : case 65: return '%';
44 2 : case 66: return '&';
45 1 : case 67: return '(';
46 2 : case 68: return ')';
47 1 : case 69: return '*';
48 2 : case 70: return '+';
49 6 : case 71: return '-';
50 1 : case 72: return ';';
51 2 : case 73: return '<';
52 2 : case 74: return '=';
53 1 : case 75: return '>';
54 1 : case 76: return '?';
55 2 : case 77: return '@';
56 2 : case 78: return '^';
57 2 : case 79: return '_';
58 2 : case 80: return '`';
59 2 : case 81: return '{';
60 5 : case 82: return '|';
61 2 : case 83: return '}';
62 2 : case 84: return '~';
63 : // clang-format on
64 87 : default:
65 87 : if (val < 10)
66 : {
67 26 : return static_cast<char>('0' + val);
68 : }
69 61 : if (10 <= val && val < 36)
70 : {
71 30 : return static_cast<char>('A' + val - 10);
72 : }
73 31 : if (36 <= val && val < 62)
74 : {
75 31 : return static_cast<char>('a' + val - 36);
76 : }
77 : }
78 0 : return 0; // not possible
79 : }
80 :
81 : // Convert an RFC 1924 base85 character to a value in the range 0..84,
82 : // or UINT8_MAX if the character is not in the alphabet.
83 134 : static uint8_t Base85CharToVal(char c)
84 : {
85 134 : switch (c)
86 : {
87 : // clang-format off
88 1 : case '!': return 62;
89 1 : case '#': return 63;
90 1 : case '$': return 64;
91 2 : case '%': return 65;
92 2 : case '&': return 66;
93 1 : case '(': return 67;
94 2 : case ')': return 68;
95 1 : case '*': return 69;
96 2 : case '+': return 70;
97 5 : case '-': return 71;
98 1 : case ';': return 72;
99 2 : case '<': return 73;
100 2 : case '=': return 74;
101 1 : case '>': return 75;
102 1 : case '?': return 76;
103 2 : case '@': return 77;
104 2 : case '^': return 78;
105 2 : case '_': return 79;
106 2 : case '`': return 80;
107 1 : case '{': return 81;
108 4 : case '|': return 82;
109 1 : case '}': return 83;
110 7 : case '~': return 84;
111 : // clang-format on
112 88 : default:
113 88 : if (c >= '0' && c <= '9')
114 : {
115 30 : return static_cast<uint8_t>(c - '0');
116 : }
117 58 : if (c >= 'A' && c <= 'Z')
118 : {
119 28 : return static_cast<uint8_t>(c - 'A' + 10);
120 : }
121 30 : if (c >= 'a' && c <= 'z')
122 : {
123 27 : return static_cast<uint8_t>(c - 'a' + 36);
124 : }
125 : }
126 3 : return UINT8_MAX;
127 : }
128 :
129 25 : static uint32_t ReadGroup(const uint8_t * src, size_t count)
130 : {
131 25 : uint8_t padded[4] = { 0 };
132 25 : memcpy(padded, src, count);
133 25 : return (static_cast<uint32_t>(padded[0]) << 24) | //
134 25 : (static_cast<uint32_t>(padded[1]) << 16) | //
135 25 : (static_cast<uint32_t>(padded[2]) << 8) | //
136 25 : (static_cast<uint32_t>(padded[3]));
137 : }
138 :
139 25 : static void WriteGroup(uint32_t value, uint8_t * dest, size_t count)
140 : {
141 : uint8_t decoded[4] = {
142 25 : static_cast<uint8_t>(value >> 24),
143 25 : static_cast<uint8_t>(value >> 16),
144 25 : static_cast<uint8_t>(value >> 8),
145 : static_cast<uint8_t>(value),
146 25 : };
147 25 : memcpy(dest, decoded, count);
148 25 : }
149 :
150 31 : static bool DecodeGroup(const char * src, size_t count, uint32_t & outValue)
151 : {
152 : // "|NsC0" is the largest allowed group value (UINT32_MAX); we could
153 : // check against this value here directly, but it is simpler to do the
154 : // calculation in a uint64_t and validate the decoded result.
155 31 : uint64_t value = 0;
156 162 : for (size_t j = 0; j < count; j++)
157 : {
158 134 : uint8_t digit = Base85CharToVal(src[j]);
159 134 : VerifyOrReturnValue(digit != UINT8_MAX, false);
160 131 : value = value * 85 + digit;
161 : }
162 43 : for (size_t j = count; j < 5; j++)
163 : {
164 15 : value = value * 85 + 84; // implicitly pad to 5 "digits" with '~' (84)
165 : }
166 28 : VerifyOrReturnValue(value <= UINT32_MAX, false);
167 26 : outValue = static_cast<uint32_t>(value);
168 26 : return true;
169 : }
170 :
171 32 : static void EncodeGroup(uint32_t value, char * out, size_t count)
172 : {
173 59 : for (size_t j = 5; j-- > count;)
174 : {
175 27 : value /= 85; // truncate least significant "digits"
176 : }
177 165 : for (size_t j = count; j-- > 0;)
178 : {
179 133 : out[j] = Base85ValToChar(static_cast<uint8_t>(value % 85));
180 133 : value /= 85;
181 : }
182 32 : }
183 :
184 16 : CHIP_ERROR BytesToBase85(const uint8_t * src, size_t srcSize, char * dest, size_t destSize)
185 : {
186 16 : VerifyOrReturnError(src != nullptr || srcSize == 0, CHIP_ERROR_INVALID_ARGUMENT);
187 15 : VerifyOrReturnError(dest != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
188 : // Base85EncodedLength(srcSize) could overflow / saturate to SIZE_MAX,
189 : // checking via Base85DecodedLength(destSize) instead avoids this issue.
190 14 : VerifyOrReturnError(Base85DecodedLength(destSize) >= srcSize, CHIP_ERROR_BUFFER_TOO_SMALL);
191 :
192 13 : size_t remainder = srcSize % 4;
193 32 : for (auto * end = src + (srcSize - remainder); src < end; src += 4, dest += 5)
194 : {
195 19 : EncodeGroup(ReadGroup(src, 4), dest, 5);
196 : }
197 13 : if (remainder > 0)
198 : {
199 6 : EncodeGroup(ReadGroup(src, remainder), dest, remainder + 1);
200 : }
201 13 : return CHIP_NO_ERROR;
202 : }
203 :
204 23 : CHIP_ERROR Base85ToBytes(const char * src, size_t srcSize, uint8_t * dest, size_t destSize)
205 : {
206 23 : VerifyOrReturnError(src != nullptr || srcSize == 0, CHIP_ERROR_INVALID_ARGUMENT);
207 22 : VerifyOrReturnError(dest != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
208 21 : VerifyOrReturnError(destSize >= Base85DecodedLength(srcSize), CHIP_ERROR_BUFFER_TOO_SMALL);
209 :
210 : uint32_t value;
211 21 : size_t remainder = srcSize % 5;
212 21 : VerifyOrReturnError(remainder != 1, CHIP_ERROR_INVALID_ARGUMENT); // 1 "digit" does not encode a full byte
213 38 : for (auto * end = src + (srcSize - remainder); src < end; src += 5, dest += 4)
214 : {
215 24 : VerifyOrReturnError(DecodeGroup(src, 5, value), CHIP_ERROR_INVALID_ARGUMENT);
216 19 : WriteGroup(value, dest, 4);
217 : }
218 14 : if (remainder > 0) // 2..4
219 : {
220 7 : VerifyOrReturnError(DecodeGroup(src, remainder, value), CHIP_ERROR_INVALID_ARGUMENT);
221 :
222 : char check[4];
223 7 : uint32_t padding = UINT32_MAX >> (8 * (remainder - 1));
224 7 : EncodeGroup(value & ~padding, check, remainder);
225 7 : VerifyOrReturnError(memcmp(src, check, remainder) == 0, CHIP_ERROR_INVALID_ARGUMENT);
226 :
227 6 : WriteGroup(value, dest, remainder - 1); // might clobber src
228 : }
229 13 : return CHIP_NO_ERROR;
230 : }
231 :
232 : } // namespace chip
|