2021-11-09 12:17:16 +00:00

430 lines
11 KiB
Go

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:
return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value)
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:
localmap[arg.Name+string(arg.DataType)] = v.PublicKey.EncodeCompressed()
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
// 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
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
}