apps/node 중심 구현 — TCP+JSON transport, Hexagonal Architecture, mock/cli adapter, fx DI, SQLite 실행 이력 저장. edge/control-plane/worker는 cobra placeholder. 유닛 테스트 및 통합 테스트 클라이언트 계획서 추가.
63 lines
1.5 KiB
Go
63 lines
1.5 KiB
Go
// Package transport implements IOP's TCP framing layer.
|
|
//
|
|
// Wire format (same endianness as proto-socket):
|
|
// [4 bytes big-endian uint32: payload length] [payload bytes]
|
|
//
|
|
// Payload is a JSON-encoded Envelope (see codec.go).
|
|
// TODO: switch payload encoding to protobuf once proto/gen is available.
|
|
package transport
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
const (
|
|
frameLenBytes = 4
|
|
maxFrameSize = 64 << 20 // 64 MiB — same limit as proto-socket
|
|
)
|
|
|
|
// readFrame reads one length-prefixed frame from r.
|
|
func readFrame(r io.Reader) ([]byte, error) {
|
|
hdr := make([]byte, frameLenBytes)
|
|
if _, err := io.ReadFull(r, hdr); err != nil {
|
|
return nil, err
|
|
}
|
|
length := binary.BigEndian.Uint32(hdr)
|
|
if length == 0 {
|
|
return []byte{}, nil
|
|
}
|
|
if length > maxFrameSize {
|
|
return nil, fmt.Errorf("frame too large: %d bytes (max %d)", length, maxFrameSize)
|
|
}
|
|
buf := make([]byte, length)
|
|
if _, err := io.ReadFull(r, buf); err != nil {
|
|
return nil, err
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
// writeFrame writes payload as a length-prefixed frame to w.
|
|
func writeFrame(w io.Writer, payload []byte) error {
|
|
hdr := make([]byte, frameLenBytes)
|
|
binary.BigEndian.PutUint32(hdr, uint32(len(payload)))
|
|
if err := writeFull(w, hdr); err != nil {
|
|
return err
|
|
}
|
|
return writeFull(w, payload)
|
|
}
|
|
|
|
func writeFull(w io.Writer, b []byte) error {
|
|
for len(b) > 0 {
|
|
n, err := w.Write(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n == 0 {
|
|
return io.ErrShortWrite
|
|
}
|
|
b = b[n:]
|
|
}
|
|
return nil
|
|
}
|