Files
story/docs/device-protocol-c/vl_protocol.c
T
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

157 lines
6.2 KiB
C

#include "vl_protocol.h"
#include "mbedtls/base64.h"
#include <string.h>
static int hex_digit(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
static void to_hex(const uint8_t *in, size_t n, char *out) {
const char *hex = "0123456789abcdef";
for (size_t i = 0; i < n; i++) {
out[i * 2] = hex[in[i] >> 4];
out[i * 2 + 1] = hex[in[i] & 15];
}
out[n * 2] = '\0';
}
int vl_key_from_hex(const char *hex, uint8_t key[32]) {
if (!hex || !key || strlen(hex) != 64) return -1;
for (size_t i = 0; i < 64; i++) if (hex_digit(hex[i]) < 0) return -1;
for (size_t i = 0; i < 32; i++)
key[i] = (uint8_t)((hex_digit(hex[i * 2]) << 4) | hex_digit(hex[i * 2 + 1]));
return 0;
}
int vl_nonce(vl_rng_fn rng, void *context, char out[23]) {
uint8_t random[16], base64[25];
size_t n = 0;
if (!rng || !out) return -1;
out[0] = '\0';
if (rng(context, random, sizeof(random)) != 0) return -1;
if (mbedtls_base64_encode(base64, sizeof(base64), &n, random, sizeof(random))) return -1;
if (n != 24) return -1;
for (size_t i = 0; i < 22; i++)
out[i] = base64[i] == '+' ? '-' : base64[i] == '/' ? '_' : (char)base64[i];
out[22] = '\0';
return 0;
}
int vl_uuid4(vl_rng_fn rng, void *context, char out[37]) {
uint8_t random[16];
char hex[33];
size_t p = 0;
if (!rng || !out) return -1;
out[0] = '\0';
if (rng(context, random, sizeof(random)) != 0) return -1;
random[6] = (uint8_t)((random[6] & 15) | 0x40);
random[8] = (uint8_t)((random[8] & 63) | 0x80);
to_hex(random, 16, hex);
for (size_t i = 0; i < 32; i++) {
if (i == 8 || i == 12 || i == 16 || i == 20) out[p++] = '-';
out[p++] = hex[i];
}
out[p] = '\0';
return 0;
}
int vl_sha256_begin(mbedtls_md_context_t *context) {
if (!context) return -1;
mbedtls_md_init(context);
const mbedtls_md_info_t *info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
if (!info) return -1;
int rc = mbedtls_md_setup(context, info, 0);
if (rc == 0) rc = mbedtls_md_starts(context);
return rc;
}
int vl_sha256_update(mbedtls_md_context_t *context, const void *bytes, size_t n) {
if (!context || (!bytes && n)) return -1;
return n ? mbedtls_md_update(context, bytes, n) : 0;
}
int vl_sha256_finish(mbedtls_md_context_t *context, char out[65]) {
uint8_t digest[32];
if (!context || !out) return -1;
out[0] = '\0';
int rc = mbedtls_md_finish(context, digest);
if (rc == 0) to_hex(digest, 32, out);
return rc;
}
int vl_sha256_hex(const void *bytes, size_t length, char out[65]) {
if (!out || (!bytes && length)) return -1;
out[0] = '\0';
mbedtls_md_context_t context;
int rc = vl_sha256_begin(&context);
if (rc == 0) rc = vl_sha256_update(&context, bytes, length);
if (rc == 0) rc = vl_sha256_finish(&context, out);
mbedtls_md_free(&context);
return rc;
}
/* Hash the exact LF-joined fields without building a large intermediate string. */
static int hmac_fields(const uint8_t key[32], const char **fields,
size_t count, char out[65]) {
if (!key || !fields || !out) return -1;
out[0] = '\0';
for (size_t i = 0; i < count; i++)
if (!fields[i] || strchr(fields[i], '\n') || strchr(fields[i], '\r')) return -1;
const mbedtls_md_info_t *info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
if (!info) return -1;
mbedtls_md_context_t context;
uint8_t digest[32];
mbedtls_md_init(&context);
int rc = mbedtls_md_setup(&context, info, 1);
if (rc == 0) rc = mbedtls_md_hmac_starts(&context, key, 32);
for (size_t i = 0; rc == 0 && i < count; i++) {
if (i) rc = mbedtls_md_hmac_update(&context, (const uint8_t *)"\n", 1);
if (rc == 0) rc = mbedtls_md_hmac_update(&context, (const uint8_t *)fields[i], strlen(fields[i]));
}
if (rc == 0) rc = mbedtls_md_hmac_finish(&context, digest);
mbedtls_md_free(&context);
if (rc == 0) to_hex(digest, 32, out);
return rc;
}
int vl_http_sign(const uint8_t key[32], const char *method, const char *path,
const char *device_id, const char *key_version,
const char *timestamp_ms, const char *nonce,
const char *request_id, const void *body, size_t body_length,
char signature_hex[65]) {
if (!signature_hex) return -1;
signature_hex[0] = '\0';
if (!method || strcmp(method, "POST") || !path || path[0] != '/') return -1;
char body_sha[65];
if (vl_sha256_hex(body, body_length, body_sha)) return -1;
const char *fields[] = {"DEVREQ1", method, path, device_id, key_version,
timestamp_ms, nonce, request_id, body_sha};
return hmac_fields(key, fields, 9, signature_hex);
}
int vl_ble_pack(const void *json_utf8, size_t length,
uint8_t *frame, size_t capacity, size_t *frame_length) {
if (!frame_length) return -1;
*frame_length = 0;
if (!json_utf8 || !frame || length == 0 || length > 2048 || capacity < length + 2) return -1;
frame[0] = (uint8_t)(length & 255);
frame[1] = (uint8_t)(length >> 8);
memcpy(frame + 2, json_utf8, length);
*frame_length = length + 2;
return 0;
}
int vl_ota_verify(vl_read_fn read, void *context, uint64_t expected_size,
const char *expected_sha256) {
uint8_t buffer[1024], expected[32], actual[32];
char hash[65];
uint64_t total = 0;
if (!read || !expected_size || vl_key_from_hex(expected_sha256, expected)) return -1;
mbedtls_md_context_t sha;
int rc = vl_sha256_begin(&sha);
while (rc == 0) {
size_t n = 0;
if (read(context, buffer, sizeof(buffer), &n) != 0 || n > sizeof(buffer)) { rc = -1; break; }
if (n == 0) break;
if ((uint64_t)n > expected_size - total) { rc = -1; break; }
total += n;
rc = vl_sha256_update(&sha, buffer, n);
}
if (rc == 0 && total != expected_size) rc = -1;
if (rc == 0) rc = vl_sha256_finish(&sha, hash);
mbedtls_md_free(&sha);
if (rc != 0 || vl_key_from_hex(hash, actual)) return -1;
return memcmp(actual, expected, 32) == 0 ? 0 : -1;
}