diff --git a/rpc/api/api.go b/rpc/api/api.go index e870ec58e..206647946 100644 --- a/rpc/api/api.go +++ b/rpc/api/api.go @@ -13,7 +13,8 @@ const ( MergedApiName = "merged" MinerApiName = "miner" NetApiName = "net" - txPoolApiName = "txpool" + ShhApiName = "shh" + TxPoolApiName = "txpool" PersonalApiName = "personal" Web3ApiName = "web3" ) @@ -21,7 +22,8 @@ const ( var ( // List with all API's which are offered over the IPC interface by default DefaultIpcApis = strings.Join([]string{ - AdminApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, txPoolApiName, PersonalApiName, Web3ApiName, + AdminApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, + ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName, }, ",") ) diff --git a/rpc/api/shh.go b/rpc/api/shh.go new file mode 100644 index 000000000..04c53c93e --- /dev/null +++ b/rpc/api/shh.go @@ -0,0 +1,171 @@ +package api + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" + "github.com/ethereum/go-ethereum/xeth" +) + +var ( + // mapping between methods and handlers + shhMapping = map[string]shhhandler{ + "shh_version": (*shhApi).Version, + "shh_post": (*shhApi).Post, + "shh_hasIdentity": (*shhApi).HasIdentity, + "shh_newIdentity": (*shhApi).NewIdentity, + "shh_newFilter": (*shhApi).NewFilter, + "shh_uninstallFilter": (*shhApi).UninstallFilter, + "shh_getFilterChanges": (*shhApi).GetFilterChanges, + } +) + +func newWhisperOfflineError(method string) error { + return shared.NewNotAvailableError(method, "whisper offline") +} + +// net callback handler +type shhhandler func(*shhApi, *shared.Request) (interface{}, error) + +// shh api provider +type shhApi struct { + xeth *xeth.XEth + ethereum *eth.Ethereum + methods map[string]shhhandler + codec codec.ApiCoder +} + +// create a new whisper api instance +func NewShhApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *shhApi { + return &shhApi{ + xeth: xeth, + ethereum: eth, + methods: shhMapping, + codec: coder.New(nil), + } +} + +// collection with supported methods +func (self *shhApi) Methods() []string { + methods := make([]string, len(self.methods)) + i := 0 + for k := range self.methods { + methods[i] = k + i++ + } + return methods +} + +// Execute given request +func (self *shhApi) Execute(req *shared.Request) (interface{}, error) { + if callback, ok := self.methods[req.Method]; ok { + return callback(self, req) + } + + return nil, shared.NewNotImplementedError(req.Method) +} + +func (self *shhApi) Name() string { + return ShhApiName +} + +func (self *shhApi) Version(req *shared.Request) (interface{}, error) { + w := self.xeth.Whisper() + if w == nil { + return nil, newWhisperOfflineError(req.Method) + } + + return w.Version(), nil +} + +func (self *shhApi) Post(req *shared.Request) (interface{}, error) { + w := self.xeth.Whisper() + if w == nil { + return nil, newWhisperOfflineError(req.Method) + } + + args := new(WhisperMessageArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, err + } + + err := w.Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl) + if err != nil { + return false, err + } + + return true, nil +} + +func (self *shhApi) HasIdentity(req *shared.Request) (interface{}, error) { + w := self.xeth.Whisper() + if w == nil { + return nil, newWhisperOfflineError(req.Method) + } + + args := new(WhisperIdentityArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, err + } + + return w.HasIdentity(args.Identity), nil +} + +func (self *shhApi) NewIdentity(req *shared.Request) (interface{}, error) { + w := self.xeth.Whisper() + if w == nil { + return nil, newWhisperOfflineError(req.Method) + } + + return w.NewIdentity(), nil +} + +func (self *shhApi) NewFilter(req *shared.Request) (interface{}, error) { + args := new(WhisperFilterArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, err + } + + id := self.xeth.NewWhisperFilter(args.To, args.From, args.Topics) + return newHexNum(big.NewInt(int64(id)).Bytes()), nil +} + +func (self *shhApi) UninstallFilter(req *shared.Request) (interface{}, error) { + args := new(FilterIdArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, err + } + return self.xeth.UninstallWhisperFilter(args.Id), nil +} + +func (self *shhApi) GetFilterChanges(req *shared.Request) (interface{}, error) { + w := self.xeth.Whisper() + if w == nil { + return nil, newWhisperOfflineError(req.Method) + } + + // Retrieve all the new messages arrived since the last request + args := new(FilterIdArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, err + } + + return self.xeth.WhisperMessagesChanged(args.Id), nil +} + +func (self *shhApi) GetMessages(req *shared.Request) (interface{}, error) { + w := self.xeth.Whisper() + if w == nil { + return nil, newWhisperOfflineError(req.Method) + } + + // Retrieve all the cached messages matching a specific, existing filter + args := new(FilterIdArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, err + } + + return self.xeth.WhisperMessages(args.Id), nil +} diff --git a/rpc/api/shh_args.go b/rpc/api/shh_args.go new file mode 100644 index 000000000..00abac232 --- /dev/null +++ b/rpc/api/shh_args.go @@ -0,0 +1,158 @@ +package api + +import ( + "encoding/json" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/rpc/shared" +) + +type WhisperMessageArgs struct { + Payload string + To string + From string + Topics []string + Priority uint32 + Ttl uint32 +} + +func (args *WhisperMessageArgs) UnmarshalJSON(b []byte) (err error) { + var obj []struct { + Payload string + To string + From string + Topics []string + Priority interface{} + Ttl interface{} + } + + if err = json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + if len(obj) < 1 { + return shared.NewInsufficientParamsError(len(obj), 1) + } + args.Payload = obj[0].Payload + args.To = obj[0].To + args.From = obj[0].From + args.Topics = obj[0].Topics + + var num *big.Int + if num, err = numString(obj[0].Priority); err != nil { + return err + } + args.Priority = uint32(num.Int64()) + + if num, err = numString(obj[0].Ttl); err != nil { + return err + } + args.Ttl = uint32(num.Int64()) + + return nil +} + +type WhisperIdentityArgs struct { + Identity string +} + +func (args *WhisperIdentityArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + if len(obj) < 1 { + return shared.NewInsufficientParamsError(len(obj), 1) + } + + argstr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("arg0", "not a string") + } + + args.Identity = argstr + + return nil +} + +type WhisperFilterArgs struct { + To string + From string + Topics [][]string +} + +// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a +// JSON message blob into a WhisperFilterArgs structure. +func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { + // Unmarshal the JSON message and sanity check + var obj []struct { + To interface{} `json:"to"` + From interface{} `json:"from"` + Topics interface{} `json:"topics"` + } + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + if len(obj) < 1 { + return shared.NewInsufficientParamsError(len(obj), 1) + } + // Retrieve the simple data contents of the filter arguments + if obj[0].To == nil { + args.To = "" + } else { + argstr, ok := obj[0].To.(string) + if !ok { + return shared.NewInvalidTypeError("to", "is not a string") + } + args.To = argstr + } + if obj[0].From == nil { + args.From = "" + } else { + argstr, ok := obj[0].From.(string) + if !ok { + return shared.NewInvalidTypeError("from", "is not a string") + } + args.From = argstr + } + // Construct the nested topic array + if obj[0].Topics != nil { + // Make sure we have an actual topic array + list, ok := obj[0].Topics.([]interface{}) + if !ok { + return shared.NewInvalidTypeError("topics", "is not an array") + } + // Iterate over each topic and handle nil, string or array + topics := make([][]string, len(list)) + for idx, field := range list { + switch value := field.(type) { + case nil: + topics[idx] = []string{} + + case string: + topics[idx] = []string{value} + + case []interface{}: + topics[idx] = make([]string, len(value)) + for i, nested := range value { + switch value := nested.(type) { + case nil: + topics[idx][i] = "" + + case string: + topics[idx][i] = value + + default: + return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d][%d]", idx, i), "is not a string") + } + } + default: + return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d]", idx), "not a string or array") + } + } + args.Topics = topics + } + return nil +} diff --git a/rpc/api/ssh_js.go b/rpc/api/ssh_js.go new file mode 100644 index 000000000..f401f70f1 --- /dev/null +++ b/rpc/api/ssh_js.go @@ -0,0 +1,30 @@ +package api + +const Shh_JS = ` +web3._extend({ + property: 'shh', + methods: + [ + new web3._extend.Method({ + name: 'post', + call: 'shh_post', + params: 6, + inputFormatter: [web3._extend.formatters.formatInputString, + web3._extend.formatters.formatInputString, + web3._extend.formatters.formatInputString, + , + , web3._extend.formatters.formatInputInt + , web3._extend.formatters.formatInputInt], + outputFormatter: web3._extend.formatters.formatOutputBool + }), + ], + properties: + [ + new web3._extend.Property({ + name: 'version', + getter: 'shh_version', + outputFormatter: web3._extend.formatters.formatOutputInt + }) + ] +}); +` diff --git a/rpc/api/txpool.go b/rpc/api/txpool.go index f340c501f..ebbe199b1 100644 --- a/rpc/api/txpool.go +++ b/rpc/api/txpool.go @@ -56,7 +56,7 @@ func (self *txPoolApi) Execute(req *shared.Request) (interface{}, error) { } func (self *txPoolApi) Name() string { - return txPoolApiName + return TxPoolApiName } func (self *txPoolApi) Status(req *shared.Request) (interface{}, error) { diff --git a/rpc/api/utils.go b/rpc/api/utils.go index b44a325a8..ad8a97e92 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -31,7 +31,9 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth. apis[i] = NewMinerApi(eth, codec) case NetApiName: apis[i] = NewNetApi(xeth, eth, codec) - case txPoolApiName: + case ShhApiName: + apis[i] = NewShhApi(xeth, eth, codec) + case TxPoolApiName: apis[i] = NewTxPoolApi(xeth, eth, codec) case PersonalApiName: apis[i] = NewPersonalApi(xeth, eth, codec) @@ -55,7 +57,9 @@ func Javascript(name string) string { return Miner_JS case NetApiName: return Net_JS - case txPoolApiName: + case ShhApiName: + return Shh_JS + case TxPoolApiName: return TxPool_JS case PersonalApiName: return Personal_JS