// tera-serial-stream-check performs one bounded CH01/COM3 data-plane check. // It never opens a raw TTY and requires an explicit operator confirmation. package main import ( "context" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "errors" "flag" "fmt" "io" "os" "time" "tera-clone/services/tera-api/internal/serial" ) const maximumPayloadBytes = 64 type streamOpener interface { Open(context.Context, string) (io.ReadWriteCloser, error) } type report struct { Kind string `json:"kind"` Status string `json:"status"` Direction string `json:"direction"` Channel string `json:"channel"` Device string `json:"device"` PCPort string `json:"pc_port"` Bytes int `json:"bytes"` SHA256 string `json:"sha256"` DataSent bool `json:"data_sent"` } func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr, serial.NewStreamSocketClient(serial.DefaultStreamSocketPath, "tera-serial-stream-check"))) } func run(args []string, stdout, stderr io.Writer, opener streamOpener) int { flags := flag.NewFlagSet("tera-serial-stream-check", flag.ContinueOnError) flags.SetOutput(stderr) direction := flags.String("direction", "", "tx or rx relative to the target") payloadBase64 := flags.String("payload-base64", "", "bounded payload for tx") byteCount := flags.Int("bytes", 0, "expected byte count for rx") timeout := flags.Duration("timeout", 10*time.Second, "bounded stream timeout") confirmed := flags.Bool("operator-confirmed-ch01-com3", false, "confirm CH01 /dev/ttyWCH4 is connected to PC COM3") if err := flags.Parse(args); err != nil { return 2 } if flags.NArg() != 0 || !*confirmed || (*direction != "tx" && *direction != "rx") || *timeout <= 0 { fmt.Fprintln(stderr, "require tx/rx, a positive timeout, and explicit CH01-COM3 confirmation") return 2 } var payload []byte var err error if *direction == "tx" { payload, err = base64.StdEncoding.Strict().DecodeString(*payloadBase64) if err != nil || len(payload) < 1 || len(payload) > maximumPayloadBytes || *byteCount != 0 { fmt.Fprintln(stderr, "tx requires a strict Base64 payload of 1 to 64 bytes") return 2 } } else if *payloadBase64 != "" || *byteCount < 1 || *byteCount > maximumPayloadBytes { fmt.Fprintln(stderr, "rx requires an expected byte count of 1 to 64") return 2 } ctx, cancel := context.WithTimeout(context.Background(), *timeout) defer cancel() stream, err := opener.Open(ctx, "CH01") if err != nil { fmt.Fprintln(stderr, err) return 1 } defer stream.Close() if *direction == "tx" { if err := writeAll(stream, payload); err != nil { fmt.Fprintln(stderr, err) return 1 } } else { payload, err = readExact(ctx, stream, *byteCount) if err != nil { fmt.Fprintln(stderr, err) return 1 } } digest := sha256.Sum256(payload) result := report{ Kind: "tera-serial-stream-check", Status: "pass", Direction: *direction, Channel: "CH01", Device: "/dev/ttyWCH4", PCPort: "COM3", Bytes: len(payload), SHA256: hex.EncodeToString(digest[:]), DataSent: true, } if err := json.NewEncoder(stdout).Encode(result); err != nil { fmt.Fprintln(stderr, err) return 1 } return 0 } func writeAll(writer io.Writer, payload []byte) error { for len(payload) > 0 { written, err := writer.Write(payload) if err != nil { return err } if written <= 0 { return io.ErrShortWrite } payload = payload[written:] } return nil } func readExact(ctx context.Context, stream io.ReadWriteCloser, count int) ([]byte, error) { type outcome struct { payload []byte err error } done := make(chan outcome, 1) go func() { payload := make([]byte, count) _, err := io.ReadFull(stream, payload) done <- outcome{payload: payload, err: err} }() select { case result := <-done: return result.payload, result.err case <-ctx.Done(): _ = stream.Close() result := <-done return result.payload, errors.Join(ctx.Err(), result.err) } }