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