2
0
mirror of https://github.com/soheilhy/cmux.git synced 2025-10-17 20:58:14 +08:00

18 Commits

Author SHA1 Message Date
Soheil Hassas Yeganeh
79b9df6ccf Add Go 1.8 to the travis config. 2017-03-13 09:57:57 -04:00
Soheil Hassas Yeganeh
210139db95 Change connection closed string in tests.
Go 1.8 and 1.9 use different text for the connection closed error.
Use their common prefix (i.e., "use of closed") in the tests for
them to pass on all Go versions.

Go 1.8: "use of closed network connection"
Go 1.9: "use of closed file or network connection"

Suggested-by: Damien Neil <dneil@google.com>
2017-03-13 09:57:57 -04:00
Soheil Hassas Yeganeh
bf4a8ede9e Merge pull request #36 from soheilhy/dev/fix-mem-grow
Fix memory growth
2016-09-25 21:07:37 -04:00
Soheil Hassas Yeganeh
f661dcfb59 Reset the sniffing buffer when not needed
The sniffing buffer will live as long as the connection is open,
and we should reset it as soon as the application has read all the
sniffed data.
2016-09-25 20:56:09 -04:00
Soheil Hassas Yeganeh
526b64db7a update the list of contributors. 2016-09-25 01:03:35 -04:00
Soheil Hassas Yeganeh
3ac8d3a667 Fix lint errors in cmux_test.go 2016-09-25 00:58:27 -04:00
Soheil Hassas Yeganeh
861c99e0fc Return not-match on different field values in HTTP2
Retun as soon as we have the matched field in the HTTP2 matcher
regardless of weather the value is matched or not. Fixes #35.

Issue #35 reports that cmux leaks memory when the client is HTTP2
but does not sends the expected header field. For example, when
the non-gRPC client sends a large field in the header and we are
matching for gRPC, we waste a lot of memory in the sniff buffer.
2016-09-25 00:58:17 -04:00
Soheil Hassas Yeganeh
13f520d62c Merge pull request #34 from ekle/master
SetReadDeadline for Matching
2016-09-05 16:05:55 -04:00
Andreas Jaekle
e132036cce SetReadDeadline for Matching 2016-09-05 19:56:50 +02:00
Soheil Hassas Yeganeh
b26951527b Merge pull request #33 from soheilhy/fix-b32
Fix index out of range in patricia tree
2016-07-15 11:38:34 -07:00
Soheil Hassas Yeganeh
eddb3b1467 Fix index out of range in patricia tree
Bug #32 reported that there is an index out of range error. This
issue was introduced in 703b087.

Fix #32 and add a test to detect this issue
2016-07-15 10:01:37 -07:00
Soheil Hassas Yeganeh
e85da3027e Merge pull request #30 from soheilhy/fix-patricia-tree
Fix race in patricia tree
2016-07-15 08:04:18 -07:00
Tamir Duberstein
df31d48636 Parallelize benchmarks 2016-07-14 22:03:14 -07:00
Soheil Hassas Yeganeh
fd01d3cc6c Fix race in patricia tree
This commit fixes a major issue added in 703b087a.

There are still wins on allocation though.

benchmark                      old ns/op     new ns/op     delta
BenchmarkCMuxConnHTTP1-4       783           836           +6.77%
BenchmarkCMuxConnHTTP2-4       895           806           -9.94%
BenchmarkCMuxConnHTTP1n2-4     1000          1026          +2.60%
BenchmarkCMuxConnHTTP2n1-4     916           961           +4.91%

benchmark                      old allocs     new allocs     delta
BenchmarkCMuxConnHTTP1-4       3              4              +33.33%
BenchmarkCMuxConnHTTP2-4       4              4              +0.00%
BenchmarkCMuxConnHTTP1n2-4     4              5              +25.00%
BenchmarkCMuxConnHTTP2n1-4     4              5              +25.00%

benchmark                      old bytes     new bytes     delta
BenchmarkCMuxConnHTTP1-4       272           280           +2.94%
BenchmarkCMuxConnHTTP2-4       304           304           +0.00%
BenchmarkCMuxConnHTTP1n2-4     304           312           +2.63%
BenchmarkCMuxConnHTTP2n1-4     304           312           +2.63%
2016-07-14 22:01:30 -07:00
Soheil Hassas Yeganeh
f952454ed9 Add the list of contributors
Add the list of contributors in a separate file and add links
to the LICENSE and CONTRIBUTORS files.

