[#ISSUE-3] New protocol messages support
This commit is contained in:
parent
9b46298eca
commit
473a135243
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"Exclude": [
|
||||
".*_mock.go",
|
||||
"geth/jail/doc.go",
|
||||
".*Errors unhandled.*fmt.Fprint.*gasv2.*"
|
||||
],
|
||||
"Skip": ["helpers", "static"],
|
||||
"Vendor": true,
|
||||
"Test": true,
|
||||
"Linters": {
|
||||
"gasv2": {
|
||||
"Command": "gas -fmt=csv",
|
||||
"Pattern": "^(?P<path>.*?\\.go),(?P<line>\\d+),(?P<message>[^,]+,[^,]+,[^,]+,\".*\")"
|
||||
}
|
||||
},
|
||||
"Enable": [
|
||||
"deadcode",
|
||||
"errcheck",
|
||||
"gasv2",
|
||||
"goconst",
|
||||
"gocyclo",
|
||||
"gofmt",
|
||||
"golint",
|
||||
"ineffassign",
|
||||
"interfacer",
|
||||
"megacheck",
|
||||
"misspell",
|
||||
"structcheck",
|
||||
"unconvert",
|
||||
"varcheck",
|
||||
"vet"
|
||||
],
|
||||
"Cyclo": 16,
|
||||
"Deadline": "200s"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
notifications:
|
||||
email: false
|
||||
language: go
|
||||
go:
|
||||
- 1.9.x
|
||||
sudo: false
|
||||
dist: trusty
|
||||
install:
|
||||
- make lint-install
|
||||
jobs:
|
||||
include:
|
||||
- stage: Lint
|
||||
sudo: required
|
||||
script:
|
||||
- make lint
|
|
@ -0,0 +1,7 @@
|
|||
lint-install:
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
gometalinter --install
|
||||
|
||||
lint:
|
||||
@echo "lint"
|
||||
@gometalinter ./...
|
79
chan.go
79
chan.go
|
@ -1,13 +1,14 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Channel : ...
|
||||
type Channel struct {
|
||||
conn *Conn
|
||||
conn *SDK
|
||||
channelName string
|
||||
filterID string
|
||||
channelKey string
|
||||
|
@ -17,18 +18,17 @@ type Channel struct {
|
|||
|
||||
// Publish : Publishes a message with the given body on the current channel
|
||||
func (c *Channel) Publish(body string) error {
|
||||
message := NewMsg(c.conn.userName, body, c.channelName)
|
||||
msg := NewMsg(c.conn.userName, body, c.channelName)
|
||||
cmd := fmt.Sprintf(standardMessageFormat,
|
||||
c.conn.address,
|
||||
c.channelKey,
|
||||
message.ToPayload(),
|
||||
msg.ToPayload(),
|
||||
c.topic,
|
||||
c.conn.minimumPoW,
|
||||
)
|
||||
_, err := c.conn.call(cmd)
|
||||
|
||||
c.conn.rpc.Call(cmd)
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Subscribe : ...
|
||||
|
@ -48,6 +48,73 @@ func (c *Channel) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
// NewContactKeyRequest : First message that is sent to a future contact. At that
|
||||
// point the only topic we know that the contact is filtering is the
|
||||
// discovery-topic with his public key so that is what NewContactKey will
|
||||
// be sent to.
|
||||
// It contains the sym-key and topic that will be used for future communications
|
||||
// as well as the actual message that we want to send.
|
||||
// The sym-key and topic are generated randomly because we don’t want to have
|
||||
// any correlation between a topic and its participants to avoid leaking
|
||||
// metadata.
|
||||
// When one of the contacts recovers his account, a NewContactKey message is
|
||||
// sent as well to change the symmetric key and topic.
|
||||
func (c *Channel) NewContactKeyRequest(username string) {
|
||||
contactRequest := fmt.Sprintf(contactRequestMsg, username, "", "", "")
|
||||
msg := fmt.Sprintf(newContactKeyMsg, c.conn.address, c.topic, contactRequest)
|
||||
|
||||
c.callStandardMsg(msg)
|
||||
}
|
||||
|
||||
// ContactRequest : Wrapped in a NewContactKey message when initiating a contact request.
|
||||
func (c *Channel) ContactRequest(username, image string) {
|
||||
msg := fmt.Sprintf(contactRequestMsg, username, image, c.conn.address, "")
|
||||
c.callStandardMsg(msg)
|
||||
}
|
||||
|
||||
// ConfirmedContactRequest : This is the message that will be sent when the
|
||||
// contact accepts the contact request. It will be sent on the topic that
|
||||
// was provided in the NewContactKey message and use the sym-key.
|
||||
// Both users will therefore have the same filter.
|
||||
func (c *Channel) ConfirmedContactRequest(username, image string) {
|
||||
msg := fmt.Sprintf(confirmedContactRequestMsg, username, image, c.conn.address, "")
|
||||
c.callStandardMsg(msg)
|
||||
}
|
||||
|
||||
// ContactUpdateRequest : Sent when the user changes his name or profile-image.
|
||||
func (c *Channel) ContactUpdateRequest(username, image string) {
|
||||
msg := fmt.Sprintf(contactUpdateMsg, username, image)
|
||||
c.callStandardMsg(msg)
|
||||
}
|
||||
|
||||
// SeenRequest : Sent when a user sees a message (opens the chat and loads the
|
||||
// message). Can acknowledge multiple messages at the same time
|
||||
func (c *Channel) SeenRequest(ids []string) error {
|
||||
body, err := json.Marshal(ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(seenMsg, body)
|
||||
c.callStandardMsg(msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Channel) callStandardMsg(body string) {
|
||||
msg := rawrChatMessage(body)
|
||||
|
||||
cmd := fmt.Sprintf(standardMessageFormat,
|
||||
c.conn.address,
|
||||
c.channelKey,
|
||||
msg,
|
||||
c.topic,
|
||||
c.conn.minimumPoW)
|
||||
|
||||
// TODO (adriacidre) manage this error
|
||||
_, _ = c.conn.call(cmd)
|
||||
}
|
||||
|
||||
func (c *Channel) removeSubscription(sub *Subscription) {
|
||||
var subs []*Subscription
|
||||
for _, s := range c.subscriptions {
|
||||
|
|
|
@ -8,7 +8,11 @@ var (
|
|||
web3ShaFormat = `{"jsonrpc":"2.0","method":"web3_sha3","params":["%s"],"id":%d}`
|
||||
statusLoginFormat = `{"jsonrpc":"2.0","method":"status_login","params":["%s","%s"]}`
|
||||
statusSignupFormat = `{"jsonrpc":"2.0","method":"status_signup","params":["%s","%s"]}`
|
||||
statusJoinPublicChannel = `{"jsonrpc":"2.0","method":"status_joinpublicchannel","params":["%s"]}`
|
||||
|
||||
messagePayloadFormat = `["~#c4",["%s","text/plain","~:public-group-user-message",%d,%d]]`
|
||||
newContactKeyMsg = `["~#c1",["%s","%s",%s]`
|
||||
contactRequestMsg = `["~#c2",["%s","%s","%s","%s”]]]`
|
||||
confirmedContactRequestMsg = `["~#c3",["%s","%s","%s","%s"]]`
|
||||
messagePayloadMsg = `["~#c4",["%s","text/plain","~:public-group-user-message",%d,%d]]`
|
||||
seenMsg = `["~#c5",["%s","%s"]]`
|
||||
contactUpdateMsg = `["~#c6",["%s","%s"]]`
|
||||
)
|
||||
|
|
|
@ -8,18 +8,18 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
conn := sdk.NewConn("localhost:30303")
|
||||
if err := conn.Signup("111222333"); err != nil {
|
||||
sdk := sdk.New("localhost:30303")
|
||||
if err := sdk.Signup("111222333"); err != nil {
|
||||
panic("Couldn't create an account")
|
||||
}
|
||||
|
||||
ch, err := conn.Join("supu")
|
||||
ch, err := sdk.Join("supu")
|
||||
if err != nil {
|
||||
panic("Couldn't connect to status")
|
||||
}
|
||||
|
||||
for range time.Tick(10 * time.Second) {
|
||||
message := fmt.Sprintf("PING : %d", time.Now().Unix())
|
||||
ch.Publish(message)
|
||||
_ = ch.Publish(message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,24 +11,24 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
conn := sdk.NewConn()
|
||||
client := sdk.New("localhost:30303")
|
||||
|
||||
if err := conn.SignupOrLogin("supu", "password"); err != nil {
|
||||
if err := client.SignupOrLogin("supu", "password"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ch, err := conn.Join("supu")
|
||||
ch, err := client.Join("supu")
|
||||
if err != nil {
|
||||
panic("Couldn't connect to status")
|
||||
}
|
||||
|
||||
ch.Subscribe(func(m *sdk.Msg) {
|
||||
_, _ = ch.Subscribe(func(m *sdk.Msg) {
|
||||
log.Println("Message from ", m.From, " with body: ", m.Text)
|
||||
|
||||
if strings.Contains(m.Text, "PING :") {
|
||||
time.Sleep(5 * time.Second)
|
||||
message := fmt.Sprintf("PONG : %d", time.Now().Unix())
|
||||
ch.Publish(message)
|
||||
_ = ch.Publish(message)
|
||||
}
|
||||
|
||||
})
|
||||
|
|
67
msg.go
67
msg.go
|
@ -1,51 +1,101 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
)
|
||||
|
||||
var (
|
||||
// NewContactKeyType message type for newContactKeyFormat
|
||||
NewContactKeyType = "~#c1"
|
||||
// ContactRequestType message type for contactRequestFormat
|
||||
ContactRequestType = "~#c2"
|
||||
// ConfirmedContactRequestType message type for confirmedContactRequestFormat
|
||||
ConfirmedContactRequestType = "~#c3"
|
||||
// StandardMessageType message type for StandardMessageFormat
|
||||
StandardMessageType = "~#c4"
|
||||
// SeenType message type for SeentType
|
||||
SeenType = "~#c5"
|
||||
// ContactUpdateType message type for contactUpdateMsg
|
||||
ContactUpdateType = "~#c6"
|
||||
)
|
||||
|
||||
// supportedMessage check if the message type is supported
|
||||
func supportedMessage(msgType string) bool {
|
||||
_, ok := map[string]bool{
|
||||
NewContactKeyType: true,
|
||||
ContactRequestType: true,
|
||||
ConfirmedContactRequestType: true,
|
||||
StandardMessageType: true,
|
||||
SeenType: true,
|
||||
ContactUpdateType: true,
|
||||
}[msgType]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Msg is a structure used by Subscribers and Publish().
|
||||
type Msg struct {
|
||||
ID string `json:"id"`
|
||||
From string `json:"from"`
|
||||
Text string `json:"text"`
|
||||
ChannelName string `json:"channel"`
|
||||
Timestamp int64 `json:"ts"`
|
||||
Raw string `json:"-"`
|
||||
Type string `json:"-"`
|
||||
}
|
||||
|
||||
// NewMsg : Creates a new Msg with a generated UUID
|
||||
// NewMsg creates a new Msg with a generated UUID
|
||||
func NewMsg(from, text, channel string) *Msg {
|
||||
return &Msg{
|
||||
ID: newUUID(),
|
||||
From: from,
|
||||
Text: text,
|
||||
ChannelName: channel,
|
||||
Timestamp: time.Now().Unix() * 1000,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
// ToPayload converts current struct to a valid payload
|
||||
// ID gets the message id
|
||||
func (m *Msg) ID() string {
|
||||
return fmt.Sprintf("%X", sha3.Sum256([]byte(m.Raw)))
|
||||
}
|
||||
|
||||
// ToPayload converts current struct to a valid payload
|
||||
func (m *Msg) ToPayload() string {
|
||||
message := fmt.Sprintf(messagePayloadFormat,
|
||||
message := fmt.Sprintf(messagePayloadMsg,
|
||||
m.Text,
|
||||
m.Timestamp*100,
|
||||
m.Timestamp)
|
||||
println(message)
|
||||
|
||||
return rawrChatMessage(message)
|
||||
}
|
||||
|
||||
// MessageFromPayload : TODO ...
|
||||
func rawrChatMessage(raw string) string {
|
||||
bytes := []byte(raw)
|
||||
|
||||
return fmt.Sprintf("0x%s", hex.EncodeToString(bytes))
|
||||
}
|
||||
|
||||
func unrawrChatMessage(message string) ([]byte, error) {
|
||||
return hex.DecodeString(message[2:])
|
||||
}
|
||||
|
||||
// MessageFromPayload creates a message from a payload
|
||||
func MessageFromPayload(payload string) (*Msg, error) {
|
||||
message, err := unrawrChatMessage(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var x []interface{}
|
||||
json.Unmarshal([]byte(message), &x)
|
||||
if err := json.Unmarshal(message, &x); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(x) < 1 {
|
||||
return nil, errors.New("unsupported message type")
|
||||
}
|
||||
|
@ -55,6 +105,7 @@ func MessageFromPayload(payload string) (*Msg, error) {
|
|||
properties := x[1].([]interface{})
|
||||
|
||||
return &Msg{
|
||||
// TODO (adriacidre) add from username
|
||||
From: "TODO : someone",
|
||||
Text: properties[0].(string),
|
||||
Timestamp: int64(properties[3].(float64)),
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package sdk
|
||||
|
||||
import "github.com/valyala/gorpc"
|
||||
|
||||
// RPCClient is a client to manage all rpc calls
|
||||
type RPCClient interface {
|
||||
Call(request interface{}) (response interface{}, err error)
|
||||
}
|
||||
|
||||
func newRPC(address string) RPCClient {
|
||||
rpc := &gorpc.Client{
|
||||
Addr: address, // "rpc.server.addr:12345",
|
||||
}
|
||||
rpc.Start()
|
||||
|
||||
return rpc
|
||||
}
|
|
@ -3,41 +3,36 @@ package sdk
|
|||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/valyala/gorpc"
|
||||
)
|
||||
|
||||
// Conn : TODO ...
|
||||
type Conn struct {
|
||||
rpc *gorpc.Client
|
||||
// SDK is a set of tools to interact with status node
|
||||
type SDK struct {
|
||||
RPCClient RPCClient
|
||||
address string
|
||||
userName string
|
||||
channels []*Channel
|
||||
minimumPoW string
|
||||
}
|
||||
|
||||
func NewConn(address string) *Conn {
|
||||
rpc := &gorpc.Client{
|
||||
Addr: address, // "rpc.server.addr:12345",
|
||||
}
|
||||
rpc.Start()
|
||||
|
||||
return &Conn{
|
||||
rpc: rpc,
|
||||
// New creates a default SDK object
|
||||
func New(address string) *SDK {
|
||||
return &SDK{
|
||||
RPCClient: newRPC(address),
|
||||
minimumPoW: "0.01",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Close() {
|
||||
// Close all channels you're subscribed to
|
||||
func (c *SDK) Close() {
|
||||
for _, channel := range c.channels {
|
||||
channel.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Login logs in to the network with the given credentials
|
||||
func (c *Conn) Login(addr, pwd string) error {
|
||||
// Login to status with the given credentials
|
||||
func (c *SDK) Login(addr, pwd string) error {
|
||||
cmd := fmt.Sprintf(statusLoginFormat, addr, pwd)
|
||||
res, err := c.rpc.Call(cmd)
|
||||
res, err := c.call(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -48,13 +43,9 @@ func (c *Conn) Login(addr, pwd string) error {
|
|||
}
|
||||
|
||||
// Signup creates a new account with the given credentials
|
||||
func (c *Conn) Signup(pwd string) error {
|
||||
func (c *SDK) Signup(pwd string) error {
|
||||
cmd := fmt.Sprintf(statusSignupFormat, pwd)
|
||||
res, err := c.rpc.Call(cmd)
|
||||
println("------")
|
||||
println(res)
|
||||
println(err.Error())
|
||||
println("------")
|
||||
res, err := c.call(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -66,9 +57,10 @@ func (c *Conn) Signup(pwd string) error {
|
|||
|
||||
// SignupOrLogin will attempt to login with given credentials, in first instance
|
||||
// or will sign up in case login does not work
|
||||
func (c *Conn) SignupOrLogin(user, password string) error {
|
||||
func (c *SDK) SignupOrLogin(user, password string) error {
|
||||
if err := c.Login(user, password); err != nil {
|
||||
c.Signup(password)
|
||||
// TODO (adriacidre) handle this error
|
||||
_ = c.Signup(password)
|
||||
return c.Login(user, password)
|
||||
}
|
||||
|
||||
|
@ -76,7 +68,7 @@ func (c *Conn) SignupOrLogin(user, password string) error {
|
|||
}
|
||||
|
||||
// Join a specific channel by name
|
||||
func (c *Conn) Join(channelName string) (*Channel, error) {
|
||||
func (c *SDK) Join(channelName string) (*Channel, error) {
|
||||
ch, err := c.joinPublicChannel(channelName)
|
||||
if err != nil {
|
||||
c.channels = append(c.channels, ch)
|
||||
|
@ -85,9 +77,9 @@ func (c *Conn) Join(channelName string) (*Channel, error) {
|
|||
return ch, err
|
||||
}
|
||||
|
||||
func (c *Conn) joinPublicChannel(channelName string) (*Channel, error) {
|
||||
func (c *SDK) joinPublicChannel(channelName string) (*Channel, error) {
|
||||
cmd := fmt.Sprintf(generateSymKeyFromPasswordFormat, channelName)
|
||||
res, _ := c.rpc.Call(cmd)
|
||||
res, _ := c.call(cmd)
|
||||
f := unmarshalJSON(res.(string))
|
||||
|
||||
key := f.(map[string]interface{})["result"].(string)
|
||||
|
@ -97,12 +89,12 @@ func (c *Conn) joinPublicChannel(channelName string) (*Channel, error) {
|
|||
p := "0x" + hex.EncodeToString(src)
|
||||
|
||||
cmd = fmt.Sprintf(web3ShaFormat, p, id)
|
||||
res, _ = c.rpc.Call(cmd)
|
||||
res, _ = c.call(cmd)
|
||||
topic := res.(map[string]interface{})["result"].(string)
|
||||
topic = topic[0:10]
|
||||
|
||||
cmd = fmt.Sprintf(newMessageFilterFormat, topic, key)
|
||||
res, _ = c.rpc.Call(cmd)
|
||||
res, _ = c.call(cmd)
|
||||
f3 := unmarshalJSON(res.(string))
|
||||
filterID := f3.(map[string]interface{})["result"].(string)
|
||||
|
||||
|
@ -114,3 +106,7 @@ func (c *Conn) joinPublicChannel(channelName string) (*Channel, error) {
|
|||
channelKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *SDK) call(cmd string) (interface{}, error) {
|
||||
return c.RPCClient.Call(cmd)
|
||||
}
|
|
@ -10,11 +10,14 @@ import (
|
|||
// asynchronous subscribers.
|
||||
type MsgHandler func(msg *Msg)
|
||||
|
||||
// Subscription is a polling helper for a specific channel
|
||||
type Subscription struct {
|
||||
unsubscribe chan bool
|
||||
channel *Channel
|
||||
}
|
||||
|
||||
// Subscribe polls on specific channel topic and executes given function if
|
||||
// any message is received
|
||||
func (s *Subscription) Subscribe(channel *Channel, fn MsgHandler) {
|
||||
s.channel = channel
|
||||
s.unsubscribe = make(chan bool)
|
||||
|
@ -24,7 +27,7 @@ func (s *Subscription) Subscribe(channel *Channel, fn MsgHandler) {
|
|||
return
|
||||
default:
|
||||
cmd := fmt.Sprintf(getFilterMessagesFormat, channel.filterID)
|
||||
response, err := channel.conn.rpc.Call(cmd)
|
||||
response, err := channel.conn.call(cmd)
|
||||
if err != nil {
|
||||
log.Fatalf("Error when sending request to server: %s", err)
|
||||
}
|
||||
|
@ -39,7 +42,9 @@ func (s *Subscription) Subscribe(channel *Channel, fn MsgHandler) {
|
|||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
fn(message)
|
||||
if supportedMessage(message.Type) {
|
||||
fn(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
@ -50,6 +55,7 @@ func (s *Subscription) Subscribe(channel *Channel, fn MsgHandler) {
|
|||
}
|
||||
}
|
||||
|
||||
// Unsubscribe stops polling on the current subscription channel
|
||||
func (s *Subscription) Unsubscribe() {
|
||||
s.unsubscribe <- true
|
||||
s.channel.removeSubscription(s)
|
||||
|
|
31
utils.go
31
utils.go
|
@ -1,39 +1,12 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func unmarshalJSON(j string) interface{} {
|
||||
var v interface{}
|
||||
json.Unmarshal([]byte(j), &v)
|
||||
// TODO(adriacidre) Handle this error
|
||||
_ = json.Unmarshal([]byte(j), &v)
|
||||
return v
|
||||
}
|
||||
|
||||
// newUUID generates a random UUID according to RFC 4122
|
||||
func newUUID() string {
|
||||
uuid := make([]byte, 16)
|
||||
n, err := io.ReadFull(rand.Reader, uuid)
|
||||
if n != len(uuid) || err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// variant bits; see section 4.1.1
|
||||
uuid[8] = uuid[8]&^0xc0 | 0x80
|
||||
// version 4 (pseudo-random); see section 4.1.3
|
||||
uuid[6] = uuid[6]&^0xf0 | 0x40
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
|
||||
}
|
||||
|
||||
func rawrChatMessage(raw string) string {
|
||||
bytes := []byte(raw)
|
||||
|
||||
return fmt.Sprintf("0x%s", hex.EncodeToString(bytes))
|
||||
}
|
||||
|
||||
func unrawrChatMessage(message string) ([]byte, error) {
|
||||
return hex.DecodeString(message[2:])
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue