Commit 9f7cd756 authored by gluk256's avatar gluk256 Committed by Felix Lange

whisper/whisperv6: initial commit (clone of v5) (#15324)

parent 0131bd6f
This diff is collapsed.
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisperv6
import (
"testing"
"github.com/ethereum/go-ethereum/crypto"
)
func BenchmarkDeriveKeyMaterial(b *testing.B) {
for i := 0; i < b.N; i++ {
deriveKeyMaterial([]byte("test"), 0)
}
}
func BenchmarkEncryptionSym(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
for i := 0; i < b.N; i++ {
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params)
if err != nil {
b.Errorf("failed Wrap with seed %d: %s.", seed, err)
b.Errorf("i = %d, len(msg.Raw) = %d, params.Payload = %d.", i, len(msg.Raw), len(params.Payload))
return
}
}
}
func BenchmarkEncryptionAsym(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params.KeySym = nil
params.Dst = &key.PublicKey
for i := 0; i < b.N; i++ {
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
}
}
func BenchmarkDecryptionSymValid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f := Filter{KeySym: params.KeySym}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg == nil {
b.Fatalf("failed to open with seed %d.", seed)
}
}
}
func BenchmarkDecryptionSymInvalid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
f := Filter{KeySym: []byte("arbitrary stuff here")}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg != nil {
b.Fatalf("opened envelope with invalid key, seed: %d.", seed)
}
}
}
func BenchmarkDecryptionAsymValid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
f := Filter{KeyAsym: key}
params.KeySym = nil
params.Dst = &key.PublicKey
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg == nil {
b.Fatalf("fail to open, seed: %d.", seed)
}
}
}
func BenchmarkDecryptionAsymInvalid(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
key, err := crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
params.KeySym = nil
params.Dst = &key.PublicKey
msg, _ := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
key, err = crypto.GenerateKey()
if err != nil {
b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
}
f := Filter{KeyAsym: key}
for i := 0; i < b.N; i++ {
msg := env.Open(&f)
if msg != nil {
b.Fatalf("opened envelope with invalid key, seed: %d.", seed)
}
}
}
func increment(x []byte) {
for i := 0; i < len(x); i++ {
x[i]++
if x[i] != 0 {
break
}
}
}
func BenchmarkPoW(b *testing.B) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.Payload = make([]byte, 32)
params.PoW = 10.0
params.TTL = 1
for i := 0; i < b.N; i++ {
increment(params.Payload)
msg, _ := NewSentMessage(params)
_, err := msg.Wrap(params)
if err != nil {
b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
}
}
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisperv6
type Config struct {
MaxMessageSize uint32 `toml:",omitempty"`
MinimumAcceptedPOW float64 `toml:",omitempty"`
}
var DefaultConfig = Config{
MaxMessageSize: DefaultMaxMessageSize,
MinimumAcceptedPOW: DefaultMinimumPoW,
}
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
/*
Package whisper implements the Whisper protocol (version 6).
Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
As such it may be likened and compared to both, not dissimilar to the
matter/energy duality (apologies to physicists for the blatant abuse of a
fundamental and beautiful natural principle).
Whisper is a pure identity-based messaging system. Whisper provides a low-level
(non-application-specific) but easily-accessible API without being based upon
or prejudiced by the low-level hardware attributes and characteristics,
particularly the notion of singular endpoints.
*/
package whisperv6
import (
"fmt"
"time"
)
const (
EnvelopeVersion = uint64(0)
ProtocolVersion = uint64(5)
ProtocolVersionStr = "5.0"
ProtocolName = "shh"
statusCode = 0 // used by whisper protocol
messagesCode = 1 // normal whisper message
p2pCode = 2 // peer-to-peer message (to be consumed by the peer, but not forwarded any further)
p2pRequestCode = 3 // peer-to-peer message, used by Dapp protocol
NumberOfMessageCodes = 64
paddingMask = byte(3)
signatureFlag = byte(4)
TopicLength = 4
signatureLength = 65
aesKeyLength = 32
AESNonceLength = 12
keyIdSize = 32
MaxMessageSize = uint32(10 * 1024 * 1024) // maximum accepted size of a message.
DefaultMaxMessageSize = uint32(1024 * 1024)
DefaultMinimumPoW = 0.2
padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol (must not exceed 2^24)
messageQueueLimit = 1024
expirationCycle = time.Second
transmissionCycle = 300 * time.Millisecond
DefaultTTL = 50 // seconds
SynchAllowance = 10 // seconds
)
type unknownVersionError uint64
func (e unknownVersionError) Error() string {
return fmt.Sprintf("invalid envelope version %d", uint64(e))
}
// MailServer represents a mail server, capable of
// archiving the old messages for subsequent delivery
// to the peers. Any implementation must ensure that both
// functions are thread-safe. Also, they must return ASAP.
// DeliverMail should use directMessagesCode for delivery,
// in order to bypass the expiry checks.
type MailServer interface {
Archive(env *Envelope)
DeliverMail(whisperPeer *Peer, request *Envelope)
}
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the Whisper protocol Envelope element.
package whisperv6
import (
"crypto/ecdsa"
"encoding/binary"
"fmt"
gmath "math"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/rlp"
)
// Envelope represents a clear-text data packet to transmit through the Whisper
// network. Its contents may or may not be encrypted and signed.
type Envelope struct {
Version []byte
Expiry uint32
TTL uint32
Topic TopicType
AESNonce []byte
Data []byte
EnvNonce uint64
pow float64 // Message-specific PoW as described in the Whisper specification.
hash common.Hash // Cached hash of the envelope to avoid rehashing every time.
// Don't access hash directly, use Hash() function instead.
}
// size returns the size of envelope as it is sent (i.e. public fields only)
func (e *Envelope) size() int {
return 20 + len(e.Version) + len(e.AESNonce) + len(e.Data)
}
// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
func (e *Envelope) rlpWithoutNonce() []byte {
res, _ := rlp.EncodeToBytes([]interface{}{e.Version, e.Expiry, e.TTL, e.Topic, e.AESNonce, e.Data})
return res
}
// NewEnvelope wraps a Whisper message with expiration and destination data
// included into an envelope for network forwarding.
func NewEnvelope(ttl uint32, topic TopicType, aesNonce []byte, msg *sentMessage) *Envelope {
env := Envelope{
Version: make([]byte, 1),
Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()),
TTL: ttl,
Topic: topic,
AESNonce: aesNonce,
Data: msg.Raw,
EnvNonce: 0,
}
if EnvelopeVersion < 256 {
env.Version[0] = byte(EnvelopeVersion)
} else {
panic("please increase the size of Envelope.Version before releasing this version")
}
return &env
}
func (e *Envelope) IsSymmetric() bool {
return len(e.AESNonce) > 0
}
func (e *Envelope) isAsymmetric() bool {
return !e.IsSymmetric()
}
func (e *Envelope) Ver() uint64 {
return bytesToUintLittleEndian(e.Version)
}
// Seal closes the envelope by spending the requested amount of time as a proof
// of work on hashing the data.
func (e *Envelope) Seal(options *MessageParams) error {
var target, bestBit int
if options.PoW == 0 {
// adjust for the duration of Seal() execution only if execution time is predefined unconditionally
e.Expiry += options.WorkTime
} else {
target = e.powToFirstBit(options.PoW)
if target < 1 {
target = 1
}
}
buf := make([]byte, 64)
h := crypto.Keccak256(e.rlpWithoutNonce())
copy(buf[:32], h)
finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano()
for nonce := uint64(0); time.Now().UnixNano() < finish; {
for i := 0; i < 1024; i++ {
binary.BigEndian.PutUint64(buf[56:], nonce)
d := new(big.Int).SetBytes(crypto.Keccak256(buf))
firstBit := math.FirstBitSet(d)
if firstBit > bestBit {
e.EnvNonce, bestBit = nonce, firstBit
if target > 0 && bestBit >= target {
return nil
}
}
nonce++
}
}
if target > 0 && bestBit < target {
return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime)
}
return nil
}
func (e *Envelope) PoW() float64 {
if e.pow == 0 {
e.calculatePoW(0)
}
return e.pow
}
func (e *Envelope) calculatePoW(diff uint32) {
buf := make([]byte, 64)
h := crypto.Keccak256(e.rlpWithoutNonce())
copy(buf[:32], h)
binary.BigEndian.PutUint64(buf[56:], e.EnvNonce)
d := new(big.Int).SetBytes(crypto.Keccak256(buf))
firstBit := math.FirstBitSet(d)
x := gmath.Pow(2, float64(firstBit))
x /= float64(e.size())
x /= float64(e.TTL + diff)
e.pow = x
}
func (e *Envelope) powToFirstBit(pow float64) int {
x := pow
x *= float64(e.size())
x *= float64(e.TTL)
bits := gmath.Log2(x)
bits = gmath.Ceil(bits)
return int(bits)
}
// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
func (e *Envelope) Hash() common.Hash {
if (e.hash == common.Hash{}) {
encoded, _ := rlp.EncodeToBytes(e)
e.hash = crypto.Keccak256Hash(encoded)
}
return e.hash
}
// DecodeRLP decodes an Envelope from an RLP data stream.
func (e *Envelope) DecodeRLP(s *rlp.Stream) error {
raw, err := s.Raw()
if err != nil {
return err
}
// The decoding of Envelope uses the struct fields but also needs
// to compute the hash of the whole RLP-encoded envelope. This
// type has the same structure as Envelope but is not an
// rlp.Decoder (does not implement DecodeRLP function).
// Only public members will be encoded.
type rlpenv Envelope
if err := rlp.DecodeBytes(raw, (*rlpenv)(e)); err != nil {
return err
}
e.hash = crypto.Keccak256Hash(raw)
return nil
}
// OpenAsymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, error) {
message := &ReceivedMessage{Raw: e.Data}
err := message.decryptAsymmetric(key)
switch err {
case nil:
return message, nil
case ecies.ErrInvalidPublicKey: // addressed to somebody else
return nil, err
default:
return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err)
}
}
// OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) {
msg = &ReceivedMessage{Raw: e.Data}
err = msg.decryptSymmetric(key, e.AESNonce)
if err != nil {
msg = nil
}
return msg, err
}
// Open tries to decrypt an envelope, and populates the message fields in case of success.
func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) {
if e.isAsymmetric() {
msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
if msg != nil {
msg.Dst = &watcher.KeyAsym.PublicKey
}
} else if e.IsSymmetric() {
msg, _ = e.OpenSymmetric(watcher.KeySym)
if msg != nil {
msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
}
}
if msg != nil {
ok := msg.Validate()
if !ok {
return nil
}
msg.Topic = e.Topic
msg.PoW = e.PoW()
msg.TTL = e.TTL
msg.Sent = e.Expiry - e.TTL
msg.EnvelopeHash = e.Hash()
msg.EnvelopeVersion = e.Ver()
}
return msg
}
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisperv6
import (
"crypto/ecdsa"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)
type Filter struct {
Src *ecdsa.PublicKey // Sender of the message
KeyAsym *ecdsa.PrivateKey // Private Key of recipient
KeySym []byte // Key associated with the Topic
Topics [][]byte // Topics to filter messages with
PoW float64 // Proof of work as described in the Whisper spec
AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages
SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization
Messages map[common.Hash]*ReceivedMessage
mutex sync.RWMutex
}
type Filters struct {
watchers map[string]*Filter
whisper *Whisper
mutex sync.RWMutex
}
func NewFilters(w *Whisper) *Filters {
return &Filters{
watchers: make(map[string]*Filter),
whisper: w,
}
}
func (fs *Filters) Install(watcher *Filter) (string, error) {
if watcher.Messages == nil {
watcher.Messages = make(map[common.Hash]*ReceivedMessage)
}
id, err := GenerateRandomID()
if err != nil {
return "", err
}
fs.mutex.Lock()
defer fs.mutex.Unlock()
if fs.watchers[id] != nil {
return "", fmt.Errorf("failed to generate unique ID")
}
if watcher.expectsSymmetricEncryption() {
watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
}
fs.watchers[id] = watcher
return id, err
}
func (fs *Filters) Uninstall(id string) bool {
fs.mutex.Lock()
defer fs.mutex.Unlock()
if fs.watchers[id] != nil {
delete(fs.watchers, id)
return true
}
return false
}
func (fs *Filters) Get(id string) *Filter {
fs.mutex.RLock()
defer fs.mutex.RUnlock()
return fs.watchers[id]
}
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
var msg *ReceivedMessage
fs.mutex.RLock()
defer fs.mutex.RUnlock()
i := -1 // only used for logging info
for _, watcher := range fs.watchers {
i++
if p2pMessage && !watcher.AllowP2P {
log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), i))
continue
}
var match bool
if msg != nil {
match = watcher.MatchMessage(msg)
} else {
match = watcher.MatchEnvelope(env)
if match {
msg = env.Open(watcher)
if msg == nil {
log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", i)
}
} else {
log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", i)
}
}
if match && msg != nil {
log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
watcher.Trigger(msg)
}
}
}
}
func (f *Filter) processEnvelope(env *Envelope) *ReceivedMessage {
if f.MatchEnvelope(env) {
msg := env.Open(f)
if msg != nil {
return msg
} else {
log.Trace("processing envelope: failed to open", "hash", env.Hash().Hex())
}
} else {
log.Trace("processing envelope: does not match", "hash", env.Hash().Hex())
}
return nil
}
func (f *Filter) expectsAsymmetricEncryption() bool {
return f.KeyAsym != nil
}
func (f *Filter) expectsSymmetricEncryption() bool {
return f.KeySym != nil
}
func (f *Filter) Trigger(msg *ReceivedMessage) {
f.mutex.Lock()
defer f.mutex.Unlock()
if _, exist := f.Messages[msg.EnvelopeHash]; !exist {
f.Messages[msg.EnvelopeHash] = msg
}
}
func (f *Filter) Retrieve() (all []*ReceivedMessage) {
f.mutex.Lock()
defer f.mutex.Unlock()
all = make([]*ReceivedMessage, 0, len(f.Messages))
for _, msg := range f.Messages {
all = append(all, msg)
}
f.Messages = make(map[common.Hash]*ReceivedMessage) // delete old messages
return all
}
func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
if f.PoW > 0 && msg.PoW < f.PoW {
return false
}
if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst) && f.MatchTopic(msg.Topic)
} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
return f.SymKeyHash == msg.SymKeyHash && f.MatchTopic(msg.Topic)
}
return false
}
func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
if f.PoW > 0 && envelope.pow < f.PoW {
return false
}
if f.expectsAsymmetricEncryption() && envelope.isAsymmetric() {
return f.MatchTopic(envelope.Topic)
} else if f.expectsSymmetricEncryption() && envelope.IsSymmetric() {
return f.MatchTopic(envelope.Topic)
}
return false
}
func (f *Filter) MatchTopic(topic TopicType) bool {
if len(f.Topics) == 0 {
// any topic matches
return true
}
for _, bt := range f.Topics {
if matchSingleTopic(topic, bt) {
return true
}
}
return false
}
func matchSingleTopic(topic TopicType, bt []byte) bool {
if len(bt) > 4 {
bt = bt[:4]
}
for j, b := range bt {
if topic[j] != b {
return false
}
}
return true
}
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
if !ValidatePublicKey(a) {
return false
} else if !ValidatePublicKey(b) {
return false
}
// the curve is always the same, just compare the points
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
}
This diff is collapsed.
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package whisperv6
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*criteriaOverride)(nil)
func (c Criteria) MarshalJSON() ([]byte, error) {
type Criteria struct {
SymKeyID string `json:"symKeyID"`
PrivateKeyID string `json:"privateKeyID"`
Sig hexutil.Bytes `json:"sig"`
MinPow float64 `json:"minPow"`
Topics []TopicType `json:"topics"`
AllowP2P bool `json:"allowP2P"`
}
var enc Criteria
enc.SymKeyID = c.SymKeyID
enc.PrivateKeyID = c.PrivateKeyID
enc.Sig = c.Sig
enc.MinPow = c.MinPow
enc.Topics = c.Topics
enc.AllowP2P = c.AllowP2P
return json.Marshal(&enc)
}
func (c *Criteria) UnmarshalJSON(input []byte) error {
type Criteria struct {
SymKeyID *string `json:"symKeyID"`
PrivateKeyID *string `json:"privateKeyID"`
Sig hexutil.Bytes `json:"sig"`
MinPow *float64 `json:"minPow"`
Topics []TopicType `json:"topics"`
AllowP2P *bool `json:"allowP2P"`
}
var dec Criteria
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.SymKeyID != nil {
c.SymKeyID = *dec.SymKeyID
}
if dec.PrivateKeyID != nil {
c.PrivateKeyID = *dec.PrivateKeyID
}
if dec.Sig != nil {
c.Sig = dec.Sig
}
if dec.MinPow != nil {
c.MinPow = *dec.MinPow
}
if dec.Topics != nil {
c.Topics = dec.Topics
}
if dec.AllowP2P != nil {
c.AllowP2P = *dec.AllowP2P
}
return nil
}
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package whisperv6
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*messageOverride)(nil)
func (m Message) MarshalJSON() ([]byte, error) {
type Message struct {
Sig hexutil.Bytes `json:"sig,omitempty"`
TTL uint32 `json:"ttl"`
Timestamp uint32 `json:"timestamp"`
Topic TopicType `json:"topic"`
Payload hexutil.Bytes `json:"payload"`
Padding hexutil.Bytes `json:"padding"`
PoW float64 `json:"pow"`
Hash hexutil.Bytes `json:"hash"`
Dst hexutil.Bytes `json:"recipientPublicKey,omitempty"`
}
var enc Message
enc.Sig = m.Sig
enc.TTL = m.TTL
enc.Timestamp = m.Timestamp
enc.Topic = m.Topic
enc.Payload = m.Payload
enc.Padding = m.Padding
enc.PoW = m.PoW
enc.Hash = m.Hash
enc.Dst = m.Dst
return json.Marshal(&enc)
}
func (m *Message) UnmarshalJSON(input []byte) error {
type Message struct {
Sig hexutil.Bytes `json:"sig,omitempty"`
TTL *uint32 `json:"ttl"`
Timestamp *uint32 `json:"timestamp"`
Topic *TopicType `json:"topic"`
Payload hexutil.Bytes `json:"payload"`
Padding hexutil.Bytes `json:"padding"`
PoW *float64 `json:"pow"`
Hash hexutil.Bytes `json:"hash"`
Dst hexutil.Bytes `json:"recipientPublicKey,omitempty"`
}
var dec Message
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Sig != nil {
m.Sig = dec.Sig
}
if dec.TTL != nil {
m.TTL = *dec.TTL
}
if dec.Timestamp != nil {
m.Timestamp = *dec.Timestamp
}
if dec.Topic != nil {
m.Topic = *dec.Topic
}
if dec.Payload != nil {
m.Payload = dec.Payload
}
if dec.Padding != nil {
m.Padding = dec.Padding
}
if dec.PoW != nil {
m.PoW = *dec.PoW
}
if dec.Hash != nil {
m.Hash = dec.Hash
}
if dec.Dst != nil {
m.Dst = dec.Dst
}
return nil
}
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package whisperv6
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*newMessageOverride)(nil)
func (n NewMessage) MarshalJSON() ([]byte, error) {
type NewMessage struct {
SymKeyID string `json:"symKeyID"`
PublicKey hexutil.Bytes `json:"pubKey"`
Sig string `json:"sig"`
TTL uint32 `json:"ttl"`
Topic TopicType `json:"topic"`
Payload hexutil.Bytes `json:"payload"`
Padding hexutil.Bytes `json:"padding"`
PowTime uint32 `json:"powTime"`
PowTarget float64 `json:"powTarget"`
TargetPeer string `json:"targetPeer"`
}
var enc NewMessage
enc.SymKeyID = n.SymKeyID
enc.PublicKey = n.PublicKey
enc.Sig = n.Sig
enc.TTL = n.TTL
enc.Topic = n.Topic
enc.Payload = n.Payload
enc.Padding = n.Padding
enc.PowTime = n.PowTime
enc.PowTarget = n.PowTarget
enc.TargetPeer = n.TargetPeer
return json.Marshal(&enc)
}
func (n *NewMessage) UnmarshalJSON(input []byte) error {
type NewMessage struct {
SymKeyID *string `json:"symKeyID"`
PublicKey hexutil.Bytes `json:"pubKey"`
Sig *string `json:"sig"`
TTL *uint32 `json:"ttl"`
Topic *TopicType `json:"topic"`
Payload hexutil.Bytes `json:"payload"`
Padding hexutil.Bytes `json:"padding"`
PowTime *uint32 `json:"powTime"`
PowTarget *float64 `json:"powTarget"`
TargetPeer *string `json:"targetPeer"`
}
var dec NewMessage
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.SymKeyID != nil {
n.SymKeyID = *dec.SymKeyID
}
if dec.PublicKey != nil {
n.PublicKey = dec.PublicKey
}
if dec.Sig != nil {
n.Sig = *dec.Sig
}
if dec.TTL != nil {
n.TTL = *dec.TTL
}
if dec.Topic != nil {
n.Topic = *dec.Topic
}
if dec.Payload != nil {
n.Payload = dec.Payload
}
if dec.Padding != nil {
n.Padding = dec.Padding
}
if dec.PowTime != nil {
n.PowTime = *dec.PowTime
}
if dec.PowTarget != nil {
n.PowTarget = *dec.PowTarget
}
if dec.TargetPeer != nil {
n.TargetPeer = *dec.TargetPeer
}
return nil
}
This diff is collapsed.
This diff is collapsed.
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisperv6
import (
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
set "gopkg.in/fatih/set.v0"
)
// peer represents a whisper protocol peer connection.
type Peer struct {
host *Whisper
peer *p2p.Peer
ws p2p.MsgReadWriter
trusted bool
known *set.Set // Messages already known by the peer to avoid wasting bandwidth
quit chan struct{}
}
// newPeer creates a new whisper peer object, but does not run the handshake itself.
func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
return &Peer{
host: host,
peer: remote,
ws: rw,
trusted: false,
known: set.New(),
quit: make(chan struct{}),
}
}
// start initiates the peer updater, periodically broadcasting the whisper packets
// into the network.
func (p *Peer) start() {
go p.update()
log.Trace("start", "peer", p.ID())
}
// stop terminates the peer updater, stopping message forwarding to it.
func (p *Peer) stop() {
close(p.quit)
log.Trace("stop", "peer", p.ID())
}
// handshake sends the protocol initiation status message to the remote peer and
// verifies the remote status too.
func (p *Peer) handshake() error {
// Send the handshake status message asynchronously
errc := make(chan error, 1)
go func() {
errc <- p2p.Send(p.ws, statusCode, ProtocolVersion)
}()
// Fetch the remote status packet and verify protocol match
packet, err := p.ws.ReadMsg()
if err != nil {
return err
}
if packet.Code != statusCode {
return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), packet.Code)
}
s := rlp.NewStream(packet.Payload, uint64(packet.Size))
peerVersion, err := s.Uint()
if err != nil {
return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err)
}
if peerVersion != ProtocolVersion {
return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), peerVersion, ProtocolVersion)
}
// Wait until out own status is consumed too
if err := <-errc; err != nil {
return fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err)
}
return nil
}
// update executes periodic operations on the peer, including message transmission
// and expiration.
func (p *Peer) update() {
// Start the tickers for the updates
expire := time.NewTicker(expirationCycle)
transmit := time.NewTicker(transmissionCycle)
// Loop and transmit until termination is requested
for {
select {
case <-expire.C:
p.expire()
case <-transmit.C:
if err := p.broadcast(); err != nil {
log.Trace("broadcast failed", "reason", err, "peer", p.ID())
return
}
case <-p.quit:
return
}
}
}
// mark marks an envelope known to the peer so that it won't be sent back.
func (peer *Peer) mark(envelope *Envelope) {
peer.known.Add(envelope.Hash())
}
// marked checks if an envelope is already known to the remote peer.
func (peer *Peer) marked(envelope *Envelope) bool {
return peer.known.Has(envelope.Hash())
}
// expire iterates over all the known envelopes in the host and removes all
// expired (unknown) ones from the known list.
func (peer *Peer) expire() {
unmark := make(map[common.Hash]struct{})
peer.known.Each(func(v interface{}) bool {
if !peer.host.isEnvelopeCached(v.(common.Hash)) {
unmark[v.(common.Hash)] = struct{}{}
}
return true
})
// Dump all known but no longer cached
for hash := range unmark {
peer.known.Remove(hash)
}
}
// broadcast iterates over the collection of envelopes and transmits yet unknown
// ones over the network.
func (p *Peer) broadcast() error {
var cnt int
envelopes := p.host.Envelopes()
for _, envelope := range envelopes {
if !p.marked(envelope) {
err := p2p.Send(p.ws, messagesCode, envelope)
if err != nil {
return err
} else {
p.mark(envelope)
cnt++
}
}
}
if cnt > 0 {
log.Trace("broadcast", "num. messages", cnt)
}
return nil
}
func (p *Peer) ID() []byte {
id := p.peer.ID()
return id[:]
}
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisperv6
import (
"bytes"
"crypto/ecdsa"
"fmt"
"net"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/nat"
)
var keys []string = []string{
"d49dcf37238dc8a7aac57dc61b9fee68f0a97f062968978b9fafa7d1033d03a9",
"73fd6143c48e80ed3c56ea159fe7494a0b6b393a392227b422f4c3e8f1b54f98",
"119dd32adb1daa7a4c7bf77f847fb28730785aa92947edf42fdd997b54de40dc",
"deeda8709dea935bb772248a3144dea449ffcc13e8e5a1fd4ef20ce4e9c87837",
"5bd208a079633befa349441bdfdc4d85ba9bd56081525008380a63ac38a407cf",
"1d27fb4912002d58a2a42a50c97edb05c1b3dffc665dbaa42df1fe8d3d95c9b5",
"15def52800c9d6b8ca6f3066b7767a76afc7b611786c1276165fbc61636afb68",
"51be6ab4b2dc89f251ff2ace10f3c1cc65d6855f3e083f91f6ff8efdfd28b48c",
"ef1ef7441bf3c6419b162f05da6037474664f198b58db7315a6f4de52414b4a0",
"09bdf6985aabc696dc1fbeb5381aebd7a6421727343872eb2fadfc6d82486fd9",
"15d811bf2e01f99a224cdc91d0cf76cea08e8c67905c16fee9725c9be71185c4",
"2f83e45cf1baaea779789f755b7da72d8857aeebff19362dd9af31d3c9d14620",
"73f04e34ac6532b19c2aae8f8e52f38df1ac8f5cd10369f92325b9b0494b0590",
"1e2e07b69e5025537fb73770f483dc8d64f84ae3403775ef61cd36e3faf162c1",
"8963d9bbb3911aac6d30388c786756b1c423c4fbbc95d1f96ddbddf39809e43a",
"0422da85abc48249270b45d8de38a4cc3c02032ede1fcf0864a51092d58a2f1f",
"8ae5c15b0e8c7cade201fdc149831aa9b11ff626a7ffd27188886cc108ad0fa8",
"acd8f5a71d4aecfcb9ad00d32aa4bcf2a602939b6a9dd071bab443154184f805",
"a285a922125a7481600782ad69debfbcdb0316c1e97c267aff29ef50001ec045",
"28fd4eee78c6cd4bf78f39f8ab30c32c67c24a6223baa40e6f9c9a0e1de7cef5",
"c5cca0c9e6f043b288c6f1aef448ab59132dab3e453671af5d0752961f013fc7",
"46df99b051838cb6f8d1b73f232af516886bd8c4d0ee07af9a0a033c391380fd",
"c6a06a53cbaadbb432884f36155c8f3244e244881b5ee3e92e974cfa166d793f",
"783b90c75c63dc72e2f8d11b6f1b4de54d63825330ec76ee8db34f06b38ea211",
"9450038f10ca2c097a8013e5121b36b422b95b04892232f930a29292d9935611",
"e215e6246ed1cfdcf7310d4d8cdbe370f0d6a8371e4eb1089e2ae05c0e1bc10f",
"487110939ed9d64ebbc1f300adeab358bc58875faf4ca64990fbd7fe03b78f2b",
"824a70ea76ac81366da1d4f4ac39de851c8ac49dca456bb3f0a186ceefa269a5",
"ba8f34fa40945560d1006a328fe70c42e35cc3d1017e72d26864cd0d1b150f15",
"30a5dfcfd144997f428901ea88a43c8d176b19c79dde54cc58eea001aa3d246c",
"de59f7183aca39aa245ce66a05245fecfc7e2c75884184b52b27734a4a58efa2",
"92629e2ff5f0cb4f5f08fffe0f64492024d36f045b901efb271674b801095c5a",
"7184c1701569e3a4c4d2ddce691edd983b81e42e09196d332e1ae2f1e062cff4",
}
const NumNodes = 16 // must not exceed the number of keys (32)
type TestData struct {
counter [NumNodes]int
mutex sync.RWMutex
}
type TestNode struct {
shh *Whisper
id *ecdsa.PrivateKey
server *p2p.Server
filerId string
}
var result TestData
var nodes [NumNodes]*TestNode
var sharedKey []byte = []byte("some arbitrary data here")
var sharedTopic TopicType = TopicType{0xF, 0x1, 0x2, 0}
var expectedMessage []byte = []byte("per rectum ad astra")
// This test does the following:
// 1. creates a chain of whisper nodes,
// 2. installs the filters with shared (predefined) parameters,
// 3. each node sends a number of random (undecryptable) messages,
// 4. first node sends one expected (decryptable) message,
// 5. checks if each node have received and decrypted exactly one message.
func TestSimulation(t *testing.T) {
initialize(t)
for i := 0; i < NumNodes; i++ {
sendMsg(t, false, i)
}
sendMsg(t, true, 0)
checkPropagation(t)
stopServers()
}
func initialize(t *testing.T) {
var err error
ip := net.IPv4(127, 0, 0, 1)
port0 := 30303
for i := 0; i < NumNodes; i++ {
var node TestNode
node.shh = New(&DefaultConfig)
node.shh.SetMinimumPoW(0.00000001)
node.shh.Start(nil)
topics := make([]TopicType, 0)
topics = append(topics, sharedTopic)
f := Filter{KeySym: sharedKey}
f.Topics = [][]byte{topics[0][:]}
node.filerId, err = node.shh.Subscribe(&f)
if err != nil {
t.Fatalf("failed to install the filter: %s.", err)
}
node.id, err = crypto.HexToECDSA(keys[i])
if err != nil {
t.Fatalf("failed convert the key: %s.", keys[i])
}
port := port0 + i
addr := fmt.Sprintf(":%d", port) // e.g. ":30303"
name := common.MakeName("whisper-go", "2.0")
var peers []*discover.Node
if i > 0 {
peerNodeId := nodes[i-1].id
peerPort := uint16(port - 1)
peerNode := discover.PubkeyID(&peerNodeId.PublicKey)
peer := discover.NewNode(peerNode, ip, peerPort, peerPort)
peers = append(peers, peer)
}
node.server = &p2p.Server{
Config: p2p.Config{
PrivateKey: node.id,
MaxPeers: NumNodes/2 + 1,
Name: name,
Protocols: node.shh.Protocols(),
ListenAddr: addr,
NAT: nat.Any(),
BootstrapNodes: peers,
StaticNodes: peers,
TrustedNodes: peers,
},
}
err = node.server.Start()
if err != nil {
t.Fatalf("failed to start server %d.", i)
}
nodes[i] = &node
}
}
func stopServers() {
for i := 0; i < NumNodes; i++ {
n := nodes[i]
if n != nil {
n.shh.Unsubscribe(n.filerId)
n.shh.Stop()
n.server.Stop()
}
}
}
func checkPropagation(t *testing.T) {
if t.Failed() {
return
}
const cycle = 100
const iterations = 100
for j := 0; j < iterations; j++ {
time.Sleep(cycle * time.Millisecond)
for i := 0; i < NumNodes; i++ {
f := nodes[i].shh.GetFilter(nodes[i].filerId)
if f == nil {
t.Fatalf("failed to get filterId %s from node %d.", nodes[i].filerId, i)
}
mail := f.Retrieve()
if !validateMail(t, i, mail) {
return
}
if isTestComplete() {
return
}
}
}
t.Fatalf("Test was not complete: timeout %d seconds.", iterations*cycle/1000)
}
func validateMail(t *testing.T, index int, mail []*ReceivedMessage) bool {
var cnt int
for _, m := range mail {
if bytes.Equal(m.Payload, expectedMessage) {
cnt++
}
}
if cnt == 0 {
// no messages received yet: nothing is wrong
return true
}
if cnt > 1 {
t.Fatalf("node %d received %d.", index, cnt)
return false
}
if cnt > 0 {
result.mutex.Lock()
defer result.mutex.Unlock()
result.counter[index] += cnt
if result.counter[index] > 1 {
t.Fatalf("node %d accumulated %d.", index, result.counter[index])
}
}
return true
}
func isTestComplete() bool {
result.mutex.RLock()
defer result.mutex.RUnlock()
for i := 0; i < NumNodes; i++ {
if result.counter[i] < 1 {
return false
}
}
for i := 0; i < NumNodes; i++ {
envelopes := nodes[i].shh.Envelopes()
if len(envelopes) < 2 {
return false
}
}
return true
}
func sendMsg(t *testing.T, expected bool, id int) {
if t.Failed() {
return
}
opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1}
if !expected {
opt.KeySym[0]++
opt.Topic[0]++
opt.Payload = opt.Payload[1:]
}
msg, err := NewSentMessage(&opt)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
envelope, err := msg.Wrap(&opt)
if err != nil {
t.Fatalf("failed to seal message: %s", err)
}
err = nodes[id].shh.Send(envelope)
if err != nil {
t.Fatalf("failed to send message: %s", err)
}
}
func TestPeerBasic(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d.", seed)
}
params.PoW = 0.001
msg, err := NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d.", seed)
}
p := newPeer(nil, nil, nil)
p.mark(env)
if !p.marked(env) {
t.Fatalf("failed mark with seed %d.", seed)
}
}
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the Whisper protocol Topic element.
package whisperv6
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// Topic represents a cryptographically secure, probabilistic partial
// classifications of a message, determined as the first (left) 4 bytes of the
// SHA3 hash of some arbitrary data given by the original author of the message.
type TopicType [TopicLength]byte
func BytesToTopic(b []byte) (t TopicType) {
sz := TopicLength
if x := len(b); x < TopicLength {
sz = x
}
for i := 0; i < sz; i++ {
t[i] = b[i]
}
return t
}
// String converts a topic byte array to a string representation.
func (topic *TopicType) String() string {
return string(common.ToHex(topic[:]))
}
// MarshalText returns the hex representation of t.
func (t TopicType) MarshalText() ([]byte, error) {
return hexutil.Bytes(t[:]).MarshalText()
}
// UnmarshalText parses a hex representation to a topic.
func (t *TopicType) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("Topic", input, t[:])
}
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package whisperv6
import (
"encoding/json"
"testing"
)
var topicStringTests = []struct {
topic TopicType
str string
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, str: "0x00000000"},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, str: "0x007f80ff"},
{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, str: "0xff807f00"},
{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, str: "0xf26e7779"},
}
func TestTopicString(t *testing.T) {
for i, tst := range topicStringTests {
s := tst.topic.String()
if s != tst.str {
t.Fatalf("failed test %d: have %s, want %s.", i, s, tst.str)
}
}
}
var bytesToTopicTests = []struct {
data []byte
topic TopicType
}{
{topic: TopicType{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte{0x8f, 0x9a, 0x2b, 0x7d}},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte{0x00, 0x7f, 0x80, 0xff}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00, 0x00}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00}},
{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte{0x01}},
{topic: TopicType{0x00, 0xfe, 0x00, 0x00}, data: []byte{0x00, 0xfe}},
{topic: TopicType{0xea, 0x1d, 0x43, 0x00}, data: []byte{0xea, 0x1d, 0x43}},
{topic: TopicType{0x6f, 0x3c, 0xb0, 0xdd}, data: []byte{0x6f, 0x3c, 0xb0, 0xdd, 0x0f, 0x00, 0x90}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{}},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: nil},
}
var unmarshalTestsGood = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x00000000"`)},
{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte(`"0x007f80ff"`)},
{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte(`"0xff807f00"`)},
{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte(`"0xf26e7779"`)},
}
var unmarshalTestsBad = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000000"`)},
{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"abcdefg0"`)},
}
var unmarshalTestsUgly = []struct {
topic TopicType
data []byte
}{
{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte(`"0x00000001"`)},
}
func TestBytesToTopic(t *testing.T) {
for i, tst := range bytesToTopicTests {
top := BytesToTopic(tst.data)
if top != tst.topic {
t.Fatalf("failed test %d: have %v, want %v.", i, t, tst.topic)
}
}
}
func TestUnmarshalTestsGood(t *testing.T) {
for i, tst := range unmarshalTestsGood {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err != nil {
t.Errorf("failed test %d. input: %v. err: %v", i, tst.data, err)
} else if top != tst.topic {
t.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic)
}
}
}
func TestUnmarshalTestsBad(t *testing.T) {
// in this test UnmarshalJSON() is supposed to fail
for i, tst := range unmarshalTestsBad {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err == nil {
t.Fatalf("failed test %d. input: %v.", i, tst.data)
}
}
}
func TestUnmarshalTestsUgly(t *testing.T) {
// in this test UnmarshalJSON() is NOT supposed to fail, but result should be wrong
for i, tst := range unmarshalTestsUgly {
var top TopicType
err := json.Unmarshal(tst.data, &top)
if err != nil {
t.Errorf("failed test %d. input: %v.", i, tst.data)
} else if top == tst.topic {
t.Errorf("failed test %d: have %v, want %v.", i, top, tst.topic)
}
}
}
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment