Add network incentivisation service (#1452)

This commit is contained in:
Andrea Maria Piana 2019-04-29 14:05:49 +02:00 committed by GitHub
parent 1a4fe50971
commit 354e6981ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 6121 additions and 1 deletions

17
Gopkg.lock generated
View File

@ -766,6 +766,14 @@
pruneopts = "NUT"
revision = "ed27b6fd65218132ee50cd95f38474a3d8a2cd12"
[[projects]]
branch = "master"
digest = "1:df2ffac3fa01a4eff2983f6993e3e56db39101a2c030f6bbfc99209b8a15ca9b"
name = "github.com/russolsen/transit"
packages = ["."]
pruneopts = "NUT"
revision = "0794b4c4505af6b9382d56e296a6796be0cf4d76"
[[projects]]
digest = "1:8737b20e873c7cad069dfafd6382659b4cdf9e80359cdc159b8ec464893c932e"
name = "github.com/satori/go.uuid"
@ -773,6 +781,14 @@
pruneopts = "NUT"
revision = "36e9d2ebbde5e3f13ab2e25625fd453271d6522e"
[[projects]]
digest = "1:11afe1ad32273aeda2ff5e80317638da5006aff881263b995038900a89494eff"
name = "github.com/shopspring/decimal"
packages = ["."]
pruneopts = "NUT"
revision = "cd690d0c9e2447b1ef2a129a6b7b49077da89b8e"
version = "1.1.0"
[[projects]]
digest = "1:96ea40e8fab1400b769ff9669dd7f7de613c3a1943dd7f451c4ccbb05115bcaa"
name = "github.com/spaolacci/murmur3"
@ -1100,6 +1116,7 @@
"github.com/multiformats/go-multiaddr",
"github.com/mutecomm/go-sqlcipher",
"github.com/pborman/uuid",
"github.com/russolsen/transit",
"github.com/status-im/doubleratchet",
"github.com/status-im/go-ethereum/common/hexutil",
"github.com/status-im/migrate",

View File

@ -1 +1 @@
0.24.0-beta.1
0.24.0-beta.2

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
package node
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -8,10 +9,12 @@ import (
"path/filepath"
"time"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
@ -21,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/status-im/status-go/mailserver"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/incentivisation"
"github.com/status-im/status-go/services/peer"
"github.com/status-im/status-go/services/personal"
"github.com/status-im/status-go/services/shhext"
@ -40,6 +44,7 @@ var (
ErrPersonalServiceRegistrationFailure = errors.New("failed to register the personal api service")
ErrStatusServiceRegistrationFailure = errors.New("failed to register the Status service")
ErrPeerServiceRegistrationFailure = errors.New("failed to register the Peer service")
ErrIncentivisationServiceRegistrationFailure = errors.New("failed to register the Incentivisation service")
)
// All general log messages in this package should be routed through this logger.
@ -98,6 +103,11 @@ func MakeNode(config *params.NodeConfig, db *leveldb.DB) (*node.Node, error) {
return nil, fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err)
}
// start incentivisation service
if err := activateIncentivisationService(stack, config); err != nil {
return nil, fmt.Errorf("%v: %v", ErrIncentivisationServiceRegistrationFailure, err)
}
// start status service.
if err := activateStatusService(stack, config); err != nil {
return nil, fmt.Errorf("%v: %v", ErrStatusServiceRegistrationFailure, err)
@ -333,6 +343,49 @@ func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb
})
}
// activateIncentivisationService configures Whisper and adds it to the given node.
func activateIncentivisationService(stack *node.Node, config *params.NodeConfig) (err error) {
if !config.WhisperConfig.Enabled {
logger.Info("SHH protocol is disabled")
return nil
}
if !config.IncentivisationConfig.Enabled {
logger.Info("Incentivisation is disabled")
return nil
}
logger.Info("activating incentivisation")
// TODO(dshulyak) add a config option to enable it by default, but disable if app is started from statusd
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
var w *whisper.Whisper
if err := ctx.Service(&w); err != nil {
return nil, err
}
incentivisationConfig := &incentivisation.ServiceConfig{
ContractAddress: config.IncentivisationConfig.ContractAddress,
RPCEndpoint: config.IncentivisationConfig.RPCEndpoint,
IP: config.IncentivisationConfig.IP,
Port: config.IncentivisationConfig.Port,
}
privateKey, err := crypto.HexToECDSA(config.NodeKey)
if err != nil {
return nil, err
}
client, err := ethclient.DialContext(context.TODO(), incentivisationConfig.RPCEndpoint)
if err != nil {
return nil, err
}
contract, err := incentivisation.NewContract(gethcommon.HexToAddress(incentivisationConfig.ContractAddress), client, client)
if err != nil {
return nil, err
}
return incentivisation.New(privateKey, whisper.NewPublicWhisperAPI(w), incentivisationConfig, contract), nil
})
}
// parseNodes creates list of enode.Node out of enode strings.
func parseNodes(enodes []string) []*enode.Node {
var nodes []*enode.Node

View File

@ -93,6 +93,34 @@ func (c *WhisperConfig) String() string {
return string(data)
}
// IncentivisationConfig holds incentivisation-related configuration
type IncentivisationConfig struct {
// Enabled flag specifies whether protocol is enabled
Enabled bool `validate:"required"`
// Endpoint for the RPC calls
RPCEndpoint string `validate:"required"`
// Contract address
ContractAddress string `validate:"required"`
// IP address that is used
IP string `validate:"required"`
// Port
Port uint16 `validate:"required"`
}
// String dumps config object as nicely indented JSON
func (c *IncentivisationConfig) String() string {
data, _ := json.MarshalIndent(c, "", " ") // nolint: gas
return string(data)
}
// Validate validates the IncentivisationConfig struct and returns an error if inconsistent values are found
func (c *IncentivisationConfig) Validate(validate *validator.Validate) error {
if err := validate.Struct(c); err != nil {
return err
}
return nil
}
// ----------
// SwarmConfig
// ----------
@ -295,6 +323,9 @@ type NodeConfig struct {
// WhisperConfig extra configuration for SHH
WhisperConfig WhisperConfig `json:"WhisperConfig," validate:"structonly"`
// IncentivisationConfig extra configuration for incentivisation service
IncentivisationConfig IncentivisationConfig `json:"IncentivisationConfig," validate:"structonly"`
// ShhextConfig keeps configuration for service running under shhext namespace.
ShhextConfig ShhextConfig `json:"ShhextConfig," validate:"structonly"`
@ -631,6 +662,11 @@ func (c *NodeConfig) validateChildStructs(validate *validator.Validate) error {
if err := c.ShhextConfig.Validate(validate); err != nil {
return err
}
if c.IncentivisationConfig.Enabled {
if err := c.IncentivisationConfig.Validate(validate); err != nil {
return err
}
}
return nil
}

View File

@ -45,6 +45,8 @@ func TestNewNodeConfigWithDefaults(t *testing.T) {
// assert peers limits
assert.Contains(t, c.RequireTopics, params.WhisperDiscv5Topic)
assert.Contains(t, c.RequireTopics, discv5.Topic(params.LesTopic(int(c.NetworkID))))
// assert incentivisation
assert.Equal(t, false, c.IncentivisationConfig.Enabled)
// assert other
assert.Equal(t, false, c.HTTPEnabled)
assert.Equal(t, false, c.IPCEnabled)
@ -386,6 +388,103 @@ func TestNodeConfigValidate(t *testing.T) {
}`,
Error: "field BackupDisabledDataDir is required if PFSEnabled is true",
},
{
Name: "Valid JSON config with incentivisation",
Config: `{
"NetworkId": 1,
"DataDir": "/tmp/data",
"BackupDisabledDataDir": "/tmp/data",
"KeyStoreDir": "/tmp/data",
"NoDiscovery": true,
"IncentivisationConfig": {
"Enabled": true,
"IP": "127.0.0.1",
"Port": 300,
"RPCEndpoint": "http://test.com",
"ContractAddress": "0xfffff"
}
}`,
},
{
Name: "Missing RPCEndpoint",
Config: `{
"NetworkId": 1,
"DataDir": "/tmp/data",
"BackupDisabledDataDir": "/tmp/data",
"KeyStoreDir": "/tmp/data",
"NoDiscovery": true,
"IncentivisationConfig": {
"Enabled": true,
"IP": "127.0.0.1",
"Port": 300,
"ContractAddress": "0xfffff"
}
}`,
FieldErrors: map[string]string{
"RPCEndpoint": "required",
},
Error: "RPCEndpoint is required if incentivisation is enabled",
},
{
Name: "Missing contract address",
Config: `{
"NetworkId": 1,
"DataDir": "/tmp/data",
"BackupDisabledDataDir": "/tmp/data",
"KeyStoreDir": "/tmp/data",
"NoDiscovery": true,
"IncentivisationConfig": {
"Enabled": true,
"IP": "127.0.0.1",
"Port": 300,
"RPCEndpoint": "http://test.com"
}
}`,
FieldErrors: map[string]string{
"ContractAddress": "required",
},
Error: "field ContractAddress is required if incentivisation is enabled",
},
{
Name: "Missing ip address",
Config: `{
"NetworkId": 1,
"DataDir": "/tmp/data",
"BackupDisabledDataDir": "/tmp/data",
"KeyStoreDir": "/tmp/data",
"NoDiscovery": true,
"IncentivisationConfig": {
"Enabled": true,
"Port": 300,
"RPCEndpoint": "http://test.com",
"ContractAddress": "0xfffff"
}
}`,
FieldErrors: map[string]string{
"IP": "required",
},
Error: "field IP is required if incentivisation is enabled",
},
{
Name: "Missing port",
Config: `{
"NetworkId": 1,
"DataDir": "/tmp/data",
"BackupDisabledDataDir": "/tmp/data",
"KeyStoreDir": "/tmp/data",
"NoDiscovery": true,
"IncentivisationConfig": {
"Enabled": true,
"IP": "127.0.0.1",
"RPCEndpoint": "http://test.com",
"ContractAddress": "0xfffff"
}
}`,
FieldErrors: map[string]string{
"Port": "required",
},
Error: "field Port is required if incentivisation is enabled",
},
}
for _, tc := range testCases {
@ -401,6 +500,7 @@ func TestNodeConfigValidate(t *testing.T) {
if tc.Error == "" {
require.NoError(t, err)
} else {
fmt.Println(tc.Error)
require.Contains(t, err.Error(), tc.Error)
}
case nil:

View File

@ -0,0 +1,19 @@
package incentivisation
import (
"context"
)
// PublicAPI represents a set of APIs from the `web3.peer` namespace.
type PublicAPI struct {
s *Service
}
// NewAPI creates an instance of the peer API.
func NewAPI(s *Service) *PublicAPI {
return &PublicAPI{s: s}
}
func (api *PublicAPI) Registered(context context.Context) error {
return nil
}

View File

@ -0,0 +1,78 @@
package incentivisation
import (
"context"
"errors"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/status-im/status-go/mailserver/registry"
"math/big"
"time"
)
type Contract interface {
Vote(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*types.Transaction, error)
GetCurrentSession(opts *bind.CallOpts) (*big.Int, error)
Registered(opts *bind.CallOpts, publicKey []byte) (bool, error)
RegisterNode(opts *bind.TransactOpts, publicKey []byte, ip uint32, port uint16) (*types.Transaction, error)
ActiveNodeCount(opts *bind.CallOpts) (*big.Int, error)
InactiveNodeCount(opts *bind.CallOpts) (*big.Int, error)
GetNode(opts *bind.CallOpts, index *big.Int) ([]byte, uint32, uint16, uint32, uint32, error)
GetInactiveNode(opts *bind.CallOpts, index *big.Int) ([]byte, uint32, uint16, uint32, uint32, error)
VoteSync(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*types.Transaction, error)
}
type ContractImpl struct {
registry.NodesV2
client *ethclient.Client
}
// VoteSync votes on the contract and wait until the transaction has been accepted, returns an error otherwise
func (c *ContractImpl) VoteSync(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*types.Transaction, error) {
tx, err := c.Vote(opts, joinNodes, removeNodes)
if err != nil {
return nil, err
}
for {
receipt, _ := c.client.TransactionReceipt(context.TODO(), tx.Hash())
if receipt != nil {
if receipt.Status == 0 {
return nil, errors.New("Invalid receipt")
}
break
}
time.Sleep(500 * time.Millisecond)
}
return tx, nil
}
// NewContract creates a new instance of Contract, bound to a specific deployed contract.
func NewContract(address gethcommon.Address, backend bind.ContractBackend, client *ethclient.Client) (Contract, error) {
contract := &ContractImpl{}
contract.client = client
caller, err := registry.NewNodesV2Caller(address, backend)
if err != nil {
return nil, err
}
contract.NodesV2Caller = *caller
transactor, err := registry.NewNodesV2Transactor(address, backend)
if err != nil {
return nil, err
}
contract.NodesV2Transactor = *transactor
filterer, err := registry.NewNodesV2Filterer(address, backend)
if err != nil {
return nil, err
}
contract.NodesV2Filterer = *filterer
return contract, nil
}

View File

@ -0,0 +1,87 @@
package incentivisation
import (
"bytes"
"io"
"reflect"
"time"
"github.com/russolsen/transit"
)
type StatusMessageContent struct {
ChatID string
Text string
}
type StatusMessage struct {
Text string
ContentT string
MessageT string
Clock int64
Timestamp int64
Content StatusMessageContent
}
// CreateTextStatusMessage creates a StatusMessage.
func CreateTextStatusMessage(text string, chatID string) StatusMessage {
ts := time.Now().Unix() * 1000
return StatusMessage{
Text: text,
ContentT: "text/plain",
MessageT: "public-group-user-message",
Clock: ts * 100,
Timestamp: ts,
Content: StatusMessageContent{ChatID: chatID, Text: text},
}
}
func EncodeMessage(content string, chatID string) ([]byte, error) {
value := CreateTextStatusMessage(content, chatID)
var buf bytes.Buffer
encoder := NewMessageEncoder(&buf)
if err := encoder.Encode(value); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// NewMessageEncoder returns a new Transit encoder
// that can encode StatusMessage values.
// More about Transit: https://github.com/cognitect/transit-format
func NewMessageEncoder(w io.Writer) *transit.Encoder {
encoder := transit.NewEncoder(w, false)
encoder.AddHandler(statusMessageType, defaultStatusMessageValueEncoder)
return encoder
}
var (
statusMessageType = reflect.TypeOf(StatusMessage{})
defaultStatusMessageValueEncoder = &statusMessageValueEncoder{}
)
type statusMessageValueEncoder struct{}
func (statusMessageValueEncoder) IsStringable(reflect.Value) bool {
return false
}
func (statusMessageValueEncoder) Encode(e transit.Encoder, value reflect.Value, asString bool) error {
message := value.Interface().(StatusMessage)
taggedValue := transit.TaggedValue{
Tag: "c4",
Value: []interface{}{
message.Text,
message.ContentT,
transit.Keyword(message.MessageT),
message.Clock,
message.Timestamp,
map[interface{}]interface{}{
transit.Keyword("chat-id"): message.Content.ChatID,
transit.Keyword("text"): message.Content.Text,
},
},
}
return e.EncodeInterface(taggedValue, false)
}

View File

@ -0,0 +1,512 @@
package incentivisation
import (
"bytes"
"context"
"crypto/ecdsa"
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"math/big"
"net"
"sort"
whisper "github.com/status-im/whisper/whisperv6"
"time"
)
const (
gasLimit = 1001000
pingIntervalAllowance = 240
tickerInterval = 30
defaultTopic = "status-incentivisation-topic"
)
type Enode struct {
PublicKey []byte
IP net.IP
Port uint16
JoiningSession uint32
ActiveSession uint32
Active bool
}
func formatEnodeURL(publicKey string, ip string, port uint16) string {
return fmt.Sprintf("enode://%s:%s:%d", publicKey, ip, port)
}
func (n *Enode) toEnodeURL() string {
return formatEnodeURL(n.PublicKeyString(), n.IP.String(), n.Port)
}
func (n *Enode) PublicKeyString() string {
return hex.EncodeToString(n.PublicKey)
}
type Whisper interface {
Post(ctx context.Context, req whisper.NewMessage) (hexutil.Bytes, error)
NewMessageFilter(req whisper.Criteria) (string, error)
AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error)
DeleteKeyPair(ctx context.Context, key string) (bool, error)
GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error)
GetFilterMessages(id string) ([]*whisper.Message, error)
}
type ServiceConfig struct {
RPCEndpoint string
ContractAddress string
IP string
Port uint16
}
type Service struct {
w Whisper
whisperKeyID string
whisperSymKeyID string
whisperFilterID string
nodes map[string]*Enode
ticker *time.Ticker
quit chan struct{}
config *ServiceConfig
contract Contract
privateKey *ecdsa.PrivateKey
log log.Logger
// The first round we will not be voting, as we might have incomplete data
initialSession uint64
// The current session
currentSession uint64
whisperPings map[string][]uint32
}
// New returns a new incentivization Service
func New(prv *ecdsa.PrivateKey, w Whisper, config *ServiceConfig, contract Contract) *Service {
logger := log.New("package", "status-go/incentivisation/service")
return &Service{
w: w,
config: config,
privateKey: prv,
log: logger,
contract: contract,
nodes: make(map[string]*Enode),
whisperPings: make(map[string][]uint32),
}
}
// Protocols returns a new protocols list. In this case, there are none.
func (s *Service) Protocols() []p2p.Protocol {
return []p2p.Protocol{}
}
// APIs returns a list of new APIs.
func (s *Service) APIs() []rpc.API {
apis := []rpc.API{
{
Namespace: "incentivisation",
Version: "1.0",
Service: NewAPI(s),
Public: true,
},
}
return apis
}
// checkRegistered checks that a node is registered with the contract
func (s *Service) checkRegistered() error {
registered, err := s.registered()
if err != nil {
s.log.Error("error querying contract", "registered", err)
return err
}
if registered {
s.log.Debug("Already registered")
return nil
}
_, err = s.register()
if err != nil {
s.log.Error("error querying contract", "registered", err)
return err
}
return nil
}
// ensureSession checks if we are in a new session and updates the session if so
func (s *Service) ensureSession() (bool, error) {
session, err := s.GetCurrentSession()
if err != nil {
s.log.Error("failed to get current session", "err", err)
return false, err
}
if session != s.currentSession {
s.currentSession = session
return true, nil
}
return false, nil
}
// checkPings checks we have received the expected pings since it was last called
func (s *Service) checkPings() map[string]bool {
result := make(map[string]bool)
now := time.Now().Unix()
s.log.Debug("checking votes", "votes", s.whisperPings)
for enodeID, timestamps := range s.whisperPings {
result[enodeID] = true
if len(timestamps) < 2 {
s.log.Debug("Node failed check", "enodeID", enodeID)
result[enodeID] = false
continue
}
sort.Slice(timestamps, func(i, j int) bool { return timestamps[i] < timestamps[j] })
timestamps = append(timestamps, uint32(now))
for i := 1; i < len(timestamps); i++ {
if timestamps[i]-timestamps[i-1] > pingIntervalAllowance {
result[enodeID] = false
}
}
if result[enodeID] {
s.log.Debug("Node passed check", "enodeID", enodeID)
} else {
s.log.Debug("Node failed check", "enodeID", enodeID)
}
}
s.log.Debug("voting result", "result", result)
return result
}
// perform is the main loop, it posts a ping, registers with the contract, check the pings and votes
func (s *Service) perform() error {
hash, err := s.postPing()
if err != nil {
s.log.Error("Could not post ping", "err", err)
return err
}
s.log.Debug("Posted ping", "hash", hash)
err = s.FetchEnodes()
if err != nil {
return err
}
err = s.fetchMessages()
if err != nil {
return err
}
err = s.checkRegistered()
if err != nil {
s.log.Error("Could not check if node is registered with the contract", "err", err)
return err
}
// This actually updates the session
newSession, err := s.ensureSession()
if err != nil {
s.log.Error("Could not check session", "err", err)
return err
}
if !newSession {
s.log.Debug("Not a new session idling")
return nil
}
result := s.checkPings()
err = s.vote(result)
if err != nil {
s.log.Error("Could not vote", "err", err)
return err
}
// Reset whisper pings
s.whisperPings = make(map[string][]uint32)
return nil
}
// vote reports to the contract the decisions of the votes
func (s *Service) vote(result map[string]bool) error {
var behavingNodes []gethcommon.Address
var misbehavingNodes []gethcommon.Address
auth := s.auth()
for enodeIDString, passedCheck := range result {
enodeID, err := hex.DecodeString(enodeIDString)
if err != nil {
return err
}
if passedCheck {
behavingNodes = append(behavingNodes, publicKeyBytesToAddress(enodeID))
} else {
misbehavingNodes = append(misbehavingNodes, publicKeyBytesToAddress(enodeID))
}
}
_, err := s.contract.VoteSync(&bind.TransactOpts{
GasLimit: gasLimit,
From: auth.From,
Signer: auth.Signer,
}, behavingNodes, misbehavingNodes)
return err
}
func (s *Service) startTicker() {
s.ticker = time.NewTicker(tickerInterval * time.Second)
s.quit = make(chan struct{})
go func() {
for {
select {
case <-s.ticker.C:
err := s.perform()
if err != nil {
s.log.Error("could not execute tick", "err", err)
}
case <-s.quit:
s.ticker.Stop()
return
}
}
}()
}
func (s *Service) Start(server *p2p.Server) error {
s.log.Info("Incentivisation service started", "address", s.addressString(), "publickey", s.publicKeyString())
s.startTicker()
session, err := s.GetCurrentSession()
if err != nil {
return err
}
s.initialSession = session
s.currentSession = session
whisperKeyID, err := s.w.AddPrivateKey(context.TODO(), crypto.FromECDSA(s.privateKey))
if err != nil {
return err
}
s.whisperKeyID = whisperKeyID
whisperSymKeyID, err := s.w.GenerateSymKeyFromPassword(context.TODO(), defaultTopic)
if err != nil {
return err
}
s.whisperSymKeyID = whisperSymKeyID
criteria := whisper.Criteria{
SymKeyID: whisperSymKeyID,
Topics: []whisper.TopicType{toWhisperTopic(defaultTopic)},
}
filterID, err := s.w.NewMessageFilter(criteria)
if err != nil {
s.log.Error("could not create filter", "err", err)
return err
}
s.whisperFilterID = filterID
return nil
}
// Stop is run when a service is stopped.
func (s *Service) Stop() error {
s.log.Info("Incentivisation service stopped")
_, err := s.w.DeleteKeyPair(context.TODO(), s.whisperKeyID)
return err
}
func (s *Service) publicKeyBytes() []byte {
return crypto.FromECDSAPub(&s.privateKey.PublicKey)[1:]
}
func (s *Service) GetCurrentSession() (uint64, error) {
response, err := s.contract.GetCurrentSession(nil)
if err != nil {
s.log.Error("failed to get current session", "err", err)
return 0, err
}
return response.Uint64(), nil
}
func (s *Service) registered() (bool, error) {
response, err := s.contract.Registered(nil, s.publicKeyBytes())
if err != nil {
return false, err
}
return response, nil
}
func (s *Service) register() (bool, error) {
auth := s.auth()
ip, err := ip2Long(s.config.IP)
if err != nil {
return false, err
}
_, err = s.contract.RegisterNode(&bind.TransactOpts{
GasLimit: gasLimit,
From: auth.From,
Signer: auth.Signer,
}, s.publicKeyBytes(), ip, s.config.Port)
if err != nil {
return false, err
}
return true, nil
}
func (s *Service) FetchEnodes() error {
one := big.NewInt(1)
activeNodeCount, err := s.contract.ActiveNodeCount(nil)
if err != nil {
return err
}
s.log.Debug("fetched active node count", "count", activeNodeCount)
for i := big.NewInt(0); i.Cmp(activeNodeCount) < 0; i.Add(i, one) {
publicKey, ip, port, joiningSession, activeSession, err := s.contract.GetNode(nil, i)
if err != nil {
return err
}
node := &Enode{
PublicKey: publicKey,
IP: int2ip(ip),
Port: port,
JoiningSession: joiningSession,
ActiveSession: activeSession,
}
s.log.Debug("adding node", "node", node.toEnodeURL())
if node.PublicKeyString() != s.publicKeyString() {
s.nodes[node.PublicKeyString()] = node
}
}
inactiveNodeCount, err := s.contract.InactiveNodeCount(nil)
if err != nil {
return err
}
s.log.Debug("fetched inactive node count", "count", inactiveNodeCount)
for i := big.NewInt(0); i.Cmp(inactiveNodeCount) < 0; i.Add(i, one) {
publicKey, ip, port, joiningSession, activeSession, err := s.contract.GetInactiveNode(nil, i)
if err != nil {
return err
}
node := &Enode{
PublicKey: publicKey,
IP: int2ip(ip),
Port: port,
JoiningSession: joiningSession,
ActiveSession: activeSession,
}
s.log.Debug("adding node", "node", node.toEnodeURL())
if node.PublicKeyString() != s.publicKeyString() {
s.nodes[node.PublicKeyString()] = node
}
}
return nil
}
func (s *Service) publicKeyString() string {
return hex.EncodeToString(s.publicKeyBytes())
}
func (s *Service) addressString() string {
buf := crypto.Keccak256Hash(s.publicKeyBytes())
address := buf[12:]
return hex.EncodeToString(address)
}
// postPing publishes a whisper message
func (s *Service) postPing() (hexutil.Bytes, error) {
msg := defaultWhisperMessage()
msg.Topic = toWhisperTopic(defaultTopic)
enodeURL := formatEnodeURL(s.publicKeyString(), s.config.IP, s.config.Port)
payload, err := EncodeMessage(enodeURL, defaultTopic)
if err != nil {
return nil, err
}
msg.Payload = payload
msg.Sig = s.whisperKeyID
msg.SymKeyID = s.whisperSymKeyID
return s.w.Post(context.TODO(), msg)
}
// fetchMessages checks for whisper messages
func (s *Service) fetchMessages() error {
messages, err := s.w.GetFilterMessages(s.whisperFilterID)
if err != nil {
return err
}
for i := 0; i < len(messages); i++ {
signature := hex.EncodeToString(messages[i].Sig[1:])
timestamp := messages[i].Timestamp
if s.nodes[signature] != nil {
s.whisperPings[signature] = append(s.whisperPings[signature], timestamp)
}
}
return nil
}
func (s *Service) auth() *bind.TransactOpts {
return bind.NewKeyedTransactor(s.privateKey)
}
func ip2Long(ip string) (uint32, error) {
var long uint32
err := binary.Read(bytes.NewBuffer(net.ParseIP(ip).To4()), binary.BigEndian, &long)
if err != nil {
return 0, err
}
return long, nil
}
func toWhisperTopic(s string) whisper.TopicType {
return whisper.BytesToTopic(crypto.Keccak256([]byte(s)))
}
func defaultWhisperMessage() whisper.NewMessage {
msg := whisper.NewMessage{}
msg.TTL = 10
msg.PowTarget = 0.002
msg.PowTime = 1
return msg
}
func int2ip(nn uint32) net.IP {
ip := make(net.IP, 4)
binary.BigEndian.PutUint32(ip, nn)
return ip
}
func publicKeyBytesToAddress(publicKey []byte) gethcommon.Address {
buf := crypto.Keccak256Hash(publicKey)
address := buf[12:]
return gethcommon.HexToAddress(hex.EncodeToString(address))
}

View File

@ -0,0 +1,205 @@
package incentivisation
import (
"bytes"
"context"
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
whisper "github.com/status-im/whisper/whisperv6"
"github.com/stretchr/testify/suite"
)
var (
nodeOne = []byte{0x01}
nodeTwo = []byte{0x02}
)
type MockContract struct {
currentSession *big.Int
activeNodes [][]byte
inactiveNodes [][]byte
votes []Vote
}
type Vote struct {
joinNodes []gethcommon.Address
removeNodes []gethcommon.Address
}
type MockWhisper struct {
sentMessages []whisper.NewMessage
filterMessages []*whisper.Message
}
func BuildMockContract() *MockContract {
contract := &MockContract{
currentSession: big.NewInt(0),
}
contract.activeNodes = append(contract.activeNodes, nodeOne)
contract.inactiveNodes = append(contract.activeNodes, nodeTwo)
return contract
}
func (c *MockContract) Vote(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*types.Transaction, error) {
return nil, nil
}
func (c *MockContract) VoteSync(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*types.Transaction, error) {
c.votes = append(c.votes, Vote{
joinNodes: joinNodes,
removeNodes: removeNodes,
})
return nil, nil
}
func (c *MockContract) GetCurrentSession(opts *bind.CallOpts) (*big.Int, error) {
return c.currentSession, nil
}
func (c *MockContract) Registered(opts *bind.CallOpts, publicKey []byte) (bool, error) {
for _, e := range c.activeNodes {
if bytes.Equal(publicKey, e) {
return true, nil
}
}
for _, e := range c.inactiveNodes {
if bytes.Equal(publicKey, e) {
return true, nil
}
}
return false, nil
}
func (c *MockContract) RegisterNode(opts *bind.TransactOpts, publicKey []byte, ip uint32, port uint16) (*types.Transaction, error) {
c.inactiveNodes = append(c.inactiveNodes, publicKey)
return nil, nil
}
func (c *MockContract) ActiveNodeCount(opts *bind.CallOpts) (*big.Int, error) {
return big.NewInt(int64(len(c.activeNodes))), nil
}
func (c *MockContract) InactiveNodeCount(opts *bind.CallOpts) (*big.Int, error) {
return big.NewInt(int64(len(c.inactiveNodes))), nil
}
func (c *MockContract) GetNode(opts *bind.CallOpts, index *big.Int) ([]byte, uint32, uint16, uint32, uint32, error) {
return c.activeNodes[index.Int64()], 0, 0, 0, 0, nil
}
func (c *MockContract) GetInactiveNode(opts *bind.CallOpts, index *big.Int) ([]byte, uint32, uint16, uint32, uint32, error) {
return c.inactiveNodes[index.Int64()], 0, 0, 0, 0, nil
}
func (w *MockWhisper) Post(ctx context.Context, req whisper.NewMessage) (hexutil.Bytes, error) {
w.sentMessages = append(w.sentMessages, req)
return nil, nil
}
func (w *MockWhisper) NewMessageFilter(req whisper.Criteria) (string, error) {
return "", nil
}
func (w *MockWhisper) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) {
return "", nil
}
func (w *MockWhisper) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
return true, nil
}
func (w *MockWhisper) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
return "", nil
}
func (w *MockWhisper) GetFilterMessages(id string) ([]*whisper.Message, error) {
return w.filterMessages, nil
}
func TestIncentivisationSuite(t *testing.T) {
suite.Run(t, new(IncentivisationSuite))
}
type IncentivisationSuite struct {
suite.Suite
service *Service
mockWhisper *MockWhisper
mockContract *MockContract
}
func (s *IncentivisationSuite) SetupTest() {
privateKey, err := crypto.GenerateKey()
config := &ServiceConfig{
IP: "192.168.1.1",
}
contract := BuildMockContract()
if err != nil {
panic(err)
}
w := &MockWhisper{}
s.service = New(privateKey, w, config, contract)
s.mockWhisper = w
s.mockContract = contract
}
func (s *IncentivisationSuite) TestStart() {
err := s.service.Start(nil)
s.Require().NoError(err)
}
func (s *IncentivisationSuite) TestPerform() {
err := s.service.Start(nil)
s.Require().NoError(err)
err = s.service.perform()
s.Require().NoError(err)
// It registers with the contract if not registered
registered, err := s.service.registered()
s.Require().NoError(err)
s.Require().Equal(true, registered)
now := time.Now().Unix()
// Add some envelopes
s.mockWhisper.filterMessages = []*whisper.Message{
{
// We strip the first byte when processing
Sig: append(nodeOne, nodeOne[0]),
Timestamp: uint32(now - pingIntervalAllowance),
},
{
Sig: append(nodeOne, nodeOne[0]),
Timestamp: uint32(now - (pingIntervalAllowance * 2)),
},
{
Sig: append(nodeTwo, nodeTwo[0]),
Timestamp: uint32(now - (pingIntervalAllowance * 2)),
},
}
// It publishes a ping on whisper
s.Require().Equal(1, len(s.mockWhisper.sentMessages))
// It should not vote
s.Require().Equal(0, len(s.mockContract.votes))
// We increase the session
s.mockContract.currentSession = s.mockContract.currentSession.Add(s.mockContract.currentSession, big.NewInt(1))
// We perform again
err = s.service.perform()
s.Require().NoError(err)
// it should now vote
s.Require().Equal(1, len(s.mockContract.votes))
// Node one should have been voted up
s.Require().Equal(1, len(s.mockContract.votes[0].joinNodes))
s.Require().Equal(publicKeyBytesToAddress(nodeOne), s.mockContract.votes[0].joinNodes[0])
// Node two should have been voted down
s.Require().Equal(1, len(s.mockContract.votes[0].removeNodes))
s.Require().Equal(publicKeyBytesToAddress(nodeTwo), s.mockContract.votes[0].removeNodes[0])
}

202
vendor/github.com/russolsen/transit/LICENSE generated vendored Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

90
vendor/github.com/russolsen/transit/cmap.go generated vendored Normal file
View File

@ -0,0 +1,90 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
type CMapEntry struct {
Key interface{}
Value interface{}
}
func NewCMapEntry(key, value interface{}) *CMapEntry {
return &CMapEntry{Key: key, Value: value}
}
// CMap is used to hold maps that have composite keys (i.e. #cmap values).
// Since Go arrays and maps cannot be used as map keys, a CMap is represented
// here as a simple array of key/value entrys.
type CMap struct {
Entries []CMapEntry
}
func NewCMap() *CMap {
return &CMap{}
}
// FindBy searches thru the map, calling mf on each key in turn
// and returns the first entry for which mf evaluates to true.
func (cm CMap) FindBy(key interface{}, mf MatchF) *CMapEntry {
for _, entry := range cm.Entries {
if mf(key, entry.Key) {
return &entry
}
}
return nil
}
// Find a given key in the map and return it's corresponding value.
// The search thru the keys is done with ==, so the keys in the map
// must be comparable.
func (cm CMap) Index(keyValue interface{}) interface{} {
entry := cm.FindBy(keyValue, Equals)
if entry == nil {
return nil
}
return entry.Value
}
func (cm CMap) Put(key, value interface{}, mf MatchF) *CMap {
entry := cm.FindBy(key, mf)
if entry != nil {
entry.Value = value
} else {
entry = NewCMapEntry(key, value)
cm.Entries = append(cm.Entries, *entry)
}
return &cm
}
// Append inserts a new key/value pair w/o paying attention to
// duplicate keys.
func (cm *CMap) Append(key, value interface{}) *CMap {
entry := NewCMapEntry(key, value)
cm.Entries = append(cm.Entries, *entry)
return cm
}
// Size returns the number of key/value pairs.
func (cm *CMap) Size() int {
return len(cm.Entries)
}

33
vendor/github.com/russolsen/transit/const.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
const start = "~"
const reserved = "`"
const sub = "^"
const escapeTag = "~~"
const escapeSub = "~^"
const escapeRes = "~`"
const startTag = "~#"
const startSym = "~$"
const startKW = "~:"
const mapAsArray = "^ "

322
vendor/github.com/russolsen/transit/decode.go generated vendored Normal file
View File

@ -0,0 +1,322 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"encoding/json"
"io"
"strings"
)
type Handler func(Decoder, interface{}) (interface{}, error)
type Decoder struct {
jsd *json.Decoder
decoders map[string]Handler
cache *RollingCache
}
// NewDecoder returns a new Decoder, ready to read from r.
func NewDecoder(r io.Reader) *Decoder {
jsd := json.NewDecoder(r)
return NewJsonDecoder(jsd)
}
// NewDecoder returns a new Decoder, ready to read from jsr.
func NewJsonDecoder(jsd *json.Decoder) *Decoder {
jsd.UseNumber()
decoders := make(map[string]Handler)
d := Decoder{jsd: jsd, decoders: decoders, cache: NewRollingCache()}
initHandlers(&d)
return &d
}
func initHandlers(d *Decoder) {
d.AddHandler("_", DecodeNil)
d.AddHandler(":", DecodeKeyword)
d.AddHandler("?", DecodeBoolean)
d.AddHandler("b", DecodeByte)
d.AddHandler("d", DecodeFloat)
d.AddHandler("i", DecodeInteger)
d.AddHandler("n", DecodeBigInteger)
d.AddHandler("f", DecodeDecimal)
d.AddHandler("c", DecodeRune)
d.AddHandler("$", DecodeSymbol)
d.AddHandler("t", DecodeRFC3339)
d.AddHandler("m", DecodeTime)
d.AddHandler("u", DecodeUUID)
d.AddHandler("r", DecodeURI)
d.AddHandler("'", DecodeQuote)
d.AddHandler("z", DecodeSpecialNumber)
d.AddHandler("set", DecodeSet)
d.AddHandler("link", DecodeLink)
d.AddHandler("list", DecodeList)
d.AddHandler("cmap", DecodeCMap)
d.AddHandler("ratio", DecodeRatio)
d.AddHandler("unknown", DecodeIdentity)
}
// AddHandler adds a new handler to the decoder, allowing you to extend the types it can handle.
func (d Decoder) AddHandler(tag string, valueDecoder Handler) {
d.decoders[tag] = valueDecoder
}
func (d Decoder) parseString(s string) (interface{}, error) {
if d.cache.HasKey(s) {
return d.Parse(d.cache.Read(s), false)
} else if !strings.HasPrefix(s, start) {
return s, nil
} else if strings.HasPrefix(s, startTag) {
return TagId(s[2:]), nil
} else if vd := d.decoders[s[1:2]]; vd != nil {
return vd(d, s[2:])
} else if strings.HasPrefix(s, escapeTag) ||
strings.HasPrefix(s, escapeSub) ||
strings.HasPrefix(s, escapeRes) {
return s[1:], nil
} else {
tv := TaggedValue{TagId(s[1:2]), s[2:]}
return d.decoders["unknown"](d, tv)
}
}
func (d Decoder) parseSingleEntryMap(m map[string]interface{}) (interface{}, error) {
// The loop here is just a convenient way to get at the only
// entry in the map.
for k, v := range m {
key, err := d.Parse(k, true)
if err != nil {
return nil, err
}
value, err := d.Parse(v, true)
if err != nil {
return nil, err
}
if tag, isTag := key.(TagId); isTag {
tv := TaggedValue{Tag: tag, Value: value}
valueDecoder := d.DecoderFor(tag)
return valueDecoder(d, tv)
} else {
return map[interface{}]interface{}{key: value}, nil
}
}
return nil, nil // Should never get here
}
func (d Decoder) parseMultiEntryMap(m map[string]interface{}) (interface{}, error) {
var result = make(map[interface{}]interface{})
for k, v := range m {
key, err := d.Parse(k, true)
if err != nil {
return nil, err
}
value, err := d.Parse(v, false)
if err != nil {
return nil, err
}
result[key] = value
}
return result, nil
}
func (d Decoder) parseMap(m map[string]interface{}) (interface{}, error) {
if len(m) != 1 {
return d.parseMultiEntryMap(m)
} else {
return d.parseSingleEntryMap(m)
}
}
func (d Decoder) parseNormalArray(x []interface{}) (interface{}, error) {
var result = make([]interface{}, len(x))
for i, v := range x {
var err error
result[i], err = d.Parse(v, false)
if err != nil {
return nil, err
}
}
return result, nil
}
func (d Decoder) parseCMap(x []interface{}) (interface{}, error) {
var result = NewCMap()
l := len(x)
for i := 1; i < l; i += 2 {
key, err := d.Parse(x[i], true)
if err != nil {
return nil, err
}
value, err := d.Parse(x[i+1], false)
if err != nil {
return nil, err
}
result.Append(key, value)
}
return result, nil
}
func (d Decoder) parseArrayMap(x []interface{}) (interface{}, error) {
result := make(map[interface{}]interface{})
l := len(x)
for i := 1; i < l; i += 2 {
key, err := d.Parse(x[i], true)
if err != nil {
return nil, err
}
value, err := d.Parse(x[i+1], false)
if err != nil {
return nil, err
}
result[key] = value
}
return result, nil
}
func (d Decoder) DecoderFor(tagid TagId) Handler {
key := string(tagid)
handler := d.decoders[key]
if handler == nil {
handler = d.decoders["unknown"]
}
return handler
}
func (d Decoder) parseArray(x []interface{}) (interface{}, error) {
if len(x) == 0 {
return x, nil
}
e0, err := d.Parse(x[0], false)
if err != nil {
return nil, err
}
if e0 == mapAsArray {
return d.parseArrayMap(x)
}
if tagId, isTag := e0.(TagId); isTag {
var value interface{}
if value, err = d.Parse(x[1], false); err != nil {
return nil, err
}
tv := TaggedValue{Tag: tagId, Value: value}
valueDecoder := d.DecoderFor(tagId)
return valueDecoder(d, tv)
}
return d.parseNormalArray(x)
}
func (d Decoder) parseNumber(x json.Number) (interface{}, error) {
var s = x.String()
var err error
var result interface{}
if strings.ContainsAny(s, ".Ee") {
result, err = x.Float64()
} else {
result, err = x.Int64()
}
return result, err
}
func (d Decoder) Parse(x interface{}, asKey bool) (interface{}, error) {
switch v := x.(type) {
default:
return nil, &TransitError{Message: "Unexpected type"}
case nil:
return v, nil
case bool:
return v, nil
case json.Number:
return d.parseNumber(v)
case string:
result, err := d.parseString(v)
if err == nil && d.cache.IsCacheable(v, asKey) {
d.cache.Write(v)
}
return result, err
case map[string]interface{}:
return d.parseMap(v)
case []interface{}:
return d.parseArray(v)
}
}
// Decode decodes the next Transit value from the stream.
func (d Decoder) Decode() (interface{}, error) {
var jsonObject interface{}
var err = d.jsd.Decode(&jsonObject)
if err != nil {
return nil, err
} else {
return d.Parse(jsonObject, false)
}
}
// DecodeFromString is a handly function that decodes Transit data held in a string.
func DecodeFromString(s string) (interface{}, error) {
reader := strings.NewReader(s)
decoder := NewDecoder(reader)
return decoder.Decode()
}

152
vendor/github.com/russolsen/transit/emitter.go generated vendored Normal file
View File

@ -0,0 +1,152 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"encoding/json"
"fmt"
"io"
"strings"
)
type DataEmitter interface {
Emit(s string) error
EmitString(s string, cacheable bool) error
EmitTag(s string) error
EmitInt(i int64, asKey bool) error
EmitFloat(f float64, asKey bool) error
EmitNil(asKey bool) error
EmitBool(bool, asKey bool) error
EmitStartArray() error
EmitArraySeparator() error
EmitEndArray() error
EmitStartMap() error
EmitMapSeparator() error
EmitKeySeparator() error
EmitEndMap() error
}
type JsonEmitter struct {
writer io.Writer
cache Cache
}
func NewJsonEmitter(w io.Writer, cache Cache) *JsonEmitter {
return &JsonEmitter{writer: w, cache: cache}
}
// Emit the string unaltered and without quotes. This is the lowest level emitter.
func (je JsonEmitter) Emit(s string) error {
_, err := je.writer.Write([]byte(s))
return err
}
// EmitBase emits the basic value supplied, encoding it as JSON.
func (je JsonEmitter) EmitBase(x interface{}) error {
bytes, err := json.Marshal(x)
if err == nil {
_, err = je.writer.Write(bytes)
}
return err
}
// EmitsTag emits a transit #tag. The string supplied should not include the '#'.
func (je JsonEmitter) EmitTag(s string) error {
return je.EmitString("~#"+s, true)
}
func (je JsonEmitter) EmitString(s string, cacheable bool) error {
if je.cache.IsCacheable(s, cacheable) {
s = je.cache.Write(s)
}
return je.EmitBase(s)
}
const MaxJsonInt = 1<<53 - 1
func (je JsonEmitter) EmitInt(i int64, asKey bool) error {
if asKey || (i > MaxJsonInt) {
return je.EmitString(fmt.Sprintf("~i%d", i), asKey)
}
return je.EmitBase(i)
}
func (je JsonEmitter) EmitNil(asKey bool) error {
if asKey {
return je.EmitString("~_", false)
} else {
return je.EmitBase(nil)
}
}
func (je JsonEmitter) EmitFloat(f float64, asKey bool) error {
if asKey {
return je.EmitString(fmt.Sprintf("~d%g", f), asKey)
} else {
s := fmt.Sprintf("%g", f)
if !strings.ContainsAny(s, ".eE") {
s = s + ".0" // Horible hack!
}
return je.Emit(s)
}
}
func (je JsonEmitter) EmitStartArray() error {
return je.Emit("[")
}
func (je JsonEmitter) EmitEndArray() error {
return je.Emit("]")
}
func (je JsonEmitter) EmitArraySeparator() error {
return je.Emit(",")
}
func (je JsonEmitter) EmitStartMap() error {
return je.Emit("{")
}
func (je JsonEmitter) EmitEndMap() error {
return je.Emit("}")
}
func (je JsonEmitter) EmitMapSeparator() error {
return je.Emit(",")
}
func (je JsonEmitter) EmitKeySeparator() error {
return je.Emit(":")
}
func (je JsonEmitter) EmitBool(x bool, asKey bool) error {
if asKey {
if x {
return je.EmitString("~?t", false)
} else {
return je.EmitString("~?f", false)
}
} else {
return je.EmitBase(x)
}
}

212
vendor/github.com/russolsen/transit/encode.go generated vendored Normal file
View File

@ -0,0 +1,212 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"bytes"
"container/list"
"github.com/pborman/uuid"
"github.com/shopspring/decimal"
"io"
"math/big"
"net/url"
"reflect"
"time"
)
type Encoder struct {
emitter DataEmitter
valueEncoders map[interface{}]ValueEncoder
}
var goListType = reflect.TypeOf(list.New())
var keywordType = reflect.TypeOf(Keyword(""))
var symbolType = reflect.TypeOf(Symbol(""))
var cmapType = reflect.TypeOf(NewCMap())
var aUrl, _ = url.Parse("http://foo.com")
var urlType = reflect.TypeOf(aUrl)
var turiType = reflect.TypeOf(NewTUri("http://example.com"))
var setType = reflect.TypeOf(Set{})
var timeType = reflect.TypeOf(time.Now())
var bigRatType = reflect.TypeOf(*big.NewRat(int64(1), int64(2)))
var bigIntType = reflect.TypeOf(*big.NewInt(int64(1)))
var bigFloatType = reflect.TypeOf(*big.NewFloat(float64(1.)))
var decimalType = reflect.TypeOf(decimal.NewFromFloat(3))
var uuidType = reflect.TypeOf(uuid.NewRandom())
var linkType = reflect.TypeOf(*NewLink())
var taggedValueType = reflect.TypeOf(TaggedValue{TagId("#foo"), 1})
var runeType = reflect.TypeOf('x')
var nilValue = reflect.ValueOf(nil)
var nilEncoder = NewNilEncoder()
// NewEncoder creates a new encoder set to writ to the stream supplied.
// The verbose parameter controls transit's verbose vs non-verbose mode.
// Generally for production you want verbose = false.
func NewEncoder(w io.Writer, verbose bool) *Encoder {
valueEncoders := make(map[interface{}]ValueEncoder)
var cache Cache
if verbose {
cache = NewNoopCache()
} else {
cache = NewRollingCache()
}
emitter := NewJsonEmitter(w, cache)
e := Encoder{emitter: emitter, valueEncoders: valueEncoders}
e.addHandler(reflect.String, NewStringEncoder())
e.addHandler(reflect.Bool, NewBoolEncoder())
e.addHandler(reflect.Ptr, NewPointerEncoder())
floatEncoder := NewFloatEncoder()
e.addHandler(reflect.Float32, floatEncoder)
e.addHandler(reflect.Float64, floatEncoder)
decimalEncoder := NewDecimalEncoder()
e.addHandler(decimalType, decimalEncoder)
intEncoder := NewIntEncoder()
e.addHandler(reflect.Int, intEncoder)
e.addHandler(reflect.Int8, intEncoder)
e.addHandler(reflect.Int16, intEncoder)
e.addHandler(reflect.Int32, intEncoder)
e.addHandler(reflect.Int64, intEncoder)
uintEncoder := NewUintEncoder()
e.addHandler(reflect.Uint, uintEncoder)
e.addHandler(reflect.Uint8, uintEncoder)
e.addHandler(reflect.Uint16, uintEncoder)
e.addHandler(reflect.Uint32, uintEncoder)
e.addHandler(reflect.Uint64, uintEncoder)
arrayEncoder := NewArrayEncoder()
e.addHandler(reflect.Array, arrayEncoder)
e.addHandler(reflect.Slice, arrayEncoder)
e.addHandler(reflect.Map, NewMapEncoder(verbose))
e.addHandler(runeType, NewRuneEncoder())
e.addHandler(timeType, NewTimeEncoder())
e.addHandler(uuidType, NewUuidEncoder())
e.addHandler(bigIntType, NewBigIntEncoder())
e.addHandler(bigRatType, NewBigRatEncoder())
e.addHandler(bigFloatType, NewBigFloatEncoder())
e.addHandler(goListType, NewListEncoder())
e.addHandler(symbolType, NewSymbolEncoder())
e.addHandler(keywordType, NewKeywordEncoder())
e.addHandler(cmapType, NewCMapEncoder())
e.addHandler(setType, NewSetEncoder())
e.addHandler(urlType, NewUrlEncoder())
e.addHandler(turiType, NewTUriEncoder())
e.addHandler(linkType, NewLinkEncoder())
e.addHandler(taggedValueType, NewTaggedValueEncoder())
return &e
}
// AddHandler adds a new handler to the table used by this encoder
// for encoding values. The t value should be an instance
// of reflect.Type and the c value should be an encoder for that type.
func (e Encoder) AddHandler(t reflect.Type, c ValueEncoder) {
e.addHandler(t, c)
}
// addHandler adds a new handler to the table, but the untyped first
// parameter lets you enter either reflect.Type or reflect.Kind values.
// Used internally.
func (e Encoder) addHandler(t interface{}, c ValueEncoder) {
e.valueEncoders[t] = c
}
// ValueEncoderFor finds the encoder for the given value.
func (e Encoder) ValueEncoderFor(v reflect.Value) ValueEncoder {
// Nil is a special case since it doesn't really work
// very well with the reflect package.
if v == nilValue {
return nilEncoder
}
// Look for an encoder by the specific type.
typeEncoder := e.valueEncoders[v.Type()]
if typeEncoder != nil {
return typeEncoder
}
// If we can't find a type encoder, try finding one
// by type. This is will catch values of know kinds,
// say int64 or string which have a different specific
// type.
kindEncoder := e.valueEncoders[v.Kind()]
if kindEncoder != nil {
return kindEncoder
}
// No encoder, for this type, return the error encoder.
return NewErrorEncoder()
}
// Given a Value, encode it.
func (e Encoder) EncodeValue(v reflect.Value, asKey bool) error {
valueEncoder := e.ValueEncoderFor(v)
return valueEncoder.Encode(e, v, asKey)
}
// Given a raw interface, encode it.
func (e Encoder) EncodeInterface(x interface{}, asKey bool) error {
v := reflect.ValueOf(x)
return e.EncodeValue(v, asKey)
}
// Encode a value at the top level.
func (e Encoder) Encode(x interface{}) error {
v := reflect.ValueOf(x)
valueEncoder := e.ValueEncoderFor(v)
if valueEncoder.IsStringable(v) {
x = TaggedValue{TagId("'"), x}
}
return e.EncodeInterface(x, false)
}
// Encode the given value to a string.
func EncodeToString(x interface{}, verbose bool) (string, error) {
var buf bytes.Buffer
var encoder = NewEncoder(&buf, verbose)
err := encoder.Encode(x)
if err != nil {
return "", err
}
return buf.String(), nil
}

