import
authorvictor <victor@e16421f0-f15b-0410-abcd-98678b794739>
Thu, 17 Sep 2009 10:53:44 +0000 (10:53 +0000)
committervictor <victor@e16421f0-f15b-0410-abcd-98678b794739>
Thu, 17 Sep 2009 10:53:44 +0000 (10:53 +0000)
git-svn-id: https://ttuki.vtt.fi/svn/p2p-next/p2tp@380 e16421f0-f15b-0410-abcd-98678b794739

37 files changed:
.sconsign.dblite [new file with mode: 0644]
SConstruct [new file with mode: 0644]
bin.cpp [new file with mode: 0644]
bin.h [new file with mode: 0644]
bin64.h [new file with mode: 0644]
bins.cpp [new file with mode: 0644]
bins.h [new file with mode: 0644]
collisions.cpp [new file with mode: 0644]
congctrl.cpp [new file with mode: 0644]
datagram.cpp [new file with mode: 0644]
datagram.h [new file with mode: 0644]
do_tests.sh [new file with mode: 0755]
doc/lancaster.txt [new file with mode: 0644]
doc/p2tp-protocol.txt [new file with mode: 0644]
file.h [new file with mode: 0644]
hashtree.cpp [new file with mode: 0644]
hashtree.h [new file with mode: 0644]
p2tp.cpp [new file with mode: 0644]
p2tp.h [new file with mode: 0644]
rarest1st.cpp [new file with mode: 0644]
sbit.cpp [new file with mode: 0644]
sbit.h [new file with mode: 0644]
sendrecv.cpp [new file with mode: 0644]
serp++.1 [new file with mode: 0644]
tests/SConscript [new file with mode: 0644]
tests/bin64test.cpp [new file with mode: 0644]
tests/binstest.cpp [new file with mode: 0644]
tests/bintest.cpp [new file with mode: 0644]
tests/congctrltest.cpp [new file with mode: 0644]
tests/connecttest.cpp [new file with mode: 0644]
tests/dgramtest.cpp [new file with mode: 0644]
tests/filetest.cpp [new file with mode: 0644]
tests/hashtest.cpp [new file with mode: 0644]
tests/ledbattest.cpp [new file with mode: 0644]
tests/rwtest.cpp [new file with mode: 0644]
tests/sbit2test.cpp [new file with mode: 0644]
tests/sbittest.cpp [new file with mode: 0644]

