Client.Hello, Client.Lookup and parseReply
This commit is contained in:
60
client.go
Normal file
60
client.go
Normal file
@ -0,0 +1,60 @@
|
||||
package goSam
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
samConn io.ReadWriteCloser
|
||||
|
||||
fromSam *bufio.Reader
|
||||
toSam *bufio.Writer
|
||||
}
|
||||
|
||||
func NewClient(samConn io.ReadWriteCloser) (*Client, error) {
|
||||
c := &Client{
|
||||
samConn: samConn,
|
||||
fromSam: bufio.NewReader(samConn),
|
||||
toSam: bufio.NewWriter(samConn),
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) Hello() (err error) {
|
||||
if _, err = c.toSam.WriteString("HELLO VERSION MIN=3.0 MAX=3.0\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.toSam.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
line, err := c.fromSam.ReadString('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reply, err := parseReply(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reply.Topic != "HELLO" {
|
||||
return fmt.Errorf("Unknown Reply: %+v\n", reply)
|
||||
}
|
||||
|
||||
if reply.Pairs["RESULT"] != "OK" {
|
||||
return fmt.Errorf("Handshake did not succeed")
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
return c.samConn.Close()
|
||||
}
|
41
client_test.go
Normal file
41
client_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package goSam
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
client *Client
|
||||
)
|
||||
|
||||
func setup() {
|
||||
var err error
|
||||
|
||||
// these tests expect a running SAM brige on this address
|
||||
conn, err := net.Dial("tcp", "localhost:7656")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err = NewClient(conn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func teardown() {
|
||||
client.Close()
|
||||
}
|
||||
|
||||
func TestClientHello(t *testing.T) {
|
||||
var err error
|
||||
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
err = client.Hello()
|
||||
if err != nil {
|
||||
t.Errorf("client.Hello() should not throw an error.\n%s\n", err)
|
||||
}
|
||||
}
|
76
naming.go
Normal file
76
naming.go
Normal file
@ -0,0 +1,76 @@
|
||||
package goSam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Result int
|
||||
|
||||
const (
|
||||
ResultOk Result = iota //Operation completed successfully
|
||||
ResultCantReachPeer //The peer exists, but cannot be reached
|
||||
ResultDuplicatedDest //The specified Destination is already in use
|
||||
ResultI2PError //A generic I2P error (e.g. I2CP disconnection, etc.)
|
||||
ResultInvalidKey //The specified key is not valid (bad format, etc.)
|
||||
ResultKeyNotFound //The naming system can't resolve the given name
|
||||
ResultPeerNotFound //The peer cannot be found on the network
|
||||
ResultTimeout // Timeout while waiting for an event (e.g. peer answer)
|
||||
)
|
||||
|
||||
type ReplyError struct {
|
||||
Result Result
|
||||
Reply *Reply
|
||||
}
|
||||
|
||||
func (r ReplyError) Error() string {
|
||||
return fmt.Sprintf("ReplyError: Result:%d - Reply:%+v", r.Reply)
|
||||
}
|
||||
|
||||
func (c *Client) Lookup(name string) (addr string, err error) {
|
||||
msg := fmt.Sprintf("NAMING LOOKUP NAME=%s\n", name)
|
||||
if _, err = c.toSam.WriteString(msg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = c.toSam.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
line string
|
||||
r *Reply
|
||||
)
|
||||
for {
|
||||
line, err = c.fromSam.ReadString('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r, err = parseReply(line)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if r.Topic != "NAMING" || r.Type != "REPLY" {
|
||||
err = fmt.Errorf("Unknown Reply: %+v\n", r)
|
||||
break
|
||||
}
|
||||
|
||||
switch r.Pairs["RESULT"] {
|
||||
case "OK":
|
||||
addr = r.Pairs["VALUE"]
|
||||
return
|
||||
case "KEY_NOT_FOUND":
|
||||
err = ReplyError{ResultKeyNotFound, r}
|
||||
}
|
||||
|
||||
if r.Pairs["NAME"] != name {
|
||||
err = fmt.Errorf("i2p Replyied with: %+v\n", r)
|
||||
break
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
46
naming_test.go
Normal file
46
naming_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package goSam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClientLookupInvalid(t *testing.T) {
|
||||
var err error
|
||||
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
client.Hello()
|
||||
|
||||
addr, err := client.Lookup("abci2p")
|
||||
if addr != "" || err == nil {
|
||||
t.Error("client.Lookup() should throw an error.")
|
||||
}
|
||||
|
||||
repErr, ok := err.(ReplyError)
|
||||
if ok && repErr.Result != ResultKeyNotFound {
|
||||
t.Error("client.Lookup() should throw an ResultKeyNotFound error. Got:%v\n", repErr)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_Lookup() {
|
||||
var err error
|
||||
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
client.Hello()
|
||||
|
||||
_, err = client.Lookup("zzz.i2p")
|
||||
if err != nil {
|
||||
fmt.Printf("client.Lookup() should not throw an error.\n%s\n", err)
|
||||
}
|
||||
|
||||
fmt.Println("Address of zzz.i2p:")
|
||||
// Addresses change all the time
|
||||
// fmt.Println(addr)
|
||||
|
||||
// Output:
|
||||
//Address of zzz.i2p:
|
||||
}
|
36
replyParser.go
Normal file
36
replyParser.go
Normal file
@ -0,0 +1,36 @@
|
||||
package goSam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Reply struct {
|
||||
Topic string
|
||||
Type string
|
||||
|
||||
Pairs map[string]string
|
||||
}
|
||||
|
||||
func parseReply(line string) (r *Reply, err error) {
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) < 3 {
|
||||
return nil, fmt.Errorf("Malformed Reply.\n%s\n", line)
|
||||
}
|
||||
|
||||
r = &Reply{
|
||||
Topic: parts[0],
|
||||
Type: parts[1],
|
||||
Pairs: make(map[string]string, len(parts)-2),
|
||||
}
|
||||
|
||||
for _, v := range parts[2:] {
|
||||
kvPair := strings.Split(v, "=")
|
||||
if len(kvPair) != 2 {
|
||||
return nil, fmt.Errorf("Malformed key-value-pair.\n%s\n", kvPair)
|
||||
}
|
||||
r.Pairs[kvPair[0]] = kvPair[1]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
91
replyParser_test.go
Normal file
91
replyParser_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
package goSam
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseInvalidReply(t *testing.T) {
|
||||
str := "asd asd="
|
||||
|
||||
r, err := parseReply(str)
|
||||
if err == nil {
|
||||
t.Fatalf("Should throw an error.r:%v\n", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseReplyNAMING(t *testing.T) {
|
||||
str := "NAMING REPLY RESULT=OK NAME=zzz.i2p VALUE=GKapJ8koUcBj~jmQzHsTYxDg2tpfWj0xjQTzd8BhfC9c3OS5fwPBNajgF-eOD6eCjFTqTlorlh7Hnd8kXj1qblUGXT-tDoR9~YV8dmXl51cJn9MVTRrEqRWSJVXbUUz9t5Po6Xa247Vr0sJn27R4KoKP8QVj1GuH6dB3b6wTPbOamC3dkO18vkQkfZWUdRMDXk0d8AdjB0E0864nOT~J9Fpnd2pQE5uoFT6P0DqtQR2jsFvf9ME61aqLvKPPWpkgdn4z6Zkm-NJOcDz2Nv8Si7hli94E9SghMYRsdjU-knObKvxiagn84FIwcOpepxuG~kFXdD5NfsH0v6Uri3usE3uSzpWS0EHmrlfoLr5uGGd9ZHwwCIcgfOATaPRMUEQxiK9q48PS0V3EXXO4-YLT0vIfk4xO~XqZpn8~PW1kFe2mQMHd7oO89yCk-3yizRG3UyFtI7-mO~eCI6-m1spYoigStgoupnC3G85gJkqEjMm49gUjbhfWKWI-6NwTj0ZnAAAA"
|
||||
|
||||
reply, err := parseReply(str)
|
||||
if err != nil {
|
||||
t.Fatalf("parseReply should not throw an error!\n%s", err)
|
||||
}
|
||||
|
||||
if reply.Topic != "NAMING" {
|
||||
t.Fatalf("Wrong Topic. Got %s expected NAMING", reply.Topic)
|
||||
}
|
||||
|
||||
if reply.Type != "REPLY" {
|
||||
t.Fatalf("Wrong Type. Got %s expected REPLY", reply.Type)
|
||||
}
|
||||
|
||||
if len(reply.Pairs) != 3 {
|
||||
t.Fatalf("Wrong ammount of Pairs. Got %d expected 3", len(reply.Pairs))
|
||||
}
|
||||
|
||||
for k, v := range reply.Pairs {
|
||||
switch k {
|
||||
case "RESULT":
|
||||
if v != "OK" {
|
||||
t.Fatalf("Wrong Result. Got %s expected OK", v)
|
||||
}
|
||||
case "NAME":
|
||||
if v != "zzz.i2p" {
|
||||
t.Fatalf("Wrong Name. Got %s expected OK", v)
|
||||
}
|
||||
case "VALUE":
|
||||
expect := "GKapJ8koUcBj~jmQzHsTYxDg2tpfWj0xjQTzd8BhfC9c3OS5fwPBNajgF-eOD6eCjFTqTlorlh7Hnd8kXj1qblUGXT-tDoR9~YV8dmXl51cJn9MVTRrEqRWSJVXbUUz9t5Po6Xa247Vr0sJn27R4KoKP8QVj1GuH6dB3b6wTPbOamC3dkO18vkQkfZWUdRMDXk0d8AdjB0E0864nOT~J9Fpnd2pQE5uoFT6P0DqtQR2jsFvf9ME61aqLvKPPWpkgdn4z6Zkm-NJOcDz2Nv8Si7hli94E9SghMYRsdjU-knObKvxiagn84FIwcOpepxuG~kFXdD5NfsH0v6Uri3usE3uSzpWS0EHmrlfoLr5uGGd9ZHwwCIcgfOATaPRMUEQxiK9q48PS0V3EXXO4-YLT0vIfk4xO~XqZpn8~PW1kFe2mQMHd7oO89yCk-3yizRG3UyFtI7-mO~eCI6-m1spYoigStgoupnC3G85gJkqEjMm49gUjbhfWKWI-6NwTj0ZnAAAA"
|
||||
if v != expect {
|
||||
t.Fatalf("Wrong Value.\nGot:%s\nExpected:%s", v, expect)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("Unknown kvPair %s=%s", k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseReplyHELLO(t *testing.T) {
|
||||
str := "HELLO REPLY RESULT=OK VERSION=3.0"
|
||||
|
||||
reply, err := parseReply(str)
|
||||
if err != nil {
|
||||
t.Fatalf("parseReply should not throw an error!\n%s", err)
|
||||
}
|
||||
|
||||
if reply.Topic != "HELLO" {
|
||||
t.Fatalf("Wrong Topic. Got %s expected HELLO", reply.Topic)
|
||||
}
|
||||
|
||||
if reply.Type != "REPLY" {
|
||||
t.Fatalf("Wrong Type. Got %s expected REPLY", reply.Type)
|
||||
}
|
||||
|
||||
if len(reply.Pairs) != 2 {
|
||||
t.Fatalf("Wrong ammount of Pairs. Got %d expected 3", len(reply.Pairs))
|
||||
}
|
||||
|
||||
for k, v := range reply.Pairs {
|
||||
switch k {
|
||||
case "RESULT":
|
||||
if v != "OK" {
|
||||
t.Fatalf("Wrong Result. Got %s expected OK", v)
|
||||
}
|
||||
case "VERSION":
|
||||
if v != "3.0" {
|
||||
t.Fatalf("Wrong Name. Got %s expected OK", v)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("Unknown kvPair %s=%s", k, v)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user