32
vendor/github.com/russolsen/transit/error.go generated vendored Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
type TransitError struct {
Message string // Describe the error.
Source interface{} // The value that cause the problem.
}
func NewTransitError(msg string, v interface{}) *TransitError {
return &TransitError{Message: msg, Source: v}
}
func (e *TransitError) Error() string {
return e.Message
}

31
vendor/github.com/russolsen/transit/link.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
type Link struct {
Href *TUri
Rel string
Name string
Prompt string
Render string
}
func NewLink() *Link {
return &Link{}
}

34
vendor/github.com/russolsen/transit/noop_cache.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
// NoopCache is a do-nothing implementation of a cache.
type NoopCache struct{}
func NewNoopCache() *NoopCache {
return &NoopCache{}
}
func (c *NoopCache) IsCacheable(s string, asKey bool) bool {
return false
}
func (c *NoopCache) Write(s string) string {
return s
}

128
vendor/github.com/russolsen/transit/rolling_cache.go generated vendored Normal file
View File

@ -0,0 +1,128 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"fmt"
)
const cacheCodeDigits = 44
const baseCharIndex = 48
const firstOrd = 48
const cacheSize = (cacheCodeDigits * cacheCodeDigits)
const minSizeCacheable = 4
type StringMap map[string]string
type RollingCache struct {
keyToValue StringMap
valueToKey StringMap
}
func NewRollingCache() *RollingCache {
return &RollingCache{keyToValue: make(StringMap), valueToKey: make(StringMap)}
}
func (rc *RollingCache) String() string {
return fmt.Sprintf("Cache: %v", rc.keyToValue)
}
func (rc *RollingCache) HasKey(name string) bool {
_, present := rc.keyToValue[name]
return present
}
func (rc *RollingCache) Read(name string) string {
return rc.keyToValue[name]
}
// Enter the name into the cache if it passes the cacheable critieria.
// Returns either the name or the value that was previously cached for
// the name.
func (rc *RollingCache) Write(name string) string {
existing_key, present := rc.valueToKey[name]
if present {
return existing_key
}
if rc.isCacheFull() {
rc.Clear()
}
var key = rc.encodeKey(len(rc.keyToValue))
rc.keyToValue[key] = name
rc.valueToKey[name] = key
return name
}
// IsCacheable returns true if the string is long enough to be cached
// and either asKey is true or the string represents a symbol, keyword
// or tag.
func (rc *RollingCache) IsCacheable(s string, asKey bool) bool {
if len(s) < minSizeCacheable {
return false
} else if asKey {
return true
} else {
var firstTwo = s[0:2]
//return firstTwo == "~#" || firstTwo == "~$" || firstTwo == "~:"
return firstTwo == startTag || firstTwo == startKW || firstTwo == startSym
}
}
// IsCacheKey returns true if the string looks like a cache key.
func (rc *RollingCache) IsCacheKey(name string) bool {
if len(name) == 0 {
return false
} else if (name[0:1] == sub) && (name != mapAsArray) {
return true
} else {
return false
}
}
func (rc *RollingCache) encodeKey(index int) string {
var hi = index / cacheCodeDigits
var lo = index % cacheCodeDigits
if hi == 0 {
return sub + string(lo+baseCharIndex)
} else {
return sub + string(hi+baseCharIndex) + string(lo+baseCharIndex)
}
}
func (rc *RollingCache) codeToIndex(s string) int {
var sz = len(s)
if sz == 2 {
return int(s[1]) - baseCharIndex
} else {
return ((int(s[1]) - baseCharIndex) * cacheCodeDigits) + (int(s[2]) - baseCharIndex)
}
}
func (rc *RollingCache) isCacheFull() bool {
return len(rc.keyToValue) >= cacheSize
}
func (rc *RollingCache) Clear() {
rc.valueToKey = make(StringMap)
rc.keyToValue = make(StringMap)
}

