return ret;
}
+
+/* parse_label_value reads a label value, unescapes it and prints it to buf. On
+ * success, inout is updated to point to the character just *after* the label
+ * value, i.e. the character *following* the ending quotes - either a comma or
+ * closing curlies. */
+static int parse_label_value(strbuf_t *buf, char const **inout) {
+ char const *ptr = *inout;
+
+ if (ptr[0] != '"') {
+ return EINVAL;
+ }
+ ptr++;
+
+ while (ptr[0] != '"') {
+ size_t valid_len = strcspn(ptr, "\\\"\n");
+ if (valid_len != 0) {
+ strbuf_printn(buf, ptr, valid_len);
+ ptr += valid_len;
+ continue;
+ }
+
+ if ((ptr[0] == 0) || (ptr[0] == '\n')) {
+ return EINVAL;
+ }
+
+ assert(ptr[0] == '\\');
+ if (ptr[1] == 0) {
+ return EINVAL;
+ }
+
+ char tmp[2] = {ptr[1], 0};
+ if (tmp[0] == 'n') {
+ tmp[0] = '\n';
+ } else if (tmp[0] == 'r') {
+ tmp[0] = '\r';
+ } else if (tmp[0] == 't') {
+ tmp[0] = '\t';
+ }
+
+ strbuf_print(buf, tmp);
+
+ ptr += 2;
+ }
+
+ assert(ptr[0] == '"');
+ ptr++;
+ *inout = ptr;
+ return 0;
+}
+
+/* metric_family_unmarshal_identity parses the metric identity and updates
+ * "inout" to point to the first character following the identity. With valid
+ * input, this means that "inout" will then point either to a '\0' (null byte)
+ * or a ' ' (space). */
+static int metric_family_unmarshal_identity(metric_family_t *fam,
+ char const **inout) {
+ if ((fam == NULL) || (inout == NULL) || (*inout == NULL)) {
+ return EINVAL;
+ }
+
+ char const *ptr = *inout;
+ size_t name_len = strspn(ptr, VALID_NAME_CHARS);
+ if (name_len == 0) {
+ return EINVAL;
+ }
+
+ char name[name_len + 1];
+ strncpy(name, ptr, name_len);
+ name[name_len] = 0;
+ ptr += name_len;
+
+ fam->name = strdup(name);
+ if (fam->name == NULL) {
+ return ENOMEM;
+ }
+
+ /* metric name without labels */
+ if ((ptr[0] == 0) || (ptr[0] == ' ')) {
+ *inout = ptr;
+ return 0;
+ }
+
+ if (ptr[0] != '{') {
+ return EINVAL;
+ }
+
+ metric_t *m = fam->metric.ptr;
+ int ret = 0;
+ while ((ptr[0] == '{') || (ptr[0] == ',')) {
+ ptr++;
+
+ size_t key_len = strspn(ptr, VALID_LABEL_CHARS);
+ if (key_len == 0) {
+ ret = EINVAL;
+ break;
+ }
+ char key[key_len + 1];
+ strncpy(key, ptr, key_len);
+ key[key_len] = 0;
+ ptr += key_len;
+
+ if (ptr[0] != '=') {
+ ret = EINVAL;
+ break;
+ }
+ ptr++;
+
+ strbuf_t value = STRBUF_CREATE;
+ int status = parse_label_value(&value, &ptr);
+ if (status != 0) {
+ ret = status;
+ STRBUF_DESTROY(value);
+ break;
+ }
+
+ /* one metric is added to the family by metric_family_unmarshal_text. */
+ assert(fam->metric.num >= 1);
+
+ status = label_set_add(&fam->metric.ptr[0].label, key, value.ptr);
+ STRBUF_DESTROY(value);
+ if (status != 0) {
+ ret = status;
+ break;
+ }
+ }
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ if ((ptr[0] != '}') || ((ptr[1] != 0) && (ptr[1] != ' '))) {
+ return EINVAL;
+ }
+
+ *inout = &ptr[1];
+ return 0;
+}
+
+metric_t *metric_parse_identity(char const *buf) {
+ if (buf == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ metric_family_t *fam = calloc(1, sizeof(*fam));
+ if (fam == NULL) {
+ return NULL;
+ }
+ fam->type = METRIC_TYPE_UNTYPED;
+
+ int status = metric_list_add(&fam->metric, (metric_t){.family = fam});
+ if (status != 0) {
+ metric_family_free(fam);
+ errno = status;
+ return NULL;
+ }
+
+ status = metric_family_unmarshal_identity(fam, &buf);
+ if (status != 0) {
+ metric_family_free(fam);
+ errno = status;
+ return NULL;
+ }
+
+ if (buf[0] != 0) {
+ metric_family_free(fam);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ return fam->metric.ptr;
+}