Fixes #28
2016-07-09 14:07:27 -04:00
Soheil Hassas Yeganeh
00342b4d79 Add package level docs 2016-07-09 14:01:24 -04:00
Soheil Hassas Yeganeh
cd9b7d74b9 Add copyright notice headers on all Go files.
As per http://www.apache.org/legal/src-headers.html#headers

Fixes #28
2016-07-09 13:56:02 -04:00
Soheil Hassas Yeganeh
9d1e2a64dd Merge pull request #25 from soheilhy/devel-optimize-patricia
Optimize Patricia tree
2016-05-03 22:33:52 -04:00
14 changed files with 438 additions and 54 deletions

View File

@@ -3,6 +3,8 @@ language: go
go:
- 1.5
- 1.6
- 1.7
- 1.8
- tip
matrix:
@@ -24,4 +26,5 @@ before_script:
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go tool vet --shadow .; fi
script:
- go test -v ./...
- go test -bench . -v ./...
- go test -race -bench . -v ./...

12
CONTRIBUTORS Normal file
View File

@@ -0,0 +1,12 @@
# The list of people who have contributed code to the cmux repository.
#
# Auto-generated with:
# git log --oneline --pretty=format:'%an <%aE>' | sort -u
#
Andreas Jaekle <andreas@jaekle.net>
Dmitri Shuralyov <shurcooL@gmail.com>
Ethan Mosbaugh <emosbaugh@gmail.com>
Soheil Hassas Yeganeh <soheil.h.y@gmail.com>
Soheil Hassas Yeganeh <soheil@cs.toronto.edu>
Tamir Duberstein <tamir@cockroachlabs.com>
Tamir Duberstein <tamird@gmail.com>

View File

