damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

nostrdb.c (9228B)


      1 
      2 #include "nostrdb.h"
      3 #include "jsmn.h"
      4 #include "hex.h"
      5 #include "cursor.h"
      6 #include <stdlib.h>
      7 
      8 struct ndb_json_parser {
      9 	const char *json;
     10 	int json_len;
     11 	struct ndb_builder builder;
     12 	jsmn_parser json_parser;
     13 	jsmntok_t *toks, *toks_end;
     14 	int num_tokens;
     15 };
     16 
     17 static inline int cursor_push_tag(struct cursor *cur, struct ndb_tag *tag)
     18 {
     19 	return cursor_push_u16(cur, tag->count);
     20 }
     21 
     22 int ndb_builder_new(struct ndb_builder *builder, unsigned char *buf,
     23 		    int bufsize)
     24 {
     25 	struct ndb_note *note;
     26 	struct cursor mem;
     27 	int half, size, str_indices_size;
     28 
     29 	// come on bruh
     30 	if (bufsize < sizeof(struct ndb_note) * 2)
     31 		return 0;
     32 
     33 	str_indices_size = bufsize / 32;
     34 	size = bufsize - str_indices_size;
     35 	half = size / 2;
     36 
     37 	//debug("size %d half %d str_indices %d\n", size, half, str_indices_size);
     38 
     39 	// make a safe cursor of our available memory
     40 	make_cursor(buf, buf + bufsize, &mem);
     41 
     42 	note = builder->note = (struct ndb_note *)buf;
     43 
     44 	// take slices of the memory into subcursors
     45 	if (!(cursor_slice(&mem, &builder->note_cur, half) &&
     46 	      cursor_slice(&mem, &builder->strings, half) &&
     47 	      cursor_slice(&mem, &builder->str_indices, str_indices_size))) {
     48 		return 0;
     49 	}
     50 
     51 	memset(note, 0, sizeof(*note));
     52 	builder->note_cur.p += sizeof(*note);
     53 
     54 	note->version = 1;
     55 
     56 	return 1;
     57 }
     58 
     59 static inline int ndb_json_parser_init(struct ndb_json_parser *p,
     60 				       const char *json, int json_len,
     61 				       unsigned char *buf, int bufsize)
     62 {
     63 	int half = bufsize / 2;
     64 
     65 	unsigned char *tok_start = buf + half;
     66 	unsigned char *tok_end = buf + bufsize;
     67 
     68 	p->toks = (jsmntok_t*)tok_start;
     69 	p->toks_end = (jsmntok_t*)tok_end;
     70 	p->num_tokens = 0;
     71 	p->json = json;
     72 	p->json_len = json_len;
     73 
     74 	// ndb_builder gets the first half of the buffer, and jsmn gets the
     75 	// second half. I like this way of alloating memory (without actually
     76 	// dynamically allocating memory). You get one big chunk upfront and
     77 	// then submodules can recursively subdivide it. Maybe you could do
     78 	// something even more clever like golden-ratio style subdivision where
     79 	// the more important stuff gets a larger chunk and then it spirals
     80 	// downward into smaller chunks. Thanks for coming to my TED talk.
     81 
     82 	if (!ndb_builder_new(&p->builder, buf, half))
     83 		return 0;
     84 
     85 	jsmn_init(&p->json_parser);
     86 
     87 	return 1;
     88 }
     89 
     90 static inline int ndb_json_parser_parse(struct ndb_json_parser *p)
     91 {
     92 	int cap = ((unsigned char *)p->toks_end - (unsigned char*)p->toks)/sizeof(*p->toks);
     93 	p->num_tokens =
     94 		jsmn_parse(&p->json_parser, p->json, p->json_len, p->toks, cap);
     95 
     96 	return p->num_tokens;
     97 }
     98 
     99 int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note)
    100 {
    101 	int strings_len = builder->strings.p - builder->strings.start;
    102 	unsigned char *end = builder->note_cur.p + strings_len;
    103 	int total_size = end - builder->note_cur.start;
    104 
    105 	// move the strings buffer next to the end of our ndb_note
    106 	memmove(builder->note_cur.p, builder->strings.start, strings_len);
    107 
    108 	// set the strings location
    109 	builder->note->strings = builder->note_cur.p - builder->note_cur.start;
    110 
    111 	// record the total size
    112 	//builder->note->size = total_size;
    113 
    114 	*note = builder->note;
    115 
    116 	return total_size;
    117 }
    118 
    119 struct ndb_note * ndb_builder_note(struct ndb_builder *builder)
    120 {
    121 	return builder->note;
    122 }
    123 
    124 int ndb_builder_make_string(struct ndb_builder *builder, const char *str,
    125 			    int len, union packed_str *pstr)
    126 {
    127 	uint32_t loc;
    128 
    129 	if (len == 0) {
    130 		*pstr = ndb_char_to_packed_str(0);
    131 		return 1;
    132 	} else if (len == 1) {
    133 		*pstr = ndb_char_to_packed_str(str[0]);
    134 		return 1;
    135 	} else if (len == 2) {
    136 		*pstr = ndb_chars_to_packed_str(str[0], str[1]);
    137 		return 1;
    138 	}
    139 
    140 	// find existing matching string to avoid duplicate strings
    141 	int indices = cursor_count(&builder->str_indices, sizeof(uint32_t));
    142 	for (int i = 0; i < indices; i++) {
    143 		uint32_t index = ((uint32_t*)builder->str_indices.start)[i];
    144 		const char *some_str = (const char*)builder->strings.start + index;
    145 
    146 		if (!strcmp(some_str, str)) {
    147 			// found an existing matching str, use that index
    148 			*pstr = ndb_offset_str(index);
    149 			return 1;
    150 		}
    151 	}
    152 
    153 	// no string found, push a new one
    154 	loc = builder->strings.p - builder->strings.start;
    155 	if (!(cursor_push(&builder->strings, (unsigned char*)str, len) &&
    156 	      cursor_push_byte(&builder->strings, '\0'))) {
    157 		return 0;
    158 	}
    159 	*pstr = ndb_offset_str(loc);
    160 
    161 	// record in builder indices. ignore return value, if we can't cache it
    162 	// then whatever
    163 	cursor_push_u32(&builder->str_indices, loc);
    164 
    165 	return 1;
    166 }
    167 
    168 int ndb_builder_set_content(struct ndb_builder *builder, const char *content,
    169 			    int len)
    170 {
    171 	return ndb_builder_make_string(builder, content, len, &builder->note->content);
    172 }
    173 
    174 
    175 static inline int jsoneq(const char *json, jsmntok_t *tok, int tok_len,
    176 			 const char *s)
    177 {
    178 	if (tok->type == JSMN_STRING && (int)strlen(s) == tok_len &&
    179 	    memcmp(json + tok->start, s, tok_len) == 0) {
    180 		return 1;
    181 	}
    182 	return 0;
    183 }
    184 
    185 static inline int toksize(jsmntok_t *tok)
    186 {
    187 	return tok->end - tok->start;
    188 }
    189 
    190 // Push a json array into an ndb tag ["p", "abcd..."] -> struct ndb_tag
    191 static inline int ndb_builder_tag_from_json_array(struct ndb_json_parser *p,
    192 						  jsmntok_t *array)
    193 {
    194 	jsmntok_t *str_tok;
    195 	const char *str;
    196 
    197 	if (array->size == 0)
    198 		return 0;
    199 
    200 	if (!ndb_builder_new_tag(&p->builder))
    201 		return 0;
    202 
    203 	for (int i = 0; i < array->size; i++) {
    204 		str_tok = &array[i+1];
    205 		str = p->json + str_tok->start;
    206 
    207 		if (!ndb_builder_push_tag_str(&p->builder, str, toksize(str_tok)))
    208 			return 0;
    209 	}
    210 
    211 	return 1;
    212 }
    213 
    214 // Push json tags into ndb data
    215 //   [["t", "hashtag"], ["p", "abcde..."]] -> struct ndb_tags
    216 static inline int ndb_builder_process_json_tags(struct ndb_json_parser *p,
    217 						jsmntok_t *array)
    218 {
    219 	jsmntok_t *tag = array;
    220 
    221 	if (array->size == 0)
    222 		return 1;
    223 
    224 	for (int i = 0; i < array->size; i++) {
    225         if (!ndb_builder_tag_from_json_array(p, &tag[i+1]))
    226 			return 0;
    227         tag += tag[i+1].size;
    228 	}
    229 
    230 	return 1;
    231 }
    232 
    233 
    234 int ndb_note_from_json(const char *json, int len, struct ndb_note **note,
    235 		       unsigned char *buf, int bufsize)
    236 {
    237 	jsmntok_t *tok = NULL;
    238 	unsigned char hexbuf[64];
    239 
    240 	int i, tok_len, res;
    241 	const char *start;
    242 	struct ndb_json_parser parser;
    243 
    244 	ndb_json_parser_init(&parser, json, len, buf, bufsize);
    245 	res = ndb_json_parser_parse(&parser);
    246 	if (res < 0)
    247 		return res;
    248 
    249 	if (parser.num_tokens < 1 || parser.toks[0].type != JSMN_OBJECT)
    250 		return 0;
    251 
    252 	for (i = 1; i < parser.num_tokens; i++) {
    253 		tok = &parser.toks[i];
    254 		start = json + tok->start;
    255 		tok_len = toksize(tok);
    256 
    257 		//printf("toplevel %.*s %d\n", tok_len, json + tok->start, tok->type);
    258 		if (tok_len == 0 || i + 1 >= parser.num_tokens)
    259 			continue;
    260 
    261 		if (start[0] == 'p' && jsoneq(json, tok, tok_len, "pubkey")) {
    262 			// pubkey
    263 			tok = &parser.toks[i+1];
    264 			hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
    265 			ndb_builder_set_pubkey(&parser.builder, hexbuf);
    266 		} else if (tok_len == 2 && start[0] == 'i' && start[1] == 'd') {
    267 			// id
    268 			tok = &parser.toks[i+1];
    269 			hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
    270 			// TODO: validate id
    271 			ndb_builder_set_id(&parser.builder, hexbuf);
    272 		} else if (tok_len == 3 && start[0] == 's' && start[1] == 'i' && start[2] == 'g') {
    273 			// sig
    274 			tok = &parser.toks[i+1];
    275 			hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
    276 			ndb_builder_set_signature(&parser.builder, hexbuf);
    277 		} else if (start[0] == 'k' && jsoneq(json, tok, tok_len, "kind")) {
    278 			// kind
    279 			tok = &parser.toks[i+1];
    280 			printf("json_kind %.*s\n", toksize(tok), json + tok->start);
    281 		} else if (start[0] == 'c') {
    282 			if (jsoneq(json, tok, tok_len, "created_at")) {
    283 				// created_at
    284 				tok = &parser.toks[i+1];
    285 				printf("json_created_at %.*s\n", toksize(tok), json + tok->start);
    286 			} else if (jsoneq(json, tok, tok_len, "content")) {
    287 				// content
    288 				tok = &parser.toks[i+1];
    289 				if (!ndb_builder_set_content(&parser.builder, json + tok->start, toksize(tok)))
    290 					return 0;
    291 			}
    292 		} else if (start[0] == 't' && jsoneq(json, tok, tok_len, "tags")) {
    293 			tok = &parser.toks[i+1];
    294 			ndb_builder_process_json_tags(&parser, tok);
    295 			i += tok->size;
    296 		}
    297 	}
    298 
    299 	return ndb_builder_finalize(&parser.builder, note);
    300 }
    301 
    302 void ndb_builder_set_pubkey(struct ndb_builder *builder, unsigned char *pubkey)
    303 {
    304 	memcpy(builder->note->pubkey, pubkey, 32);
    305 }
    306 
    307 void ndb_builder_set_id(struct ndb_builder *builder, unsigned char *id)
    308 {
    309 	memcpy(builder->note->id, id, 32);
    310 }
    311 
    312 void ndb_builder_set_signature(struct ndb_builder *builder,
    313 			       unsigned char *signature)
    314 {
    315 	memcpy(builder->note->signature, signature, 64);
    316 }
    317 
    318 void ndb_builder_set_kind(struct ndb_builder *builder, uint32_t kind)
    319 {
    320 	builder->note->kind = kind;
    321 }
    322 
    323 int ndb_builder_new_tag(struct ndb_builder *builder)
    324 {
    325 	builder->note->tags.count++;
    326 	struct ndb_tag tag = {0};
    327 	builder->current_tag = (struct ndb_tag *)builder->note_cur.p;
    328 	return cursor_push_tag(&builder->note_cur, &tag);
    329 }
    330 
    331 /// Push an element to the current tag
    332 /// 
    333 /// Basic idea is to call ndb_builder_new_tag
    334 inline int ndb_builder_push_tag_str(struct ndb_builder *builder,
    335 				    const char *str, int len)
    336 {
    337 	union packed_str pstr;
    338 	if (!ndb_builder_make_string(builder, str, len, &pstr))
    339 		return 0;
    340 	if (!cursor_push_u32(&builder->note_cur, pstr.offset))
    341 		return 0;
    342 	builder->current_tag->count++;
    343 	return 1;
    344 }