55
vendor/github.com/russolsen/transit/set.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"fmt"
)
// A set is a very simple minded representation of a set, one that
// does not enforce any redundancy constraints. Just a box
// for sets read from transit.
type Set struct {
Contents []interface{}
}
func MakeSet(contents ...interface{}) *Set {
return &Set{Contents: contents}
}
func NewSet(contents []interface{}) *Set {
return &Set{Contents: contents}
}
func (s Set) String() string {
return fmt.Sprintf("Set[%v]: %v", len(s.Contents), s.Contents)
}
func (s Set) Contains(value interface{}, mf MatchF) bool {
for _, element := range s.Contents {
if mf(element, value) {
return true
}
}
return false
}
func (s Set) ContainsEq(value interface{}) bool {
return s.Contains(value, Equals)
}

84
vendor/github.com/russolsen/transit/types.go generated vendored Normal file
View File

@ -0,0 +1,84 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"fmt"
"net/url"
)
// Cache is the interface for (obviously) caches. Implemented
// by RollingCache and NoopCache.
type Cache interface {
IsCacheable(s string, asKey bool) bool
Write(string) string
}
// MatchF is an equality function protocol used by
// sets and cmaps.
type MatchF func(a, b interface{}) bool
// Matches keys with a simple == test. Satisfies the
// MatchF protocol.
func Equals(a, b interface{}) bool {
return a == b
}
// A tag id represents a #tag in the transit protocol.
type TagId string
func (t TagId) String() string {
return fmt.Sprintf("[Tag: %s]", string(t))
}
// TaggedValue is a simple struct to hold the data from
// a transit #tag.
type TaggedValue struct {
Tag TagId
Value interface{}
}
// A Keyword is a transit keyword, really just a string by another type.
type Keyword string
func (k Keyword) String() string {
return fmt.Sprintf(":%s", string(k))
}
// A Symbol is a transit symbol, really just a string by another type.
type Symbol string
// A TUri is just a container for a uri string. Go url.URL cannot handle all
// of the non-ascii chars of transit uris, hence the need for this type.
type TUri struct {
Value string
}
func NewTUri(x string) *TUri {
return &TUri{Value: x}
}
func (turi TUri) ToURL() (*url.URL, error) {
return url.Parse(turi.Value)
}
func (turi TUri) String() string {
return turi.Value
}

