package brain_control import ( "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "regexp" "strings" "time" ) var stableID = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$`) func MapSourceConfig(f SourceFacts) (SourceConfig, error) { if !stableID.MatchString(f.ConfigID) || !stableID.MatchString(f.SiteID) || !stableID.MatchString(f.LogicalDeviceID) || !stableID.MatchString(f.Profile.ID) || f.Revision < 1 || f.Profile.Width < 1 || f.Profile.Height < 1 || f.Profile.FrameRate <= 0 { return SourceConfig{}, errors.New("invalid source configuration facts") } encoding := strings.ToUpper(f.Profile.Encoding) if encoding != "H264" && encoding != "H265" && encoding != "MJPEG" { return SourceConfig{}, errors.New("unsupported profile encoding") } if f.PublishedAt.IsZero() || f.EffectiveAt.Before(f.PublishedAt) { return SourceConfig{}, errors.New("invalid source configuration time") } if !stableID.MatchString(f.RuleSetVersion) { return SourceConfig{}, errors.New("invalid rule set version") } if strings.ContainsAny(f.MediaPath, "?#@\\") || strings.Contains(f.MediaPath, "://") || f.MediaPath == "" { return SourceConfig{}, errors.New("media path must be opaque and credential-free") } if len(f.Areas) > 1024 || len(f.DirectionalLines) > 1024 { return SourceConfig{}, errors.New("too many rules") } seen := map[string]bool{} for _, a := range f.Areas { if !stableID.MatchString(a.ID) || seen[a.ID] || a.Version < 1 || a.Kind != "danger_area" || len(a.Points) < 3 || len(a.Points) > 256 || !validPoints(a.Points) || polygonArea(a.Points) == 0 { return SourceConfig{}, errors.New("invalid area rule") } seen[a.ID] = true } for _, l := range f.DirectionalLines { if !stableID.MatchString(l.ID) || seen[l.ID] || l.Version < 1 || l.Kind != "directional_line" || (l.TriggerDirection != "left_to_right" && l.TriggerDirection != "right_to_left") || !validPoints([]Point{l.Start, l.End}) || l.Start == l.End { return SourceConfig{}, errors.New("invalid directional line rule") } seen[l.ID] = true } var out SourceConfig out.SchemaVersion, out.ConfigID, out.Revision = SourceConfigVersion, f.ConfigID, f.Revision out.PublishedAt, out.EffectiveAt = f.PublishedAt.UTC(), f.EffectiveAt.UTC() out.Site.ID, out.LogicalDevice.ID = f.SiteID, f.LogicalDeviceID out.Profile = f.Profile out.Profile.Encoding = encoding out.Media.Ref, out.Media.Transport = "media:"+strings.TrimPrefix(f.MediaPath, "/"), "rtsp" out.RuleSet.Version = f.RuleSetVersion out.RuleSet.State = "active" if f.Disabled { out.RuleSet.State = "disabled" } if f.NeedsRecalibration { out.RuleSet.State = "recalibration_required" } out.RuleSet.ProfileBinding.ProfileID, out.RuleSet.ProfileBinding.Width, out.RuleSet.ProfileBinding.Height = f.Profile.ID, f.Profile.Width, f.Profile.Height out.RuleSet.Areas = append([]AreaRule(nil), f.Areas...) out.RuleSet.DirectionalLines = append([]DirectionalLineRule(nil), f.DirectionalLines...) if out.RuleSet.State != "active" { for i := range out.RuleSet.Areas { out.RuleSet.Areas[i].Enabled = false } for i := range out.RuleSet.DirectionalLines { out.RuleSet.DirectionalLines[i].Enabled = false } } digest, err := sourceDigest(out) if err != nil { return SourceConfig{}, err } out.Integrity.Algorithm, out.Integrity.Value = "sha256", digest return out, nil } func MarshalSourceConfig(config SourceConfig) ([]byte, error) { return json.Marshal(config) } func sourceDigest(config SourceConfig) (string, error) { raw, err := json.Marshal(config) if err != nil { return "", err } var value map[string]any if err = json.Unmarshal(raw, &value); err != nil { return "", err } delete(value, "integrity") canonical, err := json.Marshal(value) if err != nil { return "", err } sum := sha256.Sum256(canonical) return hex.EncodeToString(sum[:]), nil } func validPoints(points []Point) bool { for _, p := range points { if p.X < 0 || p.X > 1 || p.Y < 0 || p.Y > 1 { return false } } return true } func polygonArea(p []Point) float64 { var a float64 for i := range p { n := p[(i+1)%len(p)] a += p[i].X*n.Y - n.X*p[i].Y } if a < 0 { a = -a } return a / 2 } func ValidateSourceConfig(config SourceConfig) error { if config.SchemaVersion != SourceConfigVersion { return fmt.Errorf("unsupported source config version") } digest, err := sourceDigest(config) if err != nil || config.Integrity.Algorithm != "sha256" || digest != config.Integrity.Value { return errors.New("source config integrity mismatch") } _, err = MapSourceConfig(SourceFacts{ConfigID: config.ConfigID, SiteID: config.Site.ID, LogicalDeviceID: config.LogicalDevice.ID, MediaPath: strings.TrimPrefix(config.Media.Ref, "media:"), Revision: config.Revision, PublishedAt: config.PublishedAt, EffectiveAt: config.EffectiveAt, Profile: config.Profile, RuleSetVersion: config.RuleSet.Version, Disabled: config.RuleSet.State == "disabled", NeedsRecalibration: config.RuleSet.State == "recalibration_required", Areas: config.RuleSet.Areas, DirectionalLines: config.RuleSet.DirectionalLines}) return err } func UTCNow() time.Time { return time.Now().UTC() }