hash-of-wisdom/internal/protocol/roundtrip_test.go

206 lines
6.1 KiB
Go

package protocol
import (
"bytes"
"encoding/binary"
"encoding/json"
"testing"
"hash-of-wisdom/internal/pow/challenge"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestChallengeResponse_BinaryFormat(t *testing.T) {
testChallenge := &challenge.Challenge{
Timestamp: 1640995200,
Difficulty: 4,
Resource: "quotes",
Random: []byte("test-random"),
HMAC: []byte("test-hmac"),
}
response := &ChallengeResponse{Challenge: testChallenge}
var buf bytes.Buffer
err := response.Encode(&buf)
require.NoError(t, err)
// Verify binary format
data := buf.Bytes()
assert.GreaterOrEqual(t, len(data), 5)
assert.Equal(t, byte(ChallengeResponseType), data[0])
payloadLength := binary.BigEndian.Uint32(data[1:5])
assert.Greater(t, payloadLength, uint32(0))
assert.Equal(t, len(data)-5, int(payloadLength))
// Verify payload content
payload := string(data[5:])
assert.Contains(t, payload, "1640995200")
assert.Contains(t, payload, "quotes")
}
func TestSolutionRequest_RoundTrip(t *testing.T) {
decoder := NewMessageDecoder()
payload := `{"challenge":{"timestamp":1640995200,"difficulty":4,"resource":"quotes","random":"dGVzdC1yYW5kb20=","hmac":"dGVzdC1obWFj"},"nonce":12345}`
var buf bytes.Buffer
buf.WriteByte(byte(SolutionRequestType))
binary.Write(&buf, binary.BigEndian, uint32(len(payload)))
buf.WriteString(payload)
// Decode header
msg, err := decoder.Decode(&buf)
require.NoError(t, err)
assert.Equal(t, SolutionRequestType, msg.Type)
assert.Equal(t, uint32(len(payload)), msg.PayloadLength)
// Decode request
req := &SolutionRequest{}
err = req.Decode(msg.PayloadStream)
require.NoError(t, err)
assert.Equal(t, int64(1640995200), req.Challenge.Timestamp)
assert.Equal(t, 4, req.Challenge.Difficulty)
assert.Equal(t, "quotes", req.Challenge.Resource)
assert.Equal(t, uint64(12345), req.Nonce)
assert.Equal(t, []byte("test-random"), req.Challenge.Random)
assert.Equal(t, []byte("test-hmac"), req.Challenge.HMAC)
}
func TestErrorResponse_BinaryFormat(t *testing.T) {
errorResp := &ErrorResponse{
Code: "INVALID_SOLUTION",
Message: "Test error message",
RetryAfter: 30,
Details: map[string]string{"reason": "test"},
}
var buf bytes.Buffer
err := errorResp.Encode(&buf)
require.NoError(t, err)
data := buf.Bytes()
assert.Equal(t, byte(ErrorResponseType), data[0])
length := binary.BigEndian.Uint32(data[1:5])
assert.Greater(t, length, uint32(0))
assert.LessOrEqual(t, length, uint32(MaxPayloadSize))
payload := string(data[5:])
assert.Contains(t, payload, "INVALID_SOLUTION")
assert.Contains(t, payload, "Test error message")
assert.Contains(t, payload, "30")
assert.Contains(t, payload, "test")
}
func TestChallengeRequest_EmptyPayload(t *testing.T) {
decoder := NewMessageDecoder()
data := []byte{0x01, 0x00, 0x00, 0x00, 0x00}
buf := bytes.NewBuffer(data)
msg, err := decoder.Decode(buf)
require.NoError(t, err)
assert.Equal(t, ChallengeRequestType, msg.Type)
assert.Equal(t, uint32(0), msg.PayloadLength)
assert.Nil(t, msg.PayloadStream)
req := &ChallengeRequest{}
err = req.Decode(msg.PayloadStream)
require.NoError(t, err)
}
func TestMessageDecoder_RejectsResponseTypes(t *testing.T) {
decoder := NewMessageDecoder()
data := []byte{byte(ErrorResponseType), 0x00, 0x00, 0x00, 0x05, 'h', 'e', 'l', 'l', 'o'}
buf := bytes.NewBuffer(data)
_, err := decoder.Decode(buf)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid message type")
}
func TestPayloadStream_LimitedRead(t *testing.T) {
decoder := NewMessageDecoder()
payload := `{"challenge":{"timestamp":1640995200,"difficulty":4,"resource":"quotes","random":"dGVzdA==","hmac":"dGVzdA=="},"nonce":999}`
extraData := "this should not be read"
var buf bytes.Buffer
buf.WriteByte(byte(SolutionRequestType))
binary.Write(&buf, binary.BigEndian, uint32(len(payload)))
buf.WriteString(payload)
buf.WriteString(extraData)
msg, err := decoder.Decode(&buf)
require.NoError(t, err)
req := &SolutionRequest{}
err = req.Decode(msg.PayloadStream)
require.NoError(t, err)
assert.Equal(t, uint64(999), req.Nonce)
// Verify extra data wasn't consumed
remaining := buf.String()
assert.Equal(t, extraData, remaining)
}
func TestTrueRoundTrip_ServerClientCommunication(t *testing.T) {
// Simulate actual server-client communication
// 1. SERVER: Create and encode a challenge response
originalChallenge := &challenge.Challenge{
Timestamp: 1640995200,
Difficulty: 4,
Resource: "quotes",
Random: []byte("server-random-data"),
HMAC: []byte("server-hmac-signature"),
}
serverResponse := &ChallengeResponse{Challenge: originalChallenge}
var networkBuffer bytes.Buffer
// Server encodes response to "network"
err := serverResponse.Encode(&networkBuffer)
require.NoError(t, err)
// 2. NETWORK: Simulate transmission (networkBuffer contains binary data)
wireData := networkBuffer.Bytes()
assert.Greater(t, len(wireData), 5) // Has header + payload
// 3. CLIENT: Receives and decodes the binary data
// (Client would use a generic decoder, not our server-side MessageDecoder)
clientBuf := bytes.NewBuffer(wireData)
// Client reads header manually
var msgType MessageType
err = binary.Read(clientBuf, binary.BigEndian, &msgType)
require.NoError(t, err)
assert.Equal(t, ChallengeResponseType, msgType)
var payloadLength uint32
err = binary.Read(clientBuf, binary.BigEndian, &payloadLength)
require.NoError(t, err)
// Client reads payload
payloadBytes := make([]byte, payloadLength)
_, err = clientBuf.Read(payloadBytes)
require.NoError(t, err)
// Client deserializes the challenge
var receivedChallenge challenge.Challenge
err = json.Unmarshal(payloadBytes, &receivedChallenge)
require.NoError(t, err)
// 4. VERIFY: Client received exactly what server sent
assert.Equal(t, originalChallenge.Timestamp, receivedChallenge.Timestamp)
assert.Equal(t, originalChallenge.Difficulty, receivedChallenge.Difficulty)
assert.Equal(t, originalChallenge.Resource, receivedChallenge.Resource)
assert.Equal(t, originalChallenge.Random, receivedChallenge.Random)
assert.Equal(t, originalChallenge.HMAC, receivedChallenge.HMAC)
}