package dbreads import ( "encoding/binary" "encoding/json" "errors" "io" "github.com/carlosframework/platform/internal/dbdoor" ) // ProtocolVersion is the worker protocol's version, answered in hello. A // worker that speaks another one never advertises anything (spec §7.1). const ProtocolVersion = 1 // Frames are one type byte, a four-byte big-endian length and the payload, // at most maxFrame bytes (spec §8.3: length-framed with a fixed maximum). const ( maxFrame = 1 << 20 fileFrameBytes = 256 << 10 outputFrameBytes = 64 << 10 ) const ( frameHello byte = 'H' // agent: the readiness probe frameHelloReply byte = 'h' // worker: its version and its view of its sandbox frameHeader byte = 'P' // agent: the plan frameFile byte = 'F' // agent: file bytes, in the header's order frameReady byte = 'R' // worker: restored and checked; runs nothing yet frameRun byte = 'X' // agent: check B passed; the statement frameStop byte = 'S' // agent: end without running frameOutput byte = 'O' // worker: output bytes frameEnd byte = 'E' // worker: how it ended ) var ( errFrameTooLarge = errors.New("dbreads: frame over the protocol's maximum") errProtocol = errors.New("dbreads: unexpected frame") ) type hello struct { V int `json:"v"` } // HelloReply is the worker's answer to the probe: its protocol version and // what it can see of the box (spec §7.1). A sandboxed worker sees only the // loopback interface, an empty /data and no /etc/carlos. type HelloReply struct { V int `json:"v"` Interfaces []string `json:"interfaces"` DataEntries int `json:"data_entries"` EtcReadable bool `json:"etc_readable"` // Errors names every observation the worker could not make. A probe // fails on any: an unobserved sandbox is not a sandbox. Errors []string `json:"errors,omitempty"` } type planHeader struct { V int `json:"v"` Op string `json:"op"` Files []dbdoor.LTXFile `json:"files"` Target uint64 `json:"target"` MaxRestored int64 `json:"max_restored"` } type readyMsg struct { TXID uint64 `json:"txid"` } type runMsg struct { SQL string `json:"sql"` } type endMsg struct { Code string `json:"code"` Error string `json:"error"` } // writeFrame writes one frame in a single Write. func writeFrame(w io.Writer, typ byte, p []byte) error { if len(p) > maxFrame { return errFrameTooLarge } buf := make([]byte, 5, 5+len(p)) buf[0] = typ binary.BigEndian.PutUint32(buf[1:], uint32(len(p))) _, err := w.Write(append(buf, p...)) return err } func writeJSONFrame(w io.Writer, typ byte, v any) error { p, err := json.Marshal(v) if err != nil { return err } return writeFrame(w, typ, p) } func readFrame(r io.Reader) (byte, []byte, error) { var hdr [5]byte if _, err := io.ReadFull(r, hdr[:]); err != nil { return 0, nil, err } n := binary.BigEndian.Uint32(hdr[1:]) if n > maxFrame { return 0, nil, errFrameTooLarge } p := make([]byte, n) if _, err := io.ReadFull(r, p); err != nil { return 0, nil, err } return hdr[0], p, nil }