diff --git a/accept.go b/accept.go index 09d1a0d..5ce0254 100644 --- a/accept.go +++ b/accept.go @@ -21,7 +21,7 @@ func (c *Client) Accept() (net.Conn, error) { return nil, err } - if ConnDebug { + if c.debug { newC.SamConn = debug.WrapConn(newC.SamConn) } diff --git a/client.go b/client.go index c05c9de..8537eda 100644 --- a/client.go +++ b/client.go @@ -8,13 +8,28 @@ import ( "github.com/cryptix/go/debug" ) -// ConnDebug if set to true, Sam connections are wrapped with logging -var ConnDebug = false - // A Client represents a single Connection to the SAM bridge type Client struct { + host string + port string + SamConn net.Conn rd *bufio.Reader + + inLength uint + inVariance int + inQuantity uint + inBackups uint + + outLength uint + outVariance int + outQuantity uint + outBackups uint + + dontPublishLease bool + encryptLease bool + + debug bool } // NewDefaultClient creates a new client, connecting to the default host:port at localhost:7656 @@ -24,23 +39,50 @@ func NewDefaultClient() (*Client, error) { // NewClient creates a new client, connecting to a specified port func NewClient(addr string) (*Client, error) { - conn, err := net.Dial("tcp", addr) + return NewClientFromOptions(SetAddr(addr)) +} + +// NewClientFromOptions creates a new client, connecting to a specified port +func NewClientFromOptions(opts ...func(*Client) error) (*Client, error) { + var c Client + c.host = "127.0.0.1" + c.port = "7656" + c.inLength = 3 + c.inVariance = 0 + c.inQuantity = 4 + c.inBackups = 2 + c.outLength = 3 + c.outVariance = 0 + c.outQuantity = 4 + c.outBackups = 2 + c.dontPublishLease = true + c.encryptLease = false + c.debug = false + for _, o := range opts { + if err := o(&c); err != nil { + return nil, err + } + } + conn, err := net.Dial("tcp", c.samaddr()) if err != nil { return nil, err } - if ConnDebug { + if c.debug { conn = debug.WrapConn(conn) } - c := &Client{ - SamConn: conn, - rd: bufio.NewReader(conn), - } - return c, c.hello() + c.SamConn = conn + c.rd = bufio.NewReader(conn) + return &c, c.hello() +} + +//return the combined host:port of the SAM bridge +func (c *Client) samaddr() string { + return fmt.Sprintf("%s:%s", c.host, c.port) } // send the initial handshake command and check that the reply is ok func (c *Client) hello() error { - r, err := c.sendCmd("HELLO VERSION MIN=3.0 MAX=3.0\n") + r, err := c.sendCmd("HELLO VERSION MIN=3.0 MAX=3.0\n", c.allOptions()) if err != nil { return err } diff --git a/client_test.go b/client_test.go index 1d9e3f3..b562658 100644 --- a/client_test.go +++ b/client_test.go @@ -7,10 +7,8 @@ var client *Client func setup(t *testing.T) { var err error - ConnDebug = true - // these tests expect a running SAM brige on this address - client, err = NewDefaultClient() + client, err = NewClientFromOptions(SetDebug(true)) if err != nil { t.Fatalf("NewDefaultClient() Error: %q\n", err) } diff --git a/dial.go b/dial.go index d4147c1..f297eb6 100644 --- a/dial.go +++ b/dial.go @@ -21,7 +21,7 @@ func (c *Client) Dial(network, addr string) (net.Conn, error) { return nil, err } - newC, err := NewDefaultClient() + newC, err := NewClient(c.samaddr()) if err != nil { return nil, err } diff --git a/example/httpTest.go b/example/httpTest.go index 1fadd34..bd8c890 100644 --- a/example/httpTest.go +++ b/example/httpTest.go @@ -10,7 +10,8 @@ import ( ) func main() { - goSam.ConnDebug = true + //In order to enable debugging, pass the SetDebug(true) option. + //sam, err := goSam.NewClientFromOptions(SetDebug(true)) // create a default sam client sam, err := goSam.NewDefaultClient() diff --git a/options.go b/options.go new file mode 100644 index 0000000..55ca897 --- /dev/null +++ b/options.go @@ -0,0 +1,271 @@ +package goSam + +import ( + "fmt" + "strconv" + "strings" +) + +//Option is a client Option +type Option func(*Client) error + +//SetAddr sets a clients's address in the form host:port or host, port +func SetAddr(s ...string) func(*Client) error { + return func(c *Client) error { + if len(s) == 1 { + split := strings.SplitN(s[0], ":", 2) + if len(split) == 2 { + if i, err := strconv.Atoi(split[1]); err == nil { + if i < 65536 { + c.host = split[0] + c.port = split[1] + return nil + } + return fmt.Errorf("Invalid port") + } + return fmt.Errorf("Invalid port; non-number") + } + return fmt.Errorf("Invalid address; use host:port %s", split) + } else if len(s) == 2 { + if i, err := strconv.Atoi(s[1]); err == nil { + if i < 65536 { + c.host = s[0] + c.port = s[1] + return nil + } + return fmt.Errorf("Invalid port") + } + return fmt.Errorf("Invalid port; non-number") + } else { + return fmt.Errorf("Invalid address") + } + } +} + +//SetAddrMixed sets a clients's address in the form host, port(int) +func SetAddrMixed(s string, i int) func(*Client) error { + return func(c *Client) error { + if i < 65536 && i > 0 { + c.host = s + c.port = strconv.Itoa(i) + return nil + } + return fmt.Errorf("Invalid port") + } +} + +//SetHost sets the host of the client's SAM bridge +func SetHost(s string) func(*Client) error { + return func(c *Client) error { + c.host = s + return nil + } +} + +//SetPort sets the port of the client's SAM bridge using a string +func SetPort(s string) func(*Client) error { + return func(c *Client) error { + port, err := strconv.Atoi(s) + if err != nil { + return fmt.Errorf("Invalid port; non-number") + } + if port < 65536 && port > -1 { + c.port = s + return nil + } + return fmt.Errorf("Invalid port") + } +} + +//SetPortInt sets the port of the client's SAM bridge using a string +func SetPortInt(i int) func(*Client) error { + return func(c *Client) error { + if i < 65536 && i > -1 { + c.port = strconv.Itoa(i) + return nil + } + return fmt.Errorf("Invalid port") + } +} + +//SetDebug enables debugging messages +func SetDebug(b bool) func(*Client) error { + return func(c *Client) error { + c.debug = b + return nil + } +} + +//SetInLength sets the number of hops inbound +func SetInLength(u uint) func(*Client) error { + return func(c *Client) error { + if u < 7 { + c.inLength = u + return nil + } + return fmt.Errorf("Invalid inbound tunnel length") + } +} + +//SetOutLength sets the number of hops outbound +func SetOutLength(u uint) func(*Client) error { + return func(c *Client) error { + if u < 7 { + c.outLength = u + return nil + } + return fmt.Errorf("Invalid outbound tunnel length") + } +} + +//SetInVariance sets the variance of a number of hops inbound +func SetInVariance(i int) func(*Client) error { + return func(c *Client) error { + if i < 7 && i > -7 { + c.inVariance = i + return nil + } + return fmt.Errorf("Invalid inbound tunnel length") + } +} + +//SetOutVariance sets the variance of a number of hops outbound +func SetOutVariance(i int) func(*Client) error { + return func(c *Client) error { + if i < 7 && i > -7 { + c.outVariance = i + return nil + } + return fmt.Errorf("Invalid outbound tunnel variance") + } +} + +//SetInQuantity sets the inbound tunnel quantity +func SetInQuantity(u uint) func(*Client) error { + return func(c *Client) error { + if u <= 16 { + c.inQuantity = u + return nil + } + return fmt.Errorf("Invalid inbound tunnel quantity") + } +} + +//SetOutQuantity sets the outbound tunnel quantity +func SetOutQuantity(u uint) func(*Client) error { + return func(c *Client) error { + if u <= 16 { + c.outQuantity = u + return nil + } + return fmt.Errorf("Invalid outbound tunnel quantity") + } +} + +//SetInBackups sets the inbound tunnel backups +func SetInBackups(u uint) func(*Client) error { + return func(c *Client) error { + if u < 6 { + c.inBackups = u + return nil + } + return fmt.Errorf("Invalid inbound tunnel backup quantity") + } +} + +//SetOutBackups sets the inbound tunnel backups +func SetOutBackups(u uint) func(*Client) error { + return func(c *Client) error { + if u < 6 { + c.outBackups = u + return nil + } + return fmt.Errorf("Invalid outbound tunnel backup quantity") + } +} + +//SetUnpublished tells the router to not publish the client leaseset +func SetUnpublished(b bool) func(*Client) error { + return func(c *Client) error { + c.dontPublishLease = b + return nil + } +} + +//SetEncrypt tells the router to use an encrypted leaseset +func SetEncrypt(b bool) func(*Client) error { + return func(c *Client) error { + c.encryptLease = b + return nil + } +} + +//return the inbound length as a string. +func (c *Client) inlength() string { + return "inbound.length=" + fmt.Sprint(c.inLength) +} + +//return the outbound length as a string. +func (c *Client) outlength() string { + return "outbound.length=" + fmt.Sprint(c.outLength) +} + +//return the inbound length variance as a string. +func (c *Client) invariance() string { + return "inbound.lengthVariance=" + fmt.Sprint(c.inVariance) +} + +//return the outbound length variance as a string. +func (c *Client) outvariance() string { + return "outbound.lengthVariance=" + fmt.Sprint(c.outVariance) +} + +//return the inbound tunnel quantity as a string. +func (c *Client) inquantity() string { + return "inbound.quantity=" + fmt.Sprint(c.inQuantity) +} + +//return the outbound tunnel quantity as a string. +func (c *Client) outquantity() string { + return "outbound.quantity=" + fmt.Sprint(c.outQuantity) +} + +//return the inbound tunnel quantity as a string. +func (c *Client) inbackups() string { + return "inbound.backupQuantity=" + fmt.Sprint(c.inQuantity) +} + +//return the outbound tunnel quantity as a string. +func (c *Client) outbackups() string { + return "outbound.backupQuantity=" + fmt.Sprint(c.outQuantity) +} + +func (c *Client) encryptlease() string { + if c.encryptLease { + return "i2cp.encryptLeaseSet=true" + } + return "i2cp.encryptLeaseSet=false" +} + +func (c *Client) dontpublishlease() string { + if c.dontPublishLease { + return "i2cp.dontPublishLeaseSet=true" + } + return "i2cp.dontPublishLeaseSet=false" +} + +//return all options as string array ready for passing to sendcmd +func (c *Client) allOptions() []string { + var options []string + options = append(options, c.inlength()) + options = append(options, c.outlength()) + options = append(options, c.invariance()) + options = append(options, c.outvariance()) + options = append(options, c.inquantity()) + options = append(options, c.outquantity()) + options = append(options, c.inbackups()) + options = append(options, c.outbackups()) + options = append(options, c.dontpublishlease()) + options = append(options, c.encryptlease()) + return options +} diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..2aef557 --- /dev/null +++ b/options_test.go @@ -0,0 +1,191 @@ +package goSam + +import "testing" + +func TestOptionAddrString(t *testing.T) { + client, err := NewClientFromOptions(SetAddr("127.0.0.1:7656"), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionAddrStringLh(t *testing.T) { + client, err := NewClientFromOptions(SetAddr("localhost:7656"), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionAddrSlice(t *testing.T) { + client, err := NewClientFromOptions(SetAddr("127.0.0.1", "7656"), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionAddrMixedSlice(t *testing.T) { + client, err := NewClientFromOptions(SetAddrMixed("127.0.0.1", 7656), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionHost(t *testing.T) { + client, err := NewClientFromOptions(SetHost("127.0.0.1"), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionPort(t *testing.T) { + client, err := NewClientFromOptions(SetPort("7656"), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionPortInt(t *testing.T) { + client, err := NewClientFromOptions(SetPortInt(7656), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionDebug(t *testing.T) { + client, err := NewClientFromOptions(SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionInLength(t *testing.T) { + client, err := NewClientFromOptions(SetInLength(3), SetDebug(true)) + client.inlength() + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionOutLength(t *testing.T) { + client, err := NewClientFromOptions(SetInLength(3), SetDebug(true)) + client.outlength() + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionInVariance(t *testing.T) { + client, err := NewClientFromOptions(SetInVariance(1), SetDebug(true)) + client.invariance() + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionOutVariance(t *testing.T) { + client, err := NewClientFromOptions(SetOutVariance(1), SetDebug(true)) + client.outvariance() + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionInQuantity(t *testing.T) { + client, err := NewClientFromOptions(SetInQuantity(6), SetDebug(true)) + client.inquantity() + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionOutQuantity(t *testing.T) { + client, err := NewClientFromOptions(SetOutQuantity(6), SetDebug(true)) + client.outquantity() + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionInBackups(t *testing.T) { + client, err := NewClientFromOptions(SetInBackups(5), SetDebug(true)) + client.inbackups() + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionOutBackups(t *testing.T) { + client, err := NewClientFromOptions(SetOutBackups(5), SetDebug(true)) + client.outbackups() + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionEncryptLease(t *testing.T) { + client, err := NewClientFromOptions(SetEncrypt(true), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} + +func TestOptionUnpublishedLease(t *testing.T) { + client, err := NewClientFromOptions(SetUnpublished(true), SetDebug(true)) + if err != nil { + t.Fatalf("NewDefaultClient() Error: %q\n", err) + } + if err := client.Close(); err != nil { + t.Fatalf("client.Close() Error: %q\n", err) + } +} diff --git a/sessions.go b/sessions.go index b6725be..b1ebd60 100644 --- a/sessions.go +++ b/sessions.go @@ -19,7 +19,7 @@ func (c *Client) CreateStreamSession(dest string) (int32, string, error) { } id := rand.Int31n(math.MaxInt32) - r, err := c.sendCmd("SESSION CREATE STYLE=STREAM ID=%d DESTINATION=%s\n", id, dest) + r, err := c.sendCmd("SESSION CREATE STYLE=STREAM ID=%d DESTINATION=%s %s\n", id, dest, c.allOptions()) if err != nil { return -1, "", err }