Phase 4: Implement binary parsing #4
|
|
@ -60,22 +60,27 @@
|
||||||
- [X] Add workflow tests with real PoW implementations
|
- [X] Add workflow tests with real PoW implementations
|
||||||
- [X] Add unsuccessful flow tests for invalid solutions
|
- [X] Add unsuccessful flow tests for invalid solutions
|
||||||
|
|
||||||
## Phase 4: Basic Server Architecture
|
## Phase 4: Binary Protocol Implementation
|
||||||
|
- [X] Implement binary message protocol codec with Reader/Writer abstraction
|
||||||
|
- [X] Create protocol message types and structures
|
||||||
|
- [X] Add message serialization/deserialization (JSON)
|
||||||
|
- [X] Implement protocol parsing with proper error handling
|
||||||
|
- [X] Create message validation and bounds checking
|
||||||
|
- [X] Write unit tests for protocol components
|
||||||
|
|
||||||
|
## Phase 5: Basic Server Architecture
|
||||||
- [ ] Set up structured logging (zerolog/logrus)
|
- [ ] Set up structured logging (zerolog/logrus)
|
||||||
- [ ] Set up metrics collection (prometheus)
|
- [ ] Set up metrics collection (prometheus)
|
||||||
- [ ] Create configuration management
|
- [ ] Create configuration management
|
||||||
- [ ] Integrate all components into server architecture
|
- [ ] Integrate all components into server architecture
|
||||||
|
|
||||||
## Phase 5: TCP Protocol Implementation
|
## Phase 6: TCP Protocol & Connection Handling
|
||||||
- [ ] Implement binary message protocol codec
|
|
||||||
- [ ] Create protocol message types and structures
|
|
||||||
- [ ] Implement connection handler with proper error handling
|
- [ ] Implement connection handler with proper error handling
|
||||||
- [ ] Add message serialization/deserialization (JSON)
|
|
||||||
- [ ] Create protocol state machine
|
- [ ] Create protocol state machine
|
||||||
- [ ] Implement connection lifecycle management
|
- [ ] Implement connection lifecycle management
|
||||||
- [ ] Write unit tests for protocol components
|
- [ ] Add connection timeout and lifecycle management
|
||||||
|
|
||||||
## Phase 6: Server Core & Request Handling
|
## Phase 7: Server Core & Request Handling
|
||||||
- [ ] Implement TCP server with connection pooling
|
- [ ] Implement TCP server with connection pooling
|
||||||
- [ ] Create request router and handler dispatcher
|
- [ ] Create request router and handler dispatcher
|
||||||
- [ ] Add connection timeout and lifecycle management
|
- [ ] Add connection timeout and lifecycle management
|
||||||
|
|
@ -84,7 +89,7 @@
|
||||||
- [ ] Create health check endpoints
|
- [ ] Create health check endpoints
|
||||||
- [ ] Write integration tests for server core
|
- [ ] Write integration tests for server core
|
||||||
|
|
||||||
## Phase 7: DDOS Protection & Rate Limiting
|
## Phase 9: DDOS Protection & Rate Limiting
|
||||||
- [ ] Implement IP-based connection limiting
|
- [ ] Implement IP-based connection limiting
|
||||||
- [ ] Create rate limiting service with time windows
|
- [ ] Create rate limiting service with time windows
|
||||||
- [ ] Add automatic difficulty adjustment based on load
|
- [ ] Add automatic difficulty adjustment based on load
|
||||||
|
|
@ -93,7 +98,7 @@
|
||||||
- [ ] Add monitoring for attack detection
|
- [ ] Add monitoring for attack detection
|
||||||
- [ ] Write tests for protection mechanisms
|
- [ ] Write tests for protection mechanisms
|
||||||
|
|
||||||
## Phase 8: Observability & Monitoring
|
## Phase 9: Observability & Monitoring
|
||||||
- [ ] Add structured logging throughout application
|
- [ ] Add structured logging throughout application
|
||||||
- [ ] Implement metrics for key performance indicators:
|
- [ ] Implement metrics for key performance indicators:
|
||||||
- [ ] Active connections count
|
- [ ] Active connections count
|
||||||
|
|
@ -105,7 +110,7 @@
|
||||||
- [ ] Add error categorization and reporting
|
- [ ] Add error categorization and reporting
|
||||||
- [ ] Implement health check endpoints
|
- [ ] Implement health check endpoints
|
||||||
|
|
||||||
## Phase 9: Configuration & Environment Setup
|
## Phase 10: Configuration & Environment Setup
|
||||||
- [ ] Create configuration structure with validation
|
- [ ] Create configuration structure with validation
|
||||||
- [ ] Support environment variables and config files
|
- [ ] Support environment variables and config files
|
||||||
- [ ] Add configuration for different environments (dev/prod)
|
- [ ] Add configuration for different environments (dev/prod)
|
||||||
|
|
@ -113,7 +118,7 @@
|
||||||
- [ ] Create deployment configuration templates
|
- [ ] Create deployment configuration templates
|
||||||
- [ ] Add configuration validation and defaults
|
- [ ] Add configuration validation and defaults
|
||||||
|
|
||||||
## Phase 10: Client Implementation
|
## Phase 11: Client Implementation
|
||||||
- [ ] Create client application structure
|
- [ ] Create client application structure
|
||||||
- [ ] Implement PoW solver algorithm
|
- [ ] Implement PoW solver algorithm
|
||||||
- [ ] Create client-side protocol implementation
|
- [ ] Create client-side protocol implementation
|
||||||
|
|
@ -123,7 +128,7 @@
|
||||||
- [ ] Add client metrics and logging
|
- [ ] Add client metrics and logging
|
||||||
- [ ] Write client unit and integration tests
|
- [ ] Write client unit and integration tests
|
||||||
|
|
||||||
## Phase 11: Docker & Deployment
|
## Phase 12: Docker & Deployment
|
||||||
- [ ] Create multi-stage Dockerfile for server
|
- [ ] Create multi-stage Dockerfile for server
|
||||||
- [ ] Create Dockerfile for client
|
- [ ] Create Dockerfile for client
|
||||||
- [ ] Create docker-compose.yml for local development
|
- [ ] Create docker-compose.yml for local development
|
||||||
|
|
@ -132,7 +137,7 @@
|
||||||
- [ ] Add environment-specific configurations
|
- [ ] Add environment-specific configurations
|
||||||
- [ ] Create deployment documentation
|
- [ ] Create deployment documentation
|
||||||
|
|
||||||
## Phase 12: Testing & Quality Assurance
|
## Phase 13: Testing & Quality Assurance
|
||||||
- [ ] Write comprehensive unit tests (>80% coverage):
|
- [ ] Write comprehensive unit tests (>80% coverage):
|
||||||
- [ ] PoW algorithm tests
|
- [ ] PoW algorithm tests
|
||||||
- [ ] Protocol handler tests
|
- [ ] Protocol handler tests
|
||||||
|
|
@ -147,7 +152,7 @@
|
||||||
- [ ] Add benchmark tests for performance validation
|
- [ ] Add benchmark tests for performance validation
|
||||||
- [ ] Create stress testing scenarios
|
- [ ] Create stress testing scenarios
|
||||||
|
|
||||||
## Phase 13: Documentation & Final Polish
|
## Phase 14: Documentation & Final Polish
|
||||||
- [ ] Write comprehensive README with setup instructions
|
- [ ] Write comprehensive README with setup instructions
|
||||||
- [ ] Create API documentation for all interfaces
|
- [ ] Create API documentation for all interfaces
|
||||||
- [ ] Add inline code documentation
|
- [ ] Add inline code documentation
|
||||||
|
|
@ -156,7 +161,7 @@
|
||||||
- [ ] Add performance tuning recommendations
|
- [ ] Add performance tuning recommendations
|
||||||
- [ ] Create monitoring and alerting guide
|
- [ ] Create monitoring and alerting guide
|
||||||
|
|
||||||
## Phase 14: Production Readiness Checklist
|
## Phase 15: Production Readiness Checklist
|
||||||
- [ ] Security audit of all components
|
- [ ] Security audit of all components
|
||||||
- [ ] Performance benchmarking and optimization
|
- [ ] Performance benchmarking and optimization
|
||||||
- [ ] Memory leak detection and prevention
|
- [ ] Memory leak detection and prevention
|
||||||
|
|
|
||||||
111
internal/protocol/codec.go
Normal file
111
internal/protocol/codec.go
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Codec handles encoding and decoding of protocol messages
|
||||||
|
type Codec struct{}
|
||||||
|
|
||||||
|
// NewCodec creates a new protocol codec
|
||||||
|
func NewCodec() *Codec {
|
||||||
|
return &Codec{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes a message to the writer using the protocol format
|
||||||
|
func (c *Codec) Encode(w io.Writer, msg *Message) error {
|
||||||
|
if msg == nil {
|
||||||
|
return fmt.Errorf("message cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate payload size
|
||||||
|
if len(msg.Payload) > MaxPayloadSize {
|
||||||
|
return fmt.Errorf("payload size %d exceeds maximum %d", len(msg.Payload), MaxPayloadSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write message type (1 byte)
|
||||||
|
if err := binary.Write(w, binary.BigEndian, msg.Type); err != nil {
|
||||||
|
return fmt.Errorf("failed to write message type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write payload length (4 bytes, big-endian)
|
||||||
|
payloadLength := uint32(len(msg.Payload))
|
||||||
|
if err := binary.Write(w, binary.BigEndian, payloadLength); err != nil {
|
||||||
|
return fmt.Errorf("failed to write payload length: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write payload if present
|
||||||
|
if len(msg.Payload) > 0 {
|
||||||
|
if _, err := w.Write(msg.Payload); err != nil {
|
||||||
|
return fmt.Errorf("failed to write payload: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads a message from the reader using the protocol format
|
||||||
|
func (c *Codec) Decode(r io.Reader) (*Message, error) {
|
||||||
|
// Read message type (1 byte)
|
||||||
|
var msgType MessageType
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &msgType); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to read message type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate message type
|
||||||
|
if !isValidMessageType(msgType) {
|
||||||
|
return nil, fmt.Errorf("invalid message type: 0x%02x", msgType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read payload length (4 bytes, big-endian)
|
||||||
|
var payloadLength uint32
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &payloadLength); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read payload length: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate payload length
|
||||||
|
if payloadLength > MaxPayloadSize {
|
||||||
|
return nil, fmt.Errorf("payload length %d exceeds maximum %d", payloadLength, MaxPayloadSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read payload if present
|
||||||
|
var payload []byte
|
||||||
|
if payloadLength > 0 {
|
||||||
|
payload = make([]byte, payloadLength)
|
||||||
|
// Use LimitReader to ensure we don't read more than payloadLength bytes
|
||||||
|
// even if the underlying reader has more data available
|
||||||
|
limitedReader := io.LimitReader(r, int64(payloadLength))
|
||||||
|
// Note: ReadFull may block waiting for data. The connection handler
|
||||||
|
// MUST set appropriate read deadlines to prevent slowloris attacks
|
||||||
|
if _, err := io.ReadFull(limitedReader, payload); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate payload is valid UTF-8
|
||||||
|
if !utf8.Valid(payload) {
|
||||||
|
return nil, fmt.Errorf("payload contains invalid UTF-8")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Message{
|
||||||
|
Type: msgType,
|
||||||
|
Payload: payload,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// isValidMessageType checks if the message type is defined in the protocol
|
||||||
|
func isValidMessageType(msgType MessageType) bool {
|
||||||
|
switch msgType {
|
||||||
|
case ChallengeRequest, ChallengeResponse, SolutionRequest, QuoteResponse, ErrorResponse:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
308
internal/protocol/codec_test.go
Normal file
308
internal/protocol/codec_test.go
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCodec_Encode_Decode(t *testing.T) {
|
||||||
|
codec := NewCodec()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
message *Message
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "challenge request (empty payload)",
|
||||||
|
message: &Message{
|
||||||
|
Type: ChallengeRequest,
|
||||||
|
Payload: []byte{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "challenge response with payload",
|
||||||
|
message: &Message{
|
||||||
|
Type: ChallengeResponse,
|
||||||
|
Payload: []byte(`{"challenge":{"timestamp":1640995200,"difficulty":4}}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error response",
|
||||||
|
message: &Message{
|
||||||
|
Type: ErrorResponse,
|
||||||
|
Payload: []byte(`{"code":"INVALID_SOLUTION","message":"Invalid nonce"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Encode message
|
||||||
|
err := codec.Encode(&buf, tt.message)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Decode message
|
||||||
|
decoded, err := codec.Decode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.message.Type, decoded.Type)
|
||||||
|
if len(tt.message.Payload) == 0 && len(decoded.Payload) == 0 {
|
||||||
|
// Both are empty (nil or empty slice)
|
||||||
|
assert.True(t, true)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, tt.message.Payload, decoded.Payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Encode_Errors(t *testing.T) {
|
||||||
|
codec := NewCodec()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
message *Message
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil message",
|
||||||
|
message: nil,
|
||||||
|
wantErr: "message cannot be nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "payload too large",
|
||||||
|
message: &Message{
|
||||||
|
Type: ChallengeRequest,
|
||||||
|
Payload: make([]byte, MaxPayloadSize+1),
|
||||||
|
},
|
||||||
|
wantErr: "payload size",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := codec.Encode(&buf, tt.message)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode_Errors(t *testing.T) {
|
||||||
|
codec := NewCodec()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty data",
|
||||||
|
data: []byte{},
|
||||||
|
wantErr: "EOF",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid message type",
|
||||||
|
data: []byte{0xFF, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
wantErr: "invalid message type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incomplete header",
|
||||||
|
data: []byte{0x01, 0x00, 0x00},
|
||||||
|
wantErr: "failed to read payload length",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "payload too large",
|
||||||
|
data: append([]byte{0x01}, encodeBigEndianUint32(MaxPayloadSize+1)...),
|
||||||
|
wantErr: "payload length",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incomplete payload",
|
||||||
|
data: []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x02},
|
||||||
|
wantErr: "failed to read payload",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid UTF-8 in payload",
|
||||||
|
data: []byte{0x01, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0xFD},
|
||||||
|
wantErr: "invalid UTF-8",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer(tt.data)
|
||||||
|
_, err := codec.Decode(buf)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestCodec_RoundTrip_RealPayloads(t *testing.T) {
|
||||||
|
codec := NewCodec()
|
||||||
|
|
||||||
|
t.Run("challenge response round trip", func(t *testing.T) {
|
||||||
|
original := &ChallengeResponsePayload{
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
Difficulty: 4,
|
||||||
|
Resource: "quotes",
|
||||||
|
Random: []byte("random123"),
|
||||||
|
HMAC: []byte("hmac_signature"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal payload
|
||||||
|
jsonData, err := json.Marshal(original)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg := &Message{
|
||||||
|
Type: ChallengeResponse,
|
||||||
|
Payload: jsonData,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate network transmission
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = codec.Encode(&buf, msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Decode message
|
||||||
|
decoded, err := codec.Decode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Unmarshal payload
|
||||||
|
var result ChallengeResponsePayload
|
||||||
|
err = json.Unmarshal(decoded.Payload, &result)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, original.Timestamp, result.Timestamp)
|
||||||
|
assert.Equal(t, original.Difficulty, result.Difficulty)
|
||||||
|
assert.Equal(t, original.Resource, result.Resource)
|
||||||
|
assert.Equal(t, original.Random, result.Random)
|
||||||
|
assert.Equal(t, original.HMAC, result.HMAC)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("quote response round trip", func(t *testing.T) {
|
||||||
|
original := &QuoteResponsePayload{
|
||||||
|
Text: "Test quote",
|
||||||
|
Author: "Test author",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal payload
|
||||||
|
jsonData, err := json.Marshal(original)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg := &Message{
|
||||||
|
Type: QuoteResponse,
|
||||||
|
Payload: jsonData,
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = codec.Encode(&buf, msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
decoded, err := codec.Decode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var result QuoteResponsePayload
|
||||||
|
err = json.Unmarshal(decoded.Payload, &result)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, original.Text, result.Text)
|
||||||
|
assert.Equal(t, original.Author, result.Author)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_WriteError_Handling(t *testing.T) {
|
||||||
|
codec := NewCodec()
|
||||||
|
|
||||||
|
// Create a writer that fails after a certain number of bytes
|
||||||
|
failAfter := 3
|
||||||
|
writer := &failingWriter{failAfter: failAfter}
|
||||||
|
|
||||||
|
msg := &Message{
|
||||||
|
Type: ChallengeResponse,
|
||||||
|
Payload: []byte("test payload"),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := codec.Encode(writer, msg)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_ReadError_Handling(t *testing.T) {
|
||||||
|
codec := NewCodec()
|
||||||
|
|
||||||
|
// Create a reader that fails after reading header
|
||||||
|
reader := &failingReader{
|
||||||
|
data: []byte{0x01, 0x00, 0x00, 0x00, 0x05},
|
||||||
|
failAfter: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := codec.Decode(reader)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "failed to read payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions and types for testing
|
||||||
|
|
||||||
|
func encodeBigEndianUint32(val uint32) []byte {
|
||||||
|
return []byte{
|
||||||
|
byte(val >> 24),
|
||||||
|
byte(val >> 16),
|
||||||
|
byte(val >> 8),
|
||||||
|
byte(val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type failingWriter struct {
|
||||||
|
written int
|
||||||
|
failAfter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *failingWriter) Write(data []byte) (int, error) {
|
||||||
|
if w.written >= w.failAfter {
|
||||||
|
return 0, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining := w.failAfter - w.written
|
||||||
|
if len(data) <= remaining {
|
||||||
|
w.written += len(data)
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.written = w.failAfter
|
||||||
|
return remaining, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
type failingReader struct {
|
||||||
|
data []byte
|
||||||
|
pos int
|
||||||
|
failAfter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *failingReader) Read(buf []byte) (int, error) {
|
||||||
|
if r.pos >= r.failAfter {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.pos >= len(r.data) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
n := copy(buf, r.data[r.pos:])
|
||||||
|
r.pos += n
|
||||||
|
|
||||||
|
if r.pos >= r.failAfter {
|
||||||
|
return n, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
64
internal/protocol/types.go
Normal file
64
internal/protocol/types.go
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash-of-wisdom/internal/pow/challenge"
|
||||||
|
"hash-of-wisdom/internal/quotes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageType represents the type of protocol message
|
||||||
|
type MessageType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChallengeRequest MessageType = 0x01
|
||||||
|
ChallengeResponse MessageType = 0x02
|
||||||
|
SolutionRequest MessageType = 0x03
|
||||||
|
QuoteResponse MessageType = 0x04
|
||||||
|
ErrorResponse MessageType = 0x05
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message represents a protocol message with type and payload
|
||||||
|
type Message struct {
|
||||||
|
Type MessageType
|
||||||
|
Payload []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChallengeRequestPayload is empty (no payload for challenge requests)
|
||||||
|
type ChallengeRequestPayload struct{}
|
||||||
|
|
||||||
|
// ChallengeResponsePayload is the direct challenge object (not wrapped)
|
||||||
|
type ChallengeResponsePayload challenge.Challenge
|
||||||
|
|
||||||
|
// SolutionRequestPayload contains the client's solution attempt
|
||||||
|
type SolutionRequestPayload struct {
|
||||||
|
Challenge challenge.Challenge `json:"challenge"`
|
||||||
|
Nonce uint64 `json:"nonce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuoteResponsePayload is the direct quote object (not wrapped)
|
||||||
|
type QuoteResponsePayload quotes.Quote
|
||||||
|
|
||||||
|
// ErrorResponsePayload contains error information
|
||||||
|
type ErrorResponsePayload struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
RetryAfter int `json:"retry_after,omitempty"`
|
||||||
|
Details map[string]string `json:"details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error codes as defined in protocol specification
|
||||||
|
const (
|
||||||
|
ErrMalformedMessage = "MALFORMED_MESSAGE"
|
||||||
|
ErrInvalidChallenge = "INVALID_CHALLENGE"
|
||||||
|
ErrInvalidSolution = "INVALID_SOLUTION"
|
||||||
|
ErrExpiredChallenge = "EXPIRED_CHALLENGE"
|
||||||
|
ErrRateLimited = "RATE_LIMITED"
|
||||||
|
ErrServerError = "SERVER_ERROR"
|
||||||
|
ErrTooManyConnections = "TOO_MANY_CONNECTIONS"
|
||||||
|
ErrDifficultyTooHigh = "DIFFICULTY_TOO_HIGH"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Protocol constants
|
||||||
|
const (
|
||||||
|
MaxPayloadSize = 8 * 1024 // 8KB maximum payload size
|
||||||
|
HeaderSize = 5 // 1 byte type + 4 bytes length
|
||||||
|
)
|
||||||
Loading…
Reference in a new issue