63
vendor/github.com/russolsen/transit/utils.go generated vendored Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"reflect"
)
// GetMapElement pulls a value out of a map (represented by
// the m value). This function differs from the reflect.Value
// MapIndex in that the type and kind of the returned value
// reflect the type and kind of the element, not what was
// declared in the map.
func GetMapElement(m reflect.Value, key reflect.Value) reflect.Value {
element := m.MapIndex(key)
return reflect.ValueOf(element.Interface())
}
// GetElement pulls a value out of a array (represented by
// the array value). This function differs from the reflect.Value
// Index in that the type and kind of the returned value
// reflect the type and kind of the element, not what was
// declared in the array.
func GetElement(array reflect.Value, i int) reflect.Value {
element := array.Index(i)
return reflect.ValueOf(element.Interface())
}
// KeyValues returns the Values for the keys in a map.
func KeyValues(m reflect.Value) []reflect.Value {
keys := m.MapKeys()
result := make([]reflect.Value, len(keys))
for i, v := range keys {
result[i] = reflect.ValueOf(v.Interface())
}
return result
}
func IsGenericArray(x interface{}) bool {
switch x.(type) {
case []interface{}:
return true
default:
return false
}
}

276
vendor/github.com/russolsen/transit/value_decoder.go generated vendored Normal file
View File

