Add an response that mailserver failed to complete request.

This commit is contained in:
Igor Mandrigin 2018-10-16 17:20:37 +02:00 committed by Igor Mandrigin
parent ca91ec35f6
commit 14e1bbfd9b
3 changed files with 273 additions and 33 deletions

View File

@ -0,0 +1,136 @@
package whisperv6
import (
"bytes"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
)
const (
mailServerFailedPayloadPrefix = "ERROR="
cursorSize = 36
)
func invalidResponseSizeError(size int) error {
return fmt.Errorf("unexpected payload size: %d", size)
}
// CreateMailServerRequestCompletedPayload creates a payload representing
// a successful request to mailserver
func CreateMailServerRequestCompletedPayload(requestID, lastEnvelopeHash common.Hash, cursor []byte) []byte {
payload := append(requestID[:], lastEnvelopeHash[:]...)
payload = append(payload, cursor...)
return payload
}
// CreateMailServerRequestFailedPayload creates a payload representing
// a failed request to a mailserver
func CreateMailServerRequestFailedPayload(requestID common.Hash, err error) []byte {
payloadPrefix := []byte(mailServerFailedPayloadPrefix)
errorString := []byte(err.Error())
payload := append(payloadPrefix, requestID[:]...)
payload = append(payload, errorString[:]...)
return payload
}
// CreateMailServerEvent returns EnvelopeEvent with correct data
// if payload corresponds to any of the know mailserver events:
// * request completed successfully
// * request failed
// If the payload is unknown/unparseable, it returns `nil`
func CreateMailServerEvent(payload []byte) (*EnvelopeEvent, error) {
if len(payload) < common.HashLength {
return nil, invalidResponseSizeError(len(payload))
}
event, err := tryCreateMailServerRequestFailedEvent(payload)
if err != nil || event != nil {
return event, err
}
return tryCreateMailServerRequestCompletedEvent(payload)
}
func tryCreateMailServerRequestFailedEvent(payload []byte) (*EnvelopeEvent, error) {
if len(payload) < common.HashLength+len(mailServerFailedPayloadPrefix) {
return nil, nil
}
prefix, remainder := extractPrefix(payload, len(mailServerFailedPayloadPrefix))
if !bytes.Equal(prefix, []byte(mailServerFailedPayloadPrefix)) {
return nil, nil
}
var (
requestID common.Hash
errorMsg string
)
requestID, remainder = extractHash(remainder)
errorMsg = string(remainder)
event := EnvelopeEvent{
Hash: requestID,
Event: EventMailServerRequestCompleted,
Data: &MailServerResponse{
Error: errors.New(errorMsg),
},
}
return &event, nil
}
func tryCreateMailServerRequestCompletedEvent(payload []byte) (*EnvelopeEvent, error) {
// check if payload is
// - requestID or
// - requestID + lastEnvelopeHash or
// - requestID + lastEnvelopeHash + cursor
// requestID is the hash of the request envelope.
// lastEnvelopeHash is the last envelope sent by the mail server
// cursor is the db key, 36 bytes: 4 for the timestamp + 32 for the envelope hash.
if len(payload) > common.HashLength*2+cursorSize {
return nil, invalidResponseSizeError(len(payload))
}
var (
requestID common.Hash
lastEnvelopeHash common.Hash
cursor []byte
)
requestID, remainder := extractHash(payload)
if len(remainder) >= common.HashLength {
lastEnvelopeHash, remainder = extractHash(remainder)
}
if len(remainder) >= cursorSize {
cursor = remainder
}
event := EnvelopeEvent{
Hash: requestID,
Event: EventMailServerRequestCompleted,
Data: &MailServerResponse{
LastEnvelopeHash: lastEnvelopeHash,
Cursor: cursor,
},
}
return &event, nil
}
func extractHash(payload []byte) (common.Hash, []byte) {
prefix, remainder := extractPrefix(payload, common.HashLength)
return common.BytesToHash(prefix), remainder
}
func extractPrefix(payload []byte, size int) ([]byte, []byte) {
return payload[:size], payload[size:]
}

View File

