commit 78cf12171292eda315043ac8e18e0b242a9c5dc4
parent 98a5275bd77898e13c243343555b1c4044e79368
Author: William Casarin <jb55@jb55.com>
Date: Tue, 25 Jul 2023 19:29:08 -0700
ndb: add websocket event parser
next up is calculating and verifying the hash right off the wire as well
Diffstat:
M | hex.h | | | 2 | +- |
M | nostrdb.c | | | 131 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
M | nostrdb.h | | | 44 | +++++++++++++++++++++++++++++++++++++++++--- |
M | test.c | | | 24 | ++++++++++++++++++++++++ |
4 files changed, 167 insertions(+), 34 deletions(-)
diff --git a/hex.h b/hex.h
@@ -40,7 +40,7 @@ static int hex_decode(const char *str, size_t slen, void *buf, size_t bufsize)
}
-static char hexchar(unsigned int val)
+static inline char hexchar(unsigned int val)
{
if (val < 10)
return '0' + val;
diff --git a/nostrdb.c b/nostrdb.c
@@ -18,6 +18,7 @@ struct ndb_json_parser {
struct ndb_builder builder;
jsmn_parser json_parser;
jsmntok_t *toks, *toks_end;
+ int i;
int num_tokens;
};
@@ -102,6 +103,8 @@ static inline int ndb_json_parser_parse(struct ndb_json_parser *p)
p->num_tokens =
jsmn_parse(&p->json_parser, p->json, p->json_len, p->toks, cap);
+ p->i = 0;
+
return p->num_tokens;
}
@@ -645,92 +648,160 @@ static int parse_unsigned_int(const char *start, int len, unsigned int *num)
return 1;
}
-
-int ndb_note_from_json(const char *json, int len, struct ndb_note **note,
- unsigned char *buf, int bufsize)
+int ndb_ws_event_from_json(const char *json, int len, struct ndb_tce *tce,
+ unsigned char *buf, int bufsize)
{
jsmntok_t *tok = NULL;
- unsigned char hexbuf[64];
-
- int i, tok_len, res;
- const char *start;
+ int tok_len, res;
struct ndb_json_parser parser;
ndb_json_parser_init(&parser, json, len, buf, bufsize);
- res = ndb_json_parser_parse(&parser);
- if (res < 0)
+ if ((res = ndb_json_parser_parse(&parser)) < 0)
return res;
- if (parser.num_tokens < 1 || parser.toks[0].type != JSMN_OBJECT)
+ if (parser.num_tokens < 3 || parser.toks[0].type != JSMN_ARRAY)
+ return 0;
+
+ parser.i = 1;
+ tok = &parser.toks[parser.i++];
+ tok_len = toksize(tok);
+ if (tok->type != JSMN_STRING)
return 0;
- for (i = 1; i < parser.num_tokens; i++) {
- tok = &parser.toks[i];
+ if (tok_len == 5 && !memcmp("EVENT", json + tok->start, 5)) {
+ tce->evtype = NDB_TCE_EVENT;
+ struct ndb_event *ev = &tce->event;
+
+ tok = &parser.toks[parser.i++];
+ if (tok->type != JSMN_STRING)
+ return 0;
+
+ ev->subid = json + tok->start;
+ ev->subid_len = toksize(tok);
+
+ return ndb_parse_json_note(&parser, &ev->note);
+ } else if (tok_len == 2 && !memcmp("OK", json + tok->start, 2)) {
+ if (parser.num_tokens != 5)
+ return 0;
+
+ struct ndb_command_result *cr = &tce->command_result;
+
+ tce->evtype = NDB_TCE_OK;
+
+ tok = &parser.toks[parser.i++];
+ if (tok->type != JSMN_STRING)
+ return 0;
+
+ cr->subid = json + tok->start;
+ cr->subid_len = toksize(tok);
+
+ tok = &parser.toks[parser.i++];
+ if (tok->type != JSMN_PRIMITIVE || toksize(tok) == 0)
+ return 0;
+
+ cr->ok = (json + tok->start)[0] == 't';
+ return 1;
+ }
+
+ return 0;
+}
+
+int ndb_parse_json_note(struct ndb_json_parser *parser, struct ndb_note **note)
+{
+ jsmntok_t *tok = NULL;
+ unsigned char hexbuf[64];
+ const char *json = parser->json;
+ const char *start;
+ int i, tok_len;
+
+ if (parser->toks[parser->i].type != JSMN_OBJECT)
+ return 0;
+
+ // TODO: build id buffer and verify at end
+
+ for (i = parser->i + 1; i < parser->num_tokens; i++) {
+ tok = &parser->toks[i];
start = json + tok->start;
tok_len = toksize(tok);
//printf("toplevel %.*s %d\n", tok_len, json + tok->start, tok->type);
- if (tok_len == 0 || i + 1 >= parser.num_tokens)
+ if (tok_len == 0 || i + 1 >= parser->num_tokens)
continue;
if (start[0] == 'p' && jsoneq(json, tok, tok_len, "pubkey")) {
// pubkey
- tok = &parser.toks[i+1];
+ tok = &parser->toks[i+1];
hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
- ndb_builder_set_pubkey(&parser.builder, hexbuf);
+ ndb_builder_set_pubkey(&parser->builder, hexbuf);
} else if (tok_len == 2 && start[0] == 'i' && start[1] == 'd') {
// id
- tok = &parser.toks[i+1];
+ tok = &parser->toks[i+1];
hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
// TODO: validate id
- ndb_builder_set_id(&parser.builder, hexbuf);
+ ndb_builder_set_id(&parser->builder, hexbuf);
} else if (tok_len == 3 && start[0] == 's' && start[1] == 'i' && start[2] == 'g') {
// sig
- tok = &parser.toks[i+1];
+ tok = &parser->toks[i+1];
hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
- ndb_builder_set_sig(&parser.builder, hexbuf);
+ ndb_builder_set_sig(&parser->builder, hexbuf);
} else if (start[0] == 'k' && jsoneq(json, tok, tok_len, "kind")) {
// kind
- tok = &parser.toks[i+1];
+ tok = &parser->toks[i+1];
start = json + tok->start;
if (tok->type != JSMN_PRIMITIVE || tok_len <= 0)
return 0;
if (!parse_unsigned_int(start, toksize(tok),
- &parser.builder.note->kind))
+ &parser->builder.note->kind))
return 0;
} else if (start[0] == 'c') {
if (jsoneq(json, tok, tok_len, "created_at")) {
// created_at
- tok = &parser.toks[i+1];
+ tok = &parser->toks[i+1];
start = json + tok->start;
if (tok->type != JSMN_PRIMITIVE || tok_len <= 0)
return 0;
if (!parse_unsigned_int(start, toksize(tok),
- &parser.builder.note->created_at))
+ &parser->builder.note->created_at))
return 0;
} else if (jsoneq(json, tok, tok_len, "content")) {
// content
- tok = &parser.toks[i+1];
+ tok = &parser->toks[i+1];
union ndb_packed_str pstr;
tok_len = toksize(tok);
int written, pack_ids = 0;
- if (!ndb_builder_make_json_str(&parser.builder,
+ if (!ndb_builder_make_json_str(&parser->builder,
json + tok->start,
tok_len, &pstr,
&written, pack_ids)) {
return 0;
}
- parser.builder.note->content_length = written;
- parser.builder.note->content = pstr;
+ parser->builder.note->content_length = written;
+ parser->builder.note->content = pstr;
}
} else if (start[0] == 't' && jsoneq(json, tok, tok_len, "tags")) {
- tok = &parser.toks[i+1];
- ndb_builder_process_json_tags(&parser, tok);
+ tok = &parser->toks[i+1];
+ ndb_builder_process_json_tags(parser, tok);
i += tok->size;
}
}
- return ndb_builder_finalize(&parser.builder, note, NULL);
+ return ndb_builder_finalize(&parser->builder, note, NULL);
+}
+
+int ndb_note_from_json(const char *json, int len, struct ndb_note **note,
+ unsigned char *buf, int bufsize)
+{
+ struct ndb_json_parser parser;
+ int res;
+
+ ndb_json_parser_init(&parser, json, len, buf, bufsize);
+ if ((res = ndb_json_parser_parse(&parser)) < 0)
+ return res;
+
+ if (parser.num_tokens < 1)
+ return 0;
+
+ return ndb_parse_json_note(&parser, note);
}
void ndb_builder_set_pubkey(struct ndb_builder *builder, unsigned char *pubkey)
diff --git a/nostrdb.h b/nostrdb.h
@@ -4,6 +4,17 @@
#include <inttypes.h>
#include "cursor.h"
+// To-client event types
+#define NDB_TCE_EVENT 0x1
+#define NDB_TCE_OK 0x2
+#define NDB_TCE_NOTICE 0x3
+#define NDB_TCE_EOSE 0x4
+
+#define NDB_PACKED_STR 0x1
+#define NDB_PACKED_ID 0x2
+
+struct ndb_json_parser;
+
struct ndb_str {
unsigned char flag;
union {
@@ -12,9 +23,36 @@ struct ndb_str {
};
};
+struct ndb_event {
+ struct ndb_note *note;
+ const char *subid;
+ int subid_len;
+};
+
+struct ndb_command_result {
+ int ok;
+ const char *msg;
+ int msglen;
+ const char *subid;
+ int subid_len;
+};
+
+// To-client event
+struct ndb_tce {
+ int evtype;
+ union {
+ struct ndb_event event;
+ struct ndb_command_result command_result;
+ };
+};
+
struct ndb_keypair {
unsigned char pubkey[32];
unsigned char secret[32];
+
+ // this corresponds to secp256k1's keypair type. it's guaranteed to
+ // be 96 bytes according to their docs. I don't want to depend on
+ // the secp256k1 header here so we just use raw bytes.
unsigned char pair[96];
};
@@ -22,9 +60,6 @@ struct ndb_keypair {
// representation
#pragma pack(push, 1)
-/// We can store byte data in the string table, so
-#define NDB_PACKED_STR 0x1
-#define NDB_PACKED_ID 0x2
union ndb_packed_str {
struct {
@@ -91,6 +126,9 @@ int ndb_create_keypair(struct ndb_keypair *key);
int ndb_decode_key(const char *secstr, struct ndb_keypair *keypair);
// BUILDER
+
+int ndb_parse_json_note(struct ndb_json_parser *, struct ndb_note **);
+int ndb_ws_event_from_json(const char *json, int len, struct ndb_tce *tce, unsigned char *buf, int bufsize);
int ndb_note_from_json(const char *json, int len, struct ndb_note **, unsigned char *buf, int buflen);
int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf, int bufsize);
int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note, struct ndb_keypair *privkey);
diff --git a/test.c b/test.c
@@ -246,10 +246,34 @@ static void test_strings_work_before_finalization() {
assert(!strcmp(ndb_note_str(b->note, &b->note->content).str, "hello"));
}
+// test to-client event
+static void test_tce() {
+
+#define HEX_ID "5004a081e397c6da9dc2f2d6b3134006a9d0e8c1b46689d9fe150bb2f21a204d"
+#define HEX_PK "b169f596968917a1abeb4234d3cf3aa9baee2112e58998d17c6db416ad33fe40"
+#define JSON "{\"id\": \"" HEX_ID "\",\"pubkey\": \"" HEX_PK "\",\"created_at\": 1689836342,\"kind\": 1,\"tags\": [[\"p\",\"" HEX_ID "\"], [\"word\", \"words\", \"w\"]],\"content\": \"共通語\",\"sig\": \"e4d528651311d567f461d7be916c37cbf2b4d530e672f29f15f353291ed6df60c665928e67d2f18861c5ca88\"}"
+ unsigned char buf[1024];
+ const char json[] = "[\"EVENT\",\"subid123\"," JSON "]";
+ struct ndb_tce tce;
+ int ok;
+
+ ok = ndb_ws_event_from_json(json, sizeof(json), &tce, buf, sizeof(buf));
+ assert(ok);
+
+ assert(tce.evtype == NDB_TCE_EVENT);
+ assert(tce.event.subid_len == 8);
+ assert(!memcmp(tce.event.subid, "subid123", 8));
+
+#undef HEX_ID
+#undef HEX_PK
+#undef JSON
+}
+
int main(int argc, const char *argv[]) {
test_basic_event();
test_empty_tags();
test_parse_json();
test_parse_contact_list();
test_strings_work_before_finalization();
+ test_tce();
}