2021-11-08 16:39:17 +00:00
|
|
|
package rpc
|
|
|
|
|
|
|
|
import "fmt"
|
|
|
|
import "time"
|
|
|
|
import "sort"
|
|
|
|
import "encoding/json"
|
|
|
|
|
|
|
|
import "github.com/fxamacker/cbor/v2"
|
|
|
|
import "github.com/deroproject/derohe/cryptography/crypto"
|
|
|
|
|
|
|
|
// this package defines interfaces and necessary glue code Digital Network, it exposes and provides encrypted RPC calls over DERO chain
|
|
|
|
|
|
|
|
var enc_options = cbor.EncOptions{
|
|
|
|
Sort: cbor.SortCTAP2,
|
|
|
|
TimeTag: cbor.EncTagRequired,
|
|
|
|
}
|
|
|
|
|
|
|
|
var dec_options = cbor.DecOptions{
|
|
|
|
TimeTag: cbor.DecTagRequired,
|
|
|
|
}
|
|
|
|
|
|
|
|
var dec cbor.DecMode
|
|
|
|
var enc cbor.EncMode
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
var err error
|
|
|
|
if dec, err = dec_options.DecMode(); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
if enc, err = enc_options.EncMode(); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// currently we support only the following Data Type
|
|
|
|
// the following data types are present
|
|
|
|
// int64 represented by inputbox
|
|
|
|
// uint64 represented by inputbox
|
|
|
|
// string represented by input box
|
|
|
|
// what about listbox, checkbox , checkbox can be represented by bool but currently not suported
|
|
|
|
type DataType string
|
|
|
|
|
|
|
|
const (
|
|
|
|
DataString DataType = "S"
|
|
|
|
DataInt64 = "I"
|
|
|
|
DataUint64 = "U"
|
|
|
|
DataFloat64 = "F"
|
|
|
|
DataHash = "H" // a 256 bit hash (basically sha256 of 32 bytes long)
|
|
|
|
DataAddress = "A" // dero address represented in 33 bytes
|
|
|
|
DataTime = "T"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (d DataType) String() string {
|
|
|
|
switch d {
|
|
|
|
case DataString:
|
|
|
|
return "string"
|
|
|
|
case DataInt64:
|
|
|
|
return "int64"
|
|
|
|
case DataUint64:
|
|
|
|
return "uint64"
|
|
|
|
case DataFloat64:
|
|
|
|
return "float64"
|
|
|
|
case DataHash:
|
|
|
|
return "hash"
|
|
|
|
case DataAddress:
|
|
|
|
return "address"
|
|
|
|
case DataTime:
|
|
|
|
return "time"
|
|
|
|
|
|
|
|
default:
|
|
|
|
return "unknown data type"
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//type DataType byte
|
|
|
|
type Argument struct {
|
|
|
|
Name string `json:"name"` // string name must be atleast 1 byte
|
|
|
|
DataType DataType `json:"datatype"` // Type must one of the known data types
|
|
|
|
Value interface{} `json:"value"` // value should be as per type
|
|
|
|
}
|
|
|
|
|
|
|
|
type Arguments []Argument
|
|
|
|
|
|
|
|
func (arg Argument) String() string {
|
|
|
|
switch arg.DataType {
|
|
|
|
case DataString:
|
|
|
|
return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value)
|
|
|
|
case DataInt64:
|
|
|
|
return fmt.Sprintf("Name:%s Type:%s Value:'%d'", arg.Name, arg.DataType, arg.Value)
|
|
|
|
case DataUint64:
|
|
|
|
return fmt.Sprintf("Name:%s Type:%s Value:'%d'", arg.Name, arg.DataType, arg.Value)
|
|
|
|
case DataFloat64:
|
|
|
|
return fmt.Sprintf("Name:%s Type:%s Value:'%f'", arg.Name, arg.DataType, arg.Value)
|
|
|
|
case DataHash:
|
|
|
|
return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value)
|
|
|
|
case DataAddress:
|
2021-11-09 12:17:16 +00:00
|
|
|
return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value)
|
2021-11-08 16:39:17 +00:00
|
|
|
case DataTime:
|
|
|
|
return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value)
|
|
|
|
|
|
|
|
default:
|
|
|
|
return "unknown data type"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// tells whether the arguments have an argument of this type
|
|
|
|
func (args Arguments) Has(name string, dtype DataType) bool {
|
|
|
|
for _, arg := range args {
|
|
|
|
if arg.Name == name && arg.DataType == dtype {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// tells whether the arguments have an argument of this type and value it not nil
|
|
|
|
func (args Arguments) HasValue(name string, dtype DataType) bool {
|
|
|
|
for _, arg := range args {
|
|
|
|
if arg.Name == name && arg.DataType == dtype && arg.Value != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// tells the index of the specific argument
|
|
|
|
func (args Arguments) Index(name string, dtype DataType) int {
|
|
|
|
for i, arg := range args {
|
|
|
|
if arg.Name == name && arg.DataType == dtype {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// return value wrapped in an interface
|
|
|
|
func (args Arguments) Value(name string, dtype DataType) interface{} {
|
|
|
|
for _, arg := range args {
|
|
|
|
if arg.Name == name && arg.DataType == dtype {
|
|
|
|
return arg.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// this function will pack the args into buffer of specific limit, if it fails, it panics
|
|
|
|
func (args Arguments) MustPack(limit int) []byte {
|
|
|
|
if packed, err := args.CheckPack(limit); err != nil {
|
|
|
|
panic(err)
|
|
|
|
} else {
|
|
|
|
return packed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this function will pack the args into buffer of specific limit, if it fails, it gives eroor
|
|
|
|
func (args Arguments) CheckPack(limit int) ([]byte, error) {
|
|
|
|
packed, err := args.MarshalBinary()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(packed) > limit {
|
|
|
|
return nil, fmt.Errorf("Packed size %d bytes, but limit is %d", len(packed), limit)
|
|
|
|
}
|
|
|
|
if len(packed) == limit {
|
|
|
|
return packed, nil
|
|
|
|
} else { // we need to fill with 0 values, upto limit size
|
|
|
|
|
|
|
|
fill_count := limit - len(packed)
|
|
|
|
for i := 0; i < fill_count; i++ {
|
|
|
|
packed = append(packed, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return packed, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// pack more deeply
|
|
|
|
func (args Arguments) MarshalBinary() (data []byte, err error) {
|
|
|
|
if err = args.Validate_Arguments(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
localmap := map[string]interface{}{} // this also filters any duplicates
|
|
|
|
for _, arg := range args {
|
|
|
|
switch v := arg.Value.(type) {
|
|
|
|
case int64:
|
|
|
|
localmap[arg.Name+string(arg.DataType)] = v
|
|
|
|
case uint64:
|
|
|
|
localmap[arg.Name+string(arg.DataType)] = v
|
|
|
|
case float64:
|
|
|
|
localmap[arg.Name+string(arg.DataType)] = v
|
|
|
|
case crypto.Hash:
|
|
|
|
localmap[arg.Name+string(arg.DataType)] = v
|
|
|
|
case Address:
|
2021-11-09 12:17:16 +00:00
|
|
|
localmap[arg.Name+string(arg.DataType)] = v.PublicKey.EncodeCompressed()
|
2021-11-08 16:39:17 +00:00
|
|
|
case string:
|
|
|
|
localmap[arg.Name+string(arg.DataType)] = v
|
|
|
|
case time.Time:
|
|
|
|
localmap[arg.Name+string(arg.DataType)] = v
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("I don't know about type %T!\n", v)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return enc.Marshal(localmap)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (args *Arguments) UnmarshalBinary(data []byte) (err error) {
|
|
|
|
localmap := map[string]interface{}{}
|
|
|
|
|
|
|
|
if err = dec.Unmarshal(data, &localmap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
*args = (*args)[:0]
|
|
|
|
|
|
|
|
for k, v := range localmap {
|
|
|
|
if len(k) < 2 {
|
|
|
|
return fmt.Errorf("Invalid encoding for key '%s'", k)
|
|
|
|
}
|
|
|
|
|
|
|
|
arg := Argument{Name: string(k[:len(k)-1]), DataType: DataType(k[len(k)-1:])}
|
|
|
|
|
|
|
|
switch arg.DataType {
|
|
|
|
case DataInt64:
|
|
|
|
if value, ok := v.(int64); ok {
|
|
|
|
arg.Value = value
|
|
|
|
} else if value, ok := v.(uint64); ok {
|
|
|
|
arg.Value = int64(value)
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("%+v has invalid data typei %T\n", arg, v)
|
|
|
|
}
|
|
|
|
case DataUint64:
|
|
|
|
if value, ok := v.(uint64); ok {
|
|
|
|
arg.Value = value
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("%+v has invalid data type %T\n", arg, v)
|
|
|
|
}
|
|
|
|
case DataFloat64:
|
|
|
|
if value, ok := v.(float64); ok {
|
|
|
|
arg.Value = value
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("%+v has invalid data type %T\n", arg, v)
|
|
|
|
}
|
|
|
|
case DataHash:
|
|
|
|
if value, ok := v.([]uint8); ok {
|
|
|
|
var hash crypto.Hash
|
|
|
|
copy(hash[:], value)
|
|
|
|
arg.Value = hash
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("%+v has invalid data type %T\n", arg, v)
|
|
|
|
}
|
|
|
|
case DataAddress:
|
|
|
|
if value, ok := v.([]uint8); ok {
|
|
|
|
a := make([]byte, 33, 33)
|
|
|
|
copy(a[:], value)
|
|
|
|
|
|
|
|
p := new(crypto.Point)
|
|
|
|
if err = p.DecodeCompressed(a[0:33]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addr := NewAddressFromKeys(p)
|
|
|
|
arg.Value = *addr
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("%+v has invalid data type %T\n", arg, v)
|
|
|
|
}
|
|
|
|
case DataString:
|
|
|
|
if value, ok := v.(string); ok {
|
|
|
|
arg.Value = value
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("%+v has invalid data type %T\n", arg, v)
|
|
|
|
}
|
|
|
|
case DataTime:
|
|
|
|
if value, ok := v.(time.Time); ok {
|
|
|
|
arg.Value = value
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("%+v has invalid data type %T\n", arg, v)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("I don't know about typeaa %T %s!\n", v, k)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
*args = append(*args, arg)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = args.Validate_Arguments(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
args.Sort() // sort everything
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// used to validata arguments whether the type is proper
|
|
|
|
func (args Arguments) Validate_Arguments() error {
|
|
|
|
for _, arg := range args {
|
|
|
|
if len(arg.Name) < 1 {
|
|
|
|
return fmt.Errorf("Name must be atleast 1 char long")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch arg.DataType {
|
|
|
|
case DataString:
|
|
|
|
if _, ok := arg.Value.(string); !ok {
|
|
|
|
return fmt.Errorf("'%s' value should be of type string", arg.Name)
|
|
|
|
}
|
|
|
|
case DataInt64:
|
|
|
|
if _, ok := arg.Value.(int64); !ok {
|
|
|
|
return fmt.Errorf("'%s' value should be of type int64", arg.Name)
|
|
|
|
}
|
|
|
|
case DataUint64:
|
|
|
|
if _, ok := arg.Value.(uint64); !ok {
|
|
|
|
return fmt.Errorf("'%s' value should be of type uint64", arg.Name)
|
|
|
|
}
|
|
|
|
case DataFloat64:
|
|
|
|
if _, ok := arg.Value.(float64); !ok {
|
|
|
|
return fmt.Errorf("'%s' value should be of type float64", arg.Name)
|
|
|
|
}
|
|
|
|
case DataHash:
|
|
|
|
if _, ok := arg.Value.(crypto.Hash); !ok {
|
|
|
|
return fmt.Errorf("'%s' value should be of type Hash", arg.Name)
|
|
|
|
}
|
|
|
|
case DataAddress:
|
|
|
|
if _, ok := arg.Value.(Address); !ok {
|
|
|
|
return fmt.Errorf("'%s' value should be of type address", arg.Name)
|
|
|
|
}
|
|
|
|
case DataTime:
|
|
|
|
if _, ok := arg.Value.(time.Time); !ok {
|
|
|
|
return fmt.Errorf("'%s' value should be of type time", arg.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unknown data type. Pls implement")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort the arguments by their name
|
|
|
|
func (args *Arguments) Sort() {
|
|
|
|
s := *args
|
|
|
|
if len(*args) <= 1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sort.Slice(s, func(i, j int) bool {
|
|
|
|
return s[i].Name <= s[j].Name
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// some fields are already defined
|
2021-11-09 12:17:16 +00:00
|
|
|
// TODO we need to define ABI here to use names also, we have a name service
|
|
|
|
|
|
|
|
const RPC_DESTINATION_PORT = "D" // mandatory,uint64, used for ID of type uint64
|
|
|
|
const RPC_SOURCE_PORT = "S" // mandatory,uint64, used for ID
|
|
|
|
const RPC_VALUE_TRANSFER = "V" //uint64, this is representation and is only readable, value is never transferred
|
|
|
|
const RPC_COMMENT = "C" //optional,string, used for display MSG to user
|
|
|
|
const RPC_EXPIRY = "E" //optional,time used for Expiry for this service call
|
|
|
|
const RPC_REPLYBACK_ADDRESS = "R" //this is mandatory this is an address,otherwise how will otherside respond
|
|
|
|
//RPC will include own address so as the other enc can respond
|
|
|
|
|
|
|
|
const RPC_NEEDS_REPLYBACK_ADDRESS = "N" //optional, uint64
|
2021-11-08 16:39:17 +00:00
|
|
|
|
|
|
|
type argument_raw struct {
|
|
|
|
Name string `json:"name"` // string name must be atleast 1 byte
|
|
|
|
DataType DataType `json:"datatype"` // Type must one of the known data types
|
|
|
|
Value json.RawMessage `json:"value"` // delay parsing until we know the value should be as per type
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Argument) UnmarshalJSON(b []byte) (err error) {
|
|
|
|
var raw argument_raw
|
|
|
|
if err = json.Unmarshal(b, &raw); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
a.Name = raw.Name
|
|
|
|
a.DataType = raw.DataType
|
|
|
|
switch raw.DataType {
|
|
|
|
case DataString:
|
|
|
|
var x string
|
|
|
|
if err = json.Unmarshal(raw.Value, &x); err == nil {
|
|
|
|
a.Value = x
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case DataInt64:
|
|
|
|
var x int64
|
|
|
|
if err = json.Unmarshal(raw.Value, &x); err == nil {
|
|
|
|
a.Value = x
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case DataUint64:
|
|
|
|
var x uint64
|
|
|
|
if err = json.Unmarshal(raw.Value, &x); err == nil {
|
|
|
|
a.Value = x
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case DataFloat64:
|
|
|
|
var x float64
|
|
|
|
if err = json.Unmarshal(raw.Value, &x); err == nil {
|
|
|
|
a.Value = x
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case DataHash:
|
|
|
|
var x crypto.Hash
|
|
|
|
if err = json.Unmarshal(raw.Value, &x); err == nil {
|
|
|
|
a.Value = x
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case DataAddress:
|
|
|
|
var x Address
|
|
|
|
if err = json.Unmarshal(raw.Value, &x); err == nil {
|
|
|
|
a.Value = x
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case DataTime:
|
|
|
|
var x time.Time
|
|
|
|
if err = json.Unmarshal(raw.Value, &x); err == nil {
|
|
|
|
a.Value = x
|
|
|
|
return
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unknown data type %s", raw.DataType)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|