@ -0,0 +1,130 @@
package whisperv6
import (
"bytes"
"encoding/binary"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/syndtr/goleveldb/leveldb/errors"
)
func checkValidErrorPayload(t *testing.T, id []byte, errorMsg string) {
requestID := common.BytesToHash(id)
errPayload := CreateMailServerRequestFailedPayload(requestID, errors.New(errorMsg))
event, err := CreateMailServerEvent(errPayload)
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
if event == nil {
t.Errorf("Could not parse payload: %v", errPayload)
return
}
if !bytes.Equal(event.Hash[:], requestID[:]) {
t.Errorf("Unexpected hash: %v, expected %v", event.Hash, requestID)
return
}
eventData, ok := event.Data.(*MailServerResponse)
if !ok {
t.Errorf("Unexpected data in event: %v, expected a MailServerResponse", event.Data)
return
}
if strings.Compare(eventData.Error.Error(), errorMsg) != 0 {
t.Errorf("Unexpected error string: '%s', expected '%s'", eventData, errorMsg)
return
}
}
func checkValidSuccessPayload(t *testing.T, id []byte, lastHash []byte, timestamp uint32, envHash []byte) {
requestID := common.BytesToHash(id)
lastEnvelopeHash := common.BytesToHash(lastHash)
timestampBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(timestampBytes, timestamp)
envelopeHash := common.BytesToHash(envHash)
cursor := append(timestampBytes, envelopeHash[:]...)
successPayload := CreateMailServerRequestCompletedPayload(requestID, lastEnvelopeHash, cursor)
event, err := CreateMailServerEvent(successPayload)
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
if event == nil {
t.Errorf("Could not parse payload: %v", successPayload)
return
}
if !bytes.Equal(event.Hash[:], requestID[:]) {
t.Errorf("Unexpected hash: %v, expected %v", event.Hash, requestID)
return
}
eventData, ok := event.Data.(*MailServerResponse)
if !ok {
t.Errorf("Unexpected data in event: %v, expected a MailServerResponse", event.Data)
return
}
if !bytes.Equal(eventData.LastEnvelopeHash[:], lastEnvelopeHash[:]) {
t.Errorf("Unexpected LastEnvelopeHash: %v, expected %v",
eventData.LastEnvelopeHash, lastEnvelopeHash)
return
}
if !bytes.Equal(eventData.Cursor, cursor) {
t.Errorf("Unexpected cursor: %v, expected: %v", eventData.Cursor, cursor)
return
}
if eventData.Error != nil {
t.Errorf("Unexpected error: %v", eventData.Error)
return
}
}
func TestCreateMailServerEvent(t *testing.T) {
// valid cases
longErrorMessage := "longMessage|"
for i := 0; i < 5; i++ {
longErrorMessage = longErrorMessage + longErrorMessage
}
checkValidErrorPayload(t, []byte{0x01}, "test error 1")
checkValidErrorPayload(t, []byte{0x02}, "test error 2")
checkValidErrorPayload(t, []byte{0x02}, "")
checkValidErrorPayload(t, []byte{0x00}, "test error 3")
checkValidErrorPayload(t, []byte{}, "test error 4")
checkValidSuccessPayload(t, []byte{0x01}, []byte{0x02}, 123, []byte{0x03})
// invalid payloads
// too small
_, err := CreateMailServerEvent([]byte{0x00})
if err == nil {
t.Errorf("Expected an error, got nil")
return
}
// too big and not error payload
payloadTooBig := make([]byte, common.HashLength*2+cursorSize+100)
_, err = CreateMailServerEvent(payloadTooBig)
if err == nil {
t.Errorf("Expected an error, got nil")
return
}
}

View File

@ -66,6 +66,7 @@ const (
type MailServerResponse struct { type MailServerResponse struct {
LastEnvelopeHash common.Hash LastEnvelopeHash common.Hash
Cursor []byte Cursor []byte
Error error
} }
// Whisper represents a dark communication interface through the Ethereum // Whisper represents a dark communication interface through the Ethereum
@ -911,44 +912,17 @@ func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
return errors.New("invalid request response message") return errors.New("invalid request response message")
} }
// check if payload is event, err := CreateMailServerEvent(payload)
// - requestID or
// - requestID + lastEnvelopeHash or
// - requestID + lastEnvelopeHash + cursor
// requestID is the hash of the request envelope.
// lastEnvelopeHash is the last envelope sent by the mail server
// cursor is the db key, 36 bytes: 4 for the timestamp + 32 for the envelope hash.
// length := len(payload)
if len(payload) < common.HashLength || len(payload) > common.HashLength*3+4 { if err != nil {
log.Warn("invalid response message, peer will be disconnected", "peer", p.peer.ID(), "err", err, "payload size", len(payload)) log.Warn("error while parsing request complete code, peer will be disconnected", "peer", p.peer.ID(), "err", err)
return errors.New("invalid response size") return err
} }
var ( if event != nil {
requestID common.Hash whisper.envelopeFeed.Send(*event)
lastEnvelopeHash common.Hash
cursor []byte
)
requestID = common.BytesToHash(payload[:common.HashLength])
if len(payload) >= common.HashLength*2 {
lastEnvelopeHash = common.BytesToHash(payload[common.HashLength : common.HashLength*2])
} }
if len(payload) >= common.HashLength*2+36 {
cursor = payload[common.HashLength*2 : common.HashLength*2+36]
}
whisper.envelopeFeed.Send(EnvelopeEvent{
Hash: requestID,
Event: EventMailServerRequestCompleted,
Data: &MailServerResponse{
LastEnvelopeHash: lastEnvelopeHash,
Cursor: cursor,
},
})
} }
default: default:
// New message types might be implemented in the future versions of Whisper. // New message types might be implemented in the future versions of Whisper.