package main import ( "bytes" "context" "errors" "log" "maps" "net" "net/http" "os" "reflect" "slices" "strings" "time" agentpb "git.ipao.vip/rogee/go-sip/gen/agent" "git.ipao.vip/rogee/go-sip/internal/agent" "git.ipao.vip/rogee/go-sip/internal/config" "git.ipao.vip/rogee/go-sip/internal/rpc" "git.ipao.vip/rogee/go-sip/internal/tenant" "github.com/google/uuid" ) // newCurrentAgentServer binds the authenticated Agent session, task controls // and per-call Mock recording delivery. Serving the returned server requires // a separately verified mutual-TLS listener and a pinned local D connection. func newCurrentAgentServer(ctx context.Context, settings config.AgentEnvironment, scenario approvedMockScenario, appliedSIP map[string]int64, dispatcher agentpb.AgentControlServiceClient) (*rpc.Server, error) { if ctx == nil || ctx.Err() != nil || settings.AgentID == "" || settings.CellID == "" || settings.SessionPath == "" || settings.RecoveryRoot == "" || len(settings.PeerFingerprints) == 0 || len(appliedSIP) == 0 || scenario.MaxWAVBytes <= 44 || len(scenario.Script.Turns) == 0 || strings.TrimSpace(scenario.ReasonMessage) == "" { return nil, errors.New("current Agent requires an active process, explicit Mock media and deployment identity") } if tenant.ValidateDispatcherID(settings.DispatcherID) != nil { return nil, errors.New("current Agent requires an approved Dispatcher UUID v4") } if dispatcher == nil { return nil, errors.New("current Agent requires a pinned Dispatcher transport") } value := reflect.ValueOf(dispatcher) switch value.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: if value.IsNil() { return nil, errors.New("current Agent requires a pinned Dispatcher transport") } } root, err := os.Stat(settings.RecoveryRoot) if err != nil || !root.IsDir() || root.Mode().Perm() != 0700 { return nil, errors.New("current Agent requires an existing private 0700 recovery directory") } for trunk, revision := range appliedSIP { if strings.TrimSpace(trunk) == "" || revision <= 0 { return nil, errors.New("current Agent requires explicit applied Mock SIP revisions") } } loaded := maps.Clone(appliedSIP) pins := maps.Clone(settings.PeerFingerprints) scenario.InboundPCM16 = bytes.Clone(scenario.InboundPCM16) scenario.Script.OpeningPCM16 = bytes.Clone(scenario.Script.OpeningPCM16) scenario.Script.Turns = slices.Clone(scenario.Script.Turns) for index := range scenario.Script.Turns { scenario.Script.Turns[index].ReplyPCM16 = bytes.Clone(scenario.Script.Turns[index].ReplyPCM16) } uploadHTTP := localMockHTTPClient() var handler *rpc.Server worker := &rpc.ApprovedCallWorker{ Lifecycle: ctx, Calls: &agent.TaskCalls{}, Prepare: func(execution rpc.ApprovedExecution) (func(context.Context) error, error) { if handler == nil { return nil, errors.New("Agent session is unavailable") } delivery := &agent.RecordingDelivery{ Call: agent.RecordingClient{ Client: dispatcher, DispatcherID: execution.DispatcherID, TenantID: execution.TenantID, SourceEventID: execution.SourceEventID, Session: func(context.Context) (*agentpb.RequestMeta, error) { return handler.ActiveSessionMeta() }, }, Recovery: &agent.RecordingRecovery{ Root: settings.RecoveryRoot, Upload: agent.UploadClient{HTTPClient: uploadHTTP, AllowInsecureHTTP: true}, }, } mock := &rpc.ApprovedRecordedMockCall{ InboundPCM16: scenario.InboundPCM16, Script: scenario.Script, MaxWAVBytes: scenario.MaxWAVBytes, ExpectedRecording: scenario.ExpectedRecording, Outcome: scenario.Outcome, ReasonMessage: scenario.ReasonMessage, ReportTimeout: 15 * time.Minute, Delivery: delivery, } return mock.Prepare(execution) }, OnFailure: func(execution rpc.ApprovedExecution, cause error) error { // The runner already tried to report termination and persisted any // failed upload. Never invent a second result or retry an unknown PUT. log.Printf("Agent Mock call requires inspection: event_id=%q task_id=%q cause_type=%T", execution.SourceEventID, execution.TaskID, cause) return nil }, } handler, err = rpc.NewApprovedAgentServer(rpc.ServerOptions{ Mode: "mock", StatePath: settings.SessionPath, ApprovedDispatcherID: settings.DispatcherID, Status: &agentpb.AgentStatus{AgentId: settings.AgentID, CellId: settings.CellID, BootId: uuid.NewString(), ProtocolVersion: "agent.v1"}, LoadedSIP: func(context.Context) (map[string]int64, error) { return maps.Clone(loaded), nil }, PeerCertificateFingerprints: pins, RequirePeerCertificate: true, }, worker) if err != nil { return nil, err } return handler, nil } // The isolated Mock uploader may reach localhost only, even if a grant or // redirect unexpectedly names a real OSS endpoint. It never logs signed URLs. func localMockHTTPClient() *http.Client { transport := http.DefaultTransport.(*http.Transport).Clone() transport.Proxy = nil transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { host, _, err := net.SplitHostPort(address) if err != nil { return nil, errors.New("Mock upload target is not local") } ip := net.ParseIP(host) if !strings.EqualFold(host, "localhost") && (ip == nil || !ip.IsLoopback()) { return nil, errors.New("Mock upload target is not local") } return (&net.Dialer{}).DialContext(ctx, network, address) } return &http.Client{Transport: transport, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }} }