diff --git a/Makefile b/Makefile index 8d40d2d..3482b23 100644 --- a/Makefile +++ b/Makefile @@ -18,12 +18,18 @@ test-host: lib/host_tests/test_ccid_logic.c \ lib/host_tests/test_t1_existing.c \ lib/host_tests/test_t1_protocol.c \ + lib/host_tests/test_snmp.c \ lib/host_tests/t1_test_stubs.c \ lib/host_tests/bit_buffer_mock.c \ lrc.c \ ccid_logic.c \ t_1_logic.c \ t_1.c \ + snmp_ber_view.c \ + snmp_codec.c \ + snmp_response_view.c \ + uhf_tag_config_view.c \ + uhf_snmp_probe.c \ -o build/host_tests/seader_tests ./build/host_tests/seader_tests diff --git a/lib/host_tests/test_main.c b/lib/host_tests/test_main.c index afbf0fe..ae93b93 100644 --- a/lib/host_tests/test_main.c +++ b/lib/host_tests/test_main.c @@ -4,6 +4,7 @@ extern MunitSuite test_lrc_suite; extern MunitSuite test_ccid_logic_suite; extern MunitSuite test_t1_existing_suite; extern MunitSuite test_t1_protocol_suite; +extern MunitSuite test_snmp_suite; int main(int argc, char* argv[]) { MunitSuite child_suites[] = { @@ -11,6 +12,7 @@ int main(int argc, char* argv[]) { {"/t1", test_t1_existing_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/t1", test_t1_protocol_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/ccid", test_ccid_logic_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/snmp", test_snmp_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {NULL, NULL, NULL, 0, 0}, }; MunitSuite main_suite = { diff --git a/lib/host_tests/test_snmp.c b/lib/host_tests/test_snmp.c new file mode 100644 index 0000000..f09c275 --- /dev/null +++ b/lib/host_tests/test_snmp.c @@ -0,0 +1,255 @@ +#include +#include + +#include "munit.h" +#include "snmp_codec.h" +#include "uhf_snmp_probe.h" +#include "snmp_response_view.h" +#include "uhf_tag_config_view.h" + +static const char* snmp_discovery_request_hex = + "3038020103300E020100020202F4040104020201010410300E0400020100020100040004000400301104000400A00B0201000201000201003000"; +static const char* snmp_discovery_response_hex = + "308200A80201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020101040D2B0601040181E438010104080F0400040030820051041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F040D2B0601040181E438010104080FA08200210201000201000201003082001430820010060A2B060106030F0101040002020141"; +static const char* snmp_ice_response_hex = + "308200A80201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020102040D2B0601040181E438010104080F0400040030820051041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F040D2B0601040181E438010104080FA2820021020100020100020100308200143082001006050301070138040749434531383033"; +static const char* snmp_uhf_config_response_hex = + "308200F40201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020103040D2B0601040181E438010104080F040004003082009D041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F040D2B0601040181E438010104080FA282006D020100020100020100308200603082005C0606030107030B00045204E2003412112B0601040181E438010102012201010101112B0601040181E43801010201220101020104E2801105112B0601040181E438010102011E01010101112B0601040181E438010102011E01010201"; +static const char* snmp_ice_request_hex = + "307A020103300E020100020202F40401040202010104383036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020101040D2B0601040181E438010104080F04000400302B04000400A025020100020100020100301A30180604030003060410300E0605030107013802010002020100"; +static const char* snmp_monza_request_hex = + "308186020103300E020100020202F40401040202010104383036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020101040D2B0601040181E438010104080F04000400303704000400A03102010002010002010030263024060403000306041C301A06112B0601040181E438010102011E0101010102010002020100"; +static const char* snmp_higgs_request_hex = + "308186020103300E020100020202F40401040202010104383036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020101040D2B0601040181E438010104080F04000400303704000400A03102010002010002010030263024060403000306041C301A06112B0601040181E43801010201220101010102010002020100"; + +static const uint8_t oid_elite_ice[] = {0x03, 0x01, 0x07, 0x01, 0x38}; +static const uint8_t oid_uhf_tags_config[] = {0x03, 0x01, 0x07, 0x03, 0x0B, 0x00}; +static const uint8_t oid_monza4qt_access_key[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x1E, 0x01, 0x01, 0x01, 0x01}; +static const uint8_t oid_higgs3_access_key[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x22, 0x01, 0x01, 0x01, 0x01}; +static const uint8_t live_engine_id[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x03, + 0x05, 0x0F, 0x8C, 0x90, 0x88, 0xCD, 0xA2, 0xA8, 0xD8, 0x85, 0xC0, + 0xB2, 0x98, 0xD7, 0xFF, 0x7F}; +static const uint8_t live_username[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x04, 0x08, 0x0F}; + +static size_t test_hex_to_bytes(const char* hex, uint8_t* out, size_t out_size) { + size_t len = 0U; + int high_nibble = -1; + + for(const char* p = hex; *p; ++p) { + int value = -1; + if(*p >= '0' && *p <= '9') value = *p - '0'; + else if(*p >= 'A' && *p <= 'F') value = *p - 'A' + 10; + else if(*p >= 'a' && *p <= 'f') value = *p - 'a' + 10; + else if(isspace((unsigned char)*p)) continue; + else munit_error("invalid hex character"); + + if(high_nibble < 0) { + high_nibble = value; + } else { + if(len >= out_size) munit_error("hex output buffer too small"); + out[len++] = (uint8_t)((high_nibble << 4) | value); + high_nibble = -1; + } + } + + if(high_nibble >= 0) munit_error("odd-length hex string"); + return len; +} + +static MunitResult test_build_discovery_request_matches_live_vector(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t message[512] = {0}; + uint8_t scratch[512] = {0}; + uint8_t expected[512] = {0}; + size_t message_len = 0U; + size_t expected_len = test_hex_to_bytes(snmp_discovery_request_hex, expected, sizeof(expected)); + + munit_assert_true(seader_snmp_build_discovery_request( + scratch, sizeof(scratch), message, sizeof(message), &message_len)); + munit_assert_size(message_len, ==, expected_len); + munit_assert_memory_equal(expected_len, message, expected); + return MUNIT_OK; +} + +static MunitResult test_build_get_data_requests_match_live_vectors(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t message[512] = {0}; + uint8_t scratch[512] = {0}; + uint8_t expected[512] = {0}; + size_t message_len = 0U; + size_t expected_len = test_hex_to_bytes(snmp_ice_request_hex, expected, sizeof(expected)); + + munit_assert_true(seader_snmp_build_get_data_request( + live_engine_id, sizeof(live_engine_id), live_username, sizeof(live_username), 5U, 1U, + oid_elite_ice, sizeof(oid_elite_ice), scratch, sizeof(scratch), message, sizeof(message), &message_len)); + munit_assert_size(message_len, ==, expected_len); + munit_assert_memory_equal(expected_len, message, expected); + + expected_len = test_hex_to_bytes(snmp_monza_request_hex, expected, sizeof(expected)); + munit_assert_true(seader_snmp_build_get_data_request( + live_engine_id, sizeof(live_engine_id), live_username, sizeof(live_username), 5U, 1U, + oid_monza4qt_access_key, sizeof(oid_monza4qt_access_key), scratch, sizeof(scratch), message, sizeof(message), &message_len)); + munit_assert_size(message_len, ==, expected_len); + munit_assert_memory_equal(expected_len, message, expected); + + expected_len = test_hex_to_bytes(snmp_higgs_request_hex, expected, sizeof(expected)); + munit_assert_true(seader_snmp_build_get_data_request( + live_engine_id, sizeof(live_engine_id), live_username, sizeof(live_username), 5U, 1U, + oid_higgs3_access_key, sizeof(oid_higgs3_access_key), scratch, sizeof(scratch), message, sizeof(message), &message_len)); + munit_assert_size(message_len, ==, expected_len); + munit_assert_memory_equal(expected_len, message, expected); + + return MUNIT_OK; +} + +static MunitResult test_parse_response_and_zero_copy_views(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[256] = {0}; + size_t response_len = test_hex_to_bytes(snmp_discovery_response_hex, response, sizeof(response)); + SeaderSnmpResponseView view = {0}; + + munit_assert_true(seader_snmp_parse_response_view(response, response_len, &view)); + munit_assert_uint32(view.error_status, ==, 0U); + munit_assert_uint32(view.error_index, ==, 0U); + munit_assert_uint32(view.usm_engine_boots, ==, 5U); + munit_assert_uint32(view.usm_engine_time, ==, 1U); + munit_assert_memory_equal(sizeof(live_engine_id), view.context_engine_id.ptr, live_engine_id); + munit_assert_memory_equal(sizeof(live_engine_id), view.usm_engine_id.ptr, live_engine_id); + munit_assert_memory_equal(sizeof(live_username), view.usm_username.ptr, live_username); + munit_assert_true(view.context_engine_id.ptr >= response); + munit_assert_true(view.context_engine_id.ptr < response + response_len); + return MUNIT_OK; +} + +static MunitResult test_parse_ice_and_tag_config_values(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + size_t response_len = test_hex_to_bytes(snmp_ice_response_hex, response, sizeof(response)); + SeaderSnmpResponseView view = {0}; + SeaderBytesView value = {0}; + + munit_assert_true(seader_snmp_parse_response_view(response, response_len, &view)); + munit_assert_true(seader_snmp_find_varbind_octet_value( + view.varbind_sequence, (SeaderBytesView){oid_elite_ice, sizeof(oid_elite_ice)}, &value)); + munit_assert_size(value.len, ==, 7); + munit_assert_memory_equal(7, value.ptr, "ICE1803"); + + response_len = test_hex_to_bytes(snmp_uhf_config_response_hex, response, sizeof(response)); + munit_assert_true(seader_snmp_parse_response_view(response, response_len, &view)); + munit_assert_true(seader_snmp_find_varbind_octet_value( + view.varbind_sequence, (SeaderBytesView){oid_uhf_tags_config, sizeof(oid_uhf_tags_config)}, &value)); + munit_assert_size(value.len, >, 80); + munit_assert_true(value.ptr >= response); + munit_assert_true(value.ptr < response + response_len); + return MUNIT_OK; +} + +static MunitResult test_probe_stages(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + size_t response_len = test_hex_to_bytes(snmp_discovery_response_hex, response, sizeof(response)); + SeaderUhfSnmpProbe probe = {0}; + uint8_t message[512] = {0}; + uint8_t scratch[512] = {0}; + size_t message_len = 0U; + + seader_uhf_snmp_probe_init(&probe); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageDiscovery); + munit_assert_true(seader_uhf_snmp_probe_build_next_request( + &probe, scratch, sizeof(scratch), message, sizeof(message), &message_len)); + munit_assert_size(message_len, >, 0); + + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadIce); + memset(response, 0xA5, response_len); + munit_assert_true(seader_uhf_snmp_probe_build_next_request( + &probe, scratch, sizeof(scratch), message, sizeof(message), &message_len)); + + response_len = test_hex_to_bytes(snmp_ice_response_hex, response, sizeof(response)); + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadTagConfig); + munit_assert_size(probe.ice_value_len, ==, 7); + munit_assert_memory_equal(7, probe.ice_value_storage, "ICE1803"); + + response_len = test_hex_to_bytes(snmp_uhf_config_response_hex, response, sizeof(response)); + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + munit_assert_true(probe.has_monza4qt); + munit_assert_true(probe.has_higgs3); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadMonza4QtKey); + munit_assert_size(probe.tag_config_value.len, >, 80); + + munit_assert_true(seader_uhf_snmp_probe_consume_error( + &probe, 0x06U, (const uint8_t*)"\x69\x82", 2U)); + munit_assert_true(probe.monza4qt_key_present); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadHiggs3Key); + + munit_assert_true(seader_uhf_snmp_probe_consume_error( + &probe, 0x06U, (const uint8_t*)"\x69\x82", 2U)); + munit_assert_true(probe.higgs3_key_present); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageDone); + return MUNIT_OK; +} + +static MunitResult test_tag_config_view_extracts_known_entries(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + size_t response_len = test_hex_to_bytes(snmp_uhf_config_response_hex, response, sizeof(response)); + SeaderSnmpResponseView snmp = {0}; + SeaderBytesView config_payload = {0}; + SeaderUhfTagConfigView view = {0}; + SeaderUhfTagConfigEntryView entry = {0}; + + munit_assert_true(seader_snmp_parse_response_view(response, response_len, &snmp)); + munit_assert_true(seader_snmp_find_varbind_octet_value( + snmp.varbind_sequence, (SeaderBytesView){oid_uhf_tags_config, sizeof(oid_uhf_tags_config)}, &config_payload)); + munit_assert_true(seader_uhf_tag_config_parse(config_payload, &view)); + munit_assert_true(view.has_higgs3); + munit_assert_true(view.has_monza4qt); + munit_assert_size(view.entry_count, ==, 4); + + munit_assert_true(seader_uhf_tag_config_get_entry(&view, 0U, &entry)); + munit_assert_int(entry.kind, ==, SeaderUhfTagConfigEntryHiggs3Access); + munit_assert_size(entry.oid.len, ==, 17U); + + munit_assert_true(seader_uhf_tag_config_get_entry(&view, 2U, &entry)); + munit_assert_int(entry.kind, ==, SeaderUhfTagConfigEntryMonza4QtAccess); + return MUNIT_OK; +} + +static MunitResult test_response_rejects_truncated_length(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + const uint8_t malformed[] = {0x30, 0x82, 0x01}; + SeaderSnmpResponseView view = {0}; + munit_assert_false(seader_snmp_parse_response_view(malformed, sizeof(malformed), &view)); + return MUNIT_OK; +} + +static MunitTest test_snmp_cases[] = { + {(char*)"/build-discovery", test_build_discovery_request_matches_live_vector, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/build-get-data", test_build_get_data_requests_match_live_vectors, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/parse-response", test_parse_response_and_zero_copy_views, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/parse-values", test_parse_ice_and_tag_config_values, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/probe", test_probe_stages, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/tag-config", test_tag_config_view_extracts_known_entries, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/malformed-length", test_response_rejects_truncated_length, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_snmp_suite = { + "", + test_snmp_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/snmp_ber_view.c b/snmp_ber_view.c new file mode 100644 index 0000000..e981c47 --- /dev/null +++ b/snmp_ber_view.c @@ -0,0 +1,63 @@ +#include "snmp_ber_view.h" + +void seader_ber_cursor_init(SeaderBerCursor* cursor, const uint8_t* buffer, size_t len) { + if(!cursor) return; + cursor->buffer = buffer; + cursor->len = len; + cursor->offset = 0U; +} + +static bool seader_ber_read_length(SeaderBerCursor* cursor, size_t* value_len) { + if(!cursor || !value_len || cursor->offset >= cursor->len) return false; + + uint8_t descriptor = cursor->buffer[cursor->offset++]; + if((descriptor & 0x80U) == 0U) { + *value_len = descriptor; + return true; + } + + size_t length_len = descriptor & 0x7FU; + if(length_len == 0U || length_len > sizeof(size_t)) return false; + if(cursor->offset + length_len > cursor->len) return false; + + size_t parsed = 0U; + for(size_t i = 0U; i < length_len; i++) { + parsed = (parsed << 8U) | cursor->buffer[cursor->offset++]; + } + + *value_len = parsed; + return true; +} + +bool seader_ber_next_tlv(SeaderBerCursor* cursor, SeaderBerTlvView* tlv) { + if(!cursor || !tlv || !cursor->buffer) return false; + if(cursor->offset + 2U > cursor->len) return false; + + tlv->tag = cursor->buffer[cursor->offset++]; + + size_t value_len = 0U; + if(!seader_ber_read_length(cursor, &value_len)) return false; + if(cursor->offset + value_len > cursor->len) return false; + + tlv->value.ptr = cursor->buffer + cursor->offset; + tlv->value.len = value_len; + cursor->offset += value_len; + return true; +} + +bool seader_ber_parse_uint32(SeaderBytesView value, uint32_t* out) { + if(!value.ptr || value.len == 0U || !out) return false; + if(value.len > 1U && value.ptr[0] == 0x00U) { + value.ptr++; + value.len--; + } + if(value.len == 0U || value.len > 4U) return false; + + uint32_t parsed = 0U; + for(size_t i = 0U; i < value.len; i++) { + parsed = (parsed << 8U) | value.ptr[i]; + } + + *out = parsed; + return true; +} diff --git a/snmp_ber_view.h b/snmp_ber_view.h new file mode 100644 index 0000000..d48f1ac --- /dev/null +++ b/snmp_ber_view.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +typedef struct { + const uint8_t* ptr; + size_t len; +} SeaderBytesView; + +typedef struct { + uint8_t tag; + SeaderBytesView value; +} SeaderBerTlvView; + +typedef struct { + const uint8_t* buffer; + size_t len; + size_t offset; +} SeaderBerCursor; + +void seader_ber_cursor_init(SeaderBerCursor* cursor, const uint8_t* buffer, size_t len); + +bool seader_ber_next_tlv(SeaderBerCursor* cursor, SeaderBerTlvView* tlv); + +bool seader_ber_parse_uint32(SeaderBytesView value, uint32_t* out); diff --git a/snmp_codec.c b/snmp_codec.c new file mode 100644 index 0000000..a35f56b --- /dev/null +++ b/snmp_codec.c @@ -0,0 +1,321 @@ +#include "snmp_codec.h" + +#include + +static bool + seader_snmp_append_length(uint8_t* dst, size_t dst_size, size_t* offset, size_t value_len) { + if(!dst || !offset) return false; + + if(value_len < 0x80U) { + if(*offset + 1U > dst_size) return false; + dst[(*offset)++] = (uint8_t)value_len; + return true; + } + if(value_len <= 0xFFU) { + if(*offset + 2U > dst_size) return false; + dst[(*offset)++] = 0x81U; + dst[(*offset)++] = (uint8_t)value_len; + return true; + } + if(value_len <= 0xFFFFU) { + if(*offset + 3U > dst_size) return false; + dst[(*offset)++] = 0x82U; + dst[(*offset)++] = (uint8_t)(value_len >> 8U); + dst[(*offset)++] = (uint8_t)value_len; + return true; + } + if(value_len <= 0xFFFFFFU) { + if(*offset + 4U > dst_size) return false; + dst[(*offset)++] = 0x83U; + dst[(*offset)++] = (uint8_t)(value_len >> 16U); + dst[(*offset)++] = (uint8_t)(value_len >> 8U); + dst[(*offset)++] = (uint8_t)value_len; + return true; + } + + return false; +} + +static bool seader_snmp_append_tlv( + uint8_t* dst, + size_t dst_size, + size_t* offset, + uint8_t tag, + const uint8_t* value, + size_t value_len) { + if(!dst || !offset) return false; + if(*offset + 1U > dst_size) return false; + + dst[(*offset)++] = tag; + if(!seader_snmp_append_length(dst, dst_size, offset, value_len)) return false; + + if(value_len > 0U) { + if(!value || *offset + value_len > dst_size) return false; + memcpy(dst + *offset, value, value_len); + *offset += value_len; + } + + return true; +} + +static bool + seader_snmp_append_uint32(uint8_t* dst, size_t dst_size, size_t* offset, uint32_t value) { + uint8_t encoded[5] = {0}; + size_t encoded_len = 0U; + + do { + encoded[4U - encoded_len] = (uint8_t)(value & 0xFFU); + encoded_len++; + value >>= 8U; + } while(value != 0U && encoded_len < 5U); + + const uint8_t* integer_value = encoded + (5U - encoded_len); + uint8_t prefixed[6] = {0}; + size_t prefixed_len = 0U; + + if(integer_value[0] & 0x80U) { + prefixed[prefixed_len++] = 0x00U; + } + memcpy(prefixed + prefixed_len, integer_value, encoded_len); + prefixed_len += encoded_len; + + return seader_snmp_append_tlv(dst, dst_size, offset, 0x02U, prefixed, prefixed_len); +} + +static bool seader_snmp_wrap_tlv_inplace( + uint8_t* buffer, + size_t buffer_capacity, + size_t value_len, + uint8_t tag, + size_t* encoded_len) { + if(!buffer || !encoded_len) return false; + + uint8_t header[5] = {tag}; + size_t header_len = 1U; + if(!seader_snmp_append_length(header, sizeof(header), &header_len, value_len)) return false; + if(header_len + value_len > buffer_capacity) return false; + + memmove(buffer + header_len, buffer, value_len); + memcpy(buffer, header, header_len); + *encoded_len = header_len + value_len; + return true; +} + +static bool seader_snmp_build_get_data_payload( + const uint8_t* target_oid, + size_t target_oid_len, + uint32_t offset_value, + uint32_t max_chunk_len, + uint8_t* payload, + size_t payload_capacity, + size_t* payload_len) { + if(!target_oid || target_oid_len == 0U || !payload || !payload_len) return false; + *payload_len = 0U; + + if(!seader_snmp_append_tlv( + payload, payload_capacity, payload_len, 0x06U, target_oid, target_oid_len)) { + return false; + } + if(!seader_snmp_append_uint32(payload, payload_capacity, payload_len, offset_value)) + return false; + if(!seader_snmp_append_uint32(payload, payload_capacity, payload_len, max_chunk_len)) + return false; + + return seader_snmp_wrap_tlv_inplace( + payload, payload_capacity, *payload_len, 0x30U, payload_len); +} + +static bool seader_snmp_build_reportable_get_request( + const uint8_t* engine_id, + size_t engine_id_len, + const uint8_t* user_name, + size_t user_name_len, + uint32_t engine_boots, + uint32_t engine_time, + const uint8_t* data_oid, + size_t data_oid_len, + const uint8_t* data, + size_t data_len, + uint8_t* scratch, + size_t scratch_capacity, + uint8_t* message, + size_t message_capacity, + size_t* message_len) { + size_t scratch_len = 0U; + size_t scoped_pdu_len = 0U; + uint8_t global_data_value[16] = {0}; + uint8_t global_data[24] = {0}; + uint8_t version[8] = {0}; + size_t global_data_value_len = 0U; + size_t global_data_len = 0U; + size_t version_len = 0U; + const uint8_t flags[] = {0x04U}; + + if(!scratch || !message || !message_len) return false; + *message_len = 0U; + + if(data_oid && data_oid_len > 0U) { + if(!seader_snmp_append_tlv( + scratch, scratch_capacity, &scratch_len, 0x06U, data_oid, data_oid_len)) { + return false; + } + if(!seader_snmp_append_tlv( + scratch, scratch_capacity, &scratch_len, 0x04U, data, data_len)) { + return false; + } + if(!seader_snmp_wrap_tlv_inplace( + scratch, scratch_capacity, scratch_len, 0x30U, &scratch_len)) { + return false; + } + } + + memcpy(message, scratch, scratch_len); + *message_len = scratch_len; + if(!seader_snmp_wrap_tlv_inplace(message, message_capacity, *message_len, 0x30U, message_len)) { + return false; + } + + scratch_len = 0U; + if(!seader_snmp_append_uint32(scratch, scratch_capacity, &scratch_len, 0U) || + !seader_snmp_append_uint32(scratch, scratch_capacity, &scratch_len, 0U) || + !seader_snmp_append_uint32(scratch, scratch_capacity, &scratch_len, 0U)) { + return false; + } + if(scratch_len + *message_len > scratch_capacity) return false; + memcpy(scratch + scratch_len, message, *message_len); + scratch_len += *message_len; + if(!seader_snmp_wrap_tlv_inplace(scratch, scratch_capacity, scratch_len, 0xA0U, &scratch_len)) { + return false; + } + + *message_len = 0U; + if(!seader_snmp_append_tlv(message, message_capacity, message_len, 0x04U, NULL, 0U) || + !seader_snmp_append_tlv(message, message_capacity, message_len, 0x04U, NULL, 0U)) { + return false; + } + if(*message_len + scratch_len > message_capacity) return false; + memcpy(message + *message_len, scratch, scratch_len); + *message_len += scratch_len; + if(!seader_snmp_wrap_tlv_inplace( + message, message_capacity, *message_len, 0x30U, &scoped_pdu_len)) { + return false; + } + + scratch_len = 0U; + if(!seader_snmp_append_tlv( + scratch, scratch_capacity, &scratch_len, 0x04U, engine_id, engine_id_len) || + !seader_snmp_append_uint32(scratch, scratch_capacity, &scratch_len, engine_boots) || + !seader_snmp_append_uint32(scratch, scratch_capacity, &scratch_len, engine_time) || + !seader_snmp_append_tlv( + scratch, scratch_capacity, &scratch_len, 0x04U, user_name, user_name_len) || + !seader_snmp_append_tlv(scratch, scratch_capacity, &scratch_len, 0x04U, NULL, 0U) || + !seader_snmp_append_tlv(scratch, scratch_capacity, &scratch_len, 0x04U, NULL, 0U)) { + return false; + } + if(!seader_snmp_wrap_tlv_inplace(scratch, scratch_capacity, scratch_len, 0x30U, &scratch_len) || + !seader_snmp_wrap_tlv_inplace(scratch, scratch_capacity, scratch_len, 0x04U, &scratch_len)) { + return false; + } + + if(!seader_snmp_append_uint32( + global_data_value, sizeof(global_data_value), &global_data_value_len, 0U) || + !seader_snmp_append_uint32( + global_data_value, sizeof(global_data_value), &global_data_value_len, 756U) || + !seader_snmp_append_tlv( + global_data_value, + sizeof(global_data_value), + &global_data_value_len, + 0x04U, + flags, + sizeof(flags)) || + !seader_snmp_append_uint32( + global_data_value, sizeof(global_data_value), &global_data_value_len, 0x0101U)) { + return false; + } + if(!seader_snmp_append_tlv( + global_data, + sizeof(global_data), + &global_data_len, + 0x30U, + global_data_value, + global_data_value_len)) { + return false; + } + if(!seader_snmp_append_uint32(version, sizeof(version), &version_len, 3U)) return false; + + if(version_len + global_data_len + scratch_len + scoped_pdu_len > message_capacity) + return false; + memmove(message + version_len + global_data_len + scratch_len, message, scoped_pdu_len); + memcpy(message, version, version_len); + memcpy(message + version_len, global_data, global_data_len); + memcpy(message + version_len + global_data_len, scratch, scratch_len); + + *message_len = version_len + global_data_len + scratch_len + scoped_pdu_len; + return seader_snmp_wrap_tlv_inplace( + message, message_capacity, *message_len, 0x30U, message_len); +} + +bool seader_snmp_build_discovery_request( + uint8_t* scratch, + size_t scratch_capacity, + uint8_t* message, + size_t message_capacity, + size_t* message_len) { + return seader_snmp_build_reportable_get_request( + NULL, + 0U, + NULL, + 0U, + 0U, + 0U, + NULL, + 0U, + NULL, + 0U, + scratch, + scratch_capacity, + message, + message_capacity, + message_len); +} + +bool seader_snmp_build_get_data_request( + const uint8_t* engine_id, + size_t engine_id_len, + const uint8_t* user_name, + size_t user_name_len, + uint32_t engine_boots, + uint32_t engine_time, + const uint8_t* target_oid, + size_t target_oid_len, + uint8_t* scratch, + size_t scratch_capacity, + uint8_t* message, + size_t message_capacity, + size_t* message_len) { + static const uint8_t oid_get_data[] = {0x03U, 0x00U, 0x03U, 0x06U}; + size_t payload_len = 0U; + + if(!scratch || !message) return false; + if(!seader_snmp_build_get_data_payload( + target_oid, target_oid_len, 0U, 0x100U, message, message_capacity, &payload_len)) { + return false; + } + + return seader_snmp_build_reportable_get_request( + engine_id, + engine_id_len, + user_name, + user_name_len, + engine_boots, + engine_time, + oid_get_data, + sizeof(oid_get_data), + message, + payload_len, + scratch, + scratch_capacity, + message, + message_capacity, + message_len); +} diff --git a/snmp_codec.h b/snmp_codec.h new file mode 100644 index 0000000..6fb975a --- /dev/null +++ b/snmp_codec.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +bool seader_snmp_build_discovery_request( + uint8_t* scratch, + size_t scratch_capacity, + uint8_t* message, + size_t message_capacity, + size_t* message_len); + +bool seader_snmp_build_get_data_request( + const uint8_t* engine_id, + size_t engine_id_len, + const uint8_t* user_name, + size_t user_name_len, + uint32_t engine_boots, + uint32_t engine_time, + const uint8_t* target_oid, + size_t target_oid_len, + uint8_t* scratch, + size_t scratch_capacity, + uint8_t* message, + size_t message_capacity, + size_t* message_len); diff --git a/snmp_response_view.c b/snmp_response_view.c new file mode 100644 index 0000000..1b50860 --- /dev/null +++ b/snmp_response_view.c @@ -0,0 +1,122 @@ +#include "snmp_response_view.h" + +#include + +bool seader_snmp_parse_response_view( + const uint8_t* response, + size_t response_len, + SeaderSnmpResponseView* view) { + SeaderBerCursor root_cursor = {0}; + SeaderBerTlvView root = {0}; + SeaderBerTlvView tlv = {0}; + + if(!response || response_len == 0U || !view) return false; + memset(view, 0, sizeof(*view)); + + seader_ber_cursor_init(&root_cursor, response, response_len); + if(!seader_ber_next_tlv(&root_cursor, &root) || root.tag != 0x30U) return false; + + SeaderBerCursor message_cursor = {0}; + seader_ber_cursor_init(&message_cursor, root.value.ptr, root.value.len); + + if(!seader_ber_next_tlv(&message_cursor, &tlv) || tlv.tag != 0x02U) return false; + if(!seader_ber_next_tlv(&message_cursor, &tlv) || tlv.tag != 0x30U) return false; + + SeaderBerTlvView security_params_octet = {0}; + if(!seader_ber_next_tlv(&message_cursor, &security_params_octet) || + security_params_octet.tag != 0x04U) { + return false; + } + if(!seader_ber_next_tlv(&message_cursor, &tlv) || tlv.tag != 0x30U) return false; + + SeaderBerCursor sec_octet_cursor = {0}; + SeaderBerTlvView sec_sequence = {0}; + seader_ber_cursor_init( + &sec_octet_cursor, security_params_octet.value.ptr, security_params_octet.value.len); + if(!seader_ber_next_tlv(&sec_octet_cursor, &sec_sequence) || sec_sequence.tag != 0x30U) + return false; + + SeaderBerCursor sec_cursor = {0}; + SeaderBerTlvView usm_engine_id = {0}; + SeaderBerTlvView usm_engine_boots = {0}; + SeaderBerTlvView usm_engine_time = {0}; + SeaderBerTlvView usm_username = {0}; + SeaderBerTlvView ignore = {0}; + seader_ber_cursor_init(&sec_cursor, sec_sequence.value.ptr, sec_sequence.value.len); + + if(!seader_ber_next_tlv(&sec_cursor, &usm_engine_id) || usm_engine_id.tag != 0x04U) + return false; + if(!seader_ber_next_tlv(&sec_cursor, &usm_engine_boots) || usm_engine_boots.tag != 0x02U) + return false; + if(!seader_ber_next_tlv(&sec_cursor, &usm_engine_time) || usm_engine_time.tag != 0x02U) + return false; + if(!seader_ber_next_tlv(&sec_cursor, &usm_username) || usm_username.tag != 0x04U) return false; + if(!seader_ber_next_tlv(&sec_cursor, &ignore) || ignore.tag != 0x04U) return false; + if(!seader_ber_next_tlv(&sec_cursor, &ignore) || ignore.tag != 0x04U) return false; + + SeaderBerCursor scoped_cursor = {0}; + SeaderBerTlvView context_engine = {0}; + SeaderBerTlvView context_name = {0}; + SeaderBerTlvView pdu = {0}; + seader_ber_cursor_init(&scoped_cursor, tlv.value.ptr, tlv.value.len); + if(!seader_ber_next_tlv(&scoped_cursor, &context_engine) || context_engine.tag != 0x04U) + return false; + if(!seader_ber_next_tlv(&scoped_cursor, &context_name) || context_name.tag != 0x04U) + return false; + if(!seader_ber_next_tlv(&scoped_cursor, &pdu)) return false; + + SeaderBerCursor pdu_cursor = {0}; + SeaderBerTlvView request_id = {0}; + SeaderBerTlvView error_status = {0}; + SeaderBerTlvView error_index = {0}; + SeaderBerTlvView varbinds = {0}; + seader_ber_cursor_init(&pdu_cursor, pdu.value.ptr, pdu.value.len); + if(!seader_ber_next_tlv(&pdu_cursor, &request_id) || request_id.tag != 0x02U) return false; + if(!seader_ber_next_tlv(&pdu_cursor, &error_status) || error_status.tag != 0x02U) return false; + if(!seader_ber_next_tlv(&pdu_cursor, &error_index) || error_index.tag != 0x02U) return false; + if(!seader_ber_next_tlv(&pdu_cursor, &varbinds) || varbinds.tag != 0x30U) return false; + + view->context_engine_id = context_engine.value; + view->usm_engine_id = usm_engine_id.value; + view->usm_username = usm_username.value; + view->varbind_sequence = varbinds.value; + view->pdu_tag = pdu.tag; + + if(!seader_ber_parse_uint32(usm_engine_boots.value, &view->usm_engine_boots) || + !seader_ber_parse_uint32(usm_engine_time.value, &view->usm_engine_time) || + !seader_ber_parse_uint32(error_status.value, &view->error_status) || + !seader_ber_parse_uint32(error_index.value, &view->error_index)) { + return false; + } + + return true; +} + +bool seader_snmp_find_varbind_octet_value( + SeaderBytesView varbind_sequence, + SeaderBytesView expected_oid, + SeaderBytesView* value) { + SeaderBerCursor cursor = {0}; + seader_ber_cursor_init(&cursor, varbind_sequence.ptr, varbind_sequence.len); + + while(cursor.offset < cursor.len) { + SeaderBerTlvView varbind = {0}; + SeaderBerTlvView oid = {0}; + SeaderBerTlvView object_value = {0}; + SeaderBerCursor inner = {0}; + + if(!seader_ber_next_tlv(&cursor, &varbind) || varbind.tag != 0x30U) return false; + seader_ber_cursor_init(&inner, varbind.value.ptr, varbind.value.len); + if(!seader_ber_next_tlv(&inner, &oid) || oid.tag != 0x06U) return false; + if(!seader_ber_next_tlv(&inner, &object_value)) return false; + + if(oid.value.len == expected_oid.len && + memcmp(oid.value.ptr, expected_oid.ptr, expected_oid.len) == 0) { + if(object_value.tag != 0x04U) return false; + if(value) *value = object_value.value; + return true; + } + } + + return false; +} diff --git a/snmp_response_view.h b/snmp_response_view.h new file mode 100644 index 0000000..8b4879d --- /dev/null +++ b/snmp_response_view.h @@ -0,0 +1,25 @@ +#pragma once + +#include "snmp_ber_view.h" + +typedef struct { + SeaderBytesView context_engine_id; + SeaderBytesView usm_engine_id; + SeaderBytesView usm_username; + SeaderBytesView varbind_sequence; + uint8_t pdu_tag; + uint32_t usm_engine_boots; + uint32_t usm_engine_time; + uint32_t error_status; + uint32_t error_index; +} SeaderSnmpResponseView; + +bool seader_snmp_parse_response_view( + const uint8_t* response, + size_t response_len, + SeaderSnmpResponseView* view); + +bool seader_snmp_find_varbind_octet_value( + SeaderBytesView varbind_sequence, + SeaderBytesView expected_oid, + SeaderBytesView* value); diff --git a/uhf_snmp_probe.c b/uhf_snmp_probe.c new file mode 100644 index 0000000..f8fc90e --- /dev/null +++ b/uhf_snmp_probe.c @@ -0,0 +1,284 @@ +#include "uhf_snmp_probe.h" + +#include "snmp_codec.h" +#include + +static const uint8_t oid_elite_ice[] = {0x03, 0x01, 0x07, 0x01, 0x38}; +static const uint8_t oid_uhf_tags_config[] = {0x03, 0x01, 0x07, 0x03, 0x0B, 0x00}; +static const uint8_t oid_monza4qt_access_key[] = { + 0x2B, + 0x06, + 0x01, + 0x04, + 0x01, + 0x81, + 0xE4, + 0x38, + 0x01, + 0x01, + 0x02, + 0x01, + 0x1E, + 0x01, + 0x01, + 0x01, + 0x01}; +static const uint8_t oid_higgs3_access_key[] = { + 0x2B, + 0x06, + 0x01, + 0x04, + 0x01, + 0x81, + 0xE4, + 0x38, + 0x01, + 0x01, + 0x02, + 0x01, + 0x22, + 0x01, + 0x01, + 0x01, + 0x01}; + +static void seader_uhf_snmp_probe_advance_after_tag_config(SeaderUhfSnmpProbe* probe) { + if(probe->has_monza4qt) { + probe->stage = SeaderUhfSnmpProbeStageReadMonza4QtKey; + } else if(probe->has_higgs3) { + probe->stage = SeaderUhfSnmpProbeStageReadHiggs3Key; + } else { + probe->stage = SeaderUhfSnmpProbeStageDone; + } +} + +static void seader_uhf_snmp_probe_advance_after_monza(SeaderUhfSnmpProbe* probe) { + if(probe->has_higgs3) { + probe->stage = SeaderUhfSnmpProbeStageReadHiggs3Key; + } else { + probe->stage = SeaderUhfSnmpProbeStageDone; + } +} + +void seader_uhf_snmp_probe_init(SeaderUhfSnmpProbe* probe) { + if(!probe) return; + memset(probe, 0, sizeof(*probe)); + probe->stage = SeaderUhfSnmpProbeStageDiscovery; +} + +bool seader_uhf_snmp_probe_build_next_request( + const SeaderUhfSnmpProbe* probe, + uint8_t* scratch, + size_t scratch_capacity, + uint8_t* message, + size_t message_capacity, + size_t* message_len) { + if(!probe) return false; + + switch(probe->stage) { + case SeaderUhfSnmpProbeStageDiscovery: + return seader_snmp_build_discovery_request( + scratch, scratch_capacity, message, message_capacity, message_len); + case SeaderUhfSnmpProbeStageReadIce: + return seader_snmp_build_get_data_request( + probe->usm_engine_id_storage, + probe->usm_engine_id_len, + probe->usm_username_storage, + probe->usm_username_len, + probe->usm_engine_boots, + probe->usm_engine_time, + oid_elite_ice, + sizeof(oid_elite_ice), + scratch, + scratch_capacity, + message, + message_capacity, + message_len); + case SeaderUhfSnmpProbeStageReadTagConfig: + return seader_snmp_build_get_data_request( + probe->usm_engine_id_storage, + probe->usm_engine_id_len, + probe->usm_username_storage, + probe->usm_username_len, + probe->usm_engine_boots, + probe->usm_engine_time, + oid_uhf_tags_config, + sizeof(oid_uhf_tags_config), + scratch, + scratch_capacity, + message, + message_capacity, + message_len); + case SeaderUhfSnmpProbeStageReadMonza4QtKey: + return seader_snmp_build_get_data_request( + probe->usm_engine_id_storage, + probe->usm_engine_id_len, + probe->usm_username_storage, + probe->usm_username_len, + probe->usm_engine_boots, + probe->usm_engine_time, + oid_monza4qt_access_key, + sizeof(oid_monza4qt_access_key), + scratch, + scratch_capacity, + message, + message_capacity, + message_len); + case SeaderUhfSnmpProbeStageReadHiggs3Key: + return seader_snmp_build_get_data_request( + probe->usm_engine_id_storage, + probe->usm_engine_id_len, + probe->usm_username_storage, + probe->usm_username_len, + probe->usm_engine_boots, + probe->usm_engine_time, + oid_higgs3_access_key, + sizeof(oid_higgs3_access_key), + scratch, + scratch_capacity, + message, + message_capacity, + message_len); + default: + return false; + } +} + +bool seader_uhf_snmp_probe_consume_response( + SeaderUhfSnmpProbe* probe, + const uint8_t* response, + size_t response_len) { + SeaderSnmpResponseView view = {0}; + SeaderBytesView value = {0}; + + if(!probe || !seader_snmp_parse_response_view(response, response_len, &view)) { + if(probe) probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } + if(view.error_status != 0U) { + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } + + switch(probe->stage) { + case SeaderUhfSnmpProbeStageDiscovery: + probe->engine_id = view.context_engine_id; + probe->usm_engine_id_len = view.usm_engine_id.len; + if(probe->usm_engine_id_len > sizeof(probe->usm_engine_id_storage)) { + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } + memcpy(probe->usm_engine_id_storage, view.usm_engine_id.ptr, probe->usm_engine_id_len); + probe->usm_engine_id = + (SeaderBytesView){probe->usm_engine_id_storage, probe->usm_engine_id_len}; + + probe->usm_username_len = view.usm_username.len; + if(probe->usm_username_len > sizeof(probe->usm_username_storage)) { + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } + memcpy(probe->usm_username_storage, view.usm_username.ptr, probe->usm_username_len); + probe->usm_username = + (SeaderBytesView){probe->usm_username_storage, probe->usm_username_len}; + probe->usm_engine_boots = view.usm_engine_boots; + probe->usm_engine_time = view.usm_engine_time; + probe->stage = SeaderUhfSnmpProbeStageReadIce; + return true; + case SeaderUhfSnmpProbeStageReadIce: + if(!seader_snmp_find_varbind_octet_value( + view.varbind_sequence, + (SeaderBytesView){oid_elite_ice, sizeof(oid_elite_ice)}, + &value)) { + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } + probe->ice_value_len = value.len; + if(probe->ice_value_len > sizeof(probe->ice_value_storage)) { + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } + memcpy(probe->ice_value_storage, value.ptr, probe->ice_value_len); + probe->ice_value = value; + probe->stage = SeaderUhfSnmpProbeStageReadTagConfig; + return true; + case SeaderUhfSnmpProbeStageReadTagConfig: + if(!seader_snmp_find_varbind_octet_value( + view.varbind_sequence, + (SeaderBytesView){oid_uhf_tags_config, sizeof(oid_uhf_tags_config)}, + &value)) { + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } + probe->tag_config_value = value; + { + SeaderUhfTagConfigView tag_config = {0}; + if(!seader_uhf_tag_config_parse(value, &tag_config)) { + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } + probe->has_monza4qt = tag_config.has_monza4qt; + probe->has_higgs3 = tag_config.has_higgs3; + } + seader_uhf_snmp_probe_advance_after_tag_config(probe); + return true; + case SeaderUhfSnmpProbeStageReadMonza4QtKey: + probe->monza4qt_key_present = + seader_snmp_find_varbind_octet_value( + view.varbind_sequence, + (SeaderBytesView){oid_monza4qt_access_key, sizeof(oid_monza4qt_access_key)}, + &value) && + value.len > 0U; + seader_uhf_snmp_probe_advance_after_monza(probe); + return true; + case SeaderUhfSnmpProbeStageReadHiggs3Key: + probe->higgs3_key_present = + seader_snmp_find_varbind_octet_value( + view.varbind_sequence, + (SeaderBytesView){oid_higgs3_access_key, sizeof(oid_higgs3_access_key)}, + &value) && + value.len > 0U; + probe->stage = SeaderUhfSnmpProbeStageDone; + return true; + default: + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; + } +} + +bool seader_uhf_snmp_probe_consume_error( + SeaderUhfSnmpProbe* probe, + uint32_t error_code, + const uint8_t* data, + size_t data_len) { + if(!probe) { + return false; + } + + if(error_code == 0x06U && data_len >= 2U && data[0] == 0x69U && data[1] == 0x82U) { + if(probe->stage == SeaderUhfSnmpProbeStageReadMonza4QtKey) { + probe->monza4qt_key_present = true; + seader_uhf_snmp_probe_advance_after_monza(probe); + return true; + } else if(probe->stage == SeaderUhfSnmpProbeStageReadHiggs3Key) { + probe->higgs3_key_present = true; + probe->stage = SeaderUhfSnmpProbeStageDone; + return true; + } + } + + if(error_code == 0x11U && data_len >= 2U && + ((data[0] == 0x2EU && data[1] == 0x00U) || (data[0] == 0x39U && data[1] == 0x00U))) { + if(probe->stage == SeaderUhfSnmpProbeStageReadMonza4QtKey) { + probe->monza4qt_key_present = false; + seader_uhf_snmp_probe_advance_after_monza(probe); + return true; + } else if(probe->stage == SeaderUhfSnmpProbeStageReadHiggs3Key) { + probe->higgs3_key_present = false; + probe->stage = SeaderUhfSnmpProbeStageDone; + return true; + } + } + + probe->stage = SeaderUhfSnmpProbeStageFailed; + return false; +} diff --git a/uhf_snmp_probe.h b/uhf_snmp_probe.h new file mode 100644 index 0000000..7aaa69c --- /dev/null +++ b/uhf_snmp_probe.h @@ -0,0 +1,61 @@ +#pragma once + +#include "snmp_ber_view.h" +#include "snmp_response_view.h" +#include "uhf_tag_config_view.h" + +#define SEADER_UHF_SNMP_MAX_ID_LEN 32U +#define SEADER_UHF_SNMP_MAX_VALUE_LEN 32U + +typedef enum { + SeaderUhfSnmpProbeStageIdle = 0, + SeaderUhfSnmpProbeStageDiscovery, + SeaderUhfSnmpProbeStageReadIce, + SeaderUhfSnmpProbeStageReadTagConfig, + SeaderUhfSnmpProbeStageReadMonza4QtKey, + SeaderUhfSnmpProbeStageReadHiggs3Key, + SeaderUhfSnmpProbeStageDone, + SeaderUhfSnmpProbeStageFailed, +} SeaderUhfSnmpProbeStage; + +typedef struct { + SeaderUhfSnmpProbeStage stage; + SeaderBytesView engine_id; + SeaderBytesView usm_engine_id; + SeaderBytesView usm_username; + SeaderBytesView ice_value; + SeaderBytesView tag_config_value; + uint32_t usm_engine_boots; + uint32_t usm_engine_time; + bool has_monza4qt; + bool has_higgs3; + bool monza4qt_key_present; + bool higgs3_key_present; + uint8_t usm_engine_id_storage[SEADER_UHF_SNMP_MAX_ID_LEN]; + size_t usm_engine_id_len; + uint8_t usm_username_storage[SEADER_UHF_SNMP_MAX_ID_LEN]; + size_t usm_username_len; + uint8_t ice_value_storage[SEADER_UHF_SNMP_MAX_VALUE_LEN]; + size_t ice_value_len; +} SeaderUhfSnmpProbe; + +void seader_uhf_snmp_probe_init(SeaderUhfSnmpProbe* probe); + +bool seader_uhf_snmp_probe_build_next_request( + const SeaderUhfSnmpProbe* probe, + uint8_t* scratch, + size_t scratch_capacity, + uint8_t* message, + size_t message_capacity, + size_t* message_len); + +bool seader_uhf_snmp_probe_consume_response( + SeaderUhfSnmpProbe* probe, + const uint8_t* response, + size_t response_len); + +bool seader_uhf_snmp_probe_consume_error( + SeaderUhfSnmpProbe* probe, + uint32_t error_code, + const uint8_t* data, + size_t data_len); diff --git a/uhf_tag_config_view.c b/uhf_tag_config_view.c new file mode 100644 index 0000000..a8e7f6d --- /dev/null +++ b/uhf_tag_config_view.c @@ -0,0 +1,130 @@ +#include "uhf_tag_config_view.h" + +#include + +static const uint8_t family_monza4qt[] = {0xE2, 0x80, 0x11, 0x05}; +static const uint8_t family_higgs3[] = {0xE2, 0x00, 0x34, 0x12}; +static const uint8_t oid_monza4qt_prefix[] = + {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x1EU}; +static const uint8_t oid_higgs3_prefix[] = + {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x22U}; + +static bool seader_uhf_tag_config_match_oid( + const uint8_t* ptr, + size_t len, + const uint8_t* oid, + size_t oid_len) { + return len >= oid_len && memcmp(ptr, oid, oid_len) == 0; +} + +static size_t seader_uhf_tag_config_next_entry_offset(SeaderBytesView payload, size_t ordinal) { + size_t offset = 0U; + size_t seen = 0U; + + while(offset < payload.len) { + uint8_t tag = payload.ptr[offset++]; + size_t value_len = 0U; + + if(tag == 0x04U) { + value_len = 4U; + } else if(tag == 0x11U) { + value_len = 17U; + if(seen == ordinal) { + return offset - 1U; + } + seen++; + } else { + return payload.len; + } + + if(offset + value_len > payload.len) { + return payload.len; + } + offset += value_len; + } + + return payload.len; +} + +bool seader_uhf_tag_config_parse(SeaderBytesView payload, SeaderUhfTagConfigView* view) { + size_t offset = 0U; + + if(!payload.ptr || payload.len == 0U || !view) return false; + memset(view, 0, sizeof(*view)); + view->raw = payload; + view->prefix.ptr = payload.ptr; + + while(offset < payload.len) { + uint8_t tag = payload.ptr[offset++]; + if(tag == 0x04U) { + if(offset + sizeof(family_monza4qt) > payload.len) { + return false; + } + if(memcmp(payload.ptr + offset, family_monza4qt, sizeof(family_monza4qt)) == 0) { + view->has_monza4qt = true; + view->monza4qt_family_id = + (SeaderBytesView){payload.ptr + offset, sizeof(family_monza4qt)}; + } else if(memcmp(payload.ptr + offset, family_higgs3, sizeof(family_higgs3)) == 0) { + view->has_higgs3 = true; + view->higgs3_family_id = + (SeaderBytesView){payload.ptr + offset, sizeof(family_higgs3)}; + } + offset += sizeof(family_monza4qt); + } else if(tag == 0x11U) { + if(view->entry_count == 0U) { + view->prefix.len = (offset - 1U) - 0U; + } + if(offset + 17U > payload.len) { + return false; + } + view->entry_count++; + offset += 17U; + } else { + return false; + } + } + + if(view->entry_count == 0U) { + view->prefix.len = payload.len; + } + + return true; +} + +bool seader_uhf_tag_config_get_entry( + const SeaderUhfTagConfigView* view, + size_t index, + SeaderUhfTagConfigEntryView* entry) { + if(!view || !entry || index >= view->entry_count) return false; + memset(entry, 0, sizeof(*entry)); + + const size_t start = seader_uhf_tag_config_next_entry_offset(view->raw, index); + const size_t next = seader_uhf_tag_config_next_entry_offset(view->raw, index + 1U); + if(start >= view->raw.len) return false; + + entry->raw.ptr = view->raw.ptr + start; + entry->raw.len = 18U; + + entry->oid.ptr = view->raw.ptr + start + 1U; + entry->oid.len = 17U; + + if(next > start + entry->raw.len) { + entry->value.ptr = view->raw.ptr + start + entry->raw.len; + entry->value.len = next - (start + entry->raw.len); + } + + if(seader_uhf_tag_config_match_oid( + entry->oid.ptr, entry->oid.len, oid_higgs3_prefix, sizeof(oid_higgs3_prefix))) { + entry->kind = SeaderUhfTagConfigEntryHiggs3Access; + } else if(seader_uhf_tag_config_match_oid( + entry->oid.ptr, + entry->oid.len, + oid_monza4qt_prefix, + sizeof(oid_monza4qt_prefix))) { + entry->kind = SeaderUhfTagConfigEntryMonza4QtAccess; + } else { + entry->kind = SeaderUhfTagConfigEntryUnknown; + } + + return true; +} diff --git a/uhf_tag_config_view.h b/uhf_tag_config_view.h new file mode 100644 index 0000000..7dab1b6 --- /dev/null +++ b/uhf_tag_config_view.h @@ -0,0 +1,33 @@ +#pragma once + +#include "snmp_ber_view.h" + +typedef enum { + SeaderUhfTagConfigEntryUnknown = 0, + SeaderUhfTagConfigEntryHiggs3Access, + SeaderUhfTagConfigEntryMonza4QtAccess, +} SeaderUhfTagConfigEntryKind; + +typedef struct { + SeaderBytesView raw; + SeaderBytesView oid; + SeaderBytesView value; + SeaderUhfTagConfigEntryKind kind; +} SeaderUhfTagConfigEntryView; + +typedef struct { + SeaderBytesView raw; + SeaderBytesView prefix; + size_t entry_count; + bool has_higgs3; + bool has_monza4qt; + SeaderBytesView higgs3_family_id; + SeaderBytesView monza4qt_family_id; +} SeaderUhfTagConfigView; + +bool seader_uhf_tag_config_parse(SeaderBytesView payload, SeaderUhfTagConfigView* view); + +bool seader_uhf_tag_config_get_entry( + const SeaderUhfTagConfigView* view, + size_t index, + SeaderUhfTagConfigEntryView* entry);