@@ -74,3 +74,10 @@ gRPC server please match with writers:
```go
grpcl := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
```
# Copyright and License
Copyright 2016 The CMux Authors. All rights reserved.
See [CONTRIBUTORS](https://github.com/soheilhy/cmux/blob/master/CONTRIBUTORS)
for the CMux Authors. Code is released under
[the Apache 2 license](https://github.com/soheilhy/cmux/blob/master/LICENSE).

View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux
import (
@@ -6,6 +20,7 @@ import (
"net"
"sync"
"testing"
"time"
"golang.org/x/net/http2"
)
@@ -29,6 +44,10 @@ func (c *mockConn) Read(b []byte) (n int, err error) {
return c.r.Read(b)
}
func (c *mockConn) SetReadDeadline(time.Time) error {
return nil
}
func discard(l net.Listener) {
for {
if _, err := l.Accept(); err != nil {
@@ -48,12 +67,14 @@ func BenchmarkCMuxConnHTTP1(b *testing.B) {
wg.Add(b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
c := &mockConn{
r: bytes.NewReader(benchHTTP1Payload),
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
wg.Add(1)
m.serve(&mockConn{
r: bytes.NewReader(benchHTTP1Payload),
}, donec, &wg)
}
m.serve(c, donec, &wg)
}
})
}
func BenchmarkCMuxConnHTTP2(b *testing.B) {
@@ -66,12 +87,14 @@ func BenchmarkCMuxConnHTTP2(b *testing.B) {
wg.Add(b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
c := &mockConn{
r: bytes.NewReader(benchHTTP2Payload),
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
wg.Add(1)
m.serve(&mockConn{
r: bytes.NewReader(benchHTTP2Payload),
}, donec, &wg)
}
m.serve(c, donec, &wg)
}
})
}
func BenchmarkCMuxConnHTTP1n2(b *testing.B) {
@@ -84,15 +107,16 @@ func BenchmarkCMuxConnHTTP1n2(b *testing.B) {
donec := make(chan struct{})
var wg sync.WaitGroup
wg.Add(b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
c := &mockConn{
r: bytes.NewReader(benchHTTP2Payload),
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
wg.Add(1)
m.serve(&mockConn{
r: bytes.NewReader(benchHTTP2Payload),
}, donec, &wg)
}
m.serve(c, donec, &wg)
}
})
}
func BenchmarkCMuxConnHTTP2n1(b *testing.B) {
@@ -105,13 +129,14 @@ func BenchmarkCMuxConnHTTP2n1(b *testing.B) {
donec := make(chan struct{})
var wg sync.WaitGroup
wg.Add(b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
c := &mockConn{
r: bytes.NewReader(benchHTTP1Payload),
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
wg.Add(1)
m.serve(&mockConn{
r: bytes.NewReader(benchHTTP1Payload),
}, donec, &wg)
}
m.serve(c, donec, &wg)
}
})
}

View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux
import (
@@ -28,6 +42,10 @@ func (s *bufferedReader) Read(p []byte) (int, error) {
bn := copy(p, s.buffer.Bytes()[s.bufferRead:s.bufferSize])
s.bufferRead += bn
return bn, s.lastErr
} else if !s.sniffing && s.buffer.Cap() != 0 {
// We don't need the buffer anymore.
// Reset it to release the internal slice.
s.buffer = bytes.Buffer{}
}
// If there is nothing more to return in the sniffed buffer, read from the

50
cmux.go
View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux
import (
@@ -5,6 +19,7 @@ import (
"io"
"net"
"sync"
"time"
)
// Matcher matches a connection based on its content.
@@ -46,13 +61,17 @@ func (e errListenerClosed) Timeout() bool { return false }
// listener is closed.
var ErrListenerClosed = errListenerClosed("mux: listener closed")
// for readability of readTimeout
var noTimeout time.Duration
// New instantiates a new connection multiplexer.
func New(l net.Listener) CMux {
return &cMux{
root: l,
bufLen: 1024,
errh: func(_ error) bool { return true },
donec: make(chan struct{}),
root: l,
bufLen: 1024,
errh: func(_ error) bool { return true },
donec: make(chan struct{}),
readTimeout: noTimeout,
}
}
@@ -76,6 +95,8 @@ type CMux interface {
Serve() error
// HandleError registers an error handler that handles listener errors.
HandleError(ErrorHandler)
// sets a timeout for the read of matchers
SetReadTimeout(time.Duration)
}
type matchersListener struct {
@@ -84,11 +105,12 @@ type matchersListener struct {
}
type cMux struct {
root net.Listener
bufLen int
errh ErrorHandler
donec chan struct{}
sls []matchersListener
root net.Listener
bufLen int
errh ErrorHandler
donec chan struct{}
sls []matchersListener
readTimeout time.Duration
}
func matchersToMatchWriters(matchers []Matcher) []MatchWriter {
@@ -115,6 +137,10 @@ func (m *cMux) MatchWithWriters(matchers ...MatchWriter) net.Listener {
return ml
}
func (m *cMux) SetReadTimeout(t time.Duration) {
m.readTimeout = t
}
func (m *cMux) Serve() error {
var wg sync.WaitGroup
@@ -149,11 +175,17 @@ func (m *cMux) serve(c net.Conn, donec <-chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
muc := newMuxConn(c)
if m.readTimeout > noTimeout {
_ = c.SetReadDeadline(time.Now().Add(m.readTimeout))
}
for _, sl := range m.sls {
for _, s := range sl.ss {
matched := s(muc.Conn, muc.startSniffing())
if matched {
muc.doneSniffing()
if m.readTimeout > noTimeout {
_ = c.SetReadDeadline(time.Time{})
}
select {
case sl.l.connc <- muc:
case <-donec:

View File

@@ -1,10 +1,26 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/rpc"
@@ -17,6 +33,7 @@ import (
"time"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
)
const (
@@ -25,7 +42,7 @@ const (
)
func safeServe(errCh chan<- error, muxl CMux) {
if err := muxl.Serve(); !strings.Contains(err.Error(), "use of closed network connection") {
if err := muxl.Serve(); !strings.Contains(err.Error(), "use of closed") {
errCh <- err
}
}
@@ -59,14 +76,17 @@ func (l *chanListener) Accept() (net.Conn, error) {
}
func testListener(t *testing.T) (net.Listener, func()) {
l, err := net.Listen("tcp", ":0")
l, err := net.Listen("tcp4", ":0")
if err != nil {
t.Fatal(err)
}
var once sync.Once
return l, func() {
if err := l.Close(); err != nil {
t.Fatal(err)
}
once.Do(func() {
if err := l.Close(); err != nil {
t.Fatal(err)
}
})
}
}
@@ -167,6 +187,84 @@ func runTestRPCClient(t *testing.T, addr net.Addr) {
}
}
const (
handleHTTP1Close = 1
handleHTTP1Request = 2
handleAnyClose = 3
handleAnyRequest = 4
)
func TestTimeout(t *testing.T) {
defer leakCheck(t)()
lis, Close := testListener(t)
defer Close()
result := make(chan int, 5)
testDuration := time.Millisecond * 100
m := New(lis)
m.SetReadTimeout(testDuration)
http1 := m.Match(HTTP1Fast())
any := m.Match(Any())
go func() {
_ = m.Serve()
}()
go func() {
con, err := http1.Accept()
if err != nil {
result <- handleHTTP1Close
} else {
_, _ = con.Write([]byte("http1"))
_ = con.Close()
result <- handleHTTP1Request
}
}()
go func() {
con, err := any.Accept()
if err != nil {
result <- handleAnyClose
} else {
_, _ = con.Write([]byte("any"))
_ = con.Close()
result <- handleAnyRequest
}
}()
time.Sleep(testDuration) // wait to prevent timeouts on slow test-runners
client, err := net.Dial("tcp", lis.Addr().String())
if err != nil {
log.Fatal("testTimeout client failed: ", err)
}
defer func() {
_ = client.Close()
}()
time.Sleep(testDuration / 2)
if len(result) != 0 {
log.Print("tcp ")
t.Fatal("testTimeout failed: accepted to fast: ", len(result))
}
_ = client.SetReadDeadline(time.Now().Add(testDuration * 3))
buffer := make([]byte, 10)
rl, err := client.Read(buffer)
if err != nil {
t.Fatal("testTimeout failed: client error: ", err, rl)
}
Close()
if rl != 3 {
log.Print("testTimeout failed: response from wrong sevice ", rl)
}
if string(buffer[0:3]) != "any" {
log.Print("testTimeout failed: response from wrong sevice ")
}
time.Sleep(testDuration * 2)
if len(result) != 2 {
t.Fatal("testTimeout failed: accepted to less: ", len(result))
}
if a := <-result; a != handleAnyRequest {
t.Fatal("testTimeout failed: any rule did not match")
}
if a := <-result; a != handleHTTP1Close {
t.Fatal("testTimeout failed: no close an http rule")
}
}
func TestRead(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error)
@@ -298,6 +396,72 @@ func TestHTTP2(t *testing.T) {
}
}
func TestHTTP2MatchHeaderField(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
t.Fatal(err)
default:
}
}()
name := "name"
value := "value"
writer, reader := net.Pipe()
go func() {
if _, err := io.WriteString(writer, http2.ClientPreface); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
enc := hpack.NewEncoder(&buf)
if err := enc.WriteField(hpack.HeaderField{Name: name, Value: value}); err != nil {
t.Fatal(err)
}
framer := http2.NewFramer(writer, nil)
err := framer.WriteHeaders(http2.HeadersFrameParam{
StreamID: 1,
BlockFragment: buf.Bytes(),
EndStream: true,
EndHeaders: true,
})
if err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
}()
l := newChanListener()
l.connCh <- reader
muxl := New(l)
// Register a bogus matcher that only reads one byte.
muxl.Match(func(r io.Reader) bool {
var b [1]byte
_, _ = r.Read(b[:])
return false
})
// Create a matcher that cannot match the response.
muxl.Match(HTTP2HeaderField(name, "another"+value))
// Then match with the expected field.
h2l := muxl.Match(HTTP2HeaderField(name, value))
go safeServe(errCh, muxl)
muxedConn, err := h2l.Accept()
close(l.connCh)
if err != nil {
t.Fatal(err)
}
var b [len(http2.ClientPreface)]byte
// We have the sniffed buffer first...
if _, err := muxedConn.Read(b[:]); err == io.EOF {
t.Fatal(err)
}
if string(b[:]) != http2.ClientPreface {
t.Errorf("got unexpected read %s, expected %s", b, http2.ClientPreface)
}
}
func TestHTTPGoRPC(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error)
@@ -425,6 +589,7 @@ func interestingGoroutines() (gs []string) {
}
if stack == "" ||
strings.Contains(stack, "main.main()") ||
strings.Contains(stack, "testing.Main(") ||
strings.Contains(stack, "runtime.goexit") ||
strings.Contains(stack, "created by runtime.gc") ||

18
doc.go Normal file
View File

@@ -0,0 +1,18 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
// Package cmux is a library to multiplex network connections based on
// their payload. Using cmux, you can serve different protocols from the
// same listener.
package cmux

View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux_test
import (

View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux_test
import (

View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux_test
import (

View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux
import (
@@ -130,10 +144,14 @@ func matchHTTP2Field(w io.Writer, r io.Reader, name, value string) (matched bool
return false
}
done := false
framer := http2.NewFramer(w, r)
hdec := hpack.NewDecoder(uint32(4<<10), func(hf hpack.HeaderField) {
if hf.Name == name && hf.Value == value {
matched = true
if hf.Name == name {
done = true
if hf.Value == value {
matched = true
}
}
})
for {
@@ -147,17 +165,20 @@ func matchHTTP2Field(w io.Writer, r io.Reader, name, value string) (matched bool
if err := framer.WriteSettings(); err != nil {
return false
}
case *http2.ContinuationFrame:
if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
return false
}
done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0
case *http2.HeadersFrame:
if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
return false
}
if matched {
return true
}
done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0
}
if f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0 {
return false
}
if done {
return matched
}
}
}

View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux
import (
@@ -8,8 +22,8 @@ import (
// patriciaTree is a simple patricia tree that handles []byte instead of string
// and cannot be changed after instantiation.
type patriciaTree struct {
root *ptNode
buf []byte // preallocated buffer to read data while matching
root *ptNode
maxDepth int // max depth of the tree.
}
func newPatriciaTree(bs ...[]byte) *patriciaTree {
@@ -20,8 +34,8 @@ func newPatriciaTree(bs ...[]byte) *patriciaTree {
}
}
return &patriciaTree{
root: newNode(bs),
buf: make([]byte, max+1),
root: newNode(bs),
maxDepth: max + 1,
}
}
@@ -34,13 +48,15 @@ func newPatriciaTreeString(strs ...string) *patriciaTree {
}
func (t *patriciaTree) matchPrefix(r io.Reader) bool {
n, _ := io.ReadFull(r, t.buf)
return t.root.match(t.buf[:n], true)
buf := make([]byte, t.maxDepth)
n, _ := io.ReadFull(r, buf)
return t.root.match(buf[:n], true)
}
func (t *patriciaTree) match(r io.Reader) bool {
n, _ := io.ReadFull(r, t.buf)
return t.root.match(t.buf[:n], false)
buf := make([]byte, t.maxDepth)
n, _ := io.ReadFull(r, buf)
return t.root.match(buf[:n], false)
}
type ptNode struct {
@@ -145,6 +161,10 @@ func (n *ptNode) match(b []byte, prefix bool) bool {
return true
}
if l >= len(b) {
return false
}
nextN, ok := n.next[b[l]]
if !ok {
return false

View File

@@ -1,3 +1,17 @@
// Copyright 2016 The CMux Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package cmux
import (
@@ -19,6 +33,13 @@ func testPTree(t *testing.T, strs ...string) {
if pt.match(strings.NewReader(s + s)) {
t.Errorf("%s matches %s", s+s, s)
}
// The following tests are just to catch index out of
// range and off-by-one errors and not the functionality.
pt.matchPrefix(strings.NewReader(s[:len(s)-1]))
pt.match(strings.NewReader(s[:len(s)-1]))
pt.matchPrefix(strings.NewReader(s + "$"))
pt.match(strings.NewReader(s + "$"))
}
}
@@ -31,5 +52,5 @@ func TestPatriciaNonOverlapping(t *testing.T) {
}
func TestPatriciaOverlapping(t *testing.T) {
testPTree(t, "foo", "far", "farther", "boo", "bar")
testPTree(t, "foo", "far", "farther", "boo", "ba", "bar")
}