// worker/frame.go — length-prefixed JSON framing. // // Identical shape to internal/ipc/frame.go with one difference: the cap is // 64 MiB here vs 4 there. Audio bytes (base64-encoded in JSON) push frame // sizes up to ~80% over their decoded size; 64 MiB on the wire comfortably // allows minutes-long single utterances, while still bounding a confused // peer's length prefix from allocating an unbounded buffer. // // Binary bytes are base64-encoded inside the JSON envelope. Local socket // + single-user scale ⇒ the 33% expansion cost is invisible vs. a model // forward pass; the benefit is one debuggable wire shape (socat shows a // readable JSON frame, no separate byte-pump to inspect audio). package worker import ( "encoding/binary" "encoding/json" "errors" "fmt" "io" ) // ErrFrameTooLarge — a frame exceeded maxFrame; the conn is now // desynchronized (we read the length but not the body), so the caller must // close it. var ErrFrameTooLarge = errors.New("worker: frame too large") func writeFrame(w io.Writer, v any) error { body, err := json.Marshal(v) if err != nil { return fmt.Errorf("worker: marshal frame: %w", err) } if len(body) > maxFrame { return fmt.Errorf("%w: %d bytes", ErrFrameTooLarge, len(body)) } var hdr [4]byte binary.BigEndian.PutUint32(hdr[:], uint32(len(body))) if _, err := w.Write(hdr[:]); err != nil { return fmt.Errorf("worker: write frame header: %w", err) } if _, err := w.Write(body); err != nil { return fmt.Errorf("worker: write frame body: %w", err) } return nil } func readFrame(r io.Reader, v any) error { var hdr [4]byte if _, err := io.ReadFull(r, hdr[:]); err != nil { if errors.Is(err, io.EOF) { return io.EOF } return fmt.Errorf("worker: read frame header: %w", err) } n := binary.BigEndian.Uint32(hdr[:]) if n > maxFrame { return fmt.Errorf("%w: %d bytes", ErrFrameTooLarge, n) } buf := make([]byte, n) if _, err := io.ReadFull(r, buf); err != nil { return fmt.Errorf("worker: read frame body: %w", err) } if err := json.Unmarshal(buf, v); err != nil { return fmt.Errorf("worker: unmarshal frame: %w", err) } return nil }