diff --git a/.sconsign.dblite b/.sconsign.dblite
new file mode 100644 (file)
index 0000000..fa060ce
Binary files /dev/null and b/.sconsign.dblite differ
diff --git a/SConstruct b/SConstruct
new file mode 100644 (file)
index 0000000..f1d6cfe
--- /dev/null
@@ -0,0 +1,14 @@
+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')
diff --git a/bin.cpp b/bin.cpp
new file mode 100644 (file)
index 0000000..187dde4
--- /dev/null
+++ b/bin.cpp
@@ -0,0 +1,59 @@
+/*
+ *  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());
+}
diff --git a/bin.h b/bin.h
new file mode 100644 (file)
index 0000000..3742b88
--- /dev/null
+++ b/bin.h
@@ -0,0 +1,234 @@
+/*
+ *  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
diff --git a/bin64.h b/bin64.h
new file mode 100644 (file)
index 0000000..da45678
--- /dev/null
+++ b/bin64.h
@@ -0,0 +1,98 @@
+#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
diff --git a/bins.cpp b/bins.cpp
new file mode 100644 (file)
index 0000000..4dd1e3d
--- /dev/null
+++ b/bins.cpp
@@ -0,0 +1,23 @@
+/*
+ *  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
+}
diff --git a/bins.h b/bins.h
new file mode 100644 (file)
index 0000000..d95406f
--- /dev/null
+++ b/bins.h
@@ -0,0 +1,91 @@
+/*
+ *  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
+
+};
diff --git a/collisions.cpp b/collisions.cpp
new file mode 100644 (file)
index 0000000..24c3173
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ *  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");
+}
+
diff --git a/congctrl.cpp b/congctrl.cpp
new file mode 100644 (file)
index 0000000..451832d
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ *  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_;
+}
diff --git a/datagram.cpp b/datagram.cpp
new file mode 100644 (file)
index 0000000..3c2794a
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ *  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;
+}
+
+}
diff --git a/datagram.h b/datagram.h
new file mode 100644 (file)
index 0000000..5ff689b
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ *  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
diff --git a/do_tests.sh b/do_tests.sh
new file mode 100755 (executable)
index 0000000..d478ec6
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+for tst in `ls tests/*test`; do
+    $tst
+done
diff --git a/doc/lancaster.txt b/doc/lancaster.txt
new file mode 100644 (file)
index 0000000..d4d79cb
--- /dev/null
@@ -0,0 +1,27 @@
+* 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
+
diff --git a/doc/p2tp-protocol.txt b/doc/p2tp-protocol.txt
new file mode 100644 (file)
index 0000000..9a00ec8
--- /dev/null
@@ -0,0 +1,485 @@
+
+        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
diff --git a/file.h b/file.h
new file mode 100644 (file)
index 0000000..29e8f6b
--- /dev/null
+++ b/file.h
@@ -0,0 +1,62 @@
+#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*);
+
+       };
+
+};
diff --git a/hashtree.cpp b/hashtree.cpp
new file mode 100644 (file)
index 0000000..6c17892
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ *  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;
+       
+}
+
+
diff --git a/hashtree.h b/hashtree.h
new file mode 100644 (file)
index 0000000..f4bdb4e
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ *  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
diff --git a/p2tp.cpp b/p2tp.cpp
new file mode 100644 (file)
index 0000000..fe540bc
--- /dev/null
+++ b/p2tp.cpp
@@ -0,0 +1,278 @@
+/*
+ *  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;
+}
+
diff --git a/p2tp.h b/p2tp.h
new file mode 100644 (file)
index 0000000..45a2ea6
--- /dev/null
+++ b/p2tp.h
@@ -0,0 +1,285 @@
+/*
+ *  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
diff --git a/rarest1st.cpp b/rarest1st.cpp
new file mode 100644 (file)
index 0000000..4c1552a
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  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;
+}
diff --git a/sbit.cpp b/sbit.cpp
new file mode 100644 (file)
index 0000000..d56b2e4
--- /dev/null
+++ b/sbit.cpp
@@ -0,0 +1,254 @@
+/*
+ *  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
diff --git a/sbit.h b/sbit.h
new file mode 100644 (file)
index 0000000..edb84a6
--- /dev/null
+++ b/sbit.h
@@ -0,0 +1,177 @@
+/*
+ *  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
diff --git a/sendrecv.cpp b/sendrecv.cpp
new file mode 100644 (file)
index 0000000..a222843
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ *  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));
+}
+
+
diff --git a/serp++.1 b/serp++.1
new file mode 100644 (file)
index 0000000..06b9ab9
--- /dev/null
+++ b/serp++.1
@@ -0,0 +1,79 @@
+.\"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
diff --git a/tests/SConscript b/tests/SConscript
new file mode 100644 (file)
index 0000000..21bad71
--- /dev/null
@@ -0,0 +1,44 @@
+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='..' )
+
diff --git a/tests/bin64test.cpp b/tests/bin64test.cpp
new file mode 100644 (file)
index 0000000..a7c4c12
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *  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();
+       
+}
diff --git a/tests/binstest.cpp b/tests/binstest.cpp
new file mode 100644 (file)
index 0000000..caa51b3
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ *  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();
+}
diff --git a/tests/bintest.cpp b/tests/bintest.cpp
new file mode 100644 (file)
index 0000000..7f6dadc
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ *  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();
+       
+}
diff --git a/tests/congctrltest.cpp b/tests/congctrltest.cpp
new file mode 100644 (file)
index 0000000..769fec2
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ *  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();
+       
+}
diff --git a/tests/connecttest.cpp b/tests/connecttest.cpp
new file mode 100644 (file)
index 0000000..d831a5e
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ *  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;
+       
+}
diff --git a/tests/dgramtest.cpp b/tests/dgramtest.cpp
new file mode 100644 (file)
index 0000000..3e27baa
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ *  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();
+       
+}
diff --git a/tests/filetest.cpp b/tests/filetest.cpp
new file mode 100644 (file)
index 0000000..952bcf9
--- /dev/null
@@ -0,0 +1,36 @@
+#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();
+       
+}
diff --git a/tests/hashtest.cpp b/tests/hashtest.cpp
new file mode 100644 (file)
index 0000000..527d2cc
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ *  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();
+       
+}
diff --git a/tests/ledbattest.cpp b/tests/ledbattest.cpp
new file mode 100644 (file)
index 0000000..1e69efc
--- /dev/null
@@ -0,0 +1,174 @@
+#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();
+}
diff --git a/tests/rwtest.cpp b/tests/rwtest.cpp
new file mode 100644 (file)
index 0000000..1c80845
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ *  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();
+       
+}
diff --git a/tests/sbit2test.cpp b/tests/sbit2test.cpp
new file mode 100644 (file)
index 0000000..4251d5a
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ *  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();
+       
+}
diff --git a/tests/sbittest.cpp b/tests/sbittest.cpp
new file mode 100644 (file)
index 0000000..c321ac9
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ *  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();
+       
+}