1 /*
2  * Copyright (c)2019 ZeroTier, Inc.
3  *
4  * Use of this software is governed by the Business Source License included
5  * in the LICENSE.TXT file in the project's root directory.
6  *
7  * Change Date: 2025-01-01
8  *
9  * On the date above, in accordance with the Business Source License, use
10  * of this software will be governed by version 2.0 of the Apache License.
11  */
12 /****/
13 
14 #ifndef ZT_DICTIONARY_HPP
15 #define ZT_DICTIONARY_HPP
16 
17 #include "Constants.hpp"
18 #include "Utils.hpp"
19 #include "Buffer.hpp"
20 #include "Address.hpp"
21 
22 #include <stdint.h>
23 
24 namespace ZeroTier {
25 
26 /**
27  * A small (in code and data) packed key=value store
28  *
29  * This stores data in the form of a compact blob that is sort of human
30  * readable (depending on whether you put binary data in it) and is backward
31  * compatible with older versions. Binary data is escaped such that the
32  * serialized form of a Dictionary is always a valid null-terminated C string.
33  *
34  * Keys are restricted: no binary data, no CR/LF, and no equals (=). If a key
35  * contains these characters it may not be retrievable. This is not checked.
36  *
37  * Lookup is via linear search and will be slow with a lot of keys. It's
38  * designed for small things.
39  *
40  * There is code to test and fuzz this in selftest.cpp. Fuzzing a blob of
41  * pointer tricks like this is important after any modifications.
42  *
43  * This is used for network configurations and for saving some things on disk
44  * in the ZeroTier One service code.
45  *
46  * @tparam C Dictionary max capacity in bytes
47  */
48 template<unsigned int C>
49 class Dictionary
50 {
51 public:
Dictionary()52 	Dictionary() { memset(_d,0,sizeof(_d)); }
Dictionary(const char * s)53 	Dictionary(const char *s) { this->load(s); }
Dictionary(const char * s,unsigned int len)54 	Dictionary(const char *s,unsigned int len)
55 	{
56 		for(unsigned int i=0;i<C;++i) {
57 			if ((s)&&(i < len)) {
58 				if (!(_d[i] = *s))
59 					s = (const char *)0;
60 				else ++s;
61 			} else _d[i] = (char)0;
62 		}
63 		_d[C - 1] = (char)0;
64 	}
Dictionary(const Dictionary & d)65 	Dictionary(const Dictionary &d) { memcpy(_d,d._d,C); }
66 
operator =(const Dictionary & d)67 	inline Dictionary &operator=(const Dictionary &d)
68 	{
69 		memcpy(_d,d._d,C);
70 		return *this;
71 	}
72 
operator bool() const73 	inline operator bool() const { return (_d[0] != 0); }
74 
75 	/**
76 	 * Load a dictionary from a C-string
77 	 *
78 	 * @param s Dictionary in string form
79 	 * @return False if 's' was longer than our capacity
80 	 */
load(const char * s)81 	inline bool load(const char *s)
82 	{
83 		for(unsigned int i=0;i<C;++i) {
84 			if (s) {
85 				if (!(_d[i] = *s))
86 					s = (const char *)0;
87 				else ++s;
88 			} else _d[i] = (char)0;
89 		}
90 		_d[C - 1] = (char)0;
91 		return (!s);
92 	}
93 
94 	/**
95 	 * Delete all entries
96 	 */
clear()97 	inline void clear()
98 	{
99 		memset(_d,0,sizeof(_d));
100 	}
101 
102 	/**
103 	 * @return Size of dictionary in bytes not including terminating NULL
104 	 */
sizeBytes() const105 	inline unsigned int sizeBytes() const
106 	{
107 		for(unsigned int i=0;i<C;++i) {
108 			if (!_d[i])
109 				return i;
110 		}
111 		return C-1;
112 	}
113 
114 	/**
115 	 * Get an entry
116 	 *
117 	 * Note that to get binary values, dest[] should be at least one more than
118 	 * the maximum size of the value being retrieved. That's because even if
119 	 * the data is binary a terminating 0 is still appended to dest[] after it.
120 	 *
121 	 * If the key is not found, dest[0] is set to 0 to make dest[] an empty
122 	 * C string in that case. The dest[] array will *never* be unterminated
123 	 * after this call.
124 	 *
125 	 * Security note: if 'key' is ever directly based on anything that is not
126 	 * a hard-code or internally-generated name, it must be checked to ensure
127 	 * that the buffer is NULL-terminated since key[] does not take a secondary
128 	 * size parameter. In NetworkConfig all keys are hard-coded strings so this
129 	 * isn't a problem in the core.
130 	 *
131 	 * @param key Key to look up
132 	 * @param dest Destination buffer
133 	 * @param destlen Size of destination buffer
134 	 * @return -1 if not found, or actual number of bytes stored in dest[] minus trailing 0
135 	 */
get(const char * key,char * dest,unsigned int destlen) const136 	inline int get(const char *key,char *dest,unsigned int destlen) const
137 	{
138 		const char *p = _d;
139 		const char *const eof = p + C;
140 		const char *k;
141 		bool esc;
142 		int j;
143 
144 		if (!destlen) // sanity check
145 			return -1;
146 
147 		while (*p) {
148 			k = key;
149 			while ((*k)&&(*p)) {
150 				if (*p != *k)
151 					break;
152 				++k;
153 				if (++p == eof) {
154 					dest[0] = (char)0;
155 					return -1;
156 				}
157 			}
158 
159 			if ((!*k)&&(*p == '=')) {
160 				j = 0;
161 				esc = false;
162 				++p;
163 				while ((*p != 0)&&(*p != 13)&&(*p != 10)) {
164 					if (esc) {
165 						esc = false;
166 						switch(*p) {
167 							case 'r': dest[j++] = 13; break;
168 							case 'n': dest[j++] = 10; break;
169 							case '0': dest[j++] = (char)0; break;
170 							case 'e': dest[j++] = '='; break;
171 							default: dest[j++] = *p; break;
172 						}
173 						if (j == (int)destlen) {
174 							dest[j-1] = (char)0;
175 							return j-1;
176 						}
177 					} else if (*p == '\\') {
178 						esc = true;
179 					} else {
180 						dest[j++] = *p;
181 						if (j == (int)destlen) {
182 							dest[j-1] = (char)0;
183 							return j-1;
184 						}
185 					}
186 					if (++p == eof) {
187 						dest[0] = (char)0;
188 						return -1;
189 					}
190 				}
191 				dest[j] = (char)0;
192 				return j;
193 			} else {
194 				while ((*p)&&(*p != 13)&&(*p != 10)) {
195 					if (++p == eof) {
196 						dest[0] = (char)0;
197 						return -1;
198 					}
199 				}
200 				if (*p) {
201 					if (++p == eof) {
202 						dest[0] = (char)0;
203 						return -1;
204 					}
205 				}
206 				else break;
207 			}
208 		}
209 
210 		dest[0] = (char)0;
211 		return -1;
212 	}
213 
214 	/**
215 	 * Get the contents of a key into a buffer
216 	 *
217 	 * @param key Key to get
218 	 * @param dest Destination buffer
219 	 * @return True if key was found (if false, dest will be empty)
220 	 * @tparam BC Buffer capacity (usually inferred)
221 	 */
222 	template<unsigned int BC>
get(const char * key,Buffer<BC> & dest) const223 	inline bool get(const char *key,Buffer<BC> &dest) const
224 	{
225 		const int r = this->get(key,const_cast<char *>(reinterpret_cast<const char *>(dest.data())),BC);
226 		if (r >= 0) {
227 			dest.setSize((unsigned int)r);
228 			return true;
229 		} else {
230 			dest.clear();
231 			return false;
232 		}
233 	}
234 
235 	/**
236 	 * Get a boolean value
237 	 *
238 	 * @param key Key to look up
239 	 * @param dfl Default value if not found in dictionary
240 	 * @return Boolean value of key or 'dfl' if not found
241 	 */
getB(const char * key,bool dfl=false) const242 	bool getB(const char *key,bool dfl = false) const
243 	{
244 		char tmp[4];
245 		if (this->get(key,tmp,sizeof(tmp)) >= 0)
246 			return ((*tmp == '1')||(*tmp == 't')||(*tmp == 'T'));
247 		return dfl;
248 	}
249 
250 	/**
251 	 * Get an unsigned int64 stored as hex in the dictionary
252 	 *
253 	 * @param key Key to look up
254 	 * @param dfl Default value or 0 if unspecified
255 	 * @return Decoded hex UInt value or 'dfl' if not found
256 	 */
getUI(const char * key,uint64_t dfl=0) const257 	inline uint64_t getUI(const char *key,uint64_t dfl = 0) const
258 	{
259 		char tmp[128];
260 		if (this->get(key,tmp,sizeof(tmp)) >= 1)
261 			return Utils::hexStrToU64(tmp);
262 		return dfl;
263 	}
264 
265 	/**
266 	 * Get an unsigned int64 stored as hex in the dictionary
267 	 *
268 	 * @param key Key to look up
269 	 * @param dfl Default value or 0 if unspecified
270 	 * @return Decoded hex UInt value or 'dfl' if not found
271 	 */
getI(const char * key,int64_t dfl=0) const272 	inline int64_t getI(const char *key,int64_t dfl = 0) const
273 	{
274 		char tmp[128];
275 		if (this->get(key,tmp,sizeof(tmp)) >= 1)
276 			return Utils::hexStrTo64(tmp);
277 		return dfl;
278 	}
279 
280 	/**
281 	 * Add a new key=value pair
282 	 *
283 	 * If the key is already present this will append another, but the first
284 	 * will always be returned by get(). This is not checked. If you want to
285 	 * ensure a key is not present use erase() first.
286 	 *
287 	 * Use the vlen parameter to add binary values. Nulls will be escaped.
288 	 *
289 	 * @param key Key -- nulls, CR/LF, and equals (=) are illegal characters
290 	 * @param value Value to set
291 	 * @param vlen Length of value in bytes or -1 to treat value[] as a C-string and look for terminating 0
292 	 * @return True if there was enough room to add this key=value pair
293 	 */
add(const char * key,const char * value,int vlen=-1)294 	inline bool add(const char *key,const char *value,int vlen = -1)
295 	{
296 		for(unsigned int i=0;i<C;++i) {
297 			if (!_d[i]) {
298 				unsigned int j = i;
299 
300 				if (j > 0) {
301 					_d[j++] = (char)10;
302 					if (j == C) {
303 						_d[i] = (char)0;
304 						return false;
305 					}
306 				}
307 
308 				const char *p = key;
309 				while (*p) {
310 					_d[j++] = *(p++);
311 					if (j == C) {
312 						_d[i] = (char)0;
313 						return false;
314 					}
315 				}
316 
317 				_d[j++] = '=';
318 				if (j == C) {
319 					_d[i] = (char)0;
320 					return false;
321 				}
322 
323 				p = value;
324 				int k = 0;
325 				while ( ((vlen < 0)&&(*p)) || (k < vlen) ) {
326 					switch(*p) {
327 						case 0:
328 						case 13:
329 						case 10:
330 						case '\\':
331 						case '=':
332 							_d[j++] = '\\';
333 							if (j == C) {
334 								_d[i] = (char)0;
335 								return false;
336 							}
337 							switch(*p) {
338 								case 0: _d[j++] = '0'; break;
339 								case 13: _d[j++] = 'r'; break;
340 								case 10: _d[j++] = 'n'; break;
341 								case '\\': _d[j++] = '\\'; break;
342 								case '=': _d[j++] = 'e'; break;
343 							}
344 							if (j == C) {
345 								_d[i] = (char)0;
346 								return false;
347 							}
348 							break;
349 						default:
350 							_d[j++] = *p;
351 							if (j == C) {
352 								_d[i] = (char)0;
353 								return false;
354 							}
355 							break;
356 					}
357 					++p;
358 					++k;
359 				}
360 
361 				_d[j] = (char)0;
362 
363 				return true;
364 			}
365 		}
366 		return false;
367 	}
368 
369 	/**
370 	 * Add a boolean as a '1' or a '0'
371 	 */
add(const char * key,bool value)372 	inline bool add(const char *key,bool value)
373 	{
374 		return this->add(key,(value) ? "1" : "0",1);
375 	}
376 
377 	/**
378 	 * Add a 64-bit integer (unsigned) as a hex value
379 	 */
add(const char * key,uint64_t value)380 	inline bool add(const char *key,uint64_t value)
381 	{
382 		char tmp[32];
383 		return this->add(key,Utils::hex(value,tmp),-1);
384 	}
385 
386 	/**
387 	 * Add a 64-bit integer (unsigned) as a hex value
388 	 */
add(const char * key,int64_t value)389 	inline bool add(const char *key,int64_t value)
390 	{
391 		char tmp[32];
392 		if (value >= 0) {
393 			return this->add(key,Utils::hex((uint64_t)value,tmp),-1);
394 		} else {
395 			tmp[0] = '-';
396 			return this->add(key,Utils::hex((uint64_t)(value * -1),tmp+1),-1);
397 		}
398 	}
399 
400 	/**
401 	 * Add a 64-bit integer (unsigned) as a hex value
402 	 */
add(const char * key,const Address & a)403 	inline bool add(const char *key,const Address &a)
404 	{
405 		char tmp[32];
406 		return this->add(key,Utils::hex(a.toInt(),tmp),-1);
407 	}
408 
409 	/**
410 	 * Add a binary buffer's contents as a value
411 	 *
412 	 * @tparam BC Buffer capacity (usually inferred)
413 	 */
414 	template<unsigned int BC>
add(const char * key,const Buffer<BC> & value)415 	inline bool add(const char *key,const Buffer<BC> &value)
416 	{
417 		return this->add(key,(const char *)value.data(),(int)value.size());
418 	}
419 
420 	/**
421 	 * @param key Key to check
422 	 * @return True if key is present
423 	 */
contains(const char * key) const424 	inline bool contains(const char *key) const
425 	{
426 		char tmp[2];
427 		return (this->get(key,tmp,2) >= 0);
428 	}
429 
430 	/**
431 	 * @return Value of C template parameter
432 	 */
capacity() const433 	inline unsigned int capacity() const { return C; }
434 
data() const435 	inline const char *data() const { return _d; }
unsafeData()436 	inline char *unsafeData() { return _d; }
437 
438 private:
439 	char _d[C];
440 };
441 
442 } // namespace ZeroTier
443 
444 #endif
445