@ -0,0 +1,276 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"container/list"
"encoding/base64"
"github.com/pborman/uuid"
"github.com/shopspring/decimal"
"math"
"math/big"
"strconv"
"time"
)
// DecodeKeyword decodes ~: style keywords.
func DecodeKeyword(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
var result = Keyword(s)
return result, nil
}
// DecodeKeyword decodes ~$ style symbols.
func DecodeSymbol(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
var result = Symbol(s)
return result, nil
}
// DecodeIdentity returns the value unchanged.
func DecodeIdentity(d Decoder, x interface{}) (interface{}, error) {
return x, nil
}
// DecodeCMap decodes maps with composite keys.
func DecodeCMap(d Decoder, x interface{}) (interface{}, error) {
tagged := x.(TaggedValue)
if !IsGenericArray(tagged.Value) {
return nil, NewTransitError("Cmap contents are not an array.", tagged)
}
array := tagged.Value.([]interface{})
if (len(array) % 2) != 0 {
return nil, NewTransitError("Cmap contents must contain an even number of elements.", tagged)
}
var result = NewCMap()
l := len(array)
for i := 0; i < l; i += 2 {
key := array[i]
value := array[i+1]
result.Append(key, value)
}
return result, nil
}
// DecodeSet decodes a transit set into a transit.Set instance.
func DecodeSet(d Decoder, x interface{}) (interface{}, error) {
tagged := x.(TaggedValue)
if !IsGenericArray(tagged.Value) {
return nil, NewTransitError("Set contents are not an array.", tagged)
}
values := (tagged.Value).([]interface{})
result := NewSet(values)
return result, nil
}
// DecodeList decodes a transit list into a Go list.
func DecodeList(d Decoder, x interface{}) (interface{}, error) {
tagged := x.(TaggedValue)
if !IsGenericArray(tagged.Value) {
return nil, NewTransitError("List contents are not an array.", tagged)
}
values := (tagged.Value).([]interface{})
result := list.New()
for _, item := range values {
result.PushBack(item)
}
return result, nil
}
// DecodeQuote decodes a transit quoted value by simply returning the value.
func DecodeQuote(d Decoder, x interface{}) (interface{}, error) {
tagged := x.(TaggedValue)
return tagged.Value, nil
}
// DecodeRFC3339 decodes a time value into a Go time instance.
// TBD not 100% this covers all possible values.
func DecodeRFC3339(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
var result, err = time.Parse(time.RFC3339Nano, s)
return result, err
}
// DecodeTime decodes a time value represended as millis since 1970.
func DecodeTime(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
var millis, _ = strconv.ParseInt(s, 10, 64)
seconds := millis / 1000
remainder_millis := millis - (seconds * 1000)
nanos := remainder_millis * 1000000
result := time.Unix(seconds, nanos).UTC()
return result, nil
}
// DecodeBoolean decodes a transit boolean into a Go bool.
func DecodeBoolean(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
if s == "t" {
return true, nil
} else if s == "f" {
return false, nil
} else {
return nil, &TransitError{Message: "Unknown boolean value."}
}
}
// DecodeBigInteger decodes a transit big integer into a Go big.Int.
func DecodeBigInteger(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
result := new(big.Int)
_, good := result.SetString(s, 10)
if !good {
return nil, &TransitError{Message: "Unable to part big integer: " + s}
}
return result, nil
}
// DecodeInteger decodes a transit integer into a plain Go int64
func DecodeInteger(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
result, err := strconv.ParseInt(s, 10, 64)
return result, err
}
func newRational(a, b *big.Int) *big.Rat {
var r = big.NewRat(1, 1)
r.SetFrac(a, b)
return r
}
func toBigInt(x interface{}) (*big.Int, error) {
switch v := x.(type) {
default:
return nil, NewTransitError("Not a numeric value", v)
case *big.Int:
return v, nil
case int64:
return big.NewInt(v), nil
}
}
// DecodeRatio decodes a transit ratio into a Go big.Rat.
func DecodeRatio(d Decoder, x interface{}) (interface{}, error) {
tagged := x.(TaggedValue)
if !IsGenericArray(tagged.Value) {
return nil, NewTransitError("Ratio contents are not an array.", tagged)
}
values := (tagged.Value).([]interface{})
if len(values) != 2 {
return nil, NewTransitError("Ratio contents does not contain 2 elements.", tagged)
}
a, err := toBigInt(values[0])
if err != nil {
return nil, err
}
b, err := toBigInt(values[1])
if err != nil {
return nil, err
}
result := newRational(a, b)
return *result, nil
}
// DecodeRune decodes a transit char.
func DecodeRune(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
return rune(s[0]), nil
}
// DecodeFloat decodes the value into a float.
func DecodeFloat(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
return strconv.ParseFloat(s, 64)
}
// DecodeDecimal decodes a transit big decimal into decimal.Decimal.
func DecodeDecimal(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
return decimal.NewFromString((s))
}
// DecodeRatio decodes a transit null/nil.
func DecodeNil(d Decoder, x interface{}) (interface{}, error) {
return nil, nil
}
// DecodeRatio decodes a transit base64 encoded byte array into a
// Go byte array.
func DecodeByte(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
return base64.StdEncoding.DecodeString(s)
}
// DecodeLink decodes a transit link into an instance of Link.
func DecodeLink(d Decoder, x interface{}) (interface{}, error) {
tv := x.(TaggedValue)
v := tv.Value.(map[interface{}]interface{})
l := NewLink()
l.Href = v["href"].(*TUri)
l.Name = v["name"].(string)
l.Rel = v["rel"].(string)
l.Prompt = v["prompt"].(string)
l.Render = v["render"].(string)
return l, nil
}
// DecodeURI decodes a transit URI into an instance of TUri.
func DecodeURI(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
return NewTUri(s), nil
}
// DecodeUUID decodes a transit UUID into an instance of net/UUID
func DecodeUUID(d Decoder, x interface{}) (interface{}, error) {
s := x.(string)
var u = uuid.Parse(s)
if u == nil {
return nil, &TransitError{Message: "Unable to parse uuid [" + s + "]"}
}
return u, nil
}
// DecodeSpecialNumber decodes NaN, INF and -INF into their Go equivalents.
func DecodeSpecialNumber(d Decoder, x interface{}) (interface{}, error) {
tag := x.(string)
if tag == "NaN" {
return math.NaN(), nil
} else if tag == "INF" {
return math.Inf(1), nil
} else if tag == "-INF" {
return math.Inf(-1), nil
} else {
return nil, &TransitError{Message: "Bad special number:"}
}
}

