Line data Source code
1 : //
2 : // pk.c
3 : // cloudsync
4 : //
5 : // Created by Marco Bambini on 21/08/24.
6 : //
7 :
8 : #include "pk.h"
9 : #include "utils.h"
10 : #include "cloudsync_endian.h"
11 : #include "cloudsync.h"
12 :
13 : #include <stdio.h>
14 : #include <string.h>
15 : #include <limits.h>
16 : #include <inttypes.h>
17 :
18 : /*
19 :
20 : The pk_encode and pk_decode functions are designed to serialize and deserialize an array of values (sqlite_value structures)
21 : into a binary format that can be transmitted over a network or stored efficiently.
22 : These functions support all the data types natively supported by SQLite (integer, float, blob, text, and null)
23 : and ensure that the serialized data is platform-independent, particularly with respect to endianess.
24 :
25 : pk_encode
26 : =========
27 : The pk_encode function encodes an array of values into a contiguous memory buffer.
28 : This buffer can then be sent over a network or saved to a file, ensuring that the data can be reliably reconstructed later, regardless of the platform.
29 :
30 : Algorithm:
31 :
32 : * Number of Columns: The first byte of the buffer stores the number of columns (num_args), which is limited to 255 columns.
33 : * Type and Length Encoding: For each column:
34 : * The type of the column (e.g., integer, float, text) is encoded in a single byte. The first 3 bits represent the type, and the remaining 5 bits encode the number of bytes required for the integer or length information if applicable.
35 : * If the column is an integer or a blob/text type, additional bytes are written to the buffer to store the actual value or the length of the data.
36 : * Endianess handling is applied using htonl/htonll to ensure integers and floating-point numbers are consistently stored in big-endian format (network byte order), making the serialized data platform-independent.
37 : * Floating-point numbers are treated as 64-bit integers for endianess conversion.
38 : * Efficient Storage: By using only the minimum number of bytes required to represent integers and lengths, the solution optimizes storage space, reducing the size of the serialized buffer.
39 :
40 : Advantages:
41 :
42 : * Platform Independence: By converting all integers and floating-point values to network byte order, the serialized data can be transmitted between systems with different endianess.
43 : * Efficiency: The function encodes data into the smallest possible format, minimizing the memory footprint of the serialized data. This is particularly important for network transmission and storage.
44 : * Flexibility: Supports multiple data types (integer, float, text, blob, null) and variable-length data, making it suitable for a wide range of applications.
45 :
46 : pk_decode
47 : =========
48 : The pk_decode function decodes the buffer created by pk_encode back into an array of sqlite_value structures.
49 : This allows the original data to be reconstructed and used by the application.
50 :
51 : Algorithm:
52 :
53 : * Read Number of Columns: The function starts by reading the first byte to determine the number of columns in the buffer.
54 : * Type and Length Decoding: For each column:
55 : * The function reads the type byte to determine the column's data type and the number of bytes used to store length or integer values.
56 : * Depending on the type, the appropriate number of bytes is read from the buffer to reconstruct the integer, floating-point value, blob, or text data.
57 : * Endianess is handled by converting from network byte order back to the host's byte order using ntohl/ntohll.
58 : * Memory Management: For blob and text data, memory is dynamically allocated to store the decoded data. The caller is responsible for freeing this memory after use.
59 :
60 : Advantages:
61 :
62 : * Correctness: By reversing the serialization process, the unpack_columns function ensures that the original data can be accurately reconstructed.
63 : * Endianess Handling: The function handles endianess conversion during decoding, ensuring that data is correctly interpreted regardless of the platform on which it was serialized or deserialized.
64 : * Robustness: The function includes error handling to manage cases where the buffer is malformed or insufficient data is available, reducing the risk of corruption or crashes.
65 :
66 : Overall Advantages of the Solution
67 :
68 : * Portability: The serialized format is platform-independent, ensuring data can be transmitted across different architectures without compatibility issues.
69 : * Efficiency: The use of compact encoding for integers and lengths reduces the size of the serialized data, optimizing it for storage and transmission.
70 : * Versatility: The ability to handle multiple data types and variable-length data makes this solution suitable for complex data structures.
71 : * Simplicity: The functions are designed to be straightforward to use, with clear memory management responsibilities.
72 :
73 : Notes
74 :
75 : * Floating point values are encoded as IEEE754 double, 64-bit, big-endian byte order.
76 :
77 : */
78 :
79 : // Three bits are reserved for the type field, so only values in the 0..7 range can be used (8 values)
80 : // SQLITE already reserved values from 1 to 5
81 : // #define SQLITE_INTEGER 1 // now DBTYPE_INTEGER
82 : // #define SQLITE_FLOAT 2 // now DBTYPE_FLOAT
83 : // #define SQLITE_TEXT 3 // now DBTYPE_TEXT
84 : // #define SQLITE_BLOB 4 // now DBTYPE_BLOB
85 : // #define SQLITE_NULL 5 // now DBTYPE_NULL
86 : #define DATABASE_TYPE_NEGATIVE_INTEGER 0 // was SQLITE_NEGATIVE_INTEGER
87 : #define DATABASE_TYPE_MAX_NEGATIVE_INTEGER 6 // was SQLITE_MAX_NEGATIVE_INTEGER
88 : #define DATABASE_TYPE_NEGATIVE_FLOAT 7 // was SQLITE_NEGATIVE_FLOAT
89 :
90 : char * const PRIKEY_NULL_CONSTRAINT_ERROR = "PRIKEY_NULL_CONSTRAINT_ERROR";
91 :
92 : // MARK: - Public Callbacks -
93 :
94 845954 : int pk_decode_bind_callback (void *xdata, int index, int type, int64_t ival, double dval, char *pval) {
95 : // default decode callback used to bind values to a dbvm_t vm
96 :
97 845954 : int rc = DBRES_OK;
98 845954 : switch (type) {
99 : case DBTYPE_INTEGER:
100 294068 : rc = databasevm_bind_int(xdata, index+1, ival);
101 294068 : break;
102 :
103 : case DBTYPE_FLOAT:
104 0 : rc = databasevm_bind_double(xdata, index+1, dval);
105 0 : break;
106 :
107 : case DBTYPE_NULL:
108 1991 : rc = databasevm_bind_null(xdata, index+1);
109 1991 : break;
110 :
111 : case DBTYPE_TEXT:
112 433437 : rc = databasevm_bind_text(xdata, index+1, pval, (int)ival);
113 433437 : break;
114 :
115 : case DBTYPE_BLOB:
116 116458 : rc = databasevm_bind_blob(xdata, index+1, (const void *)pval, ival);
117 116458 : break;
118 : }
119 :
120 845954 : return rc;
121 : }
122 :
123 7 : int pk_decode_print_callback (void *xdata, int index, int type, int64_t ival, double dval, char *pval) {
124 7 : switch (type) {
125 : case DBTYPE_INTEGER:
126 4 : printf("%d\tINTEGER:\t%" PRId64 "\n", index, ival);
127 4 : break;
128 :
129 : case DBTYPE_FLOAT:
130 1 : printf("%d\tFLOAT:\t%.5f\n", index, dval);
131 1 : break;
132 :
133 : case DBTYPE_NULL:
134 0 : printf("%d\tNULL\n", index);
135 0 : break;
136 :
137 : case DBTYPE_TEXT:
138 1 : printf("%d\tTEXT:\t%.*s\n", index, (int)ival, pval);
139 1 : break;
140 :
141 : case DBTYPE_BLOB:
142 1 : printf("%d\tBLOB:\t%" PRId64 " bytes\n", index, ival);
143 1 : break;
144 : }
145 :
146 7 : return DBRES_OK;
147 : }
148 :
149 4106 : uint64_t pk_checksum (const char *buffer, size_t blen) {
150 4106 : const uint8_t *p = (const uint8_t *)buffer;
151 4106 : uint64_t h = 14695981039346656037ULL;
152 79183082 : for (size_t i = 0; i < blen; i++) {
153 79178976 : h ^= p[i];
154 79178976 : h *= 1099511628211ULL;
155 79178976 : }
156 4106 : return h;
157 : }
158 :
159 : // MARK: - Decoding -
160 :
161 4030279 : static inline int pk_decode_check_bounds (size_t bseek, size_t blen, size_t need) {
162 : // bounds check helper for decoding
163 4030279 : if (bseek > blen) return 0;
164 4030279 : return need <= (blen - bseek);
165 4030279 : }
166 :
167 1676118 : int pk_decode_u8 (const uint8_t *buffer, size_t blen, size_t *bseek, uint8_t *out) {
168 1676118 : if (!pk_decode_check_bounds(*bseek, blen, 1)) return 0;
169 1676114 : *out = buffer[*bseek];
170 1676114 : *bseek += 1;
171 1676114 : return 1;
172 1676118 : }
173 :
174 1483957 : static int pk_decode_uint64 (const uint8_t *buffer, size_t blen, size_t *bseek, size_t nbytes, uint64_t *out) {
175 1483957 : if (nbytes > 8) return 0;
176 1483957 : if (!pk_decode_check_bounds(*bseek, blen, nbytes)) return 0;
177 :
178 : // decode bytes in big-endian order (most significant byte first)
179 1483957 : uint64_t v = 0;
180 5579002 : for (size_t i = 0; i < nbytes; i++) {
181 4095045 : v = (v << 8) | (uint64_t)buffer[*bseek];
182 4095045 : (*bseek)++;
183 4095045 : }
184 :
185 1483957 : *out = v;
186 1483957 : return 1;
187 1483957 : }
188 :
189 870204 : static int pk_decode_data (const uint8_t *buffer, size_t blen, size_t *bseek, size_t n, const uint8_t **out) {
190 870204 : if (!pk_decode_check_bounds(*bseek, blen, n)) return 0;
191 870204 : *out = buffer + *bseek;
192 870204 : *bseek += n;
193 :
194 870204 : return 1;
195 870204 : }
196 :
197 159589 : int pk_decode_double (const uint8_t *buffer, size_t blen, size_t *bseek, double *out) {
198 : // Doubles travel as little-endian IEEE754 bytes on every host, unlike integer
199 : // fields: pk_decode_uint64 reads the bytes as a big-endian integer, and swapping it
200 : // yields the IEEE754 bit pattern whatever the host's byte order.
201 :
202 159589 : uint64_t bits_be = 0;
203 159589 : if (!pk_decode_uint64(buffer, blen, bseek, sizeof(uint64_t), &bits_be)) return 0;
204 :
205 159589 : uint64_t bits = bswap64_u64(bits_be);
206 159589 : double value = 0.0;
207 159589 : memcpy(&value, &bits, sizeof(bits));
208 159589 : *out = value;
209 159589 : return 1;
210 159589 : }
211 :
212 248224 : int pk_decode (char *buffer, size_t blen, int count, size_t *seek, int skip_decode_idx, pk_decode_callback cb, void *xdata) {
213 248224 : const uint8_t *ubuf = (const uint8_t *)buffer;
214 248224 : size_t bseek = (seek) ? *seek : 0;
215 248224 : if (count == -1) {
216 1 : uint8_t c = 0;
217 1 : if (!pk_decode_u8(ubuf, blen, &bseek, &c)) return -1;
218 1 : count = (int)c;
219 1 : }
220 :
221 1734175 : for (size_t i = 0; i < (size_t)count; i++) {
222 1485952 : uint8_t type_byte = 0;
223 1485952 : if (!pk_decode_u8(ubuf, blen, &bseek, &type_byte)) return -1;
224 1485951 : int raw_type = (int)(type_byte & 0x07);
225 1485951 : size_t nbytes = (size_t)((type_byte >> 3) & 0x1F);
226 :
227 : // skip_decode wants the raw encoded slice (type_byte + optional len/int + payload)
228 : // we still must parse with the *raw* type to know how much to skip
229 1485951 : bool skip_decode = ((skip_decode_idx >= 0) && (i == (size_t)skip_decode_idx));
230 1485951 : size_t initial_bseek = bseek - 1; // points to type_byte
231 :
232 1485951 : switch (raw_type) {
233 : case DATABASE_TYPE_MAX_NEGATIVE_INTEGER: {
234 : // must not carry length bits
235 3 : if (nbytes != 0) return -1;
236 3 : if (skip_decode) {
237 0 : size_t slice_len = bseek - initial_bseek;
238 0 : if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
239 0 : } else {
240 3 : int64_t value = INT64_MIN;
241 3 : if (cb) if (cb(xdata, (int)i, DBTYPE_INTEGER, value, 0.0, NULL) != DBRES_OK) return -1;
242 : }
243 : }
244 3 : break;
245 :
246 : case DATABASE_TYPE_NEGATIVE_INTEGER:
247 : case DBTYPE_INTEGER: {
248 : // validate nbytes to avoid UB/overreads
249 454164 : if (nbytes < 1 || nbytes > 8) return -1;
250 454164 : uint64_t u = 0;
251 454164 : if (!pk_decode_uint64(ubuf, blen, &bseek, nbytes, &u)) return -1;
252 :
253 454164 : if (skip_decode) {
254 0 : size_t slice_len = bseek - initial_bseek;
255 0 : if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
256 0 : } else {
257 454164 : int64_t value = (int64_t)u;
258 454164 : if (raw_type == DATABASE_TYPE_NEGATIVE_INTEGER) value = -value;
259 454164 : if (cb) if (cb(xdata, (int)i, DBTYPE_INTEGER, value, 0.0, NULL) != DBRES_OK) return -1;
260 : }
261 : }
262 454164 : break;
263 :
264 : case DATABASE_TYPE_NEGATIVE_FLOAT:
265 : case DBTYPE_FLOAT: {
266 : // encoder stores float type with no length bits, so enforce nbytes==0
267 159589 : if (nbytes != 0) return -1;
268 159589 : double value = 0.0;
269 159589 : if (!pk_decode_double(ubuf, blen, &bseek, &value)) return -1;
270 :
271 159589 : if (skip_decode) {
272 0 : size_t slice_len = bseek - initial_bseek;
273 0 : if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
274 0 : } else {
275 159589 : if (raw_type == DATABASE_TYPE_NEGATIVE_FLOAT) value = -value;
276 159589 : if (cb) if (cb(xdata, (int)i, DBTYPE_FLOAT, 0, value, NULL) != DBRES_OK) return -1;
277 : }
278 : }
279 159589 : break;
280 :
281 : case DBTYPE_TEXT:
282 : case DBTYPE_BLOB: {
283 : // validate nbytes for length field
284 870204 : if (nbytes < 1 || nbytes > 8) return -1;
285 870204 : uint64_t ulen = 0;
286 870204 : if (!pk_decode_uint64(ubuf, blen, &bseek, nbytes, &ulen)) return -1;
287 :
288 : // ensure ulen fits in size_t on this platform
289 870204 : if (ulen > (uint64_t)SIZE_MAX) return -1;
290 870204 : size_t len = (size_t)ulen;
291 870204 : const uint8_t *p = NULL;
292 870204 : if (!pk_decode_data(ubuf, blen, &bseek, len, &p)) return -1;
293 :
294 870204 : if (skip_decode) {
295 : // return the full encoded slice (type_byte + len bytes + payload)
296 0 : size_t slice_len = bseek - initial_bseek;
297 0 : if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
298 0 : } else {
299 870204 : if (cb) if (cb(xdata, (int)i, raw_type, (int64_t)len, 0.0, (char *)p) != DBRES_OK) return -1;
300 : }
301 : }
302 870204 : break;
303 :
304 : case DBTYPE_NULL: {
305 1991 : if (nbytes != 0) return -1;
306 1991 : if (skip_decode) {
307 0 : size_t slice_len = bseek - initial_bseek;
308 0 : if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
309 0 : } else {
310 1991 : if (cb) if (cb(xdata, (int)i, DBTYPE_NULL, 0, 0.0, NULL) != DBRES_OK) return -1;
311 : }
312 : }
313 1991 : break;
314 :
315 : default:
316 : // should never reach this point
317 0 : return -1;
318 : }
319 1485951 : }
320 :
321 248223 : if (seek) *seek = bseek;
322 248223 : return count;
323 248224 : }
324 :
325 190165 : int pk_decode_prikey (char *buffer, size_t blen, pk_decode_callback cb, void *xdata) {
326 190165 : const uint8_t *ubuf = (const uint8_t *)buffer;
327 190165 : size_t bseek = 0;
328 190165 : uint8_t count = 0;
329 190165 : if (!pk_decode_u8(ubuf, blen, &bseek, &count)) return -1;
330 190162 : return pk_decode(buffer, blen, count, &bseek, -1, cb, xdata);
331 190165 : }
332 :
333 : // MARK: - Encoding -
334 :
335 2652816 : size_t pk_encode_nbytes_needed (int64_t value) {
336 2652816 : uint64_t v = (uint64_t)value;
337 2652816 : if (v <= 0xFFULL) return 1;
338 1053155 : if (v <= 0xFFFFULL) return 2;
339 320442 : if (v <= 0xFFFFFFULL) return 3;
340 319607 : if (v <= 0xFFFFFFFFULL) return 4;
341 319607 : if (v <= 0xFFFFFFFFFFULL) return 5;
342 319607 : if (v <= 0xFFFFFFFFFFFFULL) return 6;
343 319603 : if (v <= 0xFFFFFFFFFFFFFFULL) return 7;
344 317045 : return 8;
345 2652816 : }
346 :
347 4473418 : static inline int pk_encode_add_overflow_size (size_t a, size_t b, size_t *out) {
348 : // safe size_t addition helper (prevents overflow)
349 4473418 : if (b > (SIZE_MAX - a)) return 1;
350 4473418 : *out = a + b;
351 4473418 : return 0;
352 4473418 : }
353 :
354 143444 : size_t pk_encode_size (dbvalue_t **argv, int argc, int reserved, int skip_idx) {
355 : // estimate the required buffer size
356 143444 : size_t required = reserved;
357 : size_t nbytes;
358 : int64_t val;
359 :
360 1922450 : for (int i = 0; i < argc; i++) {
361 1779006 : switch (database_value_type(argv[i])) {
362 : case DBTYPE_INTEGER: {
363 695847 : val = database_value_int(argv[i]);
364 695847 : if (val == INT64_MIN) {
365 2 : if (pk_encode_add_overflow_size(required, 1, &required)) return SIZE_MAX;
366 2 : break;
367 : }
368 695845 : if (val < 0) val = -val;
369 695845 : nbytes = pk_encode_nbytes_needed(val);
370 :
371 695845 : size_t tmp = 0;
372 695845 : if (pk_encode_add_overflow_size(1, nbytes, &tmp)) return SIZE_MAX;
373 695845 : if (pk_encode_add_overflow_size(required, tmp, &required)) return SIZE_MAX;
374 695845 : } break;
375 :
376 : case DBTYPE_FLOAT: {
377 159587 : size_t tmp = 0;
378 159587 : if (pk_encode_add_overflow_size(1, sizeof(uint64_t), &tmp)) return SIZE_MAX;
379 159587 : if (pk_encode_add_overflow_size(required, tmp, &required)) return SIZE_MAX;
380 159587 : } break;
381 :
382 : case DBTYPE_TEXT:
383 : case DBTYPE_BLOB: {
384 919490 : size_t len_sz = (size_t)database_value_bytes(argv[i]);
385 919490 : if (i == skip_idx) {
386 0 : if (pk_encode_add_overflow_size(required, len_sz, &required)) return SIZE_MAX;
387 0 : break;
388 : }
389 :
390 : // Ensure length can be represented by encoder (we encode length with up to 8 bytes)
391 : // pk_encode_nbytes_needed expects int64-ish values; clamp-check here.
392 919490 : if (len_sz > (size_t)INT64_MAX) return SIZE_MAX;
393 919490 : nbytes = pk_encode_nbytes_needed((int64_t)len_sz);
394 :
395 919490 : size_t tmp = 0;
396 : // 1(type) + nbytes(len) + len_sz(payload)
397 919490 : if (pk_encode_add_overflow_size(1, nbytes, &tmp)) return SIZE_MAX;
398 919490 : if (pk_encode_add_overflow_size(tmp, len_sz, &tmp)) return SIZE_MAX;
399 919490 : if (pk_encode_add_overflow_size(required, tmp, &required)) return SIZE_MAX;
400 919490 : } break;
401 :
402 : case DBTYPE_NULL: {
403 4082 : if (pk_encode_add_overflow_size(required, 1, &required)) return SIZE_MAX;
404 4082 : } break;
405 : }
406 1779006 : }
407 :
408 143444 : return required;
409 143444 : }
410 :
411 1214606 : size_t pk_encode_u8 (char *buffer, size_t bseek, uint8_t value) {
412 1214606 : buffer[bseek++] = value;
413 1214606 : return bseek;
414 : }
415 :
416 1194406 : static size_t pk_encode_uint64 (char *buffer, size_t bseek, uint64_t value, size_t nbytes) {
417 4965746 : for (size_t i = 0; i < nbytes; i++) {
418 3771340 : buffer[bseek++] = (uint8_t)((value >> (8 * (nbytes - 1 - i))) & 0xFFu);
419 3771340 : }
420 1194406 : return bseek;
421 : }
422 :
423 615161 : size_t pk_encode_data (char *buffer, size_t bseek, char *data, size_t datalen) {
424 615161 : memcpy(buffer + bseek, data, datalen);
425 615161 : return bseek + datalen;
426 : }
427 :
428 2661 : size_t pk_encode_raw_size (int type, int64_t len_or_value) {
429 2661 : switch (type) {
430 : case DBTYPE_INTEGER: {
431 1020 : if (len_or_value == INT64_MIN) return 1;
432 1020 : if (len_or_value < 0) len_or_value = -len_or_value;
433 1020 : return 1 + pk_encode_nbytes_needed(len_or_value);
434 : }
435 : case DBTYPE_FLOAT:
436 0 : return 1 + sizeof(uint64_t);
437 : case DBTYPE_TEXT:
438 : case DBTYPE_BLOB: {
439 1641 : if (len_or_value < 0) return SIZE_MAX;
440 1641 : size_t nbytes = pk_encode_nbytes_needed(len_or_value);
441 1641 : return 1 + nbytes + (size_t)len_or_value;
442 : }
443 : case DBTYPE_NULL:
444 0 : return 1;
445 : }
446 0 : return SIZE_MAX;
447 2661 : }
448 :
449 532 : size_t pk_encode_raw_int (char *buffer, int64_t value) {
450 532 : int type = DBTYPE_INTEGER;
451 532 : size_t bseek = 0;
452 532 : if (value == INT64_MIN) {
453 0 : return pk_encode_u8(buffer, bseek, DATABASE_TYPE_MAX_NEGATIVE_INTEGER);
454 : }
455 532 : if (value < 0) { value = -value; type = DATABASE_TYPE_NEGATIVE_INTEGER; }
456 532 : size_t nbytes = pk_encode_nbytes_needed(value);
457 532 : uint8_t type_byte = (uint8_t)((nbytes << 3) | type);
458 532 : bseek = pk_encode_u8(buffer, bseek, type_byte);
459 532 : return pk_encode_uint64(buffer, bseek, (uint64_t)value, nbytes);
460 532 : }
461 :
462 266 : size_t pk_encode_raw_text (char *buffer, const char *value, size_t len) {
463 266 : size_t nbytes = pk_encode_nbytes_needed((int64_t)len);
464 266 : uint8_t type_byte = (uint8_t)((nbytes << 3) | DBTYPE_TEXT);
465 266 : size_t bseek = pk_encode_u8(buffer, 0, type_byte);
466 266 : bseek = pk_encode_uint64(buffer, bseek, (uint64_t)len, nbytes);
467 266 : return pk_encode_data(buffer, bseek, (char *)value, len);
468 : }
469 :
470 399 : size_t pk_encode_raw_blob (char *buffer, const void *value, size_t len) {
471 399 : size_t nbytes = pk_encode_nbytes_needed((int64_t)len);
472 399 : uint8_t type_byte = (uint8_t)((nbytes << 3) | DBTYPE_BLOB);
473 399 : size_t bseek = pk_encode_u8(buffer, 0, type_byte);
474 399 : bseek = pk_encode_uint64(buffer, bseek, (uint64_t)len, nbytes);
475 399 : return pk_encode_data(buffer, bseek, (char *)value, len);
476 : }
477 :
478 78583 : char *pk_encode (dbvalue_t **argv, int argc, char *b, bool is_prikey, size_t *bsize, int skip_idx) {
479 78583 : size_t bseek = 0;
480 78583 : char *buffer = b;
481 :
482 : // always compute blen (even if it is not a primary key)
483 78583 : size_t blen = pk_encode_size(argv, argc, (is_prikey) ? 1 : 0, skip_idx);
484 78583 : if (blen == SIZE_MAX) return NULL;
485 78583 : if (argc < 0) return NULL;
486 :
487 : // in primary-key encoding the number of items must be explicitly added to the encoded buffer
488 78583 : if (is_prikey) {
489 18160 : if (!bsize) return NULL;
490 : // must fit in a single byte
491 18160 : if (argc > 255) return NULL;
492 :
493 : // if schema does not enforce NOT NULL on primary keys, check at runtime
494 : #ifndef CLOUDSYNC_CHECK_NOTNULL_PRIKEYS
495 669605 : for (int i = 0; i < argc; i++) {
496 651447 : if (database_value_type(argv[i]) == DBTYPE_NULL) return PRIKEY_NULL_CONSTRAINT_ERROR;
497 651445 : }
498 : #endif
499 :
500 : // 1 is the number of items in the serialization
501 : // always 1 byte so max 255 primary keys, even if there is an hard SQLite limit of 128
502 18158 : size_t blen_curr = *bsize;
503 18158 : buffer = (blen > blen_curr || b == NULL) ? cloudsync_memory_alloc((uint64_t)blen) : b;
504 18158 : if (!buffer) return NULL;
505 :
506 : // the first u8 value is the total number of items in the primary key(s)
507 18158 : bseek = pk_encode_u8(buffer, 0, (uint8_t)argc);
508 18158 : } else {
509 : // ensure buffer exists and is large enough also in non-prikey mode
510 60423 : size_t curr = (bsize) ? *bsize : 0;
511 60423 : if (buffer == NULL || curr < blen) return NULL;
512 : }
513 :
514 1273832 : for (int i = 0; i < argc; i++) {
515 1195251 : int type = database_value_type(argv[i]);
516 1195251 : switch (type) {
517 : case DBTYPE_INTEGER: {
518 419129 : int64_t value = database_value_int(argv[i]);
519 419129 : if (value == INT64_MIN) {
520 2 : bseek = pk_encode_u8(buffer, bseek, DATABASE_TYPE_MAX_NEGATIVE_INTEGER);
521 2 : break;
522 : }
523 419127 : if (value < 0) {value = -value; type = DATABASE_TYPE_NEGATIVE_INTEGER;}
524 419127 : size_t nbytes = pk_encode_nbytes_needed(value);
525 419127 : uint8_t type_byte = (uint8_t)((nbytes << 3) | type);
526 419127 : bseek = pk_encode_u8(buffer, bseek, type_byte);
527 419127 : bseek = pk_encode_uint64(buffer, bseek, (uint64_t)value, nbytes);
528 : }
529 419127 : break;
530 : case DBTYPE_FLOAT: {
531 : // Encode doubles as IEEE754 64-bit little-endian bytes on every host.
532 159586 : double value = database_value_double(argv[i]);
533 159586 : if (value < 0) {value = -value; type = DATABASE_TYPE_NEGATIVE_FLOAT;}
534 : uint64_t bits;
535 159586 : memcpy(&bits, &value, sizeof(bits));
536 : // pk_encode_uint64 writes big-endian bytes, so the swapped value lands as
537 : // the little-endian bytes of the bit pattern, independent of the host.
538 159586 : bits = bswap64_u64(bits);
539 159586 : bseek = pk_encode_u8(buffer, bseek, (uint8_t)type);
540 159586 : bseek = pk_encode_uint64(buffer, bseek, bits, sizeof(bits));
541 : }
542 159586 : break;
543 : case DBTYPE_TEXT:
544 : case DBTYPE_BLOB: {
545 614496 : size_t len = (size_t)database_value_bytes(argv[i]);
546 614496 : if (i == skip_idx) {
547 0 : memcpy(buffer + bseek, (char *)database_value_blob(argv[i]), len);
548 0 : bseek += len;
549 0 : break;
550 : }
551 :
552 614496 : if (len > (size_t)INT64_MAX) return NULL;
553 614496 : size_t nbytes = pk_encode_nbytes_needed((int64_t)len);
554 614496 : uint8_t type_byte = (uint8_t)((nbytes << 3) | database_value_type(argv[i]));
555 614496 : bseek = pk_encode_u8(buffer, bseek, type_byte);
556 614496 : bseek = pk_encode_uint64(buffer, bseek, (uint64_t)len, nbytes);
557 614496 : bseek = pk_encode_data(buffer, bseek, (char *)database_value_blob(argv[i]), len);
558 : }
559 614496 : break;
560 : case DBTYPE_NULL: {
561 2040 : bseek = pk_encode_u8(buffer, bseek, DBTYPE_NULL);
562 : }
563 2040 : break;
564 : }
565 1195251 : }
566 :
567 : // return actual bytes written; for prikey it's equal to blen, but safer to report bseek
568 78581 : if (bsize) *bsize = bseek;
569 78581 : return buffer;
570 78583 : }
571 :
572 18160 : char *pk_encode_prikey (dbvalue_t **argv, int argc, char *b, size_t *bsize) {
573 18160 : return pk_encode(argv, argc, b, true, bsize, -1);
574 : }
575 :
576 0 : char *pk_encode_value (dbvalue_t *value, size_t *bsize) {
577 0 : dbvalue_t *argv[1] = {value};
578 :
579 0 : size_t blen = pk_encode_size(argv, 1, 0, -1);
580 0 : if (blen == SIZE_MAX) return NULL;
581 :
582 0 : char *buffer = cloudsync_memory_alloc((uint64_t)blen);
583 0 : if (!buffer) return NULL;
584 :
585 0 : *bsize = blen;
586 0 : return pk_encode(argv, 1, buffer, false, bsize, -1);
587 0 : }
|