package rpc import ( "context" "encoding/json" "log/slog" agentpb "git.ipao.vip/rogee/go-sip/gen/agent" "git.ipao.vip/rogee/go-sip/internal/asterisk" "git.ipao.vip/rogee/go-sip/internal/configread" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // ApplySIP is deliberately unavailable on the Mock call server. A native // Agent accepts a complete Dispatcher-owned snapshot, then reports // only revisions it actually applied and inspected in native Asterisk. func (s *Server) ApplySIP(ctx context.Context, req *agentpb.ApplySIPRequest) (*agentpb.ApplySIPResponse, error) { if req == nil || req.Meta == nil || len(req.ApprovedSnapshotJson) == 0 || len(req.ApprovedSnapshotJson) > 1<<20 { return nil, status.Error(codes.InvalidArgument, "approved SIP request or bounded snapshot is required") } if err := s.authorize(ctx, req.Meta); err != nil { return nil, err } dispatcherID, err := s.sessions.ApprovedDispatcher(req.Meta, s.now()) if err != nil { return nil, err } if (s.mode != "sip-only" && s.mode != "nonprod-real") || s.applySIP == nil { return nil, status.Error(codes.FailedPrecondition, "real SIP apply is unavailable on this Agent") } var approved configread.SIP if err := json.Unmarshal(req.ApprovedSnapshotJson, &approved); err != nil || approved.DispatcherID != dispatcherID || approved.Revision <= 0 { return nil, status.Error(codes.InvalidArgument, "SIP snapshot owner or content is invalid") } if _, err := asterisk.Render(approved); err != nil { return nil, status.Error(codes.InvalidArgument, "SIP snapshot is not approved for native reload") } var trunks []struct { ID string `json:"trunk_id"` Enabled bool `json:"enabled"` } if err := json.Unmarshal(approved.Trunks, &trunks); err != nil { return nil, status.Error(codes.InvalidArgument, "SIP trunk list is invalid") } expected := make(map[string]bool) for _, trunk := range trunks { if trunk.Enabled { expected[trunk.ID] = true } } s.sipMu.Lock() defer s.sipMu.Unlock() loaded, err := s.applySIP(ctx, req.ApprovedSnapshotJson) if err != nil { slog.Error("native SIP apply failed", "dispatcher_id", dispatcherID, "revision", approved.Revision, "error", err) return nil, status.Error(codes.Unavailable, "native Asterisk SIP reload failed") } if len(loaded) != len(expected) { slog.Error("native SIP loaded-set mismatch", "dispatcher_id", dispatcherID, "revision", approved.Revision, "expected_count", len(expected), "loaded_count", len(loaded)) return nil, status.Error(codes.Unavailable, "native Asterisk SIP loaded-set mismatch") } for trunk, revision := range loaded { if !expected[trunk] || revision != approved.Revision { slog.Error("native SIP loaded-revision mismatch", "dispatcher_id", dispatcherID, "revision", approved.Revision, "trunk_id", trunk, "observed_revision", revision) return nil, status.Error(codes.Unavailable, "native Asterisk SIP loaded-revision mismatch") } } slog.Info("native Asterisk SIP revision confirmed", "dispatcher_id", dispatcherID, "revision", approved.Revision, "trunk_count", len(loaded)) return &agentpb.ApplySIPResponse{TrunkRevision: loaded}, nil }