LCOV - code coverage report
Current view: top level - src - pk.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 87.7 % 277 243
Test Date: 2026-03-16 13:50:46 Functions: 94.7 % 19 18

            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       741186 : 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       741186 :     int rc = DBRES_OK;
      98       741186 :     switch (type) {
      99              :         case DBTYPE_INTEGER:
     100       258136 :             rc = databasevm_bind_int(xdata, index+1, ival);
     101       258136 :             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          856 :             rc = databasevm_bind_null(xdata, index+1);
     109          856 :             break;
     110              :             
     111              :         case DBTYPE_TEXT:
     112       384159 :             rc = databasevm_bind_text(xdata, index+1, pval, (int)ival);
     113       384159 :             break;
     114              :             
     115              :         case DBTYPE_BLOB:
     116        98035 :             rc = databasevm_bind_blob(xdata, index+1, (const void *)pval, ival);
     117        98035 :             break;
     118              :     }
     119              :     
     120       741186 :     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         1292 : uint64_t pk_checksum (const char *buffer, size_t blen) {
     150         1292 :     const uint8_t *p = (const uint8_t *)buffer;
     151         1292 :     uint64_t h = 14695981039346656037ULL;
     152      1841089 :     for (size_t i = 0; i < blen; i++) {
     153      1839797 :         h ^= p[i];
     154      1839797 :         h *= 1099511628211ULL;
     155      1839797 :     }
     156         1292 :     return h;
     157              : }
     158              : 
     159              : // MARK: - Decoding -
     160              : 
     161      3736609 : static inline int pk_decode_check_bounds (size_t bseek, size_t blen, size_t need) {
     162              :     // bounds check helper for decoding
     163      3736609 :     if (bseek > blen) return 0;
     164      3736609 :     return need <= (blen - bseek);
     165      3736609 : }
     166              : 
     167      1550234 : int pk_decode_u8 (const uint8_t *buffer, size_t blen, size_t *bseek, uint8_t *out) {
     168      1550234 :     if (!pk_decode_check_bounds(*bseek, blen, 1)) return 0;
     169      1550230 :     *out = buffer[*bseek];
     170      1550230 :     *bseek += 1;
     171      1550230 :     return 1;
     172      1550234 : }
     173              : 
     174      1383360 : static int pk_decode_uint64 (const uint8_t *buffer, size_t blen, size_t *bseek, size_t nbytes, uint64_t *out) {
     175      1383360 :     if (nbytes > 8) return 0;
     176      1383360 :     if (!pk_decode_check_bounds(*bseek, blen, nbytes)) return 0;
     177              :     
     178              :     // decode bytes in big-endian order (most significant byte first)
     179      1383360 :     uint64_t v = 0;
     180      5395121 :     for (size_t i = 0; i < nbytes; i++) {
     181      4011761 :         v = (v << 8) | (uint64_t)buffer[*bseek];
     182      4011761 :         (*bseek)++;
     183      4011761 :     }
     184              :     
     185      1383360 :     *out = v;
     186      1383360 :     return 1;
     187      1383360 : }
     188              : 
     189       803015 : static int pk_decode_data (const uint8_t *buffer, size_t blen, size_t *bseek, size_t n, const uint8_t **out) {
     190       803015 :     if (!pk_decode_check_bounds(*bseek, blen, n)) return 0;
     191       803015 :     *out = buffer + *bseek;
     192       803015 :     *bseek += n;
     193              :     
     194       803015 :     return 1;
     195       803015 : }
     196              : 
     197       161103 : int pk_decode_double (const uint8_t *buffer, size_t blen, size_t *bseek, double *out) {
     198              :     // Doubles are encoded as IEEE754 64-bit, big-endian.
     199              :     // Convert back to host order before memcpy into double.
     200              :     
     201       161103 :     uint64_t bits_be = 0;
     202       161103 :     if (!pk_decode_uint64(buffer, blen, bseek, sizeof(uint64_t), &bits_be)) return 0;
     203              :     
     204       161103 :     uint64_t bits = be64_to_host(bits_be);
     205       161103 :     double value = 0.0;
     206       161103 :     memcpy(&value, &bits, sizeof(bits));
     207       161103 :     *out = value;
     208       161103 :     return 1;
     209       161103 : }
     210              : 
     211       215028 : int pk_decode (char *buffer, size_t blen, int count, size_t *seek, int skip_decode_idx, pk_decode_callback cb, void *xdata) {
     212       215028 :     const uint8_t *ubuf = (const uint8_t *)buffer;
     213       215028 :     size_t bseek = (seek) ? *seek : 0;
     214       215028 :     if (count == -1) {
     215            1 :         uint8_t c = 0;
     216            1 :         if (!pk_decode_u8(ubuf, blen, &bseek, &c)) return -1;
     217            1 :         count = (int)c;
     218            1 :     }
     219              :         
     220      1599247 :     for (size_t i = 0; i < (size_t)count; i++) {
     221      1384220 :         uint8_t type_byte = 0;
     222      1384220 :         if (!pk_decode_u8(ubuf, blen, &bseek, &type_byte)) return -1;
     223      1384219 :         int raw_type = (int)(type_byte & 0x07);
     224      1384219 :         size_t nbytes = (size_t)((type_byte >> 3) & 0x1F);
     225              : 
     226              :         // skip_decode wants the raw encoded slice (type_byte + optional len/int + payload)
     227              :         // we still must parse with the *raw* type to know how much to skip
     228      1384219 :         bool skip_decode = ((skip_decode_idx >= 0) && (i == (size_t)skip_decode_idx));
     229      1384219 :         size_t initial_bseek = bseek - 1; // points to type_byte
     230              : 
     231      1384219 :         switch (raw_type) {
     232              :             case DATABASE_TYPE_MAX_NEGATIVE_INTEGER: {
     233              :                 // must not carry length bits
     234            3 :                 if (nbytes != 0) return -1;
     235            3 :                 if (skip_decode) {
     236            0 :                     size_t slice_len = bseek - initial_bseek;
     237            0 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
     238            0 :                 } else {
     239            3 :                     int64_t value = INT64_MIN;
     240            3 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_INTEGER, value, 0.0, NULL) != DBRES_OK) return -1;
     241              :                 }
     242              :             }
     243            3 :                 break;
     244              :                 
     245              :             case DATABASE_TYPE_NEGATIVE_INTEGER:
     246              :             case DBTYPE_INTEGER: {
     247              :                 // validate nbytes to avoid UB/overreads
     248       419242 :                 if (nbytes < 1 || nbytes > 8) return -1;
     249       419242 :                 uint64_t u = 0;
     250       419242 :                 if (!pk_decode_uint64(ubuf, blen, &bseek, nbytes, &u)) return -1;
     251              :                 
     252       419242 :                 if (skip_decode) {
     253            0 :                     size_t slice_len = bseek - initial_bseek;
     254            0 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
     255            0 :                 } else {
     256       419242 :                     int64_t value = (int64_t)u;
     257       419242 :                     if (raw_type == DATABASE_TYPE_NEGATIVE_INTEGER) value = -value;
     258       419242 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_INTEGER, value, 0.0, NULL) != DBRES_OK) return -1;
     259              :                 }
     260              :             }
     261       419242 :                 break;
     262              :                 
     263              :             case DATABASE_TYPE_NEGATIVE_FLOAT:
     264              :             case DBTYPE_FLOAT: {
     265              :                 // encoder stores float type with no length bits, so enforce nbytes==0
     266       161103 :                 if (nbytes != 0) return -1;
     267       161103 :                 double value = 0.0;
     268       161103 :                 if (!pk_decode_double(ubuf, blen, &bseek, &value)) return -1;
     269              :                 
     270       161103 :                 if (skip_decode) {
     271            0 :                     size_t slice_len = bseek - initial_bseek;
     272            0 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
     273            0 :                 } else {
     274       161103 :                     if (raw_type == DATABASE_TYPE_NEGATIVE_FLOAT) value = -value;
     275       161103 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_FLOAT, 0, value, NULL) != DBRES_OK) return -1;
     276              :                 }
     277              :             }
     278       161103 :                 break;
     279              :                 
     280              :             case DBTYPE_TEXT:
     281              :             case DBTYPE_BLOB: {
     282              :                 // validate nbytes for length field
     283       803015 :                 if (nbytes < 1 || nbytes > 8) return -1;
     284       803015 :                 uint64_t ulen = 0;
     285       803015 :                 if (!pk_decode_uint64(ubuf, blen, &bseek, nbytes, &ulen)) return -1;
     286              :                 
     287              :                 // ensure ulen fits in size_t on this platform
     288       803015 :                 if (ulen > (uint64_t)SIZE_MAX) return -1;
     289       803015 :                 size_t len = (size_t)ulen;
     290       803015 :                 const uint8_t *p = NULL;
     291       803015 :                 if (!pk_decode_data(ubuf, blen, &bseek, len, &p)) return -1;
     292              :                 
     293       803015 :                 if (skip_decode) {
     294              :                     // return the full encoded slice (type_byte + len bytes + payload)
     295            0 :                     size_t slice_len = bseek - initial_bseek;
     296            0 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
     297            0 :                 } else {
     298       803015 :                     if (cb) if (cb(xdata, (int)i, raw_type, (int64_t)len, 0.0, (char *)p) != DBRES_OK) return -1;
     299              :                 }
     300              :             }
     301       803015 :                 break;
     302              :                 
     303              :             case DBTYPE_NULL: {
     304          856 :                 if (nbytes != 0) return -1;
     305          856 :                 if (skip_decode) {
     306            0 :                     size_t slice_len = bseek - initial_bseek;
     307            0 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_BLOB, (int64_t)slice_len, 0.0, (char *)(buffer + initial_bseek)) != DBRES_OK) return -1;
     308            0 :                 } else {
     309          856 :                     if (cb) if (cb(xdata, (int)i, DBTYPE_NULL, 0, 0.0, NULL) != DBRES_OK) return -1;
     310              :                 }
     311              :             }
     312          856 :                 break;
     313              :             
     314              :             default:
     315              :                 // should never reach this point
     316            0 :                 return -1;
     317              :         }
     318      1384219 :     }
     319              :     
     320       215027 :     if (seek) *seek = bseek;
     321       215027 :     return count;
     322       215028 : }
     323              : 
     324       166013 : int pk_decode_prikey (char *buffer, size_t blen, pk_decode_callback cb, void *xdata) {
     325       166013 :     const uint8_t *ubuf = (const uint8_t *)buffer;
     326       166013 :     size_t bseek = 0;
     327       166013 :     uint8_t count = 0;
     328       166013 :     if (!pk_decode_u8(ubuf, blen, &bseek, &count)) return -1;
     329       166010 :     return pk_decode(buffer, blen, count, &bseek, -1, cb, xdata);
     330       166013 : }
     331              : 
     332              : // MARK: - Encoding -
     333              : 
     334      2303900 : size_t pk_encode_nbytes_needed (int64_t value) {
     335      2303900 :     uint64_t v = (uint64_t)value;
     336      2303900 :     if (v <= 0xFFULL) return 1;
     337      1033835 :     if (v <= 0xFFFFULL) return 2;
     338       322250 :     if (v <= 0xFFFFFFULL) return 3;
     339       322229 :     if (v <= 0xFFFFFFFFULL) return 4;
     340       322229 :     if (v <= 0xFFFFFFFFFFULL) return 5;
     341       322229 :     if (v <= 0xFFFFFFFFFFFFULL) return 6;
     342       322215 :     if (v <= 0xFFFFFFFFFFFFFFULL) return 7;
     343       319731 :     return 8;
     344      2303900 : }
     345              : 
     346      3852556 : static inline int pk_encode_add_overflow_size (size_t a, size_t b, size_t *out) {
     347              :     // safe size_t addition helper (prevents overflow)
     348      3852556 :     if (b > (SIZE_MAX - a)) return 1;
     349      3852556 :     *out = a + b;
     350      3852556 :     return 0;
     351      3852556 : }
     352              : 
     353       112669 : size_t pk_encode_size (dbvalue_t **argv, int argc, int reserved, int skip_idx) {
     354              :     // estimate the required buffer size
     355       112669 :     size_t required = reserved;
     356              :     size_t nbytes;
     357              :     int64_t val;
     358              :     
     359      1647955 :     for (int i = 0; i < argc; i++) {
     360      1535286 :         switch (database_value_type(argv[i])) {
     361              :             case DBTYPE_INTEGER: {
     362       588735 :                 val = database_value_int(argv[i]);
     363       588735 :                 if (val == INT64_MIN) {
     364            2 :                     if (pk_encode_add_overflow_size(required, 1, &required)) return SIZE_MAX;
     365            2 :                     break;
     366              :                 }
     367       588733 :                 if (val < 0) val = -val;
     368       588733 :                 nbytes = pk_encode_nbytes_needed(val);
     369              :                 
     370       588733 :                 size_t tmp = 0;
     371       588733 :                 if (pk_encode_add_overflow_size(1, nbytes, &tmp)) return SIZE_MAX;
     372       588733 :                 if (pk_encode_add_overflow_size(required, tmp, &required)) return SIZE_MAX;
     373       588733 :             } break;
     374              :                 
     375              :             case DBTYPE_FLOAT: {
     376       161101 :                 size_t tmp = 0;
     377       161101 :                 if (pk_encode_add_overflow_size(1, sizeof(uint64_t), &tmp)) return SIZE_MAX;
     378       161101 :                 if (pk_encode_add_overflow_size(required, tmp, &required)) return SIZE_MAX;
     379       161101 :             } break;
     380              :                 
     381              :             case DBTYPE_TEXT:
     382              :             case DBTYPE_BLOB: {
     383       783718 :                 size_t len_sz = (size_t)database_value_bytes(argv[i]);
     384       783718 :                 if (i == skip_idx) {
     385            0 :                     if (pk_encode_add_overflow_size(required, len_sz, &required)) return SIZE_MAX;
     386            0 :                     break;
     387              :                 }
     388              :                 
     389              :                 // Ensure length can be represented by encoder (we encode length with up to 8 bytes)
     390              :                 // pk_encode_nbytes_needed expects int64-ish values; clamp-check here.
     391       783718 :                 if (len_sz > (size_t)INT64_MAX) return SIZE_MAX;
     392       783718 :                 nbytes = pk_encode_nbytes_needed((int64_t)len_sz);
     393              :                 
     394       783718 :                 size_t tmp = 0;
     395              :                 // 1(type) + nbytes(len) + len_sz(payload)
     396       783718 :                 if (pk_encode_add_overflow_size(1, nbytes, &tmp)) return SIZE_MAX;
     397       783718 :                 if (pk_encode_add_overflow_size(tmp, len_sz, &tmp)) return SIZE_MAX;
     398       783718 :                 if (pk_encode_add_overflow_size(required, tmp, &required)) return SIZE_MAX;
     399       783718 :             } break;
     400              :                 
     401              :             case DBTYPE_NULL: {
     402         1732 :                 if (pk_encode_add_overflow_size(required, 1, &required)) return SIZE_MAX;
     403         1732 :             } break;
     404              :         }
     405      1535286 :     }
     406              :     
     407       112669 :     return required;
     408       112669 : }
     409              : 
     410      1107891 : size_t pk_encode_u8 (char *buffer, size_t bseek, uint8_t value) {
     411      1107891 :     buffer[bseek++] = value;
     412      1107891 :     return bseek;
     413              : }
     414              : 
     415      1092549 : static size_t pk_encode_uint64 (char *buffer, size_t bseek, uint64_t value, size_t nbytes) {
     416      4776868 :     for (size_t i = 0; i < nbytes; i++) {
     417      3684319 :         buffer[bseek++] = (uint8_t)((value >> (8 * (nbytes - 1 - i))) & 0xFFu);
     418      3684319 :     }
     419      1092549 :     return bseek;
     420              : }
     421              : 
     422       556344 : size_t pk_encode_data (char *buffer, size_t bseek, char *data, size_t datalen) {
     423       556344 :     memcpy(buffer + bseek, data, datalen);
     424       556344 :     return bseek + datalen;
     425              : }
     426              :     
     427        63573 : char *pk_encode (dbvalue_t **argv, int argc, char *b, bool is_prikey, size_t *bsize, int skip_idx) {
     428        63573 :     size_t bseek = 0;
     429        63573 :     char *buffer = b;
     430              :     
     431              :     // always compute blen (even if it is not a primary key)
     432        63573 :     size_t blen = pk_encode_size(argv, argc, (is_prikey) ? 1 : 0, skip_idx);
     433        63573 :     if (blen == SIZE_MAX) return NULL;
     434        63573 :     if (argc < 0) return NULL;
     435              :     
     436              :     // in primary-key encoding the number of items must be explicitly added to the encoded buffer
     437        63573 :     if (is_prikey) {
     438        14477 :         if (!bsize) return NULL;
     439              :         // must fit in a single byte
     440        14477 :         if (argc > 255) return NULL;
     441              :         
     442              :         // if schema does not enforce NOT NULL on primary keys, check at runtime
     443              :         #ifndef CLOUDSYNC_CHECK_NOTNULL_PRIKEYS
     444       666030 :         for (int i = 0; i < argc; i++) {
     445       651555 :             if (database_value_type(argv[i]) == DBTYPE_NULL) return PRIKEY_NULL_CONSTRAINT_ERROR;
     446       651553 :         }
     447              :         #endif
     448              :         
     449              :         // 1 is the number of items in the serialization
     450              :         // always 1 byte so max 255 primary keys, even if there is an hard SQLite limit of 128
     451        14475 :         size_t blen_curr = *bsize;
     452        14475 :         buffer = (blen > blen_curr || b == NULL) ? cloudsync_memory_alloc((uint64_t)blen) : b;
     453        14475 :         if (!buffer) return NULL;
     454              :         
     455              :         // the first u8 value is the total number of items in the primary key(s)
     456        14475 :         bseek = pk_encode_u8(buffer, 0, (uint8_t)argc);
     457        14475 :     } else {
     458              :         // ensure buffer exists and is large enough also in non-prikey mode
     459        49096 :         size_t curr = (bsize) ? *bsize : 0;
     460        49096 :         if (buffer == NULL || curr < blen) return NULL;
     461              :     }
     462              :         
     463      1156987 :     for (int i = 0; i < argc; i++) {
     464      1093416 :         int type = database_value_type(argv[i]);
     465      1093416 :         switch (type) {
     466              :             case DBTYPE_INTEGER: {
     467       375107 :                 int64_t value = database_value_int(argv[i]);
     468       375107 :                 if (value == INT64_MIN) {
     469            2 :                     bseek = pk_encode_u8(buffer, bseek, DATABASE_TYPE_MAX_NEGATIVE_INTEGER);
     470            2 :                     break;
     471              :                 }
     472       375105 :                 if (value < 0) {value = -value; type = DATABASE_TYPE_NEGATIVE_INTEGER;}
     473       375105 :                 size_t nbytes = pk_encode_nbytes_needed(value);
     474       375105 :                 uint8_t type_byte = (uint8_t)((nbytes << 3) | type);
     475       375105 :                 bseek = pk_encode_u8(buffer, bseek, type_byte);
     476       375105 :                 bseek = pk_encode_uint64(buffer, bseek, (uint64_t)value, nbytes);
     477              :             }
     478       375105 :                 break;
     479              :             case DBTYPE_FLOAT: {
     480              :                 // Encode doubles as IEEE754 64-bit, big-endian
     481       161100 :                 double value = database_value_double(argv[i]);
     482       161100 :                 if (value < 0) {value = -value; type = DATABASE_TYPE_NEGATIVE_FLOAT;}
     483              :                 uint64_t bits;
     484       161100 :                 memcpy(&bits, &value, sizeof(bits));
     485       161100 :                 bits = host_to_be64(bits);
     486       161100 :                 bseek = pk_encode_u8(buffer, bseek, (uint8_t)type);
     487       161100 :                 bseek = pk_encode_uint64(buffer, bseek, bits, sizeof(bits));
     488              :             }
     489       161100 :                 break;
     490              :             case DBTYPE_TEXT:
     491              :             case DBTYPE_BLOB: {
     492       556344 :                 size_t len = (size_t)database_value_bytes(argv[i]);
     493       556344 :                 if (i == skip_idx) {
     494            0 :                     memcpy(buffer + bseek, (char *)database_value_blob(argv[i]), len);
     495            0 :                     bseek += len;
     496            0 :                     break;
     497              :                 }
     498              : 
     499       556344 :                 if (len > (size_t)INT64_MAX) return NULL;
     500       556344 :                 size_t nbytes = pk_encode_nbytes_needed((int64_t)len);
     501       556344 :                 uint8_t type_byte = (uint8_t)((nbytes << 3) | database_value_type(argv[i]));
     502       556344 :                 bseek = pk_encode_u8(buffer, bseek, type_byte);
     503       556344 :                 bseek = pk_encode_uint64(buffer, bseek, (uint64_t)len, nbytes);
     504       556344 :                 bseek = pk_encode_data(buffer, bseek, (char *)database_value_blob(argv[i]), len);
     505              :             }
     506       556344 :                 break;
     507              :             case DBTYPE_NULL: {
     508          865 :                 bseek = pk_encode_u8(buffer, bseek, DBTYPE_NULL);
     509              :             }
     510          865 :                 break;
     511              :         }
     512      1093416 :     }
     513              :     
     514              :     // return actual bytes written; for prikey it's equal to blen, but safer to report bseek
     515        63571 :     if (bsize) *bsize = bseek;
     516        63571 :     return buffer;
     517        63573 : }
     518              : 
     519        14477 : char *pk_encode_prikey (dbvalue_t **argv, int argc, char *b, size_t *bsize) {
     520        14477 :     return pk_encode(argv, argc, b, true, bsize, -1);
     521              : }
     522              : 
     523            0 : char *pk_encode_value (dbvalue_t *value, size_t *bsize) {
     524            0 :     dbvalue_t *argv[1] = {value};
     525              :     
     526            0 :     size_t blen = pk_encode_size(argv, 1, 0, -1);
     527            0 :     if (blen == SIZE_MAX) return NULL;
     528              :     
     529            0 :     char *buffer = cloudsync_memory_alloc((uint64_t)blen);
     530            0 :     if (!buffer) return NULL;
     531              :     
     532            0 :     *bsize = blen;
     533            0 :     return pk_encode(argv, 1, buffer, false, bsize, -1);
     534            0 : }
        

Generated by: LCOV version 2.4-0