Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

794 lines
21 KiB
Go

package mobileremote
import (
"context"
"crypto/ed25519"
"errors"
"net"
"net/http"
"strings"
"sync"
"time"
devicelink "github.com/tutti-os/tutti/packages/device-link"
authenticatedlink "github.com/tutti-os/tutti/packages/device-link/authenticated"
"github.com/tutti-os/tutti/packages/device-link/candidateexchange"
"github.com/tutti-os/tutti/packages/device-link/linkmanager"
mobileremotebiz "github.com/tutti-os/tutti/services/tuttid/biz/mobileremote"
)
const (
defaultRemotePollInterval = 2 * time.Second
remoteCallerSettleFallback = 500 * time.Millisecond
deviceLinkProtocolVersion = 2
mobileRemoteRelayDriver = "mobile-remote"
)
type activeRemoteAttempt struct {
pairingID string
cancel context.CancelFunc
generation uint64
}
type remoteLinkMetadata struct {
pairingID string
handler http.Handler
liveEvents AgentLiveEventSource
}
type remoteManagedLink struct {
connectionID string
}
type remoteHostState struct {
mu sync.Mutex
cancel context.CancelFunc
stopping bool
stopDone chan struct{}
handler http.Handler
liveEvents AgentLiveEventSource
attempts map[string]activeRemoteAttempt
registeredSession string
registeredDevice RegisteredDevice
registerAfter time.Time
nextGeneration uint64
linkManager *linkmanager.Manager[string, remoteLinkMetadata]
managedLinks map[string]remoteManagedLink
observedLinkEvents map[string]uint64
relayOwnerAcquired bool
remoteHostGeneration uint64
activePairings map[string]struct{}
attemptWake *AttemptWake
pollWake chan struct{}
}
func (s *Service) StartRemoteHost(handler http.Handler) {
if s == nil || handler == nil {
return
}
for {
s.remoteHost.mu.Lock()
if s.remoteHost.stopping {
done := s.remoteHost.stopDone
s.remoteHost.mu.Unlock()
<-done
continue
}
if s.remoteHost.cancel != nil {
s.remoteHost.handler = handler
s.remoteHost.liveEvents = s.AgentLiveEvents
s.remoteHost.mu.Unlock()
return
}
ctx, cancel := context.WithCancel(context.Background())
s.remoteWG.Add(1)
s.remoteHost.cancel = cancel
s.remoteHost.handler = handler
s.remoteHost.liveEvents = s.AgentLiveEvents
s.remoteHost.attempts = make(map[string]activeRemoteAttempt)
s.remoteHost.managedLinks = make(map[string]remoteManagedLink)
s.remoteHost.observedLinkEvents = make(map[string]uint64)
s.remoteHost.activePairings = make(map[string]struct{})
s.remoteHost.attemptWake = NewAttemptWake()
s.remoteHost.pollWake = make(chan struct{}, 1)
s.remoteHost.linkManager = s.newRemoteLinkManager()
s.remoteHost.remoteHostGeneration++
remoteHostGeneration := s.remoteHost.remoteHostGeneration
relayOwner := s.RelayOwner
s.remoteHost.mu.Unlock()
if relayOwner != nil {
if err := relayOwner.Acquire(ctx, mobileRemoteRelayDriver); err == nil {
s.remoteHost.mu.Lock()
if s.remoteHost.remoteHostGeneration == remoteHostGeneration && !s.remoteHost.stopping {
s.remoteHost.relayOwnerAcquired = true
relayOwner = nil
}
s.remoteHost.mu.Unlock()
if relayOwner != nil {
_ = relayOwner.Release(mobileRemoteRelayDriver)
}
}
}
if s.AttemptEvents != nil {
s.remoteWG.Add(1)
go func() {
defer s.remoteWG.Done()
s.runRemoteAttemptEvents(ctx)
}()
}
go func() {
defer s.remoteWG.Done()
s.runRemoteHost(ctx)
}()
return
}
}
func (s *Service) Close() {
s.StopRemoteHost()
}
func (s *Service) StopRemoteHost() {
if s == nil {
return
}
s.remoteHost.mu.Lock()
if s.remoteHost.stopping {
done := s.remoteHost.stopDone
s.remoteHost.mu.Unlock()
<-done
return
}
cancel := s.remoteHost.cancel
manager := s.remoteHost.linkManager
if cancel == nil && manager == nil {
s.remoteHost.mu.Unlock()
return
}
done := make(chan struct{})
s.remoteHost.stopping = true
s.remoteHost.stopDone = done
relayOwner := s.RelayOwner
relayOwnerAcquired := s.remoteHost.relayOwnerAcquired
s.remoteHost.relayOwnerAcquired = false
for _, attempt := range s.remoteHost.attempts {
attempt.cancel()
}
s.remoteHost.mu.Unlock()
if manager != nil {
manager.BeginQuiescence()
}
if cancel != nil {
cancel()
}
s.remoteWG.Wait()
if manager != nil {
_ = manager.WaitForQuiescence(context.Background())
}
if relayOwnerAcquired && relayOwner != nil {
_ = relayOwner.Release(mobileRemoteRelayDriver)
}
s.remoteHost.mu.Lock()
s.remoteHost.cancel = nil
s.remoteHost.linkManager = nil
s.remoteHost.attempts = nil
s.remoteHost.managedLinks = nil
s.remoteHost.observedLinkEvents = nil
s.remoteHost.activePairings = nil
s.remoteHost.attemptWake = nil
s.remoteHost.pollWake = nil
s.remoteHost.stopping = false
s.remoteHost.stopDone = nil
close(done)
s.remoteHost.mu.Unlock()
}
func (s *Service) runRemoteHost(ctx context.Context) {
interval := s.RemotePollInterval
if interval <= 0 {
interval = defaultRemotePollInterval
}
timer := time.NewTimer(0)
defer timer.Stop()
for {
s.remoteHost.mu.Lock()
pollWake := s.remoteHost.pollWake
s.remoteHost.mu.Unlock()
select {
case <-ctx.Done():
return
case <-timer.C:
case <-pollWake:
}
if ctx.Err() != nil {
return
}
s.pollRemoteHost(ctx)
if !timer.Stop() {
select {
case <-timer.C:
default:
}
}
timer.Reset(interval)
}
}
func (s *Service) runRemoteAttemptEvents(ctx context.Context) {
backoff := time.Second
for {
if ctx.Err() != nil {
return
}
session, identity, err := s.readyIdentity(ctx)
if err == nil {
s.remoteHost.mu.Lock()
wake := s.remoteHost.attemptWake
s.remoteHost.mu.Unlock()
if wake != nil {
runErr := s.AttemptEvents.Run(
ctx, session.Cookie, identity.DeviceID, s.notifyRemoteAttemptChanged,
)
if ctx.Err() != nil {
return
}
if runErr == nil {
backoff = time.Second
} else {
backoff = nextAttemptEventBackoff(backoff)
}
}
}
if !waitRemoteAttemptEventRetry(ctx, backoff) {
return
}
}
}
// notifyRemoteAttemptChanged treats realtime messages as a best-effort wake
// for both stages of owner rendezvous: an attempt may not have a worker yet,
// so the discovery loop must run immediately before the worker can refetch it.
// The HTTP attempt API remains authoritative when either wake is lost.
func (s *Service) notifyRemoteAttemptChanged(attemptID string) {
s.remoteHost.mu.Lock()
wake := s.remoteHost.attemptWake
pollWake := s.remoteHost.pollWake
s.remoteHost.mu.Unlock()
if wake != nil {
wake.Notify(attemptID)
}
if pollWake != nil {
select {
case pollWake <- struct{}{}:
default:
}
}
}
func nextAttemptEventBackoff(current time.Duration) time.Duration {
if current <= 0 {
return time.Second
}
if current >= 15*time.Second {
return 15 * time.Second
}
next := current + current/2
if next > 15*time.Second {
return 15 * time.Second
}
return next
}
func waitRemoteAttemptEventRetry(ctx context.Context, delay time.Duration) bool {
timer := time.NewTimer(delay)
defer timer.Stop()
select {
case <-ctx.Done():
return false
case <-timer.C:
return true
}
}
func (s *Service) pollRemoteHost(ctx context.Context) {
session, identity, err := s.readyIdentity(ctx)
if err != nil {
s.stopRemoteAttempts(nil)
return
}
s.setRemoteLinkEnabled(true)
registered, err := s.ensureRegisteredDevice(ctx, session.SessionID, session.Cookie, identity)
if err != nil {
if isControlPlaneUnauthorized(err) {
s.stopRemoteAttempts(nil)
}
return
}
pairings, err := s.ControlPlane.ListPairings(ctx, session.Cookie)
if err != nil {
if isControlPlaneUnauthorized(err) {
s.stopRemoteAttempts(nil)
}
return
}
validPairings := make(map[string]struct{})
for _, pairing := range pairings {
if pairing.State != "active" || pairing.TargetUserDeviceID != registered.UserDeviceID {
continue
}
validPairings[pairing.PairingID] = struct{}{}
signature := ed25519.Sign(identity.PrivateKey, deviceLinkProof("list", pairing.PairingID, "", ""))
attempts, err := s.ControlPlane.ListDeviceLinkAttempts(
ctx, session.Cookie, pairing.PairingID, identity.DeviceID, signature,
)
if err != nil {
if isControlPlaneUnauthorized(err) {
s.stopRemoteAttempts(nil)
return
}
continue
}
for _, attempt := range attempts {
if attempt.State != "awaiting_owner" || attempt.OwnerDeviceID != identity.DeviceID ||
attempt.OwnerFingerprint != "" || attempt.OwnerICE != nil {
continue
}
s.startRemoteAttempt(ctx, session.Cookie, identity, pairing.PairingID, attempt)
}
}
s.stopRemoteAttempts(validPairings)
}
func (s *Service) ensureRegisteredDevice(
ctx context.Context,
sessionID string,
cookie string,
identity mobileremotebiz.DeviceIdentity,
) (RegisteredDevice, error) {
now := s.now()
s.remoteHost.mu.Lock()
if strings.TrimSpace(sessionID) != "" &&
s.remoteHost.registeredSession == sessionID &&
s.remoteHost.registeredDevice.UserDeviceID != "" &&
now.Before(s.remoteHost.registerAfter) {
registered := s.remoteHost.registeredDevice
s.remoteHost.mu.Unlock()
return registered, nil
}
s.remoteHost.mu.Unlock()
registered, err := s.registerIdentityResult(ctx, cookie, identity)
if err != nil {
return RegisteredDevice{}, err
}
s.remoteHost.mu.Lock()
s.remoteHost.registeredSession = strings.TrimSpace(sessionID)
s.remoteHost.registeredDevice = registered
s.remoteHost.registerAfter = now.Add(5 * time.Minute)
s.remoteHost.mu.Unlock()
return registered, nil
}
func (s *Service) startRemoteAttempt(
parent context.Context,
cookie string,
identity mobileremotebiz.DeviceIdentity,
pairingID string,
attempt DeviceLinkAttempt,
) {
s.remoteHost.mu.Lock()
if s.remoteHost.stopping || s.remoteHost.cancel == nil || parent.Err() != nil {
s.remoteHost.mu.Unlock()
return
}
if _, exists := s.remoteHost.attempts[attempt.AttemptID]; exists {
s.remoteHost.mu.Unlock()
return
}
ctx, cancel := context.WithCancel(parent)
s.remoteHost.nextGeneration++
generation := s.remoteHost.nextGeneration
s.remoteHost.attempts[attempt.AttemptID] = activeRemoteAttempt{
pairingID: pairingID, cancel: cancel, generation: generation,
}
handler := s.remoteHost.handler
liveEvents := s.remoteHost.liveEvents
s.remoteWG.Add(1)
s.remoteHost.mu.Unlock()
go func() {
defer s.remoteWG.Done()
defer cancel()
defer s.finishRemoteAttempt(attempt.AttemptID, generation)
var ok bool
attempt, ok = s.settledRemoteAttempt(ctx, cookie, identity, pairingID, attempt)
if !ok {
return
}
s.serveRemoteAttempt(ctx, handler, liveEvents, cookie, identity, pairingID, attempt)
}()
}
func (s *Service) finishRemoteAttempt(attemptID string, generation uint64) {
s.remoteHost.mu.Lock()
wake := s.remoteHost.attemptWake
finished := false
if current, exists := s.remoteHost.attempts[attemptID]; exists &&
current.generation == generation {
delete(s.remoteHost.attempts, attemptID)
finished = true
}
s.remoteHost.mu.Unlock()
if finished && wake != nil {
wake.Forget(attemptID)
}
}
func (s *Service) stopRemotePairing(pairingID string) {
pairingID = strings.TrimSpace(pairingID)
s.remoteHost.mu.Lock()
finishedAttemptIDs := make([]string, 0)
for attemptID, attempt := range s.remoteHost.attempts {
if attempt.pairingID != pairingID {
continue
}
attempt.cancel()
delete(s.remoteHost.attempts, attemptID)
finishedAttemptIDs = append(finishedAttemptIDs, attemptID)
}
manager := s.remoteHost.linkManager
wake := s.remoteHost.attemptWake
s.remoteHost.mu.Unlock()
if wake != nil {
for _, attemptID := range finishedAttemptIDs {
wake.Forget(attemptID)
}
}
if manager != nil {
manager.Invalidate(pairingID)
}
}
func isControlPlaneUnauthorized(err error) bool {
var controlPlaneErr *ControlPlaneError
return errors.As(err, &controlPlaneErr) &&
(controlPlaneErr.StatusCode == http.StatusUnauthorized ||
controlPlaneErr.StatusCode == http.StatusForbidden)
}
func (s *Service) settledRemoteAttempt(
ctx context.Context,
cookie string,
identity mobileremotebiz.DeviceIdentity,
pairingID string,
attempt DeviceLinkAttempt,
) (DeviceLinkAttempt, bool) {
startedAt := time.Now()
if len(attempt.STUNEndpoints) == 0 {
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "rendezvous_wait", Outcome: "skipped",
ElapsedMS: time.Since(startedAt).Milliseconds(),
})
return attempt, true
}
s.remoteHost.mu.Lock()
wake := s.remoteHost.attemptWake
var wakeVersion uint64
if wake != nil {
wakeVersion = wake.Version(attempt.AttemptID)
}
s.remoteHost.mu.Unlock()
if wake != nil {
settleCtx, cancel := context.WithTimeout(ctx, remoteCallerSettleFallback)
wake.Wait(settleCtx, attempt.AttemptID, wakeVersion)
cancel()
} else if !waitRemoteAttemptEventRetry(ctx, remoteCallerSettleFallback) {
return DeviceLinkAttempt{}, false
}
if ctx.Err() != nil {
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "rendezvous_wait", Outcome: "cancelled",
ElapsedMS: time.Since(startedAt).Milliseconds(),
})
return DeviceLinkAttempt{}, false
}
signature := ed25519.Sign(identity.PrivateKey, deviceLinkProof("list", pairingID, "", ""))
attempts, err := s.ControlPlane.ListDeviceLinkAttempts(
ctx, cookie, pairingID, identity.DeviceID, signature,
)
if err != nil {
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "rendezvous_refetch", Outcome: "failed",
ElapsedMS: time.Since(startedAt).Milliseconds(), Error: err.Error(),
})
return DeviceLinkAttempt{}, false
}
for _, latest := range attempts {
if latest.AttemptID == attempt.AttemptID && latest.State == "awaiting_owner" &&
latest.OwnerFingerprint == "" && latest.OwnerICE == nil {
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "rendezvous_wait", Outcome: "succeeded",
ElapsedMS: time.Since(startedAt).Milliseconds(),
})
return latest, true
}
}
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "rendezvous_wait", Outcome: "superseded",
ElapsedMS: time.Since(startedAt).Milliseconds(),
})
return DeviceLinkAttempt{}, false
}
func (s *Service) serveRemoteAttempt(
ctx context.Context,
handler http.Handler,
liveEvents AgentLiveEventSource,
cookie string,
identity mobileremotebiz.DeviceIdentity,
pairingID string,
attempt DeviceLinkAttempt,
) {
deadline, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(attempt.ExpiresAt))
if err != nil {
return
}
handshakeCtx, cancelHandshake := context.WithDeadline(ctx, deadline)
defer cancelHandshake()
s.remoteHost.mu.Lock()
manager := s.remoteHost.linkManager
s.remoteHost.mu.Unlock()
if manager == nil {
return
}
admission, err := manager.Admit(handshakeCtx, pairingID)
if err != nil {
return
}
defer admission.Close()
participant, err := authenticatedlink.NewParticipant(authenticatedlink.ParticipantConfig{
STUNEndpoints: append([]string(nil), attempt.STUNEndpoints...),
IncludeLoopback: s.includeLoopback,
})
if err != nil {
return
}
transferred := false
defer func() {
if !transferred {
_ = participant.Close()
}
}()
exchange, description, err := candidateexchange.Start(
participant,
candidateexchange.Config{},
)
if err != nil {
return
}
signature := ed25519.Sign(
identity.PrivateKey,
deviceLinkProof("update", pairingID, attempt.AttemptID, description.Fingerprint),
)
var callerWakeVersion uint64
if wake := s.remoteAttemptWake(); wake != nil {
callerWakeVersion = wake.Version(attempt.AttemptID)
}
participantStartedAt := time.Now()
updated, err := s.ControlPlane.UpdateDeviceLinkParticipant(
handshakeCtx, cookie, pairingID, attempt.AttemptID, identity.DeviceID,
DeviceLinkParticipantInput{
Fingerprint: description.Fingerprint,
ProtocolVersion: deviceLinkProtocolVersion,
ICE: DeviceLinkICEParams{
Ufrag: description.Ufrag, Pwd: description.Pwd,
Candidates: append([]string(nil), description.Candidates...),
},
IdentitySignature: signature,
},
)
if err == nil {
err = validateOwnerCandidatePublication(
updated,
attempt.AttemptID,
description,
)
}
if err != nil {
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "owner_participant", Outcome: "failed",
ElapsedMS: time.Since(participantStartedAt).Milliseconds(),
})
return
}
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "owner_participant", Outcome: "succeeded",
ElapsedMS: time.Since(participantStartedAt).Milliseconds(),
})
peer := updated.CallerICE
if peer == nil {
return
}
stopCandidateExchange := s.runRemoteCandidateExchange(
handshakeCtx,
cookie,
identity,
pairingID,
attempt.AttemptID,
updated.CallerFingerprint,
*peer,
callerWakeVersion,
exchange,
cancelHandshake,
)
defer stopCandidateExchange()
connectStartedAt := time.Now()
link, err := participant.Connect(handshakeCtx, authenticatedlink.Description{
Fingerprint: updated.CallerFingerprint,
Ufrag: peer.Ufrag,
Pwd: peer.Pwd,
Candidates: append([]string(nil), peer.Candidates...),
}, authenticatedlink.RoleOwner)
if err != nil {
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "direct_connect", Outcome: "failed",
ElapsedMS: time.Since(connectStartedAt).Milliseconds(), Error: err.Error(),
})
return
}
s.recordRemoteAttempt(RemoteAttemptEvent{
AttemptID: attempt.AttemptID, PairingID: pairingID,
Stage: "direct_connect", Outcome: "succeeded",
ElapsedMS: time.Since(connectStartedAt).Milliseconds(),
})
_, err = manager.Register(admission, linkmanager.Registration[string, remoteLinkMetadata]{
Key: pairingID,
ConnectionID: attempt.AttemptID,
Link: link,
Metadata: remoteLinkMetadata{
pairingID: pairingID, handler: handler, liveEvents: liveEvents,
},
HandleIncoming: serveManagedRemoteStream,
})
transferred = true
if err != nil {
return
}
cancelHandshake()
}
func (s *Service) stopRemoteAttempts(validPairings map[string]struct{}) {
s.remoteHost.mu.Lock()
invalidPairingSet := make(map[string]struct{})
finishedAttemptIDs := make([]string, 0)
for attemptID, attempt := range s.remoteHost.attempts {
if validPairings != nil {
if _, valid := validPairings[attempt.pairingID]; valid {
continue
}
}
attempt.cancel()
delete(s.remoteHost.attempts, attemptID)
invalidPairingSet[attempt.pairingID] = struct{}{}
finishedAttemptIDs = append(finishedAttemptIDs, attemptID)
}
manager := s.remoteHost.linkManager
wake := s.remoteHost.attemptWake
for pairingID := range s.remoteHost.managedLinks {
if validPairings != nil {
if _, valid := validPairings[pairingID]; valid {
continue
}
}
invalidPairingSet[pairingID] = struct{}{}
}
if validPairings == nil {
s.remoteHost.activePairings = nil
} else {
s.remoteHost.activePairings = make(map[string]struct{}, len(validPairings))
for pairingID := range validPairings {
s.remoteHost.activePairings[pairingID] = struct{}{}
}
}
if validPairings == nil {
s.remoteHost.registeredSession = ""
s.remoteHost.registeredDevice = RegisteredDevice{}
s.remoteHost.registerAfter = time.Time{}
}
s.remoteHost.mu.Unlock()
if wake != nil {
for _, attemptID := range finishedAttemptIDs {
wake.Forget(attemptID)
}
}
if manager == nil {
return
}
if validPairings == nil {
_ = manager.SetEnabled(false)
return
}
for pairingID := range invalidPairingSet {
manager.Invalidate(pairingID)
}
}
func (s *Service) setRemoteLinkEnabled(enabled bool) {
s.remoteHost.mu.Lock()
manager := s.remoteHost.linkManager
s.remoteHost.mu.Unlock()
if manager != nil {
_ = manager.SetEnabled(enabled)
}
}
func (s *Service) newRemoteLinkManager() *linkmanager.Manager[string, remoteLinkMetadata] {
return linkmanager.NewManager(linkmanager.ManagerConfig[string, remoteLinkMetadata]{
Observe: func(event linkmanager.LinkEvent[string, remoteLinkMetadata]) {
s.remoteHost.mu.Lock()
defer s.remoteHost.mu.Unlock()
if s.remoteHost.observedLinkEvents == nil {
s.remoteHost.observedLinkEvents = make(map[string]uint64)
}
if event.Sequence <= s.remoteHost.observedLinkEvents[event.ConnectionID] {
return
}
s.remoteHost.observedLinkEvents[event.ConnectionID] = event.Sequence
switch event.State {
case linkmanager.LinkReady:
if s.remoteHost.managedLinks != nil {
s.remoteHost.managedLinks[event.Key] = remoteManagedLink{
connectionID: event.ConnectionID,
}
}
case linkmanager.LinkDisconnected:
if s.remoteHost.managedLinks[event.Key].connectionID == event.ConnectionID {
delete(s.remoteHost.managedLinks, event.Key)
}
}
},
})
}
func serveManagedRemoteStream(
ctx context.Context,
incoming linkmanager.IncomingStream[string, remoteLinkMetadata],
) error {
metadata := incoming.Metadata
return devicelink.ServeStreamProbe(ctx, incoming.Stream, func(
ctx context.Context,
stream net.Conn,
) error {
return serveRemoteStreamWithAgentLive(
ctx,
stream,
metadata.handler,
metadata.pairingID,
metadata.liveEvents,
)
})
}
func deviceLinkProof(action, pairingID, attemptID, fingerprint string) []byte {
return []byte("tutti-device-link/1\n" + strings.TrimSpace(action) + "\n" +
strings.TrimSpace(pairingID) + "\n" + strings.TrimSpace(attemptID) + "\n" +
strings.TrimSpace(fingerprint))
}
func (s *Service) recordRemoteAttempt(event RemoteAttemptEvent) {
if s != nil && s.Diagnostics != nil {
s.Diagnostics.Record(event)
}
}