diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b9beea618..5810e9613 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -32,10 +32,26 @@ "ImportPath": "github.com/alecthomas/units", "Rev": "6b4e7dc5e3143b85ea77909c72caf89416fc2915" }, + { + "ImportPath": "github.com/anacrolix/jitter", + "Rev": "2ea5c18645100745b24e9f5cfc9b3f6f7eac51ef" + }, + { + "ImportPath": "github.com/anacrolix/missinggo", + "Rev": "4e1ca5963308863b56c31863f60c394a7365ec29" + }, + { + "ImportPath": "github.com/anacrolix/utp", + "Rev": "0bb24de92c268452fb9106ca4fb9302442ca0dee" + }, { "ImportPath": "github.com/beorn7/perks/quantile", "Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d" }, + { + "ImportPath": "github.com/bradfitz/iter", + "Rev": "454541ec3da2a73fc34fd049b19ee5777bf19345" + }, { "ImportPath": "github.com/bren2010/proquint", "Rev": "5958552242606512f714d2e93513b380f43f9991" @@ -104,10 +120,6 @@ "ImportPath": "github.com/golang/protobuf/proto", "Rev": "aece6fb931241ad332956db4f62798dfbea944b3" }, - { - "ImportPath": "github.com/h2so5/utp", - "Rev": "6ca83358f5c331028feb9b97c445e9c7354967b0" - }, { "ImportPath": "github.com/hashicorp/golang-lru", "Rev": "253b2dc1ca8bae42c3b5b6e53dd2eab1a7551116" @@ -171,7 +183,7 @@ }, { "ImportPath": "github.com/jbenet/go-multiaddr-net", - "Rev": "6b29a00b65526d23f534813eb5bfa64dfa281e4a" + "Rev": "4a8bd8f8baf45afcf2bb385bbc17e5208d5d4c71" }, { "ImportPath": "github.com/jbenet/go-multihash", diff --git a/Godeps/_workspace/src/github.com/anacrolix/jitter/jitter.go b/Godeps/_workspace/src/github.com/anacrolix/jitter/jitter.go new file mode 100644 index 000000000..7f0f39b8f --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/jitter/jitter.go @@ -0,0 +1,12 @@ +package jitter + +import ( + "math/rand" + "time" +) + +func Duration(average, plusMinus time.Duration) (ret time.Duration) { + ret = average - plusMinus + ret += time.Duration(rand.Int63n(2*int64(plusMinus) + 1)) + return +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/addr.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/addr.go new file mode 100644 index 000000000..7d7a4f8ba --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/addr.go @@ -0,0 +1,72 @@ +package missinggo + +import ( + "net" + "strconv" + "strings" +) + +type HostMaybePort struct { + Host string + Port int + NoPort bool +} + +func (me HostMaybePort) String() string { + if me.NoPort { + return me.Host + } + return net.JoinHostPort(me.Host, strconv.FormatInt(int64(me.Port), 10)) +} + +func SplitHostPort(hostport string) (ret HostMaybePort) { + host, port, err := net.SplitHostPort(hostport) + if err != nil { + if strings.Contains(err.Error(), "missing port") { + ret.Host = hostport + ret.NoPort = true + return + } + panic(err) + } + i64, err := strconv.ParseInt(port, 0, 0) + ret.Host = host + ret.Port = int(i64) + if err != nil { + ret.NoPort = true + } + return +} + +// Extracts the port as an integer from an address string. +func AddrPort(addr net.Addr) int { + switch raw := addr.(type) { + case *net.UDPAddr: + return raw.Port + default: + _, port, err := net.SplitHostPort(addr.String()) + if err != nil { + panic(err) + } + i64, err := strconv.ParseInt(port, 0, 0) + if err != nil { + panic(err) + } + return int(i64) + } +} + +func AddrIP(addr net.Addr) net.IP { + switch raw := addr.(type) { + case *net.UDPAddr: + return raw.IP + case *net.TCPAddr: + return raw.IP + default: + host, _, err := net.SplitHostPort(addr.String()) + if err != nil { + panic(err) + } + return net.ParseIP(host) + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/addr_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/addr_test.go new file mode 100644 index 000000000..96ffee0e6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/addr_test.go @@ -0,0 +1,17 @@ +package missinggo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSplitHostPort(t *testing.T) { + assert.EqualValues(t, HostMaybePort{"a", 1, false}, SplitHostPort("a:1")) + assert.EqualValues(t, HostMaybePort{"a", 0, true}, SplitHostPort("a")) +} + +func TestHostMaybePortString(t *testing.T) { + assert.EqualValues(t, "a:1", (HostMaybePort{"a", 1, false}).String()) + assert.EqualValues(t, "a", (HostMaybePort{"a", 0, true}).String()) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/args/args.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/args/args.go new file mode 100644 index 000000000..8f0d614f1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/args/args.go @@ -0,0 +1,15 @@ +package args + +import ( + "flag" + "fmt" + "os" +) + +func Parse() { + flag.Parse() + if flag.NArg() != 0 { + fmt.Fprintf(os.Stderr, "unexpected positional arguments\n") + os.Exit(2) + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime.go new file mode 100644 index 000000000..41ede5895 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime.go @@ -0,0 +1,11 @@ +package missinggo + +import ( + "os" + "time" +) + +// Extracts the access time from the FileInfo internals. +func FileInfoAccessTime(fi os.FileInfo) time.Time { + return fileInfoAccessTime(fi) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_darwin.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_darwin.go new file mode 100644 index 000000000..5ccbc434f --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_darwin.go @@ -0,0 +1,12 @@ +package missinggo + +import ( + "os" + "syscall" + "time" +) + +func fileInfoAccessTime(fi os.FileInfo) time.Time { + ts := fi.Sys().(*syscall.Stat_t).Atimespec + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_freebsd.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_freebsd.go new file mode 100644 index 000000000..5ccbc434f --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_freebsd.go @@ -0,0 +1,12 @@ +package missinggo + +import ( + "os" + "syscall" + "time" +) + +func fileInfoAccessTime(fi os.FileInfo) time.Time { + ts := fi.Sys().(*syscall.Stat_t).Atimespec + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_linux.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_linux.go new file mode 100644 index 000000000..e599d8b77 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_linux.go @@ -0,0 +1,12 @@ +package missinggo + +import ( + "os" + "syscall" + "time" +) + +func fileInfoAccessTime(fi os.FileInfo) time.Time { + ts := fi.Sys().(*syscall.Stat_t).Atim + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_windows.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_windows.go new file mode 100644 index 000000000..bedf8c3f2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/atime_windows.go @@ -0,0 +1,12 @@ +package missinggo + +import ( + "os" + "syscall" + "time" +) + +func fileInfoAccessTime(fi os.FileInfo) time.Time { + ts := fi.Sys().(syscall.Win32FileAttributeData).LastAccessTime + return time.Unix(0, int64(ts.Nanoseconds())) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/castslice.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/castslice.go new file mode 100644 index 000000000..7be8823a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/castslice.go @@ -0,0 +1,17 @@ +package missinggo + +import ( + "reflect" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/bradfitz/iter" +) + +func ConvertToSliceOfEmptyInterface(slice interface{}) (ret []interface{}) { + v := reflect.ValueOf(slice) + l := v.Len() + ret = make([]interface{}, 0, l) + for i := range iter.N(v.Len()) { + ret = append(ret, v.Index(i).Interface()) + } + return +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/go-env/main.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/go-env/main.go new file mode 100644 index 000000000..66a736e00 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/go-env/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + for _, v := range os.Environ() { + fmt.Printf("%s\n", v) + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/nop/main.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/nop/main.go new file mode 100644 index 000000000..38dd16da6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/nop/main.go @@ -0,0 +1,3 @@ +package main + +func main() {} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/query-escape/main.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/query-escape/main.go new file mode 100644 index 000000000..cddd9d93d --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/query-escape/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + "net/url" + "os" +) + +func main() { + fmt.Println(url.QueryEscape(os.Args[1])) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/query-unescape/main.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/query-unescape/main.go new file mode 100644 index 000000000..3099cc8df --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/cmd/query-unescape/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + "net/url" + "os" +) + +func main() { + fmt.Println(url.QueryUnescape(os.Args[1])) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/copy.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/copy.go new file mode 100644 index 000000000..79aa58ff7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/copy.go @@ -0,0 +1,32 @@ +package missinggo + +import ( + "fmt" + "reflect" +) + +func CopyExact(dest interface{}, src interface{}) { + dV := reflect.ValueOf(dest) + sV := reflect.ValueOf(src) + if dV.Kind() == reflect.Ptr { + dV = dV.Elem() + } + if dV.Kind() == reflect.Array && !dV.CanAddr() { + panic(fmt.Sprintf("dest not addressable: %T", dest)) + } + if sV.Kind() == reflect.Ptr { + sV = sV.Elem() + } + if sV.Kind() == reflect.String { + sV = sV.Convert(reflect.SliceOf(dV.Type().Elem())) + } + if !sV.IsValid() { + panic("invalid source, probably nil") + } + if dV.Len() != sV.Len() { + panic(fmt.Sprintf("dest len (%d) != src len (%d)", dV.Len(), sV.Len())) + } + if dV.Len() != reflect.Copy(dV, sV) { + panic("dammit") + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/copy_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/copy_test.go new file mode 100644 index 000000000..190f2a10f --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/copy_test.go @@ -0,0 +1,89 @@ +package missinggo + +import ( + "bytes" + "strings" + "testing" +) + +func TestCopyToArray(t *testing.T) { + var arr [3]byte + bb := []byte{1, 2, 3} + CopyExact(&arr, bb) + if !bytes.Equal(arr[:], bb) { + t.FailNow() + } +} + +func TestCopyToSlicedArray(t *testing.T) { + var arr [5]byte + CopyExact(arr[:], "hello") + if !bytes.Equal(arr[:], []byte("hello")) { + t.FailNow() + } +} + +func TestCopyDestNotAddr(t *testing.T) { + defer func() { + r := recover() + if r == nil { + t.FailNow() + } + t.Log(r) + }() + var arr [3]byte + CopyExact(arr, "nope") +} + +func TestCopyLenMismatch(t *testing.T) { + defer func() { + r := recover() + if r == nil { + t.FailNow() + } + t.Log(r) + }() + CopyExact(make([]byte, 2), "abc") +} + +func TestCopySrcString(t *testing.T) { + dest := make([]byte, 3) + CopyExact(dest, "lol") + if string(dest) != "lol" { + t.FailNow() + } + func() { + defer func() { + r := recover() + if r == nil { + t.FailNow() + } + }() + CopyExact(dest, "rofl") + }() + var arr [5]byte + CopyExact(&arr, interface{}("hello")) + if string(arr[:]) != "hello" { + t.FailNow() + } +} + +func TestCopySrcNilInterface(t *testing.T) { + var arr [3]byte + defer func() { + r := recover().(string) + if !strings.Contains(r, "invalid source") { + t.FailNow() + } + }() + CopyExact(&arr, nil) +} + +func TestCopySrcPtr(t *testing.T) { + var bigDst [1024]byte + var bigSrc [1024]byte = [1024]byte{'h', 'i'} + CopyExact(&bigDst, &bigSrc) + if !bytes.Equal(bigDst[:], bigSrc[:]) { + t.FailNow() + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/croak.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/croak.go new file mode 100644 index 000000000..ded86ef0b --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/croak.go @@ -0,0 +1,18 @@ +package missinggo + +import ( + "fmt" + "os" +) + +func Unchomp(s string) string { + if len(s) > 0 && s[len(s)-1] == '\n' { + return s + } + return s + "\n" +} + +func Fatal(msg interface{}) { + os.Stderr.WriteString(Unchomp(fmt.Sprint(msg))) + os.Exit(1) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/doc.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/doc.go new file mode 100644 index 000000000..9b8aa8ddd --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/doc.go @@ -0,0 +1,3 @@ +// Package missinggo contains miscellaneous helpers used in many of anacrolix' +// projects. +package missinggo diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/expvarIndentMap.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/expvarIndentMap.go new file mode 100644 index 000000000..c99529956 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/expvarIndentMap.go @@ -0,0 +1,35 @@ +package missinggo + +import ( + "bytes" + "expvar" + "fmt" +) + +type IndentMap struct { + expvar.Map +} + +var _ expvar.Var = &IndentMap{} + +func NewExpvarIndentMap(name string) *IndentMap { + v := new(IndentMap) + v.Init() + expvar.Publish(name, v) + return v +} + +func (v *IndentMap) String() string { + var b bytes.Buffer + fmt.Fprintf(&b, "{") + first := true + v.Do(func(kv expvar.KeyValue) { + if !first { + fmt.Fprintf(&b, ",") + } + fmt.Fprintf(&b, "\n\t%q: %v", kv.Key, kv.Value) + first = false + }) + fmt.Fprintf(&b, "}") + return b.String() +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/cache.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/cache.go new file mode 100644 index 000000000..734169c69 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/cache.go @@ -0,0 +1,269 @@ +package filecache + +import ( + "errors" + "log" + "os" + "path" + "path/filepath" + "sync" + "time" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" +) + +type Cache struct { + mu sync.Mutex + capacity int64 + filled int64 + items *lruItems + paths map[string]ItemInfo + root string +} + +type CacheInfo struct { + Capacity int64 + Filled int64 + NumItems int +} + +type ItemInfo struct { + Accessed time.Time + Size int64 + Path string +} + +// Calls the function for every item known to the cache. The ItemInfo should +// not be modified. +func (me *Cache) WalkItems(cb func(ItemInfo)) { + me.mu.Lock() + defer me.mu.Unlock() + for e := me.items.Front(); e != nil; e = e.Next() { + cb(e.Value().(ItemInfo)) + } +} + +func (me *Cache) Info() (ret CacheInfo) { + me.mu.Lock() + defer me.mu.Unlock() + ret.Capacity = me.capacity + ret.Filled = me.filled + ret.NumItems = len(me.paths) + return +} + +func (me *Cache) SetCapacity(capacity int64) { + me.mu.Lock() + defer me.mu.Unlock() + me.capacity = capacity +} + +func NewCache(root string) (ret *Cache, err error) { + if !filepath.IsAbs(root) { + err = errors.New("root is not an absolute filepath") + return + } + ret = &Cache{ + root: root, + capacity: -1, // unlimited + } + ret.mu.Lock() + go func() { + defer ret.mu.Unlock() + ret.rescan() + }() + return +} + +// An empty return path is an error. +func sanitizePath(p string) (ret string) { + if p == "" { + return + } + ret = path.Clean("/" + p) + if ret[0] == '/' { + ret = ret[1:] + } + return +} + +// Leaf is a descendent of root. +func pruneEmptyDirs(root string, leaf string) (err error) { + rootInfo, err := os.Stat(root) + if err != nil { + return + } + for { + var leafInfo os.FileInfo + leafInfo, err = os.Stat(leaf) + if os.IsNotExist(err) { + goto parent + } + if err != nil { + return + } + if !leafInfo.IsDir() { + return + } + if os.SameFile(rootInfo, leafInfo) { + return + } + if os.Remove(leaf) != nil { + return + } + parent: + leaf = filepath.Dir(leaf) + } +} + +func (me *Cache) Remove(path string) (err error) { + path = sanitizePath(path) + me.mu.Lock() + defer me.mu.Unlock() + err = me.remove(path) + return +} + +var ( + ErrBadPath = errors.New("bad path") + ErrIsDir = errors.New("is directory") +) + +func (me *Cache) OpenFile(path string, flag int) (ret *File, err error) { + path = sanitizePath(path) + if path == "" { + err = ErrIsDir + return + } + f, err := os.OpenFile(me.realpath(path), flag, 0644) + if flag&os.O_CREATE != 0 && os.IsNotExist(err) { + os.MkdirAll(me.root, 0755) + os.MkdirAll(filepath.Dir(me.realpath(path)), 0755) + f, err = os.OpenFile(me.realpath(path), flag, 0644) + if err != nil { + me.pruneEmptyDirs(path) + } + } + if err != nil { + return + } + ret = &File{ + c: me, + path: path, + f: f, + } + me.mu.Lock() + go func() { + defer me.mu.Unlock() + me.statItem(path, time.Now()) + }() + return +} + +func (me *Cache) rescan() { + me.filled = 0 + me.items = newLRUItems() + me.paths = make(map[string]ItemInfo) + err := filepath.Walk(me.root, func(path string, info os.FileInfo, err error) error { + if os.IsNotExist(err) { + return nil + } + if err != nil { + return err + } + if info.IsDir() { + return nil + } + path, err = filepath.Rel(me.root, path) + if err != nil { + log.Print(err) + return nil + } + me.statItem(path, time.Time{}) + return nil + }) + if err != nil { + panic(err) + } +} + +func (me *Cache) insertItem(i ItemInfo) { + me.items.Insert(i) +} + +func (me *Cache) removeInfo(path string) (ret ItemInfo, ok bool) { + ret, ok = me.paths[path] + if !ok { + return + } + if !me.items.Remove(ret) { + panic(ret) + } + me.filled -= ret.Size + delete(me.paths, path) + return +} + +// Triggers the item for path to be updated. If access is non-zero, set the +// item's access time to that value, otherwise deduce it appropriately. +func (me *Cache) statItem(path string, access time.Time) { + info, ok := me.removeInfo(path) + fi, err := os.Stat(me.realpath(path)) + if os.IsNotExist(err) { + return + } + if err != nil { + panic(err) + } + if !ok { + info.Path = path + } + if !access.IsZero() { + info.Accessed = access + } + if info.Accessed.IsZero() { + info.Accessed = missinggo.FileInfoAccessTime(fi) + } + info.Size = fi.Size() + me.filled += info.Size + me.insertItem(info) + me.paths[path] = info +} + +func (me *Cache) realpath(path string) string { + return filepath.Join(me.root, filepath.FromSlash(path)) +} + +func (me *Cache) TrimToCapacity() { + me.mu.Lock() + defer me.mu.Unlock() + me.trimToCapacity() +} + +func (me *Cache) pruneEmptyDirs(path string) { + pruneEmptyDirs(me.root, me.realpath(path)) +} + +func (me *Cache) remove(path string) (err error) { + err = os.Remove(me.realpath(path)) + if os.IsNotExist(err) { + err = nil + } + me.pruneEmptyDirs(path) + me.removeInfo(path) + return +} + +func (me *Cache) trimToCapacity() { + if me.capacity < 0 { + return + } + for me.filled > me.capacity { + item := me.items.LRU() + me.remove(item.Path) + } +} + +func (me *Cache) pathInfo(p string) ItemInfo { + return me.paths[p] +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/cache_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/cache_test.go new file mode 100644 index 000000000..89ba6e4d5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/cache_test.go @@ -0,0 +1,84 @@ +package filecache + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" +) + +func TestCache(t *testing.T) { + td, err := ioutil.TempDir("", "gotest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(td) + c, err := NewCache(filepath.Join(td, "cache")) + if err != nil { + t.Fatal(err) + } + assert.EqualValues(t, 0, c.Info().Filled) + c.WalkItems(func(i ItemInfo) {}) + _, err = c.OpenFile("/", os.O_CREATE) + assert.NotNil(t, err) + _, err = c.OpenFile("", os.O_CREATE) + assert.NotNil(t, err) + c.WalkItems(func(i ItemInfo) {}) + require.Equal(t, 0, c.Info().NumItems) + _, err = c.OpenFile("notexist", 0) + assert.True(t, os.IsNotExist(err), err) + _, err = c.OpenFile("/notexist", 0) + assert.True(t, os.IsNotExist(err), err) + _, err = c.OpenFile("/dir/notexist", 0) + assert.True(t, os.IsNotExist(err), err) + f, err := c.OpenFile("dir/blah", os.O_CREATE) + require.NoError(t, err) + defer f.Close() + c.WalkItems(func(i ItemInfo) {}) + assert.True(t, missinggo.FilePathExists(filepath.Join(td, filepath.FromSlash("cache/dir/blah")))) + assert.True(t, missinggo.FilePathExists(filepath.Join(td, filepath.FromSlash("cache/dir/")))) + assert.Equal(t, 1, c.Info().NumItems) + f.Remove() + assert.False(t, missinggo.FilePathExists(filepath.Join(td, filepath.FromSlash("dir/blah")))) + assert.False(t, missinggo.FilePathExists(filepath.Join(td, filepath.FromSlash("dir/")))) + _, err = f.Read(nil) + assert.NotEqual(t, io.EOF, err) + a, err := c.OpenFile("/a", os.O_CREATE|os.O_WRONLY) + defer a.Close() + require.Nil(t, err) + b, err := c.OpenFile("b", os.O_CREATE|os.O_WRONLY) + defer b.Close() + require.Nil(t, err) + c.mu.Lock() + assert.True(t, c.pathInfo("a").Accessed.Before(c.pathInfo("b").Accessed)) + c.mu.Unlock() + n, err := a.Write([]byte("hello")) + assert.Nil(t, err) + assert.EqualValues(t, 5, n) + assert.EqualValues(t, 5, c.Info().Filled) + assert.True(t, c.pathInfo("b").Accessed.Before(c.pathInfo("a").Accessed)) + c.SetCapacity(5) + n, err = a.Write([]byte(" world")) + assert.NotNil(t, err) + _, err = b.Write([]byte("boom!")) + // "a" and "b" have been evicted. + assert.NotNil(t, err) + assert.EqualValues(t, 0, c.Info().Filled) + assert.EqualValues(t, 0, c.Info().NumItems) + _, err = a.Seek(0, os.SEEK_SET) + assert.NotNil(t, err) +} + +func TestSanitizePath(t *testing.T) { + assert.Equal(t, "", sanitizePath("////")) + assert.Equal(t, "", sanitizePath("/../..")) + assert.Equal(t, "a", sanitizePath("/a//b/..")) + assert.Equal(t, "a", sanitizePath("../a")) + assert.Equal(t, "a", sanitizePath("./a")) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/file.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/file.go new file mode 100644 index 000000000..70e4743a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/file.go @@ -0,0 +1,117 @@ +package filecache + +import ( + "errors" + "math" + "os" + "sync" + "time" +) + +type File struct { + mu sync.Mutex + c *Cache + path string + f *os.File + gone bool +} + +func (me *File) Remove() (err error) { + return me.c.Remove(me.path) +} + +func (me *File) Seek(offset int64, whence int) (ret int64, err error) { + ret, err = me.f.Seek(offset, whence) + return +} + +func (me *File) maxWrite() (max int64, err error) { + if me.c.capacity < 0 { + max = math.MaxInt64 + return + } + pos, err := me.Seek(0, os.SEEK_CUR) + if err != nil { + return + } + max = me.c.capacity - pos + if max < 0 { + max = 0 + } + return +} + +var ( + ErrFileTooLarge = errors.New("file too large for cache") + ErrFileDisappeared = errors.New("file disappeared") +) + +func (me *File) checkGone() { + if me.gone { + return + } + ffi, _ := me.Stat() + fsfi, _ := os.Stat(me.c.realpath(me.path)) + me.gone = !os.SameFile(ffi, fsfi) +} + +func (me *File) goneErr() error { + me.mu.Lock() + defer me.mu.Unlock() + me.checkGone() + if me.gone { + me.f.Close() + return ErrFileDisappeared + } + return nil +} + +func (me *File) Write(b []byte) (n int, err error) { + err = me.goneErr() + if err != nil { + return + } + n, err = me.f.Write(b) + me.c.mu.Lock() + me.c.statItem(me.path, time.Now()) + me.c.trimToCapacity() + me.c.mu.Unlock() + if err == nil { + err = me.goneErr() + } + return +} + +func (me *File) Close() error { + return me.f.Close() +} + +func (me *File) Stat() (os.FileInfo, error) { + return me.f.Stat() +} + +func (me *File) Read(b []byte) (n int, err error) { + err = me.goneErr() + if err != nil { + return + } + defer func() { + me.c.mu.Lock() + defer me.c.mu.Unlock() + me.c.statItem(me.path, time.Now()) + }() + return me.f.Read(b) +} + +func (me *File) ReadAt(b []byte, off int64) (n int, err error) { + err = me.goneErr() + if err != nil { + return + } + defer func() { + me.c.mu.Lock() + defer me.c.mu.Unlock() + me.c.statItem(me.path, time.Now()) + }() + return me.f.ReadAt(b, off) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/lruitems.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/lruitems.go new file mode 100644 index 000000000..75f0f7bd4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/lruitems.go @@ -0,0 +1,94 @@ +package filecache + +import ( + "container/list" + "io" + + "github.com/cznic/b" +) + +type Iterator interface { + Next() Iterator + Value() interface{} +} + +type listElementIterator struct { + le *list.Element +} + +func (me listElementIterator) Next() Iterator { + e := me.le.Next() + if e == nil { + return nil + } + return listElementIterator{e} +} + +func (me listElementIterator) Value() interface{} { + return me.le.Value +} + +func newLRUItems() *lruItems { + return &lruItems{b.TreeNew(func(_a, _b interface{}) int { + a := _a.(ItemInfo) + b := _b.(ItemInfo) + if a.Accessed != b.Accessed { + if a.Accessed.Before(b.Accessed) { + return -1 + } else { + return 1 + } + } + if a.Path == b.Path { + return 0 + } + if a.Path < b.Path { + return -1 + } + return 1 + })} +} + +type lruItems struct { + tree *b.Tree +} + +type bEnumeratorIterator struct { + e *b.Enumerator + v ItemInfo +} + +func (me bEnumeratorIterator) Next() Iterator { + _, v, err := me.e.Next() + if err == io.EOF { + return nil + } + return bEnumeratorIterator{me.e, v.(ItemInfo)} +} + +func (me bEnumeratorIterator) Value() interface{} { + return me.v +} + +func (me *lruItems) Front() Iterator { + e, _ := me.tree.SeekFirst() + if e == nil { + return nil + } + return bEnumeratorIterator{ + e: e, + }.Next() +} + +func (me *lruItems) LRU() ItemInfo { + _, v := me.tree.First() + return v.(ItemInfo) +} + +func (me *lruItems) Insert(ii ItemInfo) { + me.tree.Set(ii, ii) +} + +func (me *lruItems) Remove(ii ItemInfo) bool { + return me.tree.Delete(ii) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/lruitems_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/lruitems_test.go new file mode 100644 index 000000000..abe70a8bd --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/filecache/lruitems_test.go @@ -0,0 +1,22 @@ +package filecache + +import ( + "math/rand" + "testing" + "time" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/bradfitz/iter" +) + +func BenchmarkInsert(b *testing.B) { + for range iter.N(b.N) { + li := newLRUItems() + for range iter.N(10000) { + r := rand.Int63() + t := time.Unix(r/1e9, r%1e9) + li.Insert(ItemInfo{ + Accessed: t, + }) + } + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpcontentrange.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpcontentrange.go new file mode 100644 index 000000000..acf10023e --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpcontentrange.go @@ -0,0 +1,38 @@ +package missinggo + +import ( + "regexp" + "strconv" +) + +type HTTPBytesContentRange struct { + First, Last, Length int64 +} + +var bytesContentRangeRegexp = regexp.MustCompile(`bytes[ =](\d+)-(\d+)/(\d+|\*)`) + +func ParseHTTPBytesContentRange(s string) (ret HTTPBytesContentRange, ok bool) { + ss := bytesContentRangeRegexp.FindStringSubmatch(s) + if ss == nil { + return + } + var err error + ret.First, err = strconv.ParseInt(ss[1], 10, 64) + if err != nil { + return + } + ret.Last, err = strconv.ParseInt(ss[2], 10, 64) + if err != nil { + return + } + if ss[3] == "*" { + ret.Length = -1 + } else { + ret.Length, err = strconv.ParseInt(ss[3], 10, 64) + if err != nil { + return + } + } + ok = true + return +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpcontentrange_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpcontentrange_test.go new file mode 100644 index 000000000..e76ce6018 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpcontentrange_test.go @@ -0,0 +1,27 @@ +package missinggo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseHTTPContentRange(t *testing.T) { + for _, _case := range []struct { + h string + cr *HTTPBytesContentRange + }{ + {"", nil}, + {"1-2/*", nil}, + {"bytes=1-2/3", &HTTPBytesContentRange{1, 2, 3}}, + {"bytes=12-34/*", &HTTPBytesContentRange{12, 34, -1}}, + {" bytes=12-34/*", &HTTPBytesContentRange{12, 34, -1}}, + {" bytes 12-34/56", &HTTPBytesContentRange{12, 34, 56}}, + } { + ret, ok := ParseHTTPBytesContentRange(_case.h) + assert.Equal(t, _case.cr != nil, ok) + if _case.cr != nil { + assert.Equal(t, *_case.cr, ret) + } + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpfile/httpfile.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpfile/httpfile.go new file mode 100644 index 000000000..b534c825e --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpfile/httpfile.go @@ -0,0 +1,222 @@ +package httpfile + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "os" + "strconv" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" +) + +type File struct { + off int64 + r io.ReadCloser + rOff int64 + length int64 + url string +} + +func OpenSectionReader(url string, off, n int64) (ret io.ReadCloser, err error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, off+n-1)) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + if resp.StatusCode == http.StatusNotFound { + err = ErrNotFound + resp.Body.Close() + return + } + if resp.StatusCode != http.StatusPartialContent { + err = fmt.Errorf("bad response status: %s", resp.Status) + resp.Body.Close() + return + } + ret = resp.Body + return +} + +func Open(url string) *File { + return &File{ + url: url, + } +} + +func (me *File) prepareReader() (err error) { + if me.r != nil && me.off != me.rOff { + me.r.Close() + me.r = nil + } + if me.r != nil { + return nil + } + req, err := http.NewRequest("GET", me.url, nil) + if err != nil { + return + } + if me.off != 0 { + req.Header.Set("Range", fmt.Sprintf("bytes=%d-", me.off)) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + switch resp.StatusCode { + case http.StatusPartialContent: + cr, ok := missinggo.ParseHTTPBytesContentRange(resp.Header.Get("Content-Range")) + if !ok || cr.First != me.off { + err = errors.New("bad response") + resp.Body.Close() + return + } + me.length = cr.Length + case http.StatusOK: + if me.off != 0 { + err = errors.New("bad response") + resp.Body.Close() + return + } + if h := resp.Header.Get("Content-Length"); h != "" { + var cl uint64 + cl, err = strconv.ParseUint(h, 10, 64) + if err != nil { + resp.Body.Close() + return + } + me.length = int64(cl) + } + default: + err = errors.New(resp.Status) + resp.Body.Close() + return + } + me.r = resp.Body + me.rOff = me.off + return +} + +func (me *File) Read(b []byte) (n int, err error) { + err = me.prepareReader() + if err != nil { + return + } + n, err = me.r.Read(b) + me.off += int64(n) + me.rOff += int64(n) + return +} + +func instanceLength(r *http.Response) (int64, error) { + switch r.StatusCode { + case http.StatusOK: + if h := r.Header.Get("Content-Length"); h != "" { + return strconv.ParseInt(h, 10, 64) + } else { + return -1, nil + } + case http.StatusPartialContent: + cr, ok := missinggo.ParseHTTPBytesContentRange(r.Header.Get("Content-Range")) + if !ok { + return -1, errors.New("bad 206 response") + } + return cr.Length, nil + default: + return -1, errors.New(r.Status) + } +} + +func (me *File) Seek(offset int64, whence int) (ret int64, err error) { + switch whence { + case os.SEEK_SET: + ret = offset + case os.SEEK_CUR: + ret = me.off + offset + case os.SEEK_END: + if me.length < 0 { + err = errors.New("length unknown") + return + } + ret = me.length + offset + default: + err = fmt.Errorf("unhandled whence: %d", whence) + return + } + me.off = ret + return +} + +func (me *File) Write(b []byte) (n int, err error) { + req, err := http.NewRequest("PATCH", me.url, bytes.NewReader(b)) + if err != nil { + return + } + req.Header.Set("Content-Range", fmt.Sprintf("bytes=%d-", me.off)) + req.ContentLength = int64(len(b)) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + resp.Body.Close() + if resp.StatusCode != http.StatusPartialContent { + err = errors.New(resp.Status) + return + } + n = len(b) + me.off += int64(n) + return +} + +var ( + ErrNotFound = errors.New("not found") +) + +// Returns the length of the resource in bytes. +func GetLength(url string) (ret int64, err error) { + resp, err := http.Head(url) + if err != nil { + return + } + resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + err = ErrNotFound + return + } + return instanceLength(resp) +} + +func (me *File) Close() error { + me.url = "" + if me.r != nil { + me.r.Close() + me.r = nil + } + return nil +} + +func Delete(urlStr string) (err error) { + req, err := http.NewRequest("DELETE", urlStr, nil) + if err != nil { + return + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + err = ErrNotFound + return + } + if resp.StatusCode != 200 { + err = fmt.Errorf("response: %s", resp.Status) + } + return +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpgzip.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpgzip.go new file mode 100644 index 000000000..503eb0f79 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpgzip.go @@ -0,0 +1,47 @@ +package missinggo + +import ( + "compress/gzip" + "io" + "net/http" + "strings" +) + +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter + haveWritten bool +} + +func (w *gzipResponseWriter) Write(b []byte) (int, error) { + if w.haveWritten { + goto write + } + w.haveWritten = true + if w.Header().Get("Content-Type") != "" { + goto write + } + if type_ := http.DetectContentType(b); type_ != "application/octet-stream" { + w.Header().Set("Content-Type", type_) + } +write: + return w.Writer.Write(b) +} + +// Gzips response body if the request says it'll allow it. +func GzipHTTPHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") || w.Header().Get("Content-Encoding") != "" || w.Header().Get("Vary") != "" { + h.ServeHTTP(w, r) + return + } + w.Header().Set("Content-Encoding", "gzip") + w.Header().Set("Vary", "Accept-Encoding") + gz := gzip.NewWriter(w) + defer gz.Close() + h.ServeHTTP(&gzipResponseWriter{ + Writer: gz, + ResponseWriter: w, + }, r) + }) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpresponsestatus.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpresponsestatus.go new file mode 100644 index 000000000..a42e09801 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/httpresponsestatus.go @@ -0,0 +1,60 @@ +package missinggo + +import ( + "bufio" + "io" + "net" + "net/http" +) + +// A http.ResponseWriter that tracks the status of the response. The status +// code, and number of bytes written for example. +type StatusResponseWriter struct { + RW http.ResponseWriter + Code int + BytesWritten int64 +} + +var _ http.ResponseWriter = &StatusResponseWriter{} + +func (me *StatusResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return me.RW.(http.Hijacker).Hijack() +} + +func (me *StatusResponseWriter) CloseNotify() <-chan bool { + return me.RW.(http.CloseNotifier).CloseNotify() +} + +func (me *StatusResponseWriter) Flush() { + me.RW.(http.Flusher).Flush() +} + +func (me *StatusResponseWriter) Header() http.Header { + return me.RW.Header() +} + +func (me *StatusResponseWriter) Write(b []byte) (n int, err error) { + if me.Code == 0 { + me.Code = 200 + } + n, err = me.RW.Write(b) + me.BytesWritten += int64(n) + return +} + +func (me *StatusResponseWriter) WriteHeader(code int) { + me.RW.WriteHeader(code) + me.Code = code +} + +type ReaderFromStatusResponseWriter struct { + StatusResponseWriter + io.ReaderFrom +} + +func NewReaderFromStatusResponseWriter(w http.ResponseWriter) *ReaderFromStatusResponseWriter { + return &ReaderFromStatusResponseWriter{ + StatusResponseWriter{RW: w}, + w.(io.ReaderFrom), + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/groupby.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/groupby.go new file mode 100644 index 000000000..17761facc --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/groupby.go @@ -0,0 +1,81 @@ +package itertools + +type groupBy struct { + curKey interface{} + curKeyOk bool + curValue interface{} + keyFunc func(interface{}) interface{} + input Iterator + groupKey interface{} + groupKeyOk bool +} + +type Group interface { + Iterator + Key() interface{} +} + +type group struct { + gb *groupBy + key interface{} + first bool +} + +func (me *group) Next() (ok bool) { + if me.first { + me.first = false + return true + } + me.gb.advance() + if !me.gb.curKeyOk || me.gb.curKey != me.key { + return + } + ok = true + return +} + +func (me group) Value() (ret interface{}) { + ret = me.gb.curValue + return +} + +func (me group) Key() interface{} { + return me.key +} + +func (me *groupBy) advance() { + me.curKeyOk = me.input.Next() + if me.curKeyOk { + me.curValue = me.input.Value() + me.curKey = me.keyFunc(me.curValue) + } +} + +func (me *groupBy) Next() (ok bool) { + for me.curKey == me.groupKey { + ok = me.input.Next() + if !ok { + return + } + me.curValue = me.input.Value() + me.curKey = me.keyFunc(me.curValue) + me.curKeyOk = true + } + me.groupKey = me.curKey + me.groupKeyOk = true + return true +} + +func (me *groupBy) Value() (ret interface{}) { + return &group{me, me.groupKey, true} +} + +func GroupBy(input Iterator, keyFunc func(interface{}) interface{}) Iterator { + if keyFunc == nil { + keyFunc = func(a interface{}) interface{} { return a } + } + return &groupBy{ + input: input, + keyFunc: keyFunc, + } +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/groupby_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/groupby_test.go new file mode 100644 index 000000000..c194e7302 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/groupby_test.go @@ -0,0 +1,31 @@ +package itertools + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGroupByKey(t *testing.T) { + var ks []byte + gb := GroupBy(StringIterator("AAAABBBCCDAABBB"), nil) + for gb.Next() { + ks = append(ks, gb.Value().(Group).Key().(byte)) + } + t.Log(ks) + require.EqualValues(t, "ABCDAB", ks) +} + +func TestGroupByList(t *testing.T) { + var gs []string + gb := GroupBy(StringIterator("AAAABBBCCD"), nil) + for gb.Next() { + i := gb.Value().(Iterator) + var g string + for i.Next() { + g += string(i.Value().(byte)) + } + gs = append(gs, g) + } + t.Log(gs) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/iterator.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/iterator.go new file mode 100644 index 000000000..ad8737343 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/iterator.go @@ -0,0 +1,41 @@ +package itertools + +import "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" + +type Iterator interface { + Next() bool + Value() interface{} +} + +type sliceIterator struct { + slice []interface{} + value interface{} + ok bool +} + +func (me *sliceIterator) Next() bool { + if len(me.slice) == 0 { + return false + } + me.value = me.slice[0] + me.slice = me.slice[1:] + me.ok = true + return true +} + +func (me *sliceIterator) Value() interface{} { + if !me.ok { + panic("no value; call Next") + } + return me.value +} + +func SliceIterator(a []interface{}) Iterator { + return &sliceIterator{ + slice: a, + } +} + +func StringIterator(a string) Iterator { + return SliceIterator(missinggo.ConvertToSliceOfEmptyInterface(a)) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/iterator_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/iterator_test.go new file mode 100644 index 000000000..61f0be8c6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/itertools/iterator_test.go @@ -0,0 +1,17 @@ +package itertools + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIterator(t *testing.T) { + const s = "AAAABBBCCDAABBB" + si := StringIterator(s) + for i := range s { + require.True(t, si.Next()) + require.Equal(t, s[i], si.Value().(byte)) + } + require.False(t, si.Next()) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/net.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/net.go new file mode 100644 index 000000000..ce406d3b8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/net.go @@ -0,0 +1,26 @@ +package missinggo + +import ( + "net" +) + +type HostPort struct { + Host string // Just the host, with no port. + Port string // May be empty if no port was given. + Err error // The error returned from net.SplitHostPort. +} + +// Parse a "hostport" string, a concept that floats around the stdlib a lot +// and is painful to work with. If no port is present, what's usually present +// is just the host. +func ParseHostPort(hostPort string) (ret HostPort) { + ret.Host, ret.Port, ret.Err = net.SplitHostPort(hostPort) + if ret.Err != nil { + ret.Host = hostPort + } + return +} + +func (me *HostPort) Join() string { + return net.JoinHostPort(me.Host, me.Port) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/path.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/path.go new file mode 100644 index 000000000..6e3428cb6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/path.go @@ -0,0 +1,20 @@ +package missinggo + +import ( + "os" + "path" +) + +// Splits the pathname p into Root and Ext, such that Root+Ext==p. +func PathSplitExt(p string) (ret struct { + Root, Ext string +}) { + ret.Ext = path.Ext(p) + ret.Root = p[:len(p)-len(ret.Ext)] + return +} + +func FilePathExists(p string) bool { + _, err := os.Stat(p) + return err == nil +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/path_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/path_test.go new file mode 100644 index 000000000..a38aa2e3e --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/path_test.go @@ -0,0 +1,17 @@ +package missinggo + +import ( + "fmt" +) + +func ExamplePathSplitExt() { + fmt.Printf("%q\n", PathSplitExt(".cshrc")) + fmt.Printf("%q\n", PathSplitExt("dir/a.ext")) + fmt.Printf("%q\n", PathSplitExt("dir/.rc")) + fmt.Printf("%q\n", PathSplitExt("home/.secret/file")) + // Output: + // {"" ".cshrc"} + // {"dir/a" ".ext"} + // {"dir/" ".rc"} + // {"home/.secret/file" ""} +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/mutex.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/mutex.go new file mode 100644 index 000000000..095e9990d --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/mutex.go @@ -0,0 +1,48 @@ +package perf + +import ( + "sync" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" +) + +type TimedLocker struct { + L sync.Locker + Desc string +} + +func (me *TimedLocker) Lock() { + tr := NewTimer() + me.L.Lock() + tr.Stop(me.Desc) +} + +func (me *TimedLocker) Unlock() { + me.L.Unlock() +} + +type TimedRWLocker struct { + RWL missinggo.RWLocker + WriteDesc string + ReadDesc string +} + +func (me *TimedRWLocker) Lock() { + tr := NewTimer() + me.RWL.Lock() + tr.Stop(me.WriteDesc) +} + +func (me *TimedRWLocker) Unlock() { + me.RWL.Unlock() +} + +func (me *TimedRWLocker) RLock() { + tr := NewTimer() + me.RWL.RLock() + tr.Stop(me.ReadDesc) +} + +func (me *TimedRWLocker) RUnlock() { + me.RWL.RUnlock() +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/perf.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/perf.go new file mode 100644 index 000000000..4caf9fb60 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/perf.go @@ -0,0 +1,92 @@ +package perf + +import ( + "bytes" + "expvar" + "fmt" + "strconv" + "sync" + "time" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" +) + +var ( + em = missinggo.NewExpvarIndentMap("perfBuckets") + mu sync.RWMutex +) + +type Timer struct { + started time.Time +} + +func NewTimer() Timer { + return Timer{time.Now()} +} + +func bucketExponent(d time.Duration) int { + e := -9 + for d != 0 { + d /= 10 + e++ + } + return e +} + +type buckets struct { + mu sync.Mutex + buckets []int64 +} + +func (me *buckets) Add(t time.Duration) { + e := bucketExponent(t) + me.mu.Lock() + for e+9 >= len(me.buckets) { + me.buckets = append(me.buckets, 0) + } + me.buckets[e+9]++ + me.mu.Unlock() +} + +func (me *buckets) String() string { + var b bytes.Buffer + fmt.Fprintf(&b, "{") + first := true + me.mu.Lock() + for i, count := range me.buckets { + if first { + if count == 0 { + continue + } + first = false + } else { + fmt.Fprintf(&b, ", ") + } + key := strconv.Itoa(i - 9) + fmt.Fprintf(&b, "%q: %d", key, count) + } + me.mu.Unlock() + fmt.Fprintf(&b, "}") + return b.String() +} + +var _ expvar.Var = &buckets{} + +func (t *Timer) Stop(desc string) time.Duration { + d := time.Since(t.started) + mu.RLock() + _m := em.Get(desc) + mu.RUnlock() + if _m == nil { + mu.Lock() + _m = em.Get(desc) + if _m == nil { + _m = new(buckets) + em.Set(desc, _m) + } + mu.Unlock() + } + m := _m.(*buckets) + m.Add(d) + return d +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/perf_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/perf_test.go new file mode 100644 index 000000000..aaa7f922a --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/perf/perf_test.go @@ -0,0 +1,52 @@ +package perf + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/bradfitz/iter" + "github.com/stretchr/testify/assert" +) + +func TestTimer(t *testing.T) { + tr := NewTimer() + tr.Stop("hiyo") + tr.Stop("hiyo") + t.Log(em.Get("hiyo").(*buckets)) +} + +func BenchmarkStopWarm(b *testing.B) { + tr := NewTimer() + for range iter.N(b.N) { + tr.Stop("a") + } +} + +func BenchmarkStopCold(b *testing.B) { + tr := NewTimer() + for i := range iter.N(b.N) { + tr.Stop(strconv.FormatInt(int64(i), 10)) + } +} + +func TestExponent(t *testing.T) { + for _, c := range []struct { + e int + d time.Duration + }{ + {-1, 10 * time.Millisecond}, + {-2, 5 * time.Millisecond}, + {-2, time.Millisecond}, + {-3, 500 * time.Microsecond}, + {-3, 100 * time.Microsecond}, + } { + tr := NewTimer() + time.Sleep(c.d) + assert.Equal(t, c.e, bucketExponent(tr.Stop(fmt.Sprintf("%d", c.e))), "%s", c.d) + } + assert.Equal(t, `{"-1": 1}`, em.Get("-1").String()) + assert.Equal(t, `{"-2": 2}`, em.Get("-2").String()) + assert.Equal(t, `{"-3": 2}`, em.Get("-3").String()) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/pubsub/pubsub.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/pubsub/pubsub.go new file mode 100644 index 000000000..f92338590 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/pubsub/pubsub.go @@ -0,0 +1,92 @@ +package pubsub + +import ( + "sync" +) + +type PubSub struct { + mu sync.Mutex + next chan item + closed bool +} + +type item struct { + value interface{} + next chan item +} + +type Subscription struct { + next chan item + Values chan interface{} + mu sync.Mutex + closed chan struct{} +} + +func NewPubSub() (ret *PubSub) { + return &PubSub{ + next: make(chan item, 1), + } +} + +func (me *PubSub) Publish(v interface{}) { + next := make(chan item, 1) + i := item{v, next} + me.mu.Lock() + me.next <- i + me.next = next + me.mu.Unlock() +} + +func (me *Subscription) Close() { + me.mu.Lock() + defer me.mu.Unlock() + select { + case <-me.closed: + default: + close(me.closed) + } +} + +func (me *Subscription) runner() { + defer close(me.Values) + for { + select { + case i, ok := <-me.next: + if !ok { + me.Close() + return + } + me.next <- i + me.next = i.next + select { + case me.Values <- i.value: + case <-me.closed: + return + } + case <-me.closed: + return + } + } +} + +func (me *PubSub) Subscribe() (ret *Subscription) { + ret = &Subscription{ + closed: make(chan struct{}), + Values: make(chan interface{}), + } + me.mu.Lock() + ret.next = me.next + me.mu.Unlock() + go ret.runner() + return +} + +func (me *PubSub) Close() { + me.mu.Lock() + defer me.mu.Unlock() + if me.closed { + return + } + close(me.next) + me.closed = true +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/pubsub/pubsub_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/pubsub/pubsub_test.go new file mode 100644 index 000000000..56c871ba2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/pubsub/pubsub_test.go @@ -0,0 +1,74 @@ +package pubsub + +import ( + "sync" + "testing" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/bradfitz/iter" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDoubleClose(t *testing.T) { + ps := NewPubSub() + ps.Close() + ps.Close() +} + +func testBroadcast(t testing.TB, subs, vals int) { + ps := NewPubSub() + var wg sync.WaitGroup + for range iter.N(subs) { + wg.Add(1) + s := ps.Subscribe() + go func() { + defer wg.Done() + var e int + for i := range s.Values { + assert.Equal(t, e, i.(int)) + e++ + } + assert.Equal(t, vals, e) + }() + } + for i := range iter.N(vals) { + ps.Publish(i) + } + ps.Close() + wg.Wait() +} + +func TestBroadcast(t *testing.T) { + testBroadcast(t, 100, 10) +} + +func BenchmarkBroadcast(b *testing.B) { + for range iter.N(b.N) { + testBroadcast(b, 10, 1000) + } +} + +func TestCloseSubscription(t *testing.T) { + ps := NewPubSub() + ps.Publish(1) + s := ps.Subscribe() + select { + case <-s.Values: + t.FailNow() + default: + } + ps.Publish(2) + s2 := ps.Subscribe() + ps.Publish(3) + require.Equal(t, 2, <-s.Values) + require.EqualValues(t, 3, <-s.Values) + s.Close() + _, ok := <-s.Values + require.False(t, ok) + ps.Publish(4) + ps.Close() + require.Equal(t, 3, <-s2.Values) + require.Equal(t, 4, <-s2.Values) + require.Nil(t, <-s2.Values) + s2.Close() +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/rle.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/rle.go new file mode 100644 index 000000000..63d499767 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/rle.go @@ -0,0 +1,46 @@ +package missinggo + +// A RunLengthEncoder counts successive duplicate elements and emits the +// element and the run length when the element changes or the encoder is +// flushed. +type RunLengthEncoder interface { + // Add a series of identical elements to the stream. + Append(element interface{}, count uint64) + // Emit the current element and its count if non-zero without waiting for + // the element to change. + Flush() +} + +type runLengthEncoder struct { + eachRun func(element interface{}, count uint64) + element interface{} + count uint64 +} + +// Creates a new RunLengthEncoder. eachRun is called when an element and its +// count is emitted, per the RunLengthEncoder interface. +func NewRunLengthEncoder(eachRun func(element interface{}, count uint64)) RunLengthEncoder { + return &runLengthEncoder{ + eachRun: eachRun, + } +} + +func (me *runLengthEncoder) Append(element interface{}, count uint64) { + if element == me.element { + me.count += count + return + } + if me.count != 0 { + me.eachRun(me.element, me.count) + } + me.count = count + me.element = element +} + +func (me *runLengthEncoder) Flush() { + if me.count == 0 { + return + } + me.eachRun(me.element, me.count) + me.count = 0 +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/rle_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/rle_test.go new file mode 100644 index 000000000..4e923f6fb --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/rle_test.go @@ -0,0 +1,20 @@ +package missinggo_test + +import ( + "fmt" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" +) + +func ExampleNewRunLengthEncoder() { + var s string + rle := missinggo.NewRunLengthEncoder(func(e interface{}, count uint64) { + s += fmt.Sprintf("%d%c", count, e) + }) + for _, e := range "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW" { + rle.Append(e, 1) + } + rle.Flush() + fmt.Println(s) + // Output: 12W1B12W3B24W1B14W +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/singleflight.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/singleflight.go new file mode 100644 index 000000000..7a6e2996f --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/singleflight.go @@ -0,0 +1,39 @@ +package missinggo + +import "sync" + +type ongoing struct { + do sync.Mutex + users int +} + +type SingleFlight struct { + mu sync.Mutex + ongoing map[string]*ongoing +} + +func (me *SingleFlight) Lock(id string) { + me.mu.Lock() + on, ok := me.ongoing[id] + if !ok { + on = new(ongoing) + if me.ongoing == nil { + me.ongoing = make(map[string]*ongoing) + } + me.ongoing[id] = on + } + on.users++ + me.mu.Unlock() + on.do.Lock() +} + +func (me *SingleFlight) Unlock(id string) { + me.mu.Lock() + on := me.ongoing[id] + on.do.Unlock() + on.users-- + if on.users == 0 { + delete(me.ongoing, id) + } + me.mu.Unlock() +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/sync.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/sync.go new file mode 100644 index 000000000..fc099bd63 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/sync.go @@ -0,0 +1,11 @@ +package missinggo + +import ( + "sync" +) + +type RWLocker interface { + sync.Locker + RLock() + RUnlock() +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/url.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/url.go new file mode 100644 index 000000000..5f01eeee6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/url.go @@ -0,0 +1,30 @@ +package missinggo + +import ( + "net/http" + "net/url" +) + +// Deep copies a URL. +func CopyURL(u *url.URL) (ret *url.URL) { + ret = new(url.URL) + *ret = *u + if u.User != nil { + ret.User = new(url.Userinfo) + *ret.User = *u.User + } + return +} + +// Reconstructs the URL that would have produced the given Request. +// Request.URLs are not fully populated in http.Server handlers. +func RequestedURL(r *http.Request) (ret *url.URL) { + ret = CopyURL(r.URL) + ret.Host = r.Host + if r.TLS != nil { + ret.Scheme = "https" + } else { + ret.Scheme = "http" + } + return +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/wolf.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/wolf.go new file mode 100644 index 000000000..5552c61cd --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/wolf.go @@ -0,0 +1,51 @@ +package missinggo + +import ( + "log" + "runtime" + "sync" + "sync/atomic" +) + +const debug = false + +// A Wolf represents some event that becomes less and less interesting as it +// occurs. Call CryHeard to see if we should pay attention this time. +type Wolf struct { + cries uint64 +} + +// Returns true less and less often. Convenient for exponentially decreasing +// the amount of noise due to errors. +func (me *Wolf) CryHeard() bool { + n := atomic.AddUint64(&me.cries, 1) + return n&(n-1) == 0 +} + +var ( + mu sync.Mutex + wolves map[uintptr]*Wolf +) + +// Calls CryHeard() on a Wolf that is unique to the callers program counter. +// i.e. every CryHeard() expression has its own Wolf. +func CryHeard() bool { + pc, file, line, ok := runtime.Caller(1) + if debug { + log.Println(pc, file, line, ok) + } + if !ok { + return true + } + mu.Lock() + if wolves == nil { + wolves = make(map[uintptr]*Wolf) + } + w, ok := wolves[pc] + if !ok { + w = new(Wolf) + wolves[pc] = w + } + mu.Unlock() + return w.CryHeard() +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/missinggo/wolf_test.go b/Godeps/_workspace/src/github.com/anacrolix/missinggo/wolf_test.go new file mode 100644 index 000000000..3ee59ca79 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/missinggo/wolf_test.go @@ -0,0 +1,30 @@ +package missinggo + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func cryHeard() bool { + return CryHeard() +} + +func TestCrySameLocation(t *testing.T) { + require.True(t, cryHeard()) + require.True(t, cryHeard()) + require.False(t, cryHeard()) + require.True(t, cryHeard()) + require.False(t, cryHeard()) + require.False(t, cryHeard()) + require.False(t, cryHeard()) + require.True(t, cryHeard()) +} + +func TestCryDifferentLocations(t *testing.T) { + require.True(t, CryHeard()) + require.True(t, CryHeard()) + require.True(t, CryHeard()) + require.True(t, CryHeard()) + require.True(t, CryHeard()) +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/utp/LICENSE b/Godeps/_workspace/src/github.com/anacrolix/utp/LICENSE new file mode 100644 index 000000000..be2cc4dfb --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/utp/LICENSE @@ -0,0 +1,362 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. diff --git a/Godeps/_workspace/src/github.com/anacrolix/utp/README.md b/Godeps/_workspace/src/github.com/anacrolix/utp/README.md new file mode 100644 index 000000000..01c049e33 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/utp/README.md @@ -0,0 +1,19 @@ +# utp +[![GoDoc](https://godoc.org/github.com/anacrolix/utp?status.svg)](https://godoc.org/github.com/anacrolix/utp) +[![Build Status](https://drone.io/github.com/anacrolix/utp/status.png)](https://drone.io/github.com/anacrolix/utp/latest) + +Package utp implements uTP, the micro transport protocol as used with Bittorrent. It opts for simplicity and reliability over strict adherence to the (poor) spec. + +## Supported + + * Multiple uTP connections switched on a single PacketConn, including those initiated locally. + * Raw access to the PacketConn for non-uTP purposes, like sharing the PacketConn with a DHT implementation. + +## Implementation characteristics + + * Receive window size is used to limit out of order packets received. + * There is no MTU path discovery. The minimum size is always used. + * A fixed 64 slot selective ack window is used in both sending and receiving. + * All received non-ACK packets are ACKed in response. + +Patches welcomed. diff --git a/Godeps/_workspace/src/github.com/anacrolix/utp/cmd/ucat/ucat.go b/Godeps/_workspace/src/github.com/anacrolix/utp/cmd/ucat/ucat.go new file mode 100644 index 000000000..fc2e827c8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/utp/cmd/ucat/ucat.go @@ -0,0 +1,66 @@ +package main + +import ( + "flag" + "fmt" + "io" + "log" + "net" + "os" + "os/signal" + + "github.com/anacrolix/envpprof" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/utp" +) + +func main() { + defer envpprof.Stop() + listen := flag.Bool("l", false, "listen") + port := flag.Int("p", 0, "port to listen on") + flag.Parse() + var ( + conn net.Conn + err error + ) + if *listen { + s, err := utp.NewSocket("udp", fmt.Sprintf(":%d", *port)) + if err != nil { + log.Fatal(err) + } + defer s.Close() + conn, err = s.Accept() + if err != nil { + log.Fatal(err) + } + } else { + conn, err = utp.Dial(net.JoinHostPort(flag.Arg(0), flag.Arg(1))) + if err != nil { + log.Fatal(err) + } + } + defer conn.Close() + go func() { + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt) + <-sig + conn.Close() + }() + writerDone := make(chan struct{}) + go func() { + defer close(writerDone) + written, err := io.Copy(conn, os.Stdin) + if err != nil { + conn.Close() + log.Fatalf("error after writing %d bytes: %s", written, err) + } + log.Printf("wrote %d bytes", written) + conn.Close() + }() + n, err := io.Copy(os.Stdout, conn) + if err != nil { + log.Fatal(err) + } + log.Printf("received %d bytes", n) + // <-writerDone +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/utp/pingpong b/Godeps/_workspace/src/github.com/anacrolix/utp/pingpong new file mode 100644 index 000000000..0a9333e75 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/utp/pingpong @@ -0,0 +1,61 @@ +# This shell script uses nc-like executables to send and receive the file at +# $1, and prints the checksums. 3 such executables are +# github.com/h2so5/utp/ucat, invoked as h2so5-ucat, libutp-ucat, which is the +# ucat or ucat-static generated by the C++ libutp, and lastly, ./cmd/ucat from +# this repository. A good file in my experiments is no more than a few 100MB, +# or you'll be waiting a while. + +set -eu +# set -x + +# Passed to invocations of godo for package ./cmd/ucat. +#GODOFLAGS=-race + +#export GO_UTP_PACKET_DROP=0.1 +export GOPPROF= + +# Invokes the implementation to test against. If there's an arg, then it's +# expected to listen. +function other_ucat() { + if [[ $# != 0 ]]; then + libutp-ucat -l -p 4000 + # h2so5-ucat -l :4000 + else + libutp-ucat localhost 4000 + # h2so5-ucat localhost:4000 + fi +} + +# Check what the correct result is. +md5 "$1" + +rate() { + pv -a -W -b +} + +echo 'utp->other_ucat' +# Send from this uTP implementation to another client. +other_ucat -l | rate | md5 & +# sleep 1 +godo ${GODOFLAGS-} ./cmd/ucat localhost 4000 < "$1" +wait + +echo 'other_ucat->utp' +# Send from the other implementation, to this one. +GO_UTP_LOGGING=0 GOPPROF= godo ${GODOFLAGS-} ./cmd/ucat -l -p 4000 | rate | md5 & +# Never receive from h2so5's ucat without a small sleep first. Don't know why. +# sleep 1 +other_ucat < "$1" +wait + +echo 'libutp->libutp' +libutp-ucat -l -p 4000 | rate | md5 & +libutp-ucat localhost 4000 < "$1" +wait + +echo 'utp->utp' +godo ./cmd/ucat -l -p 4000 | rate | md5 & +godo ./cmd/ucat localhost 4000 < "$1" +wait + +# Now check the hashes match (yes you). diff --git a/Godeps/_workspace/src/github.com/anacrolix/utp/utp.go b/Godeps/_workspace/src/github.com/anacrolix/utp/utp.go new file mode 100644 index 000000000..205c8b278 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/utp/utp.go @@ -0,0 +1,1461 @@ +// Package utp implements uTP, the micro transport protocol as used with +// Bittorrent. It opts for simplicity and reliability over strict adherence to +// the (poor) spec. It allows using the underlying OS-level transport despite +// dispatching uTP on top to allow for example, shared socket use with DHT. +// Additionally, multiple uTP connections can share the same OS socket, to +// truly realize uTP's claim to be light on system and network switching +// resources. +// +// Socket is a wrapper of net.UDPConn, and performs dispatching of uTP packets +// to attached uTP Conns. Dial and Accept is done via Socket. Conn implements +// net.Conn over uTP, via aforementioned Socket. +package utp + +import ( + "encoding/binary" + "errors" + "expvar" + "fmt" + "io" + "log" + "math/rand" + "net" + "os" + "strconv" + "sync" + "time" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/jitter" + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" +) + +const ( + // Maximum received SYNs that haven't been accepted. If more SYNs are + // received, a pseudo randomly selected SYN is replied to with a reset to + // make room. + backlog = 50 + + // IPv6 min MTU is 1280, -40 for IPv6 header, and ~8 for fragment header? + minMTU = 1232 + recvWindow = 0x8000 // 32KiB + // uTP header of 20, +2 for the next extension, and 8 bytes of selective + // ACK. + maxHeaderSize = 30 + maxPayloadSize = minMTU - maxHeaderSize + maxRecvSize = 0x2000 + + // Maximum out-of-order packets to buffer. + maxUnackedInbound = 64 + + // If an send isn't acknowledged after this period, its connection is + // destroyed. There are resends during this period. + sendTimeout = 15 * time.Second +) + +var ( + ackSkippedResends = expvar.NewInt("utpAckSkippedResends") + // Inbound packets processed by a Conn. + deliveriesProcessed = expvar.NewInt("utpDeliveriesProcessed") + sentStatePackets = expvar.NewInt("utpSentStatePackets") + unusedReads = expvar.NewInt("utpUnusedReads") + sendBufferPool = sync.Pool{ + New: func() interface{} { return make([]byte, minMTU) }, + } +) + +type deadlineCallback struct { + deadline time.Time + timer *time.Timer + callback func() + inited bool +} + +func (me *deadlineCallback) deadlineExceeded() bool { + return !me.deadline.IsZero() && !time.Now().Before(me.deadline) +} + +func (me *deadlineCallback) updateTimer() { + if me.timer != nil { + me.timer.Stop() + } + if me.deadline.IsZero() { + return + } + if me.callback == nil { + panic("deadline callback is nil") + } + me.timer = time.AfterFunc(me.deadline.Sub(time.Now()), me.callback) +} + +func (me *deadlineCallback) setDeadline(t time.Time) { + me.deadline = t + me.updateTimer() +} + +func (me *deadlineCallback) setCallback(f func()) { + me.callback = f + me.updateTimer() +} + +type connDeadlines struct { + // mu sync.Mutex + read, write deadlineCallback +} + +func (c *connDeadlines) SetDeadline(t time.Time) error { + c.read.setDeadline(t) + c.write.setDeadline(t) + return nil +} + +func (c *connDeadlines) SetReadDeadline(t time.Time) error { + c.read.setDeadline(t) + return nil +} + +func (c *connDeadlines) SetWriteDeadline(t time.Time) error { + c.write.setDeadline(t) + return nil +} + +// Strongly-type guarantee of resolved network address. +type resolvedAddrStr string + +// Uniquely identifies any uTP connection on top of the underlying packet +// stream. +type connKey struct { + remoteAddr resolvedAddrStr + connID uint16 +} + +// A Socket wraps a net.PacketConn, diverting uTP packets to its child uTP +// Conns. +type Socket struct { + mu sync.RWMutex + event sync.Cond + pc net.PacketConn + conns map[connKey]*Conn + backlog map[syn]struct{} + reads chan read + closing chan struct{} + + unusedReads chan read + connDeadlines + // If a read error occurs on the underlying net.PacketConn, it is put + // here. This is because reading is done in its own goroutine to dispatch + // to uTP Conns. + ReadErr error +} + +type read struct { + data []byte + from net.Addr +} + +type syn struct { + seq_nr, conn_id uint16 + addr string +} + +const ( + extensionTypeSelectiveAck = 1 +) + +type extensionField struct { + Type byte + Bytes []byte +} + +type header struct { + Type st + Version int + ConnID uint16 + Timestamp uint32 + TimestampDiff uint32 + WndSize uint32 + SeqNr uint16 + AckNr uint16 + Extensions []extensionField +} + +var ( + mu sync.RWMutex + logLevel = 0 + artificialPacketDropChance = 0.0 +) + +func init() { + logLevel, _ = strconv.Atoi(os.Getenv("GO_UTP_LOGGING")) + fmt.Sscanf(os.Getenv("GO_UTP_PACKET_DROP"), "%f", &artificialPacketDropChance) +} + +var ( + errClosed = errors.New("closed") + errNotImplemented = errors.New("not implemented") + errTimeout net.Error = timeoutError{"i/o timeout"} + errAckTimeout = timeoutError{"timed out waiting for ack"} +) + +type timeoutError struct { + msg string +} + +func (me timeoutError) Timeout() bool { return true } +func (me timeoutError) Error() string { return me.msg } +func (me timeoutError) Temporary() bool { return false } + +func unmarshalExtensions(_type byte, b []byte) (n int, ef []extensionField, err error) { + for _type != 0 { + if _type != extensionTypeSelectiveAck { + // An extension type that is not known to us. Generally we're + // unmarshalling an packet that isn't actually uTP but we don't + // yet know for sure until we try to deliver it. + + // logonce.Stderr.Printf("utp extension %d", _type) + } + if len(b) < 2 || len(b) < int(b[1])+2 { + err = fmt.Errorf("buffer ends prematurely: %x", b) + return + } + ef = append(ef, extensionField{ + Type: _type, + Bytes: append([]byte{}, b[2:int(b[1])+2]...), + }) + _type = b[0] + n += 2 + int(b[1]) + b = b[2+int(b[1]):] + } + return +} + +var errInvalidHeader = errors.New("invalid header") + +func (h *header) Unmarshal(b []byte) (n int, err error) { + h.Type = st(b[0] >> 4) + h.Version = int(b[0] & 0xf) + if h.Type > stMax || h.Version != 1 { + err = errInvalidHeader + return + } + n, h.Extensions, err = unmarshalExtensions(b[1], b[20:]) + if err != nil { + return + } + h.ConnID = binary.BigEndian.Uint16(b[2:4]) + h.Timestamp = binary.BigEndian.Uint32(b[4:8]) + h.TimestampDiff = binary.BigEndian.Uint32(b[8:12]) + h.WndSize = binary.BigEndian.Uint32(b[12:16]) + h.SeqNr = binary.BigEndian.Uint16(b[16:18]) + h.AckNr = binary.BigEndian.Uint16(b[18:20]) + n += 20 + return +} + +func (h *header) Marshal() (ret []byte) { + hLen := 20 + func() (ret int) { + for _, ext := range h.Extensions { + ret += 2 + len(ext.Bytes) + } + return + }() + ret = sendBufferPool.Get().([]byte)[:hLen:minMTU] + // ret = make([]byte, hLen, minMTU) + p := ret // Used for manipulating ret. + p[0] = byte(h.Type<<4 | 1) + binary.BigEndian.PutUint16(p[2:4], h.ConnID) + binary.BigEndian.PutUint32(p[4:8], h.Timestamp) + binary.BigEndian.PutUint32(p[8:12], h.TimestampDiff) + binary.BigEndian.PutUint32(p[12:16], h.WndSize) + binary.BigEndian.PutUint16(p[16:18], h.SeqNr) + binary.BigEndian.PutUint16(p[18:20], h.AckNr) + // Pointer to the last type field so the next extension can set it. + _type := &p[1] + // We're done with the basic header. + p = p[20:] + for _, ext := range h.Extensions { + *_type = ext.Type + // The next extension's type will go here. + _type = &p[0] + p[1] = uint8(len(ext.Bytes)) + if int(p[1]) != copy(p[2:], ext.Bytes) { + panic("unexpected extension length") + } + p = p[2+len(ext.Bytes):] + } + if len(p) != 0 { + panic("header length changed") + } + return +} + +var ( + _ net.Listener = &Socket{} + _ net.PacketConn = &Socket{} +) + +const ( + csInvalid = iota + csSynSent + csConnected + csDestroy +) + +type st int + +func (me st) String() string { + switch me { + case stData: + return "stData" + case stFin: + return "stFin" + case stState: + return "stState" + case stReset: + return "stReset" + case stSyn: + return "stSyn" + default: + panic(fmt.Sprintf("%d", me)) + } +} + +const ( + stData st = 0 + stFin = 1 + stState = 2 + stReset = 3 + stSyn = 4 + + // Used for validating packet headers. + stMax = stSyn +) + +// Conn is a uTP stream and implements net.Conn. It owned by a Socket, which +// handles dispatching packets to and from Conns. +type Conn struct { + mu sync.Mutex + event sync.Cond + + recv_id, send_id uint16 + seq_nr, ack_nr uint16 + lastAck uint16 + lastTimeDiff uint32 + peerWndSize uint32 + + readBuf []byte + + socket *Socket + remoteAddr net.Addr + // The uTP timestamp. + startTimestamp uint32 + // When the conn was allocated. + created time.Time + // Callback to unregister Conn from a parent Socket. Should be called when + // no more packets will be handled. + detach func() + + cs int + gotFin bool + sentFin bool + err error + + unackedSends []*send + // Inbound payloads, the first is ack_nr+1. + inbound []recv + packetsIn chan packet + connDeadlines + latencies []time.Duration + pendingSendState bool + destroyed chan struct{} +} + +type send struct { + acked chan struct{} // Closed with Conn lock. + payloadSize uint32 + started time.Time + // This send was skipped in a selective ack. + resend func() + timedOut func() + conn *Conn + + mu sync.Mutex + acksSkipped int + resendTimer *time.Timer + numResends int +} + +func (s *send) Ack() (latency time.Duration) { + s.mu.Lock() + defer s.mu.Unlock() + s.resendTimer.Stop() + select { + case <-s.acked: + return + default: + close(s.acked) + } + latency = time.Since(s.started) + return +} + +type recv struct { + seen bool + data []byte + Type st +} + +var ( + _ net.Conn = &Conn{} +) + +func (c *Conn) age() time.Duration { + return time.Since(c.created) +} + +func (c *Conn) timestamp() uint32 { + return nowTimestamp() - c.startTimestamp +} + +func (c *Conn) connected() bool { + return c.cs == csConnected +} + +// addr is used to create a listening UDP conn which becomes the underlying +// net.PacketConn for the Socket. +func NewSocket(network, addr string) (s *Socket, err error) { + s = &Socket{ + backlog: make(map[syn]struct{}, backlog), + reads: make(chan read, 100), + closing: make(chan struct{}), + + unusedReads: make(chan read, 100), + } + s.event.L = &s.mu + s.pc, err = net.ListenPacket(network, addr) + if err != nil { + return + } + go s.reader() + go s.dispatcher() + return +} + +func packetDebugString(h *header, payload []byte) string { + return fmt.Sprintf("%s->%d: %q", h.Type, h.ConnID, payload) +} + +func (s *Socket) reader() { + defer close(s.reads) + var b [maxRecvSize]byte + for { + if s.pc == nil { + break + } + n, addr, err := s.pc.ReadFrom(b[:]) + if err != nil { + select { + case <-s.closing: + default: + s.ReadErr = err + } + return + } + var nilB []byte + s.reads <- read{append(nilB, b[:n:n]...), addr} + } +} + +func (s *Socket) unusedRead(read read) { + unusedReads.Add(1) + select { + case s.unusedReads <- read: + default: + // Drop the packet. + } +} + +func stringAddr(s string) net.Addr { + addr, err := net.ResolveUDPAddr("udp", s) + if err != nil { + panic(err) + } + return addr +} + +func (s *Socket) pushBacklog(syn syn) { + if _, ok := s.backlog[syn]; ok { + return + } + for k := range s.backlog { + if len(s.backlog) < backlog { + break + } + delete(s.backlog, k) + // A syn is sent on the remote's recv_id, so this is where we can send + // the reset. + s.reset(stringAddr(k.addr), k.seq_nr, k.conn_id) + } + s.backlog[syn] = struct{}{} + s.event.Broadcast() +} + +func (s *Socket) dispatcher() { + for { + select { + case read, ok := <-s.reads: + if !ok { + return + } + if len(read.data) < 20 { + s.unusedRead(read) + continue + } + s.dispatch(read) + } + } +} + +func (s *Socket) dispatch(read read) { + b := read.data + addr := read.from + var h header + hEnd, err := h.Unmarshal(b) + if logLevel >= 1 { + log.Printf("recvd utp msg: %s", packetDebugString(&h, b[hEnd:])) + } + if err != nil || h.Type > stMax || h.Version != 1 { + s.unusedRead(read) + return + } + s.mu.RLock() + c, ok := s.conns[connKey{resolvedAddrStr(addr.String()), func() (recvID uint16) { + recvID = h.ConnID + // If a SYN is resent, its connection ID field will be one lower + // than we expect. + if h.Type == stSyn { + recvID++ + } + return + }()}] + s.mu.RUnlock() + if ok { + if h.Type == stSyn { + if h.ConnID == c.send_id-2 { + // This is a SYN for connection that cannot exist locally. The + // connection the remote wants to establish here with the proposed + // recv_id, already has an existing connection that was dialled + // *out* from this socket, which is why the send_id is 1 higher, + // rather than 1 lower than the recv_id. + log.Print("resetting conflicting syn") + s.reset(addr, h.SeqNr, h.ConnID) + return + } else if h.ConnID != c.send_id { + panic("bad assumption") + } + } + c.deliver(h, b[hEnd:]) + return + } + if h.Type == stSyn { + if logLevel >= 1 { + log.Printf("adding SYN to backlog") + } + syn := syn{ + seq_nr: h.SeqNr, + conn_id: h.ConnID, + addr: addr.String(), + } + s.mu.Lock() + s.pushBacklog(syn) + s.mu.Unlock() + return + } else if h.Type != stReset { + // This is an unexpected packet. We'll send a reset, but also pass + // it on. + // log.Print("resetting unexpected packet") + // I don't think you can reset on the received packets ConnID if it isn't a SYN, as the send_id will differ in this case. + s.reset(addr, h.SeqNr, h.ConnID) + s.reset(addr, h.SeqNr, h.ConnID-1) + s.reset(addr, h.SeqNr, h.ConnID+1) + } + s.unusedRead(read) +} + +// Send a reset in response to a packet with the given header. +func (s *Socket) reset(addr net.Addr, ackNr, connId uint16) { + go s.writeTo((&header{ + Type: stReset, + Version: 1, + ConnID: connId, + AckNr: ackNr, + }).Marshal(), addr) +} + +// Attempt to connect to a remote uTP listener, creating a Socket just for +// this connection. +func Dial(addr string) (net.Conn, error) { + return DialTimeout(addr, 0) +} + +// Same as Dial with a timeout parameter. +func DialTimeout(addr string, timeout time.Duration) (nc net.Conn, err error) { + s, err := NewSocket("udp", ":0") + if err != nil { + return + } + return s.DialTimeout(addr, timeout) + +} + +// Return a recv_id that should be free. Handling the case where it isn't is +// deferred to a more appropriate function. +func (s *Socket) newConnID(remoteAddr resolvedAddrStr) (id uint16) { + // Rather than use math.Rand, which requires generating all the IDs up + // front and allocating a slice, we do it on the stack, generating the IDs + // only as required. To do this, we use the fact that the array is + // default-initialized. IDs that are 0, are actually their index in the + // array. IDs that are non-zero, are +1 from their intended ID. + var idsBack [0x10000]int + ids := idsBack[:] + for len(ids) != 0 { + // Pick the next ID from the untried ids. + i := rand.Intn(len(ids)) + id = uint16(ids[i]) + // If it's zero, then treat it as though the index i was the ID. + // Otherwise the value we get is the ID+1. + if id == 0 { + id = uint16(i) + } else { + id-- + } + // Check there's no connection using this ID for its recv_id... + _, ok1 := s.conns[connKey{remoteAddr, id}] + // and if we're connecting to our own Socket, that there isn't a Conn + // already receiving on what will correspond to our send_id. Note that + // we just assume that we could be connecting to our own Socket. This + // will halve the available connection IDs to each distinct remote + // address. Presumably that's ~0x8000, down from ~0x10000. + _, ok2 := s.conns[connKey{remoteAddr, id + 1}] + _, ok4 := s.conns[connKey{remoteAddr, id - 1}] + if !ok1 && !ok2 && !ok4 { + return + } + // The set of possible IDs is shrinking. The highest one will be lost, so + // it's moved to the location of the one we just tried. + ids[i] = len(ids) // Conveniently already +1. + // And shrink. + ids = ids[:len(ids)-1] + } + return +} + +func (c *Conn) sendPendingState() { + if !c.pendingSendState { + return + } + c.sendState() +} + +func (s *Socket) newConn(addr net.Addr) (c *Conn) { + c = &Conn{ + socket: s, + remoteAddr: addr, + startTimestamp: nowTimestamp(), + created: time.Now(), + packetsIn: make(chan packet, 100), + destroyed: make(chan struct{}), + } + c.event.L = &c.mu + c.mu.Lock() + c.connDeadlines.read.setCallback(func() { + c.mu.Lock() + c.event.Broadcast() + c.mu.Unlock() + }) + c.connDeadlines.write.setCallback(func() { + c.mu.Lock() + c.event.Broadcast() + c.mu.Unlock() + }) + c.mu.Unlock() + go c.deliveryProcessor() + return +} + +func (s *Socket) Dial(addr string) (net.Conn, error) { + return s.DialTimeout(addr, 0) +} + +func (s *Socket) DialTimeout(addr string, timeout time.Duration) (nc net.Conn, err error) { + netAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return + } + + s.mu.Lock() + c := s.newConn(netAddr) + c.recv_id = s.newConnID(resolvedAddrStr(netAddr.String())) + c.send_id = c.recv_id + 1 + if logLevel >= 1 { + log.Printf("dial registering addr: %s", netAddr.String()) + } + if !s.registerConn(c.recv_id, resolvedAddrStr(netAddr.String()), c) { + err = errors.New("couldn't register new connection") + log.Println(c.recv_id, netAddr.String()) + for k, c := range s.conns { + log.Println(k, c, c.age()) + } + log.Printf("that's %d connections", len(s.conns)) + } + s.mu.Unlock() + if err != nil { + return + } + + connErr := make(chan error, 1) + go func() { + connErr <- c.connect() + }() + var timeoutCh <-chan time.Time + if timeout != 0 { + timeoutCh = time.After(timeout) + } + select { + case err = <-connErr: + case <-timeoutCh: + c.Close() + err = errTimeout + } + if err == nil { + nc = c + } + return +} + +func (c *Conn) wndSize() uint32 { + if len(c.inbound) > maxUnackedInbound/2 { + return 0 + } + var buffered int + for _, r := range c.inbound { + buffered += len(r.data) + } + buffered += len(c.readBuf) + if buffered >= recvWindow { + return 0 + } + return recvWindow - uint32(buffered) +} + +func nowTimestamp() uint32 { + return uint32(time.Now().UnixNano() / int64(time.Microsecond)) +} + +// Send the given payload with an up to date header. +func (c *Conn) send(_type st, connID uint16, payload []byte, seqNr uint16) (err error) { + // Always selectively ack the first 64 packets. Don't bother with rest for + // now. + selAck := selectiveAckBitmask(make([]byte, 8)) + for i := 1; i < 65; i++ { + if len(c.inbound) <= i { + break + } + if c.inbound[i].seen { + selAck.SetBit(i - 1) + } + } + h := header{ + Type: _type, + Version: 1, + ConnID: connID, + SeqNr: seqNr, + AckNr: c.ack_nr, + WndSize: c.wndSize(), + Timestamp: c.timestamp(), + TimestampDiff: c.lastTimeDiff, + // Currently always send an 8 byte selective ack. + Extensions: []extensionField{{ + Type: extensionTypeSelectiveAck, + Bytes: selAck, + }}, + } + p := h.Marshal() + // Extension headers are currently fixed in size. + if len(p) != maxHeaderSize { + panic("header has unexpected size") + } + p = append(p, payload...) + if logLevel >= 1 { + log.Printf("writing utp msg to %s: %s", c.remoteAddr, packetDebugString(&h, payload)) + } + n1, err := c.socket.writeTo(p, c.remoteAddr) + if err != nil { + return + } + if n1 != len(p) { + panic(n1) + } + c.unpendSendState() + return +} + +func (me *Conn) unpendSendState() { + me.pendingSendState = false +} + +func (c *Conn) pendSendState() { + c.pendingSendState = true +} + +func (me *Socket) writeTo(b []byte, addr net.Addr) (n int, err error) { + mu.RLock() + apdc := artificialPacketDropChance + mu.RUnlock() + if apdc != 0 { + if rand.Float64() < apdc { + n = len(b) + return + } + } + n, err = me.pc.WriteTo(b, addr) + return +} + +func (s *send) timeoutResend() { + select { + case <-s.acked: + return + default: + } + if time.Since(s.started) >= sendTimeout { + s.timedOut() + return + } + s.conn.mu.Lock() + rt := s.conn.resendTimeout() + s.conn.mu.Unlock() + go s.resend() + s.mu.Lock() + s.numResends++ + s.resendTimer.Reset(rt) + s.mu.Unlock() +} + +func (me *Conn) writeSyn() (err error) { + if me.cs != csInvalid { + panic(me.cs) + } + _, err = me.write(stSyn, me.recv_id, nil, me.seq_nr) + return +} + +func (c *Conn) write(_type st, connID uint16, payload []byte, seqNr uint16) (n int, err error) { + switch _type { + case stSyn, stFin, stData: + default: + panic(_type) + } + switch c.cs { + case csConnected, csSynSent, csInvalid: + default: + panic(c.cs) + } + if c.sentFin { + panic(c) + } + if len(payload) > maxPayloadSize { + payload = payload[:maxPayloadSize] + } + err = c.send(_type, connID, payload, seqNr) + if err != nil { + return + } + n = len(payload) + // Copy payload so caller to write can continue to use the buffer. + if payload != nil { + payload = append(sendBufferPool.Get().([]byte)[:0:minMTU], payload...) + } + send := &send{ + acked: make(chan struct{}), + payloadSize: uint32(len(payload)), + started: time.Now(), + resend: func() { + c.mu.Lock() + err := c.send(_type, connID, payload, seqNr) + if err != nil { + log.Printf("error resending packet: %s", err) + } + c.mu.Unlock() + }, + timedOut: func() { + c.mu.Lock() + c.destroy(errAckTimeout) + c.mu.Unlock() + }, + conn: c, + } + send.mu.Lock() + send.resendTimer = time.AfterFunc(c.resendTimeout(), send.timeoutResend) + send.mu.Unlock() + c.unackedSends = append(c.unackedSends, send) + c.seq_nr++ + return +} + +func (c *Conn) latency() (ret time.Duration) { + if len(c.latencies) == 0 { + // Sort of the p95 of latencies? + return 200 * time.Millisecond + } + for _, l := range c.latencies { + ret += l + } + ret = (ret + time.Duration(len(c.latencies)) - 1) / time.Duration(len(c.latencies)) + return +} + +func (c *Conn) numUnackedSends() (num int) { + for _, s := range c.unackedSends { + select { + case <-s.acked: + default: + num++ + } + } + return +} + +func (c *Conn) cur_window() (window uint32) { + for _, s := range c.unackedSends { + select { + case <-s.acked: + default: + window += s.payloadSize + } + } + return +} + +func (c *Conn) sendState() { + c.send(stState, c.send_id, nil, c.seq_nr) + sentStatePackets.Add(1) +} + +func seqLess(a, b uint16) bool { + if b < 0x8000 { + return a < b || a >= b-0x8000 + } else { + return a < b && a >= b-0x8000 + } +} + +// Ack our send with the given sequence number. +func (c *Conn) ack(nr uint16) { + if !seqLess(c.lastAck, nr) { + // Already acked. + return + } + i := nr - c.lastAck - 1 + if int(i) >= len(c.unackedSends) { + log.Printf("got ack ahead of syn (%x > %x)", nr, c.seq_nr-1) + return + } + latency := c.unackedSends[i].Ack() + if latency != 0 { + c.latencies = append(c.latencies, latency) + if len(c.latencies) > 10 { + c.latencies = c.latencies[len(c.latencies)-10:] + } + } + for { + if len(c.unackedSends) == 0 { + break + } + select { + case <-c.unackedSends[0].acked: + default: + // Can't trim unacked sends any further. + return + } + // Trim the front of the unacked sends. + c.unackedSends = c.unackedSends[1:] + c.lastAck++ + } + c.event.Broadcast() +} + +func (c *Conn) ackTo(nr uint16) { + if !seqLess(nr, c.seq_nr) { + return + } + for seqLess(c.lastAck, nr) { + c.ack(c.lastAck + 1) + } +} + +type selectiveAckBitmask []byte + +func (me selectiveAckBitmask) NumBits() int { + return len(me) * 8 +} + +func (me selectiveAckBitmask) SetBit(index int) { + me[index/8] |= 1 << uint(index%8) +} + +func (me selectiveAckBitmask) BitIsSet(index int) bool { + return me[index/8]>>uint(index%8)&1 == 1 +} + +// Return the send state for the sequence number. Returns nil if there's no +// outstanding send for that sequence number. +func (c *Conn) seqSend(seqNr uint16) *send { + if !seqLess(c.lastAck, seqNr) { + // Presumably already acked. + return nil + } + i := int(seqNr - c.lastAck - 1) + if i >= len(c.unackedSends) { + // No such send. + return nil + } + return c.unackedSends[i] +} + +func (c *Conn) resendTimeout() time.Duration { + l := c.latency() + if l < 10*time.Millisecond { + l = 10 * time.Millisecond + } + ret := jitter.Duration(3*l, l) + // log.Print(ret) + return ret +} + +func (c *Conn) ackSkipped(seqNr uint16) { + send := c.seqSend(seqNr) + if send == nil { + return + } + send.mu.Lock() + defer send.mu.Unlock() + send.acksSkipped++ + switch send.acksSkipped { + case 3, 60: + ackSkippedResends.Add(1) + go send.resend() + send.resendTimer.Reset(c.resendTimeout()) + default: + } +} + +type packet struct { + h header + payload []byte +} + +func (c *Conn) deliver(h header, payload []byte) { + c.packetsIn <- packet{h, payload} +} + +func (c *Conn) deliveryProcessor() { + for { + select { + case p := <-c.packetsIn: + c.processDelivery(p.h, p.payload) + timeout := time.After(500 * time.Microsecond) + batched: + for { + select { + case p := <-c.packetsIn: + c.processDelivery(p.h, p.payload) + case <-timeout: + break batched + } + } + c.mu.Lock() + c.sendPendingState() + c.mu.Unlock() + case <-c.destroyed: + return + } + } +} + +func (c *Conn) processDelivery(h header, payload []byte) { + deliveriesProcessed.Add(1) + c.mu.Lock() + defer c.mu.Unlock() + defer c.event.Broadcast() + c.assertHeader(h) + c.peerWndSize = h.WndSize + c.applyAcks(h) + if h.Timestamp == 0 { + c.lastTimeDiff = 0 + } else { + c.lastTimeDiff = c.timestamp() - h.Timestamp + } + + // We want this connection destroyed, and our peer has acked everything. + if c.sentFin && len(c.unackedSends) == 0 { + // log.Print("gracefully completed") + c.destroy(nil) + return + } + if h.Type == stReset { + c.destroy(errors.New("peer reset")) + return + } + if c.cs == csSynSent { + if h.Type != stState { + return + } + c.changeState(csConnected) + c.ack_nr = h.SeqNr - 1 + return + } + if h.Type == stState { + return + } + c.pendSendState() + if !seqLess(c.ack_nr, h.SeqNr) { + // Already received this packet. + return + } + inboundIndex := int(h.SeqNr - c.ack_nr - 1) + if inboundIndex < len(c.inbound) && c.inbound[inboundIndex].seen { + // Already received this packet. + return + } + // Derived from running in production: + // grep -oP '(?<=packet out of order, index=)\d+' log | sort -n | uniq -c + // 64 should correspond to 8 bytes of selective ack. + if inboundIndex >= maxUnackedInbound { + // Discard packet too far ahead. + if missinggo.CryHeard() { + // I can't tell if this occurs due to bad peers, or something + // missing in the implementation. + log.Printf("received packet from %s %d ahead of next seqnr (%x > %x)", c.remoteAddr, inboundIndex, h.SeqNr, c.ack_nr+1) + } + return + } + // Extend inbound so the new packet has a place. + for inboundIndex >= len(c.inbound) { + c.inbound = append(c.inbound, recv{}) + } + c.inbound[inboundIndex] = recv{true, payload, h.Type} + c.processInbound() +} + +func (c *Conn) applyAcks(h header) { + c.ackTo(h.AckNr) + for _, ext := range h.Extensions { + switch ext.Type { + case extensionTypeSelectiveAck: + c.ackSkipped(h.AckNr + 1) + bitmask := selectiveAckBitmask(ext.Bytes) + for i := 0; i < bitmask.NumBits(); i++ { + if bitmask.BitIsSet(i) { + nr := h.AckNr + 2 + uint16(i) + // log.Printf("selectively acked %d", nr) + c.ack(nr) + } else { + c.ackSkipped(h.AckNr + 2 + uint16(i)) + } + } + } + } +} + +func (c *Conn) assertHeader(h header) { + if h.Type == stSyn { + if h.ConnID != c.send_id { + panic(fmt.Sprintf("%d != %d", h.ConnID, c.send_id)) + } + } else { + if h.ConnID != c.recv_id { + panic("erroneous delivery") + } + } +} + +func (c *Conn) processInbound() { + // Consume consecutive next packets. + for !c.gotFin && len(c.inbound) > 0 && c.inbound[0].seen { + c.ack_nr++ + p := c.inbound[0] + c.inbound = c.inbound[1:] + c.readBuf = append(c.readBuf, p.data...) + if p.Type == stFin { + c.gotFin = true + } + } +} + +func (c *Conn) waitAck(seq uint16) { + send := c.seqSend(seq) + if send == nil { + return + } + c.mu.Unlock() + defer c.mu.Lock() + <-send.acked + return +} + +func (c *Conn) changeState(cs int) { + // log.Println(c, "goes", c.cs, "->", cs) + c.cs = cs +} + +func (c *Conn) connect() error { + c.mu.Lock() + defer c.mu.Unlock() + c.seq_nr = 1 + err := c.writeSyn() + if err != nil { + return err + } + c.changeState(csSynSent) + if logLevel >= 2 { + log.Printf("sent syn") + } + // c.seq_nr++ + c.waitAck(1) + if c.err != nil { + err = c.err + } + c.event.Broadcast() + return err +} + +// Returns true if the connection was newly registered, false otherwise. +func (s *Socket) registerConn(recvID uint16, remoteAddr resolvedAddrStr, c *Conn) bool { + if s.conns == nil { + s.conns = make(map[connKey]*Conn) + } + key := connKey{remoteAddr, recvID} + if _, ok := s.conns[key]; ok { + return false + } + s.conns[key] = c + c.detach = func() { + s.mu.Lock() + defer s.mu.Unlock() + defer s.event.Broadcast() + if s.conns[key] != c { + panic("conn changed") + } + // log.Println("detached", key) + delete(s.conns, key) + if len(s.conns) == 0 { + s.pc.Close() + } + } + return true +} + +func (s *Socket) nextSyn() (syn syn, ok bool) { + s.mu.Lock() + defer s.mu.Unlock() + for { + for k := range s.backlog { + syn = k + delete(s.backlog, k) + ok = true + return + } + select { + case <-s.closing: + return + default: + } + s.event.Wait() + } +} + +// Accept and return a new uTP connection. +func (s *Socket) Accept() (c net.Conn, err error) { + for { + syn, ok := s.nextSyn() + if !ok { + err = errClosed + return + } + s.mu.Lock() + _c := s.newConn(stringAddr(syn.addr)) + _c.send_id = syn.conn_id + _c.recv_id = _c.send_id + 1 + _c.seq_nr = uint16(rand.Int()) + _c.lastAck = _c.seq_nr - 1 + _c.ack_nr = syn.seq_nr + _c.cs = csConnected + if !s.registerConn(_c.recv_id, resolvedAddrStr(syn.addr), _c) { + // SYN that triggered this accept duplicates existing connection. + // Ack again in case the SYN was a resend. + _c = s.conns[connKey{resolvedAddrStr(syn.addr), _c.recv_id}] + if _c.send_id != syn.conn_id { + panic(":|") + } + _c.sendState() + s.mu.Unlock() + continue + } + _c.sendState() + // _c.seq_nr++ + c = _c + s.mu.Unlock() + return + } +} + +// The address we're listening on for new uTP connections. +func (s *Socket) Addr() net.Addr { + return s.pc.LocalAddr() +} + +// Marks the Socket for close. Currently this just axes the underlying OS +// socket. +func (s *Socket) Close() (err error) { + s.mu.Lock() + defer s.mu.Unlock() + select { + case <-s.closing: + return + default: + } + s.event.Broadcast() + close(s.closing) + if len(s.conns) == 0 { + err = s.pc.Close() + } + return +} + +func (s *Socket) LocalAddr() net.Addr { + return s.pc.LocalAddr() +} + +func (s *Socket) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + read, ok := <-s.unusedReads + if !ok { + err = io.EOF + } + n = copy(p, read.data) + addr = read.from + return +} + +func (s *Socket) WriteTo(b []byte, addr net.Addr) (int, error) { + return s.pc.WriteTo(b, addr) +} + +func (c *Conn) writeFin() (err error) { + if c.sentFin { + return + } + _, err = c.write(stFin, c.send_id, nil, c.seq_nr) + if err != nil { + return + } + c.sentFin = true + c.event.Broadcast() + return +} + +func (c *Conn) destroy(reason error) { + if c.err != nil && reason != nil { + log.Printf("duplicate destroy call: %s", reason) + } + if c.cs == csDestroy { + return + } + close(c.destroyed) + c.writeFin() + c.changeState(csDestroy) + c.err = reason + c.event.Broadcast() + c.detach() + for _, s := range c.unackedSends { + s.Ack() + } +} + +func (c *Conn) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.writeFin() +} + +func (c *Conn) LocalAddr() net.Addr { + return c.socket.Addr() +} + +func (c *Conn) Read(b []byte) (n int, err error) { + c.mu.Lock() + defer c.mu.Unlock() + for { + if len(c.readBuf) != 0 { + break + } + if c.cs == csDestroy || c.gotFin || c.sentFin { + err = c.err + if err == nil { + err = io.EOF + } + return + } + if c.connDeadlines.read.deadlineExceeded() { + err = errTimeout + return + } + if logLevel >= 2 { + log.Printf("nothing to read, state=%d", c.cs) + } + c.event.Wait() + } + n = copy(b, c.readBuf) + c.readBuf = c.readBuf[n:] + + return +} + +func (c *Conn) RemoteAddr() net.Addr { + return c.remoteAddr +} + +func (c *Conn) String() string { + return fmt.Sprintf("", c.LocalAddr(), c.RemoteAddr(), c.recv_id) +} + +func (c *Conn) Write(p []byte) (n int, err error) { + c.mu.Lock() + defer c.mu.Unlock() + for len(p) != 0 { + for { + if c.sentFin { + err = io.ErrClosedPipe + return + } + // If peerWndSize is 0, we still want to send something, so don't + // block until we exceed it. + if c.cur_window() <= c.peerWndSize && len(c.unackedSends) < 64 && c.cs == csConnected { + break + } + if c.connDeadlines.write.deadlineExceeded() { + err = errTimeout + return + } + c.event.Wait() + } + var n1 int + n1, err = c.write(stData, c.send_id, p, c.seq_nr) + if err != nil { + return + } + // c.seq_nr++ + n += n1 + p = p[n1:] + } + return +} diff --git a/Godeps/_workspace/src/github.com/anacrolix/utp/utp_test.go b/Godeps/_workspace/src/github.com/anacrolix/utp/utp_test.go new file mode 100644 index 000000000..a604f257c --- /dev/null +++ b/Godeps/_workspace/src/github.com/anacrolix/utp/utp_test.go @@ -0,0 +1,411 @@ +package utp + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "net" + "runtime" + "sync" + "testing" + "time" + + _ "github.com/anacrolix/envpprof" + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/missinggo" + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/bradfitz/iter" + "github.com/stretchr/testify/require" +) + +func init() { + log.SetFlags(log.Flags() | log.Lshortfile) +} + +func TestUTPPingPong(t *testing.T) { + defer goroutineLeakCheck(t)() + s, err := NewSocket("udp", "localhost:0") + require.NoError(t, err) + defer s.Close() + pingerClosed := make(chan struct{}) + go func() { + defer close(pingerClosed) + b, err := Dial(s.Addr().String()) + require.NoError(t, err) + defer b.Close() + n, err := b.Write([]byte("ping")) + require.NoError(t, err) + require.EqualValues(t, 4, n) + buf := make([]byte, 4) + b.Read(buf) + require.EqualValues(t, "pong", buf) + log.Printf("got pong") + }() + a, err := s.Accept() + require.NoError(t, err) + defer a.Close() + log.Printf("accepted %s", a) + buf := make([]byte, 42) + n, err := a.Read(buf) + require.NoError(t, err) + require.EqualValues(t, "ping", buf[:n]) + log.Print("got ping") + n, err = a.Write([]byte("pong")) + require.NoError(t, err) + require.Equal(t, 4, n) + log.Print("waiting for pinger to close") + <-pingerClosed +} + +func goroutineLeakCheck(t testing.TB) func() { + if !testing.Verbose() { + return func() {} + } + numStart := runtime.NumGoroutine() + return func() { + var numNow int + for range iter.N(1) { + numNow = runtime.NumGoroutine() + if numNow == numStart { + return + } + time.Sleep(10 * time.Millisecond) + } + // I'd print stacks, or treat this as fatal, but I think + // runtime.NumGoroutine is including system routines for which we are + // not provided the stacks, and are spawned unpredictably. + t.Logf("have %d goroutines, started with %d", numNow, numStart) + } +} + +func TestDialTimeout(t *testing.T) { + defer goroutineLeakCheck(t)() + s, _ := NewSocket("udp", "localhost:0") + defer s.Close() + conn, err := DialTimeout(s.Addr().String(), 10*time.Millisecond) + if err == nil { + conn.Close() + t.Fatal("expected timeout") + } + t.Log(err) +} + +func TestMinMaxHeaderType(t *testing.T) { + require.Equal(t, stSyn, stMax) +} + +func TestUTPRawConn(t *testing.T) { + l, err := NewSocket("udp", "") + require.NoError(t, err) + defer l.Close() + go func() { + for { + _, err := l.Accept() + if err != nil { + break + } + } + }() + // Connect a UTP peer to see if the RawConn will still work. + log.Print("dialing") + utpPeer := func() net.Conn { + s, _ := NewSocket("udp", "") + defer s.Close() + ret, err := s.Dial(fmt.Sprintf("localhost:%d", missinggo.AddrPort(l.Addr()))) + require.NoError(t, err) + return ret + }() + log.Print("dial returned") + if err != nil { + t.Fatalf("error dialing utp listener: %s", err) + } + defer utpPeer.Close() + peer, err := net.ListenPacket("udp", ":0") + if err != nil { + t.Fatal(err) + } + defer peer.Close() + + msgsReceived := 0 + const N = 5000 // How many messages to send. + readerStopped := make(chan struct{}) + // The reader goroutine. + go func() { + defer close(readerStopped) + b := make([]byte, 500) + for i := 0; i < N; i++ { + n, _, err := l.ReadFrom(b) + if err != nil { + t.Fatalf("error reading from raw conn: %s", err) + } + msgsReceived++ + var d int + fmt.Sscan(string(b[:n]), &d) + if d != i { + log.Printf("got wrong number: expected %d, got %d", i, d) + } + } + }() + udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", missinggo.AddrPort(l.Addr()))) + if err != nil { + t.Fatal(err) + } + for i := 0; i < N; i++ { + _, err := peer.WriteTo([]byte(fmt.Sprintf("%d", i)), udpAddr) + if err != nil { + t.Fatal(err) + } + time.Sleep(time.Microsecond) + } + select { + case <-readerStopped: + case <-time.After(time.Second): + t.Fatal("reader timed out") + } + if msgsReceived != N { + t.Fatalf("messages received: %d", msgsReceived) + } +} + +func TestConnReadDeadline(t *testing.T) { + ls, _ := NewSocket("udp", "localhost:0") + ds, _ := NewSocket("udp", "localhost:0") + dcReadErr := make(chan error) + go func() { + c, _ := ds.Dial(ls.Addr().String()) + defer c.Close() + _, err := c.Read(nil) + dcReadErr <- err + }() + c, _ := ls.Accept() + dl := time.Now().Add(time.Millisecond) + c.SetReadDeadline(dl) + _, err := c.Read(nil) + require.Equal(t, errTimeout, err) + // The deadline has passed. + if !time.Now().After(dl) { + t.FailNow() + } + // Returns timeout on subsequent read. + _, err = c.Read(nil) + require.Equal(t, errTimeout, err) + // Disable the deadline. + c.SetReadDeadline(time.Time{}) + readReturned := make(chan struct{}) + go func() { + c.Read(nil) + close(readReturned) + }() + select { + case <-readReturned: + // Read returned but shouldn't have. + t.FailNow() + case <-time.After(time.Millisecond): + } + c.Close() + select { + case <-readReturned: + case <-time.After(time.Millisecond): + t.Fatal("read should return after Conn is closed") + } + if err := <-dcReadErr; err != io.EOF { + t.Fatalf("dial conn read returned %s", err) + } +} + +func connectSelfLots(n int, t testing.TB) { + defer goroutineLeakCheck(t)() + s, err := NewSocket("udp", "localhost:0") + if err != nil { + t.Fatal(err) + } + go func() { + for range iter.N(n) { + c, err := s.Accept() + if err != nil { + log.Fatal(err) + } + defer c.Close() + } + }() + dialErr := make(chan error) + connCh := make(chan net.Conn) + dialSema := make(chan struct{}, backlog) + for range iter.N(n) { + go func() { + dialSema <- struct{}{} + c, err := s.Dial(s.Addr().String()) + <-dialSema + if err != nil { + dialErr <- err + return + } + connCh <- c + }() + } + conns := make([]net.Conn, 0, n) + for range iter.N(n) { + select { + case c := <-connCh: + conns = append(conns, c) + case err := <-dialErr: + t.Fatal(err) + } + } + for _, c := range conns { + if c != nil { + c.Close() + } + } + s.mu.Lock() + for len(s.conns) != 0 { + // log.Print(len(s.conns)) + s.event.Wait() + } + s.mu.Unlock() + s.Close() +} + +// Connect to ourself heaps. +func TestConnectSelf(t *testing.T) { + // A rough guess says that at worst, I can only have 0x10000/3 connections + // to the same socket, due to fragmentation in the assigned connection + // IDs. + connectSelfLots(0x1000, t) +} + +func BenchmarkConnectSelf(b *testing.B) { + for range iter.N(b.N) { + connectSelfLots(2, b) + } +} + +func BenchmarkNewCloseSocket(b *testing.B) { + for range iter.N(b.N) { + s, err := NewSocket("udp", "localhost:0") + if err != nil { + b.Fatal(err) + } + err = s.Close() + if err != nil { + b.Fatal(err) + } + } +} + +func TestRejectDialBacklogFilled(t *testing.T) { + s, err := NewSocket("udp", "localhost:0") + if err != nil { + t.Fatal(err) + } + errChan := make(chan error, 1) + dial := func() { + _, err := s.Dial(s.Addr().String()) + if err != nil { + errChan <- err + } + } + // Fill the backlog. + for range iter.N(backlog + 1) { + go dial() + } + s.mu.Lock() + for len(s.backlog) < backlog { + s.event.Wait() + } + s.mu.Unlock() + select { + case <-errChan: + t.FailNow() + default: + } + // One more connection should cause a dial attempt to get reset. + go dial() + err = <-errChan + if err.Error() != "peer reset" { + t.FailNow() + } + s.Close() +} + +// Make sure that we can reset AfterFunc timers, so we don't have to create +// brand new ones everytime they fire. Specifically for the Conn resend timer. +func TestResetAfterFuncTimer(t *testing.T) { + fired := make(chan struct{}) + timer := time.AfterFunc(time.Millisecond, func() { + fired <- struct{}{} + }) + <-fired + if timer.Reset(time.Millisecond) { + // The timer should have expired + t.FailNow() + } + <-fired +} + +func connPair() (initer, accepted net.Conn) { + s, err := NewSocket("udp", "localhost:0") + if err != nil { + panic(err) + } + defer s.Close() + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + var err error + initer, err = Dial(s.Addr().String()) + if err != nil { + panic(err) + } + }() + accepted, err = s.Accept() + if err != nil { + panic(err) + } + wg.Wait() + return +} + +// Check that peer sending FIN doesn't cause unread data to be dropped in a +// receiver. +func TestReadFinishedConn(t *testing.T) { + a, b := connPair() + defer a.Close() + defer b.Close() + mu.Lock() + originalAPDC := artificialPacketDropChance + artificialPacketDropChance = 1 + mu.Unlock() + n, err := a.Write([]byte("hello")) + require.Equal(t, 5, n) + require.NoError(t, err) + n, err = a.Write([]byte("world")) + require.Equal(t, 5, n) + require.NoError(t, err) + mu.Lock() + artificialPacketDropChance = originalAPDC + mu.Unlock() + a.Close() + all, err := ioutil.ReadAll(b) + require.NoError(t, err) + require.EqualValues(t, "helloworld", all) +} + +func TestCloseDetachesQuickly(t *testing.T) { + s, _ := NewSocket("udp", "localhost:0") + defer s.Close() + go func() { + a, _ := s.Dial(s.Addr().String()) + log.Print("close a") + a.Close() + log.Print("closed a") + }() + b, _ := s.Accept() + b.Close() + s.mu.Lock() + for len(s.conns) != 0 { + log.Print(len(s.conns)) + s.event.Wait() + } + s.mu.Unlock() +} diff --git a/Godeps/_workspace/src/github.com/bradfitz/iter/.gitignore b/Godeps/_workspace/src/github.com/bradfitz/iter/.gitignore new file mode 100644 index 000000000..b25c15b81 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bradfitz/iter/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/Godeps/_workspace/src/github.com/bradfitz/iter/README.txt b/Godeps/_workspace/src/github.com/bradfitz/iter/README.txt new file mode 100644 index 000000000..763a71c46 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bradfitz/iter/README.txt @@ -0,0 +1 @@ +See http://godoc.org/github.com/bradfitz/iter diff --git a/Godeps/_workspace/src/github.com/bradfitz/iter/iter.go b/Godeps/_workspace/src/github.com/bradfitz/iter/iter.go new file mode 100644 index 000000000..547b3d94d --- /dev/null +++ b/Godeps/_workspace/src/github.com/bradfitz/iter/iter.go @@ -0,0 +1,17 @@ +// Package iter provides a syntantically different way to iterate over integers. That's it. +package iter + +// N returns a slice of n 0-sized elements, suitable for ranging over. +// +// For example: +// +// for i := range iter.N(10) { +// fmt.Println(i) +// } +// +// ... will print 0 to 9, inclusive. +// +// It does not cause any allocations. +func N(n int) []struct{} { + return make([]struct{}, n) +} diff --git a/Godeps/_workspace/src/github.com/bradfitz/iter/iter_test.go b/Godeps/_workspace/src/github.com/bradfitz/iter/iter_test.go new file mode 100644 index 000000000..11565404c --- /dev/null +++ b/Godeps/_workspace/src/github.com/bradfitz/iter/iter_test.go @@ -0,0 +1,29 @@ +package iter_test + +import ( + "fmt" + "testing" + + "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/bradfitz/iter" +) + +func ExampleN() { + for i := range iter.N(4) { + fmt.Println(i) + } + // Output: + // 0 + // 1 + // 2 + // 3 +} + +func TestAllocs(t *testing.T) { + var x []struct{} + allocs := testing.AllocsPerRun(500, func() { + x = iter.N(1e9) + }) + if allocs > 0.1 { + t.Errorf("allocs = %v", allocs) + } +} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/.gitignore b/Godeps/_workspace/src/github.com/h2so5/utp/.gitignore deleted file mode 100644 index 485e6d2e2..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof - -_ucat_test/libutp diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/.travis.yml b/Godeps/_workspace/src/github.com/h2so5/utp/.travis.yml deleted file mode 100644 index a924a180e..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: go - -script: - - GO_UTP_LOGGING=2 go test -v - - GOMAXPROCS=4 GO_UTP_LOGGING=2 go test -v - - go test -v -race - - GO_UTP_LOGGING=2 go run benchmark/main.go -h - - GOMAXPROCS=4 GO_UTP_LOGGING=2 go run benchmark/main.go -h - - GO_UTP_LOGGING=2 cd _ucat_test; make test diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/LICENSE b/Godeps/_workspace/src/github.com/h2so5/utp/LICENSE deleted file mode 100644 index 8ef628adc..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Ron Hashimoto - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/README.md b/Godeps/_workspace/src/github.com/h2so5/utp/README.md deleted file mode 100644 index c061f0cfa..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/README.md +++ /dev/null @@ -1,28 +0,0 @@ -utp -=== - -μTP (Micro Transport Protocol) implementation - -[![Build status](https://ci.appveyor.com/api/projects/status/j1be8y7p6nd2wqqw?svg=true&branch=master)](https://ci.appveyor.com/project/h2so5/utp) -[![Build Status](https://travis-ci.org/h2so5/utp.svg?branch=master)](https://travis-ci.org/h2so5/utp) -[![GoDoc](https://godoc.org/github.com/h2so5/utp?status.svg)](http://godoc.org/github.com/h2so5/utp) - -http://www.bittorrent.org/beps/bep_0029.html - -## Installation - -``` -go get github.com/h2so5/utp -``` - -## Debug Log - -Use GO_UTP_LOGGING to show debug logs. - -``` -GO_UTP_LOGGING=0 go test <- default, no logging -GO_UTP_LOGGING=1 go test -GO_UTP_LOGGING=2 go test -GO_UTP_LOGGING=3 go test -GO_UTP_LOGGING=4 go test <- most verbose -``` diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/addr.go b/Godeps/_workspace/src/github.com/h2so5/utp/addr.go deleted file mode 100644 index c8ddfb054..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/addr.go +++ /dev/null @@ -1,42 +0,0 @@ -package utp - -import "net" - -// Addr represents the address of a UTP end point. -type Addr struct { - net.Addr -} - -// Network returns the address's network name, "utp". -func (a Addr) Network() string { return "utp" } - -// ResolveAddr parses addr as a UTP address of the form "host:port" -// or "[ipv6-host%zone]:port" and resolves a pair of domain name and -// port name on the network net, which must be "utp", "utp4" or -// "utp6". A literal address or host name for IPv6 must be enclosed -// in square brackets, as in "[::1]:80", "[ipv6-host]:http" or -// "[ipv6-host%zone]:80". -func ResolveAddr(n, addr string) (*Addr, error) { - udpnet, err := utp2udp(n) - if err != nil { - return nil, err - } - udp, err := net.ResolveUDPAddr(udpnet, addr) - if err != nil { - return nil, err - } - return &Addr{Addr: udp}, nil -} - -func utp2udp(n string) (string, error) { - switch n { - case "utp": - return "udp", nil - case "utp4": - return "udp4", nil - case "utp6": - return "udp6", nil - default: - return "", net.UnknownNetworkError(n) - } -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/base.go b/Godeps/_workspace/src/github.com/h2so5/utp/base.go deleted file mode 100644 index 328e386fa..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/base.go +++ /dev/null @@ -1,325 +0,0 @@ -package utp - -import ( - "errors" - "net" - "sync" - "sync/atomic" - "syscall" - "time" -) - -var baseConnMap = make(map[string]*baseConn) -var baseConnMutex sync.Mutex - -type packetHandler struct { - send chan<- *packet - closed chan int -} - -type baseConn struct { - addr string - conn net.PacketConn - synPackets *packetRingBuffer - outOfBandPackets *packetRingBuffer - - handlers map[uint16]*packetHandler - handlerMutex sync.RWMutex - ref int32 - refMutex sync.RWMutex - - rdeadline time.Time - wdeadline time.Time - - softClosed int32 - closed int32 -} - -func newBaseConn(n string, addr *Addr) (*baseConn, error) { - udpnet, err := utp2udp(n) - if err != nil { - return nil, err - } - var s string - if addr != nil { - s = addr.String() - } else { - s = ":0" - } - conn, err := net.ListenPacket(udpnet, s) - if err != nil { - return nil, err - } - c := &baseConn{ - conn: conn, - synPackets: newPacketRingBuffer(packetBufferSize), - outOfBandPackets: newPacketRingBuffer(packetBufferSize), - handlers: make(map[uint16]*packetHandler), - } - c.Register(-1, nil) - go c.recvLoop() - return c, nil -} - -func getSharedBaseConn(n string, addr *Addr) (*baseConn, error) { - baseConnMutex.Lock() - defer baseConnMutex.Unlock() - var s string - if addr != nil { - s = addr.String() - } else { - s = ":0" - } - if c, ok := baseConnMap[s]; ok { - return c, nil - } - c, err := newBaseConn(n, addr) - if err != nil { - return nil, err - } - c.addr = s - baseConnMap[s] = c - go c.recvLoop() - return c, nil -} - -func (c *baseConn) ok() bool { return c != nil && c.conn != nil } - -func (c *baseConn) LocalAddr() net.Addr { - if !c.ok() { - return nil - } - return &Addr{Addr: c.conn.LocalAddr()} -} - -func (c *baseConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) { - if !c.ok() { - return 0, nil, syscall.EINVAL - } - if !c.isOpen() { - return 0, nil, &net.OpError{ - Op: "read", - Net: c.LocalAddr().Network(), - Addr: c.LocalAddr(), - Err: errClosing, - } - } - var d time.Duration - if !c.rdeadline.IsZero() { - d = c.rdeadline.Sub(time.Now()) - if d < 0 { - d = 0 - } - } - p, err := c.outOfBandPackets.popOne(d) - if err != nil { - return 0, nil, &net.OpError{ - Op: "read", - Net: c.LocalAddr().Network(), - Addr: c.LocalAddr(), - Err: err, - } - } - return copy(b, p.payload), p.addr, nil -} - -func (c *baseConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { - if !c.ok() { - return 0, syscall.EINVAL - } - if !c.isOpen() { - return 0, &net.OpError{ - Op: "write", - Net: c.LocalAddr().Network(), - Addr: c.LocalAddr(), - Err: errClosing, - } - } - return c.conn.WriteTo(b, addr) -} - -func (c *baseConn) Close() error { - if !c.ok() { - return syscall.EINVAL - } - if c.isOpen() && atomic.CompareAndSwapInt32(&c.softClosed, 0, 1) { - c.Unregister(-1) - } else { - return &net.OpError{ - Op: "close", - Net: c.LocalAddr().Network(), - Addr: c.LocalAddr(), - Err: errClosing, - } - } - return nil -} - -func (c *baseConn) SetDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - err := c.SetReadDeadline(t) - if err != nil { - return err - } - return c.SetWriteDeadline(t) -} - -func (c *baseConn) SetReadDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - c.rdeadline = t - return nil -} - -func (c *baseConn) SetWriteDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - c.wdeadline = t - return nil -} - -func (c *baseConn) recvLoop() { - var buf [maxUdpPayload]byte - for { - l, addr, err := c.conn.ReadFrom(buf[:]) - if err != nil { - ulog.Printf(3, "baseConn(%v): %v", c.LocalAddr(), err) - return - } - p, err := c.decodePacket(buf[:l]) - if err != nil { - ulog.Printf(3, "baseConn(%v): RECV out-of-band packet (len: %d) from %v", c.LocalAddr(), l, addr) - c.outOfBandPackets.push(&packet{payload: append([]byte{}, buf[:l]...), addr: addr}) - } else { - p.addr = addr - ulog.Printf(3, "baseConn(%v): RECV: %v from %v", c.LocalAddr(), p, addr) - if p.header.typ == stSyn { - // ignore duplicated syns - if !c.exists(p.header.id + 1) { - c.synPackets.push(p) - } - } else { - c.processPacket(p) - } - } - } -} - -func (c *baseConn) decodePacket(b []byte) (*packet, error) { - var p packet - err := p.UnmarshalBinary(b) - if err != nil { - return nil, err - } - if p.header.ver != version { - return nil, errors.New("unsupported utp version") - } - return &p, nil -} - -func (c *baseConn) exists(id uint16) bool { - c.handlerMutex.RLock() - defer c.handlerMutex.RUnlock() - return c.handlers[id] != nil -} - -func (c *baseConn) processPacket(p *packet) { - c.handlerMutex.RLock() - h, ok := c.handlers[p.header.id] - c.handlerMutex.RUnlock() - if ok { - select { - case <-h.closed: - case h.send <- p: - } - } -} - -func (c *baseConn) Register(id int32, f chan<- *packet) { - if id < 0 { - c.refMutex.Lock() - c.ref++ - c.refMutex.Unlock() - } else { - if f == nil { - panic("nil handler not allowed") - } - c.handlerMutex.Lock() - _, ok := c.handlers[uint16(id)] - c.handlerMutex.Unlock() - if !ok { - c.refMutex.Lock() - c.ref++ - c.refMutex.Unlock() - c.handlerMutex.Lock() - c.handlers[uint16(id)] = &packetHandler{ - send: f, - closed: make(chan int), - } - c.handlerMutex.Unlock() - ulog.Printf(2, "baseConn(%v): register #%d (ref: %d)", c.LocalAddr(), id, c.ref) - } - } -} - -func (c *baseConn) Unregister(id int32) { - if id < 0 { - c.refMutex.Lock() - c.ref-- - c.refMutex.Unlock() - } else { - c.handlerMutex.Lock() - f, ok := c.handlers[uint16(id)] - c.handlerMutex.Unlock() - if ok { - c.handlerMutex.Lock() - close(f.closed) - delete(c.handlers, uint16(id)) - c.handlerMutex.Unlock() - c.refMutex.Lock() - c.ref-- - c.refMutex.Unlock() - } - } - c.refMutex.Lock() - r := c.ref - c.refMutex.Unlock() - if r <= 0 { - baseConnMutex.Lock() - defer baseConnMutex.Unlock() - c.close() - delete(baseConnMap, c.addr) - ulog.Printf(2, "baseConn(%v): unregister #%d (ref: %d)", c.LocalAddr(), id, c.ref) - } -} - -func (c *baseConn) close() { - if atomic.CompareAndSwapInt32(&c.closed, 0, 1) { - c.conn.Close() - } -} - -func (c *baseConn) isOpen() bool { - return atomic.LoadInt32(&c.closed) == 0 -} - -func (c *baseConn) Send(p *packet) { - b, err := p.MarshalBinary() - if err != nil { - panic(err) - } - ulog.Printf(3, "baseConn(%v): SEND: %v to %v", c.LocalAddr(), p, p.addr) - _, err = c.conn.WriteTo(b, p.addr) - if err != nil { - ulog.Printf(3, "%v", err) - panic(err) - } -} - -func (c *baseConn) RecvSyn(timeout time.Duration) (*packet, error) { - return c.synPackets.popOne(timeout) -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/base_test.go b/Godeps/_workspace/src/github.com/h2so5/utp/base_test.go deleted file mode 100644 index 618dae1ec..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/base_test.go +++ /dev/null @@ -1,308 +0,0 @@ -package utp - -import ( - "bytes" - "net" - "sync" - "testing" - "time" -) - -func TestSharedConnRecvPacket(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - c, err := getSharedBaseConn("utp", addr) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - uaddr, err := net.ResolveUDPAddr("udp", c.LocalAddr().String()) - if err != nil { - t.Fatal(err) - } - - uc, err := net.DialUDP("udp", nil, uaddr) - if err != nil { - t.Fatal(err) - } - defer uc.Close() - - ch := make(chan *packet) - c.Register(5, ch) - - for i := 0; i < 100; i++ { - p := &packet{header: header{typ: stData, ver: version, id: 5}} - payload, err := p.MarshalBinary() - if err != nil { - t.Fatal(err) - } - go func() { - uc.Write(payload) - }() - <-ch - } - - c.Unregister(5) -} - -func TestSharedConnSendPacket(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - c, err := getSharedBaseConn("utp", addr) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - uaddr, err := net.ResolveUDPAddr("udp", c.LocalAddr().String()) - if err != nil { - t.Fatal(err) - } - - uc, err := net.DialUDP("udp", nil, uaddr) - if err != nil { - t.Fatal(err) - } - defer uc.Close() - - for i := 0; i < 100; i++ { - addr, err := net.ResolveUDPAddr("udp", uc.LocalAddr().String()) - if err != nil { - t.Fatal(err) - } - p := &packet{header: header{typ: stData, ver: version, id: 5}, addr: addr} - payload, err := p.MarshalBinary() - if err != nil { - t.Fatal(err) - } - - c.Send(p) - - var b [256]byte - l, err := uc.Read(b[:]) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(b[:l], payload) { - t.Errorf("expected packet of %v; got %v", payload, b[:l]) - } - } -} - -func TestSharedConnRecvSyn(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - c, err := getSharedBaseConn("utp", addr) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - uaddr, err := net.ResolveUDPAddr("udp", c.LocalAddr().String()) - if err != nil { - t.Fatal(err) - } - - uc, err := net.DialUDP("udp", nil, uaddr) - if err != nil { - t.Fatal(err) - } - defer uc.Close() - - for i := 0; i < 100; i++ { - p := &packet{header: header{typ: stSyn, ver: version}} - payload, err := p.MarshalBinary() - if err != nil { - t.Fatal(err) - } - go func() { - uc.Write(payload) - }() - p, err = c.RecvSyn(time.Duration(0)) - if err != nil { - t.Fatal(err) - } - if p == nil { - t.Errorf("packet must not be nil") - } - } -} - -func TestSharedConnRecvOutOfBound(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - c, err := getSharedBaseConn("utp", addr) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - uaddr, err := net.ResolveUDPAddr("udp", c.LocalAddr().String()) - if err != nil { - t.Fatal(err) - } - - uc, err := net.DialUDP("udp", nil, uaddr) - if err != nil { - t.Fatal(err) - } - defer uc.Close() - - for i := 0; i < 100; i++ { - payload := []byte("Hello") - go func() { - uc.Write(payload) - }() - var b [256]byte - l, _, err := c.ReadFrom(b[:]) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(payload, b[:l]) { - t.Errorf("expected packet of %v; got %v", payload, b[:l]) - } - } -} - -func TestSharedConnSendOutOfBound(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - c, err := getSharedBaseConn("utp", addr) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - uaddr, err := net.ResolveUDPAddr("udp", c.LocalAddr().String()) - if err != nil { - t.Fatal(err) - } - - uc, err := net.DialUDP("udp", nil, uaddr) - if err != nil { - t.Fatal(err) - } - defer uc.Close() - - for i := 0; i < 100; i++ { - addr, err := net.ResolveUDPAddr("udp", uc.LocalAddr().String()) - if err != nil { - t.Fatal(err) - } - payload := []byte("Hello") - _, err = c.WriteTo(payload, addr) - if err != nil { - t.Fatal(err) - } - - var b [256]byte - l, err := uc.Read(b[:]) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(payload, b[:l]) { - t.Errorf("expected packet of %v; got %v", payload, b[:l]) - } - } -} - -func TestSharedConnReferenceCount(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - c, err := getSharedBaseConn("utp", addr) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - var w sync.WaitGroup - - c.Register(-1, nil) - - for i := 0; i < 5; i++ { - w.Add(1) - go func(i int) { - defer w.Done() - c.Register(int32(i), make(chan *packet)) - }(i) - } - - w.Wait() - for i := 0; i < 5; i++ { - w.Add(1) - go func(i int) { - defer w.Done() - c.Unregister(int32(i)) - }(i) - } - - w.Wait() - c.Unregister(-1) - c.Close() - - c = baseConnMap[addr.String()] - if c != nil { - t.Errorf("baseConn should be released", c.ref) - } -} - -func TestSharedConnClose(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - c, err := getSharedBaseConn("utp", addr) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - for i := 0; i < 5; i++ { - c.Close() - } - - var b [256]byte - _, _, err = c.ReadFrom(b[:]) - if err == nil { - t.Fatal("ReadFrom should fail") - } - - uaddr, err := net.ResolveUDPAddr("udp", c.LocalAddr().String()) - if err != nil { - t.Fatal(err) - } - - uc, err := net.DialUDP("udp", nil, uaddr) - if err != nil { - t.Fatal(err) - } - defer uc.Close() - - payload := []byte("Hello") - _, err = c.WriteTo(payload, uc.LocalAddr()) - if err == nil { - t.Fatal("WriteTo should fail") - } -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/benchmark/main.go b/Godeps/_workspace/src/github.com/h2so5/utp/benchmark/main.go deleted file mode 100644 index d99a4eceb..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/benchmark/main.go +++ /dev/null @@ -1,296 +0,0 @@ -package main - -import ( - "bytes" - "crypto/md5" - "flag" - "fmt" - "io" - "log" - "math/rand" - "sync" - "time" - - "github.com/davecheney/profile" - "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/dustin/go-humanize" - "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/h2so5/utp" -) - -type RandReader struct{} - -func (r RandReader) Read(p []byte) (n int, err error) { - for i := range p { - p[i] = byte(rand.Int()) - } - return len(p), nil -} - -type ByteCounter struct { - n int64 - mutex sync.RWMutex -} - -func (b *ByteCounter) Write(p []byte) (n int, err error) { - b.mutex.Lock() - defer b.mutex.Unlock() - b.n += int64(len(p)) - return len(p), nil -} - -func (b *ByteCounter) Length() int64 { - b.mutex.RLock() - defer b.mutex.RUnlock() - return b.n -} - -var h = flag.Bool("h", false, "Human readable") - -func main() { - var l = flag.Int("c", 10485760, "Payload length (bytes)") - var s = flag.Bool("s", false, "Stream mode(Low memory usage, but Slow)") - flag.Parse() - - defer profile.Start(profile.CPUProfile).Stop() - - if *h { - fmt.Printf("Payload: %s\n", humanize.IBytes(uint64(*l))) - } else { - fmt.Printf("Payload: %d\n", *l) - } - - c2s := c2s(int64(*l), *s) - n, p := humanize.ComputeSI(c2s) - if *h { - fmt.Printf("C2S: %f%sbps\n", n, p) - } else { - fmt.Printf("C2S: %f\n", c2s) - } - - s2c := s2c(int64(*l), *s) - n, p = humanize.ComputeSI(s2c) - if *h { - fmt.Printf("S2C: %f%sbps\n", n, p) - } else { - fmt.Printf("S2C: %f\n", s2c) - } - - avg := (c2s + s2c) / 2.0 - n, p = humanize.ComputeSI(avg) - - if *h { - fmt.Printf("AVG: %f%sbps\n", n, p) - } else { - fmt.Printf("AVG: %f\n", avg) - } -} - -func c2s(l int64, stream bool) float64 { - laddr, err := utp.ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - log.Fatal(err) - } - ln, err := utp.Listen("utp", laddr) - if err != nil { - log.Fatal(err) - } - - cch := make(chan *utp.Conn) - go func() { - c, err := utp.DialUTPTimeout("utp", nil, ln.Addr().(*utp.Addr), 1000*time.Millisecond) - if err != nil { - log.Fatal(err) - } - - if err != nil { - log.Fatal(err) - } - cch <- c - }() - - s, err := ln.Accept() - if err != nil { - log.Fatal(err) - } - defer s.Close() - ln.Close() - - c := <-cch - defer c.Close() - - rch := make(chan int) - wch := make(chan int) - - sendHash := md5.New() - readHash := md5.New() - counter := ByteCounter{} - - var bps float64 - if stream { - go func() { - defer c.Close() - defer close(wch) - io.Copy(io.MultiWriter(c, sendHash, &counter), io.LimitReader(RandReader{}, l)) - }() - - go func() { - defer close(rch) - io.Copy(readHash, s) - }() - - go func() { - for { - select { - case <-time.After(time.Second): - if *h { - fmt.Printf("\r <--> %s ", humanize.IBytes(uint64(counter.Length()))) - } else { - fmt.Printf("\r <--> %d ", counter.Length()) - } - case <-rch: - fmt.Printf("\r") - return - } - } - }() - - start := time.Now() - <-rch - <-wch - bps = float64(l*8) / (float64(time.Now().Sub(start)) / float64(time.Second)) - - } else { - var sendBuf, readBuf bytes.Buffer - io.Copy(io.MultiWriter(&sendBuf, sendHash), io.LimitReader(RandReader{}, l)) - - go func() { - defer c.Close() - defer close(wch) - io.Copy(c, &sendBuf) - }() - - go func() { - defer close(rch) - io.Copy(&readBuf, s) - }() - - start := time.Now() - <-rch - <-wch - bps = float64(l*8) / (float64(time.Now().Sub(start)) / float64(time.Second)) - - io.Copy(sendHash, &sendBuf) - io.Copy(readHash, &readBuf) - } - - if !bytes.Equal(sendHash.Sum(nil), readHash.Sum(nil)) { - log.Fatal("Broken payload") - } - - return bps -} - -func s2c(l int64, stream bool) float64 { - laddr, err := utp.ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - log.Fatal(err) - } - ln, err := utp.Listen("utp", laddr) - if err != nil { - log.Fatal(err) - } - - cch := make(chan *utp.Conn) - go func() { - c, err := utp.DialUTPTimeout("utp", nil, ln.Addr().(*utp.Addr), 1000*time.Millisecond) - if err != nil { - log.Fatal(err) - } - - if err != nil { - log.Fatal(err) - } - cch <- c - }() - - s, err := ln.Accept() - if err != nil { - log.Fatal(err) - } - defer s.Close() - ln.Close() - - c := <-cch - defer c.Close() - - rch := make(chan int) - wch := make(chan int) - - sendHash := md5.New() - readHash := md5.New() - counter := ByteCounter{} - - var bps float64 - - if stream { - go func() { - defer s.Close() - defer close(wch) - io.Copy(io.MultiWriter(s, sendHash, &counter), io.LimitReader(RandReader{}, l)) - }() - - go func() { - defer close(rch) - io.Copy(readHash, c) - }() - - go func() { - for { - select { - case <-time.After(time.Second): - if *h { - fmt.Printf("\r <--> %s ", humanize.IBytes(uint64(counter.Length()))) - } else { - fmt.Printf("\r <--> %d ", counter.Length()) - } - case <-rch: - fmt.Printf("\r") - return - } - } - }() - - start := time.Now() - <-rch - <-wch - bps = float64(l*8) / (float64(time.Now().Sub(start)) / float64(time.Second)) - - } else { - var sendBuf, readBuf bytes.Buffer - io.Copy(io.MultiWriter(&sendBuf, sendHash), io.LimitReader(RandReader{}, l)) - - go func() { - defer s.Close() - defer close(wch) - io.Copy(s, &sendBuf) - }() - - go func() { - defer close(rch) - io.Copy(&readBuf, c) - }() - - start := time.Now() - <-rch - <-wch - bps = float64(l*8) / (float64(time.Now().Sub(start)) / float64(time.Second)) - - io.Copy(sendHash, &sendBuf) - io.Copy(readHash, &readBuf) - } - - if !bytes.Equal(sendHash.Sum(nil), readHash.Sum(nil)) { - log.Fatal("Broken payload") - } - - return bps -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/buffer.go b/Godeps/_workspace/src/github.com/h2so5/utp/buffer.go deleted file mode 100644 index e40ae6265..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/buffer.go +++ /dev/null @@ -1,472 +0,0 @@ -package utp - -import ( - "errors" - "io" - "math" - "sync" - "time" -) - -type packetBuffer struct { - root *packetBufferNode - size int - begin int -} - -type packetBufferNode struct { - p *packet - next *packetBufferNode - pushed time.Time -} - -func newPacketBuffer(size, begin int) *packetBuffer { - return &packetBuffer{ - size: size, - begin: begin, - } -} - -func (b *packetBuffer) push(p *packet) error { - if int(p.header.seq) > b.begin+b.size-1 { - return errors.New("out of bounds") - } else if int(p.header.seq) < b.begin { - if int(p.header.seq)+math.MaxUint16 > b.begin+b.size-1 { - return errors.New("out of bounds") - } - } - if b.root == nil { - b.root = &packetBufferNode{} - } - n := b.root - i := b.begin - for { - if i == int(p.header.seq) { - n.p = p - n.pushed = time.Now() - return nil - } else if n.next == nil { - n.next = &packetBufferNode{} - } - n = n.next - i = (i + 1) % (math.MaxUint16 + 1) - } - return nil -} - -func (b *packetBuffer) fetch(id uint16) *packet { - for p := b.root; p != nil; p = p.next { - if p.p != nil { - if p.p.header.seq < id { - p.p = nil - } else if p.p.header.seq == id { - r := p.p - p.p = nil - return r - } - } - } - return nil -} - -func (b *packetBuffer) compact() { - for b.root != nil && b.root.p == nil { - b.root = b.root.next - b.begin = (b.begin + 1) % (math.MaxUint16 + 1) - } -} - -func (b *packetBuffer) front() *packet { - if b.root == nil || b.root.p == nil { - return nil - } - return b.root.p -} - -func (b *packetBuffer) frontPushedTime() (time.Time, error) { - if b.root == nil || b.root.p == nil { - return time.Time{}, errors.New("no first packet") - } - return b.root.pushed, nil -} - -func (b *packetBuffer) fetchSequence() []*packet { - var a []*packet - for ; b.root != nil && b.root.p != nil; b.root = b.root.next { - a = append(a, b.root.p) - b.begin = (b.begin + 1) % (math.MaxUint16 + 1) - } - return a -} - -func (b *packetBuffer) sequence() []*packet { - var a []*packet - n := b.root - for ; n != nil && n.p != nil; n = n.next { - a = append(a, n.p) - } - return a -} - -func (b *packetBuffer) space() int { - s := b.size - for p := b.root; p != nil; p = p.next { - s-- - } - return s -} - -func (b *packetBuffer) empty() bool { - return b.root == nil -} - -// test use only -func (b *packetBuffer) all() []*packet { - var a []*packet - for p := b.root; p != nil; p = p.next { - if p.p != nil { - a = append(a, p.p) - } - } - return a -} - -func (b *packetBuffer) generateSelectiveACK() []byte { - if b.empty() { - return nil - } - - var ack []byte - var bit uint - var octet byte - for p := b.root.next; p != nil; p = p.next { - if p.p != nil { - octet |= (1 << bit) - } - bit++ - if bit == 8 { - ack = append(ack, octet) - bit = 0 - octet = 0 - } - } - - if bit != 0 { - ack = append(ack, octet) - } - - for len(ack) > 0 && ack[len(ack)-1] == 0 { - ack = ack[:len(ack)-1] - } - - if len(ack) == 0 { - return nil - } - return ack -} - -func (b *packetBuffer) processSelectiveACK(ack []byte) { - if b.empty() { - return - } - - p := b.root.next - if p == nil { - return - } - - for _, a := range ack { - for i := 0; i < 8; i++ { - acked := (a & 1) != 0 - a >>= 1 - if acked { - p.p = nil - } - p = p.next - if p == nil { - return - } - } - } -} - -type packetRingBuffer struct { - b []*packet - begin int - s int - mutex sync.RWMutex - rch chan int -} - -func newPacketRingBuffer(s int) *packetRingBuffer { - return &packetRingBuffer{ - b: make([]*packet, s), - rch: make(chan int), - } -} - -func (b *packetRingBuffer) size() int { - b.mutex.RLock() - defer b.mutex.RUnlock() - return b.s -} - -func (b *packetRingBuffer) empty() bool { - return b.size() == 0 -} - -func (b *packetRingBuffer) push(p *packet) { - b.mutex.Lock() - defer b.mutex.Unlock() - b.b[(b.begin+b.s)%len(b.b)] = p - if b.s < len(b.b) { - b.s++ - } else { - b.begin = (b.begin + 1) % len(b.b) - } - select { - case b.rch <- 0: - default: - } -} - -func (b *packetRingBuffer) pop() *packet { - if b.empty() { - return nil - } - b.mutex.Lock() - defer b.mutex.Unlock() - p := b.b[b.begin] - b.begin = (b.begin + 1) % len(b.b) - b.s-- - return p -} - -func (b *packetRingBuffer) popOne(timeout time.Duration) (*packet, error) { - t := time.NewTimer(timeout) - defer t.Stop() - if timeout == 0 { - t.Stop() - } - if b.empty() { - select { - case <-b.rch: - case <-t.C: - return nil, errTimeout - } - } - return b.pop(), nil -} - -type byteRingBuffer struct { - b []byte - begin int - s int - mutex sync.RWMutex - rch chan int - closech chan int - closechMutex sync.Mutex -} - -func newByteRingBuffer(s int) *byteRingBuffer { - return &byteRingBuffer{ - b: make([]byte, s), - rch: make(chan int), - closech: make(chan int), - } -} - -func (r *byteRingBuffer) size() int { - r.mutex.RLock() - defer r.mutex.RUnlock() - return r.s -} - -func (r *byteRingBuffer) space() int { - r.mutex.RLock() - defer r.mutex.RUnlock() - return len(r.b) - r.s -} - -func (r *byteRingBuffer) empty() bool { - return r.size() == 0 -} - -func (r *byteRingBuffer) Write(b []byte) (int, error) { - r.mutex.Lock() - defer r.mutex.Unlock() - - for len(b) > 0 { - end := (r.begin + r.s) % len(r.b) - n := copy(r.b[end:], b) - b = b[n:] - - s := r.s + n - if s > len(r.b) { - r.begin = (r.begin + s - len(r.b)) % len(r.b) - r.s = len(r.b) - } else { - r.s += n - } - } - select { - case r.rch <- 0: - case <-r.closech: - return 0, io.EOF - default: - } - return len(b), nil -} - -func (r *byteRingBuffer) ReadTimeout(b []byte, timeout time.Duration) (int, error) { - t := time.NewTimer(timeout) - defer t.Stop() - if timeout == 0 { - t.Stop() - } - if r.empty() { - select { - case <-r.rch: - case <-t.C: - return 0, errTimeout - case <-r.closech: - return 0, io.EOF - } - } - l := r.size() - if l > len(b) { - l = len(b) - } - r.mutex.Lock() - defer r.mutex.Unlock() - if r.begin+l > len(r.b) { - n := copy(b, r.b[r.begin:]) - n = copy(b[n:], r.b[:]) - r.begin = n - } else { - copy(b, r.b[r.begin:r.begin+l]) - r.begin = (r.begin + l) % len(r.b) - } - r.s -= l - return l, nil -} - -func (r *byteRingBuffer) Close() error { - r.closechMutex.Lock() - defer r.closechMutex.Unlock() - select { - case <-r.closech: - return errClosing - default: - close(r.closech) - } - return nil -} - -type rateLimitedBuffer struct { - wch chan<- []byte - closech chan int - closechMutex sync.Mutex - size uint32 - sizech chan uint32 - sizeMutex sync.Mutex -} - -func newRateLimitedBuffer(ch chan<- []byte, size uint32) *rateLimitedBuffer { - return &rateLimitedBuffer{ - wch: ch, - closech: make(chan int), - size: size, - sizech: make(chan uint32), - } -} - -func (r *rateLimitedBuffer) WriteTimeout(b []byte, timeout time.Duration) (int, error) { - t := time.NewTimer(timeout) - defer t.Stop() - if timeout == 0 { - t.Stop() - } - - for wrote := uint32(0); wrote < uint32(len(b)); { - r.sizeMutex.Lock() - s := r.size - r.sizeMutex.Unlock() - if s == 0 { - select { - case ns := <-r.sizech: - s = ns - case <-r.closech: - return 0, errClosing - } - } - if s > uint32(len(b))-wrote { - s = uint32(len(b)) - wrote - } - select { - case r.wch <- append([]byte{}, b[wrote:wrote+s]...): - wrote += s - r.sizeMutex.Lock() - r.size -= uint32(s) - r.sizeMutex.Unlock() - case <-r.closech: - return 0, errClosing - case <-t.C: - return 0, errTimeout - } - } - - return len(b), nil -} - -func (r *rateLimitedBuffer) Reset(size uint32) { - r.sizeMutex.Lock() - defer r.sizeMutex.Unlock() - r.size = size - select { - case r.sizech <- size: - default: - } -} - -func (r *rateLimitedBuffer) Close() error { - r.closechMutex.Lock() - defer r.closechMutex.Unlock() - select { - case <-r.closech: - return errClosing - default: - close(r.closech) - } - return nil -} - -type baseDelayBuffer struct { - b [6]uint32 - last int - min uint32 -} - -func (b *baseDelayBuffer) Push(val uint32) { - t := time.Now() - i := t.Second()/20 + (t.Minute()%2)*3 - if b.last == i { - if b.b[i] > val { - b.b[i] = val - } - } else { - b.b[i] = val - b.last = i - } - min := val - for _, v := range b.b { - if v > 0 && min > v { - min = v - } - } - b.min = min -} - -func (b *baseDelayBuffer) Min() uint32 { - return b.min -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/buffer_test.go b/Godeps/_workspace/src/github.com/h2so5/utp/buffer_test.go deleted file mode 100644 index c698e92fc..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/buffer_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package utp - -import ( - "bytes" - "math" - "testing" - "time" -) - -func TestPacketBuffer(t *testing.T) { - size := 12 - b := newPacketBuffer(12, 1) - - if b.space() != size { - t.Errorf("expected space == %d; got %d", size, b.space()) - } - - for i := 1; i <= size; i++ { - b.push(&packet{header: header{seq: uint16(i)}}) - } - - if b.space() != 0 { - t.Errorf("expected space == 0; got %d", b.space()) - } - - a := []byte{255, 7} - ack := b.generateSelectiveACK() - if !bytes.Equal(a, ack) { - t.Errorf("expected ack == %v; got %v", a, ack) - } - - err := b.push(&packet{header: header{seq: 15}}) - if err == nil { - t.Fatal("push should fail") - } - - all := b.all() - if len(all) != size { - t.Errorf("expected %d packets sequence; got %d", size, len(all)) - } - - f := b.fetch(6) - if f == nil { - t.Fatal("fetch should not fail") - } - - b.compact() - - err = b.push(&packet{header: header{seq: 15}}) - if err != nil { - t.Fatal(err) - } - - err = b.push(&packet{header: header{seq: 17}}) - if err != nil { - t.Fatal(err) - } - - for i := 7; i <= size; i++ { - f := b.fetch(uint16(i)) - if f == nil { - t.Fatal("fetch should not fail") - } - } - - a = []byte{128, 2} - ack = b.generateSelectiveACK() - if !bytes.Equal(a, ack) { - t.Errorf("expected ack == %v; got %v", a, ack) - } - - all = b.all() - if len(all) != 2 { - t.Errorf("expected 2 packets sequence; got %d", len(all)) - } - - b.compact() - if b.space() != 9 { - t.Errorf("expected space == 9; got %d", b.space()) - } - - ack = b.generateSelectiveACK() - b.processSelectiveACK(ack) - - all = b.all() - if len(all) != 1 { - t.Errorf("expected size == 1; got %d", len(all)) - } -} - -func TestPacketBufferBoundary(t *testing.T) { - begin := math.MaxUint16 - 3 - b := newPacketBuffer(12, begin) - for i := begin; i != 5; i = (i + 1) % (math.MaxUint16 + 1) { - err := b.push(&packet{header: header{seq: uint16(i)}}) - if err != nil { - t.Fatal(err) - } - } -} - -func TestPacketRingBuffer(t *testing.T) { - b := newPacketRingBuffer(5) - for i := 0; i < 7; i++ { - b.push(&packet{header: header{seq: uint16(i)}}) - } - - if b.size() != 5 { - t.Errorf("expected size == 5; got %d", b.size()) - } - - p := b.pop() - if p.header.seq != 2 { - t.Errorf("expected header.seq == 2; got %d", p.header.seq) - } - - if b.size() != 4 { - t.Errorf("expected size == 4; got %d", b.size()) - } - - for b.pop() != nil { - } - - if !b.empty() { - t.Errorf("buffer must be empty") - } - - go func() { - for i := 0; i < 5; i++ { - b.push(&packet{header: header{seq: uint16(i)}}) - } - }() - - p, err := b.popOne(time.Second) - if err != nil { - t.Fatal(err) - } - - if p.header.seq != 0 { - t.Errorf("expected header.seq == 0; got %d", p.header.seq) - } -} - -func TestByteRingBuffer(t *testing.T) { - - b := newByteRingBuffer(5) - for i := 0; i < 100; i++ { - b.Write([]byte{byte(i)}) - } - - var buf [10]byte - l, err := b.ReadTimeout(buf[:], 0) - if err != nil { - t.Fatal(err) - } - - e := []byte{95, 96, 97, 98, 99} - if !bytes.Equal(buf[:l], e) { - t.Errorf("expected payload of %v; got %v", e, buf[:l]) - } - - e2 := []byte("abcdefghijklmnopqrstuvwxyz") - go func() { - _, err := b.Write(e2) - if err != nil { - t.Fatal(err) - } - }() - - l, err = b.ReadTimeout(buf[:], 0) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(buf[:l], e2[len(e2)-5:]) { - t.Errorf("expected payload of %v; got %v", e2[len(e2)-5:], buf[:l]) - } -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/conn.go b/Godeps/_workspace/src/github.com/h2so5/utp/conn.go deleted file mode 100644 index 4b5aac314..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/conn.go +++ /dev/null @@ -1,571 +0,0 @@ -package utp - -import ( - "math" - "net" - "sync" - "sync/atomic" - "syscall" - "time" -) - -// Conn is an implementation of the Conn interface for UTP network -// connections. -type Conn struct { - conn *baseConn - raddr net.Addr - rid, sid, seq, ack, lastAck uint16 - rtt, rttVar, minRtt, rto int64 - dupAck int - diff, maxWindow uint32 - - state int - closed int32 - - recvbuf *packetBuffer - sendbuf *packetBuffer - - readbuf *byteRingBuffer - writebuf *rateLimitedBuffer - - baseDelay baseDelayBuffer - - writech chan []byte - ackch chan int - synch chan int - - rdeadline time.Time - wdeadline time.Time - deadlineMutex sync.RWMutex - - recv chan *packet - - closing bool - closingch chan int - - keepalivech chan time.Duration - - connch chan int - - closech chan int - closechMutex sync.Mutex - - stat statistics -} - -type statistics struct { - sentPackets int - resentPackets int - receivedPackets int - receivedDuplicatedACKs int - packetTimedOuts int - sentSelectiveACKs int - receivedSelectiveACKs int - rtoSum int64 - rtoCount int -} - -func newConn() *Conn { - wch := make(chan []byte) - c := &Conn{ - minRtt: math.MaxInt64, - maxWindow: mss, - rto: int64(60), - - recv: make(chan *packet), - connch: make(chan int), - - recvbuf: newPacketBuffer(0, 0), - - readbuf: newByteRingBuffer(readBufferSize), - writebuf: newRateLimitedBuffer(wch, mss), - - writech: wch, - ackch: make(chan int), - synch: make(chan int), - - closingch: make(chan int), - keepalivech: make(chan time.Duration), - closech: make(chan int), - } - return c -} - -func (c *Conn) ok() bool { return c != nil && c.conn != nil } - -// Close closes the connection. -func (c *Conn) Close() error { - if !c.ok() { - return syscall.EINVAL - } - if !c.isOpen() { - return nil - } - select { - case <-c.closingch: - default: - close(c.closingch) - } - select { - case <-c.connch: - default: - return nil - } - <-c.closech - return nil -} - -// LocalAddr returns the local network address. -func (c *Conn) LocalAddr() net.Addr { - if !c.ok() { - return nil - } - return c.conn.LocalAddr() -} - -// RemoteAddr returns the remote network address. -func (c *Conn) RemoteAddr() net.Addr { - if !c.ok() { - return nil - } - return c.raddr -} - -// Read implements the Conn Read method. -func (c *Conn) Read(b []byte) (int, error) { - if !c.ok() { - return 0, syscall.EINVAL - } - if !c.isOpen() { - return 0, &net.OpError{ - Op: "read", - Net: c.LocalAddr().Network(), - Addr: c.LocalAddr(), - Err: errClosing, - } - } - s := c.readbuf.space() - c.deadlineMutex.RLock() - d := timeToDeadline(c.rdeadline) - c.deadlineMutex.RUnlock() - l, err := c.readbuf.ReadTimeout(b, d) - if s < mss && c.readbuf.space() > 0 { - select { - case c.ackch <- 0: - default: - } - } - return l, err -} - -func timeToDeadline(deadline time.Time) (d time.Duration) { - if deadline.IsZero() { - return - } - d = deadline.Sub(time.Now()) - if d < 0 { - d = 0 - } - return -} - -// Write implements the Conn Write method. -func (c *Conn) Write(b []byte) (int, error) { - if !c.ok() { - return 0, syscall.EINVAL - } - if !c.isOpen() { - return 0, &net.OpError{ - Op: "write", - Net: c.LocalAddr().Network(), - Addr: c.LocalAddr(), - Err: errClosing, - } - } - c.deadlineMutex.RLock() - d := timeToDeadline(c.wdeadline) - c.deadlineMutex.RUnlock() - return c.writebuf.WriteTimeout(b, d) -} - -// SetDeadline implements the Conn SetDeadline method. -func (c *Conn) SetDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - err := c.SetReadDeadline(t) - if err != nil { - return err - } - return c.SetWriteDeadline(t) -} - -// SetReadDeadline implements the Conn SetReadDeadline method. -func (c *Conn) SetReadDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - c.deadlineMutex.Lock() - defer c.deadlineMutex.Unlock() - c.rdeadline = t - return nil -} - -// SetWriteDeadline implements the Conn SetWriteDeadline method. -func (c *Conn) SetWriteDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - c.deadlineMutex.Lock() - defer c.deadlineMutex.Unlock() - c.wdeadline = t - return nil -} - -// SetKeepAlive sets the keepalive interval associated with the connection. -func (c *Conn) SetKeepAlive(d time.Duration) error { - if !c.ok() { - return syscall.EINVAL - } - if !c.isOpen() { - return errClosing - } - c.keepalivech <- d - return nil -} - -func (c *Conn) loop() { - defer c.conn.Unregister(int32(c.rid)) - - var resendSeq uint16 - var resendCont int - var keepalive <-chan time.Time - - resend := time.NewTimer(0) - resend.Stop() - defer resend.Stop() - - for { - resend.Stop() - f := c.sendbuf.front() - if f != nil { - resend.Reset(time.Duration(c.rto) * time.Millisecond) - } - select { - case <-c.ackch: - c.sendACK() - case <-c.synch: - c.sendSYN() - case p := <-c.recv: - c.stat.receivedPackets++ - c.processPacket(p) - case b := <-c.writech: - c.sendDATA(b) - case <-c.closingch: - c.enterClosing() - case <-resend.C: - if f != nil { - if resendSeq == f.header.seq { - resendCont++ - } else { - resendCont = 0 - resendSeq = f.header.seq - } - c.stat.packetTimedOuts++ - if resendCont > maxRetry { - c.sendRST() - c.close() - } else { - c.maxWindow /= 2 - if c.maxWindow < mtu { - c.maxWindow = mtu - } - for _, p := range c.sendbuf.sequence() { - c.resend(p) - } - } - } - case <-c.closech: - c.readbuf.Close() - c.state = stateClosed - atomic.StoreInt32(&c.closed, 1) - return - case d := <-c.keepalivech: - if d <= 0 { - keepalive = nil - } else { - keepalive = time.Tick(d) - } - case <-keepalive: - ulog.Printf(2, "Conn(%v): Send keepalive", c.LocalAddr()) - c.sendACK() - } - if c.closing { - c.tryFIN() - if c.state == stateSynSent || c.state == stateFinSent || (c.recvbuf.empty() && c.sendbuf.empty()) { - c.close() - } - } - } -} - -func (c *Conn) tryFIN() { - if c.state != stateFinSent { - if c.sendFIN() == nil { - c.writebuf.Close() - c.state = stateFinSent - } - } -} - -func (c *Conn) enterClosing() { - if !c.closing { - c.closing = true - } -} - -func (c *Conn) close() { - c.closechMutex.Lock() - defer c.closechMutex.Unlock() - select { - case <-c.closech: - default: - close(c.closech) - } - ulog.Printf(1, "Conn(%v): closed", c.LocalAddr()) - ulog.Printf(1, "Conn(%v): * SentPackets: %d", c.LocalAddr(), c.stat.sentPackets) - ulog.Printf(1, "Conn(%v): * ResentPackets: %d", c.LocalAddr(), c.stat.resentPackets) - ulog.Printf(1, "Conn(%v): * ReceivedPackets: %d", c.LocalAddr(), c.stat.receivedPackets) - ulog.Printf(1, "Conn(%v): * ReceivedDuplicatedACKs: %d", c.LocalAddr(), c.stat.receivedDuplicatedACKs) - ulog.Printf(1, "Conn(%v): * PacketTimedOuts: %d", c.LocalAddr(), c.stat.packetTimedOuts) - ulog.Printf(1, "Conn(%v): * SentSelectiveACKs: %d", c.LocalAddr(), c.stat.sentSelectiveACKs) - ulog.Printf(1, "Conn(%v): * ReceivedSelectiveACKs: %d", c.LocalAddr(), c.stat.receivedSelectiveACKs) - if c.stat.rtoCount > 0 { - ulog.Printf(1, "Conn(%v): * AverageRTO: %d", c.LocalAddr(), c.stat.rtoSum/int64(c.stat.rtoCount)) - } -} - -func (c *Conn) isOpen() bool { - return atomic.LoadInt32(&c.closed) == 0 -} - -func currentMicrosecond() uint32 { - return uint32(time.Now().Nanosecond() / 1000) -} - -func (c *Conn) processPacket(p *packet) { - if p.header.t == 0 { - c.diff = 0 - } else { - t := currentMicrosecond() - if t > p.header.t { - c.diff = t - p.header.t - if c.minRtt > int64(c.diff) { - c.minRtt = int64(c.diff) - } - } - } - - c.baseDelay.Push(c.diff) - - switch p.header.typ { - case stState: - f := c.sendbuf.front() - if f != nil && p.header.ack == f.header.seq { - for _, e := range p.ext { - if e.typ == extSelectiveAck { - ulog.Printf(3, "Conn(%v): Receive Selective ACK", c.LocalAddr()) - c.stat.receivedSelectiveACKs++ - c.sendbuf.processSelectiveACK(e.payload) - } - } - } - - s := c.sendbuf.fetch(p.header.ack) - if s != nil { - current := currentMicrosecond() - if current > s.header.t { - e := int64(current-s.header.t) / 1000 - if c.rtt == 0 { - c.rtt = e - c.rttVar = e / 2 - } else { - d := c.rtt - e - if d < 0 { - d = -d - } - c.rttVar += (d - c.rttVar) / 4 - c.rtt = c.rtt - c.rtt/8 + e/8 - } - c.rto = c.rtt + c.rttVar*4 - if c.rto < 60 { - c.rto = 60 - } else if c.rto > 1000 { - c.rto = 1000 - } - c.stat.rtoSum += c.rto - c.stat.rtoCount++ - } - - ourDelay := float64(c.diff - c.baseDelay.Min()) - if ourDelay != 0.0 { - offTarget := 100000.0 - ourDelay - windowFactor := float64(mtu) / float64(c.maxWindow) - delayFactor := offTarget / 100000.0 - gain := 3000.0 * delayFactor * windowFactor - c.maxWindow = uint32(int(c.maxWindow) + int(gain)) - if c.maxWindow < mtu { - c.maxWindow = mtu - } - ulog.Printf(4, "Conn(%v): Update maxWindow: %d", c.LocalAddr(), c.maxWindow) - } - } - - c.sendbuf.compact() - - if c.lastAck == p.header.ack { - c.dupAck++ - if c.dupAck >= 2 { - c.stat.receivedDuplicatedACKs++ - ulog.Printf(3, "Conn(%v): Receive 3 duplicated acks: %d", c.LocalAddr(), p.header.ack) - p := c.sendbuf.front() - if p != nil { - c.maxWindow /= 2 - if c.maxWindow < mtu { - c.maxWindow = mtu - } - ulog.Printf(4, "Conn(%v): Update maxWindow: %d", c.LocalAddr(), c.maxWindow) - c.resend(p) - } - c.dupAck = 0 - } - } else { - c.dupAck = 0 - } - - c.lastAck = p.header.ack - if p.header.ack == c.seq-1 { - wnd := p.header.wnd - if wnd > c.maxWindow { - wnd = c.maxWindow - } - c.writebuf.Reset(wnd) - } - - if c.state == stateSynSent { - c.recvbuf = newPacketBuffer(windowSize, int(p.header.seq)) - c.state = stateConnected - close(c.connch) - } - - case stReset: - c.sendRST() - c.close() - - default: - c.recvbuf.push(p) - for _, s := range c.recvbuf.fetchSequence() { - c.ack = s.header.seq - if s.header.typ == stData { - c.readbuf.Write(s.payload) - } else if s.header.typ == stFin { - c.enterClosing() - } - } - c.sendACK() - } -} - -func (c *Conn) sendACK() { - ack := c.makePacket(stState, nil, c.raddr) - selack := c.sendbuf.generateSelectiveACK() - if selack != nil { - c.stat.sentSelectiveACKs++ - ack.ext = []extension{ - extension{ - typ: extSelectiveAck, - payload: selack, - }, - } - } - c.stat.sentPackets++ - c.conn.Send(ack) -} - -func (c *Conn) sendSYN() { - syn := c.makePacket(stSyn, nil, c.raddr) - err := c.sendbuf.push(syn) - if err != nil { - ulog.Printf(2, "Conn(%v): buffer error: %v", c.LocalAddr(), err) - return - } - c.stat.sentPackets++ - c.conn.Send(syn) -} - -func (c *Conn) sendFIN() error { - fin := c.makePacket(stFin, nil, c.raddr) - err := c.sendbuf.push(fin) - if err != nil { - ulog.Printf(2, "Conn(%v): buffer error: %v", c.LocalAddr(), err) - return err - } - c.stat.sentPackets++ - c.conn.Send(fin) - return nil -} - -func (c *Conn) sendRST() { - rst := c.makePacket(stReset, nil, c.raddr) - c.stat.sentPackets++ - c.conn.Send(rst) -} - -func (c *Conn) sendDATA(b []byte) { - for i := 0; i <= len(b)/mss; i++ { - l := len(b) - i*mss - if l > mss { - l = mss - } - data := c.makePacket(stData, b[i*mss:i*mss+l], c.raddr) - c.sendbuf.push(data) - c.stat.sentPackets++ - c.conn.Send(data) - } -} - -func (c *Conn) resend(p *packet) { - c.stat.resentPackets++ - c.conn.Send(p) - ulog.Printf(3, "Conn(%v): RESEND: %s", c.LocalAddr(), p.String()) -} - -func (c *Conn) makePacket(typ int, payload []byte, dst net.Addr) *packet { - wnd := windowSize * mtu - if c.recvbuf != nil { - wnd = c.recvbuf.space() * mtu - } - s := c.readbuf.space() - if wnd > s { - wnd = s - } - id := c.sid - if typ == stSyn { - id = c.rid - } - p := &packet{} - p.header.typ = typ - p.header.ver = version - p.header.id = id - p.header.t = currentMicrosecond() - p.header.diff = c.diff - p.header.wnd = uint32(wnd) - p.header.seq = c.seq - p.header.ack = c.ack - p.addr = dst - if typ != stState && typ != stFin { - c.seq++ - } - p.payload = payload - return p -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/conn_test.go b/Godeps/_workspace/src/github.com/h2so5/utp/conn_test.go deleted file mode 100644 index 8645d322f..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/conn_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package utp - -import ( - "bytes" - "testing" -) - -func TestReadWrite(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - l, err := Listen("utp", addr) - if err != nil { - t.Fatal(err) - } - defer l.Close() - - payload := []byte("abcdefgh") - - ch := make(chan int) - go func() { - c, err := l.Accept() - if err != nil { - t.Fatal(err) - } - defer c.Close() - - var buf [256]byte - length, err := c.Read(buf[:]) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(payload, buf[:length]) { - t.Errorf("expected payload of %v; got %v", payload, buf[:length]) - } - - ch <- 0 - }() - - c, err := DialUTP("utp", nil, l.Addr().(*Addr)) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - _, err = c.Write(payload) - if err != nil { - t.Fatal(err) - } - - <-ch -} - -func TestClose(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - l, err := Listen("utp", addr) - if err != nil { - t.Fatal(err) - } - defer l.Close() - - go func() { - c, err := l.Accept() - if err != nil { - t.Fatal(err) - } - c.Close() - }() - - c, err := DialUTP("utp", nil, l.Addr().(*Addr)) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - var b [128]byte - _, err = c.Read(b[:]) - if err == nil { - t.Fatal("Read should fail") - } -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/dial.go b/Godeps/_workspace/src/github.com/h2so5/utp/dial.go deleted file mode 100644 index 8647b8c73..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/dial.go +++ /dev/null @@ -1,102 +0,0 @@ -package utp - -import ( - "errors" - "math" - "math/rand" - "net" - "time" -) - -// DialUTP connects to the remote address raddr on the network net, -// which must be "utp", "utp4", or "utp6". If laddr is not nil, it is -// used as the local address for the connection. -func DialUTP(n string, laddr, raddr *Addr) (*Conn, error) { - return DialUTPTimeout(n, laddr, raddr, 0) -} - -// DialUTPTimeout acts like Dial but takes a timeout. -// The timeout includes name resolution, if required. -func DialUTPTimeout(n string, laddr, raddr *Addr, timeout time.Duration) (*Conn, error) { - conn, err := getSharedBaseConn(n, laddr) - if err != nil { - return nil, err - } - - id := uint16(rand.Intn(math.MaxUint16)) - c := newConn() - c.conn = conn - c.raddr = raddr.Addr - c.rid = id - c.sid = id + 1 - c.seq = 1 - c.state = stateSynSent - c.sendbuf = newPacketBuffer(windowSize*2, 1) - c.conn.Register(int32(c.rid), c.recv) - go c.loop() - c.synch <- 0 - - t := time.NewTimer(timeout) - defer t.Stop() - if timeout == 0 { - t.Stop() - } - - select { - case <-c.connch: - case <-t.C: - c.Close() - return nil, &net.OpError{ - Op: "dial", - Net: c.LocalAddr().Network(), - Addr: c.LocalAddr(), - Err: errTimeout, - } - } - return c, nil -} - -// A Dialer contains options for connecting to an address. -// -// The zero value for each field is equivalent to dialing without -// that option. Dialing with the zero value of Dialer is therefore -// equivalent to just calling the Dial function. -type Dialer struct { - // Timeout is the maximum amount of time a dial will wait for - // a connect to complete. If Deadline is also set, it may fail - // earlier. - // - // The default is no timeout. - // - // With or without a timeout, the operating system may impose - // its own earlier timeout. For instance, TCP timeouts are - // often around 3 minutes. - Timeout time.Duration - - // LocalAddr is the local address to use when dialing an - // address. The address must be of a compatible type for the - // network being dialed. - // If nil, a local address is automatically chosen. - LocalAddr net.Addr -} - -// Dial connects to the address on the named network. -// -// See func Dial for a description of the network and address parameters. -func (d *Dialer) Dial(n, addr string) (*Conn, error) { - raddr, err := ResolveAddr(n, addr) - if err != nil { - return nil, err - } - - var laddr *Addr - if d.LocalAddr != nil { - var ok bool - laddr, ok = d.LocalAddr.(*Addr) - if !ok { - return nil, errors.New("Dialer.LocalAddr is not a Addr") - } - } - - return DialUTPTimeout(n, laddr, raddr, d.Timeout) -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/dial_test.go b/Godeps/_workspace/src/github.com/h2so5/utp/dial_test.go deleted file mode 100644 index 784607621..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/dial_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package utp - -import ( - "testing" - "time" -) - -func TestDial(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - l, err := Listen("utp", addr) - if err != nil { - t.Fatal(err) - } - defer l.Close() - - ch := make(chan struct{}) - go func() { - l.Accept() - close(ch) - }() - - c, err := DialUTP("utp", nil, l.Addr().(*Addr)) - if err != nil { - t.Fatal(err) - } - defer c.Close() - - <-ch -} - -func TestDialFastTimeout(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - l, err := Listen("utp", addr) - if err != nil { - t.Fatal(err) - } - defer l.Close() - _, err = (&Dialer{ - Timeout: time.Nanosecond, - }).Dial("utp", l.Addr().String()) - if err == nil { - t.Fatal("expected an error") - } -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/listener.go b/Godeps/_workspace/src/github.com/h2so5/utp/listener.go deleted file mode 100644 index cdf9a7c03..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/listener.go +++ /dev/null @@ -1,146 +0,0 @@ -package utp - -import ( - "math" - "math/rand" - "net" - "sync" - "sync/atomic" - "syscall" - "time" -) - -// Listener is a UTP network listener. Clients should typically -// use variables of type Listener instead of assuming UTP. -type Listener struct { - // RawConn represents an out-of-band connection. - // This allows a single socket to handle multiple protocols. - RawConn net.PacketConn - - conn *baseConn - deadline time.Time - deadlineMutex sync.RWMutex - closed int32 -} - -func (l *Listener) ok() bool { return l != nil && l.conn != nil } - -// Listen announces on the UTP address laddr and returns a UTP -// listener. Net must be "utp", "utp4", or "utp6". If laddr has a -// port of 0, ListenUTP will choose an available port. The caller can -// use the Addr method of Listener to retrieve the chosen address. -func Listen(n string, laddr *Addr) (*Listener, error) { - conn, err := newBaseConn(n, laddr) - if err != nil { - return nil, err - } - l := &Listener{ - RawConn: conn, - conn: conn, - } - conn.Register(-1, nil) - return l, nil -} - -// Accept implements the Accept method in the Listener interface; it -// waits for the next call and returns a generic Conn. -func (l *Listener) Accept() (net.Conn, error) { - return l.AcceptUTP() -} - -// AcceptUTP accepts the next incoming call and returns the new -// connection. -func (l *Listener) AcceptUTP() (*Conn, error) { - if !l.ok() { - return nil, syscall.EINVAL - } - if !l.isOpen() { - return nil, &net.OpError{ - Op: "accept", - Net: l.conn.LocalAddr().Network(), - Addr: l.conn.LocalAddr(), - Err: errClosing, - } - } - l.deadlineMutex.RLock() - d := timeToDeadline(l.deadline) - l.deadlineMutex.RUnlock() - p, err := l.conn.RecvSyn(d) - if err != nil { - return nil, &net.OpError{ - Op: "accept", - Net: l.conn.LocalAddr().Network(), - Addr: l.conn.LocalAddr(), - Err: errClosing, - } - } - - seq := rand.Intn(math.MaxUint16) - rid := p.header.id + 1 - - c := newConn() - c.state = stateConnected - c.conn = l.conn - c.raddr = p.addr - c.rid = p.header.id + 1 - c.sid = p.header.id - c.seq = uint16(seq) - c.ack = p.header.seq - c.recvbuf = newPacketBuffer(windowSize, int(p.header.seq)) - c.sendbuf = newPacketBuffer(windowSize*2, seq) - l.conn.Register(int32(rid), c.recv) - go c.loop() - c.recv <- p - - ulog.Printf(2, "baseConn(%v): accept #%d from %v", c.LocalAddr(), c.rid, c.raddr) - return c, nil -} - -// Addr returns the listener's network address, a *Addr. -func (l *Listener) Addr() net.Addr { - if !l.ok() { - return nil - } - return l.conn.LocalAddr() -} - -// Close stops listening on the UTP address. -// Already Accepted connections are not closed. -func (l *Listener) Close() error { - if !l.ok() { - return syscall.EINVAL - } - if !l.close() { - return &net.OpError{ - Op: "close", - Net: l.conn.LocalAddr().Network(), - Addr: l.conn.LocalAddr(), - Err: errClosing, - } - } - return nil -} - -// SetDeadline sets the deadline associated with the listener. -// A zero time value disables the deadline. -func (l *Listener) SetDeadline(t time.Time) error { - if !l.ok() { - return syscall.EINVAL - } - l.deadlineMutex.Lock() - defer l.deadlineMutex.Unlock() - l.deadline = t - return nil -} - -func (l *Listener) close() bool { - if atomic.CompareAndSwapInt32(&l.closed, 0, 1) { - l.conn.Unregister(-1) - return true - } - return false -} - -func (l *Listener) isOpen() bool { - return atomic.LoadInt32(&l.closed) == 0 -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/listener_test.go b/Godeps/_workspace/src/github.com/h2so5/utp/listener_test.go deleted file mode 100644 index e9018a32c..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/listener_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package utp - -import ( - "net" - "testing" -) - -func TestListenerAccept(t *testing.T) { - addr, err := ResolveAddr("utp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - l, err := Listen("utp", addr) - if err != nil { - t.Fatal(err) - } - defer l.Close() - - uaddr, err := net.ResolveUDPAddr("udp", l.Addr().String()) - if err != nil { - t.Fatal(err) - } - - uc, err := net.DialUDP("udp", nil, uaddr) - if err != nil { - t.Fatal(err) - } - defer uc.Close() - - for i := 0; i < 1; i++ { - p := &packet{header: header{typ: stSyn, ver: version, id: uint16(i)}} - payload, err := p.MarshalBinary() - if err != nil { - t.Fatal(err) - } - go func() { - uc.Write(payload) - }() - - a, err := l.Accept() - if err != nil { - t.Fatal(err) - } - a.Close() - } -} - -func TestListenerClose(t *testing.T) { - addr, err := ResolveAddr("utp", ":0") - if err != nil { - t.Fatal(err) - } - - l, err := Listen("utp", addr) - if err != nil { - t.Fatal(err) - } - - for i := 0; i < 5; i++ { - l.Close() - } - - _, err = l.Accept() - if err == nil { - t.Fatal("Accept should fail") - } -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/log.go b/Godeps/_workspace/src/github.com/h2so5/utp/log.go deleted file mode 100644 index dbf4ddcd3..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/log.go +++ /dev/null @@ -1,50 +0,0 @@ -package utp - -import ( - "log" - "os" - "strconv" -) - -type logger struct { - level int -} - -var ulog *logger - -func init() { - logenv := os.Getenv("GO_UTP_LOGGING") - - var level int - if len(logenv) > 0 { - l, err := strconv.Atoi(logenv) - if err != nil { - log.Print("warning: GO_UTP_LOGGING must be numeric") - } else { - level = l - } - } - - ulog = &logger{level} -} - -func (l *logger) Print(level int, v ...interface{}) { - if l.level < level { - return - } - log.Print(v...) -} - -func (l *logger) Printf(level int, format string, v ...interface{}) { - if l.level < level { - return - } - log.Printf(format, v...) -} - -func (l *logger) Println(level int, v ...interface{}) { - if l.level < level { - return - } - log.Println(v...) -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/packet.go b/Godeps/_workspace/src/github.com/h2so5/utp/packet.go deleted file mode 100644 index da3c302f6..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/packet.go +++ /dev/null @@ -1,198 +0,0 @@ -package utp - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "io/ioutil" - "net" -) - -type header struct { - typ, ver int - id uint16 - t, diff, wnd uint32 - seq, ack uint16 -} - -type extension struct { - typ int - payload []byte -} - -type packet struct { - header header - ext []extension - payload []byte - addr net.Addr -} - -func (p *packet) MarshalBinary() ([]byte, error) { - firstExt := extNone - if len(p.ext) > 0 { - firstExt = p.ext[0].typ - } - buf := new(bytes.Buffer) - var beforeExt = []interface{}{ - // | type | ver | - uint8(((byte(p.header.typ) << 4) & 0xF0) | (byte(p.header.ver) & 0xF)), - // | extension | - uint8(firstExt), - } - var afterExt = []interface{}{ - // | connection_id | - uint16(p.header.id), - // | timestamp_microseconds | - uint32(p.header.t), - // | timestamp_difference_microseconds | - uint32(p.header.diff), - // | wnd_size | - uint32(p.header.wnd), - // | seq_nr | - uint16(p.header.seq), - // | ack_nr | - uint16(p.header.ack), - } - - for _, v := range beforeExt { - err := binary.Write(buf, binary.BigEndian, v) - if err != nil { - return nil, err - } - } - - if len(p.ext) > 0 { - for i, e := range p.ext { - next := extNone - if i < len(p.ext)-1 { - next = p.ext[i+1].typ - } - var ext = []interface{}{ - // | extension | - uint8(next), - // | len | - uint8(len(e.payload)), - } - for _, v := range ext { - err := binary.Write(buf, binary.BigEndian, v) - if err != nil { - return nil, err - } - } - _, err := buf.Write(e.payload) - if err != nil { - return nil, err - } - } - } - - for _, v := range afterExt { - err := binary.Write(buf, binary.BigEndian, v) - if err != nil { - return nil, err - } - } - - _, err := buf.Write(p.payload) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func (p *packet) UnmarshalBinary(data []byte) error { - p.ext = nil - buf := bytes.NewReader(data) - var tv, e uint8 - - var beforeExt = []interface{}{ - // | type | ver | - (*uint8)(&tv), - // | extension | - (*uint8)(&e), - } - for _, v := range beforeExt { - err := binary.Read(buf, binary.BigEndian, v) - if err != nil { - return err - } - } - - for e != extNone { - currentExt := int(e) - var l uint8 - var ext = []interface{}{ - // | extension | - (*uint8)(&e), - // | len | - (*uint8)(&l), - } - for _, v := range ext { - err := binary.Read(buf, binary.BigEndian, v) - if err != nil { - return err - } - } - payload := make([]byte, l) - size, err := buf.Read(payload[:]) - if err != nil { - return err - } - if size != len(payload) { - return io.EOF - } - p.ext = append(p.ext, extension{typ: currentExt, payload: payload}) - } - - var afterExt = []interface{}{ - // | connection_id | - (*uint16)(&p.header.id), - // | timestamp_microseconds | - (*uint32)(&p.header.t), - // | timestamp_difference_microseconds | - (*uint32)(&p.header.diff), - // | wnd_size | - (*uint32)(&p.header.wnd), - // | seq_nr | - (*uint16)(&p.header.seq), - // | ack_nr | - (*uint16)(&p.header.ack), - } - for _, v := range afterExt { - err := binary.Read(buf, binary.BigEndian, v) - if err != nil { - return err - } - } - - p.header.typ = int((tv >> 4) & 0xF) - p.header.ver = int(tv & 0xF) - - data, err := ioutil.ReadAll(buf) - if err != nil { - return err - } - p.payload = data - - return nil -} - -func (p packet) String() string { - s := fmt.Sprintf("[%d ", p.header.id) - switch p.header.typ { - case stData: - s += "ST_DATA" - case stFin: - s += "ST_FIN" - case stState: - s += "ST_STATE" - case stReset: - s += "ST_RESET" - case stSyn: - s += "ST_SYN" - } - s += fmt.Sprintf(" seq:%d ack:%d len:%d", p.header.seq, p.header.ack, len(p.payload)) - s += "]" - return s -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/packet_test.go b/Godeps/_workspace/src/github.com/h2so5/utp/packet_test.go deleted file mode 100644 index 994fa2cc4..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/packet_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package utp - -import ( - "io" - "reflect" - "testing" -) - -func TestPacketBinary(t *testing.T) { - h := header{ - typ: stFin, - ver: version, - id: 100, - t: 50000, - diff: 10000, - wnd: 65535, - seq: 100, - ack: 200, - } - - e := []extension{ - extension{ - typ: extSelectiveAck, - payload: []byte{0, 1, 0, 1}, - }, - extension{ - typ: extSelectiveAck, - payload: []byte{100, 0, 200, 0}, - }, - } - - p := packet{ - header: h, - ext: e, - payload: []byte("abcdefg"), - } - - b, err := p.MarshalBinary() - if err != nil { - t.Fatal(err) - } - - p2 := packet{payload: make([]byte, 0, mss)} - err = p2.UnmarshalBinary(b) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(p, p2) { - t.Errorf("expected packet of %v; got %v", p, p2) - } -} - -func TestUnmarshalShortPacket(t *testing.T) { - b := make([]byte, 18) - p := packet{} - err := p.UnmarshalBinary(b) - - if err == nil { - t.Fatal("UnmarshalBinary should fail") - } else if err != io.EOF { - t.Fatal(err) - } -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/ucat/.gitignore b/Godeps/_workspace/src/github.com/h2so5/utp/ucat/.gitignore deleted file mode 100644 index aab84c12d..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/ucat/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -ucat -random -.trash/ diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/ucat/Makefile b/Godeps/_workspace/src/github.com/h2so5/utp/ucat/Makefile deleted file mode 100644 index 6009f77a2..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/ucat/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# Run tests - -testnames=simple -tests=$(addprefix test_, $(testnames)) -trash=.trash/ - -all: ucat - -test: clean ucat ${tests} - @echo ${tests} - @echo "*** tests passed ***" - -# not sue why this doesn't work: -# test_%: test_%.sh -test_simple: test_simple.sh - mkdir -p ${trash} - @echo "*** running $@ ***" - ./$@.sh - -clean: - @echo "*** $@ ***" - -rm -r ${trash} - -deps: random ucat - -ucat: - go build - -random: - @echo "*** installing $@ ***" - go get github.com/jbenet/go-random/random - go build -o random github.com/jbenet/go-random/random - -.PHONY: clean ucat ${tests} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/ucat/test_simple.sh b/Godeps/_workspace/src/github.com/h2so5/utp/ucat/test_simple.sh deleted file mode 100644 index 5e7842ca2..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/ucat/test_simple.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh - -set -e # exit on error -# set -v # verbose - -log() { - echo "--> $1" -} - -test_send() { - file=$1_ - count=$2 - addr=localhost:8765 - - # generate random data - log "generating $count bytes of random data" - ./random $count $RANDOM > ${file}expected - - # dialer sends - log "sending from dialer" - ./ucat -v $addr 2>&1 <${file}expected | sed "s/^/ dialer1: /" & - ./ucat -v -l $addr 2>&1 >${file}actual1 | sed "s/^/listener1: /" - diff ${file}expected ${file}actual1 - if test $? != 0; then - log "sending from dialer failed. compare with:\n" - log "diff ${file}expected ${file}actual1" - exit 1 - fi - - # listener sends - log "sending from listener" - ./ucat -v -l $addr 2>&1 <${file}expected | sed "s/^/listener2: /" & - ./ucat -v $addr 2>&1 >${file}actual2 | sed "s/^/ dialer2: /" - diff ${file}expected ${file}actual2 - if test $? != 0; then - log "sending from listener failed. compare with:\n" - log "diff ${file}expected ${file}actual2" - exit 1 - fi - - echo rm ${file}{expected,actual1,actual2} - rm ${file}{expected,actual1,actual2} - return 0 -} - - -test_send ".trash/1KB" 1024 -test_send ".trash/1MB" 1048576 -test_send ".trash/1GB" 1073741824 diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/ucat/ucat.go b/Godeps/_workspace/src/github.com/h2so5/utp/ucat/ucat.go deleted file mode 100644 index afc15a393..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/ucat/ucat.go +++ /dev/null @@ -1,192 +0,0 @@ -// package ucat provides an implementation of netcat using the go utp package. -// It is meant to exercise the utp implementation. -// Usage: -// ucat [] -// ucat -l -// -// Address format is: [host]:port -// -// Note that uTP's congestion control gives priority to tcp flows (web traffic), -// so you could use this ucat tool to transfer massive files without hogging -// all the bandwidth. -package main - -import ( - "flag" - "fmt" - "io" - "net" - "os" - "os/signal" - "syscall" - - utp "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/h2so5/utp" -) - -var verbose = false - -// Usage prints out the usage of this module. -// Assumes flags use go stdlib flag pacakage. -var Usage = func() { - text := `ucat - uTP netcat in Go - -Usage: - - listen: %s [] - dial: %s -l - -Address format is Go's: [host]:port -` - - fmt.Fprintf(os.Stderr, text, os.Args[0], os.Args[0]) - flag.PrintDefaults() -} - -type args struct { - listen bool - verbose bool - localAddr string - remoteAddr string -} - -func parseArgs() args { - var a args - - // setup + parse flags - flag.BoolVar(&a.listen, "listen", false, "listen for connections") - flag.BoolVar(&a.listen, "l", false, "listen for connections (short)") - flag.BoolVar(&a.verbose, "v", false, "verbose debugging") - flag.Usage = Usage - flag.Parse() - osArgs := flag.Args() - - if len(osArgs) < 1 { - exit("") - } - - if a.listen { - a.localAddr = osArgs[0] - } else { - if len(osArgs) > 1 { - a.localAddr = osArgs[0] - a.remoteAddr = osArgs[1] - } else { - a.remoteAddr = osArgs[0] - } - } - - return a -} - -func main() { - args := parseArgs() - verbose = args.verbose - - var err error - if args.listen { - err = Listen(args.localAddr) - } else { - err = Dial(args.localAddr, args.remoteAddr) - } - - if err != nil { - exit("%s", err) - } -} - -func exit(format string, vals ...interface{}) { - if format != "" { - fmt.Fprintf(os.Stderr, "ucat error: "+format+"\n", vals...) - } - Usage() - os.Exit(1) -} - -func log(format string, vals ...interface{}) { - if verbose { - fmt.Fprintf(os.Stderr, "ucat log: "+format+"\n", vals...) - } -} - -// Listen listens and accepts one incoming uTP connection on a given port, -// and pipes all incoming data to os.Stdout. -func Listen(localAddr string) error { - laddr, err := utp.ResolveAddr("utp", localAddr) - if err != nil { - return fmt.Errorf("failed to resolve address %s", localAddr) - } - l, err := utp.Listen("utp", laddr) - if err != nil { - return err - } - log("listening at %s", l.Addr()) - - c, err := l.Accept() - if err != nil { - return err - } - log("accepted connection from %s", c.RemoteAddr()) - - // should be able to close listener here, but utp.Listener.Close - // closes all open connections. - defer l.Close() - - netcat(c) - return c.Close() -} - -// Dial connects to a remote address and pipes all os.Stdin to the remote end. -// If localAddr is set, uses it to Dial from. -func Dial(localAddr, remoteAddr string) error { - - var laddr net.Addr - var err error - if localAddr != "" { - laddr, err = utp.ResolveAddr("utp", localAddr) - if err != nil { - return fmt.Errorf("failed to resolve address %s", localAddr) - } - } - - if laddr != nil { - log("dialing %s from %s", remoteAddr, laddr) - } else { - log("dialing %s", remoteAddr) - } - - d := utp.Dialer{LocalAddr: laddr} - c, err := d.Dial("utp", remoteAddr) - if err != nil { - return err - } - log("connected to %s", c.RemoteAddr()) - - netcat(c) - return c.Close() -} - -func netcat(c net.Conn) { - log("piping stdio to connection") - - done := make(chan struct{}) - - go func() { - n, _ := io.Copy(c, os.Stdin) - log("sent %d bytes", n) - done <- struct{}{} - }() - go func() { - n, _ := io.Copy(os.Stdout, c) - log("received %d bytes", n) - done <- struct{}{} - }() - - // wait until we exit. - sigc := make(chan os.Signal, 1) - signal.Notify(sigc, syscall.SIGHUP, syscall.SIGINT, - syscall.SIGTERM, syscall.SIGQUIT) - select { - case <-done: - case <-sigc: - } -} diff --git a/Godeps/_workspace/src/github.com/h2so5/utp/utp.go b/Godeps/_workspace/src/github.com/h2so5/utp/utp.go deleted file mode 100644 index 1ac4b31ba..000000000 --- a/Godeps/_workspace/src/github.com/h2so5/utp/utp.go +++ /dev/null @@ -1,47 +0,0 @@ -package utp - -import ( - "errors" - "time" -) - -const ( - version = 1 - - stData = 0 - stFin = 1 - stState = 2 - stReset = 3 - stSyn = 4 - - stateClosed = iota - stateClosing - stateSynSent - stateConnected - stateFinSent - - extNone = 0 - extSelectiveAck = 1 - - headerSize = 20 - mtu = 3200 - mss = mtu - headerSize - windowSize = 100 - packetBufferSize = 256 - readBufferSize = 1048576 - maxRetry = 3 - - maxUdpPayload = 65507 - resetTimeout = time.Second -) - -type timeoutError struct{} - -func (e *timeoutError) Error() string { return "i/o timeout" } -func (e *timeoutError) Timeout() bool { return true } -func (e *timeoutError) Temporary() bool { return true } - -var ( - errTimeout error = &timeoutError{} - errClosing = errors.New("use of closed network connection") -) diff --git a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/Godeps/Godeps.json b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/Godeps/Godeps.json index 3df340b8b..9cbebc86f 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/Godeps/Godeps.json +++ b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/Godeps/Godeps.json @@ -6,8 +6,20 @@ ], "Deps": [ { - "ImportPath": "github.com/h2so5/utp", - "Rev": "5288a05e1781334589c4b8806bcfb1e69f5b5d63" + "ImportPath": "github.com/anacrolix/jitter", + "Rev": "2ea5c18645100745b24e9f5cfc9b3f6f7eac51ef" + }, + { + "ImportPath": "github.com/anacrolix/missinggo", + "Rev": "4e1ca5963308863b56c31863f60c394a7365ec29" + }, + { + "ImportPath": "github.com/anacrolix/utp", + "Rev": "0bb24de92c268452fb9106ca4fb9302442ca0dee" + }, + { + "ImportPath": "github.com/bradfitz/iter", + "Rev": "454541ec3da2a73fc34fd049b19ee5777bf19345" }, { "ImportPath": "github.com/jbenet/go-base58", diff --git a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/convert.go b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/convert.go index 21a14eb38..d129bd4af 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/convert.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/convert.go @@ -5,14 +5,18 @@ import ( "net" "strings" - utp "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/h2so5/utp" ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + utp "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/utp" ) var errIncorrectNetAddr = fmt.Errorf("incorrect network addr conversion") // FromNetAddr converts a net.Addr type to a Multiaddr. func FromNetAddr(a net.Addr) (ma.Multiaddr, error) { + if a == nil { + return nil, fmt.Errorf("nil multiaddr") + } + switch a.Network() { case "tcp", "tcp4", "tcp6": ac, ok := a.(*net.TCPAddr) @@ -63,7 +67,7 @@ func FromNetAddr(a net.Addr) (ma.Multiaddr, error) { } // Get UDP Addr - ac, ok := acc.Addr.(*net.UDPAddr) + ac, ok := acc.Child().(*net.UDPAddr) if !ok { return nil, errIncorrectNetAddr } diff --git a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/convert_test.go b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/convert_test.go index 4ccc2871a..3a4c171a6 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/convert_test.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/convert_test.go @@ -4,8 +4,8 @@ import ( "net" "testing" - utp "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/h2so5/utp" ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + mautp "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/utp" ) type GenFunc func() (ma.Multiaddr, error) @@ -90,13 +90,9 @@ func TestFromUDP(t *testing.T) { } func TestFromUTP(t *testing.T) { + a := &net.UDPAddr{IP: net.ParseIP("10.20.30.40"), Port: 1234} testConvert(t, "/ip4/10.20.30.40/udp/1234/utp", func() (ma.Multiaddr, error) { - return FromNetAddr(&utp.Addr{ - Addr: &net.UDPAddr{ - IP: net.ParseIP("10.20.30.40"), - Port: 1234, - }, - }) + return FromNetAddr(mautp.MakeAddr(a)) }) } diff --git a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/multiaddr/multiaddr.go b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/multiaddr/multiaddr.go index e80125c61..5c010ff61 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/multiaddr/multiaddr.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/multiaddr/multiaddr.go @@ -10,8 +10,8 @@ import ( manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net" ) -var formats = []string{"string", "bytes", // flags -"hex", "slice"} +// flags +var formats = []string{"string", "bytes", "hex", "slice"} var format string var hideLoopback bool diff --git a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net.go b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net.go index b7b1f5373..bc007a149 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net.go @@ -4,8 +4,8 @@ import ( "fmt" "net" - // utp "github.com/jbenet/go-multiaddr-net/Godeps/_workspace/src/github.com/h2so5/utp" ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + mautp "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/utp" ) // Conn is the equivalent of a net.Conn object. It is the @@ -109,24 +109,21 @@ func (d *Dialer) Dial(remote ma.Multiaddr) (Conn, error) { // ok, Dial! var nconn net.Conn switch rnet { - case "tcp", "tcp4", "tcp6": + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": nconn, err = d.Dialer.Dial(rnet, rnaddr) if err != nil { return nil, err } - case "udp", "udp4", "udp6": - return nil, fmt.Errorf("utp is currently broken") - - // // construct utp dialer, with options on our net.Dialer - // utpd := utp.Dialer{ - // Timeout: d.Dialer.Timeout, - // LocalAddr: d.Dialer.LocalAddr, - // } - // - // nconn, err = utpd.Dial(rnet, rnaddr) - // if err != nil { - // return nil, err - // } + case "utp", "utp4", "utp6": + utpd := mautp.Dialer{ + Timeout: d.Timeout, + LocalAddr: d.Dialer.LocalAddr, + } + // construct utp dialer, with options on our net.Dialer + nconn, err = utpd.Dial(rnet, rnaddr) + if err != nil { + return nil, err + } } // get local address (pre-specified or assigned within net.Conn) @@ -229,9 +226,8 @@ func Listen(laddr ma.Multiaddr) (Listener, error) { var nl net.Listener switch lnet { - case "utp": - // nl, err = utp.Listen(lnet, lnaddr) - return nil, fmt.Errorf("utp is currently broken") + case "utp", "utp4", "utp6": + nl, err = mautp.Listen(lnet, lnaddr) default: nl, err = net.Listen(lnet, lnaddr) } diff --git a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net_test.go b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net_test.go index ecc081387..08498c269 100644 --- a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net_test.go +++ b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/net_test.go @@ -246,12 +246,10 @@ func TestListenAndDial(t *testing.T) { } func TestListenAndDialUTP(t *testing.T) { - t.Skip("utp is broken") - maddr := newMultiaddr(t, "/ip4/127.0.0.1/udp/4323/utp") listener, err := Listen(maddr) if err != nil { - t.Fatal("failed to listen") + t.Fatal("failed to listen: ", err) } var wg sync.WaitGroup @@ -267,6 +265,8 @@ func TestListenAndDialUTP(t *testing.T) { t.Fatal("local multiaddr not equal:", maddr, cB.LocalMultiaddr()) } + defer cB.Close() + // echo out buf := make([]byte, 1024) for { diff --git a/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/utp/utp_util.go b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/utp/utp_util.go new file mode 100644 index 000000000..69fbf0e79 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net/utp/utp_util.go @@ -0,0 +1,105 @@ +package utp + +import ( + "errors" + "net" + "time" + + utp "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/anacrolix/utp" +) + +type Listener struct { + *utp.Socket +} + +type Conn struct { + net.Conn +} + +type Addr struct { + net string + child net.Addr +} + +func (ca *Addr) Network() string { + return ca.net +} + +func (ca *Addr) String() string { + return ca.child.String() +} + +func (ca *Addr) Child() net.Addr { + return ca.child +} + +func MakeAddr(a net.Addr) net.Addr { + return &Addr{ + net: "utp", + child: a, + } +} + +func ResolveAddr(network string, host string) (net.Addr, error) { + a, err := net.ResolveUDPAddr("udp"+network[3:], host) + if err != nil { + return nil, err + } + + return MakeAddr(a), nil +} + +func (u *Conn) LocalAddr() net.Addr { + return MakeAddr(u.Conn.LocalAddr()) +} + +func (u *Conn) RemoteAddr() net.Addr { + return MakeAddr(u.Conn.RemoteAddr()) +} + +func Listen(network string, laddr string) (net.Listener, error) { + switch network { + case "utp", "utp4", "utp6": + s, err := utp.NewSocket("udp"+network[3:], laddr) + if err != nil { + return nil, err + } + + return &Listener{s}, nil + + default: + return nil, errors.New("unrecognized network: " + network) + } +} + +func (u *Listener) Accept() (net.Conn, error) { + c, err := u.Socket.Accept() + if err != nil { + return nil, err + } + + return &Conn{c}, nil +} + +func (u *Listener) Addr() net.Addr { + return MakeAddr(u.Socket.Addr()) +} + +type Dialer struct { + Timeout time.Duration + LocalAddr net.Addr +} + +func (d *Dialer) Dial(rnet string, raddr string) (net.Conn, error) { + if d.LocalAddr != nil { + s, err := utp.NewSocket(d.LocalAddr.Network(), d.LocalAddr.String()) + if err != nil { + return nil, err + } + + // zero timeout is the same as calling s.Dial() + return s.DialTimeout(raddr, d.Timeout) + } + + return utp.DialTimeout(raddr, d.Timeout) +}