13static const char*
TAG =
"VCARD";
47static uint32_t
fnv1a_hash(
const char* data,
size_t len) {
58 snprintf(out, out_len,
"c%02u",
static_cast<unsigned>(slot));
66 size_t len = strlen(line);
67 while (len > 0 && (line[len - 1] ==
'\r' || line[len - 1] ==
'\n')) {
80 const char* colon =
static_cast<const char*
>(memchr(line,
':', len));
81 if (!colon)
return true;
83 const char* val = colon + 1;
84 size_t val_len = len -
static_cast<size_t>(val - line);
86 while (val_len > 0 && (val[val_len - 1] ==
'\r' || val[val_len - 1] ==
'\n' ||
87 val[val_len - 1] ==
' ' || val[val_len - 1] ==
'\t')) {
91 if (val_len == 0)
return false;
93 if (len > 2 && (line[0] ==
'N' && (line[1] ==
':' || line[1] ==
';'))) {
94 bool has_real_content =
false;
95 for (
size_t i = 0; i < val_len; i++) {
96 if (val[i] !=
';' && val[i] !=
' ' && val[i] !=
'\t') {
97 has_real_content =
true;
101 if (!has_real_content)
return false;
104 if (len > 5 && strncasecmp(line,
"IMPP:", 5) == 0) {
105 const char* second_colon = strchr(val,
':');
107 const char* actual_val = second_colon + 1;
108 size_t actual_len = val_len -
static_cast<size_t>(actual_val - val);
109 while (actual_len > 0 && (actual_val[actual_len - 1] ==
'\r' ||
110 actual_val[actual_len - 1] ==
'\n' || actual_val[actual_len - 1] ==
' ')) {
113 if (actual_len == 0)
return false;
130 const char* p = vcard;
131 const char* end = vcard + len;
134 const char* line_start = p;
135 const char* line_end = p;
136 while (line_end < end && *line_end !=
'\n') {
139 size_t line_len =
static_cast<size_t>(line_end - line_start);
142 if (line_len >= 6 && strncasecmp(line_start,
"BEGIN:", 6) == 0) keep =
true;
143 else if (line_len >= 8 && strncasecmp(line_start,
"VERSION:", 8) == 0) keep =
true;
144 else if (line_len >= 4 && strncasecmp(line_start,
"END:", 4) == 0) keep =
true;
147 if (keep && out_pos + line_len + 1 <
sizeof(result)) {
148 memcpy(result + out_pos, line_start, line_len);
150 result[out_pos++] =
'\n';
154 if (p < end && *p ==
'\n') p++;
157 result[out_pos] =
'\0';
158 memcpy(vcard, result, out_pos + 1);
171 if (!vcard || !prefix || !out || out_len == 0)
return false;
172 size_t prefix_len = strlen(prefix);
173 const char* p = vcard;
175 const char* line_start = p;
176 const char* line_end = strpbrk(p,
"\r\n");
177 size_t line_len = line_end ?
static_cast<size_t>(line_end - line_start) : strlen(line_start);
179 if (line_len >= prefix_len && strncmp(line_start, prefix, prefix_len) == 0) {
180 size_t copy_len = line_len - prefix_len;
181 if (copy_len >= out_len) copy_len = out_len - 1;
182 memcpy(out, line_start + prefix_len, copy_len);
183 out[copy_len] =
'\0';
188 if (!line_end)
break;
204 char* display,
size_t display_len) {
205 if (!last || last_len == 0 || !display || display_len == 0)
return;
212 char n_line[128] = {0};
214 const char* n_tag = strstr(vcard,
"\nN;");
216 const char* val = strchr(n_tag,
':');
220 while (val[copy_len] && val[copy_len] !=
'\r' && val[copy_len] !=
'\n') {
223 if (copy_len >=
sizeof(n_line)) copy_len =
sizeof(n_line) - 1;
224 memcpy(n_line, val, copy_len);
225 n_line[copy_len] =
'\0';
230 if (n_line[0] !=
'\0') {
231 char* family = n_line;
232 char* given = strchr(n_line,
';');
236 char* next_semi = strchr(given,
';');
237 if (next_semi) *next_semi =
'\0';
239 if (family && *family) {
240 strncpy(last, family, last_len - 1);
241 last[last_len - 1] =
'\0';
243 if (display[0] ==
'\0') {
244 if (given && *given && family && *family) {
245 snprintf(display, display_len,
"%s %s", given, family);
246 }
else if (given && *given) {
247 snprintf(display, display_len,
"%s", given);
248 }
else if (family && *family) {
249 snprintf(display, display_len,
"%s", family);
254 if (display[0] ==
'\0' && fn[0] !=
'\0') {
255 strncpy(display, fn, display_len - 1);
256 display[display_len - 1] =
'\0';
259 if (display[0] ==
'\0') {
260 strncpy(display,
"vCard", display_len - 1);
261 display[display_len - 1] =
'\0';
271static void set_err(
char* err,
size_t err_len,
const char* msg) {
272 if (!err || err_len == 0)
return;
273 strncpy(err, msg ? msg :
"", err_len - 1);
274 err[err_len - 1] =
'\0';
285static bool vcard_validate(
const char* vcard,
size_t len,
char* err,
size_t err_len) {
286 if (!vcard || len == 0) {
287 set_err(err, err_len,
"Empty vCard");
291 set_err(err, err_len,
"vCard too large");
294 if (memchr(vcard,
'\0', len) !=
nullptr) {
295 set_err(err, err_len,
"vCard contains NUL");
298 if (!strstr(vcard,
"BEGIN:VCARD")) {
299 set_err(err, err_len,
"Missing BEGIN:VCARD");
302 if (!strstr(vcard,
"VERSION:4.0")) {
303 set_err(err, err_len,
"Missing VERSION:4.0");
306 if (!strstr(vcard,
"END:VCARD")) {
307 set_err(err, err_len,
"Missing END:VCARD");
350 set_err(err, err_len,
"vCard too large");
353 memcpy(tmp, vcard, len);
360 set_err(err, err_len,
"NVS open failed");
365 ret = nvs_commit(nvs);
369 set_err(err, err_len,
"NVS write failed");
377 LOG_I(
TAG,
"Own vCard stored (%d bytes)", (
int)len);
388 if (!out || max_len == 0)
return 0;
392 if (len >= max_len) len = max_len - 1;
414 if (!out || max_len == 0)
return false;
420 strncpy(out, display, max_len - 1);
421 out[max_len - 1] =
'\0';
436 ret = nvs_commit(nvs);
442 return ret == ESP_OK;
464 if (nvs_get_str(nvs, key,
nullptr, &len) != ESP_OK || len == 0 || len >
VCARD_MAX_LEN) {
468 if (nvs_get_str(nvs, key, tmp, &len) == ESP_OK) {
503 if (nvs_get_str(nvs, key,
nullptr, &vlen) != ESP_OK || vlen == 0 || vlen >
VCARD_MAX_LEN) {
507 if (nvs_get_str(nvs, key, tmp, &vlen) == ESP_OK) {
509 if (strlen(tmp) == len && memcmp(tmp, vcard, len) == 0) {
519 if (!vcard || len == 0)
return false;
545 set_err(err, err_len,
"vCard list full");
551 set_err(err, err_len,
"NVS open failed");
558 set_err(err, err_len,
"Duplicate vCard");
565 free_slot =
static_cast<int>(i);
572 set_err(err, err_len,
"vCard list full");
579 memcpy(tmp, vcard, len);
583 esp_err_t ret = nvs_set_str(nvs, key, tmp);
585 ret = nvs_commit(nvs);
589 set_err(err, err_len,
"NVS write failed");
595 g_cards[free_slot].used =
true;
596 g_cards[free_slot].hash = hash;
616 set_err(err, err_len,
"No such vCard");
622 set_err(err, err_len,
"NVS open failed");
629 memcpy(tmp, vcard, len);
633 esp_err_t ret = nvs_set_str(nvs, key, tmp);
635 ret = nvs_commit(nvs);
639 set_err(err, err_len,
"NVS write failed");
665 esp_err_t ret = nvs_erase_key(nvs, key);
667 ret = nvs_commit(nvs);
670 if (ret != ESP_OK)
return false;
684 if (!out || max_len == 0)
return 0;
696 size_t len = max_len;
697 if (nvs_get_str(nvs, key, out, &len) != ESP_OK) {
702 if (len > 0 && len <= max_len) {
705 out[max_len - 1] =
'\0';
707 return strnlen(out, max_len);
718 if (!out || max_len == 0)
return false;
723 strncpy(out,
g_cards[slot].display, max_len - 1);
724 out[max_len - 1] =
'\0';
731static void copy_field(
char* dst,
size_t dst_size,
const char* src,
size_t src_len) {
732 if (!dst || dst_size == 0)
return;
733 if (src_len >= dst_size) src_len = dst_size - 1;
734 if (src && src_len > 0) memcpy(dst, src, src_len);
742 while (len > 0 && (line[len - 1] ==
'\r' || line[len - 1] ==
'\n' ||
743 line[len - 1] ==
' ' || line[len - 1] ==
'\t')) {
757static const char*
line_value(
const char* line,
size_t line_len,
size_t* out_len) {
758 const char* colon =
static_cast<const char*
>(memchr(line,
':', line_len));
759 if (!colon)
return nullptr;
760 const char* val = colon + 1;
761 size_t val_len = line_len -
static_cast<size_t>(val - line);
763 if (out_len) *out_len = val_len;
778 const char* type_value) {
779 size_t prop_len = strlen(prop);
780 if (line_len < prop_len)
return false;
781 if (strncasecmp(line, prop, prop_len) != 0)
return false;
782 if (line_len == prop_len)
return false;
784 char next = line[prop_len];
786 if (next !=
';')
return false;
787 const char* params_end =
static_cast<const char*
>(memchr(line,
':', line_len));
788 if (!params_end)
return false;
789 size_t params_len =
static_cast<size_t>(params_end - (line + prop_len + 1));
790 const char* params = line + prop_len + 1;
791 const char* type_token =
"TYPE=";
792 size_t token_len = strlen(type_token);
793 if (params_len < token_len)
return false;
794 size_t value_len = strlen(type_value);
795 for (
size_t i = 0; i + token_len <= params_len; i++) {
796 if (strncasecmp(params + i, type_token, token_len) == 0) {
797 const char* v = params + i + token_len;
798 size_t v_len = params_len - i - token_len;
799 if (v_len > value_len &&
800 (v[value_len] ==
';' || v[value_len] ==
',') &&
801 strncasecmp(v, type_value, value_len) == 0) {
804 if (v_len == value_len &&
805 strncasecmp(v, type_value, value_len) == 0) {
812 return next ==
':' || next ==
';';
820 if (line_len == 0)
return;
823 const char* val =
nullptr;
835 size_t copy = val_len <
sizeof(tmp) - 1 ? val_len :
sizeof(tmp) - 1;
836 memcpy(tmp, val, copy);
840 char* given =
nullptr;
841 char* sep = strchr(tmp,
';');
845 char* sep2 = strchr(given,
';');
846 if (sep2) *sep2 =
'\0';
849 family, family ? strlen(family) : 0);
852 given, strlen(given));
905 if (line_len >= 5 && strncasecmp(line,
"IMPP:", 5) == 0) {
906 const char* rest = line + 5;
907 size_t rest_len = line_len - 5;
908 const char* scheme_end =
static_cast<const char*
>(memchr(rest,
':', rest_len));
910 size_t scheme_len =
static_cast<size_t>(scheme_end - rest);
911 const char* impp_val = scheme_end + 1;
912 size_t impp_val_len = rest_len - scheme_len - 1;
915 if (scheme_len == 8 && strncasecmp(rest,
"telegram", 8) == 0) {
917 impp_val, impp_val_len);
918 }
else if (scheme_len == 6 && strncasecmp(rest,
"signal", 6) == 0) {
920 impp_val, impp_val_len);
921 }
else if (scheme_len == 6 && strncasecmp(rest,
"matrix", 6) == 0) {
923 impp_val, impp_val_len);
924 }
else if (scheme_len == 7 && strncasecmp(rest,
"threema", 7) == 0) {
926 impp_val, impp_val_len);
936 if (!out)
return false;
937 memset(out, 0,
sizeof(*out));
938 if (!raw)
return false;
940 bool saw_begin =
false;
943 const char* line_start = p;
944 while (*p && *p !=
'\n') p++;
945 size_t line_len =
static_cast<size_t>(p - line_start);
947 if (line_len >= 11 && strncasecmp(line_start,
"BEGIN:VCARD", 11) == 0) {
949 }
else if (line_len >= 9 && strncasecmp(line_start,
"END:VCARD", 9) == 0) {
951 }
else if (saw_begin) {
964 const char* prefix,
const char* value) {
965 if (!value || value[0] ==
'\0')
return true;
966 size_t prefix_len = strlen(prefix);
967 size_t value_len = strlen(value);
968 size_t need = prefix_len + value_len + 1;
969 if (*pos + need >= buf_size)
return false;
970 memcpy(buf + *pos, prefix, prefix_len);
972 memcpy(buf + *pos, value, value_len);
974 buf[(*pos)++] =
'\n';
982 if (!data || !out_buf || buf_len < 32)
return 0;
985 static const char* k_begin =
"BEGIN:VCARD\n";
986 static const char* k_version =
"VERSION:4.0\n";
987 static const char* k_end =
"END:VCARD\n";
989 size_t begin_len = strlen(k_begin);
990 size_t version_len = strlen(k_version);
991 if (pos + begin_len >= buf_len)
return 0;
992 memcpy(out_buf + pos, k_begin, begin_len); pos += begin_len;
993 if (pos + version_len >= buf_len)
return 0;
994 memcpy(out_buf + pos, k_version, version_len); pos += version_len;
999 snprintf(n_line,
sizeof(n_line),
"N:%s;%s;;;",
1001 if (!
append_field(out_buf, buf_len, &pos,
"", n_line))
return 0;
1008 char fn_fallback[128];
1010 snprintf(fn_fallback,
sizeof(fn_fallback),
"%s %s",
1013 snprintf(fn_fallback,
sizeof(fn_fallback),
"%s", data->
given_name);
1015 snprintf(fn_fallback,
sizeof(fn_fallback),
"%s", data->
family_name);
1017 if (!
append_field(out_buf, buf_len, &pos,
"FN:", fn_fallback))
return 0;
1026 if (!
append_field(out_buf, buf_len, &pos,
"URL:", data->
url))
return 0;
1034 size_t end_len = strlen(k_end);
1035 if (pos + end_len >= buf_len)
return 0;
1036 memcpy(out_buf + pos, k_end, end_len); pos += end_len;
1038 out_buf[pos] =
'\0';
1049 if (!out_slots || max_slots == 0)
return 0;
1055 out_slots[count++] = i;
1059 for (uint16_t i = 0; i < count; i++) {
1060 for (uint16_t j = i + 1; j < count; j++) {
1061 uint16_t a = out_slots[i];
1062 uint16_t b = out_slots[j];
1063 if (strcasecmp(
g_cards[a].last_name,
g_cards[b].last_name) > 0) {
1064 uint16_t tmp = out_slots[i];
1065 out_slots[i] = out_slots[j];
Centralized non-cryptographic hash utilities.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_I(tag, fmt,...)
uint32_t fnv1a_32(const uint8_t *data, size_t len)
Computes FNV-1a 32-bit hash over a byte buffer.
Structured representation of an own vCard for editor/wizard use.
size_t vcard_store_get_own(char *out, size_t max_len)
Retrieves local own-vCard text.
static void vcard_trim_cr(char *line)
Trims trailing CR/LF characters from one line.
bool vcard_store_has_own(void)
Returns whether local own-vCard exists.
static void vcard_parse_names(const char *vcard, char *last, size_t last_len, char *display, size_t display_len)
Derives sortable last-name and display-name fields from vCard.
static const char * line_value(const char *line, size_t line_len, size_t *out_len)
Returns pointer to the value portion of a vCard line and its length. For a line like TEL;TYPE=HOME:12...
static void vcard_load_own(void)
Lazily loads local own-vCard from NVS cache.
bool vcard_store_set_own(const char *vcard, size_t len, char *err, size_t err_len)
Stores local own-vCard after validation and field filtering.
bool vcard_store_update(uint16_t slot, const char *vcard, size_t len, char *err, size_t err_len)
Overwrites the vCard stored at slot in place after validation.
static vcard_meta_t g_cards[100]
static bool vcard_extract_line(const char *vcard, const char *prefix, char *out, size_t out_len)
Extracts value from first vCard line matching prefix.
size_t vcard_store_get(uint16_t slot, char *out, size_t max_len)
Retrieves raw vCard text from slot.
static constexpr const char * VCARD_NAMESPACE
static bool g_own_present
bool vcard_store_clear_own(void)
Deletes local own-vCard from storage.
static bool vcard_validate(const char *vcard, size_t len, char *err, size_t err_len)
Validates basic vCard format constraints.
static bool vcard_line_has_content(const char *line, size_t len)
Checks whether a vCard line contains meaningful field content.
static bool match_property(const char *line, size_t line_len, const char *prop, const char *type_value)
Tests whether one vCard property prefix matches a line, optionally accepting a TYPE=....
bool vcard_parse_to_struct(const char *raw, vcard_data_t *out)
Parses raw vCard 4.0 text into a structured representation.
static void parse_line_into_struct(const char *line, size_t line_len, vcard_data_t *out)
Parses a single vCard line into the structured data, when recognized.
static void vcard_key_for_slot(char *out, size_t out_len, uint16_t slot)
Formats NVS key name for a card slot.
bool vcard_store_add(const char *vcard, size_t len, char *err, size_t err_len)
Adds peer vCard to first free slot after validation and duplicate check.
uint16_t vcard_store_get_sorted(uint16_t *out_slots, uint16_t max_slots)
Returns slot indices of stored cards sorted by last name.
size_t vcard_filter_empty_fields(char *vcard, size_t len)
Removes empty optional fields from vCard text in-place.
static constexpr const char * VCARD_KEY_OWN
uint16_t vcard_store_count(void)
Returns number of stored peer vCards.
bool vcard_store_contains(const char *vcard, size_t len)
Reports whether an exact-text vCard is already stored.
static bool vcard_is_duplicate(nvs_handle_t nvs, const char *vcard, size_t len, uint32_t hash)
Checks whether candidate vCard is already stored.
bool vcard_store_get_display_own(char *out, size_t max_len)
Retrieves display name derived from local own-vCard.
bool vcard_store_get_display(uint16_t slot, char *out, size_t max_len)
Retrieves cached display label for slot.
static bool g_cards_loaded
static size_t trim_line_len(const char *line, size_t len)
Trims trailing CR and LF and whitespace characters from a length-bounded view.
static void set_err(char *err, size_t err_len, const char *msg)
Writes error text into bounded output buffer.
static void copy_field(char *dst, size_t dst_size, const char *src, size_t src_len)
Copies a bounded value into a fixed-size destination buffer.
size_t vcard_generate_from_struct(const vcard_data_t *data, char *out_buf, size_t buf_len)
Generates a vCard 4.0 text representation from the structured data.
void vcard_store_init(void)
Initializes metadata cache for stored peer vCards.
bool vcard_store_delete(uint16_t slot)
Deletes peer vCard at slot index.
static bool append_field(char *buf, size_t buf_size, size_t *pos, const char *prefix, const char *value)
Appends prefix:value\n to the output buffer if value is non-empty.
static uint16_t g_card_count
static char g_own_vcard[768+1]
static uint32_t fnv1a_hash(const char *data, size_t len)
Computes FNV-1a hash for vCard duplicate tracking.