Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
191 lines
5.2 KiB
Go
191 lines
5.2 KiB
Go
package devicelink
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
streamProbeRequest = "tutti-device-link/stream-probe/1"
|
|
streamProbeAck = "tutti-device-link/stream-probe-ack/1"
|
|
streamProbeNonce = 16
|
|
streamProbeLimit = 5 * time.Second
|
|
)
|
|
|
|
var (
|
|
ErrStreamProbeRejected = errors.New("device-link stream probe rejected")
|
|
ErrStreamProbeMismatch = errors.New("device-link stream probe acknowledgement mismatch")
|
|
)
|
|
|
|
// ProbeStream verifies that the authenticated peer can receive and answer a
|
|
// fresh stream before the caller commits a direct/Relay race winner. QUIC
|
|
// stream allocation alone is not a liveness signal: a stale session may still
|
|
// allocate a local stream after its network path has become unusable.
|
|
//
|
|
// The probe is transport-owned and carries no Agent or product payload. The
|
|
// peer must run ServeStreamProbe before handing the stream to its application
|
|
// protocol handler. The same stream is left open after the acknowledgement so
|
|
// callers can immediately write their application prelude without a second
|
|
// race or an ambiguous request retry.
|
|
func ProbeStream(ctx context.Context, stream net.Conn) error {
|
|
if stream == nil {
|
|
return errors.New("device-link stream probe requires a stream")
|
|
}
|
|
probeCtx, cancel := streamProbeContext(ctx)
|
|
defer cancel()
|
|
stopClose := closeStreamOnContext(probeCtx, stream)
|
|
defer stopClose()
|
|
if err := setProbeDeadline(probeCtx, stream); err != nil {
|
|
return fmt.Errorf("set device-link stream probe deadline: %w", err)
|
|
}
|
|
defer func() { _ = stream.SetDeadline(time.Time{}) }()
|
|
|
|
nonce := make([]byte, streamProbeNonce)
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return fmt.Errorf("generate device-link stream probe nonce: %w", err)
|
|
}
|
|
if err := writeProbeMessage(stream, streamProbeRequest, nonce); err != nil {
|
|
return fmt.Errorf("write device-link stream probe: %w", err)
|
|
}
|
|
ack, err := readProbeMessage(stream, streamProbeAck)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !equalBytes(ack, nonce) {
|
|
return ErrStreamProbeMismatch
|
|
}
|
|
stopClose()
|
|
cancel()
|
|
_ = stream.SetDeadline(time.Time{})
|
|
return nil
|
|
}
|
|
|
|
// ServeStreamProbe acknowledges one ProbeStream call and then invokes next
|
|
// with the same stream. A failed probe never reaches the product handler.
|
|
// This keeps application framing and request semantics out of the shared
|
|
// transport handshake while still proving that the remote endpoint is alive.
|
|
func ServeStreamProbe(
|
|
ctx context.Context,
|
|
stream net.Conn,
|
|
next func(context.Context, net.Conn) error,
|
|
) error {
|
|
if stream == nil {
|
|
return errors.New("device-link stream probe requires a stream")
|
|
}
|
|
if next == nil {
|
|
return errors.New("device-link stream probe handler is required")
|
|
}
|
|
probeCtx, cancel := streamProbeContext(ctx)
|
|
defer cancel()
|
|
stopClose := closeStreamOnContext(probeCtx, stream)
|
|
defer stopClose()
|
|
if err := setProbeDeadline(probeCtx, stream); err != nil {
|
|
return fmt.Errorf("set device-link stream probe deadline: %w", err)
|
|
}
|
|
defer func() { _ = stream.SetDeadline(time.Time{}) }()
|
|
|
|
nonce, err := readProbeMessage(stream, streamProbeRequest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := writeProbeMessage(stream, streamProbeAck, nonce); err != nil {
|
|
return fmt.Errorf("write device-link stream probe acknowledgement: %w", err)
|
|
}
|
|
stopClose()
|
|
cancel()
|
|
_ = stream.SetDeadline(time.Time{})
|
|
return next(ctx, stream)
|
|
}
|
|
|
|
func streamProbeContext(ctx context.Context) (context.Context, context.CancelFunc) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if _, ok := ctx.Deadline(); ok {
|
|
return ctx, func() {}
|
|
}
|
|
return context.WithTimeout(ctx, streamProbeLimit)
|
|
}
|
|
|
|
func closeStreamOnContext(ctx context.Context, stream net.Conn) func() {
|
|
done := make(chan struct{})
|
|
stopped := make(chan struct{})
|
|
var once sync.Once
|
|
go func() {
|
|
defer close(stopped)
|
|
select {
|
|
case <-ctx.Done():
|
|
_ = stream.Close()
|
|
case <-done:
|
|
}
|
|
}()
|
|
return func() {
|
|
once.Do(func() {
|
|
close(done)
|
|
<-stopped
|
|
})
|
|
}
|
|
}
|
|
|
|
func setProbeDeadline(ctx context.Context, stream net.Conn) error {
|
|
deadline, ok := ctx.Deadline()
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return stream.SetDeadline(deadline)
|
|
}
|
|
|
|
func writeProbeMessage(writer io.Writer, kind string, nonce []byte) error {
|
|
if err := writeProbeBytes(writer, []byte(kind)); err != nil {
|
|
return err
|
|
}
|
|
return writeProbeBytes(writer, nonce)
|
|
}
|
|
|
|
func readProbeMessage(reader io.Reader, kind string) ([]byte, error) {
|
|
header := make([]byte, len(kind))
|
|
if _, err := io.ReadFull(reader, header); err != nil {
|
|
return nil, fmt.Errorf("read device-link stream probe header: %w", err)
|
|
}
|
|
if string(header) != kind {
|
|
return nil, ErrStreamProbeRejected
|
|
}
|
|
nonce := make([]byte, streamProbeNonce)
|
|
if _, err := io.ReadFull(reader, nonce); err != nil {
|
|
return nil, fmt.Errorf("read device-link stream probe nonce: %w", err)
|
|
}
|
|
return nonce, nil
|
|
}
|
|
|
|
func writeProbeBytes(writer io.Writer, payload []byte) error {
|
|
for len(payload) > 0 {
|
|
written, err := writer.Write(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if written <= 0 || written > len(payload) {
|
|
return io.ErrShortWrite
|
|
}
|
|
payload = payload[written:]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func equalBytes(left, right []byte) bool {
|
|
if len(left) != len(right) {
|
|
return false
|
|
}
|
|
for index := range left {
|
|
if left[index] != right[index] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|