// must be called after every send or receive, pass pkt to e3x_encrypt before sending lob_t e3x_channel_sending(e3x_channel_t c) { lob_t ret; uint32_t now; if(!c) return NULL; // packets waiting if(c->out) { ret = lob_shift(c->out); c->out = ret->next; // if it's a content packet and reliable, add to sent list if(c->seq && ret->id) c->sent = lob_push(c->sent, lob_copy(ret)); return ret; } // check sent list for any flagged to resend now = util_sys_seconds(); for(ret = c->sent; ret; ret = ret->next) { if(!ret->id) continue; if(ret->id == now) continue; // max once per second throttle resend ret->id = now; return ret; } LOG("sending ack %d acked %d",c->ack,c->acked); // if we need to generate an ack/miss yet, do that if(c->ack != c->acked) return e3x_channel_oob(c); return NULL; }
// adds to sending queue, expects valid packet uint8_t e3x_channel_send(e3x_channel_t c, lob_t inner) { if(!c || !inner) return 1; LOG("channel send %d %s",c->id,lob_json(inner)); c->out = lob_push(c->out, inner); return 0; }
// generate json for all links, returns lob list lob_t mesh_links(mesh_t mesh) { lob_t links = NULL; link_t link; for(link = mesh->links;link;link = link->next) { links = lob_push(links,link_json(link)); } return links; }
util_frames_t util_frames_send(util_frames_t frames, lob_t out) { if(!frames) return LOG("bad args"); if(frames->err) return LOG("frame state error"); if(out) { out->id = 0; // used to track sent bytes frames->outbox = lob_push(frames->outbox, out); }else{ frames->flush = 1; } return frames; }
int main(int argc, char **argv) { lob_t packet; packet = lob_new(); fail_unless(packet); lob_free(packet); uint8_t buf[1024]; char *hex = "001d7b2274797065223a2274657374222c22666f6f223a5b22626172225d7d616e792062696e61727921"; uint8_t len = strlen(hex)/2; util_unhex(hex,strlen(hex),buf); packet = lob_parse(buf,len); fail_unless(packet); fail_unless(lob_len(packet)); fail_unless(packet->head_len == 29); fail_unless(packet->body_len == 11); fail_unless(util_cmp(lob_get(packet,"type"),"test") == 0); fail_unless(util_cmp(lob_get(packet,"foo"),"[\"bar\"]") == 0); lob_free(packet); packet = lob_new(); lob_set_base32(packet,"32",buf,len); fail_unless(lob_get(packet,"32")); fail_unless(strlen(lob_get(packet,"32")) == (base32_encode_length(len)-1)); lob_t bin = lob_get_base32(packet,"32"); fail_unless(bin); fail_unless(bin->body_len == len); lob_set(packet,"key","value"); fail_unless(lob_keys(packet) == 2); // test sorting lob_set(packet,"zz","value"); lob_set(packet,"a","value"); lob_set(packet,"z","value"); lob_sort(packet); fail_unless(util_cmp(lob_get_index(packet,0),"32") == 0); fail_unless(util_cmp(lob_get_index(packet,2),"a") == 0); fail_unless(util_cmp(lob_get_index(packet,4),"key") == 0); fail_unless(util_cmp(lob_get_index(packet,6),"z") == 0); fail_unless(util_cmp(lob_get_index(packet,8),"zz") == 0); lob_free(packet); // minimal comparison test lob_t a = lob_new(); lob_set(a,"foo","bar"); lob_t b = lob_new(); lob_set(b,"foo","bar"); fail_unless(lob_cmp(a,b) == 0); lob_set(b,"bar","foo"); fail_unless(lob_cmp(a,b) != 0); // lots of basic list testing lob_t list = lob_new(); lob_t item = lob_new(); fail_unless(lob_push(list,item)); fail_unless(lob_pop(list) == item); list = item->next; fail_unless((list = lob_unshift(list,item))); fail_unless(lob_shift(list) == item); list = item->next; fail_unless(lob_push(list,item)); fail_unless(list->next == item); lob_t insert = lob_new(); fail_unless(lob_insert(list,list,insert)); fail_unless(list->next == insert); fail_unless(insert->next == item); fail_unless(lob_splice(list,insert)); fail_unless(list->next == item); lob_t array = lob_array(list); fail_unless(array); fail_unless(util_cmp(lob_json(array),"[,]") == 0); fail_unless(lob_freeall(list) == NULL); // simple index testing lob_t index = lob_new(); lob_t c1 = lob_new(); lob_set(c1,"id","c1"); lob_push(index,c1); lob_t c2 = lob_new(); lob_set(c2,"id","c2"); lob_push(index,c2); fail_unless(lob_match(index,"id","c1") == c1); fail_unless(lob_match(index,"id","c2") == c2); float f = 42.42; lob_t ft = lob_new(); lob_head(ft,(uint8_t*)"{\"foo\":42.42}",13); fail_unless(lob_get_float(ft,"foo") == f); lob_set_float(ft,"bar2",f,2); fail_unless(lob_get_float(ft,"bar2") == f); lob_set_float(ft,"bar1",f,1); fail_unless(lob_get_cmp(ft,"bar1","42.4") == 0); lob_set_float(ft,"bar0",f,0); fail_unless(lob_get_int(ft,"bar0") == 42); LOG("floats %s",lob_json(ft)); return 0; }
// the next frame of data in/out, if data NULL bool is just ready check util_frames_t util_frames_inbox(util_frames_t frames, uint8_t *data, uint8_t *meta) { if(!frames) return LOG("bad args"); if(frames->err) return LOG("frame state error"); if(!data) return util_frames_await(frames); // conveniences for code readability uint8_t size = PAYLOAD(frames); uint32_t hash1; memcpy(&(hash1),data+size,4); uint32_t hash2 = murmur4(data,size); // LOG("frame sz %u hash rx %lu check %lu",size,hash1,hash2); // meta frames are self contained if(hash1 == hash2) { // LOG("meta frame %s",util_hex(data,size+4,NULL)); // if requested, copy in metadata block if(meta) memcpy(meta,data+10,size-10); // verify sender's last rx'd hash uint32_t rxd; memcpy(&rxd,data,4); uint8_t *bin = lob_raw(frames->outbox); uint32_t len = lob_len(frames->outbox); uint32_t rxs = frames->outbase; uint8_t i; for(i = 0;i <= frames->out;i++) { // verify/reset to last rx'd frame if(rxd == rxs) { frames->out = i; break; } // handle tail hash correctly like sender uint32_t at = i * size; rxs ^= murmur4((bin+at), ((at+size) > len) ? (len - at) : size); rxs += i; } if(rxd != rxs) { LOG("invalid received frame hash %lu check %lu",rxd,rxs); frames->err = 1; return NULL; } // advance full packet once confirmed if((frames->out * size) > len) { frames->out = 0; frames->outbase = rxd; lob_t done = lob_shift(frames->outbox); frames->outbox = done->next; done->next = NULL; lob_free(done); } // sender's last tx'd hash changes flush state if(memcmp(data+4,&(frames->inlast),4) == 0) { frames->flush = 0; }else{ frames->flush = 1; LOG("flushing mismatch, last %lu",frames->inlast); } return frames; } // dedup, if identical to last received one if(hash1 == frames->inlast) return frames; // full data frames must match combined w/ previous hash2 ^= frames->inlast; hash2 += frames->in; if(hash1 == hash2) { if(!util_frame_new(frames)) return LOG("OOM"); // append, update inlast, continue memcpy(frames->cache->data,data,size); frames->flush = 0; frames->inlast = hash1; // LOG("got data frame %lu",hash1); return frames; } // check if it's a tail data frame uint8_t tail = data[size-1]; if(tail >= size) { frames->flush = 1; return LOG("invalid frame data length: %u %s",tail,util_hex(data+(size-4),8,NULL)); } // hash must match hash2 = murmur4(data,tail); hash2 ^= frames->inlast; hash2 += frames->in; if(hash1 != hash2) { frames->flush = 1; return LOG("invalid frame %u tail (%u) hash %lu != %lu last %lu",frames->in,tail,hash1,hash2,frames->inlast); } // process full packet w/ tail, update inlast, set flush // LOG("got frame tail of %u",tail); frames->flush = 1; frames->inlast = hash1; size_t tlen = (frames->in * size) + tail; // TODO make a lob_new that creates space to prevent double-copy here uint8_t *buf = malloc(tlen); if(!buf) return LOG("OOM"); // copy in tail memcpy(buf+(frames->in * size), data, tail); // eat cached frames copying in reverse util_frame_t frame = frames->cache; while(frames->in && frame) { frames->in--; memcpy(buf+(frames->in*size),frame->data,size); frame = frame->prev; } frames->cache = util_frame_free(frames->cache); lob_t packet = lob_parse(buf,tlen); if(!packet) LOG("packet parsing failed: %s",util_hex(buf,tlen,NULL)); free(buf); frames->inbox = lob_push(frames->inbox,packet); return frames; }
// usually sets/updates event timer, ret if accepted/valid into receiving queue uint8_t e3x_channel_receive(e3x_channel_t c, lob_t inner) { lob_t prev, miss; uint32_t ack; if(!c || !inner) return 1; // TODO timer checks if(inner == c->timer) { // uhoh or resend return 1; } // no reliability, just append and done if(!c->seq) { c->in = lob_push(c->in, inner); return 0; } // reliability inner->id = lob_get_uint(inner, "seq"); // if there's an id, it's content if(inner->id) { if(inner->id <= c->acked) { LOG("dropping old seq %d",inner->id); return 1; } // insert it sorted prev = c->in; while(prev && prev->id > inner->id) prev = prev->next; if(prev && prev->id == inner->id) { LOG("dropping dup seq %d",inner->id); lob_free(inner); return 1; } c->in = lob_insert(c->in, prev, inner); // LOG("inserted seq %d",c->in?c->in->id:-1); } // remove any from outgoing cache that have been ack'd if((ack = lob_get_uint(inner, "ack"))) { prev = c->out; while(prev && prev->id <= ack) { c->out = lob_splice(c->out, prev); lob_free(prev); // TODO, notify app this packet was ack'd prev = c->out; } // process array, update list of packets that are missed or not if((miss = lob_get_json(inner, "miss"))) { // set resend flag timestamp on them // update window } } // TODO reset timer stuff return 0; }