mirror of
https://github.com/status-im/status-go.git
synced 2025-01-22 04:31:30 +00:00
1f6cccd0fc
This commit adds a list new table, installations, which is used to keep track of which installation are active for a given identity key. In general, we limit the number of installation that we keep synchronized to 5, to avoid excessive usage of resources. Any installation coming from our own identity, will have to be manually enabled, otherwise we trust the other peer has correctly paired their devices. We use a timestamp to decide which installations to keep synchronized as a logical clock would have make the creation of the bundle more complicated, but this can always be converted to a logical clock at later stages without breaking compatibility.
490 lines
13 KiB
Go
490 lines
13 KiB
Go
package main
|
|
|
|
// #include <stdlib.h>
|
|
import "C"
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"github.com/NaySoftware/go-fcm"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/status-im/status-go/api"
|
|
"github.com/status-im/status-go/logutils"
|
|
"github.com/status-im/status-go/params"
|
|
"github.com/status-im/status-go/profiling"
|
|
"github.com/status-im/status-go/services/personal"
|
|
"github.com/status-im/status-go/services/typeddata"
|
|
"github.com/status-im/status-go/signal"
|
|
"github.com/status-im/status-go/transactions"
|
|
"gopkg.in/go-playground/validator.v9"
|
|
)
|
|
|
|
// All general log messages in this package should be routed through this logger.
|
|
var logger = log.New("package", "status-go/lib")
|
|
|
|
//StartNode - start Status node
|
|
//export StartNode
|
|
func StartNode(configJSON *C.char) *C.char {
|
|
config, err := params.NewConfigFromJSON(C.GoString(configJSON))
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
if err := logutils.OverrideRootLog(config.LogEnabled, config.LogLevel, config.LogFile, false); err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
api.RunAsync(func() error { return statusBackend.StartNode(config) })
|
|
|
|
return makeJSONResponse(nil)
|
|
}
|
|
|
|
//StopNode - stop status node
|
|
//export StopNode
|
|
func StopNode() *C.char {
|
|
api.RunAsync(statusBackend.StopNode)
|
|
return makeJSONResponse(nil)
|
|
}
|
|
|
|
// Create an X3DH bundle
|
|
//export CreateContactCode
|
|
func CreateContactCode() *C.char {
|
|
bundle, err := statusBackend.CreateContactCode()
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
cstr := C.CString(bundle)
|
|
|
|
return cstr
|
|
}
|
|
|
|
//export ProcessContactCode
|
|
func ProcessContactCode(bundleString *C.char) *C.char {
|
|
err := statusBackend.ProcessContactCode(C.GoString(bundleString))
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
//export ExtractIdentityFromContactCode
|
|
func ExtractIdentityFromContactCode(bundleString *C.char) *C.char {
|
|
bundle := C.GoString(bundleString)
|
|
|
|
identity, err := statusBackend.ExtractIdentityFromContactCode(bundle)
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
if err := statusBackend.ProcessContactCode(bundle); err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
data, err := json.Marshal(struct {
|
|
Identity string `json:"identity"`
|
|
}{Identity: identity})
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return C.CString(string(data))
|
|
}
|
|
|
|
// ExtractGroupMembershipSignatures extract public keys from tuples of content/signature
|
|
//export ExtractGroupMembershipSignatures
|
|
func ExtractGroupMembershipSignatures(signaturePairsStr *C.char) *C.char {
|
|
var signaturePairs [][2]string
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(signaturePairsStr)), &signaturePairs); err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
identities, err := statusBackend.ExtractGroupMembershipSignatures(signaturePairs)
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
data, err := json.Marshal(struct {
|
|
Identities []string `json:"identities"`
|
|
}{Identities: identities})
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return C.CString(string(data))
|
|
}
|
|
|
|
// Sign signs a string containing group membership information
|
|
//export SignGroupMembership
|
|
func SignGroupMembership(content *C.char) *C.char {
|
|
signature, err := statusBackend.SignGroupMembership(C.GoString(content))
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
data, err := json.Marshal(struct {
|
|
Signature string `json:"signature"`
|
|
}{Signature: signature})
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return C.CString(string(data))
|
|
}
|
|
|
|
// EnableInstallation enables an installation for multi-device sync.
|
|
//export EnableInstallation
|
|
func EnableInstallation(installationID *C.char) *C.char {
|
|
err := statusBackend.EnableInstallation(C.GoString(installationID))
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
data, err := json.Marshal(struct {
|
|
Response string `json:"response"`
|
|
}{Response: "ok"})
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return C.CString(string(data))
|
|
}
|
|
|
|
// DisableInstallation disables an installation for multi-device sync.
|
|
//export DisableInstallation
|
|
func DisableInstallation(installationID *C.char) *C.char {
|
|
err := statusBackend.DisableInstallation(C.GoString(installationID))
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
data, err := json.Marshal(struct {
|
|
Response string `json:"response"`
|
|
}{Response: "ok"})
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return C.CString(string(data))
|
|
}
|
|
|
|
//ValidateNodeConfig validates config for status node
|
|
//export ValidateNodeConfig
|
|
func ValidateNodeConfig(configJSON *C.char) *C.char {
|
|
var resp APIDetailedResponse
|
|
|
|
_, err := params.NewConfigFromJSON(C.GoString(configJSON))
|
|
|
|
// Convert errors to APIDetailedResponse
|
|
switch err := err.(type) {
|
|
case validator.ValidationErrors:
|
|
resp = APIDetailedResponse{
|
|
Message: "validation: validation failed",
|
|
FieldErrors: make([]APIFieldError, len(err)),
|
|
}
|
|
|
|
for i, ve := range err {
|
|
resp.FieldErrors[i] = APIFieldError{
|
|
Parameter: ve.Namespace(),
|
|
Errors: []APIError{
|
|
{
|
|
Message: fmt.Sprintf("field validation failed on the '%s' tag", ve.Tag()),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
case error:
|
|
resp = APIDetailedResponse{
|
|
Message: fmt.Sprintf("validation: %s", err.Error()),
|
|
}
|
|
case nil:
|
|
resp = APIDetailedResponse{
|
|
Status: true,
|
|
}
|
|
}
|
|
|
|
respJSON, err := json.Marshal(resp)
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return C.CString(string(respJSON))
|
|
}
|
|
|
|
//ResetChainData remove chain data from data directory
|
|
//export ResetChainData
|
|
func ResetChainData() *C.char {
|
|
api.RunAsync(statusBackend.ResetChainData)
|
|
return makeJSONResponse(nil)
|
|
}
|
|
|
|
//CallRPC calls public APIs via RPC
|
|
//export CallRPC
|
|
func CallRPC(inputJSON *C.char) *C.char {
|
|
outputJSON := statusBackend.CallRPC(C.GoString(inputJSON))
|
|
return C.CString(outputJSON)
|
|
}
|
|
|
|
//CallPrivateRPC calls both public and private APIs via RPC
|
|
//export CallPrivateRPC
|
|
func CallPrivateRPC(inputJSON *C.char) *C.char {
|
|
outputJSON := statusBackend.CallPrivateRPC(C.GoString(inputJSON))
|
|
return C.CString(outputJSON)
|
|
}
|
|
|
|
//CreateAccount is equivalent to creating an account from the command line,
|
|
// just modified to handle the function arg passing
|
|
//export CreateAccount
|
|
func CreateAccount(password *C.char) *C.char {
|
|
address, pubKey, mnemonic, err := statusBackend.AccountManager().CreateAccount(C.GoString(password))
|
|
|
|
errString := ""
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
errString = err.Error()
|
|
}
|
|
|
|
out := AccountInfo{
|
|
Address: address,
|
|
PubKey: pubKey,
|
|
Mnemonic: mnemonic,
|
|
Error: errString,
|
|
}
|
|
outBytes, _ := json.Marshal(out)
|
|
return C.CString(string(outBytes))
|
|
}
|
|
|
|
//CreateChildAccount creates sub-account
|
|
//export CreateChildAccount
|
|
func CreateChildAccount(parentAddress, password *C.char) *C.char {
|
|
address, pubKey, err := statusBackend.AccountManager().CreateChildAccount(C.GoString(parentAddress), C.GoString(password))
|
|
|
|
errString := ""
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
errString = err.Error()
|
|
}
|
|
|
|
out := AccountInfo{
|
|
Address: address,
|
|
PubKey: pubKey,
|
|
Error: errString,
|
|
}
|
|
outBytes, _ := json.Marshal(out)
|
|
return C.CString(string(outBytes))
|
|
}
|
|
|
|
//RecoverAccount re-creates master key using given details
|
|
//export RecoverAccount
|
|
func RecoverAccount(password, mnemonic *C.char) *C.char {
|
|
address, pubKey, err := statusBackend.AccountManager().RecoverAccount(C.GoString(password), C.GoString(mnemonic))
|
|
|
|
errString := ""
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
errString = err.Error()
|
|
}
|
|
|
|
out := AccountInfo{
|
|
Address: address,
|
|
PubKey: pubKey,
|
|
Mnemonic: C.GoString(mnemonic),
|
|
Error: errString,
|
|
}
|
|
outBytes, _ := json.Marshal(out)
|
|
return C.CString(string(outBytes))
|
|
}
|
|
|
|
//VerifyAccountPassword verifies account password
|
|
//export VerifyAccountPassword
|
|
func VerifyAccountPassword(keyStoreDir, address, password *C.char) *C.char {
|
|
_, err := statusBackend.AccountManager().VerifyAccountPassword(C.GoString(keyStoreDir), C.GoString(address), C.GoString(password))
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
//Login loads a key file (for a given address), tries to decrypt it using the password, to verify ownership
|
|
// if verified, purges all the previous identities from Whisper, and injects verified key as shh identity
|
|
//export Login
|
|
func Login(address, password *C.char) *C.char {
|
|
err := statusBackend.SelectAccount(C.GoString(address), C.GoString(password))
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
//Logout is equivalent to clearing whisper identities
|
|
//export Logout
|
|
func Logout() *C.char {
|
|
err := statusBackend.Logout()
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// SignMessage unmarshals rpc params {data, address, password} and passes
|
|
// them onto backend.SignMessage
|
|
//export SignMessage
|
|
func SignMessage(rpcParams *C.char) *C.char {
|
|
var params personal.SignParams
|
|
err := json.Unmarshal([]byte(C.GoString(rpcParams)), ¶ms)
|
|
if err != nil {
|
|
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
}
|
|
result, err := statusBackend.SignMessage(params)
|
|
return C.CString(prepareJSONResponse(result.String(), err))
|
|
}
|
|
|
|
// Recover unmarshals rpc params {signDataString, signedData} and passes
|
|
// them onto backend.
|
|
//export Recover
|
|
func Recover(rpcParams *C.char) *C.char {
|
|
var params personal.RecoverParams
|
|
err := json.Unmarshal([]byte(C.GoString(rpcParams)), ¶ms)
|
|
if err != nil {
|
|
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
}
|
|
addr, err := statusBackend.Recover(params)
|
|
return C.CString(prepareJSONResponse(addr.String(), err))
|
|
}
|
|
|
|
// SendTransaction converts RPC args and calls backend.SendTransaction
|
|
//export SendTransaction
|
|
func SendTransaction(txArgsJSON, password *C.char) *C.char {
|
|
var params transactions.SendTxArgs
|
|
err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms)
|
|
if err != nil {
|
|
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
}
|
|
hash, err := statusBackend.SendTransaction(params, C.GoString(password))
|
|
code := codeUnknown
|
|
if c, ok := errToCodeMap[err]; ok {
|
|
code = c
|
|
}
|
|
return C.CString(prepareJSONResponseWithCode(hash.String(), err, code))
|
|
}
|
|
|
|
// SignTypedData unmarshall data into TypedData, validate it and signs with selected account,
|
|
// if password matches selected account.
|
|
//export SignTypedData
|
|
func SignTypedData(data, password *C.char) *C.char {
|
|
var typed typeddata.TypedData
|
|
err := json.Unmarshal([]byte(C.GoString(data)), &typed)
|
|
if err != nil {
|
|
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
}
|
|
if err := typed.Validate(); err != nil {
|
|
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
}
|
|
result, err := statusBackend.SignTypedData(typed, C.GoString(password))
|
|
return C.CString(prepareJSONResponse(result.String(), err))
|
|
}
|
|
|
|
//StartCPUProfile runs pprof for cpu
|
|
//export StartCPUProfile
|
|
func StartCPUProfile(dataDir *C.char) *C.char {
|
|
err := profiling.StartCPUProfile(C.GoString(dataDir))
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
//StopCPUProfiling stops pprof for cpu
|
|
//export StopCPUProfiling
|
|
func StopCPUProfiling() *C.char { //nolint: deadcode
|
|
err := profiling.StopCPUProfile()
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
//WriteHeapProfile starts pprof for heap
|
|
//export WriteHeapProfile
|
|
func WriteHeapProfile(dataDir *C.char) *C.char { //nolint: deadcode
|
|
err := profiling.WriteHeapFile(C.GoString(dataDir))
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
func makeJSONResponse(err error) *C.char {
|
|
errString := ""
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
errString = err.Error()
|
|
}
|
|
|
|
out := APIResponse{
|
|
Error: errString,
|
|
}
|
|
outBytes, _ := json.Marshal(out)
|
|
|
|
return C.CString(string(outBytes))
|
|
}
|
|
|
|
// NotifyUsers sends push notifications by given tokens.
|
|
//export NotifyUsers
|
|
func NotifyUsers(message, payloadJSON, tokensArray *C.char) (outCBytes *C.char) {
|
|
var (
|
|
err error
|
|
outBytes []byte
|
|
)
|
|
errString := ""
|
|
|
|
defer func() {
|
|
out := NotifyResult{
|
|
Status: err == nil,
|
|
Error: errString,
|
|
}
|
|
|
|
outBytes, err = json.Marshal(out)
|
|
if err != nil {
|
|
logger.Error("failed to marshal Notify output", "error", err)
|
|
outCBytes = makeJSONResponse(err)
|
|
return
|
|
}
|
|
|
|
outCBytes = C.CString(string(outBytes))
|
|
}()
|
|
|
|
tokens, err := ParseJSONArray(C.GoString(tokensArray))
|
|
if err != nil {
|
|
errString = err.Error()
|
|
return
|
|
}
|
|
|
|
var payload fcm.NotificationPayload
|
|
err = json.Unmarshal([]byte(C.GoString(payloadJSON)), &payload)
|
|
if err != nil {
|
|
errString = err.Error()
|
|
return
|
|
}
|
|
|
|
err = statusBackend.NotifyUsers(C.GoString(message), payload, tokens...)
|
|
if err != nil {
|
|
errString = err.Error()
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// AddPeer adds an enode as a peer.
|
|
//export AddPeer
|
|
func AddPeer(enode *C.char) *C.char {
|
|
err := statusBackend.StatusNode().AddPeer(C.GoString(enode))
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// ConnectionChange handles network state changes as reported
|
|
// by ReactNative (see https://facebook.github.io/react-native/docs/netinfo.html)
|
|
//export ConnectionChange
|
|
func ConnectionChange(typ *C.char, expensive C.int) {
|
|
statusBackend.ConnectionChange(C.GoString(typ), expensive == 1)
|
|
}
|
|
|
|
// AppStateChange handles app state changes (background/foreground).
|
|
//export AppStateChange
|
|
func AppStateChange(state *C.char) {
|
|
statusBackend.AppStateChange(C.GoString(state))
|
|
}
|
|
|
|
// SetSignalEventCallback setup geth callback to notify about new signal
|
|
//export SetSignalEventCallback
|
|
func SetSignalEventCallback(cb unsafe.Pointer) {
|
|
signal.SetSignalEventCallback(cb)
|
|
}
|