identity.cpp
1 #include <identity.h> 2 3 #include "../boot/provisioning.h" 4 #include <networking/wifi.h> 5 6 #include <Arduino.h> 7 #include <Preferences.h> 8 #include <string.h> 9 10 namespace { 11 12 static char hostname_data[config::shell::HOSTNAME_SIZE + 1] = {}; 13 static bool hostname_initialized = false; 14 15 static bool get_provisioning_string(const char *key, 16 IdentityStringQuery *query) { 17 if (!query || !query->buffer || query->capacity == 0) 18 return false; 19 Preferences prefs; 20 if (!prefs.begin(config::provisioning::NVS_NAMESPACE, true)) 21 return false; 22 bool ok = prefs.getString(key, query->buffer, query->capacity) > 0; 23 prefs.end(); 24 query->ok = ok; 25 return ok; 26 } 27 28 static bool set_provisioning_string(const char *key, const char *value) { 29 Preferences prefs; 30 if (!prefs.begin(config::provisioning::NVS_NAMESPACE, false)) 31 return false; 32 prefs.putString(key, value ? value : ""); 33 prefs.end(); 34 return true; 35 } 36 37 static void ensure_hostname(void) { 38 if (hostname_initialized) 39 return; 40 strncpy(hostname_data, config::HOSTNAME, config::shell::HOSTNAME_SIZE); 41 hostname_data[config::shell::HOSTNAME_SIZE] = '\0'; 42 hostname_initialized = true; 43 } 44 45 } // namespace 46 47 void services::identity::initialize() { ensure_hostname(); } 48 49 const char *services::identity::access_hostname() { 50 ensure_hostname(); 51 return hostname_data; 52 } 53 54 bool services::identity::configure_hostname(const char *hostname) { 55 ensure_hostname(); 56 if (!hostname) 57 return false; 58 59 strncpy(hostname_data, hostname, config::shell::HOSTNAME_SIZE); 60 hostname_data[config::shell::HOSTNAME_SIZE] = '\0'; 61 networking::wifi::configure_hostname(hostname_data); 62 return true; 63 } 64 65 bool services::identity::access_username(IdentityStringQuery *query) { 66 return get_provisioning_string("username", query); 67 } 68 69 bool services::identity::configure_username(const char *value) { 70 return set_provisioning_string("username", value); 71 } 72 73 bool services::identity::access_device_name(IdentityStringQuery *query) { 74 return get_provisioning_string("device_name", query); 75 } 76 77 bool services::identity::configureDeviceName(const char *value) { 78 return set_provisioning_string("device_name", value); 79 } 80 81 bool services::identity::accessAPIKey(IdentityStringQuery *query) { 82 return get_provisioning_string("api_key", query); 83 } 84 85 bool services::identity::configureAPIKey(const char *value) { 86 return set_provisioning_string("api_key", value); 87 } 88 89 bool services::identity::accessSnapshot(DeviceIdentitySnapshot *snapshot) { 90 if (!snapshot) 91 return false; 92 memset(snapshot, 0, sizeof(*snapshot)); 93 94 strncpy(snapshot->hostname, services::identity::access_hostname(), 95 sizeof(snapshot->hostname) - 1); 96 IdentityStringQuery username_query = { 97 .buffer = snapshot->username, 98 .capacity = sizeof(snapshot->username), 99 .ok = false, 100 }; 101 services::identity::access_username(&username_query); 102 IdentityStringQuery device_name_query = { 103 .buffer = snapshot->device_name, 104 .capacity = sizeof(snapshot->device_name), 105 .ok = false, 106 }; 107 if (!services::identity::access_device_name(&device_name_query)) { 108 strncpy(snapshot->device_name, config::HOSTNAME, 109 sizeof(snapshot->device_name) - 1); 110 } 111 IdentityStringQuery api_key_query = { 112 .buffer = snapshot->api_key, 113 .capacity = sizeof(snapshot->api_key), 114 .ok = false, 115 }; 116 services::identity::accessAPIKey(&api_key_query); 117 snapshot->provisioned = boot::provisioning::isProvisioned(); 118 return true; 119 } 120 121 #ifdef PIO_UNIT_TESTING 122 123 #include <testing/utils.h> 124 125 static void test_identity_hostname_default(void) { 126 GIVEN("a freshly booted device"); 127 THEN("hostname matches config::HOSTNAME"); 128 TEST_ASSERT_EQUAL_STRING_MESSAGE( 129 config::HOSTNAME, services::identity::access_hostname(), 130 "device: boot-time hostname should match config::HOSTNAME"); 131 } 132 133 static void test_identity_hostname_truncation(void) { 134 WHEN("a hostname longer than HOSTNAME_SIZE is set"); 135 136 char long_name[config::shell::HOSTNAME_SIZE + 20]; 137 memset(long_name, 'A', sizeof(long_name) - 1); 138 long_name[sizeof(long_name) - 1] = '\0'; 139 140 services::identity::configure_hostname(long_name); 141 size_t result_len = strlen(services::identity::access_hostname()); 142 TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE( 143 config::shell::HOSTNAME_SIZE, result_len, 144 "device: hostname exceeds config::shell::HOSTNAME_SIZE after truncation"); 145 146 services::identity::configure_hostname(config::HOSTNAME); 147 } 148 149 static void test_identity_username_roundtrip(void) { 150 WHEN("a username is stored and read back"); 151 TEST_ASSERT_TRUE_MESSAGE(services::identity::configure_username("alice"), 152 "device: username should be writable"); 153 154 char username[64] = {0}; 155 IdentityStringQuery query = { 156 .buffer = username, 157 .capacity = sizeof(username), 158 .ok = false, 159 }; 160 TEST_ASSERT_TRUE_MESSAGE(services::identity::access_username(&query), 161 "device: username should be readable"); 162 TEST_ASSERT_EQUAL_STRING_MESSAGE("alice", username, 163 "device: username mismatch after roundtrip"); 164 } 165 166 static void test_identity_device_name_roundtrip(void) { 167 WHEN("a device name is stored and read back"); 168 TEST_ASSERT_TRUE_MESSAGE( 169 services::identity::configureDeviceName("ceratina-lab"), 170 "device: device_name should be writable"); 171 172 char device_name[64] = {0}; 173 IdentityStringQuery query = { 174 .buffer = device_name, 175 .capacity = sizeof(device_name), 176 .ok = false, 177 }; 178 TEST_ASSERT_TRUE_MESSAGE(services::identity::access_device_name(&query), 179 "device: device_name should be readable"); 180 TEST_ASSERT_EQUAL_STRING_MESSAGE( 181 "ceratina-lab", device_name, 182 "device: device_name mismatch after roundtrip"); 183 } 184 185 static void test_identity_api_key_roundtrip(void) { 186 WHEN("an API key is stored and read back"); 187 TEST_ASSERT_TRUE_MESSAGE(services::identity::configureAPIKey("secret-key"), 188 "device: api_key should be writable"); 189 190 char api_key[64] = {0}; 191 IdentityStringQuery query = { 192 .buffer = api_key, 193 .capacity = sizeof(api_key), 194 .ok = false, 195 }; 196 TEST_ASSERT_TRUE_MESSAGE(services::identity::accessAPIKey(&query), 197 "device: api_key should be readable"); 198 TEST_ASSERT_EQUAL_STRING_MESSAGE("secret-key", api_key, 199 "device: api_key mismatch after roundtrip"); 200 } 201 202 void services::identity::test() { 203 MODULE("Identity"); 204 RUN_TEST(test_identity_hostname_default); 205 RUN_TEST(test_identity_hostname_truncation); 206 RUN_TEST(test_identity_username_roundtrip); 207 RUN_TEST(test_identity_device_name_roundtrip); 208 RUN_TEST(test_identity_api_key_roundtrip); 209 } 210 211 #endif