From 2c8d6c828f7e2aec3fb3997087efded35d23c4fa Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 13:00:14 +0700 Subject: [PATCH] [PHASE-7] Add encoding and decoding for client --- internal/protocol/encoder.go | 41 ++++++++++++++++++ internal/protocol/requests.go | 11 +++++ internal/protocol/responses.go | 78 ++++++++++++++++------------------ 3 files changed, 88 insertions(+), 42 deletions(-) create mode 100644 internal/protocol/encoder.go diff --git a/internal/protocol/encoder.go b/internal/protocol/encoder.go new file mode 100644 index 0000000..3c3dbc7 --- /dev/null +++ b/internal/protocol/encoder.go @@ -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 +} diff --git a/internal/protocol/requests.go b/internal/protocol/requests.go index 3961223..588aadd 100644 --- a/internal/protocol/requests.go +++ b/internal/protocol/requests.go @@ -7,6 +7,7 @@ import ( "hash-of-wisdom/internal/pow/challenge" ) + // ChallengeRequest is empty (no payload for challenge requests) type ChallengeRequest struct{} @@ -16,6 +17,11 @@ func (r *ChallengeRequest) Decode(stream io.Reader) error { 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 type SolutionRequest struct { Challenge challenge.Challenge `json:"challenge"` @@ -33,3 +39,8 @@ func (r *SolutionRequest) Decode(stream io.Reader) error { decoder := json.NewDecoder(stream) 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) +} diff --git a/internal/protocol/responses.go b/internal/protocol/responses.go index 8521d4f..ba59dda 100644 --- a/internal/protocol/responses.go +++ b/internal/protocol/responses.go @@ -1,63 +1,46 @@ package protocol import ( - "encoding/binary" "encoding/json" - "fmt" "io" "hash-of-wisdom/internal/pow/challenge" "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 type ChallengeResponse struct { 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) type SolutionResponse struct { 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 type ErrorResponse struct { Code string `json:"code"` @@ -66,17 +49,28 @@ type ErrorResponse struct { 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 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 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 func (r *ErrorResponse) Encode(w io.Writer) error { - return encodeResponse(w, ErrorResponseType, r) + return encode(w, ErrorResponseType, r) }