--- /dev/null
+import os
+import re
+
+TestDir='tests'
+
+env = Environment(CPPPATH = ['.'])
+
+env.SharedLibrary (
+ target='p2tp',
+ source = [ 'bin.cpp','hashtree.cpp','datagram.cpp',
+ 'sbit.cpp' ],
+ LIBS=['stdc++','gtest','glog','crypto'] )
+
+SConscript('tests/SConscript')
--- /dev/null
+/*
+ * bin.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/6/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+
+#include "bin.h"
+#include <algorithm>
+
+bin bin::NONE = 0;
+bin bin::ALL = 0x7fffffff;
+uint8_t bin::BC[256] = {};
+uint8_t bin::T0[256] = {};
+
+void bin::init () {
+ for(int i=0; i<256; i++) {
+ int bc=0, bit;
+ for(bit=0; bit<8; bit++)
+ if ((i>>bit)&1) bc++;
+ BC[i] = bc;
+ for(bit=0; bit<8 && ((i>>bit)&1)==0; bit++);
+ T0[i] = bit;
+ }
+}
+
+bin::vec bin::peaks (uint32_t len) {
+ bin::vec pks;
+ uint32_t i=len, run=0;
+ while (i) {
+ uint32_t bit = bin::highbit(i);
+ i^=bit;
+ run |= bit;
+ pks.push_back(lenpeak(run));
+ }
+ return pks;
+}
+
+void bin::order (vec* vv) {
+ vec& v = *vv;
+ std::sort(v.begin(),v.end());
+ std::reverse(v.begin(),v.end());
+ vec::iterator pw=v.begin(), pr=v.begin();
+ while (pr!=v.end()) {
+ *pw = *pr;
+ while (pw!=v.begin() && (pw-1)->sibling()==*pw) {
+ pw--;
+ *pw = pw->parent();
+ }
+ bin skipto = *pw - pw->mass();
+ while (pr!=v.end() && *pr>skipto) {
+ pr++;
+ }
+ pw++;
+ }
+ v.resize(pw-v.begin());
+}
--- /dev/null
+/*
+ * bin.h
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/6/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#ifndef BIN_H
+#define BIN_H
+#include <assert.h>
+#include <stdint.h>
+#include <deque>
+
+struct bin {
+ uint32_t b;
+
+ static bin NONE;
+ static bin ALL;
+ static uint8_t BC[256];
+ static uint8_t T0[256];
+
+ bin() : b(0) {}
+ bin(const bin& b_) : b(b_.b) {}
+ bin(uint32_t b_) : b(b_) {}
+
+ bin(uint8_t layer_, uint32_t offset) {
+ b = lenpeak((offset+1)<<layer_);
+ b -= layer() - layer_;
+ }
+
+ static void init ();
+
+ static uint8_t tailzeros (uint32_t i) {
+ uint8_t ret = 0;
+ if ( (i&0xffff)==0 )
+ ret = 16, i>>=16;
+ if ( (i&0xff)==0 )
+ ret +=8, i>>=8;
+ return ret+T0[i&0xff];
+ }
+
+ static uint8_t bitcount (uint32_t i) {
+ //uint8_t* p = (uint8_t*) &i;
+ //return BC[p[0]] + BC[p[1]] + BC[p[2]] + BC[p[3]];
+ return BC[i&0xff] +
+ BC[(i>>8)&0xff] +
+ BC[(i>>16)&0xff] +
+ BC[i>>24];
+ }
+
+ static uint32_t blackout (uint32_t i) {
+ return i|=(i|=(i|=(i|=(i|=i>>1)>>2)>>4)>>8)>>16;
+ }
+
+ static uint32_t highbit (uint32_t i) {
+ return (blackout(i)+1)>>1;
+ }
+
+ static bool all1 (uint32_t a) {
+ return !(a&(a+1));
+ }
+
+ static bin lenpeak (uint32_t length) {
+ return (length<<1) - bitcount(length);
+ }
+
+ static uint8_t lenlayer (uint32_t len) {
+ return tailzeros(len);
+ }
+
+ static bin layermass (uint8_t layer) {
+ return (2<<layer)-1;
+ }
+
+ static uint32_t lastbiton (uint32_t i) {
+ return (~i+1)&i;
+ }
+
+ typedef std::deque<bin> vec;
+ static vec peaks (uint32_t len);
+
+ static void order (vec* v);
+
+ operator uint32_t() const { return b; }
+
+ bin operator ++ () { return b++; }
+ bin operator -- () { return b--; }
+ bin operator ++ (int) { return ++b; }
+ bin operator -- (int) { return --b; }
+
+ uint32_t mlat() const {
+ return 0;
+ }
+
+ bin left() const {
+ return bin(b-(mass()>>1)-1);
+ }
+
+ bin right() const {
+ return bin(b-1);
+ }
+
+ bin right_foot() const {
+ return bin(b-layer());
+ }
+
+ bin left_foot() const {
+ return bin(b-mass()+1);
+ }
+
+ uint32_t length() const {
+ //assert(*this<=ALL);
+ uint32_t apx = (b>>1) + 16; //if (b<=ALL-32) apx = ALL>>1;
+ uint32_t next = apx-8;
+ next = apx = lenpeak(next)>=b ? next : apx;
+ next -= 4;
+ next = apx = lenpeak(next)>=b ? next : apx;
+ next -= 2;
+ next = apx = lenpeak(next)>=b ? next : apx;
+ next -= 1;
+ next = apx = lenpeak(next)>=b ? next : apx;
+ return apx;
+ }
+
+ uint32_t mass() const {
+ return layermass(layer());
+ }
+
+ uint8_t layer() const {
+ uint32_t len = length();
+ uint8_t topeak = lenpeak(len) - b;
+ return lenlayer(len) - topeak;
+ }
+
+ uint32_t width () const {
+ return 1<<layer();
+ }
+
+ bin peak() const {
+ return lenpeak(length());
+ }
+
+ bin divide (uint8_t ls) const {
+ uint32_t newlen = ((length()-1)>>ls) +1;
+ uint8_t newlr = std::max(0,layer()-ls);
+ return lenpeak(newlen) - lenlayer(newlen) + newlr;
+ }
+
+ uint32_t offset () const {
+ return length() - width();
+ }
+
+ bin modulo (uint8_t ls) const {
+ if (layer()>=ls)
+ return layermass(ls);
+ bin blockleft = lenpeak(((length()-1) & ~((1<<ls)-1)) + 1);
+ return b - blockleft + 1;
+ }
+
+ bin multiply (uint8_t ls) const {
+ return b + length()*(layermass(ls)-1);
+ }
+
+ bool contains (bin c) const {
+ return c.b<=b && c.b>b-mass();
+ }
+
+ bin commonParent (bin other) const {
+ uint8_t maxlayer = std::max(layer(),other.layer());
+ uint32_t myoff = offset()>>maxlayer, othoff = other.offset()>>maxlayer;
+ uint32_t diff = blackout(myoff^othoff);
+ uint8_t toshift = bitcount(diff);
+ return bin(maxlayer+toshift,myoff>>toshift);
+ }
+
+ bin child (bin dir) const {
+ return left().contains(dir) ? left() : right();
+ }
+
+ bin parent (uint8_t g=1) const {
+ uint32_t l = length();
+ uint8_t h2b = layer()+g;
+ uint32_t pbit = 1<<h2b;
+ uint32_t l2b = l & ~(pbit-1);
+ if (l2b!=l)
+ l2b += pbit;
+ return lenpeak(l2b) - lenlayer(l2b) + h2b;
+ //length()==bin(b+1).length() ? b+1 : b+mass()+1;
+ }
+
+ bool is_right () const {
+ return this->parent()==b+1;
+ }
+
+ bool is_left () const {
+ return !is_right();
+ }
+
+ bin sibling () const {
+ return is_left() ? bin(b+mass()) : bin(b-mass());
+ }
+
+ bin scoped (bin top, uint8_t height) const {
+ assert(layer()<=top.layer()); // TERRIBLE
+ assert(top.layer()>=height);
+ uint8_t rel_layer;
+ if (layer()+height>=top.layer())
+ rel_layer = layer()+height-top.layer();
+ else
+ rel_layer = 0;//top.layer() - height;
+ uint32_t rel_offset = (offset()-top.offset()) >> (top.layer()-height+rel_layer);
+ return bin(rel_layer,rel_offset);
+ }
+
+ bin unscoped (bin top, uint8_t height) const {
+ uint32_t undermass = layermass(top.layer()-height);
+ uint32_t pad = (1<<height) - length();
+ uint32_t peak = (1<<(height+1))-1;
+ return top - (peak-this->b) + pad - undermass*pad;
+ }
+
+} ;
+
+
+uint8_t bitcount (uint32_t num);
+
+/*bin l=b>a.b?a.b:b, g=b>a.b?b:a.b;
+ while (!g.contains(l))
+ g = g.parent();
+ return g;*/
+
+#endif
+//20 mln ops per second
--- /dev/null
+#ifndef BIN64_H
+#define BIN64_H
+#include <assert.h>
+#include <stdint.h>
+
+#include <stdio.h>
+
+/** Bin numbers in the tail111 encoding: meaningless
+ bits in the tail are set to 0111...11, while the
+ head denotes the offset. Thus, 1101 is the bin
+ at layer 1, offset 3 (i.e. fourth). */
+struct bin64_t {
+ uint64_t v;
+ static const uint64_t NONE = 0xffffffffffffffffULL;
+ static const uint64_t ALL = 0x7fffffffffffffffULL;
+
+ bin64_t() : v(NONE) {}
+ bin64_t(const bin64_t&b) : v(b.v) {}
+ bin64_t(const uint64_t val) : v(val) {}
+ bin64_t(uint8_t layer, uint64_t offset) :
+ v( (offset<<(layer+1)) | ((1ULL<<layer)-1) ) {}
+ operator uint64_t () const { return v; }
+ bool operator == (bin64_t& b) const { return v==b.v; }
+
+ uint64_t tail_bits () const {
+ return v ^ (v+1);
+ }
+
+ uint64_t tail_bit () const {
+ return (tail_bits()+1)>>1;
+ }
+
+
+ int layer () const {
+ int r = 0;
+ uint64_t tail = ((v^(v+1))+1)>>1;
+ if (tail>0xffffffffULL) {
+ r = 32;
+ tail>>=32;
+ }
+ // courtesy of Sean Eron Anderson
+ // http://graphics.stanford.edu/~seander/bithacks.html
+ static const int DeBRUIJN[32] = {
+ 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
+ 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
+ };
+ r += DeBRUIJN[((uint32_t)(tail*0x077CB531U))>>27];
+ return r;
+ }
+
+ uint64_t base_offset () const {
+ return v&~(tail_bits());
+ }
+
+ uint64_t offset () const {
+ return v >> (layer()+1);
+ }
+
+ bin64_t left () const {
+ assert(layer());
+ return bin64_t( v ^ (tail_bit()>>1) );
+ }
+
+ bin64_t right () const {
+ assert(layer());
+ uint64_t tb = tail_bit();
+ return bin64_t( v ^ (tb|(tb>>1)) );
+ }
+
+ bin64_t parent () const {
+ uint64_t tbs = tail_bits(), ntbs = (tbs+1)|tbs;
+ return bin64_t( (v&~ntbs) | tbs );
+ }
+
+ bool is_left () const {
+ uint64_t tb = tail_bit();
+ return !(v&(tb<<1));
+ }
+
+ /** The array must have 64 cells, as it is the max
+ number of peaks possible (and there are no reason
+ to assume there will be less in any given case. */
+ static void GetPeaks(uint64_t length, bin64_t* peaks) {
+ int pp=0;
+ uint8_t layer = 0;
+ while (length) {
+ if (length&1)
+ peaks[pp++] = bin64_t(layer,length^1);
+ length>>=1;
+ layer++;
+ }
+ peaks[pp] = NONE;
+ }
+
+};
+
+
+#endif
--- /dev/null
+/*
+ * sbit.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 4/1/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include "bins.h"
+
+uint16_t bins::SPLIT[256];
+uint8_t bins::JOIN[256];
+uint16_t bins::MASK1000[32];
+
+bin64_t bins::find (bin64_t range, int layer) {
+ // fall to left border
+ // do
+ // if up && has 0 success
+ // if lower && range-shift-trick
+ // success
+ // while (next && within range)
+ // return fail
+}
--- /dev/null
+/*
+ * sbit.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/28/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#ifndef SERP_SBIT_H
+#define SERP_SBIT_H
+#include "bin64.h"
+
+class bins64 {
+
+ private:
+
+ uint32_t *bits;
+ uint32_t alloc_block;
+
+ protected:
+
+ bool join(uint32_t half) {
+ uint32_t cellno = bits[half]>>(half&1?16:0);
+
+ if (deep(left) || deep(right)) // some half is deep
+ return false;
+ uint8_t b1=JOIN[cell&0xf],
+ b2=JOIN[(cell>>8)&0xf],
+ b3=JOIN[(cell>>16)&0xf],
+ b4=JOIN[cell>>24];
+ if (b1==0xff || b2==0xff || b3==0xff || b4==0xff)
+ return false;
+ bits[half] = b1 | (b2<<4) | (b3<<8) | (b4<<12) ;
+ (*parentdeepcell) ^= 1<<(halfno&32);
+ (*childdeepcell) &= 0xffff>>1; // clean the full bit
+ }
+
+ bool split(uint32_t half) {
+ if (deep(half))
+ return false;
+ uint32_t cell = alloc_cell(), left=cell<<1, right=left+1;
+ bits[half|0xf] |= 1<<(half&0xf);
+ bits[left] = SPLIT[bits[half]>>8];
+ bits[right] = SPLIT[bits[half&0xff]];
+ bits[half] = cell;
+ return true;
+ }
+
+ uint32_t alloc_cell () {
+ do{
+ for(int block=alloc_block; bits[block]&(1<<32); block+=32);
+ for(int off=0; bits[block+off]==0 && off<31; off++);
+ if (off==31)
+ bits[block] |= 1<<32;
+ if (block&(1<<31)) {
+ bits = realloc(bits,block*2);
+ memset();
+ }
+ } while (off==31);
+ alloc_block = block;
+ return block+off;
+ }
+
+ public:
+
+ class iterator {
+ bins64_t *host;
+ uint32_t back[32];
+ uint32_t half;
+ bin64_t top;
+ bin64_t focus;
+ bin16_t mask;
+ public:
+ void left();
+ void right();
+ void parent();
+ bin64_t next();
+ bool defined();
+ uint16_t& operator* ();
+ };
+ friend class iterator;
+
+ bool get (uint64_t bin);
+
+ void set (uint64_t bin);
+
+ bin64_t find (bin64_t range, int layer);
+
+ // TODO: bitwise operators
+
+};
--- /dev/null
+/*
+ * collisions.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/15/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include "bin.h"
+#include "sbit.h"
+#include <deque>
+#include <cmath>
+
+using namespace std;
+
+#define NUMPEERS 40
+#define QUEUE_LEN 32
+#define WIDTH 20
+#define TOP (bin(WIDTH,0))
+
+sbit bigpic; // common knowledge
+
+struct Peer {
+ deque<bin> packets; // packets in flight & unacknowledged
+ feye map;
+ int peerno;
+ bin focus;
+ Peer () : map(bigpic,bin(0,rand()%(1<<WIDTH))) {
+ focus = map.focus;
+ }
+ void jump () {
+ bin oldfoc = focus;
+
+ map = feye(bigpic,focus);
+ for(int i=0; i<packets.size(); i++)
+ map|=packets[i];
+
+ while (map.get(focus) && focus<TOP)
+ focus = focus.parent();
+ if (focus==TOP) {
+ printf("DONE\n");
+ packets.push_back(0);
+ return;
+ }
+ //if (focus!=bin(WIDTH,0))
+ // focus = focus.parent();
+ while (focus.layer()) {
+ bin left = focus.left(), right = focus.right();
+ bool lfull = map.get(left);
+ bool rfull = map.get(right);
+ if (lfull)
+ focus = right;
+ else if (rfull)
+ focus = left;
+ else
+ focus = rand()%2 ? left : right;
+ }
+ if (map.focus.commonParent(focus).layer()>6)
+ map.refocus(focus);
+ if (labs(map.focus-focus)>=129)
+ printf("bred!\n");
+ map |= focus;
+ // sanity check
+ if (bigpic.get(focus))
+ printf("zhopa: peer %i redid %x after jumped %i\n",
+ peerno,focus.offset(),oldfoc.commonParent(focus).layer());
+ assert(focus.layer()==0 && focus<TOP);
+ packets.push_back(focus);
+ }
+ void hint (bin newfoc) {
+ printf("peer %i hinted at %x\n",
+ peerno,newfoc.offset());
+ focus = newfoc;
+ //map.refocus(newfoc); // preserve recent sends if possible
+ //map |= feye(bigpic,newfoc); // update with the big picture
+ map = feye(bigpic,newfoc);
+ }
+ void ack (bin packet) {
+ feye a(bigpic,packet);
+ a.refocus(map.focus);
+ map |= a;
+ }
+};
+
+
+bin random_descend() {
+ bin ret = bin(WIDTH,0);
+ while (ret.layer()) {
+ bin left = ret.left(), right = ret.right();
+ bool lfull = bigpic.get(left);
+ bool rfull = bigpic.get(right);
+ if (lfull)
+ ret = right;
+ else if (rfull)
+ ret = left;
+ else
+ ret = rand()%2 ? left : right;
+ }
+ return ret;
+}
+
+
+int main (int argc, char** args) {
+ bin::init();
+ sbit::init();
+ freopen( "log", "w", stdout );
+ Peer peers[NUMPEERS];
+ int numpack = 0;
+ int coll_layer[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
+ for(int i=0; i<NUMPEERS; i++)
+ peers[i].peerno = i;
+ while ( ! bigpic.get(bin(WIDTH,0)) ) {
+ for(int i=0; i<NUMPEERS; i++)
+ peers[i].jump();
+ for(int i=0; i<NUMPEERS; i++)
+ if (peers[i].packets.size()>=QUEUE_LEN) { // round trip, acks reach senders
+ bin packet = peers[i].packets.front();
+ peers[i].packets.pop_front();
+ if (packet==0) continue;
+ bool collision = bigpic.get(packet);
+ bin round = bigpic.set(packet);
+ printf("peer %i arrived %x filled %i coll %i\n",
+ i,packet.offset(),round.layer(),collision);
+ for(int j=0; j<NUMPEERS; j++)
+ peers[j].map |= round;
+ peers[i].ack(packet);
+ numpack++;
+ if (collision) {
+ peers[i].hint(random_descend());
+ coll_layer[round.layer()]++;
+ }
+ /*{ // update with the big picture
+ feye update(bigpic,packet); // current focus is unknown
+ update.refocus(peers[i].focus);
+ peers[i].map |= update;
+ }*/
+ }
+ }
+ printf("%i useful, %i total plus the tail\n",1<<WIDTH,numpack);
+ for(int l=0;l<32; l++)
+ printf("%i ",coll_layer[l]);
+ printf("\n");
+}
+
--- /dev/null
+/*
+ * congctrl.cpp
+ * p2tp
+ *
+ * Created by Victor Grishchenko on 4/14/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include "p2tp.h"
+#include <glog/logging.h>
+
+using namespace p2tp;
+
+CongestionControl::CongestionControl () {
+ state_ = SLOW_START_STATE;
+ cwnd_ = 1;
+ cainc_ = 0;
+ ssthresh_ = 100;
+ rtt_avg_ = 0;
+ dev_avg_ = 0;
+ peer_cwnd_ = 0;
+ data_ins_ = 0;
+ last_arrival_ = 0;
+ rate_ = TINT_1SEC/10;
+}
+
+
+void CongestionControl::RttSample (tint rtt) {
+ if (rtt_avg_>0) {
+ rtt_avg_ = (rtt_avg_*7 + rtt) >> 3; // affected by reordering
+ dev_avg_ = (dev_avg_*7 + ::abs(rtt-rtt_avg_)) >> 3;
+ } else {
+ rtt_avg_ = rtt;
+ dev_avg_ = rtt>>3;
+ }
+ DLOG(INFO)<<"sample "<<rtt<<" rtt "<<rtt_avg_<<" dev "<<dev_avg_;
+}
+
+
+void CongestionControl::OnCongestionEvent (CongCtrlEvents ev) {
+ switch (ev) {
+ case LOSS_EV:
+ cwnd_ /= 2;
+ state_ = CONG_AVOID_STATE;
+ break;
+ case ACK_EV:
+ if (state_==SLOW_START_STATE) {
+ cwnd_++;
+ if (cwnd_>=ssthresh_)
+ state_ = CONG_AVOID_STATE;
+ } else if (state_==CONG_AVOID_STATE) {
+ cainc_++;
+ if (cainc_>=cwnd_) {
+ cainc_ = 0;
+ cwnd_++;
+ }
+ }
+ break;
+ case DATA_EV:
+ tint interarrival = last_arrival_ ?
+ Datagram::now - last_arrival_ :
+ rtt_avg_; // starting est. corresp. cwnd==1
+ last_arrival_ = Datagram::now;
+ if (rate_)
+ rate_ = ( rate_*3 + interarrival ) / 4;
+ else
+ rate_ = interarrival;
+ break;
+ }
+ DLOG(INFO)<<"peer irr "<<rate_<<" pcwnd "<<peer_cwnd();
+}
+
+int CongestionControl::peer_cwnd () const {
+ if (!rate_)
+ return 0;
+ int pc = rtt_avg_ / rate_;
+ if ( rtt_avg_%rate_ > rate_/2 ) // too many /
+ pc++;
+ return pc;
+}
+
+int CongestionControl::peer_bps () const {
+ return 1024 * TINT_1SEC / rate_;
+}
--- /dev/null
+/*
+ * datagram.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/9/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include <arpa/inet.h>
+#include <glog/logging.h>
+#include "datagram.h"
+
+namespace p2tp {
+
+tint Datagram::now = Datagram::Time();
+
+int Datagram::Send () {
+ int r = sendto(sock,buf+offset,length-offset,0,
+ (struct sockaddr*)&(addr),sizeof(struct sockaddr_in));
+ offset=0;
+ length=0;
+ now = Time();
+ return r;
+}
+
+int Datagram::Recv () {
+ socklen_t addrlen = sizeof(struct sockaddr_in);
+ offset = 0;
+ length = recvfrom (sock, buf, MAXDGRAMSZ, 0,
+ (struct sockaddr*)&(addr), &addrlen);
+ if (length<0)
+ PLOG(ERROR)<<"on recv";
+ now = Time();
+ return length;
+}
+
+
+int Datagram::Wait (int sockcnt, int* sockets, tint usec) {
+ LOG(INFO)<<"waiting for "<<sockcnt;
+ struct timeval timeout;
+ timeout.tv_sec = usec/SEC;
+ timeout.tv_usec = usec%SEC;
+ int max_sock_fd = 0;
+ fd_set bases, err;
+ FD_ZERO(&bases);
+ FD_ZERO(&err);
+ for(int i=0; i<sockcnt; i++) {
+ FD_SET(sockets[i],&bases);
+ FD_SET(sockets[i],&err);
+ if (sockets[i]>max_sock_fd)
+ max_sock_fd = sockets[i];
+ }
+ int sel = select(max_sock_fd+1, &bases, NULL, &err, &timeout);
+ if (sel>0) {
+ for (int i=0; i<=sockcnt; i++)
+ if (FD_ISSET(sockets[i],&bases))
+ return sockets[i];
+ } else if (sel<0)
+ PLOG(ERROR)<<"select fails";
+ return -1;
+}
+
+tint Datagram::Time () {
+ struct timeval t;
+ gettimeofday(&t,NULL);
+ tint ret;
+ ret = t.tv_sec;
+ ret *= 1000000;
+ ret += t.tv_usec;
+ //DLOG(INFO)<<"now is "<<ret;
+ return now=ret;
+}
+
+int Datagram::Bind (int portno) {
+ struct sockaddr_in addr;
+ int fd, len = sizeof(struct sockaddr_in),
+ sndbuf=1<<20, rcvbuf=1<<20;
+ if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ PLOG(ERROR)<<"socket fails";
+ return -1;
+ }
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
+ return -2;
+ if ( setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) < 0 ) {
+ PLOG(ERROR)<<"setsockopt fails";
+ return -3;
+ }
+ if ( setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) < 0 ) {
+ PLOG(ERROR)<<"setsockopt2 fails";
+ return -3;
+ }
+ printf("BUFS: %i %i\n",sndbuf,rcvbuf);
+ memset(&addr, 0, sizeof(struct sockaddr_in));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(portno);
+ addr.sin_addr.s_addr = INADDR_ANY;
+ if (::bind(fd, (struct sockaddr*)&addr, len) != 0) {
+ PLOG(ERROR)<<"bind fails";
+ return -4;
+ }
+ return fd;
+}
+
+void Datagram::Close (int sock) { // remove from fd_set
+ if (::close(sock)!=0)
+ PLOG(ERROR)<<"on closing a socket";
+}
+
+
+std::string sock2str (struct sockaddr_in addr) {
+ char ipch[32];
+ inet_ntop(AF_INET,&(addr.sin_addr),ipch,32);
+ sprintf(ipch+strlen(ipch),":%i",ntohs(addr.sin_port));
+ return std::string(ipch);
+}
+
+
+std::string Datagram::to_string () const { // TODO: pretty-print P2TP
+ std::string addrs = sock2str(addr);
+ char hex[MAXDGRAMSZ*2];
+ for(int i=offset; i<length; i++)
+ sprintf(hex+i*2,"%02x",buf[i]);
+ std::string hexs(hex+offset*2,(length-offset)*2);
+ return addrs + '\t' + hexs;
+}
+
+}
--- /dev/null
+/*
+ * datagram.h
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/9/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#ifndef DATAGRAM_H
+#define DATAGRAM_H
+#include <stdint.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+//#include <sys/mman.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string>
+#include "hashtree.h"
+
+namespace p2tp {
+
+typedef int64_t tint;
+#define SEC ((tint)1000000)
+#define MSEC ((tint)1000)
+#define uSEC ((tint)1)
+#define NEVER ((tint)0x7fffffffffffffffLL)
+#define MAXDGRAMSZ 1400
+
+struct Datagram {
+ struct sockaddr_in addr;
+ int sock;
+ int offset, length;
+ uint8_t buf[MAXDGRAMSZ];
+
+ static int Bind(int port);
+ static void Close(int port);
+ static tint Time();
+ static int Wait (int sockcnt, int* sockets, tint usec=0);
+ static tint now;
+
+ Datagram (int socket, struct sockaddr_in& addr_) : addr(addr_), offset(0),
+ length(0), sock(socket) {}
+ Datagram (int socket) : offset(0), length(0), sock(socket) {
+ memset(&addr,0,sizeof(struct sockaddr_in));
+ }
+
+ int space () const { return MAXDGRAMSZ-length; }
+ int size() const { return length-offset; }
+ std::string str() const { return std::string((char*)buf+offset,size()); }
+
+ int Push (const uint8_t* data, int l) { // scatter-gather one day
+ int toc = l<space() ? l : space();
+ memcpy(buf+length,data,toc);
+ length += toc;
+ return toc;
+ }
+ int Pull (uint8_t** data, int l) {
+ int toc = l<size() ? l : size();
+ //memcpy(data,buf+offset,toc);
+ *data = buf+offset;
+ offset += toc;
+ return toc;
+ }
+
+ int Send ();
+ int Recv ();
+ const struct sockaddr_in& address() const { return addr; }
+ void Clear() { offset=length=0; }
+
+ void PushString (std::string str) {
+ Push((uint8_t*)str.c_str(),str.size());
+ }
+ void Push8 (uint8_t b) {
+ buf[length++] = b;
+ }
+ void Push16 (uint16_t w) {
+ *(uint16_t*)(buf+length) = htons(w);
+ length+=2;
+ }
+ void Push32 (uint32_t i) {
+ *(uint32_t*)(buf+length) = htonl(i);
+ length+=4;
+ }
+ void Push64 (uint64_t l) {
+ *(uint32_t*)(buf+length) = htonl((uint32_t)(l>>32));
+ *(uint32_t*)(buf+length+4) = htonl((uint32_t)(l&0xffffffff));
+ length+=8;
+ }
+ void PushHash (const Sha1Hash& hash) {
+ Push(hash.bits, Sha1Hash::SIZE);
+ }
+
+ uint8_t Pull8() {
+ if (size()<1) return 0;
+ return buf[offset++];
+ }
+ uint16_t Pull16() {
+ if (size()<2) return 0;
+ offset+=2;
+ return ntohs(*(uint16_t*)(buf+offset-2));
+ }
+ uint32_t Pull32() {
+ if (size()<4) return 0;
+ uint32_t i = ntohl(*(uint32_t*)(buf+offset));
+ offset+=4;
+ return i;
+ }
+ uint64_t Pull64() {
+ if (size()<8) return 0;
+ uint64_t l = ntohl(*(uint32_t*)(buf+offset));
+ l<<=32;
+ l |= ntohl(*(uint32_t*)(buf+offset+4));
+ offset+=8;
+ return l;
+ }
+ Sha1Hash PullHash() {
+ if (size()<Sha1Hash::SIZE) return Sha1Hash::ZERO;
+ offset += Sha1Hash::SIZE;
+ return Sha1Hash(false,(char*)buf+offset-Sha1Hash::SIZE);
+ }
+ std::string to_string () const ;
+
+};
+
+std::string sock2str (struct sockaddr_in addr);
+
+}
+
+#endif
--- /dev/null
+#!/bin/bash
+
+for tst in `ls tests/*test`; do
+ $tst
+done
--- /dev/null
+* MISSION : <1 min
+ * multiparty transport protocol
+ * diverse usecases (files, VoD, live)
+ * for embedded 24x7 use
+* OBJECTIVES : 1 min
+ * light footprint (embedded)
+ * NAT penetration
+ * non-intrusive congestion control
+ * genericity
+* METHOD : 1 min
+ * UDP
+ * LEDBAT (Mugurel)
+ * common messages
+ * Merkle hashes
+ * binmaps
+* STATUS : 5 min
+ * state machine: implemented
+ * congestion control: implemented
+ * binmaps: implemented
+ * streaming: in progress
+ * todo: test, integrate, test
+* HELP NEEDED : 4 min
+ * testing
+ * security
+ * robustness
+ * usecases
+
--- /dev/null
+
+ The Generic Multiparty Transport Protocol (P2TP)
+
+Status of this Memo
+
+ This is a work-in-progress memo.
+
+Copyright Notice
+
+Abstract
+
+ This text is intended to explain inner workings of the P2TP
+ (peer-to-peer transport protocol), which is currently work in
+ progress. P2TP was devised to simplify and unify the peer-to-peer/
+ peer-assisted/ multi-source download domain with the long-term
+ practical goal of embedding it into mass software and hardware.
+
+
+Table of Contents
+
+ 1. Requirements notation
+ 2. Introduction
+ 3. Design goals
+ 4. P2TP subsystems and design choices
+ 4.1. The atomic datagram principle
+ 4.2. Handshake and multiplexing
+ 4.3. Data integrity and on-demand Merkle hashes
+ 4.4. Generic acknowledgements
+ 4.5. Peer exchange and NAT hole punching
+ 4.6. Congestion control
+ 4.7. Hints and piece picking
+ 5. Security Considerations
+ 6. Pending issues
+ 7. Normative References
+ Author's address
+
+1. Requirements notation
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in
+ this document are to be interpreted as described in [RFC2119].
+
+
+2. Introduction
+
+ Historically, the Internet was based on end-to-end unicast
+ communication, mostly carried out using TCP. Still, the need for
+ distribution of heavyweight content to masses of users persisted
+ and, considering the failure of multicast, was addressed by
+ different technologies, which ultimately boiled down to maintaining
+ and coordinating distributed replicas. On one hand, downloading
+ from a nearby well-provisioned replica is somewhat faster and/or
+ cheaper; on the other hand, it requires to coordinate multiple
+ parties (the data source, mirrors/CDN sites/peers, consumers). As
+ the Internet progresses to richer and richer content, the overhead
+ of peer/replica coordination becomes dwarfed by the mass of the
+ download itself. Thus, the niche for multiparty transfers expands.
+ Still, current, relevant technologies are tightly coupled to a
+ single usecase or even infrastructure of a particular corporation.
+ Hence, the focus of P2TP is to find the simplest solution involving
+ the minimum set of primitives, still being sufficient to implement
+ all the targeted usecases (see Table 1) and suitable for use in
+ general-purpose software and hardware (i.e. a web browser or a
+ set-top box).
+
+ | mirror-based peer-assisted peer-to-peer
+ ------+----------------------------------------------------
+ data | SunSITE CacheLogic VelociX BitTorrent
+ VoD | YouTube Azureus(+seedboxes) SwarmPlayer
+ live | Akamai Str. Octoshape, Joost PPlive
+ TABLE 1. Usecases.
+
+
+3. Design goals
+
+ The five design goals for the protocol are:
+
+ 1. Embeddable kernel-ready protocol.
+ 2. Embrace both real-time streaming and download.
+ 3. Short warm-up times.
+ 4. Transparent NAT traversal.
+ 5. Non-intrusive congestion control.
+
+ Later in the draft, the objectives are referenced as (1)-(5).
+
+ The goal of embedding (1) means that the protocol must be ready to
+ function as a regular transport protocol inside a set-top box,
+ mobile device, a browser or even in the kernel space. Thus, the
+ protocol must have light footprint, even less than TCP due to the
+ necessity to support numerous ongoing connections as well as to
+ constantly probe the network for new possibilities. The practical
+ overhead for TCP is estimated at 40KB per connection [HTTP1MLN]. We
+ aim at 1KB per peer connected. Also, the amount of code necessary
+ to make a basic implementation must be limited to 10KLoC of C.
+ Otherwise, besides the resource considerations, maintaining and
+ auditing the code might become prohibitively expensive.
+
+ The support for all three basic usecases of real-time streaming,
+ in-order download and out-of-order download (2) is necessary for
+ the manifested goal of THE multiparty transport protocol as no
+ single usecase dominates over the others.
+
+ The objective of short warm-up times (3) is the matter of end-user
+ experience; the playback must start as soon as possible. Thus
+ at the transport layer any unnecessary initialization roundtrips
+ and warm-up cycles must be eliminated.
+
+ Transparent NAT traversal (4) is absolutely necessary as at least
+ 60% of today's users are hidden behind NATs. NATs severely affect
+ conection patterns in P2P networks thus impacting performance and
+ fairness [MOLNAT,LUCNAT].
+
+ Custom non-intrusive congestion control (5) is a must-have as
+ interference of downloads with the regular traffic is the main
+ incentive for the end-user to shut down the program. Advanced
+ bandwidth-scavenging congestion control techniques might
+ potentially lead to incentiveless seeding (see Sec. 4.6). Besides
+ that, behavior patterns of swarming download applications are so
+ special and bandwidth consumption is so high that custom congestion
+ control makes sense in the general case.
+
+
+4. P2TP subsystems and design choices
+
+ To large extent, P2TP design is defined by the cornerstone decision
+ to get rid of TCP and not to reinvent any TCP-like transports on
+ top of UDP or otherwise. The requirements (1), (4), (5) make TCP a
+ bad choice due to its high per-connection footprint, complex and
+ less reliable NAT traversal and fixed predefined congestion control
+ algorithms. Besides that, an important consideration is that no
+ block of TCP functionality turns out to be useful for the general
+ case of swarming downloads. Namely,
+ 1. in-order delivery is less useful as peer-to-peer protocols
+ often employ out-of-order delivery themselves and in either case
+ out-of-order data can still be stored;
+ 2. reliable delivery/retransmissions are less useful because
+ the same data might be requested from different sources; as
+ in-order delivery is not necessary, packet losses might be
+ patched up lazily, without stopping the flow of data;
+ 3. flow control is not necessary as the receiver is much less
+ likely to be saturated with the data and even if so, that
+ situation is perfectly detected by the congestion control
+ 4. TCP congestion control is less useful as custom congestion
+ control is often needed.
+ In general, TCP is built and optimized for a different usecase than
+ we have with swarmed downloads. Thus, the choice is to make a
+ UDP-based transport, possibly reserving HTTP tonneling as an
+ universal fallback. Further, instead of reimplementing TCP we
+ create a datagram-centered protocol, completely dropping the
+ sequential data stream abstraction. Ripping of unnecessary features
+ of TCP makes it easier both to implement the protocol and to check
+ it for vulnerabilities; numerous TCP vulnerabilities were caused by
+ complexity of the protocol's state machine.
+ Pursuing the maxim of making the things as simple as possible but
+ not simpler we drop all the transmission's metadata except for the
+ content's root hash (compare to e.g. .torrent files in BitTorrent).
+ To avoid the usual layering of positive/negative acknowledgement
+ mechanisms we design a scale-invariant acknowledgement system (Sec
+ 4.4). The system allows for aggregation and variable level of
+ detail in requesting, announcing and acknowledging data, in-order
+ or out-of-order with equal ease.
+ Besides the protocol's footprint, we also target the size of a
+ minimal useful interaction; every single datagram might be checked
+ for data integrity, consumed or relayed immediately once received.
+
+4.1. The atomic datagram principle
+
+ Ideally, every datagram sent must be independent of other
+ datagrams, so each datagram SHOULD be processed separately and a
+ loss of one datagram MUST NOT disrupt the flow. Thus, a datagram
+ carries zero or more messages, and neither messages nor message
+ interdependencies must span over multiple datagrams. In particular,
+ Merkle hashes necessary for verifying data integrity are put into
+ the same datagram as the data (Sec. 4.3). As a general rule, if
+ some additional data is still needed to process a message within a
+ datagram, the message SHOULD be dropped.
+
+ Each datagram starts with four bytes corresponding to the receiving
+ channel number (Sec. 4.2). Each message within a datagram has fixed
+ length, depending on the type of the message. The first byte of a
+ message denotes its type. Integers are serialized in the network
+ (big-endian) byte order. No variable-length messages, free-form
+ text or JSON object allowed. E.g. an acknowledgement message (Sec
+ 4.4) having message type of 2 and payload of a four-byte integer 1
+ might be written in hex as: "02 00000001". Later in the document, a
+ hex-like two char per byte notation is used to represent message
+ formats.
+
+4.2. Handshake and multiplexing
+
+ For the sake of simplicity, one transfer always deals with one file
+ only. Retrieval of large collections of files is done by retrieving
+ a directory list file and then recursively retrieving files, which
+ might also be directory lists. To distinguish different transfers
+ to/from the same peer the protocol introduces an additional layer
+ of multiplexing, the channels. "Channels" loosely correspond to
+ TCP connections; "content" of a single "channel" is a single file.
+ Channel is established with a handshake. To start a handshake, the
+ initiating peer needs to know (1) IP address (2) UDP port and
+ (3) SHA1 root hash of the content (Sec. 4.3). The handshake is make
+ by a HANDSHAKE message, whose only payload is a channel number.
+ HANDSHAKE message type is 0. The initiating handshake must be
+ followed with the transfer's root hash.
+
+ Initiator sends an initiating datagram to a peer:
+ 00000000 00 00000011 04 FFFFFFFF
+ 1234123412341234123412341234123412341234
+ (to unknown channel, handshake from channel 0x11, initiating a
+ transfer for a file with a root hash 123...1234)
+ Peer's response datagram:
+ 00000011 00 00000022
+ (peer to the initiator: use channel number 0x22 for this transfer)
+ At this point, the initiator knows that the peer really responds;
+ for that purpose channel ids MUST be random enough to prevent easy
+ guessing. So, the third datagram of a handshake MAY already contain
+ some heavy payload. To minimize the number of initialization
+ roundtrips, the first two datagrams MAY also contain some minor
+ payload, e.g. a couple of ACK messages roughly indicating the
+ current progress of a peer.
+ 00000022
+ (this is a simple zero-payload keepalive datagram; at this point
+ both peers have the proof they really talk to each other; three-way
+ handshake is complete)
+
+ In general, no error codes or responses are used in the protocol;
+ absence of any response indicates an error. Invalid messages are
+ discarded.
+
+ Simple NAT hole punching [SNP] introduces the scenario when both
+ parties of the handshake are initiators. To avoid creation of two
+ transfers in the case both initiating datagrams get through, both
+ peers must then act as responding peers. Thus, once an initiating
+ datagram is sent and another initiating "counter"-datagram is
+ received, the initiating peer sends a response datagram with the
+ same channel id as in the outstanding initiating datagram.
+
+
+4.3. Generic acknowledgements
+
+ The generci acknowledgements came out of the need to simplify the
+ data addressing/requesting/acknowledging mechanics, which tends
+ to become overly complex and multilayered with the conventional
+ approach. Take BitTorrent+TCP tandem for example:
+
+ 1. Its highest-level unit is a ``torrent'', physically a byte range
+ resulting from concatenation of one or many content files.
+ 2. A torrent is divided into ``pieces'', typically about a thousand
+ of them. Pieces are used to communicate own progress to other
+ peers. Pieces are also basic data integrity units, as the torrent's
+ metadata includes SHA1 hash for every piece.
+ 3. The actual data transfers are requested and made in 16KByte
+ units, named blocks or chunks.
+ 4. The ``basic'' data unit is of course a byte of content.
+ 5. Still, one layer lower, TCP also operates with bytes and byte
+ offsets which are totally different from the torrent's bytes and
+ offsets as TCP considers cumulative byte offsets for all content
+ sent by a connection, both data, metadata and commands.
+ 6. Finally, one more layer lower IP transfers independent datagrams
+ (typically around a kilobyte), which TCP then reassembles into
+ continuous streams.
+
+ Obviously, the addressing schemes need mappings; from piece number
+ and block to file(s) and offset(s) to TCP sequence numbers to the
+ actual packets and the other way around. Lots of complexity is
+ introduced by mismatch of bounds: packet bounds are different from
+ file, block or hash/piece bounds. The picture is typical for a code
+ which was historically layered.
+
+ To simplify this aspect, we employ a generic content addressing
+ scheme based on binary intervals (shortcutted "bins"). The base
+ interval is 1KB "packet", the top interval is the complete file.
+ Till Sec. 4.4.1 any file is considered to be 2^k bytes long.
+ The binary tree of intervals is simple, well-understood, correlates
+ well with machine representation of integers and the structure of
+ Merkle hashes (Sec. 4.4). A novel addition to the classical scheme
+ are "bin numbers", a scheme of numbering binary intervals which
+ lays them out into a vector nicely. Bin numbering is done in the
+ order of interval's right bound, then in the order of length, both
+ ascending:
+
+ 15
+ 7 14
+ 3 6 10 13
+ 1 2 4 5 8 9 11 12
+
+ Zero number stands for empty interval. The important feature is
+ that for any file, numbers of filled intervals (i.e. intervals not
+ extending past the end of the file) represent a solid range. For
+ example, in a 7KB file intervals 1-11 are filled (1KB is the basic
+ unit). In general, this numbering system allows to work with
+ simpler datastructures, use arrays instead of binary trees in most
+ cases. As a minor convenience, it also allows to use one integer
+ instead of two to denote an interval.
+
+ Back to the acknowledgement message. An ACK message (type 2) states
+ that the sending peer obtained the specified bin and successfully
+ checked its integrity:
+
+ 02 00000007
+ (got/checked first four kilobytes of a file/stream)
+
+ For keeping the state information, an implementation MAY use the
+ "binmap" datastructure, which is a hybrid of a bitmap and a binary
+ tree, discussed in detail in [BINMAP].
+
+
+4.4. Data integrity and on-demand Merkle hashes
+
+ The integrity checking scheme is unified both for the usecases of
+ download and streaming. Also, it works down to the level of a
+ single datagram by employing Merkle hash trees [MERKLE]. Peers
+ receive chains of uncle hashes as required, just in time to check
+ the incoming data. As the file metadata is restricted to a single
+ hash, hashes also play the role of proving file size to newcomer
+ peers. Those functionalities heavily depend on the concept of peak
+ hashes, discussed in Sec. 4.4.1. Any specifics related to the cases
+ of file download and streaming is discussed in Sec. 4.4.2, 4.4.3
+ respectively.
+
+ Here, we discuss the common part of the workflow. As a general
+ rule, the sender SHOULD prepend data with hashes which are
+ necessary for verifying that data, no more, no less. While some
+ optimistic optimizations are possible, the receiver SHOULD drop
+ data if it is impossible to verify it. Before sending a kilobyte
+ packet of data to the reciever, the sender inspects the receiver's
+ previous acknowledgements to derive which hashes the receiver
+ already has. I.e. if the receiver had acknowledged bin 8, it must
+ already have uncle hashes 9, 13 and 7 for the example case of
+ 7KB-long file. That is because those hashes are necessary to check
+ the packet against the root hash. Then, hashes 10, 14 and 15 must
+ be also known as they are calculated in the process of checking the
+ uncle hash chain. Hence, to send the packet 11 (i.e. the last, 7th
+ kilobyte of data), the sender needs to prepend no hashes, as hash
+ 12 covers an empty range and thus set to zeros, and the hash 13 is
+ already known to the receiver. In less lucky cases, the sender MUST
+ put into the datagram the chain of uncle hashes necessary for
+ verification of the packet, always before the data message itself,
+ i.e.:
+
+ 04 00000007 F01234567890ABCDEF1234567890ABCDEF123456
+ 04 0000000A 01234567890ABCDEF1234567890ABCDEF1234567
+ (uncle hashes for the packet 11, the trivial hash for 12 omitted)
+ The sender MAY optimistically skip hashes which were sent out in
+ previous (still unacknowledged) datagrams.
+ This way, the receiver incrementally builds the Merkle tree, as it
+ is necessary for data validation.
+
+
+4.4.1. Peak hashes
+
+ Download/streaming unification, also proving of file size both
+ depend on the use of peak hashes. Formally, peak hashes are hashes
+ defined over filled bins, whose parent hashes are defined over
+ incomplete (not filled) bins. Practically, we use peak hashes to
+ cover the data range with logarithmical number of hashes, so each
+ hash is defined over a "round" 2^k interval.
+
+ 04 00000007 1234567890ABCDEF1234567890ABCDEF12345678
+ 04 0000000A 234567890ABCDEF1234567890ABCDEF123456789
+ 04 0000000B 34567890ABCDEF1234567890ABCDEF1234567890
+ (this sequence of peak hashes proves that a file is 7KB long)
+
+
+4.4.2. Hash trees for files
+
+ In the case of static file download, as it was mentioned, a
+ transfer is bootstrapped with the root hash. The root hash covers
+ the entire 2^30KB data range, i.e. a terabyte. Every hash in the
+ tree is defined in the usual way, as a SHA-1 hash of a
+ concatenation of two lower-level SHA-1 hashes, corresponding to
+ left and right data half-ranges resp. For example,
+ hash_2 = SHA1 (hash_1+hash_2)
+ where + stands for concatenation and hash_i stands for Merkle hash
+ of the bin number i. Obviously, that does not hold for the
+ base-layer hashes, which are normal SHA-1 hashes over 1KB data
+ ranges ("packets"), except probably for the last packet in the
+ file, which might have less than 1KB of data. Hashes over empty
+ intervals are set to zeros.
+
+ Lemma. Peak hashes could be checked against the root hash.
+ Proof. (a) Any peak hash is always the left sibling. Otherwise, be
+ it the right sibling, its left neighbor/sibling must also be
+ defined over a filled bin, so their parent is also defined over a
+ filled bin, contradiction. (b) For the rightmost peak hash, its
+ right sibling is zero. (c) For any peak hash, its right sibling
+ might be calculated using peak hashes to the left and zeros for
+ empty bins. (d) Once the right sibling of the leftmost peak hash
+ is calculated, its parent might be calculated. (e) Once that parent
+ is calculated, we might trivially get to the root hash by
+ concatenating the hash with zeros and hashing it repeatedly.
+
+ Informally, the Lemma might be expressed as follows: peak hashes
+ cover all data, so the remaining hashes are either trivial (zeros)
+ or might be calculated from peak hashes and zero hashes.
+
+ Thus, once a peer gets peak hashes and checks them against the
+ root hash, it learns the file size and it also gets practical
+ anchors for building uncle chains during the transmission (as the
+ root hash is too high in the sky). A newcomer peer signals it
+ already has peak hashes by acknowledging an empty bin:
+ 02 00000000
+ Otherwise, the first of the senders bootstraps him with all the
+ peak hashes.
+
+
+4.4.3. Hash trees for streams
+
+ In the case of live streaming a transfer is bootstrapped with a
+ public key instead of a root hash, as the root hash is undefined
+ or, more precisely, transient, as long as new data keeps coming.
+ Stream/download unification is achieved by sending signed peak
+ hashes on-demand, ahead of the actual data. Similarly to the
+ previous case, the sender mightuse acknowledgements to derive which
+ data range the receiver has peak hashes for and to prepend the data
+ and the uncle hashes with the necessary (signed) peak hashes.
+ Except for the fact that the set of peak hashes changes with the
+ time, other parts of the algorithm work as described in 4.4.3 As we
+ see, in both cases data length is not known on advance, bit derived
+ on-the-go from the peak hashes. Suppose, our 7KB stream extended to
+ another kilobyte. Thus, now hash 15 becomes the only peak hash,
+ eating hashes 7, 10, 11 and the source sends out a signed peak hash
+ message (type 7) to announce the fact:
+
+ 07 0000000F 1234567890ABCDEF1234567890ABCDEF12345678 SOME-SIGN-HERE
+
+
+4.5. Peer exchange and NAT hole punching
+
+ Peer exchange messages are common for many peer-to-peer protocols.
+ By exchanging peer IP addresses in gossip fashion, the central
+ coordinating entities (trackers) might be releived of unnecessary
+ work. Following the example of BitTorrent, P2TP features two types
+ of PEX messages: "peer connected" (type 5) and "peer disconnected"
+ (type 6). Peers are represented as IPv4 address-port pairs:
+ 05 7F000000 1F40
+ (connected to 127.0.0.1:8000)
+
+ To unify peer exchange and NAT hole punching functionality, the
+ sending pattern of PEX messages is restricted. As P2TP handshake is
+ able to do simple NAT hole punching [SNHP] transparently, PEX
+ messages must be emitted in the way to facilitate that. Namely,
+ once peer A introduces peer B to peer C by sending a PEX message to
+ C, it SHOULD also send a message to B introducing C. The messages
+ MUST be within 2 seconds from each other, but MAY and better not be
+ simultaneous, leaving a gap of twice the "typical" RTT, i.e.
+ 300-600ms. The peers are supposed to initiate handshakes to each
+ other thus forming a simple NAT hole punching pattern where the
+ introducing peer effectively acts as a STUN server. Still, peers
+ MAY ignore PEX messages if uninterested in obtaining new peer or
+ because of security considerations (rate limiting) or any other
+ reason.
+
+
+4.6. Congestion control
+
+ Custom weaker-than-TCP to eliminate seeding counter-incentives
+ lazy seeding
+ further simplify by making tit-for-tat rarest-first unnecessary
+
+
+5. Security Considerations
+
+ * simplify as possible (e.g. no buffer overruns)
+ Still, resource, membership subverting [] for DDoS
+ Thus, implementation MUST at least rate-limit activity to untested
+ destinations. E.g. 4 attempts a second: million peers, 1Gb/sec,
+ amplification ratio 4.
+
+
+6. Pending issues
+
+
+6.1. Extensibility
+
+ avoid unnecessary generality
+
+6.1.2. 64-bit counters
+6.1.3. Different crypto/hashing schemes
+6.1.4. IPv6
+6.1.5. Congestion control algorithms
+
+7. Normative References
+
+
+Author's address
--- /dev/null
+#include <mmap.h>
+
+namespace p2tp {
+
+ class File {
+
+ typedef enum {EMPTY,IN_PROGRESS,DONE} status_t;
+
+ static std::vector<File*> files;
+
+ /** File descriptor. */
+ int fd;
+ /** Whether the file is completely downloaded. */
+ status_t status_;
+
+ // A map for all packets obtained and succesfully checked.
+ bins ack_out;
+ // Hinted packets.
+ bins hint_out;
+
+ HashTree hashes;
+ // History of bin retrieval.
+ bin::vec history;
+
+ // TBD
+ uint64_t options;
+
+ /** Submit a fresh file. */
+ File (int fd);
+ /** Retrieve a file. */
+ File (Sha1Hash hash, int fd);
+ /** Placeholder. */
+ File () : fd(-1), hashes(Sha1Hash::ZERO) {}
+ /** Close everything. */
+ ~File();
+
+ bool OfferHash (bin pos, const Sha1Hash& hash);
+ const Sha1Hash& root_hash () const { return hashes.root; }
+ size_t size () const { return hashes.data_size()<<10; }
+ size_t packet_size () const { return hashes.data_size(); }
+ status_t status () const { return status_; }
+
+ static File* find (const Sha1Hash& hash);
+ static File* file (int fd) { return fd<files.size() ? files[fd] : NULL; }
+
+ friend int Open (const char* filename);
+ friend int Open (const Sha1Hash& hash, const char* filename);
+ friend void Close (int fdes);
+ friend class Channel;
+
+
+ int Open (const char* filename) ;
+ int Open (const Sha1Hash& root_hash, const char* filename);
+ size_t file_size (int fd);
+ void Close (int fid) ;
+
+ void* Map(bin64_t bin);
+ void UnMap(void*);
+
+ };
+
+};
--- /dev/null
+/*
+ * hashtree.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/6/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+
+#include "hashtree.h"
+#include <openssl/sha.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+using namespace std;
+
+#define HASHSZ 20
+const size_t Sha1Hash::SIZE = HASHSZ;
+const Sha1Hash Sha1Hash::ZERO = Sha1Hash();
+
+Sha1Hash::Sha1Hash(const Sha1Hash& left, const Sha1Hash& right) {
+ uint8_t data[HASHSZ*2];
+ memcpy(data,left.bits,SIZE);
+ memcpy(data+SIZE,right.bits,SIZE);
+ SHA1(data,SIZE*2,bits);
+}
+
+Sha1Hash::Sha1Hash(const uint8_t* data, size_t length) {
+ SHA1(data,length,bits);
+}
+
+Sha1Hash::Sha1Hash(const char* str) {
+ SHA1((const unsigned char*)str,strlen(str),bits);
+}
+
+Sha1Hash::Sha1Hash(bool hex, const char* hash) {
+ assert(!hex);
+ memcpy(bits,hash,SIZE);
+}
+
+string Sha1Hash::hex() {
+ char hex[HASHSZ*2+1];
+ for(int i=0; i<HASHSZ; i++)
+ sprintf(hex+i*2, "%02x", bits[i]);
+ return string(hex,HASHSZ*2);
+}
+
+
+
+/*void HashTree::expand (bin tolen) {
+ if (bits)
+ munmap(bits,length*HASHSZ);
+ length = tolen;
+ status.resize(length);
+ bits = (Sha1Hash*) mmap(NULL,length*HASHSZ,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
+}*/
+
+
+Sha1Hash HashTree::deriveRoot () {
+ int i = peaks.size()-1;
+ bin p = peaks[i].first;
+ Sha1Hash hash = peaks[i].second;
+ i--;
+ while (p<bin::ALL) {
+ if (p.is_left()) {
+ p = p.parent();
+ hash = Sha1Hash(hash,Sha1Hash::ZERO);
+ } else {
+ if (i<0 || peaks[i].first!=p.sibling())
+ return Sha1Hash::ZERO;
+ hash = Sha1Hash(peaks[i].second,hash);
+ p = p.parent();
+ i--;
+ }
+ }
+ return hash;
+}
+
+HashTree::HashTree (int fd) {
+ //fd = open(filename,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (fd<0) return;
+ struct stat st;
+ fstat(fd, &st);
+ length = (st.st_size>>10) + (st.st_size%1024 ? 1 : 0);
+ mass = bin::lenpeak(length); // incorrect
+ bits.resize(mass+1);
+ status.resize(mass+1);
+ uint8_t buf[1024];
+ for(bin i=1; i<=mass; i++)
+ if (i.layer()) {
+ bits[i] = Sha1Hash(bits[i.left()],bits[i.right()]);
+ } else {
+ int len = pread(fd,buf,1024,i.offset()<<10);
+ bits[i] = Sha1Hash(buf,len);
+ }
+ //close(fd);
+ bin::vec p = bin::peaks(length);
+ while(p.size()) {
+ peaks.push_back(binhash(p.front(),bits[p.front()]));
+ p.pop_front();
+ }
+ root = deriveRoot();
+}
+
+HashTree::HashTree (const Sha1Hash& with_root) : root(with_root), length(0), mass(0) {
+ // recover the partially filled hash file
+ // first, size
+ // then, peaks
+ // works? then offer the rest
+}
+
+HashTree::~HashTree () {
+ close(fd);
+}
+
+HashTree::hashres_t HashTree::offerPeak (bin pos, Sha1Hash hash) {
+ if (bin(pos+1).layer())
+ return REJECT;
+ if (bin::all1(pos))
+ peaks.clear();
+ peaks.push_back(binhash(pos,hash));
+ if (deriveRoot()==root) { // bingo
+ mass = peaks.back().first;
+ length = mass.length();
+ status.resize(mass+1);
+ bits.resize(mass+1);
+ for(int i=0; i<peaks.size(); i++) {
+ bits[peaks[i].first] = peaks[i].second;
+ status[peaks[i].first] = true;
+ }
+ return PEAK_ACCEPT;
+ } else
+ return pos.layer() ? DUNNO : REJECT;
+}
+
+HashTree::hashres_t HashTree::offer (bin pos, const Sha1Hash& hash) {
+ if (!length) // only peak hashes are accepted at this point
+ return offerPeak(pos,hash);
+ if (pos>mass)
+ return REJECT;
+ if (status[pos])
+ return bits[pos]==hash ? ACCEPT : REJECT;
+ bits[pos] = hash;
+ // walk to the nearest proven hash
+ if (bits[pos.sibling()]==Sha1Hash::ZERO)
+ return DUNNO;
+ bin p = pos.parent();
+ while (!status[p]) {
+ bits[p] = Sha1Hash(bits[p.left()],bits[p.right()]);
+ p = p.parent();
+ }
+ if ( bits[p] == Sha1Hash(bits[p.left()],bits[p.right()]) ) {
+ for(bin i=pos; i<p; i=i.parent())
+ status[i] = status[i.sibling()] = true;
+ return ACCEPT;
+ } else
+ return REJECT;
+
+}
+
+
--- /dev/null
+/*
+ * hashtree.h
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/6/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#ifndef P2TP_SHA1_HASH_TREE_H
+#define P2TP_SHA1_HASH_TREE_H
+#include "bin.h"
+#include <string.h>
+#include <string>
+#include <vector>
+
+struct Sha1Hash {
+ uint8_t bits[20];
+
+ Sha1Hash() { memset(bits,0,20); }
+ Sha1Hash(const Sha1Hash& left, const Sha1Hash& right);
+ Sha1Hash(const uint8_t* bits, size_t length);
+ /***/
+ Sha1Hash(const char* bits);
+ Sha1Hash(bool hex, const char* hash);
+
+ std::string hex();
+ bool operator == (const Sha1Hash& b) const
+ { return 0==memcmp(bits,b.bits,SIZE); }
+ bool operator != (const Sha1Hash& b) const { return !(*this==b); }
+
+ const static Sha1Hash ZERO;
+ const static size_t SIZE;
+};
+
+typedef std::pair<bin,Sha1Hash> binhash;
+
+struct HashTree {
+ Sha1Hash root;
+ int fd;
+ bin mass;
+ uint32_t length;
+ std::vector<bool> status;
+ std::vector<Sha1Hash> bits;
+ std::vector<binhash> peaks;
+ typedef enum { ACCEPT, DUNNO, PEAK_ACCEPT, REJECT } hashres_t;
+protected:
+ Sha1Hash deriveRoot();
+ hashres_t offerPeak (bin pos, Sha1Hash hash);
+public:
+
+ HashTree (int fd);
+ HashTree (const Sha1Hash& root);
+
+ ~HashTree ();
+
+ hashres_t offer (bin pos, const Sha1Hash& hash);
+
+ bool rooted () const { return length>0; }
+
+ const Sha1Hash& operator [] (bin i) {
+ return i<=mass ? bits[i] : Sha1Hash::ZERO;
+ }
+
+ uint32_t data_size () const { return length; }
+
+ bin data_mass () const { return mass; }
+
+ const std::vector<binhash>& peak_hashes() const { return peaks; }
+
+};
+
+#endif
--- /dev/null
+/*
+ * p2tp.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/6/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <unistd.h>
+#include <glog/logging.h>
+#include "p2tp.h"
+
+using namespace std;
+using namespace p2tp;
+
+p2tp::tint Channel::last_tick = 0;
+int Channel::MAX_REORDERING = 4;
+p2tp::tint Channel::TIMEOUT = TINT_1SEC*60;
+std::vector<Channel*> Channel::channels(1);
+std::vector<File*> File::files(4);
+int* Channel::sockets_ = (int*)malloc(40);
+int Channel::sock_count_ = 0;
+
+
+Channel::Channel (int fd_, int socket, struct sockaddr_in peer_,
+ uint32_t peer_channel_, uint64_t supports_) :
+ fd(fd_), peer(peer_), peer_channel_id(peer_channel_), ack_out(0),
+ peer_status_(File::EMPTY), socket_(socket)
+{
+ this->id = channels.size();
+ channels.push_back(this);
+ DLOG(INFO)<<"new channel "<<id<<" "<<*this;
+}
+
+
+Channel::~Channel () {
+ channels[id] = NULL;
+}
+
+
+File::File (int _fd) : fd(_fd), status_(DONE), hashes(_fd)
+{
+ bin::vec peaks = bin::peaks(hashes.data_size());
+ history.insert(history.end(),peaks.begin(),peaks.end());
+ for(bin::vec::iterator i=peaks.begin(); i!=peaks.end(); i++)
+ ack_out.set(*i);
+}
+
+File::File (Sha1Hash hash, int _fd) : hashes(hash), fd(_fd), status_(EMPTY) {
+ // TODO resubmit data
+}
+
+File::~File() {
+ if (fd>0) ::close(fd);
+}
+
+
+bool File::OfferHash (bin pos, const Sha1Hash& hash) {
+ HashTree::hashres_t res = hashes.offer(pos,hash);
+ if (res==HashTree::PEAK_ACCEPT) { // file size is finally known
+ ftruncate(fd, size());
+ LOG(INFO)<<fd<<" file size is set to "<<size();
+ history.push_back(0);
+ status_ = IN_PROGRESS;
+ }
+ return res==HashTree::PEAK_ACCEPT || res==HashTree::ACCEPT;
+}
+
+
+File* File::find (const Sha1Hash& hash) {
+ for(vector<File*>::iterator i=files.begin(); i!=files.end(); i++)
+ if (*i && (*i)->hashes.root==hash)
+ return *i;
+ return NULL;
+}
+
+int Channel::DecodeID(int scrambled) {
+ return scrambled;
+}
+int Channel::EncodeID(int unscrambled) {
+ return unscrambled;
+}
+
+
+std::ostream& p2tp::operator << (std::ostream& os, const Channel& ch) {
+ return os<<'{'<<ch.fd<<'}'<<sock2str(ch.peer)<<":"<<ch.id<<'>'<<ch.peer_channel_id;
+}
+
+
+void Channel::Recv (int socket) {
+ Datagram data(socket);
+ data.Recv();
+ //LOG(INFO)<<" RECV "<<data.to_string();
+ int id = 0;
+ if (data.size()<4)
+ RETLOG("datagram shorter than 4 bytes");
+ uint32_t mych = data.Pull32();
+ uint8_t type;
+ uint32_t peerch;
+ Sha1Hash hash;
+ Channel* channel;
+ if (!mych) { // handshake initiated
+ if (data.size()!=1+4+1+4+Sha1Hash::SIZE)
+ RETLOG ("incorrect size initial handshake packet");
+ type = data.Pull8();
+ if (type) // handshake msg id is 0
+ RETLOG ("it is not actually a handshake");
+ peerch = data.Pull32();
+ if (!peerch)
+ RETLOG ("peer channel is zero");
+ uint8_t hashid = data.Pull8();
+ if (hashid!=P2TP_HASH)
+ RETLOG ("no hash in the initial handshake");
+ bin pos = data.Pull32();
+ if (pos!=bin::ALL)
+ RETLOG ("that is not the root hash");
+ hash = data.PullHash();
+ File* file = File::find(hash);
+ if (!file)
+ RETLOG ("hash unknown, no such file");
+ channel = new Channel(file->fd, socket, data.address(), peerch);
+ } else {
+ mych = DecodeID(mych);
+ if (mych>=channels.size())
+ RETLOG ("invalid channel id");
+ channel = channels[mych];
+ id = channel->id;
+ if (channel->peer.sin_addr.s_addr != data.address().sin_addr.s_addr)
+ RETLOG ("invalid peer address");
+ if (channel->peer.sin_port!=data.address().sin_port)
+ RETLOG ("invalid peer port");
+ if (!channel->peer_channel_id) { // handshake response
+ if (data.size()<5)
+ RETLOG ("insufficient return handshake length");
+ type = data.Pull8();
+ if (type)
+ RETLOG ("it is not a handshake, after all");
+ channel->peer_channel_id = data.Pull32();
+ LOG(INFO)<<"out channel is open: "<<*channel;
+ } else if (channel->cc_.avg_rtt()==0) {
+ LOG(INFO)<<"in channel is open: "<<*channel;
+ }
+ if (channel->cc_.avg_rtt()==0)
+ channel->cc_.RttSample(Datagram::now - channel->last_send_time + 1);
+ channel->Recv(data);
+ }
+ channel->Send();
+}
+
+
+void Channel::Tick () {
+ // choking/unchoking
+ // keepalives
+ // ack timeout
+ // if unchoked: don't bother
+ // whether to unchoke
+ // reevaluate reciprocity
+ // otherwise, send update (if needed)
+ // otherwise, send a keepalive
+ CleanStaleHintIn();
+ CleanStaleHintOut();
+ if (last_send_time && Datagram::now-last_send_time>=Channel::TIMEOUT/2)
+ Send();
+}
+
+
+/** <h2> P2TP handshake </h2>
+ Basic rules:
+ <ul>
+ <li> to send a datagram, a channel must be created
+ (channels are cheap and easily recycled)
+ <li> a datagram must contain either the receiving
+ channel id (scrambled) or the root hash
+ <li> initially, the control structure (p2tp_channel)
+ is mostly zeroed; intialization happens as
+ conversation progresses
+ </ul>
+ <b>Note:</b>
+ */
+
+
+void Channel::Loop (tint time) {
+
+ tint untiltime = Datagram::Time()+time;
+
+ while ( Datagram::now <= untiltime ) {
+
+ tint towait = min(untiltime,Datagram::now+TINT_1SEC) - Datagram::now;
+ int rd = Datagram::Wait(sock_count_,sockets_,towait);
+ if (rd!=-1)
+ Recv(rd);
+
+ /*if (Datagram::now-last_tick>TINT_1SEC) {
+ for(int i=0; i<channels.size(); i++)
+ if (channels[i])
+ channels[i]->Tick();
+ last_tick = Datagram::now;
+ }*/
+
+ }
+
+}
+
+int p2tp::Open (const char* filename) {
+ int fd = ::open(filename,O_RDONLY);
+ if (fd<0)
+ return -1;
+ if (File::files.size()<fd+1)
+ File::files.resize(fd+1);
+ File::files[fd] = new File(fd);
+ return fd;
+}
+
+int p2tp::Open (const Sha1Hash& root_hash, const char* filename) {
+ int fd = ::open(filename,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (fd<0)
+ return -1;
+ if (File::files.size()<fd+1)
+ File::files.resize(fd+1);
+ File::files[fd] = new File(root_hash,fd);
+ return fd;
+}
+
+size_t p2tp::file_size (int fd) { return File::file(fd)->size(); }
+
+void p2tp::Close (int fid) {
+ if (!File::files[fid])
+ return;
+ delete File::files[fid];
+ File::files[fid] = NULL;
+}
+
+
+int p2tp::Connect (int fd, int sock, const struct sockaddr_in& addr, uint32_t peerch) {
+ Channel *ch = new Channel(fd,sock,addr,peerch);
+ ch->Send();
+ return ch->id;
+}
+
+void p2tp::Loop (tint time) {
+ Channel::Loop(time);
+}
+
+int p2tp::Init (int portno) {
+ int sock = Datagram::Bind(portno);
+ if (sock>0)
+ Channel::sockets_[Channel::sock_count_++] = sock;
+ return sock;
+}
+
+void p2tp::Shutdown (int sock) {
+ int i=0;
+ while (i<Channel::sock_count_ && Channel::sockets_[i]!=sock) i++;
+ if (i==Channel::sock_count_) {
+ LOG(ERROR)<<"socket "<<sock<<" is unknown to p2tp";
+ return;
+ }
+ Channel::sockets_[i] = Channel::sockets_[--Channel::sock_count_];
+ Datagram::Close(sock);
+}
+
+
+uint32_t p2tp::Width (const tbinvec& v) {
+ uint32_t ret = 0;
+ for(tbinvec::const_iterator i=v.begin(); i!=v.end(); i++)
+ ret += i->pos.width();
+ return ret;
+}
+
--- /dev/null
+/*
+ * p2tp.h
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/6/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+/*
+
+The P2TP protocol
+
+Messages
+
+ HANDSHAKE 00, channelid
+ Communicates the channel id of the sender. The
+ initial handshake packet also has the root hash
+ (a HASH message).
+
+ DATA 01, bin, buffer
+ 1K of data.
+
+ ACK 02, bin
+ Confirms successfull delivery of data. Used for
+ congestion control, as well.
+
+ HINT 03, bin
+ Practical value of "hints" is to avoid overlap, mostly.
+ Hints might be lost in the network or ignored.
+ Peer might send out data without a hint.
+ Hint which was not responded (by DATA) in some RTTs
+ is considered to be ignored.
+ As peers cant pick randomly kilobyte here and there,
+ they send out "long hints" for non-base bins.
+
+ HASH 04, bin, sha1hash
+ SHA1 hash tree hashes for data verification. The
+ connection to a fresh peer starts with bootstrapping
+ him with peak hashes. Later, before sending out
+ any data, a peer sends the necessary uncle hashes.
+
+ PEX+/PEX- 05/06, ip, port
+ Peer exchange messages; reports all connected and
+ disconected peers. Might has special meaning (as
+ in the case with swarm supervisors).
+
+ --8X---- maybe (give2get)
+
+ CRED 07, scrchid
+ Communicated the public channel id at the sender
+ side. Any grandchildren's credits will go to this
+ channel.
+
+ CRED1/2 08/09, ip, port, scrchid
+ Second-step and trird-step credits.
+
+ ACK1/2 10/11, scrchid, packets
+ Grandchildren's acknowledgements of data being
+ received from a child; the public channel id
+ is mentioned.
+
+*/
+#ifndef P2TP_H
+#define P2TP_H
+#include <stdint.h>
+#include <iostream>
+#include <vector>
+#include <deque>
+#include "bin.h"
+#include "sbit.h"
+#include "datagram.h"
+#include "hashtree.h"
+
+namespace p2tp {
+
+ /* 64-bit time counter, microseconds since epoch
+ typedef int64_t tint;
+ static const tint TINT_1SEC = 1000000;
+ static const tint TINT_1MSEC = 1000;
+ static const tint TINT_INFINITY = 0x7fffffffffffffffULL;
+ */
+ struct tintbin {
+ tint time;
+ bin pos;
+ tintbin(tint t, bin p) : time(t), pos(p) {}
+ };
+ typedef std::deque<tintbin> tbinvec;
+
+ typedef enum {
+ P2TP_HANDSHAKE = 0,
+ P2TP_DATA = 1,
+ P2TP_ACK = 2,
+ P2TP_HINT = 3,
+ P2TP_HASH = 4,
+ P2TP_PEX_ADD = 5,
+ P2TP_PEX_RM = 6,
+ P2TP_MESSAGE_COUNT = 7
+ } messageid_t;
+
+
+ struct File {
+
+ typedef enum {EMPTY,IN_PROGRESS,DONE} status_t;
+
+ static std::vector<File*> files;
+
+ /** File descriptor. */
+ int fd;
+ /** Whether the file is completely downloaded. */
+ status_t status_;
+
+ // A map for all packets obtained and succesfully checked.
+ bins ack_out;
+ // Hinted packets.
+ bins hint_out;
+
+ HashTree hashes;
+ // History of bin retrieval.
+ bin::vec history;
+
+ // TBD
+ uint64_t options;
+
+ /** Submit a fresh file. */
+ File (int fd);
+ /** Retrieve a file. */
+ File (Sha1Hash hash, int fd);
+ /** Placeholder. */
+ File () : fd(-1), hashes(Sha1Hash::ZERO) {}
+ /** Close everything. */
+ ~File();
+
+ bool OfferHash (bin pos, const Sha1Hash& hash);
+ const Sha1Hash& root_hash () const { return hashes.root; }
+ size_t size () const { return hashes.data_size()<<10; }
+ size_t packet_size () const { return hashes.data_size(); }
+ status_t status () const { return status_; }
+
+ static File* find (const Sha1Hash& hash);
+ static File* file (int fd) { return fd<files.size() ? files[fd] : NULL; }
+
+ friend int Open (const char* filename);
+ friend int Open (const Sha1Hash& hash, const char* filename);
+ friend void Close (int fdes);
+ friend class Channel;
+ };
+
+
+ int Open (const char* filename) ;
+ int Open (const Sha1Hash& root_hash, const char* filename);
+ size_t file_size (int fd);
+ void Close (int fid) ;
+
+
+ struct CongestionControl {
+ typedef enum {SLOW_START_STATE,CONG_AVOID_STATE} stdtcpstate_t;
+ typedef enum { DATA_EV, ACK_EV, LOSS_EV } CongCtrlEvents;
+
+ stdtcpstate_t state_;
+ tint rtt_avg_, dev_avg_, last_arrival_, rate_;
+ int cwnd_, cainc_, ssthresh_;
+ int peer_cwnd_, data_ins_;
+
+ CongestionControl();
+ virtual ~CongestionControl() {}
+ virtual void OnCongestionEvent (CongCtrlEvents ev);
+ void RttSample (tint rtt);
+
+ tint avg_rtt() const { return rtt_avg_; }
+ tint safe_avg_rtt() const { return avg_rtt() + avg_rtt_dev()*8; }
+ tint avg_rtt_dev() const { return dev_avg_; }
+ int cwnd () const { return cwnd_; }
+ int peer_cwnd () const;
+ int peer_bps () const;
+ tint data_in_rate () const { return rate_; }
+ };
+
+
+
+ /** P2TP "control block". */
+ class Channel {
+
+ public:
+ Channel (int filedes, int socket, struct sockaddr_in peer,
+ uint32_t peer_channel, uint64_t supports=0);
+ ~Channel();
+
+ static void Recv (int socket);
+ void Recv (Datagram& dgram);
+ void Send ();
+ void SendSomething ();
+ void SendHandshake ();
+ void Tick ();
+
+ typedef enum {HS_REQ_OUT,HS_RES_OUT,HS_DONE} state_t;
+ File& file () { return *File::files[fd]; }
+
+
+
+ void OnAck (Datagram& dgram);
+ void OnData (Datagram& dgram);
+ void OnHint (Datagram& dgram);
+ void OnHash (Datagram& dgram);
+
+ void AddHandshake (Datagram& dgram);
+ bin AddData (Datagram& dgram);
+ void AddAck (Datagram& dgram);
+ void AddHint (Datagram& dgram);
+ void AddUncleHashes (Datagram& dgram, bin pos);
+ void AddPeakHashes (Datagram& dgram);
+
+ bin SenderPiecePick () ;
+ bin ReceiverPiecePick (int sizelim) ;
+ void CleanStaleDataOut(bin ack_pos);
+ void CleanStaleHintOut();
+ void CleanStaleHintIn();
+
+ state_t state () const;
+ File::status_t peer_status() const { return peer_status_; }
+ const CongestionControl& congestion_control() const { return cc_; }
+
+ static int DecodeID(int scrambled);
+ static int EncodeID(int unscrambled);
+ static void Loop (tint time);
+ static Channel* channel(int ind) {return ind<channels.size()?channels[ind]:NULL;}
+ static int MAX_REORDERING;
+ static tint TIMEOUT;
+ static std::vector<Channel*> channels;
+ static int* sockets_;
+ static int sock_count_;
+ static tint last_tick;
+ //static int socket;
+
+ friend int Connect (int fd, int sock,
+ const struct sockaddr_in& addr, uint32_t peerch=0);
+ friend int Init (int portno);
+ friend std::ostream& operator << (std::ostream& os, const Channel& s);
+
+ private:
+
+ // index in the channel array
+ int id;
+ // Socket address of the peer.
+ struct sockaddr_in peer;
+ // The UDP socket fd.
+ int socket_;
+ File::status_t peer_status_;
+ // Descriptor of the file in question
+ int fd;
+ // Zero if we are trying to open a channel.
+ uint32_t peer_channel_id;
+ // Temporarily, a pointer to a composed/parsed datagram.
+ //Datagram* dgram;
+
+ // Peer's retrieved pieces.
+ bins ack_in;
+ // Progress of piece acknowledgement to the peer.
+ bin data_in_;
+ int ack_out;
+ // Transmit schedule: in most cases filled with the peer's hints
+ tbinvec hint_in;
+ // Hints sent (to detect and reschedule ignored hints).
+ tbinvec hint_out;
+ // Uncle hash send pos; in the case both data and hashes do not fit
+ // into a single datagram, we set this.
+ bin hash_out;
+ // Send history: all cwnd pieces.
+ tbinvec data_out;
+
+ CongestionControl cc_;
+ tint last_send_time;
+ };
+
+
+ //int connect (int fd, const struct sockaddr_in& addr, uint32_t peerch=0) ;
+ void Loop (tint time=0) ;
+ int Init (int portno) ;
+ void Shutdown (int portno);
+
+ uint32_t Width (const tbinvec& v);
+}
+
+#define RETLOG(str) { LOG(WARNING)<<str; return; }
+
+#endif
--- /dev/null
+/*
+ * rarest1st.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/6/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+
+
+char* Rarest1st::on_event (P2Channel& ch) {
+ if (!ch.peer_has)
+ return "nothing is known - cannot choose";
+ feye i_need (ch.file->have,ch.peer_has->focus);
+ i_need.invert();
+ i_need &= ch.peer_has;
+ if (i_need.clean())
+ return "the peer has nothing we don't have";
+ feye ladder[20];
+ for(int i=P2TP_CHANNELS.begin(); i!=P2TP_CHANNELS.end(); i++) {
+ p2tp_channel* c = *i;
+ if (!c || !c->peer_has)
+ continue;
+ feye x = feye_and(c->peer_has,i_need);
+ for(int j=0; j<20 && !x.clean(); j++) {
+ feye xx(x);
+ x &= ladder[j];
+ ladder[j] ^= xx;
+ }
+ }
+ feye not_rare (ch.peer_has->focus);
+ for(int i=20-1; i>0; i--) {
+ not_rare |= ladder[i];
+ ladder[i-1] -= not_rare;
+ }
+ int pickfrom = 0;
+ while (ladder[pickfrom].clean() && pickfrom<20)
+ pickfrom++;
+ assert(pickfrom<20);
+ mlat spot = ladder[pickfrom].randomBit();
+ ch->i_ackd.refocus(spot);
+ return NULL;
+}
--- /dev/null
+/*
+ * sbit.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 4/1/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include "sbit.h"
+
+uint16_t bins::SPLIT[256];
+uint8_t bins::JOIN[256];
+uint16_t bins::OFFMASK[32];
+static uint16_t NO_PARENT = 0xffff;
+
+bins::bins () : peak(31), bits(32,0), deep(32,false), prnt(16,NO_PARENT),
+ allocp(1), rescan_flag(true) {
+ prnt[0] = 0;
+ deep[1] = true;
+}
+
+bins::bins(const bins& b) : peak(b.peak), allocp(b.allocp),
+ bits(b.bits), prnt(b.prnt), deep(b.deep), rescan_flag(b.rescan_flag)
+{
+}
+
+
+
+void bins::init () {
+ for(int i=0; i<256; i++)
+ JOIN[i] = 0xff;
+ for(int i=0; i<256; i++) {
+ int split = 0;
+ for(int b=0; b<8; b++)
+ if (i&(1<<b))
+ split |= (1<<(2*b)) | (1<<(2*b+1));
+ SPLIT[i] = split;
+ JOIN[split&0xff] = i&0xf;
+ }
+ for(bin i=0; i<32; i++) {
+ int m = 0;
+ for(int j=i.offset(); j<i.length(); j++)
+ m |= 1<<j;
+ OFFMASK[i] = m;
+ }
+}
+
+
+void bins::unlink (int half) {
+ int s[32], sp=0;
+ s[sp++] = half;
+ while (sp) {
+ int h = s[--sp];
+ if (deep[h]) {
+ int c=bits[h], l=c<<1, r=l+1;
+ prnt[c] = NO_PARENT;
+ deep[h]=false;
+ s[sp++] = l;
+ s[sp++] = r;
+ }
+ }
+}
+
+
+bool bins::get(bin pos) const {
+ if (pos>peak)
+ return false;
+ chunk_iterator i(const_cast<bins*>(this));
+ while (i.deep() && i.chunk_top()>pos)
+ i.to(pos);
+ if (i.deep())
+ return false;
+ int l = OFFMASK[pos.scoped(i.chunk_top(),4)];
+ return (*i & l) == l;
+}
+
+
+bool bins::clean(bin pos) const {
+ if (pos>peak)
+ return bin::all1(pos) ? clean(peak) : true;
+ chunk_iterator i(const_cast<bins*>(this));
+ while (i.deep() && i.chunk_top()>pos)
+ i.to(pos);
+ if (i.deep())
+ return false;
+ int l = OFFMASK[pos.scoped(i.chunk_top(),4)];
+ return (*i & l) == 0;
+}
+
+
+void bins::expand () {
+ int oldrootcell = cell_alloc();
+ if (deep[0])
+ prnt[bits[0]] = oldrootcell;
+ prnt[oldrootcell] = 0;
+ int orl = oldrootcell<<1, orr = orl+1;
+ bits[orl] = bits[0];
+ bits[orr] = 0;
+ bits[0] = oldrootcell;
+ deep[orl] = deep[0];
+ deep[orr] = false;
+ deep[0] = true;
+ peak = peak.parent();
+ compact(0);
+}
+
+
+void bins::set (bin pos, bool to) {
+ if (!pos)
+ return;
+ while (pos>peak)
+ expand();
+ chunk_iterator i(this);
+ while (i.chunk_top().layer()>pos.layer()+4)
+ i.to(pos);
+ while (i.deep() && i.chunk_top().layer()>pos.layer())
+ i.to(pos);
+ if (i.deep())
+ unlink(i.half);
+ int mask = OFFMASK[pos.scoped(i.chunk_top(),4)];
+ if (to)
+ *i |= mask;
+ else
+ *i &= ~mask;
+ while(i.up()); //compact
+}
+
+
+bool bins::compact (int half) {
+ if (!deep[half])
+ return false;
+ int l = bits[half]<<1, r = l+1;
+ if (deep[l] || deep[r])
+ return false;
+ int l1 = JOIN[bits[l]&0xff], l2 = JOIN[bits[l]>>8];
+ if (l1==0xff || l2==0xff)
+ return false;
+ int r1 = JOIN[bits[r]&0xff], r2 = JOIN[bits[r]>>8];
+ if (r1==0xff || r2==0xff)
+ return false;
+ deep[half] = false;
+ prnt[bits[half]] = NO_PARENT;
+ deep[l] = deep[r] = false;//coward
+ bits[half] = (l1) | (l2<<4) | (r1<<8) | (r2<<12);
+ return true;
+}
+
+
+void bins::split (int half) {
+ if (!deep[half]) {
+ int newcell = cell_alloc(), oldcell=half>>1;
+ int l = newcell<<1, r = l+1;
+ bits[l] = SPLIT[bits[half]&0xff];
+ bits[r] = SPLIT[bits[half]>>8];
+ deep[half] = true;
+ bits[half] = newcell;
+ prnt[newcell] = oldcell;
+ }
+}
+
+
+void bins::doop (bins& b, int op) {
+ while (b.peak<peak)
+ b.expand();
+ while (b.peak>peak)
+ expand();
+ chunk_iterator i(this), j(&b);
+ do {
+ while (i.deep() || j.deep()) {
+ i.left();
+ j.left();
+ }
+ switch (op) {
+ case OR_OP: (*i) |= *j; break;
+ case AND_OP: (*i) &= *j; break;
+ case SUB_OP: (*i) &= ~*j; break;
+ }
+ while (i.chunk_top().is_right()) {
+ i.up();
+ j.up();
+ }
+ i.up();
+ j.up();
+ i.right();
+ j.right();
+ } while (!i.end());
+}
+
+
+void bins::operator |= (bins& b) {
+ doop(b,OR_OP);
+}
+
+
+void bins::operator &= (bins& b) {
+ doop(b,AND_OP);
+}
+
+
+void bins::operator -= (bins& b) {
+ doop(b,SUB_OP);
+}
+
+
+
+int bins::cell_alloc () { // FIXME: 0xffff size too big
+ while (allocp<prnt.size() && prnt[allocp]!=NO_PARENT)
+ allocp++;
+ if (allocp==prnt.size()) {
+ if (rescan_flag) {
+ rescan_flag = false;
+ allocp=0;
+ return cell_alloc();
+ } else {
+ rescan_flag = true;
+ bits.resize(allocp*4,0);
+ prnt.resize(allocp*2,NO_PARENT);
+ deep.resize(allocp*4,false);
+ }
+ }
+ deep[allocp*2] = false;
+ deep[allocp*2+1] = false;
+ prnt[allocp] = NO_PARENT;
+ return allocp;
+}
+
+
+
+/*void bins::make_space () { WAY TOO SMART, DO LATER
+ std::vector<int> renames(allocp), irenames(allocp);
+ int newcellsize=0;
+ for(int i=0; i<allocp; i++)
+ if (prnt[i]) {
+ renames[newcellsize] = i;
+ irenames[i] = newcellsize;
+ newcellsize++;
+ }
+ if (newcellsize<bits.size()*3/4) {
+ for (int i=0; i<newcellsize; i++) {
+ int n=renames[i], l=i<<1, r=l+1, ln=n>>1, rn=ln+1;
+ deep[l] = deep[ln];
+ deep[r] = deep[rn];
+ prnt[i] = irenames[prnt[n]];
+ bits[l] = deep[l] ? irenames[bits[ln]] : bits[ln];
+ bits[r] = deep[r] ? irenames[bits[rn]] : bits[rn];
+ }
+ allocp = newcellsize;
+ }
+ if (allocp>bits.size()/2) {
+ deep.resize(bits.size()*2);
+ prnt.resize(bits.size());
+ bits.resize(bits.size()*2);
+ }
+ }*/
\ No newline at end of file
--- /dev/null
+/*
+ * sbit.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/28/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#ifndef SERP_SBIT_H
+#define SERP_SBIT_H
+#include <vector>
+#include "bin.h"
+
+class bins {
+
+public:
+
+ class bin_iterator;
+
+ /** Traverses 16-bit chunks. */
+ class chunk_iterator {
+ bins* host;
+ bin top;
+ int half;
+
+ bool up() {
+ top=top.parent();
+ int cell = half>>1;
+ half=host->prnt[cell]<<1;
+ if (!host->deep[half] || host->bits[half]!=cell)
+ half++;
+ assert(host->deep[half] && host->bits[half]==cell);
+ return host->compact(half);
+ }
+ void left() {
+ assert(top.layer()>4);
+ top = top.left();
+ if (!deep())
+ host->split(half);
+ half = host->bits[half]<<1;
+ }
+ void right() {
+ assert(top.layer()>4);
+ top = top.right();
+ if (!deep())
+ host->split(half);
+ half = (host->bits[half]<<1)+1;
+ }
+ void to(bin target) {
+ assert(top.layer()>4);
+ bin next = top.child(target);
+ if (next.is_left())
+ left();
+ else
+ right();
+ }
+ int cell () const { return half>>1; }
+ bool deep() const { return host->deep[half]; }
+ bool is_right () const { return half&1; }
+ bool end () const { return half==1; }
+
+ public:
+ chunk_iterator(bins* h, int hlf=0) : host(h), top(h->peak), half(hlf) {
+ //while (deep())
+ // left();
+ }
+ void operator ++ () {
+ while (is_right())
+ up();
+ up();
+ right();
+ while (deep() && !end())
+ left();
+ }
+ uint16_t& operator * () {
+ return host->bits[half];
+ }
+ bool operator == (const bins::chunk_iterator& b) const {
+ return host==b.host && half==b.half;
+ }
+ bin chunk_top() const { return top; }
+ friend class bins::bin_iterator;
+ friend class bins;
+ }; // chunk_iterator
+
+
+ /** Traverses bins. */
+ class bin_iterator {
+ bins::chunk_iterator i;
+ bin cur;
+ public:
+ bin_iterator(chunk_iterator ci, bin pos=0) : i(ci), cur(pos) {
+ while (!i.end() && i.deep())
+ i.left();
+ ++(*this);
+ }
+ bin operator * () const {
+ return cur.unscoped(i.top,4);
+ }
+ void operator ++ () {
+ if (i.end())
+ return;
+ do {
+ if (cur<bin(4,0)) {
+ cur++;
+ } else {
+ cur = 1;
+ ++i;
+ }
+ } while (!i.end() && (*i&OFFMASK[cur])!=OFFMASK[cur]);
+ bin p=cur.parent();
+ while (p<=bin(4,0) && (*i&OFFMASK[p])==OFFMASK[p]) {
+ cur=p;
+ p=cur.parent();
+ }
+ }
+ bool operator == (const bins::chunk_iterator& b) const {return i==b;}
+ bool operator == (const bins::bin_iterator& b) const {
+ return i==b.i && cur==b.cur;
+ }
+ bool operator != (const bins::bin_iterator& b) const { return !(*this==b); }
+ }; // bin_iterator
+
+
+private:
+ bin peak;
+ std::vector<uint16_t> bits;
+ std::vector<uint16_t> prnt; // BAD BAD BAD
+ std::vector<bool> deep;
+ int allocp;
+ bool rescan_flag;
+
+
+private:
+
+ void unlink (int half);
+ void expand();
+ void split(int half);
+
+ //void make_space();
+ int cell_alloc();
+ bool compact (int cell);
+
+ void doop (bins& b, int op);
+
+ static uint16_t SPLIT[256];
+ static uint8_t JOIN[256];
+ static uint16_t OFFMASK[32];
+ typedef enum { AND_OP, OR_OP, SUB_OP } ops_t;
+
+public:
+
+ bins();
+ bins(const bins& orig);
+
+ bool get(bin pos) const;
+ bool clean(bin pos) const;
+ bool contains(bin pos) const { return get(pos); }
+ void set(bin pos, bool to=true);
+ bool empty() const { return !deep[0] && !bits[0]; }
+ bool operator [] (bin pos) const {return get(pos);}
+ void operator |= (bin pos) { set(pos); }
+ void operator -= (bin pos) { set(pos,false); }
+ void operator |= (bins& b);
+ void operator &= (bins& b);
+ void operator -= (bins& b);
+
+ bin_iterator begin() { return bin_iterator(chunk_iterator(this,0)); }
+ bin_iterator end() { return bin_iterator(chunk_iterator(this,1),1); }
+
+ static void init();
+ friend class SbitTest;
+
+};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+/*
+ * datasendrecv.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/6/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include <algorithm>
+#include <glog/logging.h>
+#include "p2tp.h"
+
+using namespace std;
+using namespace p2tp;
+
+
+void Channel::AddPeakHashes (Datagram& dgram) {
+ const std::vector<binhash>& peaks = file().hashes.peak_hashes();
+ for(int i=0; i<peaks.size(); i++) {
+ dgram.Push8(P2TP_HASH);
+ dgram.Push32(peaks[i].first);
+ dgram.PushHash(peaks[i].second);
+ DLOG(INFO)<<"#"<<id<<" +pHASH"<<peaks[i].first;
+ }
+}
+
+
+void Channel::AddUncleHashes (Datagram& dgram, bin pos) {
+ bin root = pos;
+ while (root.parent()<=file().hashes.data_mass() && ack_in.clean(root.parent()))
+ root = root.parent();
+ while (root!=pos) {
+ root = root.child(pos);
+ bin uncle = root.sibling();
+ dgram.Push8(P2TP_HASH);
+ dgram.Push32(uncle);
+ dgram.PushHash( file().hashes[uncle] );
+ DLOG(INFO)<<"#"<<id<<" +uHASH"<<uncle;
+ }
+}
+
+
+bin Channel::SenderPiecePick () { // TODO: resilience
+ while (!hint_in.empty()) {
+ bin hint = hint_in.front().pos;
+ hint_in.pop_front();
+ if (ack_in.contains(hint))
+ continue;
+ if (hint.layer()) {
+ bin l=hint.left(), r=hint.right();
+ //if (false)//rand()&1)
+ // swap(l,r);
+ hint_in.push_front(tintbin(Datagram::now,r));
+ hint_in.push_front(tintbin(Datagram::now,l));
+ continue;
+ }
+ if ( !file().ack_out.contains(hint) )
+ continue;
+ return hint;
+ }
+ return 0;
+}
+
+
+
+Channel::state_t Channel::state () const {
+ if (!peer_channel_id)
+ return HS_REQ_OUT;
+ if (cc_.avg_rtt()==0)
+ return HS_RES_OUT;
+ return HS_DONE;
+}
+
+
+void Channel::CleanStaleDataOut (bin ack_pos) {
+
+ if (ack_pos)
+ for(int i=0; i<data_out.size() && i<MAX_REORDERING*2; i++)
+ if (data_out[i].pos && ack_pos.contains(data_out[i].pos)) {
+ cc_.RttSample(Datagram::now-data_out[i].time);
+ cc_.OnCongestionEvent(CongestionControl::ACK_EV);
+ data_out[i].pos = 0;
+ }
+ while (!data_out.empty() && data_out[0].pos==0)
+ data_out.pop_front();
+
+ tint timed_out = Datagram::now - cc_.safe_avg_rtt();
+ while (!data_out.empty() && data_out.front().time < timed_out) {
+ DLOG(INFO)<<*this<<" loss: "<<data_out.front().pos;
+ // reordering is a loss, collision is a loss
+ cc_.OnCongestionEvent(CongestionControl::LOSS_EV);
+ data_out.pop_front();
+ }
+
+}
+
+
+void Channel::CleanStaleHintOut () {
+ while ( !hint_out.empty() && file().ack_out.contains(hint_out.front().pos) )
+ hint_out.pop_front();
+ tint timed_out = Datagram::now - cc_.safe_avg_rtt()*4; //FIXME: timeout
+ while ( !hint_out.empty() && hint_out.front().time < timed_out ) {
+ file().hint_out -= hint_out.front().pos;
+ hint_out.pop_front(); // TODO: ignore count
+ }
+}
+
+void Channel::CleanStaleHintIn () {
+ // do I need it?
+}
+
+
+void Channel::SendHandshake () {
+ Datagram dgram(socket_,peer);
+ dgram.Push32(peer_channel_id);
+ dgram.Push8(P2TP_HANDSHAKE);
+ dgram.Push32(EncodeID(id));
+ if (!peer_channel_id) { // initiating
+ dgram.Push8(P2TP_HASH);
+ dgram.Push32(bin::ALL);
+ dgram.PushHash(file().hashes.root);
+ AddAck(dgram);
+ } else { // responding
+ AddAck(dgram);
+ }
+ DLOG(INFO)<<"#"<<id<<" sending a handshake to "<<*this;
+ PCHECK( dgram.Send() != -1 )<<"error sending";
+ last_send_time = Datagram::now;
+}
+
+
+void Channel::SendData () {
+ CleanStaleDataOut(0);
+ int round = 0;
+ Datagram dgram(socket_,peer);
+ dgram.Push32(peer_channel_id);
+ AddAck(dgram);
+ AddHint(dgram);
+ while (cc_.cwnd()>data_out.size()) {
+ AddData(dgram); // always the last: might be tail block
+ if (dgram.size()==4 && Datagram::now-last_send_time<TIMEOUT/2)
+ break; // nothing to send
+ DLOG(INFO)<<"#"<<id<<" sending "<<dgram.size()<<" bytes";
+ PCHECK( dgram.Send() != -1 )<<"error sending";
+ last_send_time = Datagram::now;
+ round++;
+ dgram.Clear();
+ dgram.Push32(peer_channel_id);
+ }
+ DLOG(INFO)<<"#"<<id<<" sent "<<round<<" rounds";
+}
+
+
+void Channel::Send () {
+ if (state()==HS_DONE)
+ SendData();
+ else
+ SendHandshake();
+}
+
+
+bin Channel::ReceiverPiecePick (int limit) {
+ bins diff(ack_in);
+ diff -= file().ack_out;
+ diff -= file().hint_out;
+ if (diff.empty()) {
+ //uninteresting = true;
+ return 0;
+ }
+ bin need = *(diff.begin());
+ while (need.width()>std::max(1,limit))
+ need = need.left();
+ return need;
+}
+
+
+void Channel::AddHint (Datagram& dgram) {
+ CleanStaleHintOut();
+ int onesec = TINT_1SEC/cc_.data_in_rate();
+ if (Width(hint_out)<onesec) {
+ bin hint = ReceiverPiecePick(onesec);
+ if (hint) {
+ dgram.Push8(P2TP_HINT);
+ dgram.Push32(hint);
+ hint_out.push_back(tintbin(Datagram::now,hint));
+ file().hint_out |= hint; // FIXME: incapsulate File data
+ DLOG(INFO)<<"#"<<id<<" +HINT"<<hint;
+ }
+ }
+}
+
+
+bin Channel::AddData (Datagram& dgram) {
+ if (!file().history.size()) // know nothing
+ return 0;
+ bin tosend = hash_out ? hash_out : SenderPiecePick();
+ hash_out = 0;
+ if (tosend==0) {
+ LOG(WARNING)<<*this<<" no idea what to send";
+ cc_.OnCongestionEvent(CongestionControl::LOSS_EV);
+ return 0;
+ }
+ if (peer_status()==File::EMPTY && file().history.size()) //FIXME
+ AddPeakHashes(dgram);
+ AddUncleHashes(dgram,tosend);
+ uint8_t buf[1024];
+ size_t r = pread(fd,buf,1024,tosend.offset()<<10); // TODO: ??? corrupted data, retries
+ if (r<0) {
+ PLOG(ERROR)<<"error on reading";
+ return 0;
+ }
+ if (dgram.space()<r+4+1) {
+ hash_out = tosend;
+ return -tosend; // FIXME
+ }
+ dgram.Push8(P2TP_DATA);
+ dgram.Push32(tosend);
+ dgram.Push(buf,r);
+ data_out.push_back(tintbin(Datagram::Time(),tosend));
+ DLOG(INFO)<<"#"<<id<<" +DATA"<<tosend;
+ return tosend;
+}
+
+
+void Channel::AddAck (Datagram& dgram) {
+ int ackspace = min(4,dgram.space()/5);
+ if (data_in_) {
+ dgram.Push8(P2TP_ACK);
+ dgram.Push32(data_in_);
+ DLOG(INFO)<<"#"<<id<<" +!ACK"<<data_in_;
+ ackspace--;
+ }
+ while (ack_out<file().history.size() && ackspace) {
+ bin h=file().history[ack_out++];
+ if (!file().ack_out.contains(h.parent()) && h!=data_in_) {
+ dgram.Push8(P2TP_ACK);
+ dgram.Push32(h);
+ DLOG(INFO)<<"#"<<id<<" +ACK"<<h;
+ ackspace--;
+ }
+ }
+ data_in_ = 0;
+}
+
+
+void Channel::Recv (Datagram& dgram) {
+ while (dgram.size()) {
+ uint8_t type = dgram.Pull8();
+ switch (type) {
+ case P2TP_DATA: OnData(dgram); break;
+ case P2TP_ACK: OnAck(dgram); break;
+ case P2TP_HASH: OnHash(dgram); break;
+ case P2TP_HINT: OnHint(dgram); break;
+ default:
+ LOG(ERROR) << *this << " malformed datagram";
+ return;
+ }
+ }
+}
+
+
+void Channel::OnHash (Datagram& dgram) {
+ bin pos = dgram.Pull32();
+ Sha1Hash hash = dgram.PullHash();
+ if (file().OfferHash(pos,hash))
+ DLOG(INFO)<<"#"<<id<<" .HASH"<<(int)pos;
+}
+
+
+void Channel::OnData (Datagram& dgram) {
+ bin pos = dgram.Pull32();
+ uint8_t* data;
+ size_t length = dgram.Pull(&data,1024);
+ DLOG(INFO)<<"#"<<id<<" .DATA"<<pos;
+ if (pos.layer())
+ RETLOG("non-base layer DATA pos");
+ if (file().ack_out.contains(pos))
+ RETLOG("duplicate transmission: "<<pos);
+ if (file().status()==File::EMPTY)
+ RETLOG("DATA for an empty file");
+ if (pos.offset()>=file().packet_size())
+ RETLOG("DATA pos out of bounds");
+ Sha1Hash hash(data,length);
+ if (file().OfferHash(pos, hash)) {
+ //memcpy(file->data+offset*KILO,
+ //channel->datagram->data+channel->datagram->offset,KILO);
+ pwrite(fd,data,length,pos.offset()*1024); // TODO; if (last) ftruncate
+ if (pos==file().hashes.data_mass()) {
+ int lendiff = 1024-length;
+ ftruncate(fd, file().size()-lendiff);
+ }
+ data_in_ = pos;
+ file().ack_out |= pos;
+ file().history.push_back(file().ack_out.get(pos));
+ if (file().history.size()==file().packet_size()+1) // FIXME: encapsulate
+ file().status_ = File::DONE;
+ cc_.OnCongestionEvent(CongestionControl::DATA_EV);
+ //DLOG(INFO)<<*this<<" DATA< "<<pos;
+ CleanStaleHintOut();
+ } else
+ LOG(ERROR)<<"data hash is not accepted "<<pos<<" len "<<length;
+}
+
+
+void Channel::OnAck (Datagram& dgram) {
+
+ bin pos = dgram.Pull32();
+ DLOG(INFO)<<"#"<<id<<" .ACK"<<pos;
+ if (file().hashes.data_mass() && pos>file().hashes.data_mass()) {
+ LOG(WARNING) << "out-of-bounds ACK";
+ return;
+ }
+ ack_in |= pos;
+
+ CleanStaleDataOut(pos);
+
+ if (peer_status_==File::EMPTY) {
+ peer_status_ = File::IN_PROGRESS;
+ } else if (peer_status_==File::IN_PROGRESS) {
+ // FIXME: FINISHED ack_in_.filled(file().size())
+ }
+}
+
+
+void Channel::OnHint (Datagram& dgram) {
+ bin hint = dgram.Pull32();
+ hint_in.push_back(tintbin(Datagram::now,hint));
+}
+
+
--- /dev/null
+.\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples.\r
+.\"See Also:\r
+.\"man mdoc.samples for a complete listing of options\r
+.\"man mdoc for the short list of editing options\r
+.\"/usr/share/misc/mdoc.template\r
+.Dd 3/6/09 \" DATE \r
+.Dt serp++ 1 \" Program name and manual section number \r
+.Os Darwin\r
+.Sh NAME \" Section Header - required - don't modify \r
+.Nm serp++,\r
+.\" The following lines are read in generating the apropos(man -k) database. Use only key\r
+.\" words here as the database is built based on the words here and in the .ND line. \r
+.Nm Other_name_for_same_program(),\r
+.Nm Yet another name for the same program.\r
+.\" Use .Nm macro to designate other names for the documented program.\r
+.Nd This line parsed for whatis database.\r
+.Sh SYNOPSIS \" Section Header - required - don't modify\r
+.Nm\r
+.Op Fl abcd \" [-abcd]\r
+.Op Fl a Ar path \" [-a path] \r
+.Op Ar file \" [file]\r
+.Op Ar \" [file ...]\r
+.Ar arg0 \" Underlined argument - use .Ar anywhere to underline\r
+arg2 ... \" Arguments\r
+.Sh DESCRIPTION \" Section Header - required - don't modify\r
+Use the .Nm macro to refer to your program throughout the man page like such:\r
+.Nm\r
+Underlining is accomplished with the .Ar macro like this:\r
+.Ar underlined text .\r
+.Pp \" Inserts a space\r
+A list of items with descriptions:\r
+.Bl -tag -width -indent \" Begins a tagged list \r
+.It item a \" Each item preceded by .It macro\r
+Description of item a\r
+.It item b\r
+Description of item b\r
+.El \" Ends the list\r
+.Pp\r
+A list of flags and their descriptions:\r
+.Bl -tag -width -indent \" Differs from above in tag removed \r
+.It Fl a \"-a flag as a list item\r
+Description of -a flag\r
+.It Fl b\r
+Description of -b flag\r
+.El \" Ends the list\r
+.Pp\r
+.\" .Sh ENVIRONMENT \" May not be needed\r
+.\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1\r
+.\" .It Ev ENV_VAR_1\r
+.\" Description of ENV_VAR_1\r
+.\" .It Ev ENV_VAR_2\r
+.\" Description of ENV_VAR_2\r
+.\" .El \r
+.Sh FILES \" File used or created by the topic of the man page\r
+.Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact\r
+.It Pa /usr/share/file_name\r
+FILE_1 description\r
+.It Pa /Users/joeuser/Library/really_long_file_name\r
+FILE_2 description\r
+.El \" Ends the list\r
+.\" .Sh DIAGNOSTICS \" May not be needed\r
+.\" .Bl -diag\r
+.\" .It Diagnostic Tag\r
+.\" Diagnostic informtion here.\r
+.\" .It Diagnostic Tag\r
+.\" Diagnostic informtion here.\r
+.\" .El\r
+.Sh SEE ALSO \r
+.\" List links in ascending order by section, alphabetically within a section.\r
+.\" Please do not reference files that do not exist without filing a bug report\r
+.Xr a 1 , \r
+.Xr b 1 ,\r
+.Xr c 1 ,\r
+.Xr a 2 ,\r
+.Xr b 2 ,\r
+.Xr a 3 ,\r
+.Xr b 3 \r
+.\" .Sh BUGS \" Document known, unremedied bugs \r
+.\" .Sh HISTORY \" Document history if command behaves in a unique manner
\ No newline at end of file
--- /dev/null
+env = Environment(CPPPATH = '.',CXXFLAGS="-g")
+
+env.Program(
+ target='bintest',
+ source=['bintest.cpp'],
+ CPPPATH=['..'],
+ LIBS=['p2tp','stdc++','gtest','glog'],
+ LIBPATH='..' )
+
+env.Program(
+ target='binstest',
+ source=['binstest.cpp'],
+ CPPPATH=['..'],
+ LIBS=['p2tp','stdc++','gtest','glog'],
+ LIBPATH='..' )
+
+env.Program(
+ target='dgramtest',
+ source=['dgramtest.cpp'],
+ CPPPATH=['..'],
+ LIBS=['p2tp','stdc++','gtest','glog'],
+ LIBPATH='..' )
+
+env.Program(
+ target='hashtest',
+ source=['hashtest.cpp'],
+ CPPPATH=['..'],
+ LIBS=['p2tp','stdc++','gtest','glog','crypto'],
+ LIBPATH='..' )
+
+env.Program(
+ target='ledbattest',
+ source=['ledbattest.cpp'],
+ CPPPATH=['..'],
+ LIBS=['p2tp','stdc++','gtest','glog','crypto'],
+ LIBPATH='..' )
+
+env.Program(
+ target='bin64test',
+ source=['bin64test.cpp','../bin64.h'],
+ CPPPATH=['..'],
+ LIBS=['gtest'],
+ LIBPATH='..' )
+
--- /dev/null
+/*
+ * bintest.cpp
+ * bin++
+ *
+ * Created by Victor Grishchenko on 3/9/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include "bin64.h"
+#include <gtest/gtest.h>
+
+TEST(BinTest,InitGet) {
+
+ EXPECT_EQ(0x1,bin64_t(1,0));
+ EXPECT_EQ(0xB,bin64_t(2,1));
+ EXPECT_EQ(0x2,bin64_t(2,1).layer());
+ EXPECT_EQ(34,bin64_t(34,2345).layer());
+ EXPECT_EQ(0x7ffffffffULL,bin64_t(34,2345).tail_bits());
+ EXPECT_EQ(1,bin64_t(2,1).offset());
+ EXPECT_EQ(2345,bin64_t(34,2345).offset());
+ EXPECT_EQ(1,bin64_t(0,123).tail_bit());
+ EXPECT_EQ(1<<16,bin64_t(16,123).tail_bit());
+
+}
+
+TEST(BinTest,Navigation) {
+
+ bin64_t mid(4,18);
+ EXPECT_EQ(bin64_t(5,9),mid.parent());
+ EXPECT_EQ(bin64_t(3,36),mid.left());
+ EXPECT_EQ(bin64_t(3,37),mid.right());
+ EXPECT_EQ(bin64_t(5,9),bin64_t(4,19).parent());
+ bin64_t up32(30,1);
+ EXPECT_EQ(bin64_t(31,0),up32.parent());
+
+}
+
+TEST(BinTest,Overflows) {
+
+ EXPECT_EQ(bin64_t::NONE.parent(),bin64_t::NONE);
+ EXPECT_EQ(bin64_t::NONE.left(),bin64_t::NONE);
+ EXPECT_EQ(bin64_t::NONE.right(),bin64_t::NONE);
+ EXPECT_EQ(bin64_t::NONE,bin64_t(0,2345).left());
+ EXPECT_EQ(bin64_t::NONE,bin64_t::ALL.parent());
+
+}
+
+int main (int argc, char** argv) {
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+
+}
--- /dev/null
+/*
+ * binstest.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/22/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include <time.h>
+#include <set>
+#include <gtest/gtest.h>
+#include "bin.h"
+#include "sbit.h"
+
+TEST(BinsTest,AddSub) {
+ bins b;
+ b|=15;
+ b-=1;
+ ASSERT_TRUE(b.contains(2));
+ ASSERT_TRUE(b.contains(14));
+ ASSERT_FALSE(b.contains(3));
+ ASSERT_FALSE(b.contains(22));
+ ASSERT_TRUE(b.contains(12));
+ b-=13;
+ ASSERT_FALSE(b.contains(12));
+ ASSERT_FALSE(b.contains(14));
+ ASSERT_FALSE(b.contains(11));
+ ASSERT_TRUE(b.contains(10));
+}
+
+
+TEST(BinsTest,Peaks) {
+ bin::vec peaks = bin::peaks(11);
+ ASSERT_EQ(3,peaks.size());
+ ASSERT_EQ(15,peaks[0]);
+ ASSERT_EQ(18,peaks[1]);
+ ASSERT_EQ(19,peaks[2]);
+}
+
+TEST(BinsTest,Performance) {
+ bins b;
+ std::set<int> s;
+ clock_t start, end;
+ double b_time, s_time;
+ int b_size, s_size;
+
+ start = clock();
+ for(int i=1; i<(1<<20); i++)
+ b |= bin(i);
+ //b_size = b.bits.size();
+ end = clock();
+ b_time = ((double) (end - start)) / CLOCKS_PER_SEC;
+ //ASSERT_EQ(1,b.bits.size());
+
+ start = clock();
+ for(int i=1; i<(1<<20); i++)
+ s.insert(i);
+ s_size = s.size();
+ end = clock();
+ s_time = ((double) (end - start)) / CLOCKS_PER_SEC;
+
+ printf("bins: %f (%i), set: %f (%i)\n",b_time,b_size,s_time,s_size);
+}
+
+int main (int argc, char** argv) {
+ bin::init();
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
--- /dev/null
+/*
+ * bintest.cpp
+ * bin++
+ *
+ * Created by Victor Grishchenko on 3/9/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include "bin.h"
+#include <gtest/gtest.h>
+
+TEST(BinTest,Mass) {
+ EXPECT_EQ(bin(4).length(),3);
+ EXPECT_EQ(bin(9).length(),6);
+ EXPECT_EQ(bin(3).mass(),3);
+ EXPECT_EQ(bin(5).mass(),1);
+ EXPECT_EQ(bin(6).mass(),3);
+ EXPECT_EQ(bin(10).mass(),3);
+ EXPECT_TRUE(bin::all1(1));
+ EXPECT_TRUE(bin::all1(7));
+}
+
+TEST(BinTest,Instantiation) {
+ EXPECT_EQ(bin(0,1),2)<<"bin_new @0";
+ EXPECT_EQ(bin(2,0),7)<<"bin_new @2";
+ EXPECT_EQ(bin(1,2),10)<<"bin_new @1";
+ EXPECT_EQ(12,bin(0,7))<<"bin(0,7)";
+ EXPECT_EQ(9,bin::tailzeros(512));
+ EXPECT_EQ(0,bin::tailzeros(1));
+ EXPECT_EQ(5,bin::tailzeros(32+128));
+}
+
+TEST(BinTest,Traversing) {
+ EXPECT_EQ(6,bin(4).parent());
+ EXPECT_EQ(bin(30).parent(),31);
+ EXPECT_EQ(bin(7).parent(),15);
+ EXPECT_EQ(bin(18).parent(),22);
+ EXPECT_EQ(bin(30).left(),22);
+ EXPECT_EQ(bin(14).left(),10);
+ EXPECT_EQ(bin(22).left(),18);
+ EXPECT_EQ(bin(30).right(),29);
+ EXPECT_EQ(bin(14).right(),13);
+ EXPECT_EQ(bin(22).right(),21);
+ EXPECT_EQ(bin(15).left_foot(),1)<<"15 left foot";
+ EXPECT_EQ(bin(15).right_foot(),12)<<"15 right foot";
+ EXPECT_EQ(bin(22).left_foot(),16)<<"22 left foot";
+ EXPECT_EQ(bin(22).right_foot(),20)<<"22 right foot";
+}
+
+TEST(BinTest,Advanced) {
+ EXPECT_EQ(0,bin(1).layer());
+
+ EXPECT_TRUE(bin(31).contains(14));
+ EXPECT_FALSE(bin(22).contains(14));
+ EXPECT_TRUE(bin(7).contains(7));
+ EXPECT_TRUE(bin(11).contains(11));
+ EXPECT_TRUE(bin(22).contains(20));
+
+ EXPECT_EQ(6,bin(5).commonParent(4)) << "common parent trivial";
+ EXPECT_EQ(7,bin(2).commonParent(4)) << "common parent trivial";
+ EXPECT_EQ(14,bin(8).commonParent(11)) << "common parent trick";
+ EXPECT_EQ(31,bin(14).commonParent(16)) << "common parent complex";
+ EXPECT_EQ(31,bin(8).commonParent(16)) << "common parent trick 2";
+ EXPECT_EQ(31,bin(31).commonParent(1)) << "common parent trick 2";
+ EXPECT_EQ(22,bin(22).commonParent(19)) << "common parent nested";
+ EXPECT_EQ(22,bin(19).commonParent(22)) << "common parent nested rev";
+ EXPECT_EQ(63,bin(32).commonParent(12)) << "common parent nested rev";
+ EXPECT_EQ(31,bin(14).commonParent(18)) << "common parent nested rev";
+ EXPECT_EQ(12,bin(12).commonParent(12)) << "common parent nested rev";
+ for(bin i=1; i<=127; i++)
+ for(bin j=1; j<=127; j++) if (i!=j) {
+ bin c = i.commonParent(j);
+ EXPECT_TRUE(c.contains(i));
+ EXPECT_TRUE(c.contains(j));
+ bin l=c.left(), r=c.right();
+ //printf("%i %i => %i (%i,%i)\n",(int)i,(int)j,(int)c,(int)l,(int)r);
+ EXPECT_FALSE(l.contains(i)&&l.contains(j));
+ EXPECT_FALSE(r.contains(i)&&r.contains(j));
+ }
+ EXPECT_EQ(22,bin(16).parent(2)) << "parent 2";
+ EXPECT_EQ(31,bin(9).parent(4)) << "parent-snake";
+}
+
+TEST(BinTest,Overflows) {
+ // TODO
+ //EXPECT_EQ( 1<<31, bin::ALL.length() );
+}
+
+TEST(BinTest,Division) {
+ EXPECT_EQ(bin(14).modulo(3),14);
+ EXPECT_EQ(bin(22).modulo(3),7);
+ EXPECT_EQ(bin(21).modulo(1),3);
+ EXPECT_EQ(bin(31).modulo(3),15);
+ EXPECT_EQ(bin(31).modulo(4),31);
+ EXPECT_EQ(bin(22).divide(2),4);
+ EXPECT_EQ(bin(30).divide(2),6);
+ EXPECT_EQ(bin(31).divide(3),3);
+ EXPECT_EQ(bin(14).multiply(1),30);
+ EXPECT_EQ(bin(6).multiply(2),30);
+}
+
+TEST(BinTest, Scope) {
+ EXPECT_EQ(1,bin(32).scoped(bin(62),4));
+ EXPECT_EQ(14,bin(29).scoped(bin(30),3));
+ EXPECT_EQ(15,bin(30).scoped(bin(30),3));
+ EXPECT_EQ(5,bin(11).scoped(bin(31),3));
+ EXPECT_EQ(4,bin(22).scoped(bin(31),2));
+
+ EXPECT_EQ(14,bin(2).unscoped(bin(15),1));
+ EXPECT_EQ(22,bin(4).unscoped(bin(31),2));
+}
+
+TEST(BinTest, Order) {
+ bin::vec v;
+ v.push_back(22);
+ v.push_back(17);
+ v.push_back(19);
+ v.push_back(6);
+ v.push_back(3);
+ v.push_back(14);
+ bin::order(&v);
+ ASSERT_EQ(2,v.size());
+ EXPECT_EQ(v[1],15);
+ EXPECT_EQ(v[0],22);
+}
+
+int main (int argc, char** argv) {
+ bin::init();
+
+ bin p(12234);
+ printf("%i %i %i %i\n",(int)p,p.layer(),p.offset(),p.offset()<<p.layer());
+ p = 16382;
+ printf("%i %i %i %i\n",(int)p,p.layer(),p.offset(),p.offset()<<p.layer());
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+
+}
--- /dev/null
+/*
+ * congctrltest.cpp
+ * p2tp
+ *
+ * Created by Victor Grishchenko on 7/13/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include <stdint.h>
+#include <queue>
+#include <gtest/gtest.h>
+#include <glog/logging.h>
+#include "p2tp.h"
+
+using namespace std;
+using namespace p2tp;
+
+class SimPeer;
+
+struct SimPacket {
+ SimPacket(int from, int to, const SimPacket* toack, bool data) ;
+ int peerfrom, peerto;
+ tint datatime;
+ tint acktime;
+ tint arrivaltime;
+};
+
+tint now = 0;
+
+/** very simplified; uplink is the bottleneck */
+class SimPeer {
+public:
+ SimPeer (tint tt, tint lt, int qlen) : travtime(tt), latency(lt), queue_length(qlen) {}
+ int queue_length;
+ int travtime;
+ tint freetime;
+ tint latency;
+ int unackd;
+ int rcvd, sent;
+ queue<SimPacket> packet_queue;
+ queue<SimPacket> dropped_queue;
+ CongestionControl congc;
+
+ void send(SimPacket pck) {
+ if (packet_queue.size()==queue_length) {
+ dropped_queue.push(pck);
+ return;
+ }
+ tint start = max(now,freetime);
+ tint done = pck.datatime ? start+travtime : start;
+ freetime = done;
+ pck.arrivaltime = done + latency;
+ packet_queue.push(pck);
+ }
+
+ SimPacket recv () {
+ assert(!packet_queue.empty());
+ SimPacket ret = packet_queue.front();
+ packet_queue.pop();
+ return ret;
+ }
+
+ tint next_recv_time () const {
+ return packet_queue.empty() ? NEVER : packet_queue.front().arrivaltime;
+ }
+
+ void turn () {
+ SimPacket rp = recv();
+ SimPacket reply;
+ now = rp.arrivaltime;
+ if (rp.acktime) {
+ congc.RttSample(rp.arrivaltime-rp.acktime);
+ congc.OnCongestionEvent(CongestionControl::ACK_EV);
+ unackd--;
+ rcvd++;
+ }
+ if (rp.datatime) {
+ congc.OnCongestionEvent(CongestionControl::DATA_EV);
+ reply.acktime = reply.datatime;
+ }
+ if (!dropped_queue.empty() && dropped_queue.top().datatime<now+THR)
+ congc.OnCongestionEvent(CongestionControl::LOSS_EV);
+ if (congc.cwnd()>unackd) {
+ unackd++;
+ reply.datatime = now;
+ sent++;
+ }
+ rp.from->send(reply);
+ }
+};
+
+TEST(P2TP, TailDropTest) {
+ // two peers exchange packets over 100ms link with tail-drop discipline
+ // bw 1Mbits => travel time of 1KB is ~10ms
+ SimPeer a(10*MSEC,100*MSEC,20), b(10*MSEC,100*MSEC,20);
+ a.send(SimPacket(&b,now,0,0));
+ while (now<60*60*SEC)
+ if (a.next_recv_time()<b.next_recv_time())
+ a.turn();
+ else
+ b.turn();
+}
+
+int main (int argc, char** argv) {
+ bin::init();
+ bins::init();
+ google::InitGoogleLogging(argv[0]);
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+
+}
--- /dev/null
+/*
+ * connecttest.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/19/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+
+#include <gtest/gtest.h>
+#include <glog/logging.h>
+#include "p2tp.h"
+
+
+/*TEST(P2TP, ConnectTest) {
+
+ uint8_t buf[1024];
+ int f = open("test_file",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ for(char c='a'; c<='c'; c++) {
+ memset(buf,c,1024);
+ write(f,buf,1024);
+ }
+ close(f);
+
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(7001);
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ int sock = p2tp::Init(7001);
+ ASSERT_TRUE(0<sock);
+
+ int file = p2tp::Open("test_file");
+ p2tp::File& fileobj = * p2tp::File::file(file);
+ int copy = p2tp::Open(fileobj.root_hash(),"test_file_copy");
+ p2tp::File& copyobj = * p2tp::File::file(copy);
+ int chan = p2tp::Connect(copy,sock,addr); // TRICK: will open a channel to the first file
+ p2tp::Loop(p2tp::TINT_1SEC);
+ //ASSERT_EQ(p2tp::Channel::channel(chan)->state(),p2tp::Channel::HS_DONE); FIXME: status
+ ASSERT_EQ(p2tp::file_size(file),p2tp::file_size(copy));
+ ASSERT_EQ(p2tp::File::DONE,copyobj.status());
+ p2tp::Close(file);
+ p2tp::Close(copy);
+
+ p2tp::Shutdown(sock);
+
+}*/
+
+
+TEST(P2TP,CwndTest) {
+ int f = open("big_test_file",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ int size = rand()%(1<<19) + (1<<19);
+ char* b = (char*)malloc(size);
+ for(int i=0; i<size; i++)
+ b[i] = (i%1024!=1023) ? ('A' + rand()%('Z'-'A')) : ('\n');
+ write(f,b,size);
+ free(b);
+ close(f);
+
+ struct sockaddr_in addr1, addr2;
+ addr1.sin_family = AF_INET;
+ addr1.sin_port = htons(7003);
+ addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ addr2.sin_family = AF_INET;
+ addr2.sin_port = htons(7004);
+ addr2.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ int sock1 = p2tp::Init(7003);
+ int sock2 = p2tp::Init(7004);
+ ASSERT_TRUE(sock1>=0);
+ ASSERT_TRUE(sock2>=0);
+
+ int file = p2tp::Open("big_test_file");
+ p2tp::File& fileobj = * p2tp::File::file(file);
+
+ int copy = p2tp::Open(fileobj.root_hash(),"big_test_file_copy");
+ p2tp::File& copyobj = * p2tp::File::file(copy);
+
+ int chan = p2tp::Connect(copy,sock1,addr2);
+
+ p2tp::Loop();
+ p2tp::Loop();
+ p2tp::Channel& sendch = * p2tp::Channel::channel(chan+1);
+ while (copyobj.status()!=p2tp::File::DONE) {
+ p2tp::Loop();
+ LOG(INFO)<<sendch.congestion_control().cwnd()<<" cwnd";
+ //EXPECT_GE(1,sendch.congestion_control().cwnd());
+ }
+
+ ASSERT_EQ(p2tp::file_size(file),p2tp::file_size(copy));
+ p2tp::Close(file);
+ p2tp::Close(copy);
+
+ p2tp::Shutdown(sock1);
+ p2tp::Shutdown(sock2);
+
+}
+
+
+int main (int argc, char** argv) {
+
+ bin::init();
+ bins::init();
+ google::InitGoogleLogging(argv[0]);
+ testing::InitGoogleTest(&argc, argv);
+ int ret = RUN_ALL_TESTS();
+ return ret;
+
+}
--- /dev/null
+/*
+ * dgramtest.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/13/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include <gtest/gtest.h>
+#include <glog/logging.h>
+#include "datagram.h"
+
+using namespace p2tp;
+
+
+TEST(Datagram, BinaryTest) {
+ int socket = Datagram::Bind(7001);
+ ASSERT_TRUE(socket>0);
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(7001);
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ Datagram d(socket,addr);
+ const char * text = "text";
+ const uint8_t num8 = 0xab;
+ const uint16_t num16 = 0xabcd;
+ const uint32_t num32 = 0xabcdef01;
+ const uint64_t num64 = 0xabcdefabcdeffULL;
+ d.PushString(text);
+ d.Push8(num8);
+ d.Push16(num16);
+ d.Push32(num32);
+ d.Push64(num64);
+ char buf[1024];
+ int i;
+ for(i=0; i<d.length; i++)
+ sprintf(buf+i*2,"%02x",*(unsigned char*)(d.buf+i));
+ buf[i*2] = 0;
+ EXPECT_STREQ("74657874ababcdabcdef01000abcdefabcdeff",buf);
+ int datalen = strlen(text)+1+2+4+8;
+ ASSERT_EQ(datalen,d.Send());
+ int socks[1] = {socket};
+ socket = Datagram::Wait(1,socks);
+ Datagram rcv(socket);
+ ASSERT_EQ(datalen,rcv.Recv());
+ char* rbuf;
+ int pl = rcv.Pull((uint8_t**)&rbuf,strlen(text));
+ memcpy(buf,rbuf,pl);
+ buf[pl]=0;
+ uint8_t rnum8 = rcv.Pull8();
+ uint16_t rnum16 = rcv.Pull16();
+ uint32_t rnum32 = rcv.Pull32();
+ uint64_t rnum64 = rcv.Pull64();
+ EXPECT_STREQ("text",buf);
+ EXPECT_EQ(0xab,rnum8);
+ EXPECT_EQ(0xabcd,rnum16);
+ EXPECT_EQ(0xabcdef01,rnum32);
+ EXPECT_EQ(0xabcdefabcdeffULL,rnum64);
+ Datagram::Close(socket);
+}
+
+TEST(Datagram,TwoPortTest) {
+ int sock1 = Datagram::Bind(10001);
+ int sock2 = Datagram::Bind(10002);
+ ASSERT_TRUE(sock1>0);
+ ASSERT_TRUE(sock2>0);
+ struct sockaddr_in addr1, addr2;
+ addr1.sin_family = AF_INET;
+ addr1.sin_port = htons(10001);
+ addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr2.sin_family = AF_INET;
+ addr2.sin_port = htons(10002);
+ addr2.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ Datagram send(sock1,addr2);
+ send.Push32(1234);
+ send.Send();
+
+ int socks[2] = {sock1,sock2};
+ EXPECT_EQ(sock2,Datagram::Wait(2,socks));
+ Datagram recv(sock2);
+ recv.Recv();
+ uint32_t test = recv.Pull32();
+ ASSERT_EQ(1234,test);
+
+ Datagram::Close(sock1);
+ Datagram::Close(sock2);
+}
+
+int main (int argc, char** argv) {
+
+ testing::InitGoogleTest(&argc, argv);
+ google::InitGoogleLogging(argv[0]);
+ return RUN_ALL_TESTS();
+
+}
--- /dev/null
+#include "file.h"
+#include <gtest/gtest.h>
+
+TEST(FileTest,mmap) {
+ // open
+ // mmap
+ // unmap
+ // mmap
+ // read
+ // close
+ // open
+ // read fails
+ // mmap
+ // read
+ // close
+}
+
+TEST(FileTest,retrieval) {
+ // create with a root hash
+ // supply with hashes and data
+ // check peak hashes
+ // one broken packet
+ // check history
+ // close
+ // verify
+}
+
+TEST(FileTest,Streaming) {
+}
+
+int main (int argc, char** argv) {
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+
+}
--- /dev/null
+/*
+ * hashtest.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/12/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include <fcntl.h>
+#include "bin.h"
+#include <gtest/gtest.h>
+#include "hashtree.h"
+
+char hash123[] = "a8fdc205a9f19cc1c7507a60c4f01b13d11d7fd0";
+//char roothash123[] = "84e1e5f4b549fe072d803709eeb06143cd2ad736";
+
+TEST(Sha1HashTest,Trivial) {
+ Sha1Hash hash("123\n");
+ EXPECT_STREQ(hash123,hash.hex().c_str());
+}
+
+TEST(Sha1HashTest,HashTreeTest) {
+ Sha1Hash roothash123(hash123);
+ for(bin pos=1; pos<bin::ALL; pos=pos.parent())
+ roothash123 = Sha1Hash(roothash123,Sha1Hash::ZERO);
+ HashTree tree = HashTree(roothash123);
+ ASSERT_EQ(HashTree::PEAK_ACCEPT, tree.offer(bin(1),hash123));
+ ASSERT_TRUE(tree.rooted());
+}
+
+TEST(Sha1HashTest,HashFileTest) {
+ uint8_t a [1024], b[1024], c[1024];
+ memset(a,'a',1024);
+ memset(b,'b',1024);
+ memset(c,'c',1024);
+ Sha1Hash aaahash(a,1024), bbbhash(b,1024), ccchash(c,1024);
+ Sha1Hash abhash(aaahash,bbbhash), c0hash(ccchash,Sha1Hash::ZERO);
+ Sha1Hash aabbccroot(abhash,c0hash);
+ for(bin pos=bin(7); pos<bin::ALL; pos=pos.parent())
+ aabbccroot = Sha1Hash(aabbccroot,Sha1Hash::ZERO);
+ int f = open("testfile",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ write(f,a,1024);
+ write(f,b,1024);
+ write(f,c,1024);
+ HashTree filetree(f);
+ close(f);
+ ASSERT_TRUE(aabbccroot==filetree.root);
+ EXPECT_EQ(2,filetree.peaks.size());
+ EXPECT_TRUE(aaahash==filetree[1]);
+ HashTree bootstree(filetree.root);
+ EXPECT_EQ( HashTree::DUNNO, bootstree.offer(filetree.peaks[0].first,filetree.peaks[0].second) );
+ EXPECT_EQ( HashTree::PEAK_ACCEPT, bootstree.offer(filetree.peaks[1].first,filetree.peaks[1].second) );
+ EXPECT_EQ( 3, bootstree.length );
+ EXPECT_EQ( 4, bootstree.mass );
+ EXPECT_EQ( HashTree::DUNNO, bootstree.offer(1,aaahash) );
+ EXPECT_EQ( HashTree::ACCEPT, bootstree.offer(2,bbbhash) );
+ EXPECT_TRUE ( bootstree.bits[3]==abhash );
+ EXPECT_TRUE ( bootstree.bits[1]==aaahash );
+ EXPECT_TRUE ( bootstree.bits[2]==bbbhash );
+ EXPECT_FALSE ( bootstree.bits[2]==aaahash );
+}
+
+int main (int argc, char** argv) {
+ bin::init();
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+
+}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <vector>
+#include <deque>
+#include "datagram.h"
+#include "p2tp.h"
+#include <gtest/gtest.h>
+#include <glog/logging.h>
+
+using namespace p2tp;
+using namespace std;
+
+/**
+ TODO
+ * losses
+ * smooth rate
+ * seq 12345 stop
+ * busy pipe => negative cwnd
+*/
+
+TEST(Datagram,LedbatTest) {
+
+ int MAX_REORDERING = 3;
+ tint TARGET = 25*MSEC;
+ float GAIN = 1.0/TARGET;
+ int seq_off = 0;
+ float cwnd = 1;
+ tint DELAY_BIN = SEC*30;
+ tint min_delay = NEVER;
+ tint rtt_avg = NEVER>>4, dev_avg = NEVER>>4;
+ tint last_bin_time = 0;
+ tint last_drop_time = 0;
+ int delay_bin = 0;
+ deque<tint> history, delay_history;
+ tint min_delay_bins[4] = {NEVER,NEVER,
+ NEVER,NEVER};
+ tint cur_delays[4] = {NEVER,NEVER,
+ NEVER,NEVER};
+ tint last_sec = 0;
+ int sec_ackd = 0;
+
+ int send_sock = Datagram::Bind(10001); // bind sending socket
+ int ack_sock = Datagram::Bind(10002); // bind receiving socket
+ struct sockaddr_in send_to, ack_to;
+ send_to.sin_family = AF_INET;
+ send_to.sin_port = htons(10002);
+ send_to.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ ack_to.sin_family = AF_INET;
+ ack_to.sin_port = htons(10001);
+ ack_to.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ uint8_t* garbage = (uint8_t*) malloc(1024);
+ int socks[2] = {send_sock,ack_sock};
+ int sock2read;
+ tint wait_time = 100*MSEC;
+
+ while (sock2read = Datagram::Wait(2,socks,wait_time)) {
+ tint now = Datagram::Time();
+ if (sock2read==ack_sock) {
+ Datagram data(ack_sock); // send an acknowledgement
+ data.Recv();
+ int seq = data.Pull32();
+ Datagram ack(ack_sock,ack_to);
+ ack.Push32(seq);
+ ack.Push64(now);
+ if (4+8!=ack.Send())
+ fprintf(stderr,"short write\n");
+ fprintf(stderr,"%lli rcvd%i\n",now/SEC,seq);
+ // TODO: peer cwnd !!!
+ continue;
+ }
+ if (sock2read==send_sock) { // process an acknowledgement
+ Datagram ack(send_sock);
+ ack.Recv();
+ int seq = ack.Pull32();
+ tint arrival_time = ack.Pull64();
+ seq -= seq_off;
+ if (seq<0)
+ continue;
+ if (seq>=history.size())
+ continue;
+ if (history[seq]==0)
+ continue;
+ tint send_time = history[seq];
+ history[seq] = 0;
+ if (seq>MAX_REORDERING*2) { //loss
+ if (last_drop_time<now-rtt_avg) {
+ cwnd /= 2;
+ last_drop_time = now;
+ }
+ fprintf(stderr,"got %i. LOSS, cwnd drop: %f\n",seq,cwnd);
+ for(int i=0; i<MAX_REORDERING*2 && history.size(); i++) {
+ seq_off++;
+ history.pop_front();
+ }
+ continue;
+ }
+ tint delay = arrival_time - send_time;
+ if (seq==0 && seq_off==0) { // FIXME
+ rtt_avg = now - send_time;
+ dev_avg = rtt_avg;
+ }
+ if (send_time/DELAY_BIN != last_bin_time) {
+ last_bin_time = send_time/DELAY_BIN;
+ delay_bin = (delay_bin+1) % 4;
+ min_delay_bins[delay_bin] = NEVER;
+ min_delay = NEVER;
+ for(int i=0;i<4;i++)
+ if (min_delay_bins[i]<min_delay)
+ min_delay = min_delay_bins[i];
+ }
+ if (min_delay_bins[delay_bin] > delay)
+ min_delay_bins[delay_bin] = delay;
+ if (delay < min_delay)
+ min_delay = delay;
+ cur_delays[(seq_off+seq)%4] = delay;
+ tint current_delay = NEVER;
+ for(int i=0; i<4; i++)
+ if (current_delay > cur_delays[i])
+ current_delay = cur_delays[i]; // FIXME avg
+ tint queueing_delay = current_delay - min_delay;
+ // adjust cwnd
+ tint off_target = TARGET - queueing_delay;
+ //cerr<<"\t"<<cwnd<<"+="<<GAIN<<"*"<<off_target<<"/"<<cwnd<<endl;
+ cwnd += GAIN * off_target / cwnd;
+ fprintf(stderr,"ackd cwnd%f cur%lli min%lli seq%i off%i\n",
+ cwnd,current_delay,min_delay,seq_off+seq,seq);
+
+ if (now/SEC!=last_sec/SEC) {
+ fprintf(stderr,"%i KB/sec\n",sec_ackd);
+ sec_ackd = 0;
+ last_sec = now; // FIXME
+ } else
+ sec_ackd++;
+
+ } // if
+ while (history[0]==0 && history.size()) {
+ history.pop_front();
+ seq_off++;
+ }
+ if (history.size() && history[0]<now-rtt_avg-5*dev_avg) {
+ if (last_drop_time<now-rtt_avg) {
+ cwnd /= 2;
+ last_drop_time = now;
+ }
+ fprintf(stderr,"TIMEOUT LOSS, cwnd drop: %f\n",cwnd);
+ seq_off++;
+ history.pop_front();
+ }
+ // fill cwnd
+ if (history.size()<cwnd) {
+ int sendseq = history.size() + seq_off;
+ Datagram send(send_sock,send_to);
+ send.Push32(sendseq);
+ send.Push(garbage,1024);
+ history.push_back(now);
+ fprintf(stderr,"sent%i\n",sendseq);
+ if (4+1024!=send.Send())
+ fprintf(stderr,"short data write\n");
+ }
+ if (cwnd<1)
+ cwnd = 1;
+ if (history.size()<cwnd)
+ wait_time = rtt_avg/cwnd;
+ else
+ wait_time = 100*MSEC;
+ } // while
+}
+
+int main (int argc, char** argv) {
+ printf("Warning: use the script to set up dummynet!\n");
+ testing::InitGoogleTest(&argc, argv);
+ google::InitGoogleLogging(argv[0]);
+ return RUN_ALL_TESTS();
+}
--- /dev/null
+/*
+ * readwrite.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/19/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+
+#include <gtest/gtest.h>
+#include "p2tp.h"
+
+TEST(P2TP, ConnectTest) {
+ P2File("");
+ p2tp_init(7001);
+
+ int tf = p2tp_open("test_file",NULL);
+ int tb = p2tp_open("test_file_copy",p2tp_file_info(tf)->hash_data);
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(7001);
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ p2tp_add_peer(tb,addr,0); // TRICK: will open a channel to the first file
+ p2tp_loop(P2TP::now()+TINT1SEC/10);
+
+ while (count=copy.read(bytes)) {
+ read(orig,bytes2,count);
+ ASSERT_EQ ( 0, memcmp(bytes,bytes2,count) );
+ }
+
+ p2tp_close(tb);
+ p2tp_close(tf);
+
+}
+
+int main (int argc, char** argv) {
+ P2TP::init();
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+
+}
--- /dev/null
+/*
+ * sbit2test.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 4/1/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+
+#include "bin.h"
+#include "sbit.h"
+#include <gtest/gtest.h>
+
+TEST(SbitTest,Init) {
+ bins s;
+ s.set(1);
+ EXPECT_TRUE(s.get(1));
+ EXPECT_FALSE(s.get(2));
+ EXPECT_FALSE(s.get(3));
+ s.set(3);
+ EXPECT_TRUE(s.get(1));
+ EXPECT_TRUE(s.get(2));
+ EXPECT_TRUE(s.get(3));
+}
+
+
+TEST(SbitTest,Expand) {
+ bins s;
+ s.set(1);
+ s.set(34);
+ EXPECT_TRUE(s.get(1));
+ EXPECT_FALSE(s.get(2));
+ EXPECT_TRUE(s.get(32));
+ EXPECT_TRUE(s.get(33));
+ EXPECT_FALSE(s.get(35));
+ s.set(31);
+ s.set(62);
+ EXPECT_TRUE(s.get(3));
+ EXPECT_TRUE(s.get(63));
+}
+
+
+TEST(SbitTest,Chess) {
+ bins s;
+ s.set(7);
+ s.set(22);
+ EXPECT_FALSE(s.get(14));
+ EXPECT_FALSE(s.get(30));
+ EXPECT_FALSE(s.get(29));
+ s.set(29);
+ EXPECT_FALSE(s.get(31));
+ s.set(10);
+ EXPECT_FALSE(s.get(31));
+ s.set(11);
+ EXPECT_FALSE(s.get(31));
+ s.set(12);
+ EXPECT_TRUE(s.get(31));
+}
+
+
+TEST(SbitTest,Hole) {
+ bins b;
+ int h=14;
+ for(int i=0; i<h; i++)
+ b.set(bin(i,1));
+ ASSERT_FALSE(b.get(bin(h,0)));
+ for(int i=1; i<=bin(h,0); i++)
+ ASSERT_EQ( !bin::all1(i), b.get(bin(i)) );
+ b.set(1);
+ ASSERT_TRUE(b.get(bin(h,0)));
+}
+
+
+TEST(SbitTest,Zebra) {
+ bins b;
+ int height=9, width=1<<height;
+ bin peak = bin(height+3,0);
+ bin base[1024];
+ for(int i=0; i<width; i+=1)
+ base[i] = bin(3,i);
+ for(int j=0; j<3; j++) {
+ for(int i=0; i<width; i+=1) {
+ base[i] = base[i].left();
+ b.set(base[i]);
+ base[i] = base[i].sibling();
+ }
+ }
+ ASSERT_FALSE(b.get(peak));
+ for(int i=0; i<width; i+=1)
+ b.set(base[i]);
+ ASSERT_TRUE(b.get(peak));
+}
+
+
+TEST(SbitTest,Overlaps) {
+ bins b;
+ for(int i=0; i<400; i++)
+ b.set(bin(0,1000+i*32));
+ b.set(63);
+ b.set(15);
+ b.set(27);
+ b.set(9);
+ EXPECT_EQ(true,b.get(9));
+ EXPECT_EQ(true,b.get(30));
+ EXPECT_EQ(true,b.get(63));
+ EXPECT_EQ(false,b.get(65));
+}
+
+
+TEST(SbitTest,Random) {
+ for(int round=0; round<100; round++) {
+ bin::vec v;
+ bins b;
+ for(int i=0; i<40; i++) {
+ bin x = random()%8191+1;
+ //printf("(%i,%i) ",x.layer(),x.offset());
+ v.push_back(x);
+ b.set(x);
+ }
+ //printf("\n> ");
+ bin::order(&v);
+ ///for(int i=0; i<v.size(); i++)
+ // printf("(%i,%i) ",v[i].layer(),v[i].offset());
+ //printf("\n");
+ for(int i=0; i<v.size(); i++) {
+ EXPECT_TRUE(b.get(v[i]))<<"where ("<<(int)v[i].layer()<<","<<v[i].offset()<<")";
+ if (!b.get(v[i]))
+ b.get(v[i]);
+ }
+ for(int n=1; n<8192; n+=rand()%10) {
+ bool in = false;
+ for(int i=0; i<v.size(); i++)
+ if (v[i].contains(n))
+ in = true;
+ EXPECT_EQ(in,b.get(n));
+ }
+ }
+}
+
+
+TEST(SbitTest,Full) {
+ bins b;
+ for(int i=0; i<32; i++)
+ b.set(bin(0,i));
+ ASSERT_TRUE(b.get(63));
+}
+
+
+TEST(SbitTest,OrAnd) {
+ bins a, b;
+ a.set(15);
+ b.set(30);
+ b.set(62);
+ a|=b;
+ EXPECT_TRUE(a.get(63));
+ a&=b;
+ EXPECT_TRUE(a.get(30));
+ EXPECT_TRUE(a.get(62));
+ EXPECT_FALSE(a.get(15));
+ EXPECT_FALSE(a.get(63));
+}
+
+
+TEST(SbitTest, Iterator) {
+ bins b;
+ b.set(7);
+ b.set(14);
+ b.set(18);
+ b.set(21);
+ bin::vec memo;
+ for (bins::bin_iterator i = b.begin(); i!=b.end(); ++i)
+ memo.push_back(*i);
+ //for(int i=0; i<memo.size(); i++)
+ // printf("%i\n",(int)memo[i]);
+ EXPECT_EQ(2,memo.size());
+ EXPECT_EQ(15,memo[0]);
+ EXPECT_EQ(22,memo[1]);
+}
+
+
+TEST(SbitTest,Diff) {
+ bins a,b;
+ a.set(127);
+ b.set(1);
+ a-=b;
+ EXPECT_EQ(false,a.get(3));
+ bins::bin_iterator i = a.begin();
+ EXPECT_EQ(2,*i);
+ ++i;
+ EXPECT_EQ(6,*i);
+ ++i;
+ EXPECT_EQ(14,*i);
+ ++i;
+ EXPECT_EQ(30,*i);
+ ++i;
+ EXPECT_EQ(62,*i);
+ ++i;
+ EXPECT_EQ(126,*i);
+ ++i;
+ EXPECT_TRUE(i==a.end());
+
+ bins c,d;
+ c.set(1023);
+ d.set(31);
+ d.set(32);
+ c-=d;
+ bins::bin_iterator j = c.begin();
+ EXPECT_EQ(33,*j);
+}
+
+
+int main (int argc, char** argv) {
+ bin::init();
+ bins::init();
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+
+}
--- /dev/null
+/*
+ * sbittest.cpp
+ * serp++
+ *
+ * Created by Victor Grishchenko on 3/9/09.
+ * Copyright 2009 Delft Technical University. All rights reserved.
+ *
+ */
+#include "bin.h"
+#include "sbit.h"
+#include <gtest/gtest.h>
+
+TEST(SierpinskyBitmapTest,Init) {
+ uint64_t one32 = (1ULL<<32)-1;
+ uint64_t mask = one32;
+ uint64_t mask_history[5];
+ for(int i=0; i<5; i++) {
+ mask_history[i] = mask;
+ mask = sbit::join(bin(i+1,0),mask,0);
+ }
+ EXPECT_EQ(1,mask)<<"joined OK";
+ for(int i=5; i; i--) {
+ uint64pair p = sbit::split(bin(i,0),mask);
+ mask = p.first;
+ EXPECT_EQ(mask_history[i-1],mask)<<"mask history check";
+ }
+ EXPECT_EQ(one32,mask)<<"join/split cycle";
+
+ EXPECT_EQ(3, sbit::MASKS[0][3])<<"the first two-bit interval, layer 0";
+ EXPECT_EQ(5, sbit::MASKS[1][3])<<"the first two-bit interval, layer 1";
+ EXPECT_EQ(0x000000000000000FLLU, sbit::MASKS[0][bin(2,0)])<<"the first four-bit interval @0";
+ EXPECT_EQ(0xFF00000000000000LLU, sbit::MASKS[0][bin(3,7)])<<"the last eight-bit interval @0";
+ EXPECT_EQ(0xAAAA000000000000LLU, sbit::MASKS[1][bin(3,7)])<<"the last eight-bit interval @1";
+ EXPECT_EQ(0x8080808080808080LLU, sbit::MASKS[3][bin(3,7)])<<"the last eight-bit interval @3";
+ EXPECT_EQ(1<<16, sbit::MASKS[0][bin(0,16)])<< "trivial: bit 16";
+ EXPECT_EQ(0x100000000LLU, sbit::MASKS[1][bin(0,16)])<< "trivial: bit 16 layer 1";
+ EXPECT_EQ(2, sbit::MASKS[2][bin(0,16)])<< "layout: bit wrap, 16 @layer 2";
+}
+
+TEST(SierpinskyBitmapTest,GetSetAggregation) {
+ sbit s;
+ s.set(3);
+ EXPECT_EQ(true,s.get(1)) << "trivial set/retr";
+ EXPECT_EQ(false,s.get(31)) << "trivial 0";
+ s.set(14);
+ s.set(30);
+ EXPECT_EQ(false,s.get(31)) << "trivial 0";
+ s.set(6);
+ EXPECT_EQ(true,s.get(31)) << "aggregated set/retr";
+ s.set(127);
+ EXPECT_EQ(true,s.get(127)) << "just extension";
+ EXPECT_EQ(false,s.get(255)) << "just extension";
+ s.set(254);
+ EXPECT_EQ(true,s.get(255)) << "aggregated with extension";
+}
+
+TEST(SierpinskyBitmapTest,FishEyeBitmaps) {
+ sbit s; // 2^8 = 256 bits
+ s.set(bin(7,0));
+ s.set(bin(5,4));
+ s.set(bin(1,128));
+ s.set(bin(2,80));
+ feye eye(s, bin(6,2));
+ // water-ground-air-mountain set/get
+ EXPECT_EQ( eye.bits[2], sbit::MASK1 ) << "all-1 chunk";
+ EXPECT_EQ( sbit::MASKS[2][bin(0,16)], eye.bits[3] ) << "all-1 chunk";
+ EXPECT_EQ( true, eye.get(bin(5,4)) ) << "MOUNTAIN 1-half";
+ EXPECT_EQ( false, eye.get(bin(5,5)) ) << "MOUNTAIN 0-half";
+ EXPECT_EQ( true, eye.get(bin(6,1)) ) << "MOUNTAIN all-ones";
+ EXPECT_EQ( true, eye.get(bin(7,0)) ) << "top-MOUNTAIN 7 layer ones";
+ EXPECT_EQ( false, eye.get(bin(7,1)) ) << "AIR just a quarter is 1, must be 0";
+ EXPECT_EQ( true, eye.get(bin(2,80)) ) << "GROUND 4-bit fragment is saved";
+ EXPECT_EQ( true, eye.get(bin(1,160)) )
+ << "WATER 2-bit fragment is saved (farther from the focus)";
+ EXPECT_EQ( false, eye.get(bin(1,128)) )
+ << "sunk-WATER 2-bit fragment is lost (far from the focus)";
+ // refocus
+ feye triv(eye, bin(0,64*3));
+ EXPECT_EQ(triv.bits[0],eye.bits[1])<<"trivial refocus 1";
+ EXPECT_EQ(triv.bits[1],eye.bits[0])<<"trivial refocus 2";
+ EXPECT_EQ(triv.bits[2],eye.bits[2])<<"trivial refocus 3";
+ feye ref(eye, bin(0,64*8));
+ EXPECT_EQ (false, ref.get(bin(2,80)) ) << "after refocusing, the 4-bit fragment is lost";
+ EXPECT_EQ (true, ref.get(bin(7,0)) ) << "but 7l (128bits) fragment is still there";
+ EXPECT_EQ (sbit::MASKS[3][bin(4,0)]|sbit::MASKS[3][bin(2,4)],ref.bits[4]) << "gather OK";
+
+}
+
+
+TEST(SierpinskyBitmapTest,BitwiseOps) {
+ feye l(bin(6,2));
+ feye r(bin(6,2));
+ l.set(bin(1,0));
+ r.set(bin(1,1));
+ feye orf(l);
+ orf |= r;
+ EXPECT_EQ (true, orf.get(bin(2,0))) << "OR adds up correctly";
+ EXPECT_EQ (false, orf.get(bin(2,1))) << "OR adds up correctly";
+ feye andf = l&r;
+ EXPECT_EQ (false, andf.get(bin(2,0)) ) << "AND adds up correctly";
+ feye eye(bin(18,1));
+ eye.set(bin::ALL);
+ EXPECT_EQ(true,eye.get(bin::ALL)) << "bin_ALL";
+ EXPECT_EQ(true,eye.get(bin(12,34))) << "any in bin_ALL";
+ EXPECT_EQ(sbit::MASK1,eye.bits[24]) << "bin_ALL: all 1";
+}
+
+
+bin bin_random () {
+ return rand()%bin::ALL;
+}
+
+
+TEST (SierpinskyBitmapTest,CryBabyCry) {
+ for (int i=0; i<10000; i++) {
+ bin point = bin_random();
+ bin focus = bin(0,rand()%(1<<30));
+ feye f(focus);
+ f.set(point);
+ bin cp = point.commonParent(focus);
+ uint8_t cpheight = cp.layer();
+ uint8_t pointheight = point.layer();
+ uint8_t topheight = cp==focus? 6 : cpheight-1 ;
+ bool visible = topheight-6 <= pointheight;
+ EXPECT_EQ (visible, f.get(point)) <<
+ "FAIL: another random test: point "<<point<<" focus "<<focus;
+
+ bin refocus (0,rand()%(1<<30));
+ feye ref(f, refocus);
+ bin recp = point.commonParent(refocus);
+ uint8_t recpheight = recp.layer();
+ uint8_t retopheight = recp==refocus? 6 : recpheight-1 ;
+ bool revisible = retopheight-6 <= pointheight;
+ EXPECT_EQ (visible&&revisible, ref.get(point))
+ << "FAIL: another random refocus: point "<<point<<
+ " focus "<<focus<<" refocus "<<refocus;
+
+ if (visible&&revisible) {
+ EXPECT_FALSE (pointheight<30 && ref.get(point.parent()))
+ << "FAIL: mystically, parent is 1\n";
+ EXPECT_FALSE (pointheight>0 && !ref.get(point.left()))
+ << "FAIL: mystically, left child is 0\n";
+ /*EXPECT_FALSE (pointheight>0 && !ref.get(bin_rightn(point,pointheight)))
+ << "FAIL: mystically, the rightmost bit is 0\n";*/
+ }
+
+ }
+
+}
+
+
+TEST(FishEyeBitmap, OrTest) {
+ feye a(bin(0,0)), b(bin(0,256));
+ a.set(bin(2,64)); // 256-259
+ feye ach(a,bin(0,256));
+ EXPECT_TRUE(ach.get(bin(2,64)));
+ b.set(bin(0,260));
+ b.set(bin(0,261));
+ b.set(bin(0,262));
+ b.set(bin(0,263));
+ EXPECT_TRUE(b.get(bin(2,65)));
+ b.refocus(bin(0,257));
+ EXPECT_TRUE(b.get(bin(2,65)));
+ b |= a;
+ EXPECT_TRUE(b.get(bin(2,65).parent()));
+}
+
+
+int main (int argc, char** argv) {
+ bin::init();
+ sbit::init();
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+
+}