680
vendor/github.com/russolsen/transit/value_encoders.go generated vendored Normal file
View File

@ -0,0 +1,680 @@
// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transit
import (
"container/list"
"fmt"
"github.com/pborman/uuid"
"github.com/shopspring/decimal"
"log"
"math"
"math/big"
"net/url"
"reflect"
"time"
)
// ValueEncoder is the interface for objects that know how to
// transit encode a single value.
type ValueEncoder interface {
IsStringable(reflect.Value) bool
Encode(e Encoder, value reflect.Value, asString bool) error
}
type NilEncoder struct{}
func NewNilEncoder() *NilEncoder {
return &NilEncoder{}
}
func (ie NilEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie NilEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
return e.emitter.EmitNil(asKey)
}
type RuneEncoder struct{}
func NewRuneEncoder() *RuneEncoder {
return &RuneEncoder{}
}
func (ie RuneEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie RuneEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
r := v.Interface().(rune)
return e.emitter.EmitString(fmt.Sprintf("~c%c", r), asKey)
}
type PointerEncoder struct{}
func NewPointerEncoder() *PointerEncoder {
return &PointerEncoder{}
}
func (ie PointerEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (ie PointerEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
//log.Println("*** Defer pointer to:", v.Elem())
return e.EncodeInterface(v.Elem().Interface(), asKey)
}
type UuidEncoder struct{}
func NewUuidEncoder() *UuidEncoder {
return &UuidEncoder{}
}
func (ie UuidEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie UuidEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
u := v.Interface().(uuid.UUID)
return e.emitter.EmitString(fmt.Sprintf("~u%v", u.String()), asKey)
}
type TimeEncoder struct{}
func NewTimeEncoder() *TimeEncoder {
return &TimeEncoder{}
}
func (ie TimeEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie TimeEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
t := v.Interface().(time.Time)
nanos := t.UnixNano()
millis := nanos / int64(1000000)
//millis := t.Unix() * 1000
return e.emitter.EmitString(fmt.Sprintf("~m%d", millis), asKey)
}
type BoolEncoder struct{}
func NewBoolEncoder() *BoolEncoder {
return &BoolEncoder{}
}
func (ie BoolEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie BoolEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
b := v.Bool()
return e.emitter.EmitBool(b, asKey)
}
type FloatEncoder struct{}
func NewFloatEncoder() *FloatEncoder {
return &FloatEncoder{}
}
func (ie FloatEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie FloatEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
f := v.Float()
if math.IsNaN(f) {
return e.emitter.EmitString("~zNaN", asKey)
} else if math.IsInf(f, 1) {
return e.emitter.EmitString("~zINF", asKey)
} else if math.IsInf(f, -1) {
return e.emitter.EmitString("~z-INF", asKey)
} else {
return e.emitter.EmitFloat(f, asKey)
}
}
type DecimalEncoder struct{}
func NewDecimalEncoder() *DecimalEncoder {
return &DecimalEncoder{}
}
func (ie DecimalEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie DecimalEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
f := v.Interface().(decimal.Decimal)
return e.emitter.EmitString(fmt.Sprintf("~f%v", f.String()), asKey)
}
type BigFloatEncoder struct{}
func NewBigFloatEncoder() *BigFloatEncoder {
return &BigFloatEncoder{}
}
func (ie BigFloatEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie BigFloatEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
f := v.Interface().(big.Float)
return e.emitter.EmitString(fmt.Sprintf("~f%v", f.Text('f', 25)), asKey)
}
type BigIntEncoder struct{}
func NewBigIntEncoder() *BigIntEncoder {
return &BigIntEncoder{}
}
func (ie BigIntEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie BigIntEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
i := v.Interface().(big.Int)
return e.emitter.EmitString(fmt.Sprintf("~n%v", i.String()), asKey)
}
type BigRatEncoder struct{}
func NewBigRatEncoder() *BigRatEncoder {
return &BigRatEncoder{}
}
func (ie BigRatEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (ie BigRatEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
r := v.Interface().(big.Rat)
e.emitter.EmitStartArray()
e.emitter.EmitTag("ratio")
e.emitter.EmitArraySeparator()
e.emitter.EmitStartArray()
e.Encode(r.Num())
e.emitter.EmitArraySeparator()
e.Encode(r.Denom())
e.emitter.EmitEndArray()
return e.emitter.EmitEndArray()
}
type IntEncoder struct{}
func NewIntEncoder() *IntEncoder {
return &IntEncoder{}
}
func (ie IntEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie IntEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
return e.emitter.EmitInt(v.Int(), asKey)
}
type UintEncoder struct{}
func NewUintEncoder() *UintEncoder {
return &UintEncoder{}
}
func (ie UintEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie UintEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
return e.emitter.EmitInt(int64(v.Uint()), asKey)
}
type KeywordEncoder struct{}
func NewKeywordEncoder() *KeywordEncoder {
return &KeywordEncoder{}
}
func (ie KeywordEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie KeywordEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
s := v.String()
//log.Println("Encoding keyword:", s)
return e.emitter.EmitString("~:"+s, true)
}
type SymbolEncoder struct{}
func NewSymbolEncoder() *SymbolEncoder {
return &SymbolEncoder{}
}
func (ie SymbolEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie SymbolEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
s := v.String()
//log.Println("Encoding symbol:", s)
return e.emitter.EmitString("~$"+s, true)
}
type StringEncoder struct{}
func NewStringEncoder() *StringEncoder {
return &StringEncoder{}
}
func (ie StringEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie StringEncoder) needsEscape(s string) bool {
if len(s) == 0 {
return false
}
firstCh := s[0:1]
return firstCh == start || firstCh == reserved || firstCh == sub
}
func (ie StringEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
s := v.String()
if ie.needsEscape(s) {
s = "~" + s
}
return e.emitter.EmitString(s, asKey)
}
type UrlEncoder struct{}
func NewUrlEncoder() *UrlEncoder {
return &UrlEncoder{}
}
func (ie UrlEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie UrlEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
u := v.Interface().(*url.URL)
us := u.String()
return e.emitter.EmitString(fmt.Sprintf("~r%s", us), asKey)
}
type TUriEncoder struct{}
func NewTUriEncoder() *TUriEncoder {
return &TUriEncoder{}
}
func (ie TUriEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie TUriEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
u := v.Interface().(*TUri)
return e.emitter.EmitString(fmt.Sprintf("~r%s", u.Value), asKey)
}
type ErrorEncoder struct{}
func NewErrorEncoder() *ErrorEncoder {
return &ErrorEncoder{}
}
func (ie ErrorEncoder) IsStringable(v reflect.Value) bool {
return true
}
func (ie ErrorEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
return NewTransitError("Dont know how to encode value", v)
}
type ArrayEncoder struct{}
func NewArrayEncoder() *ArrayEncoder {
return &ArrayEncoder{}
}
func (ie ArrayEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (ie ArrayEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
e.emitter.EmitStartArray()
l := v.Len()
for i := 0; i < l; i++ {
if i > 0 {
e.emitter.EmitArraySeparator()
}
element := v.Index(i)
err := e.EncodeInterface(element.Interface(), asKey)
if err != nil {
return err
}
}
return e.emitter.EmitEndArray()
}
type MapEncoder struct {
verbose bool
}
func NewMapEncoder(verbose bool) *MapEncoder {
return &MapEncoder{verbose}
}
func (me MapEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (me MapEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
keys := KeyValues(v)
if !me.allStringable(e, keys) {
return me.encodeCompositeMap(e, v)
} else if me.verbose {
return me.encodeVerboseMap(e, v)
} else {
return me.encodeNormalMap(e, v)
}
}
func (me MapEncoder) allStringable(e Encoder, keys []reflect.Value) bool {
for _, key := range keys {
valueEncoder := e.ValueEncoderFor(reflect.ValueOf(key.Interface()))
if !valueEncoder.IsStringable(key) {
return false
}
}
return true
}
func (me MapEncoder) encodeCompositeMap(e Encoder, v reflect.Value) error {
e.emitter.EmitStartArray()
e.emitter.EmitTag("cmap")
e.emitter.EmitArraySeparator()
e.emitter.EmitStartArray()
keys := KeyValues(v)
for i, key := range keys {
if i != 0 {
e.emitter.EmitArraySeparator()
}
err := e.EncodeValue(key, false)
if err != nil {
return err
}
e.emitter.EmitArraySeparator()
value := GetMapElement(v, key)
err = e.EncodeValue(value, false)
if err != nil {
return err
}
}
e.emitter.EmitEndArray()
return e.emitter.EmitEndArray()
}
func (me MapEncoder) encodeNormalMap(e Encoder, v reflect.Value) error {
//l := v.Len()
e.emitter.EmitStartArray()
e.emitter.EmitString("^ ", false)
keys := KeyValues(v)
for _, key := range keys {
e.emitter.EmitArraySeparator()
err := e.EncodeValue(key, true)
if err != nil {
return err
}
e.emitter.EmitArraySeparator()
value := GetMapElement(v, key)
err = e.EncodeValue(value, false)
if err != nil {
return err
}
}
return e.emitter.EmitEndArray()
}
func (me MapEncoder) encodeVerboseMap(e Encoder, v reflect.Value) error {
e.emitter.EmitStartMap()
keys := KeyValues(v)
for i, key := range keys {
if i != 0 {
e.emitter.EmitMapSeparator()
}
err := e.EncodeValue(key, true)
if err != nil {
return err
}
e.emitter.EmitKeySeparator()
value := GetMapElement(v, key)
err = e.EncodeValue(value, false)
if err != nil {
return err
}
}
return e.emitter.EmitEndMap()
}
type TaggedValueEncoder struct{}
func NewTaggedValueEncoder() *TaggedValueEncoder {
return &TaggedValueEncoder{}
}
func (ie TaggedValueEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (ie TaggedValueEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
t := v.Interface().(TaggedValue)
e.emitter.EmitStartArray()
e.emitter.EmitTag(string(t.Tag))
e.emitter.EmitArraySeparator()
e.EncodeInterface(t.Value, asKey)
return e.emitter.EmitEndArray()
}
type SetEncoder struct{}
func NewSetEncoder() *SetEncoder {
return &SetEncoder{}
}
func (ie SetEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (ie SetEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
s := v.Interface().(Set)
//log.Println("*** Encode set:", v)
//l := v.Len()
e.emitter.EmitStartArray()
e.emitter.EmitTag("set")
e.emitter.EmitArraySeparator()
e.emitter.EmitStartArray()
for i, element := range s.Contents {
if i != 0 {
e.emitter.EmitArraySeparator()
}
err := e.EncodeInterface(element, asKey)
if err != nil {
return err
}
}
e.emitter.EmitEndArray()
return e.emitter.EmitEndArray()
}
type ListEncoder struct{}
func NewListEncoder() *ListEncoder {
return &ListEncoder{}
}
func (ie ListEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (ie ListEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
lst := v.Interface().(*list.List)
e.emitter.EmitStartArray()
e.emitter.EmitTag("list")
e.emitter.EmitArraySeparator()
e.emitter.EmitStartArray()
first := true
for element := lst.Front(); element != nil; element = element.Next() {
if first {
first = false
} else {
e.emitter.EmitArraySeparator()
}
err := e.EncodeInterface(element.Value, asKey)
if err != nil {
log.Println("ERROR", err)
return err
}
}
e.emitter.EmitEndArray()
return e.emitter.EmitEndArray()
}
type CMapEncoder struct{}
func NewCMapEncoder() *CMapEncoder {
return &CMapEncoder{}
}
func (ie CMapEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (ie CMapEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
cmap := v.Interface().(*CMap)
//l := v.Len()
e.emitter.EmitStartArray()
e.emitter.EmitTag("cmap")
e.emitter.EmitArraySeparator()
e.emitter.EmitStartArray()
for i, entry := range cmap.Entries {
if i != 0 {
e.emitter.EmitArraySeparator()
}
err := e.EncodeInterface(entry.Key, false)
if err != nil {
return err
}
e.emitter.EmitArraySeparator()
err = e.EncodeInterface(entry.Value, false)
if err != nil {
return err
}
}
e.emitter.EmitEndArray()
return e.emitter.EmitEndArray()
}
type LinkEncoder struct{}
func NewLinkEncoder() *LinkEncoder {
return &LinkEncoder{}
}
func (ie LinkEncoder) IsStringable(v reflect.Value) bool {
return false
}
func (ie LinkEncoder) Encode(e Encoder, v reflect.Value, asKey bool) error {
link := v.Interface().(*Link)
e.emitter.EmitStartArray()
e.emitter.EmitTag("link")
e.emitter.EmitArraySeparator()
m := map[string]interface{}{
"href": link.Href,
"rel": link.Rel,
"name": link.Name,
"prompt": link.Prompt,
"render": link.Render,
}
e.Encode(m)
return e.emitter.EmitEndArray()
}

45
vendor/github.com/shopspring/decimal/LICENSE generated vendored Normal file
View File

@ -0,0 +1,45 @@
The MIT License (MIT)
Copyright (c) 2015 Spring, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
- Based on https://github.com/oguzbilgic/fpd, which has the following license:
"""
The MIT License (MIT)
Copyright (c) 2013 Oguz Bilgic
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

414
vendor/github.com/shopspring/decimal/decimal-go.go generated vendored Normal file
View File

@ -0,0 +1,414 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Multiprecision decimal numbers.
// For floating-point formatting only; not general purpose.
// Only operations are assign and (binary) left/right shift.
// Can do binary floating point in multiprecision decimal precisely
// because 2 divides 10; cannot do decimal floating point
// in multiprecision binary precisely.
package decimal
type decimal struct {
d [800]byte // digits, big-endian representation
nd int // number of digits used
dp int // decimal point
neg bool // negative flag
trunc bool // discarded nonzero digits beyond d[:nd]
}
func (a *decimal) String() string {
n := 10 + a.nd
if a.dp > 0 {
n += a.dp
}
if a.dp < 0 {
n += -a.dp
}
buf := make([]byte, n)
w := 0
switch {
case a.nd == 0:
return "0"
case a.dp <= 0:
// zeros fill space between decimal point and digits
buf[w] = '0'
w++
buf[w] = '.'
w++
w += digitZero(buf[w : w+-a.dp])
w += copy(buf[w:], a.d[0:a.nd])
case a.dp < a.nd:
// decimal point in middle of digits
w += copy(buf[w:], a.d[0:a.dp])
buf[w] = '.'
w++
w += copy(buf[w:], a.d[a.dp:a.nd])
default:
// zeros fill space between digits and decimal point
w += copy(buf[w:], a.d[0:a.nd])
w += digitZero(buf[w : w+a.dp-a.nd])
}
return string(buf[0:w])
}
func digitZero(dst []byte) int {
for i := range dst {
dst[i] = '0'
}
return len(dst)
}
// trim trailing zeros from number.
// (They are meaningless; the decimal point is tracked
// independent of the number of digits.)
func trim(a *decimal) {
for a.nd > 0 && a.d[a.nd-1] == '0' {
a.nd--
}
if a.nd == 0 {
a.dp = 0
}
}
// Assign v to a.
func (a *decimal) Assign(v uint64) {
var buf [24]byte
// Write reversed decimal in buf.
n := 0
for v > 0 {
v1 := v / 10
v -= 10 * v1
buf[n] = byte(v + '0')
n++
v = v1
}
// Reverse again to produce forward decimal in a.d.
a.nd = 0
for n--; n >= 0; n-- {
a.d[a.nd] = buf[n]
a.nd++
}
a.dp = a.nd
trim(a)
}
// Maximum shift that we can do in one pass without overflow.
// A uint has 32 or 64 bits, and we have to be able to accommodate 9<<k.
const uintSize = 32 << (^uint(0) >> 63)
const maxShift = uintSize - 4
// Binary shift right (/ 2) by k bits. k <= maxShift to avoid overflow.
func rightShift(a *decimal, k uint) {
r := 0 // read pointer
w := 0 // write pointer
// Pick up enough leading digits to cover first shift.
var n uint
for ; n>>k == 0; r++ {
if r >= a.nd {
if n == 0 {
// a == 0; shouldn't get here, but handle anyway.
a.nd = 0
return
}
for n>>k == 0 {
n = n * 10
r++
}
break
}
c := uint(a.d[r])
n = n*10 + c - '0'
}
a.dp -= r - 1
var mask uint = (1 << k) - 1
// Pick up a digit, put down a digit.
for ; r < a.nd; r++ {
c := uint(a.d[r])
dig := n >> k
n &= mask
a.d[w] = byte(dig + '0')
w++
n = n*10 + c - '0'
}
// Put down extra digits.
for n > 0 {
dig := n >> k
n &= mask
if w < len(a.d) {
a.d[w] = byte(dig + '0')
w++
} else if dig > 0 {
a.trunc = true
}
n = n * 10
}
a.nd = w
trim(a)
}
// Cheat sheet for left shift: table indexed by shift count giving
// number of new digits that will be introduced by that shift.
//
// For example, leftcheats[4] = {2, "625"}. That means that
// if we are shifting by 4 (multiplying by 16), it will add 2 digits
// when the string prefix is "625" through "999", and one fewer digit
// if the string prefix is "000" through "624".
//
// Credit for this trick goes to Ken.
type leftCheat struct {
delta int // number of new digits
cutoff string // minus one digit if original < a.
}
var leftcheats = []leftCheat{
// Leading digits of 1/2^i = 5^i.
// 5^23 is not an exact 64-bit floating point number,
// so have to use bc for the math.
// Go up to 60 to be large enough for 32bit and 64bit platforms.
/*
seq 60 | sed 's/^/5^/' | bc |
awk 'BEGIN{ print "\t{ 0, \"\" }," }
{
log2 = log(2)/log(10)
printf("\t{ %d, \"%s\" },\t// * %d\n",
int(log2*NR+1), $0, 2**NR)
}'
*/
{0, ""},
{1, "5"}, // * 2
{1, "25"}, // * 4
{1, "125"}, // * 8
{2, "625"}, // * 16
{2, "3125"}, // * 32
{2, "15625"}, // * 64
{3, "78125"}, // * 128
{3, "390625"}, // * 256
{3, "1953125"}, // * 512
{4, "9765625"}, // * 1024
{4, "48828125"}, // * 2048
{4, "244140625"}, // * 4096
{4, "1220703125"}, // * 8192
{5, "6103515625"}, // * 16384
{5, "30517578125"}, // * 32768
{5, "152587890625"}, // * 65536
{6, "762939453125"}, // * 131072
{6, "3814697265625"}, // * 262144
{6, "19073486328125"}, // * 524288
{7, "95367431640625"}, // * 1048576
{7, "476837158203125"}, // * 2097152
{7, "2384185791015625"}, // * 4194304
{7, "11920928955078125"}, // * 8388608
{8, "59604644775390625"}, // * 16777216
{8, "298023223876953125"}, // * 33554432
{8, "1490116119384765625"}, // * 67108864
{9, "7450580596923828125"}, // * 134217728
{9, "37252902984619140625"}, // * 268435456
{9, "186264514923095703125"}, // * 536870912
{10, "931322574615478515625"}, // * 1073741824
{10, "4656612873077392578125"}, // * 2147483648
{10, "23283064365386962890625"}, // * 4294967296
{10, "116415321826934814453125"}, // * 8589934592
{11, "582076609134674072265625"}, // * 17179869184
{11, "2910383045673370361328125"}, // * 34359738368
{11, "14551915228366851806640625"}, // * 68719476736
{12, "72759576141834259033203125"}, // * 137438953472
{12, "363797880709171295166015625"}, // * 274877906944
{12, "1818989403545856475830078125"}, // * 549755813888
{13, "9094947017729282379150390625"}, // * 1099511627776
{13, "45474735088646411895751953125"}, // * 2199023255552
{13, "227373675443232059478759765625"}, // * 4398046511104
{13, "1136868377216160297393798828125"}, // * 8796093022208
{14, "5684341886080801486968994140625"}, // * 17592186044416
{14, "28421709430404007434844970703125"}, // * 35184372088832
{14, "142108547152020037174224853515625"}, // * 70368744177664
{15, "710542735760100185871124267578125"}, // * 140737488355328
{15, "3552713678800500929355621337890625"}, // * 281474976710656
{15, "17763568394002504646778106689453125"}, // * 562949953421312
{16, "88817841970012523233890533447265625"}, // * 1125899906842624
{16, "444089209850062616169452667236328125"}, // * 2251799813685248
{16, "2220446049250313080847263336181640625"}, // * 4503599627370496
{16, "11102230246251565404236316680908203125"}, // * 9007199254740992
{17, "55511151231257827021181583404541015625"}, // * 18014398509481984
{17, "277555756156289135105907917022705078125"}, // * 36028797018963968
{17, "1387778780781445675529539585113525390625"}, // * 72057594037927936
{18, "6938893903907228377647697925567626953125"}, // * 144115188075855872
{18, "34694469519536141888238489627838134765625"}, // * 288230376151711744
{18, "173472347597680709441192448139190673828125"}, // * 576460752303423488
{19, "867361737988403547205962240695953369140625"}, // * 1152921504606846976
}
// Is the leading prefix of b lexicographically less than s?
func prefixIsLessThan(b []byte, s string) bool {
for i := 0; i < len(s); i++ {
if i >= len(b) {
return true
}
if b[i] != s[i] {
return b[i] < s[i]
}
}
return false
}
// Binary shift left (* 2) by k bits. k <= maxShift to avoid overflow.
func leftShift(a *decimal, k uint) {
delta := leftcheats[k].delta
if prefixIsLessThan(a.d[0:a.nd], leftcheats[k].cutoff) {
delta--
}
r := a.nd // read index
w := a.nd + delta // write index
// Pick up a digit, put down a digit.
var n uint
for r--; r >= 0; r-- {
n += (uint(a.d[r]) - '0') << k
quo := n / 10
rem := n - 10*quo
w--
if w < len(a.d) {
a.d[w] = byte(rem + '0')
} else if rem != 0 {
a.trunc = true
}
n = quo
}
// Put down extra digits.
for n > 0 {
quo := n / 10
rem := n - 10*quo
w--
if w < len(a.d) {
a.d[w] = byte(rem + '0')
} else if rem != 0 {
a.trunc = true
}
n = quo
}
a.nd += delta
if a.nd >= len(a.d) {
a.nd = len(a.d)
}
a.dp += delta
trim(a)
}
// Binary shift left (k > 0) or right (k < 0).
func (a *decimal) Shift(k int) {
switch {
case a.nd == 0:
// nothing to do: a == 0
case k > 0:
for k > maxShift {
leftShift(a, maxShift)
k -= maxShift
}
leftShift(a, uint(k))
case k < 0:
for k < -maxShift {
rightShift(a, maxShift)
k += maxShift
}
rightShift(a, uint(-k))
}
}
// If we chop a at nd digits, should we round up?
func shouldRoundUp(a *decimal, nd int) bool {
if nd < 0 || nd >= a.nd {
return false
}
if a.d[nd] == '5' && nd+1 == a.nd { // exactly halfway - round to even
// if we truncated, a little higher than what's recorded - always round up
if a.trunc {
return true
}
return nd > 0 && (a.d[nd-1]-'0')%2 != 0
}
// not halfway - digit tells all
return a.d[nd] >= '5'
}
// Round a to nd digits (or fewer).
// If nd is zero, it means we're rounding
// just to the left of the digits, as in
// 0.09 -> 0.1.
func (a *decimal) Round(nd int) {
if nd < 0 || nd >= a.nd {
return
}
if shouldRoundUp(a, nd) {
a.RoundUp(nd)
} else {
a.RoundDown(nd)
}
}
// Round a down to nd digits (or fewer).
func (a *decimal) RoundDown(nd int) {
if nd < 0 || nd >= a.nd {
return
}
a.nd = nd
trim(a)
}
// Round a up to nd digits (or fewer).
func (a *decimal) RoundUp(nd int) {
if nd < 0 || nd >= a.nd {
return
}
// round up
for i := nd - 1; i >= 0; i-- {
c := a.d[i]
if c < '9' { // can stop after this digit
a.d[i]++
a.nd = i + 1
return
}
}
// Number is all 9s.
// Change to single 1 with adjusted decimal point.
a.d[0] = '1'
a.nd = 1
a.dp++
}
// Extract integer part, rounded appropriately.
// No guarantees about overflow.
func (a *decimal) RoundedInteger() uint64 {
if a.dp > 20 {
return 0xFFFFFFFFFFFFFFFF
}
var i int
n := uint64(0)
for i = 0; i < a.dp && i < a.nd; i++ {
n = n*10 + uint64(a.d[i]-'0')
}
for ; i < a.dp; i++ {
n *= 10
}
if shouldRoundUp(a, a.dp) {
n++
}
return n
}

1434
vendor/github.com/shopspring/decimal/decimal.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

118
vendor/github.com/shopspring/decimal/rounding.go generated vendored Normal file
View File

@ -0,0 +1,118 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Multiprecision decimal numbers.
// For floating-point formatting only; not general purpose.
// Only operations are assign and (binary) left/right shift.
// Can do binary floating point in multiprecision decimal precisely
// because 2 divides 10; cannot do decimal floating point
// in multiprecision binary precisely.
package decimal
type floatInfo struct {
mantbits uint
expbits uint
bias int
}
var float32info = floatInfo{23, 8, -127}
var float64info = floatInfo{52, 11, -1023}
// roundShortest rounds d (= mant * 2^exp) to the shortest number of digits
// that will let the original floating point value be precisely reconstructed.
func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) {
// If mantissa is zero, the number is zero; stop now.
if mant == 0 {
d.nd = 0
return
}
// Compute upper and lower such that any decimal number
// between upper and lower (possibly inclusive)
// will round to the original floating point number.
// We may see at once that the number is already shortest.
//
// Suppose d is not denormal, so that 2^exp <= d < 10^dp.
// The closest shorter number is at least 10^(dp-nd) away.
// The lower/upper bounds computed below are at distance
// at most 2^(exp-mantbits).
//
// So the number is already shortest if 10^(dp-nd) > 2^(exp-mantbits),
// or equivalently log2(10)*(dp-nd) > exp-mantbits.
// It is true if 332/100*(dp-nd) >= exp-mantbits (log2(10) > 3.32).
minexp := flt.bias + 1 // minimum possible exponent
if exp > minexp && 332*(d.dp-d.nd) >= 100*(exp-int(flt.mantbits)) {
// The number is already shortest.
return
}
// d = mant << (exp - mantbits)
// Next highest floating point number is mant+1 << exp-mantbits.
// Our upper bound is halfway between, mant*2+1 << exp-mantbits-1.
upper := new(decimal)
upper.Assign(mant*2 + 1)
upper.Shift(exp - int(flt.mantbits) - 1)
// d = mant << (exp - mantbits)
// Next lowest floating point number is mant-1 << exp-mantbits,
// unless mant-1 drops the significant bit and exp is not the minimum exp,
// in which case the next lowest is mant*2-1 << exp-mantbits-1.
// Either way, call it mantlo << explo-mantbits.
// Our lower bound is halfway between, mantlo*2+1 << explo-mantbits-1.
var mantlo uint64
var explo int
if mant > 1<<flt.mantbits || exp == minexp {
mantlo = mant - 1
explo = exp
} else {
mantlo = mant*2 - 1
explo = exp - 1
}
lower := new(decimal)
lower.Assign(mantlo*2 + 1)
lower.Shift(explo - int(flt.mantbits) - 1)
// The upper and lower bounds are possible outputs only if
// the original mantissa is even, so that IEEE round-to-even
// would round to the original mantissa and not the neighbors.
inclusive := mant%2 == 0
// Now we can figure out the minimum number of digits required.
// Walk along until d has distinguished itself from upper and lower.
for i := 0; i < d.nd; i++ {
l := byte('0') // lower digit
if i < lower.nd {
l = lower.d[i]
}
m := d.d[i] // middle digit
u := byte('0') // upper digit
if i < upper.nd {
u = upper.d[i]
}
// Okay to round down (truncate) if lower has a different digit
// or if lower is inclusive and is exactly the result of rounding
// down (i.e., and we have reached the final digit of lower).
okdown := l != m || inclusive && i+1 == lower.nd
// Okay to round up if upper has a different digit and either upper
// is inclusive or upper is bigger than the result of rounding up.
okup := m != u && (inclusive || m+1 < u || i+1 < upper.nd)
// If it's okay to do either, then round to the nearest one.
// If it's okay to do only one, do it.
switch {
case okdown && okup:
d.Round(i + 1)
return
case okdown:
d.RoundDown(i + 1)
return
case okup:
d.RoundUp(i + 1)
return
}
}
}