LCOV - code coverage report
Current view: top level - src - pk.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 87.8 % 312 274
Test Date: 2026-10-21 16:35:02 Functions: 95.7 % 23 22

            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 : }
        

Generated by: LCOV version 2.5-0