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 }