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 {
LastEnvelopeHash common.Hash
Cursor []byte
Error error
}
// 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")
}
// 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.
// length := len(payload)
event, err := CreateMailServerEvent(payload)
if len(payload) < common.HashLength || len(payload) > common.HashLength*3+4 {
log.Warn("invalid response message, peer will be disconnected", "peer", p.peer.ID(), "err", err, "payload size", len(payload))
return errors.New("invalid response size")
if err != nil {
log.Warn("error while parsing request complete code, peer will be disconnected", "peer", p.peer.ID(), "err", err)
return err
}
var (
requestID common.Hash
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 event != nil {
whisper.envelopeFeed.Send(*event)
}
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:
// New message types might be implemented in the future versions of Whisper.