|
| 1 | + |
| 2 | +#include "nostrdb.h" |
| 3 | +#include "binmoji.h" |
| 4 | + |
| 5 | +// these must be byte-aligned, they are directly accessing the serialized data |
| 6 | +// representation |
| 7 | +#pragma pack(push, 1) |
| 8 | + |
| 9 | +// 16 bytes |
| 10 | +struct ndb_note_meta_entry { |
| 11 | + // 4 byte entry header |
| 12 | + uint16_t type; |
| 13 | + uint16_t flags; |
| 14 | + |
| 15 | + // additional 4 bytes of aux storage for payloads that are >8 bytes |
| 16 | + // |
| 17 | + // for reactions types, this is used for counts |
| 18 | + // normally this would have been padding but we make use of it |
| 19 | + // in our manually packed structure |
| 20 | + uint32_t aux; |
| 21 | + |
| 22 | + // 8 byte metadata payload |
| 23 | + union { |
| 24 | + uint64_t value; |
| 25 | + |
| 26 | + struct { |
| 27 | + uint32_t offset; |
| 28 | + uint32_t padding; |
| 29 | + } offset; |
| 30 | + |
| 31 | + // the reaction binmoji[1] for reaction, count is stored in aux |
| 32 | + union ndb_reaction_str reaction_str; |
| 33 | + } payload; |
| 34 | +}; |
| 35 | +STATIC_ASSERT(sizeof(struct ndb_note_meta_entry) == 16, note_meta_entry_should_be_16_bytes); |
| 36 | + |
| 37 | +/* newtype wrapper around the header entry */ |
| 38 | +struct ndb_note_meta { |
| 39 | + // 4 bytes |
| 40 | + uint8_t version; |
| 41 | + uint8_t padding; |
| 42 | + uint16_t count; |
| 43 | + |
| 44 | + // 4 bytes |
| 45 | + uint32_t data_table_size; |
| 46 | + |
| 47 | + // 8 bytes |
| 48 | + uint64_t flags; |
| 49 | +}; |
| 50 | +STATIC_ASSERT(sizeof(struct ndb_note_meta) == 16, note_meta_entry_should_be_16_bytes); |
| 51 | + |
| 52 | +#pragma pack(pop) |
| 53 | + |
| 54 | +int ndb_reaction_str_is_emoji(union ndb_reaction_str str) |
| 55 | +{ |
| 56 | + return binmoji_get_user_flag(str.binmoji) == 0; |
| 57 | +} |
| 58 | + |
| 59 | +uint16_t ndb_note_meta_entries_count(struct ndb_note_meta *meta) |
| 60 | +{ |
| 61 | + return meta->count; |
| 62 | +} |
| 63 | + |
| 64 | +int ndb_reaction_set_emoji(union ndb_reaction_str *str, const char *emoji) |
| 65 | +{ |
| 66 | + struct binmoji binmoji; |
| 67 | + /* TODO: parse failures? */ |
| 68 | + binmoji_parse(emoji, &binmoji); |
| 69 | + str->binmoji = binmoji_encode(&binmoji); |
| 70 | + return 1; |
| 71 | +} |
| 72 | + |
| 73 | +int ndb_reaction_set_str(union ndb_reaction_str *reaction, const char *str) |
| 74 | +{ |
| 75 | + int i; |
| 76 | + char c; |
| 77 | + |
| 78 | + /* this is like memset'ing the packed string to all 0s as well */ |
| 79 | + reaction->binmoji = 0; |
| 80 | + |
| 81 | + /* set the binmoji user flag so we can catch corrupt binmojis */ |
| 82 | + /* this is in the LSB so it will only touch reaction->packed.flag */ |
| 83 | + reaction->binmoji = binmoji_set_user_flag(reaction->binmoji, 1); |
| 84 | + assert(reaction->packed.flag != 0); |
| 85 | + |
| 86 | + for (i = 0; i < 7; i++) { |
| 87 | + c = str[i]; |
| 88 | + /* string is too big */ |
| 89 | + if (i == 6 && c != '\0') |
| 90 | + return 0; |
| 91 | + reaction->packed.str[i] = c; |
| 92 | + if (c == '\0') |
| 93 | + return 1; |
| 94 | + } |
| 95 | + |
| 96 | + return 0; |
| 97 | +} |
| 98 | + |
| 99 | +static void ndb_note_meta_header_init(struct ndb_note_meta *meta) |
| 100 | +{ |
| 101 | + meta->version = 1; |
| 102 | + meta->flags = 0; |
| 103 | + meta->count = 0; |
| 104 | + meta->data_table_size = 0; |
| 105 | +} |
| 106 | + |
| 107 | +static inline size_t ndb_note_meta_entries_size(struct ndb_note_meta *meta) |
| 108 | +{ |
| 109 | + return (sizeof(struct ndb_note_meta_entry) * meta->count); |
| 110 | +} |
| 111 | + |
| 112 | +void *ndb_note_meta_data_table(struct ndb_note_meta *meta, size_t *size) |
| 113 | +{ |
| 114 | + return meta + ndb_note_meta_entries_size(meta); |
| 115 | +} |
| 116 | + |
| 117 | +size_t ndb_note_meta_total_size(struct ndb_note_meta *header) |
| 118 | +{ |
| 119 | + size_t total_size = sizeof(*header) + header->data_table_size + ndb_note_meta_entries_size(header); |
| 120 | + assert((total_size % 8) == 0); |
| 121 | + return total_size; |
| 122 | +} |
| 123 | + |
| 124 | +struct ndb_note_meta_entry *ndb_note_meta_add_entry(struct ndb_note_meta_builder *builder) |
| 125 | +{ |
| 126 | + struct ndb_note_meta *header = (struct ndb_note_meta *)builder->cursor.start; |
| 127 | + struct ndb_note_meta_entry *entry = NULL; |
| 128 | + |
| 129 | + assert(builder->cursor.p != builder->cursor.start); |
| 130 | + |
| 131 | + if (!(entry = cursor_malloc(&builder->cursor, sizeof(*entry)))) |
| 132 | + return NULL; |
| 133 | + |
| 134 | + /* increase count entry count */ |
| 135 | + header->count++; |
| 136 | + |
| 137 | + return entry; |
| 138 | +} |
| 139 | + |
| 140 | +int ndb_note_meta_builder_init(struct ndb_note_meta_builder *builder, unsigned char *buf, size_t bufsize) |
| 141 | +{ |
| 142 | + make_cursor(buf, buf + bufsize, &builder->cursor); |
| 143 | + |
| 144 | + /* allocate some space for the header */ |
| 145 | + if (!cursor_malloc(&builder->cursor, sizeof(struct ndb_note_meta))) |
| 146 | + return 0; |
| 147 | + |
| 148 | + ndb_note_meta_header_init((struct ndb_note_meta*)builder->cursor.start); |
| 149 | + |
| 150 | + return 1; |
| 151 | +} |
| 152 | + |
| 153 | +/* note flags are stored in the header entry */ |
| 154 | +uint32_t ndb_note_meta_flags(struct ndb_note_meta *meta) |
| 155 | +{ |
| 156 | + return meta->flags; |
| 157 | +} |
| 158 | + |
| 159 | +/* note flags are stored in the header entry */ |
| 160 | +void ndb_note_meta_set_flags(struct ndb_note_meta *meta, uint32_t flags) |
| 161 | +{ |
| 162 | + meta->flags = flags; |
| 163 | +} |
| 164 | + |
| 165 | +static int compare_entries(const void *a, const void *b) |
| 166 | +{ |
| 167 | + struct ndb_note_meta_entry *entry_a, *entry_b; |
| 168 | + uint64_t binmoji_a, binmoji_b; |
| 169 | + int res; |
| 170 | + |
| 171 | + entry_a = (struct ndb_note_meta_entry *)a; |
| 172 | + entry_b = (struct ndb_note_meta_entry *)b; |
| 173 | + |
| 174 | + res = entry_a->type - entry_b->type; |
| 175 | + |
| 176 | + if (res == 0 && entry_a->type == NDB_NOTE_META_REACTION) { |
| 177 | + /* we sort by reaction string for stability */ |
| 178 | + binmoji_a = entry_a->payload.reaction_str.binmoji; |
| 179 | + binmoji_b = entry_b->payload.reaction_str.binmoji; |
| 180 | + |
| 181 | + if (binmoji_a < binmoji_b) { |
| 182 | + return -1; |
| 183 | + } else if (binmoji_a > binmoji_b) { |
| 184 | + return 1; |
| 185 | + } else { |
| 186 | + return 0; |
| 187 | + } |
| 188 | + } else { |
| 189 | + return res; |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +struct ndb_note_meta_entry *ndb_note_meta_entries(struct ndb_note_meta *meta) |
| 194 | +{ |
| 195 | + /* entries start at the end of the header record */ |
| 196 | + return (struct ndb_note_meta_entry *)((uint8_t*)meta + sizeof(*meta)); |
| 197 | +} |
| 198 | + |
| 199 | +void ndb_note_meta_build(struct ndb_note_meta_builder *builder, struct ndb_note_meta **meta) |
| 200 | +{ |
| 201 | + /* sort entries */ |
| 202 | + struct ndb_note_meta_entry *entries; |
| 203 | + struct ndb_note_meta *header = (struct ndb_note_meta*)builder->cursor.start; |
| 204 | + |
| 205 | + /* not initialized */ |
| 206 | + assert(builder->cursor.start != builder->cursor.p); |
| 207 | + |
| 208 | + if (header->count > 0) { |
| 209 | + entries = ndb_note_meta_entries(header); |
| 210 | + |
| 211 | + /* ensure entries are always sorted so bsearch is possible for large metadata |
| 212 | + * entries. probably won't need that for awhile though */ |
| 213 | + qsort(entries, header->count, sizeof(struct ndb_note_meta_entry), compare_entries); |
| 214 | + } |
| 215 | + |
| 216 | + *meta = header; |
| 217 | + return; |
| 218 | +} |
| 219 | + |
| 220 | +/* find a metadata entry, optionally matching a payload */ |
| 221 | +struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta, uint16_t type, uint64_t *payload) |
| 222 | +{ |
| 223 | + struct ndb_note_meta_entry *entries, *entry; |
| 224 | + int i; |
| 225 | + |
| 226 | + if (meta->count == 0) |
| 227 | + return NULL; |
| 228 | + |
| 229 | + entries = ndb_note_meta_entries(meta); |
| 230 | + |
| 231 | + for (i = 0; i < meta->count; i++) { |
| 232 | + entry = &entries[i]; |
| 233 | + if (entry->type != type) |
| 234 | + continue; |
| 235 | + if (payload && *payload != entry->payload.value) |
| 236 | + continue; |
| 237 | + return entry; |
| 238 | + } |
| 239 | + |
| 240 | + return NULL; |
| 241 | +} |
| 242 | + |
| 243 | +void ndb_note_meta_reaction_set(struct ndb_note_meta_entry *entry, uint32_t count, union ndb_reaction_str str) |
| 244 | +{ |
| 245 | + entry->type = NDB_NOTE_META_REACTION; |
| 246 | + entry->flags = 0; |
| 247 | + entry->aux = count; |
| 248 | + entry->payload.reaction_str = str; |
| 249 | +} |
| 250 | + |
| 251 | +/* sets the quote repost count for this note */ |
| 252 | +void ndb_note_meta_quotes_set(struct ndb_note_meta_entry *entry, uint32_t count) |
| 253 | +{ |
| 254 | + entry->type = NDB_NOTE_META_QUOTES; |
| 255 | + entry->flags = 0; |
| 256 | + entry->aux = count; |
| 257 | + /* unused */ |
| 258 | + entry->payload.value = 0; |
| 259 | +} |
| 260 | + |
| 261 | +uint32_t ndb_note_meta_reaction_count(struct ndb_note_meta_entry *entry) |
| 262 | +{ |
| 263 | + return entry->aux; |
| 264 | +} |
| 265 | + |
| 266 | +void ndb_note_meta_reaction_set_count(struct ndb_note_meta_entry *entry, uint32_t count) |
| 267 | +{ |
| 268 | + entry->aux = count; |
| 269 | +} |
| 270 | + |
| 271 | +union ndb_reaction_str ndb_note_meta_reaction_str(struct ndb_note_meta_entry *entry) |
| 272 | +{ |
| 273 | + return entry->payload.reaction_str; |
| 274 | +} |
0 commit comments