#include #include #include #include #include "ble_identity_protocol.h" #include "channel_sounding_protocol.h" #include "radio_gateway_protocol.h" static void put16(uint8_t *p, uint16_t value) { p[0] = (uint8_t)value; p[1] = (uint8_t)(value >> 8); } static void put32(uint8_t *p, uint32_t value) { p[0] = (uint8_t)value; p[1] = (uint8_t)(value >> 8); p[2] = (uint8_t)(value >> 16); p[3] = (uint8_t)(value >> 24); } static size_t build_ble_advert(uint8_t *advert, size_t cap) { const size_t total = RV_BLE_SERVICE_DATA_SIZE + 2u; assert(cap >= total); advert[0] = (uint8_t)(RV_BLE_SERVICE_DATA_SIZE + 1u); advert[1] = RV_BLE_AD_TYPE_SERVICE_DATA_UUID128; memcpy(&advert[2], RV_BLE_SERVICE_UUID_LE, 16u); uint8_t *body = &advert[18]; body[0] = RV_BLE_TOKEN_VERSION; body[1] = 7u; put32(&body[2], 30000000u); put32(&body[6], 42u); for (size_t i = 0u; i < RV_BLE_EPHEMERAL_ID_SIZE; i++) body[10 + i] = (uint8_t)(0xa0u + i); for (size_t i = 0u; i < RV_BLE_AUTH_TAG_SIZE; i++) body[18 + i] = (uint8_t)(0x10u + i); return total; } static void build_cs_frame(uint8_t frame[RV_CS_FRAME_SIZE]) { memset(frame, 0, RV_CS_FRAME_SIZE); put32(&frame[0], RV_CS_MAGIC); frame[4] = RV_CS_VERSION; frame[5] = RV_CS_FLAG_CALIBRATED; frame[6] = 4u; frame[7] = 0u; put16(&frame[8], RV_CS_FRAME_SIZE); put16(&frame[10], 37u); put32(&frame[12], 9u); put32(&frame[16], 10000u); put32(&frame[20], 0x11223344u); put16(&frame[24], 800u); put16(&frame[26], 250u); put32(&frame[28], (uint32_t)-1200); put32(&frame[32], 50000u); put32(&frame[36], (uint32_t)-900); put32(&frame[40], 0x55667788u); put32(&frame[44], 0x12345678u); put16(&frame[48], 2u); put16(&frame[50], 8u); for (size_t i = 0u; i < RV_CS_AUTH_TAG_SIZE; i++) frame[52 + i] = (uint8_t)i; put32(&frame[68], rv_cs_crc32(frame, 68u)); } static void test_ble_parser_and_privacy_telemetry(void) { uint8_t advert[64]; size_t advert_len = build_ble_advert(advert, sizeof(advert)); rv_ble_token_t token; assert(rv_ble_parse_advertisement(advert, advert_len, &token)); assert(token.key_id == 7u); assert(token.epoch_min == 30000000u); assert(token.ephemeral_id[0] == 0xa0u); assert(token.auth_tag[15] == 0x1fu); uint8_t tampered[64]; memcpy(tampered, advert, advert_len); tampered[0] = 63u; /* field extends beyond report */ assert(!rv_ble_parse_advertisement(tampered, advert_len, &token)); memcpy(tampered, advert, advert_len); tampered[18] = 2u; /* unsupported token version */ assert(!rv_ble_parse_advertisement(tampered, advert_len, &token)); rv_ble_telemetry_t telemetry = { .node_id = 3u, .flags = RV_BLE_FLAG_AUTHENTICATED | RV_BLE_FLAG_TIME_VERIFIED, .key_id = 7u, .sequence = 11u, .observed_at_ms = 1200u, .ttl_ms = 3000u, .confidence_permille = 850u, .rssi_dbm = -61, .tx_power_dbm = 127, .token_epoch_min = 30000000u, }; memcpy(telemetry.ephemeral_id, token.ephemeral_id, 8u); uint8_t packet[RV_BLE_TELEMETRY_SIZE]; assert(rv_ble_serialize_telemetry(&telemetry, packet, sizeof(packet))); assert(RV_BLE_TELEMETRY_MAGIC != 0xC5110005u); /* compressed CSI */ assert(packet[0] == 0xb1u && packet[1] == 0x00u && packet[2] == 0x11u && packet[3] == 0xc5u); assert(packet[4] == RV_BLE_TELEMETRY_VERSION); assert(packet[5] == 3u); assert(packet[20] == (uint8_t)-61); /* The packet has no BLE address, raw advertising data, nonce or HMAC. */ assert(memcmp(&packet[24], token.ephemeral_id, 8u) == 0); telemetry.flags = 0u; assert(!rv_ble_serialize_telemetry(&telemetry, packet, sizeof(packet))); } static void test_channel_sounding_validation(void) { uint8_t frame[RV_CS_FRAME_SIZE]; build_cs_frame(frame); rv_cs_measurement_t measurement; assert(rv_cs_parse_frame(frame, sizeof(frame), 2000000u, 600u, &measurement) == RV_CS_PARSE_OK); assert(measurement.source_id == 0x11223344u); assert(measurement.phase_millirad == -1200); assert(measurement.frequency_offset_hz == -900); assert(measurement.source_session_id == 0x55667788u); assert(measurement.procedure_id == 0x12345678u); assert(measurement.step_index == 2u); assert(measurement.step_count == 8u); frame[28] ^= 1u; assert(rv_cs_parse_frame(frame, sizeof(frame), 2000000u, 600u, &measurement) == RV_CS_PARSE_BAD_CRC); build_cs_frame(frame); put32(&frame[16], 3000000u); put32(&frame[68], rv_cs_crc32(frame, 68u)); assert(rv_cs_parse_frame(frame, sizeof(frame), 2000000u, 600u, &measurement) == RV_CS_PARSE_STALE); build_cs_frame(frame); uint8_t covered[RV_CS_MAC_INPUT_SIZE]; rv_cs_mac_input(frame, covered); assert(memcmp(covered, "RuView/CS/v1", RV_CS_MAC_DOMAIN_SIZE) == 0); assert(memcmp(&covered[RV_CS_MAC_DOMAIN_SIZE], frame, RV_CS_SIGNED_PREFIX_SIZE) == 0); assert(rv_cs_sequence_is_newer(10u, 9u)); assert(!rv_cs_sequence_is_newer(9u, 9u)); assert(rv_cs_sequence_is_newer(0u, UINT32_MAX)); build_cs_frame(frame); put16(&frame[50], 1u); put32(&frame[68], rv_cs_crc32(frame, 68u)); assert(rv_cs_parse_frame(frame, sizeof(frame), 2000000u, 600u, &measurement) == RV_CS_PARSE_BAD_STEP); } static void test_gateway_envelope_contract(void) { uint8_t payload[RV_BLE_TELEMETRY_SIZE] = {0}; uint8_t frame[RV_GATEWAY_MAX_FRAME_SIZE]; size_t signed_len = 0u; size_t frame_len = 0u; rv_gateway_metadata_t metadata = { .payload_type = RV_GATEWAY_PAYLOAD_BLE_IDENTITY, .flags = RV_GATEWAY_FLAG_RX_MONOTONIC, .key_id = 3u, .node_id = 9u, .sequence = 11u, .boot_nonce = 0x0102030405060708ull, .received_at_boot_us = 9000u, .timing_uncertainty_us = 1000u, }; assert(rv_gateway_build_unsigned(&metadata, payload, sizeof(payload), frame, sizeof(frame), &signed_len, &frame_len)); assert(signed_len == RV_GATEWAY_HEADER_SIZE + sizeof(payload)); assert(frame_len == signed_len + RV_GATEWAY_AUTH_TAG_SIZE); assert(memcmp(&frame[0], "RVAE", 4u) == 0); assert(frame[4] == RV_GATEWAY_VERSION); assert(frame[5] == RV_GATEWAY_PAYLOAD_BLE_IDENTITY); assert(frame[13] == 0u && frame[14] == 0u && frame[15] == 0u); metadata.sequence = 0u; assert(!rv_gateway_build_unsigned(&metadata, payload, sizeof(payload), frame, sizeof(frame), &signed_len, &frame_len)); uint8_t lhs[RV_GATEWAY_AUTH_TAG_SIZE] = {0}; uint8_t rhs[RV_GATEWAY_AUTH_TAG_SIZE] = {0}; assert(rv_gateway_auth_tag_equal(lhs, rhs)); rhs[15] = 1u; assert(!rv_gateway_auth_tag_equal(lhs, rhs)); } int main(void) { test_ble_parser_and_privacy_telemetry(); test_channel_sounding_validation(); test_gateway_envelope_contract(); puts("BLE identity and external Channel Sounding protocol tests passed (SYNTHETIC)"); return 0; }