[PHASE-7] Add encoding and decoding for client

This commit is contained in:
Savely Krendelhoff 2025-08-23 13:00:14 +07:00
parent 71b5d7ed27
commit 2c8d6c828f
No known key found for this signature in database
GPG key ID: F70DFD34F40238DE
3 changed files with 88 additions and 42 deletions

View file

@ -0,0 +1,41 @@
package protocol
import (
"encoding/binary"
"encoding/json"
"fmt"
"io"
)
// encode is a helper function that encodes any message with the given message type
func encode(w io.Writer, msgType MessageType, payload interface{}) error {
var payloadBytes []byte
var err error
// Only marshal if payload is not nil
if payload != nil {
payloadBytes, err = json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to encode payload: %w", err)
}
}
// Write message type (1 byte)
if err := binary.Write(w, binary.BigEndian, msgType); err != nil {
return fmt.Errorf("failed to write message type: %w", err)
}
// Write payload length (4 bytes, big-endian)
if err := binary.Write(w, binary.BigEndian, uint32(len(payloadBytes))); err != nil {
return fmt.Errorf("failed to write payload length: %w", err)
}
// Write JSON payload if we have one
if len(payloadBytes) > 0 {
if _, err := w.Write(payloadBytes); err != nil {
return fmt.Errorf("failed to write payload: %w", err)
}
}
return nil
}

View file

@ -7,6 +7,7 @@ import (
"hash-of-wisdom/internal/pow/challenge" "hash-of-wisdom/internal/pow/challenge"
) )
// ChallengeRequest is empty (no payload for challenge requests) // ChallengeRequest is empty (no payload for challenge requests)
type ChallengeRequest struct{} type ChallengeRequest struct{}
@ -16,6 +17,11 @@ func (r *ChallengeRequest) Decode(stream io.Reader) error {
return nil return nil
} }
// Encode writes a challenge request to the writer
func (r *ChallengeRequest) Encode(w io.Writer) error {
return encode(w, ChallengeRequestType, nil)
}
// SolutionRequest contains the client's solution attempt // SolutionRequest contains the client's solution attempt
type SolutionRequest struct { type SolutionRequest struct {
Challenge challenge.Challenge `json:"challenge"` Challenge challenge.Challenge `json:"challenge"`
@ -33,3 +39,8 @@ func (r *SolutionRequest) Decode(stream io.Reader) error {
decoder := json.NewDecoder(stream) decoder := json.NewDecoder(stream)
return decoder.Decode(r) return decoder.Decode(r)
} }
// Encode writes a solution request to the writer
func (r *SolutionRequest) Encode(w io.Writer) error {
return encode(w, SolutionRequestType, r)
}

View file

@ -1,63 +1,46 @@
package protocol package protocol
import ( import (
"encoding/binary"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"hash-of-wisdom/internal/pow/challenge" "hash-of-wisdom/internal/pow/challenge"
"hash-of-wisdom/internal/quotes" "hash-of-wisdom/internal/quotes"
) )
// writeHeader writes the message type and payload length to the writer
func writeHeader(w io.Writer, msgType MessageType, payloadLength uint32) error {
// Write message type (1 byte)
if err := binary.Write(w, binary.BigEndian, msgType); err != nil {
return fmt.Errorf("failed to write message type: %w", err)
}
// Write payload length (4 bytes, big-endian)
if err := binary.Write(w, binary.BigEndian, payloadLength); err != nil {
return fmt.Errorf("failed to write payload length: %w", err)
}
return nil
}
// encodeResponse is a helper function that encodes any response with the given message type
func encodeResponse(w io.Writer, msgType MessageType, payload interface{}) error {
// Marshal to get exact payload size
payloadBytes, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to encode payload: %w", err)
}
// Write header
if err := writeHeader(w, msgType, uint32(len(payloadBytes))); err != nil {
return err
}
// Write JSON payload directly to stream
if len(payloadBytes) > 0 {
if _, err := w.Write(payloadBytes); err != nil {
return fmt.Errorf("failed to write payload: %w", err)
}
}
return nil
}
// ChallengeResponse represents a challenge response // ChallengeResponse represents a challenge response
type ChallengeResponse struct { type ChallengeResponse struct {
Challenge *challenge.Challenge Challenge *challenge.Challenge
} }
// Decode reads a challenge response from the payload stream
func (r *ChallengeResponse) Decode(stream io.Reader) error {
if stream == nil {
return io.EOF
}
// Parse JSON directly from stream
decoder := json.NewDecoder(stream)
return decoder.Decode(&r.Challenge)
}
// SolutionResponse represents a successful solution response (contains quote) // SolutionResponse represents a successful solution response (contains quote)
type SolutionResponse struct { type SolutionResponse struct {
Quote *quotes.Quote Quote *quotes.Quote
} }
// Decode reads a solution response from the payload stream
func (r *SolutionResponse) Decode(stream io.Reader) error {
if stream == nil {
return io.EOF
}
// Parse JSON directly from stream
decoder := json.NewDecoder(stream)
return decoder.Decode(&r.Quote)
}
// ErrorResponse represents an error response // ErrorResponse represents an error response
type ErrorResponse struct { type ErrorResponse struct {
Code string `json:"code"` Code string `json:"code"`
@ -66,17 +49,28 @@ type ErrorResponse struct {
Details map[string]string `json:"details,omitempty"` Details map[string]string `json:"details,omitempty"`
} }
// Decode reads an error response from the payload stream
func (r *ErrorResponse) Decode(stream io.Reader) error {
if stream == nil {
return io.EOF
}
// Parse JSON directly from stream
decoder := json.NewDecoder(stream)
return decoder.Decode(r)
}
// Encode writes the challenge response to the writer // Encode writes the challenge response to the writer
func (r *ChallengeResponse) Encode(w io.Writer) error { func (r *ChallengeResponse) Encode(w io.Writer) error {
return encodeResponse(w, ChallengeResponseType, r.Challenge) return encode(w, ChallengeResponseType, r.Challenge)
} }
// Encode writes the solution response to the writer // Encode writes the solution response to the writer
func (r *SolutionResponse) Encode(w io.Writer) error { func (r *SolutionResponse) Encode(w io.Writer) error {
return encodeResponse(w, QuoteResponseType, r.Quote) return encode(w, QuoteResponseType, r.Quote)
} }
// Encode writes the error response to the writer // Encode writes the error response to the writer
func (r *ErrorResponse) Encode(w io.Writer) error { func (r *ErrorResponse) Encode(w io.Writer) error {
return encodeResponse(w, ErrorResponseType, r) return encode(w, ErrorResponseType, r)
} }