···101101HOLD_BLUESKY_POSTS_ENABLED=true
102102103103# ==============================================================================
104104+# Scanner Configuration (SBOM & Vulnerability Scanning)
105105+# ==============================================================================
106106+107107+# Enable automatic SBOM generation and vulnerability scanning on image push
108108+# When enabled, the hold service will:
109109+# 1. Generate SBOM (Software Bill of Materials) using Syft
110110+# 2. Scan for vulnerabilities using Grype
111111+# 3. Store results as ORAS artifacts (OCI referrers pattern)
112112+# 4. Display vulnerability counts on repository pages in AppView
113113+#
114114+# Default: true
115115+HOLD_SBOM_ENABLED=true
116116+117117+# Number of concurrent scanner worker threads
118118+# Increase for faster scanning on multi-core systems
119119+# Default: 2
120120+HOLD_SBOM_WORKERS=2
121121+122122+# Enable vulnerability scanning with Grype
123123+# If false, only SBOM generation (Syft) will run
124124+# Default: true
125125+HOLD_VULN_ENABLED=true
126126+127127+# Path to Grype vulnerability database
128128+# Database is auto-downloaded and cached at this location on first run
129129+# Default: /var/lib/atcr-hold/grype-db
130130+HOLD_VULN_DB_PATH=/var/lib/atcr-hold/grype-db
131131+132132+# How often to update vulnerability database
133133+# Examples: 24h, 12h, 48h
134134+# Default: 24h
135135+HOLD_VULN_DB_UPDATE_INTERVAL=24h
136136+137137+# ==============================================================================
104138# S3/UpCloud Object Storage Configuration
105139# ==============================================================================
106140
···151151 IsMultiArch bool
152152}
153153154154+// VulnerabilitySummary represents vulnerability counts by severity
155155+type VulnerabilitySummary struct {
156156+ Critical int
157157+ High int
158158+ Medium int
159159+ Low int
160160+ Total int
161161+}
162162+154163// ManifestWithMetadata extends Manifest with tags and platform information
155164type ManifestWithMetadata struct {
156165 Manifest
157157- Tags []string
158158- Platforms []PlatformInfo
159159- PlatformCount int
160160- IsManifestList bool
161161- Reachable bool // Whether the hold endpoint is reachable
162162- Pending bool // Whether health check is still in progress
166166+ Tags []string
167167+ Platforms []PlatformInfo
168168+ PlatformCount int
169169+ IsManifestList bool
170170+ Reachable bool // Whether the hold endpoint is reachable
171171+ Pending bool // Whether health check is still in progress
172172+ Vulnerabilities *VulnerabilitySummary
173173+ HasVulnerabilities bool
163174}
+213
pkg/appview/handlers/repository.go
···33import (
44 "context"
55 "database/sql"
66+ "encoding/json"
77+ "fmt"
68 "html/template"
99+ "io"
710 "log/slog"
811 "net/http"
1212+ "net/url"
1313+ "strconv"
914 "sync"
1015 "time"
1116···3035 ReadmeCache *readme.Cache
3136}
32373838+// queryVulnerabilities queries the hold service for vulnerability scan results
3939+func (h *RepositoryPageHandler) queryVulnerabilities(ctx context.Context, holdEndpoint string, digest string) (*db.VulnerabilitySummary, error) {
4040+ // Skip if no hold endpoint
4141+ if holdEndpoint == "" {
4242+ return nil, nil
4343+ }
4444+4545+ // Query referrers endpoint for vulnerability scan results
4646+ // Match the artifactType used by the scanner in pkg/hold/scanner/storage.go
4747+ artifactType := "application/vnd.atcr.vulnerabilities+json"
4848+4949+ // Properly encode query parameters (especially the + in the media type)
5050+ queryParams := url.Values{}
5151+ queryParams.Set("digest", digest)
5252+ queryParams.Set("artifactType", artifactType)
5353+ requestURL := fmt.Sprintf("%s/xrpc/io.atcr.hold.getReferrers?%s", holdEndpoint, queryParams.Encode())
5454+5555+ req, err := http.NewRequestWithContext(ctx, "GET", requestURL, nil)
5656+ if err != nil {
5757+ return nil, err
5858+ }
5959+6060+ resp, err := http.DefaultClient.Do(req)
6161+ if err != nil {
6262+ return nil, err
6363+ }
6464+ defer resp.Body.Close()
6565+6666+ if resp.StatusCode == http.StatusNotFound {
6767+ // No scan results found
6868+ return nil, nil
6969+ }
7070+7171+ if resp.StatusCode != http.StatusOK {
7272+ body, _ := io.ReadAll(resp.Body)
7373+ return nil, fmt.Errorf("failed to query referrers: %s - %s", resp.Status, string(body))
7474+ }
7575+7676+ // Parse response
7777+ var result struct {
7878+ Referrers []struct {
7979+ Annotations map[string]string `json:"annotations"`
8080+ } `json:"referrers"`
8181+ }
8282+8383+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
8484+ return nil, fmt.Errorf("failed to decode referrers response: %w", err)
8585+ }
8686+8787+ slog.Debug("Received referrers response",
8888+ "digest", digest,
8989+ "referrerCount", len(result.Referrers))
9090+9191+ // Find the most recent vulnerability scan result
9292+ if len(result.Referrers) == 0 {
9393+ return nil, nil
9494+ }
9595+9696+ // Parse vulnerability counts from annotations
9797+ // Match the annotation keys used by the scanner in pkg/hold/scanner/storage.go
9898+ annotations := result.Referrers[0].Annotations
9999+ slog.Debug("First referrer annotations",
100100+ "digest", digest,
101101+ "annotations", annotations,
102102+ "annotationsLen", len(annotations))
103103+104104+ summary := &db.VulnerabilitySummary{}
105105+106106+ if critical, ok := annotations["io.atcr.vuln.critical"]; ok {
107107+ summary.Critical, _ = strconv.Atoi(critical)
108108+ }
109109+ if high, ok := annotations["io.atcr.vuln.high"]; ok {
110110+ summary.High, _ = strconv.Atoi(high)
111111+ }
112112+ if medium, ok := annotations["io.atcr.vuln.medium"]; ok {
113113+ summary.Medium, _ = strconv.Atoi(medium)
114114+ }
115115+ if low, ok := annotations["io.atcr.vuln.low"]; ok {
116116+ summary.Low, _ = strconv.Atoi(low)
117117+ }
118118+ if total, ok := annotations["io.atcr.vuln.total"]; ok {
119119+ summary.Total, _ = strconv.Atoi(total)
120120+ }
121121+122122+ // If Total is missing or 0, calculate from individual counts
123123+ if summary.Total == 0 {
124124+ summary.Total = summary.Critical + summary.High + summary.Medium + summary.Low
125125+ }
126126+127127+ slog.Debug("Parsed vulnerability summary",
128128+ "digest", digest,
129129+ "critical", summary.Critical,
130130+ "high", summary.High,
131131+ "medium", summary.Medium,
132132+ "low", summary.Low,
133133+ "total", summary.Total)
134134+135135+ return summary, nil
136136+}
137137+138138+// HandleVulnerabilityDetails returns the full vulnerability report for a manifest
139139+func (h *RepositoryPageHandler) HandleVulnerabilityDetails(w http.ResponseWriter, r *http.Request) {
140140+ digest := r.URL.Query().Get("digest")
141141+ holdEndpoint := r.URL.Query().Get("holdEndpoint")
142142+143143+ if digest == "" || holdEndpoint == "" {
144144+ http.Error(w, "digest and holdEndpoint required", http.StatusBadRequest)
145145+ return
146146+ }
147147+148148+ // Query referrers to get the vulnerability report digest
149149+ artifactType := "application/vnd.atcr.vulnerabilities+json"
150150+ queryParams := url.Values{}
151151+ queryParams.Set("digest", digest)
152152+ queryParams.Set("artifactType", artifactType)
153153+ requestURL := fmt.Sprintf("%s/xrpc/io.atcr.hold.getReferrers?%s", holdEndpoint, queryParams.Encode())
154154+155155+ req, err := http.NewRequestWithContext(r.Context(), "GET", requestURL, nil)
156156+ if err != nil {
157157+ http.Error(w, err.Error(), http.StatusInternalServerError)
158158+ return
159159+ }
160160+161161+ resp, err := http.DefaultClient.Do(req)
162162+ if err != nil {
163163+ http.Error(w, err.Error(), http.StatusInternalServerError)
164164+ return
165165+ }
166166+ defer resp.Body.Close()
167167+168168+ if resp.StatusCode == http.StatusNotFound {
169169+ http.Error(w, "No vulnerability scan found", http.StatusNotFound)
170170+ return
171171+ }
172172+173173+ if resp.StatusCode != http.StatusOK {
174174+ http.Error(w, "Failed to query referrers", resp.StatusCode)
175175+ return
176176+ }
177177+178178+ // Parse response - now includes the vulnerability report data directly
179179+ var result struct {
180180+ Referrers []struct {
181181+ Digest string `json:"digest"`
182182+ Annotations map[string]string `json:"annotations"`
183183+ ReportData map[string]interface{} `json:"reportData"` // The actual vulnerability report
184184+ } `json:"referrers"`
185185+ }
186186+187187+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
188188+ http.Error(w, "Failed to decode referrers response", http.StatusInternalServerError)
189189+ return
190190+ }
191191+192192+ if len(result.Referrers) == 0 {
193193+ http.Error(w, "No vulnerability scan found", http.StatusNotFound)
194194+ return
195195+ }
196196+197197+ // Check if reportData is included
198198+ if result.Referrers[0].ReportData == nil {
199199+ http.Error(w, "Vulnerability report data not available", http.StatusNotFound)
200200+ return
201201+ }
202202+203203+ // Return the vulnerability report JSON directly
204204+ w.Header().Set("Content-Type", "application/json")
205205+ json.NewEncoder(w).Encode(result.Referrers[0].ReportData)
206206+}
207207+33208func (h *RepositoryPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
34209 handle := chi.URLParam(r, "handle")
35210 repository := chi.URLParam(r, "repository")
···58233 if err != nil {
59234 http.Error(w, err.Error(), http.StatusInternalServerError)
60235 return
236236+ }
237237+238238+ // Query vulnerability scan results for each manifest (concurrent with 2s timeout)
239239+ {
240240+ ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
241241+ defer cancel()
242242+243243+ var wg sync.WaitGroup
244244+ var mu sync.Mutex
245245+246246+ for i := range manifests {
247247+ // Skip manifest lists - only query for image manifests
248248+ if manifests[i].IsManifestList {
249249+ continue
250250+ }
251251+252252+ wg.Add(1)
253253+ go func(idx int) {
254254+ defer wg.Done()
255255+256256+ vulnerabilities, err := h.queryVulnerabilities(ctx, manifests[idx].Manifest.HoldEndpoint, manifests[idx].Manifest.Digest)
257257+ if err != nil {
258258+ slog.Warn("Failed to query vulnerabilities",
259259+ "digest", manifests[idx].Manifest.Digest,
260260+ "error", err)
261261+ return
262262+ }
263263+264264+ mu.Lock()
265265+ if vulnerabilities != nil && vulnerabilities.Total > 0 {
266266+ manifests[idx].Vulnerabilities = vulnerabilities
267267+ manifests[idx].HasVulnerabilities = true
268268+ }
269269+ mu.Unlock()
270270+ }(i)
271271+ }
272272+273273+ wg.Wait()
61274 }
6227563276 // Check health status for each manifest's hold endpoint (concurrent with 1s timeout)
···1419141914201420 return nil
14211421}
14221422+func (t *ManifestRecord) MarshalCBOR(w io.Writer) error {
14231423+ if t == nil {
14241424+ _, err := w.Write(cbg.CborNull)
14251425+ return err
14261426+ }
14271427+14281428+ cw := cbg.NewCborWriter(w)
14291429+ fieldCount := 18
14301430+14311431+ if t.HoldDID == "" {
14321432+ fieldCount--
14331433+ }
14341434+14351435+ if t.HoldEndpoint == "" {
14361436+ fieldCount--
14371437+ }
14381438+14391439+ if t.ArtifactType == "" {
14401440+ fieldCount--
14411441+ }
14421442+14431443+ if t.OwnerDID == "" {
14441444+ fieldCount--
14451445+ }
14461446+14471447+ if t.ScannedAt == "" {
14481448+ fieldCount--
14491449+ }
14501450+14511451+ if t.ScannerVersion == "" {
14521452+ fieldCount--
14531453+ }
14541454+14551455+ if t.Config == nil {
14561456+ fieldCount--
14571457+ }
14581458+14591459+ if t.Layers == nil {
14601460+ fieldCount--
14611461+ }
14621462+14631463+ if t.Manifests == nil {
14641464+ fieldCount--
14651465+ }
14661466+14671467+ if t.Subject == nil {
14681468+ fieldCount--
14691469+ }
14701470+14711471+ if t.ManifestBlob == nil {
14721472+ fieldCount--
14731473+ }
14741474+14751475+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
14761476+ return err
14771477+ }
14781478+14791479+ // t.Type (string) (string)
14801480+ if len("$type") > 8192 {
14811481+ return xerrors.Errorf("Value in field \"$type\" was too long")
14821482+ }
14831483+14841484+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
14851485+ return err
14861486+ }
14871487+ if _, err := cw.WriteString(string("$type")); err != nil {
14881488+ return err
14891489+ }
14901490+14911491+ if len(t.Type) > 8192 {
14921492+ return xerrors.Errorf("Value in field t.Type was too long")
14931493+ }
14941494+14951495+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Type))); err != nil {
14961496+ return err
14971497+ }
14981498+ if _, err := cw.WriteString(string(t.Type)); err != nil {
14991499+ return err
15001500+ }
15011501+15021502+ // t.Config (atproto.BlobReference) (struct)
15031503+ if t.Config != nil {
15041504+15051505+ if len("config") > 8192 {
15061506+ return xerrors.Errorf("Value in field \"config\" was too long")
15071507+ }
15081508+15091509+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("config"))); err != nil {
15101510+ return err
15111511+ }
15121512+ if _, err := cw.WriteString(string("config")); err != nil {
15131513+ return err
15141514+ }
15151515+15161516+ if err := t.Config.MarshalCBOR(cw); err != nil {
15171517+ return err
15181518+ }
15191519+ }
15201520+15211521+ // t.Digest (string) (string)
15221522+ if len("digest") > 8192 {
15231523+ return xerrors.Errorf("Value in field \"digest\" was too long")
15241524+ }
15251525+15261526+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("digest"))); err != nil {
15271527+ return err
15281528+ }
15291529+ if _, err := cw.WriteString(string("digest")); err != nil {
15301530+ return err
15311531+ }
15321532+15331533+ if len(t.Digest) > 8192 {
15341534+ return xerrors.Errorf("Value in field t.Digest was too long")
15351535+ }
15361536+15371537+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Digest))); err != nil {
15381538+ return err
15391539+ }
15401540+ if _, err := cw.WriteString(string(t.Digest)); err != nil {
15411541+ return err
15421542+ }
15431543+15441544+ // t.Layers ([]atproto.BlobReference) (slice)
15451545+ if t.Layers != nil {
15461546+15471547+ if len("layers") > 8192 {
15481548+ return xerrors.Errorf("Value in field \"layers\" was too long")
15491549+ }
15501550+15511551+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("layers"))); err != nil {
15521552+ return err
15531553+ }
15541554+ if _, err := cw.WriteString(string("layers")); err != nil {
15551555+ return err
15561556+ }
15571557+15581558+ if len(t.Layers) > 8192 {
15591559+ return xerrors.Errorf("Slice value in field t.Layers was too long")
15601560+ }
15611561+15621562+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Layers))); err != nil {
15631563+ return err
15641564+ }
15651565+ for _, v := range t.Layers {
15661566+ if err := v.MarshalCBOR(cw); err != nil {
15671567+ return err
15681568+ }
15691569+15701570+ }
15711571+ }
15721572+15731573+ // t.HoldDID (string) (string)
15741574+ if t.HoldDID != "" {
15751575+15761576+ if len("holdDid") > 8192 {
15771577+ return xerrors.Errorf("Value in field \"holdDid\" was too long")
15781578+ }
15791579+15801580+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("holdDid"))); err != nil {
15811581+ return err
15821582+ }
15831583+ if _, err := cw.WriteString(string("holdDid")); err != nil {
15841584+ return err
15851585+ }
15861586+15871587+ if len(t.HoldDID) > 8192 {
15881588+ return xerrors.Errorf("Value in field t.HoldDID was too long")
15891589+ }
15901590+15911591+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.HoldDID))); err != nil {
15921592+ return err
15931593+ }
15941594+ if _, err := cw.WriteString(string(t.HoldDID)); err != nil {
15951595+ return err
15961596+ }
15971597+ }
15981598+15991599+ // t.Subject (atproto.BlobReference) (struct)
16001600+ if t.Subject != nil {
16011601+16021602+ if len("subject") > 8192 {
16031603+ return xerrors.Errorf("Value in field \"subject\" was too long")
16041604+ }
16051605+16061606+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
16071607+ return err
16081608+ }
16091609+ if _, err := cw.WriteString(string("subject")); err != nil {
16101610+ return err
16111611+ }
16121612+16131613+ if err := t.Subject.MarshalCBOR(cw); err != nil {
16141614+ return err
16151615+ }
16161616+ }
16171617+16181618+ // t.OwnerDID (string) (string)
16191619+ if t.OwnerDID != "" {
16201620+16211621+ if len("ownerDid") > 8192 {
16221622+ return xerrors.Errorf("Value in field \"ownerDid\" was too long")
16231623+ }
16241624+16251625+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("ownerDid"))); err != nil {
16261626+ return err
16271627+ }
16281628+ if _, err := cw.WriteString(string("ownerDid")); err != nil {
16291629+ return err
16301630+ }
16311631+16321632+ if len(t.OwnerDID) > 8192 {
16331633+ return xerrors.Errorf("Value in field t.OwnerDID was too long")
16341634+ }
16351635+16361636+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.OwnerDID))); err != nil {
16371637+ return err
16381638+ }
16391639+ if _, err := cw.WriteString(string(t.OwnerDID)); err != nil {
16401640+ return err
16411641+ }
16421642+ }
16431643+16441644+ // t.CreatedAt (string) (string)
16451645+ if len("createdAt") > 8192 {
16461646+ return xerrors.Errorf("Value in field \"createdAt\" was too long")
16471647+ }
16481648+16491649+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
16501650+ return err
16511651+ }
16521652+ if _, err := cw.WriteString(string("createdAt")); err != nil {
16531653+ return err
16541654+ }
16551655+16561656+ if len(t.CreatedAt) > 8192 {
16571657+ return xerrors.Errorf("Value in field t.CreatedAt was too long")
16581658+ }
16591659+16601660+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
16611661+ return err
16621662+ }
16631663+ if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
16641664+ return err
16651665+ }
16661666+16671667+ // t.Manifests ([]atproto.ManifestReference) (slice)
16681668+ if t.Manifests != nil {
16691669+16701670+ if len("manifests") > 8192 {
16711671+ return xerrors.Errorf("Value in field \"manifests\" was too long")
16721672+ }
16731673+16741674+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("manifests"))); err != nil {
16751675+ return err
16761676+ }
16771677+ if _, err := cw.WriteString(string("manifests")); err != nil {
16781678+ return err
16791679+ }
16801680+16811681+ if len(t.Manifests) > 8192 {
16821682+ return xerrors.Errorf("Slice value in field t.Manifests was too long")
16831683+ }
16841684+16851685+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Manifests))); err != nil {
16861686+ return err
16871687+ }
16881688+ for _, v := range t.Manifests {
16891689+ if err := v.MarshalCBOR(cw); err != nil {
16901690+ return err
16911691+ }
16921692+16931693+ }
16941694+ }
16951695+16961696+ // t.MediaType (string) (string)
16971697+ if len("mediaType") > 8192 {
16981698+ return xerrors.Errorf("Value in field \"mediaType\" was too long")
16991699+ }
17001700+17011701+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mediaType"))); err != nil {
17021702+ return err
17031703+ }
17041704+ if _, err := cw.WriteString(string("mediaType")); err != nil {
17051705+ return err
17061706+ }
17071707+17081708+ if len(t.MediaType) > 8192 {
17091709+ return xerrors.Errorf("Value in field t.MediaType was too long")
17101710+ }
17111711+17121712+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.MediaType))); err != nil {
17131713+ return err
17141714+ }
17151715+ if _, err := cw.WriteString(string(t.MediaType)); err != nil {
17161716+ return err
17171717+ }
17181718+17191719+ // t.ScannedAt (string) (string)
17201720+ if t.ScannedAt != "" {
17211721+17221722+ if len("scannedAt") > 8192 {
17231723+ return xerrors.Errorf("Value in field \"scannedAt\" was too long")
17241724+ }
17251725+17261726+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("scannedAt"))); err != nil {
17271727+ return err
17281728+ }
17291729+ if _, err := cw.WriteString(string("scannedAt")); err != nil {
17301730+ return err
17311731+ }
17321732+17331733+ if len(t.ScannedAt) > 8192 {
17341734+ return xerrors.Errorf("Value in field t.ScannedAt was too long")
17351735+ }
17361736+17371737+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.ScannedAt))); err != nil {
17381738+ return err
17391739+ }
17401740+ if _, err := cw.WriteString(string(t.ScannedAt)); err != nil {
17411741+ return err
17421742+ }
17431743+ }
17441744+17451745+ // t.Repository (string) (string)
17461746+ if len("repository") > 8192 {
17471747+ return xerrors.Errorf("Value in field \"repository\" was too long")
17481748+ }
17491749+17501750+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repository"))); err != nil {
17511751+ return err
17521752+ }
17531753+ if _, err := cw.WriteString(string("repository")); err != nil {
17541754+ return err
17551755+ }
17561756+17571757+ if len(t.Repository) > 8192 {
17581758+ return xerrors.Errorf("Value in field t.Repository was too long")
17591759+ }
17601760+17611761+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Repository))); err != nil {
17621762+ return err
17631763+ }
17641764+ if _, err := cw.WriteString(string(t.Repository)); err != nil {
17651765+ return err
17661766+ }
17671767+17681768+ // t.Annotations (map[string]string) (map)
17691769+ if len("annotations") > 8192 {
17701770+ return xerrors.Errorf("Value in field \"annotations\" was too long")
17711771+ }
17721772+17731773+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("annotations"))); err != nil {
17741774+ return err
17751775+ }
17761776+ if _, err := cw.WriteString(string("annotations")); err != nil {
17771777+ return err
17781778+ }
17791779+17801780+ {
17811781+ if len(t.Annotations) > 4096 {
17821782+ return xerrors.Errorf("cannot marshal t.Annotations map too large")
17831783+ }
17841784+17851785+ if err := cw.WriteMajorTypeHeader(cbg.MajMap, uint64(len(t.Annotations))); err != nil {
17861786+ return err
17871787+ }
17881788+17891789+ keys := make([]string, 0, len(t.Annotations))
17901790+ for k := range t.Annotations {
17911791+ keys = append(keys, k)
17921792+ }
17931793+ sort.Strings(keys)
17941794+ for _, k := range keys {
17951795+ v := t.Annotations[k]
17961796+17971797+ if len(k) > 8192 {
17981798+ return xerrors.Errorf("Value in field k was too long")
17991799+ }
18001800+18011801+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(k))); err != nil {
18021802+ return err
18031803+ }
18041804+ if _, err := cw.WriteString(string(k)); err != nil {
18051805+ return err
18061806+ }
18071807+18081808+ if len(v) > 8192 {
18091809+ return xerrors.Errorf("Value in field v was too long")
18101810+ }
18111811+18121812+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
18131813+ return err
18141814+ }
18151815+ if _, err := cw.WriteString(string(v)); err != nil {
18161816+ return err
18171817+ }
18181818+18191819+ }
18201820+ }
18211821+18221822+ // t.ArtifactType (string) (string)
18231823+ if t.ArtifactType != "" {
18241824+18251825+ if len("artifactType") > 8192 {
18261826+ return xerrors.Errorf("Value in field \"artifactType\" was too long")
18271827+ }
18281828+18291829+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artifactType"))); err != nil {
18301830+ return err
18311831+ }
18321832+ if _, err := cw.WriteString(string("artifactType")); err != nil {
18331833+ return err
18341834+ }
18351835+18361836+ if len(t.ArtifactType) > 8192 {
18371837+ return xerrors.Errorf("Value in field t.ArtifactType was too long")
18381838+ }
18391839+18401840+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.ArtifactType))); err != nil {
18411841+ return err
18421842+ }
18431843+ if _, err := cw.WriteString(string(t.ArtifactType)); err != nil {
18441844+ return err
18451845+ }
18461846+ }
18471847+18481848+ // t.HoldEndpoint (string) (string)
18491849+ if t.HoldEndpoint != "" {
18501850+18511851+ if len("holdEndpoint") > 8192 {
18521852+ return xerrors.Errorf("Value in field \"holdEndpoint\" was too long")
18531853+ }
18541854+18551855+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("holdEndpoint"))); err != nil {
18561856+ return err
18571857+ }
18581858+ if _, err := cw.WriteString(string("holdEndpoint")); err != nil {
18591859+ return err
18601860+ }
18611861+18621862+ if len(t.HoldEndpoint) > 8192 {
18631863+ return xerrors.Errorf("Value in field t.HoldEndpoint was too long")
18641864+ }
18651865+18661866+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.HoldEndpoint))); err != nil {
18671867+ return err
18681868+ }
18691869+ if _, err := cw.WriteString(string(t.HoldEndpoint)); err != nil {
18701870+ return err
18711871+ }
18721872+ }
18731873+18741874+ // t.ManifestBlob (atproto.ATProtoBlobRef) (struct)
18751875+ if t.ManifestBlob != nil {
18761876+18771877+ if len("manifestBlob") > 8192 {
18781878+ return xerrors.Errorf("Value in field \"manifestBlob\" was too long")
18791879+ }
18801880+18811881+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("manifestBlob"))); err != nil {
18821882+ return err
18831883+ }
18841884+ if _, err := cw.WriteString(string("manifestBlob")); err != nil {
18851885+ return err
18861886+ }
18871887+18881888+ if err := t.ManifestBlob.MarshalCBOR(cw); err != nil {
18891889+ return err
18901890+ }
18911891+ }
18921892+18931893+ // t.SchemaVersion (int64) (int64)
18941894+ if len("schemaVersion") > 8192 {
18951895+ return xerrors.Errorf("Value in field \"schemaVersion\" was too long")
18961896+ }
18971897+18981898+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("schemaVersion"))); err != nil {
18991899+ return err
19001900+ }
19011901+ if _, err := cw.WriteString(string("schemaVersion")); err != nil {
19021902+ return err
19031903+ }
19041904+19051905+ if t.SchemaVersion >= 0 {
19061906+ if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.SchemaVersion)); err != nil {
19071907+ return err
19081908+ }
19091909+ } else {
19101910+ if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.SchemaVersion-1)); err != nil {
19111911+ return err
19121912+ }
19131913+ }
19141914+19151915+ // t.ScannerVersion (string) (string)
19161916+ if t.ScannerVersion != "" {
19171917+19181918+ if len("scannerVersion") > 8192 {
19191919+ return xerrors.Errorf("Value in field \"scannerVersion\" was too long")
19201920+ }
19211921+19221922+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("scannerVersion"))); err != nil {
19231923+ return err
19241924+ }
19251925+ if _, err := cw.WriteString(string("scannerVersion")); err != nil {
19261926+ return err
19271927+ }
19281928+19291929+ if len(t.ScannerVersion) > 8192 {
19301930+ return xerrors.Errorf("Value in field t.ScannerVersion was too long")
19311931+ }
19321932+19331933+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.ScannerVersion))); err != nil {
19341934+ return err
19351935+ }
19361936+ if _, err := cw.WriteString(string(t.ScannerVersion)); err != nil {
19371937+ return err
19381938+ }
19391939+ }
19401940+ return nil
19411941+}
19421942+19431943+func (t *ManifestRecord) UnmarshalCBOR(r io.Reader) (err error) {
19441944+ *t = ManifestRecord{}
19451945+19461946+ cr := cbg.NewCborReader(r)
19471947+19481948+ maj, extra, err := cr.ReadHeader()
19491949+ if err != nil {
19501950+ return err
19511951+ }
19521952+ defer func() {
19531953+ if err == io.EOF {
19541954+ err = io.ErrUnexpectedEOF
19551955+ }
19561956+ }()
19571957+19581958+ if maj != cbg.MajMap {
19591959+ return fmt.Errorf("cbor input should be of type map")
19601960+ }
19611961+19621962+ if extra > cbg.MaxLength {
19631963+ return fmt.Errorf("ManifestRecord: map struct too large (%d)", extra)
19641964+ }
19651965+19661966+ n := extra
19671967+19681968+ nameBuf := make([]byte, 14)
19691969+ for i := uint64(0); i < n; i++ {
19701970+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
19711971+ if err != nil {
19721972+ return err
19731973+ }
19741974+19751975+ if !ok {
19761976+ // Field doesn't exist on this type, so ignore it
19771977+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
19781978+ return err
19791979+ }
19801980+ continue
19811981+ }
19821982+19831983+ switch string(nameBuf[:nameLen]) {
19841984+ // t.Type (string) (string)
19851985+ case "$type":
19861986+19871987+ {
19881988+ sval, err := cbg.ReadStringWithMax(cr, 8192)
19891989+ if err != nil {
19901990+ return err
19911991+ }
19921992+19931993+ t.Type = string(sval)
19941994+ }
19951995+ // t.Config (atproto.BlobReference) (struct)
19961996+ case "config":
19971997+19981998+ {
19991999+20002000+ b, err := cr.ReadByte()
20012001+ if err != nil {
20022002+ return err
20032003+ }
20042004+ if b != cbg.CborNull[0] {
20052005+ if err := cr.UnreadByte(); err != nil {
20062006+ return err
20072007+ }
20082008+ t.Config = new(BlobReference)
20092009+ if err := t.Config.UnmarshalCBOR(cr); err != nil {
20102010+ return xerrors.Errorf("unmarshaling t.Config pointer: %w", err)
20112011+ }
20122012+ }
20132013+20142014+ }
20152015+ // t.Digest (string) (string)
20162016+ case "digest":
20172017+20182018+ {
20192019+ sval, err := cbg.ReadStringWithMax(cr, 8192)
20202020+ if err != nil {
20212021+ return err
20222022+ }
20232023+20242024+ t.Digest = string(sval)
20252025+ }
20262026+ // t.Layers ([]atproto.BlobReference) (slice)
20272027+ case "layers":
20282028+20292029+ maj, extra, err = cr.ReadHeader()
20302030+ if err != nil {
20312031+ return err
20322032+ }
20332033+20342034+ if extra > 8192 {
20352035+ return fmt.Errorf("t.Layers: array too large (%d)", extra)
20362036+ }
20372037+20382038+ if maj != cbg.MajArray {
20392039+ return fmt.Errorf("expected cbor array")
20402040+ }
20412041+20422042+ if extra > 0 {
20432043+ t.Layers = make([]BlobReference, extra)
20442044+ }
20452045+20462046+ for i := 0; i < int(extra); i++ {
20472047+ {
20482048+ var maj byte
20492049+ var extra uint64
20502050+ var err error
20512051+ _ = maj
20522052+ _ = extra
20532053+ _ = err
20542054+20552055+ {
20562056+20572057+ if err := t.Layers[i].UnmarshalCBOR(cr); err != nil {
20582058+ return xerrors.Errorf("unmarshaling t.Layers[i]: %w", err)
20592059+ }
20602060+20612061+ }
20622062+20632063+ }
20642064+ }
20652065+ // t.HoldDID (string) (string)
20662066+ case "holdDid":
20672067+20682068+ {
20692069+ sval, err := cbg.ReadStringWithMax(cr, 8192)
20702070+ if err != nil {
20712071+ return err
20722072+ }
20732073+20742074+ t.HoldDID = string(sval)
20752075+ }
20762076+ // t.Subject (atproto.BlobReference) (struct)
20772077+ case "subject":
20782078+20792079+ {
20802080+20812081+ b, err := cr.ReadByte()
20822082+ if err != nil {
20832083+ return err
20842084+ }
20852085+ if b != cbg.CborNull[0] {
20862086+ if err := cr.UnreadByte(); err != nil {
20872087+ return err
20882088+ }
20892089+ t.Subject = new(BlobReference)
20902090+ if err := t.Subject.UnmarshalCBOR(cr); err != nil {
20912091+ return xerrors.Errorf("unmarshaling t.Subject pointer: %w", err)
20922092+ }
20932093+ }
20942094+20952095+ }
20962096+ // t.OwnerDID (string) (string)
20972097+ case "ownerDid":
20982098+20992099+ {
21002100+ sval, err := cbg.ReadStringWithMax(cr, 8192)
21012101+ if err != nil {
21022102+ return err
21032103+ }
21042104+21052105+ t.OwnerDID = string(sval)
21062106+ }
21072107+ // t.CreatedAt (string) (string)
21082108+ case "createdAt":
21092109+21102110+ {
21112111+ sval, err := cbg.ReadStringWithMax(cr, 8192)
21122112+ if err != nil {
21132113+ return err
21142114+ }
21152115+21162116+ t.CreatedAt = string(sval)
21172117+ }
21182118+ // t.Manifests ([]atproto.ManifestReference) (slice)
21192119+ case "manifests":
21202120+21212121+ maj, extra, err = cr.ReadHeader()
21222122+ if err != nil {
21232123+ return err
21242124+ }
21252125+21262126+ if extra > 8192 {
21272127+ return fmt.Errorf("t.Manifests: array too large (%d)", extra)
21282128+ }
21292129+21302130+ if maj != cbg.MajArray {
21312131+ return fmt.Errorf("expected cbor array")
21322132+ }
21332133+21342134+ if extra > 0 {
21352135+ t.Manifests = make([]ManifestReference, extra)
21362136+ }
21372137+21382138+ for i := 0; i < int(extra); i++ {
21392139+ {
21402140+ var maj byte
21412141+ var extra uint64
21422142+ var err error
21432143+ _ = maj
21442144+ _ = extra
21452145+ _ = err
21462146+21472147+ {
21482148+21492149+ if err := t.Manifests[i].UnmarshalCBOR(cr); err != nil {
21502150+ return xerrors.Errorf("unmarshaling t.Manifests[i]: %w", err)
21512151+ }
21522152+21532153+ }
21542154+21552155+ }
21562156+ }
21572157+ // t.MediaType (string) (string)
21582158+ case "mediaType":
21592159+21602160+ {
21612161+ sval, err := cbg.ReadStringWithMax(cr, 8192)
21622162+ if err != nil {
21632163+ return err
21642164+ }
21652165+21662166+ t.MediaType = string(sval)
21672167+ }
21682168+ // t.ScannedAt (string) (string)
21692169+ case "scannedAt":
21702170+21712171+ {
21722172+ sval, err := cbg.ReadStringWithMax(cr, 8192)
21732173+ if err != nil {
21742174+ return err
21752175+ }
21762176+21772177+ t.ScannedAt = string(sval)
21782178+ }
21792179+ // t.Repository (string) (string)
21802180+ case "repository":
21812181+21822182+ {
21832183+ sval, err := cbg.ReadStringWithMax(cr, 8192)
21842184+ if err != nil {
21852185+ return err
21862186+ }
21872187+21882188+ t.Repository = string(sval)
21892189+ }
21902190+ // t.Annotations (map[string]string) (map)
21912191+ case "annotations":
21922192+21932193+ maj, extra, err = cr.ReadHeader()
21942194+ if err != nil {
21952195+ return err
21962196+ }
21972197+ if maj != cbg.MajMap {
21982198+ return fmt.Errorf("expected a map (major type 5)")
21992199+ }
22002200+ if extra > 4096 {
22012201+ return fmt.Errorf("t.Annotations: map too large")
22022202+ }
22032203+22042204+ t.Annotations = make(map[string]string, extra)
22052205+22062206+ for i, l := 0, int(extra); i < l; i++ {
22072207+22082208+ var k string
22092209+22102210+ {
22112211+ sval, err := cbg.ReadStringWithMax(cr, 8192)
22122212+ if err != nil {
22132213+ return err
22142214+ }
22152215+22162216+ k = string(sval)
22172217+ }
22182218+22192219+ var v string
22202220+22212221+ {
22222222+ sval, err := cbg.ReadStringWithMax(cr, 8192)
22232223+ if err != nil {
22242224+ return err
22252225+ }
22262226+22272227+ v = string(sval)
22282228+ }
22292229+22302230+ t.Annotations[k] = v
22312231+22322232+ }
22332233+ // t.ArtifactType (string) (string)
22342234+ case "artifactType":
22352235+22362236+ {
22372237+ sval, err := cbg.ReadStringWithMax(cr, 8192)
22382238+ if err != nil {
22392239+ return err
22402240+ }
22412241+22422242+ t.ArtifactType = string(sval)
22432243+ }
22442244+ // t.HoldEndpoint (string) (string)
22452245+ case "holdEndpoint":
22462246+22472247+ {
22482248+ sval, err := cbg.ReadStringWithMax(cr, 8192)
22492249+ if err != nil {
22502250+ return err
22512251+ }
22522252+22532253+ t.HoldEndpoint = string(sval)
22542254+ }
22552255+ // t.ManifestBlob (atproto.ATProtoBlobRef) (struct)
22562256+ case "manifestBlob":
22572257+22582258+ {
22592259+22602260+ b, err := cr.ReadByte()
22612261+ if err != nil {
22622262+ return err
22632263+ }
22642264+ if b != cbg.CborNull[0] {
22652265+ if err := cr.UnreadByte(); err != nil {
22662266+ return err
22672267+ }
22682268+ t.ManifestBlob = new(ATProtoBlobRef)
22692269+ if err := t.ManifestBlob.UnmarshalCBOR(cr); err != nil {
22702270+ return xerrors.Errorf("unmarshaling t.ManifestBlob pointer: %w", err)
22712271+ }
22722272+ }
22732273+22742274+ }
22752275+ // t.SchemaVersion (int64) (int64)
22762276+ case "schemaVersion":
22772277+ {
22782278+ maj, extra, err := cr.ReadHeader()
22792279+ if err != nil {
22802280+ return err
22812281+ }
22822282+ var extraI int64
22832283+ switch maj {
22842284+ case cbg.MajUnsignedInt:
22852285+ extraI = int64(extra)
22862286+ if extraI < 0 {
22872287+ return fmt.Errorf("int64 positive overflow")
22882288+ }
22892289+ case cbg.MajNegativeInt:
22902290+ extraI = int64(extra)
22912291+ if extraI < 0 {
22922292+ return fmt.Errorf("int64 negative overflow")
22932293+ }
22942294+ extraI = -1 - extraI
22952295+ default:
22962296+ return fmt.Errorf("wrong type for int64 field: %d", maj)
22972297+ }
22982298+22992299+ t.SchemaVersion = int64(extraI)
23002300+ }
23012301+ // t.ScannerVersion (string) (string)
23022302+ case "scannerVersion":
23032303+23042304+ {
23052305+ sval, err := cbg.ReadStringWithMax(cr, 8192)
23062306+ if err != nil {
23072307+ return err
23082308+ }
23092309+23102310+ t.ScannerVersion = string(sval)
23112311+ }
23122312+23132313+ default:
23142314+ // Field doesn't exist on this type, so ignore it
23152315+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
23162316+ return err
23172317+ }
23182318+ }
23192319+ }
23202320+23212321+ return nil
23222322+}
23232323+func (t *BlobReference) MarshalCBOR(w io.Writer) error {
23242324+ if t == nil {
23252325+ _, err := w.Write(cbg.CborNull)
23262326+ return err
23272327+ }
23282328+23292329+ cw := cbg.NewCborWriter(w)
23302330+ fieldCount := 5
23312331+23322332+ if t.URLs == nil {
23332333+ fieldCount--
23342334+ }
23352335+23362336+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
23372337+ return err
23382338+ }
23392339+23402340+ // t.Size (int64) (int64)
23412341+ if len("size") > 8192 {
23422342+ return xerrors.Errorf("Value in field \"size\" was too long")
23432343+ }
23442344+23452345+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("size"))); err != nil {
23462346+ return err
23472347+ }
23482348+ if _, err := cw.WriteString(string("size")); err != nil {
23492349+ return err
23502350+ }
23512351+23522352+ if t.Size >= 0 {
23532353+ if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Size)); err != nil {
23542354+ return err
23552355+ }
23562356+ } else {
23572357+ if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Size-1)); err != nil {
23582358+ return err
23592359+ }
23602360+ }
23612361+23622362+ // t.URLs ([]string) (slice)
23632363+ if t.URLs != nil {
23642364+23652365+ if len("urls") > 8192 {
23662366+ return xerrors.Errorf("Value in field \"urls\" was too long")
23672367+ }
23682368+23692369+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("urls"))); err != nil {
23702370+ return err
23712371+ }
23722372+ if _, err := cw.WriteString(string("urls")); err != nil {
23732373+ return err
23742374+ }
23752375+23762376+ if len(t.URLs) > 8192 {
23772377+ return xerrors.Errorf("Slice value in field t.URLs was too long")
23782378+ }
23792379+23802380+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.URLs))); err != nil {
23812381+ return err
23822382+ }
23832383+ for _, v := range t.URLs {
23842384+ if len(v) > 8192 {
23852385+ return xerrors.Errorf("Value in field v was too long")
23862386+ }
23872387+23882388+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
23892389+ return err
23902390+ }
23912391+ if _, err := cw.WriteString(string(v)); err != nil {
23922392+ return err
23932393+ }
23942394+23952395+ }
23962396+ }
23972397+23982398+ // t.Digest (string) (string)
23992399+ if len("digest") > 8192 {
24002400+ return xerrors.Errorf("Value in field \"digest\" was too long")
24012401+ }
24022402+24032403+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("digest"))); err != nil {
24042404+ return err
24052405+ }
24062406+ if _, err := cw.WriteString(string("digest")); err != nil {
24072407+ return err
24082408+ }
24092409+24102410+ if len(t.Digest) > 8192 {
24112411+ return xerrors.Errorf("Value in field t.Digest was too long")
24122412+ }
24132413+24142414+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Digest))); err != nil {
24152415+ return err
24162416+ }
24172417+ if _, err := cw.WriteString(string(t.Digest)); err != nil {
24182418+ return err
24192419+ }
24202420+24212421+ // t.MediaType (string) (string)
24222422+ if len("mediaType") > 8192 {
24232423+ return xerrors.Errorf("Value in field \"mediaType\" was too long")
24242424+ }
24252425+24262426+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mediaType"))); err != nil {
24272427+ return err
24282428+ }
24292429+ if _, err := cw.WriteString(string("mediaType")); err != nil {
24302430+ return err
24312431+ }
24322432+24332433+ if len(t.MediaType) > 8192 {
24342434+ return xerrors.Errorf("Value in field t.MediaType was too long")
24352435+ }
24362436+24372437+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.MediaType))); err != nil {
24382438+ return err
24392439+ }
24402440+ if _, err := cw.WriteString(string(t.MediaType)); err != nil {
24412441+ return err
24422442+ }
24432443+24442444+ // t.Annotations (map[string]string) (map)
24452445+ if len("annotations") > 8192 {
24462446+ return xerrors.Errorf("Value in field \"annotations\" was too long")
24472447+ }
24482448+24492449+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("annotations"))); err != nil {
24502450+ return err
24512451+ }
24522452+ if _, err := cw.WriteString(string("annotations")); err != nil {
24532453+ return err
24542454+ }
24552455+24562456+ {
24572457+ if len(t.Annotations) > 4096 {
24582458+ return xerrors.Errorf("cannot marshal t.Annotations map too large")
24592459+ }
24602460+24612461+ if err := cw.WriteMajorTypeHeader(cbg.MajMap, uint64(len(t.Annotations))); err != nil {
24622462+ return err
24632463+ }
24642464+24652465+ keys := make([]string, 0, len(t.Annotations))
24662466+ for k := range t.Annotations {
24672467+ keys = append(keys, k)
24682468+ }
24692469+ sort.Strings(keys)
24702470+ for _, k := range keys {
24712471+ v := t.Annotations[k]
24722472+24732473+ if len(k) > 8192 {
24742474+ return xerrors.Errorf("Value in field k was too long")
24752475+ }
24762476+24772477+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(k))); err != nil {
24782478+ return err
24792479+ }
24802480+ if _, err := cw.WriteString(string(k)); err != nil {
24812481+ return err
24822482+ }
24832483+24842484+ if len(v) > 8192 {
24852485+ return xerrors.Errorf("Value in field v was too long")
24862486+ }
24872487+24882488+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
24892489+ return err
24902490+ }
24912491+ if _, err := cw.WriteString(string(v)); err != nil {
24922492+ return err
24932493+ }
24942494+24952495+ }
24962496+ }
24972497+ return nil
24982498+}
24992499+25002500+func (t *BlobReference) UnmarshalCBOR(r io.Reader) (err error) {
25012501+ *t = BlobReference{}
25022502+25032503+ cr := cbg.NewCborReader(r)
25042504+25052505+ maj, extra, err := cr.ReadHeader()
25062506+ if err != nil {
25072507+ return err
25082508+ }
25092509+ defer func() {
25102510+ if err == io.EOF {
25112511+ err = io.ErrUnexpectedEOF
25122512+ }
25132513+ }()
25142514+25152515+ if maj != cbg.MajMap {
25162516+ return fmt.Errorf("cbor input should be of type map")
25172517+ }
25182518+25192519+ if extra > cbg.MaxLength {
25202520+ return fmt.Errorf("BlobReference: map struct too large (%d)", extra)
25212521+ }
25222522+25232523+ n := extra
25242524+25252525+ nameBuf := make([]byte, 11)
25262526+ for i := uint64(0); i < n; i++ {
25272527+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
25282528+ if err != nil {
25292529+ return err
25302530+ }
25312531+25322532+ if !ok {
25332533+ // Field doesn't exist on this type, so ignore it
25342534+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
25352535+ return err
25362536+ }
25372537+ continue
25382538+ }
25392539+25402540+ switch string(nameBuf[:nameLen]) {
25412541+ // t.Size (int64) (int64)
25422542+ case "size":
25432543+ {
25442544+ maj, extra, err := cr.ReadHeader()
25452545+ if err != nil {
25462546+ return err
25472547+ }
25482548+ var extraI int64
25492549+ switch maj {
25502550+ case cbg.MajUnsignedInt:
25512551+ extraI = int64(extra)
25522552+ if extraI < 0 {
25532553+ return fmt.Errorf("int64 positive overflow")
25542554+ }
25552555+ case cbg.MajNegativeInt:
25562556+ extraI = int64(extra)
25572557+ if extraI < 0 {
25582558+ return fmt.Errorf("int64 negative overflow")
25592559+ }
25602560+ extraI = -1 - extraI
25612561+ default:
25622562+ return fmt.Errorf("wrong type for int64 field: %d", maj)
25632563+ }
25642564+25652565+ t.Size = int64(extraI)
25662566+ }
25672567+ // t.URLs ([]string) (slice)
25682568+ case "urls":
25692569+25702570+ maj, extra, err = cr.ReadHeader()
25712571+ if err != nil {
25722572+ return err
25732573+ }
25742574+25752575+ if extra > 8192 {
25762576+ return fmt.Errorf("t.URLs: array too large (%d)", extra)
25772577+ }
25782578+25792579+ if maj != cbg.MajArray {
25802580+ return fmt.Errorf("expected cbor array")
25812581+ }
25822582+25832583+ if extra > 0 {
25842584+ t.URLs = make([]string, extra)
25852585+ }
25862586+25872587+ for i := 0; i < int(extra); i++ {
25882588+ {
25892589+ var maj byte
25902590+ var extra uint64
25912591+ var err error
25922592+ _ = maj
25932593+ _ = extra
25942594+ _ = err
25952595+25962596+ {
25972597+ sval, err := cbg.ReadStringWithMax(cr, 8192)
25982598+ if err != nil {
25992599+ return err
26002600+ }
26012601+26022602+ t.URLs[i] = string(sval)
26032603+ }
26042604+26052605+ }
26062606+ }
26072607+ // t.Digest (string) (string)
26082608+ case "digest":
26092609+26102610+ {
26112611+ sval, err := cbg.ReadStringWithMax(cr, 8192)
26122612+ if err != nil {
26132613+ return err
26142614+ }
26152615+26162616+ t.Digest = string(sval)
26172617+ }
26182618+ // t.MediaType (string) (string)
26192619+ case "mediaType":
26202620+26212621+ {
26222622+ sval, err := cbg.ReadStringWithMax(cr, 8192)
26232623+ if err != nil {
26242624+ return err
26252625+ }
26262626+26272627+ t.MediaType = string(sval)
26282628+ }
26292629+ // t.Annotations (map[string]string) (map)
26302630+ case "annotations":
26312631+26322632+ maj, extra, err = cr.ReadHeader()
26332633+ if err != nil {
26342634+ return err
26352635+ }
26362636+ if maj != cbg.MajMap {
26372637+ return fmt.Errorf("expected a map (major type 5)")
26382638+ }
26392639+ if extra > 4096 {
26402640+ return fmt.Errorf("t.Annotations: map too large")
26412641+ }
26422642+26432643+ t.Annotations = make(map[string]string, extra)
26442644+26452645+ for i, l := 0, int(extra); i < l; i++ {
26462646+26472647+ var k string
26482648+26492649+ {
26502650+ sval, err := cbg.ReadStringWithMax(cr, 8192)
26512651+ if err != nil {
26522652+ return err
26532653+ }
26542654+26552655+ k = string(sval)
26562656+ }
26572657+26582658+ var v string
26592659+26602660+ {
26612661+ sval, err := cbg.ReadStringWithMax(cr, 8192)
26622662+ if err != nil {
26632663+ return err
26642664+ }
26652665+26662666+ v = string(sval)
26672667+ }
26682668+26692669+ t.Annotations[k] = v
26702670+26712671+ }
26722672+26732673+ default:
26742674+ // Field doesn't exist on this type, so ignore it
26752675+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
26762676+ return err
26772677+ }
26782678+ }
26792679+ }
26802680+26812681+ return nil
26822682+}
26832683+func (t *ManifestReference) MarshalCBOR(w io.Writer) error {
26842684+ if t == nil {
26852685+ _, err := w.Write(cbg.CborNull)
26862686+ return err
26872687+ }
26882688+26892689+ cw := cbg.NewCborWriter(w)
26902690+ fieldCount := 5
26912691+26922692+ if t.Platform == nil {
26932693+ fieldCount--
26942694+ }
26952695+26962696+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
26972697+ return err
26982698+ }
26992699+27002700+ // t.Size (int64) (int64)
27012701+ if len("size") > 8192 {
27022702+ return xerrors.Errorf("Value in field \"size\" was too long")
27032703+ }
27042704+27052705+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("size"))); err != nil {
27062706+ return err
27072707+ }
27082708+ if _, err := cw.WriteString(string("size")); err != nil {
27092709+ return err
27102710+ }
27112711+27122712+ if t.Size >= 0 {
27132713+ if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Size)); err != nil {
27142714+ return err
27152715+ }
27162716+ } else {
27172717+ if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Size-1)); err != nil {
27182718+ return err
27192719+ }
27202720+ }
27212721+27222722+ // t.Digest (string) (string)
27232723+ if len("digest") > 8192 {
27242724+ return xerrors.Errorf("Value in field \"digest\" was too long")
27252725+ }
27262726+27272727+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("digest"))); err != nil {
27282728+ return err
27292729+ }
27302730+ if _, err := cw.WriteString(string("digest")); err != nil {
27312731+ return err
27322732+ }
27332733+27342734+ if len(t.Digest) > 8192 {
27352735+ return xerrors.Errorf("Value in field t.Digest was too long")
27362736+ }
27372737+27382738+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Digest))); err != nil {
27392739+ return err
27402740+ }
27412741+ if _, err := cw.WriteString(string(t.Digest)); err != nil {
27422742+ return err
27432743+ }
27442744+27452745+ // t.Platform (atproto.Platform) (struct)
27462746+ if t.Platform != nil {
27472747+27482748+ if len("platform") > 8192 {
27492749+ return xerrors.Errorf("Value in field \"platform\" was too long")
27502750+ }
27512751+27522752+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("platform"))); err != nil {
27532753+ return err
27542754+ }
27552755+ if _, err := cw.WriteString(string("platform")); err != nil {
27562756+ return err
27572757+ }
27582758+27592759+ if err := t.Platform.MarshalCBOR(cw); err != nil {
27602760+ return err
27612761+ }
27622762+ }
27632763+27642764+ // t.MediaType (string) (string)
27652765+ if len("mediaType") > 8192 {
27662766+ return xerrors.Errorf("Value in field \"mediaType\" was too long")
27672767+ }
27682768+27692769+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mediaType"))); err != nil {
27702770+ return err
27712771+ }
27722772+ if _, err := cw.WriteString(string("mediaType")); err != nil {
27732773+ return err
27742774+ }
27752775+27762776+ if len(t.MediaType) > 8192 {
27772777+ return xerrors.Errorf("Value in field t.MediaType was too long")
27782778+ }
27792779+27802780+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.MediaType))); err != nil {
27812781+ return err
27822782+ }
27832783+ if _, err := cw.WriteString(string(t.MediaType)); err != nil {
27842784+ return err
27852785+ }
27862786+27872787+ // t.Annotations (map[string]string) (map)
27882788+ if len("annotations") > 8192 {
27892789+ return xerrors.Errorf("Value in field \"annotations\" was too long")
27902790+ }
27912791+27922792+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("annotations"))); err != nil {
27932793+ return err
27942794+ }
27952795+ if _, err := cw.WriteString(string("annotations")); err != nil {
27962796+ return err
27972797+ }
27982798+27992799+ {
28002800+ if len(t.Annotations) > 4096 {
28012801+ return xerrors.Errorf("cannot marshal t.Annotations map too large")
28022802+ }
28032803+28042804+ if err := cw.WriteMajorTypeHeader(cbg.MajMap, uint64(len(t.Annotations))); err != nil {
28052805+ return err
28062806+ }
28072807+28082808+ keys := make([]string, 0, len(t.Annotations))
28092809+ for k := range t.Annotations {
28102810+ keys = append(keys, k)
28112811+ }
28122812+ sort.Strings(keys)
28132813+ for _, k := range keys {
28142814+ v := t.Annotations[k]
28152815+28162816+ if len(k) > 8192 {
28172817+ return xerrors.Errorf("Value in field k was too long")
28182818+ }
28192819+28202820+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(k))); err != nil {
28212821+ return err
28222822+ }
28232823+ if _, err := cw.WriteString(string(k)); err != nil {
28242824+ return err
28252825+ }
28262826+28272827+ if len(v) > 8192 {
28282828+ return xerrors.Errorf("Value in field v was too long")
28292829+ }
28302830+28312831+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
28322832+ return err
28332833+ }
28342834+ if _, err := cw.WriteString(string(v)); err != nil {
28352835+ return err
28362836+ }
28372837+28382838+ }
28392839+ }
28402840+ return nil
28412841+}
28422842+28432843+func (t *ManifestReference) UnmarshalCBOR(r io.Reader) (err error) {
28442844+ *t = ManifestReference{}
28452845+28462846+ cr := cbg.NewCborReader(r)
28472847+28482848+ maj, extra, err := cr.ReadHeader()
28492849+ if err != nil {
28502850+ return err
28512851+ }
28522852+ defer func() {
28532853+ if err == io.EOF {
28542854+ err = io.ErrUnexpectedEOF
28552855+ }
28562856+ }()
28572857+28582858+ if maj != cbg.MajMap {
28592859+ return fmt.Errorf("cbor input should be of type map")
28602860+ }
28612861+28622862+ if extra > cbg.MaxLength {
28632863+ return fmt.Errorf("ManifestReference: map struct too large (%d)", extra)
28642864+ }
28652865+28662866+ n := extra
28672867+28682868+ nameBuf := make([]byte, 11)
28692869+ for i := uint64(0); i < n; i++ {
28702870+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
28712871+ if err != nil {
28722872+ return err
28732873+ }
28742874+28752875+ if !ok {
28762876+ // Field doesn't exist on this type, so ignore it
28772877+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
28782878+ return err
28792879+ }
28802880+ continue
28812881+ }
28822882+28832883+ switch string(nameBuf[:nameLen]) {
28842884+ // t.Size (int64) (int64)
28852885+ case "size":
28862886+ {
28872887+ maj, extra, err := cr.ReadHeader()
28882888+ if err != nil {
28892889+ return err
28902890+ }
28912891+ var extraI int64
28922892+ switch maj {
28932893+ case cbg.MajUnsignedInt:
28942894+ extraI = int64(extra)
28952895+ if extraI < 0 {
28962896+ return fmt.Errorf("int64 positive overflow")
28972897+ }
28982898+ case cbg.MajNegativeInt:
28992899+ extraI = int64(extra)
29002900+ if extraI < 0 {
29012901+ return fmt.Errorf("int64 negative overflow")
29022902+ }
29032903+ extraI = -1 - extraI
29042904+ default:
29052905+ return fmt.Errorf("wrong type for int64 field: %d", maj)
29062906+ }
29072907+29082908+ t.Size = int64(extraI)
29092909+ }
29102910+ // t.Digest (string) (string)
29112911+ case "digest":
29122912+29132913+ {
29142914+ sval, err := cbg.ReadStringWithMax(cr, 8192)
29152915+ if err != nil {
29162916+ return err
29172917+ }
29182918+29192919+ t.Digest = string(sval)
29202920+ }
29212921+ // t.Platform (atproto.Platform) (struct)
29222922+ case "platform":
29232923+29242924+ {
29252925+29262926+ b, err := cr.ReadByte()
29272927+ if err != nil {
29282928+ return err
29292929+ }
29302930+ if b != cbg.CborNull[0] {
29312931+ if err := cr.UnreadByte(); err != nil {
29322932+ return err
29332933+ }
29342934+ t.Platform = new(Platform)
29352935+ if err := t.Platform.UnmarshalCBOR(cr); err != nil {
29362936+ return xerrors.Errorf("unmarshaling t.Platform pointer: %w", err)
29372937+ }
29382938+ }
29392939+29402940+ }
29412941+ // t.MediaType (string) (string)
29422942+ case "mediaType":
29432943+29442944+ {
29452945+ sval, err := cbg.ReadStringWithMax(cr, 8192)
29462946+ if err != nil {
29472947+ return err
29482948+ }
29492949+29502950+ t.MediaType = string(sval)
29512951+ }
29522952+ // t.Annotations (map[string]string) (map)
29532953+ case "annotations":
29542954+29552955+ maj, extra, err = cr.ReadHeader()
29562956+ if err != nil {
29572957+ return err
29582958+ }
29592959+ if maj != cbg.MajMap {
29602960+ return fmt.Errorf("expected a map (major type 5)")
29612961+ }
29622962+ if extra > 4096 {
29632963+ return fmt.Errorf("t.Annotations: map too large")
29642964+ }
29652965+29662966+ t.Annotations = make(map[string]string, extra)
29672967+29682968+ for i, l := 0, int(extra); i < l; i++ {
29692969+29702970+ var k string
29712971+29722972+ {
29732973+ sval, err := cbg.ReadStringWithMax(cr, 8192)
29742974+ if err != nil {
29752975+ return err
29762976+ }
29772977+29782978+ k = string(sval)
29792979+ }
29802980+29812981+ var v string
29822982+29832983+ {
29842984+ sval, err := cbg.ReadStringWithMax(cr, 8192)
29852985+ if err != nil {
29862986+ return err
29872987+ }
29882988+29892989+ v = string(sval)
29902990+ }
29912991+29922992+ t.Annotations[k] = v
29932993+29942994+ }
29952995+29962996+ default:
29972997+ // Field doesn't exist on this type, so ignore it
29982998+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
29992999+ return err
30003000+ }
30013001+ }
30023002+ }
30033003+30043004+ return nil
30053005+}
30063006+func (t *Platform) MarshalCBOR(w io.Writer) error {
30073007+ if t == nil {
30083008+ _, err := w.Write(cbg.CborNull)
30093009+ return err
30103010+ }
30113011+30123012+ cw := cbg.NewCborWriter(w)
30133013+ fieldCount := 5
30143014+30153015+ if t.OSVersion == "" {
30163016+ fieldCount--
30173017+ }
30183018+30193019+ if t.OSFeatures == nil {
30203020+ fieldCount--
30213021+ }
30223022+30233023+ if t.Variant == "" {
30243024+ fieldCount--
30253025+ }
30263026+30273027+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
30283028+ return err
30293029+ }
30303030+30313031+ // t.OS (string) (string)
30323032+ if len("os") > 8192 {
30333033+ return xerrors.Errorf("Value in field \"os\" was too long")
30343034+ }
30353035+30363036+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("os"))); err != nil {
30373037+ return err
30383038+ }
30393039+ if _, err := cw.WriteString(string("os")); err != nil {
30403040+ return err
30413041+ }
30423042+30433043+ if len(t.OS) > 8192 {
30443044+ return xerrors.Errorf("Value in field t.OS was too long")
30453045+ }
30463046+30473047+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.OS))); err != nil {
30483048+ return err
30493049+ }
30503050+ if _, err := cw.WriteString(string(t.OS)); err != nil {
30513051+ return err
30523052+ }
30533053+30543054+ // t.Variant (string) (string)
30553055+ if t.Variant != "" {
30563056+30573057+ if len("variant") > 8192 {
30583058+ return xerrors.Errorf("Value in field \"variant\" was too long")
30593059+ }
30603060+30613061+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("variant"))); err != nil {
30623062+ return err
30633063+ }
30643064+ if _, err := cw.WriteString(string("variant")); err != nil {
30653065+ return err
30663066+ }
30673067+30683068+ if len(t.Variant) > 8192 {
30693069+ return xerrors.Errorf("Value in field t.Variant was too long")
30703070+ }
30713071+30723072+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Variant))); err != nil {
30733073+ return err
30743074+ }
30753075+ if _, err := cw.WriteString(string(t.Variant)); err != nil {
30763076+ return err
30773077+ }
30783078+ }
30793079+30803080+ // t.OSVersion (string) (string)
30813081+ if t.OSVersion != "" {
30823082+30833083+ if len("os.version") > 8192 {
30843084+ return xerrors.Errorf("Value in field \"os.version\" was too long")
30853085+ }
30863086+30873087+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("os.version"))); err != nil {
30883088+ return err
30893089+ }
30903090+ if _, err := cw.WriteString(string("os.version")); err != nil {
30913091+ return err
30923092+ }
30933093+30943094+ if len(t.OSVersion) > 8192 {
30953095+ return xerrors.Errorf("Value in field t.OSVersion was too long")
30963096+ }
30973097+30983098+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.OSVersion))); err != nil {
30993099+ return err
31003100+ }
31013101+ if _, err := cw.WriteString(string(t.OSVersion)); err != nil {
31023102+ return err
31033103+ }
31043104+ }
31053105+31063106+ // t.OSFeatures ([]string) (slice)
31073107+ if t.OSFeatures != nil {
31083108+31093109+ if len("os.features") > 8192 {
31103110+ return xerrors.Errorf("Value in field \"os.features\" was too long")
31113111+ }
31123112+31133113+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("os.features"))); err != nil {
31143114+ return err
31153115+ }
31163116+ if _, err := cw.WriteString(string("os.features")); err != nil {
31173117+ return err
31183118+ }
31193119+31203120+ if len(t.OSFeatures) > 8192 {
31213121+ return xerrors.Errorf("Slice value in field t.OSFeatures was too long")
31223122+ }
31233123+31243124+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.OSFeatures))); err != nil {
31253125+ return err
31263126+ }
31273127+ for _, v := range t.OSFeatures {
31283128+ if len(v) > 8192 {
31293129+ return xerrors.Errorf("Value in field v was too long")
31303130+ }
31313131+31323132+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
31333133+ return err
31343134+ }
31353135+ if _, err := cw.WriteString(string(v)); err != nil {
31363136+ return err
31373137+ }
31383138+31393139+ }
31403140+ }
31413141+31423142+ // t.Architecture (string) (string)
31433143+ if len("architecture") > 8192 {
31443144+ return xerrors.Errorf("Value in field \"architecture\" was too long")
31453145+ }
31463146+31473147+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("architecture"))); err != nil {
31483148+ return err
31493149+ }
31503150+ if _, err := cw.WriteString(string("architecture")); err != nil {
31513151+ return err
31523152+ }
31533153+31543154+ if len(t.Architecture) > 8192 {
31553155+ return xerrors.Errorf("Value in field t.Architecture was too long")
31563156+ }
31573157+31583158+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Architecture))); err != nil {
31593159+ return err
31603160+ }
31613161+ if _, err := cw.WriteString(string(t.Architecture)); err != nil {
31623162+ return err
31633163+ }
31643164+ return nil
31653165+}
31663166+31673167+func (t *Platform) UnmarshalCBOR(r io.Reader) (err error) {
31683168+ *t = Platform{}
31693169+31703170+ cr := cbg.NewCborReader(r)
31713171+31723172+ maj, extra, err := cr.ReadHeader()
31733173+ if err != nil {
31743174+ return err
31753175+ }
31763176+ defer func() {
31773177+ if err == io.EOF {
31783178+ err = io.ErrUnexpectedEOF
31793179+ }
31803180+ }()
31813181+31823182+ if maj != cbg.MajMap {
31833183+ return fmt.Errorf("cbor input should be of type map")
31843184+ }
31853185+31863186+ if extra > cbg.MaxLength {
31873187+ return fmt.Errorf("Platform: map struct too large (%d)", extra)
31883188+ }
31893189+31903190+ n := extra
31913191+31923192+ nameBuf := make([]byte, 12)
31933193+ for i := uint64(0); i < n; i++ {
31943194+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
31953195+ if err != nil {
31963196+ return err
31973197+ }
31983198+31993199+ if !ok {
32003200+ // Field doesn't exist on this type, so ignore it
32013201+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
32023202+ return err
32033203+ }
32043204+ continue
32053205+ }
32063206+32073207+ switch string(nameBuf[:nameLen]) {
32083208+ // t.OS (string) (string)
32093209+ case "os":
32103210+32113211+ {
32123212+ sval, err := cbg.ReadStringWithMax(cr, 8192)
32133213+ if err != nil {
32143214+ return err
32153215+ }
32163216+32173217+ t.OS = string(sval)
32183218+ }
32193219+ // t.Variant (string) (string)
32203220+ case "variant":
32213221+32223222+ {
32233223+ sval, err := cbg.ReadStringWithMax(cr, 8192)
32243224+ if err != nil {
32253225+ return err
32263226+ }
32273227+32283228+ t.Variant = string(sval)
32293229+ }
32303230+ // t.OSVersion (string) (string)
32313231+ case "os.version":
32323232+32333233+ {
32343234+ sval, err := cbg.ReadStringWithMax(cr, 8192)
32353235+ if err != nil {
32363236+ return err
32373237+ }
32383238+32393239+ t.OSVersion = string(sval)
32403240+ }
32413241+ // t.OSFeatures ([]string) (slice)
32423242+ case "os.features":
32433243+32443244+ maj, extra, err = cr.ReadHeader()
32453245+ if err != nil {
32463246+ return err
32473247+ }
32483248+32493249+ if extra > 8192 {
32503250+ return fmt.Errorf("t.OSFeatures: array too large (%d)", extra)
32513251+ }
32523252+32533253+ if maj != cbg.MajArray {
32543254+ return fmt.Errorf("expected cbor array")
32553255+ }
32563256+32573257+ if extra > 0 {
32583258+ t.OSFeatures = make([]string, extra)
32593259+ }
32603260+32613261+ for i := 0; i < int(extra); i++ {
32623262+ {
32633263+ var maj byte
32643264+ var extra uint64
32653265+ var err error
32663266+ _ = maj
32673267+ _ = extra
32683268+ _ = err
32693269+32703270+ {
32713271+ sval, err := cbg.ReadStringWithMax(cr, 8192)
32723272+ if err != nil {
32733273+ return err
32743274+ }
32753275+32763276+ t.OSFeatures[i] = string(sval)
32773277+ }
32783278+32793279+ }
32803280+ }
32813281+ // t.Architecture (string) (string)
32823282+ case "architecture":
32833283+32843284+ {
32853285+ sval, err := cbg.ReadStringWithMax(cr, 8192)
32863286+ if err != nil {
32873287+ return err
32883288+ }
32893289+32903290+ t.Architecture = string(sval)
32913291+ }
32923292+32933293+ default:
32943294+ // Field doesn't exist on this type, so ignore it
32953295+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
32963296+ return err
32973297+ }
32983298+ }
32993299+ }
33003300+33013301+ return nil
33023302+}
33033303+func (t *ATProtoBlobRef) MarshalCBOR(w io.Writer) error {
33043304+ if t == nil {
33053305+ _, err := w.Write(cbg.CborNull)
33063306+ return err
33073307+ }
33083308+33093309+ cw := cbg.NewCborWriter(w)
33103310+33113311+ if _, err := cw.Write([]byte{164}); err != nil {
33123312+ return err
33133313+ }
33143314+33153315+ // t.Ref (atproto.Link) (struct)
33163316+ if len("ref") > 8192 {
33173317+ return xerrors.Errorf("Value in field \"ref\" was too long")
33183318+ }
33193319+33203320+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("ref"))); err != nil {
33213321+ return err
33223322+ }
33233323+ if _, err := cw.WriteString(string("ref")); err != nil {
33243324+ return err
33253325+ }
33263326+33273327+ if err := t.Ref.MarshalCBOR(cw); err != nil {
33283328+ return err
33293329+ }
33303330+33313331+ // t.Size (int64) (int64)
33323332+ if len("size") > 8192 {
33333333+ return xerrors.Errorf("Value in field \"size\" was too long")
33343334+ }
33353335+33363336+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("size"))); err != nil {
33373337+ return err
33383338+ }
33393339+ if _, err := cw.WriteString(string("size")); err != nil {
33403340+ return err
33413341+ }
33423342+33433343+ if t.Size >= 0 {
33443344+ if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Size)); err != nil {
33453345+ return err
33463346+ }
33473347+ } else {
33483348+ if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Size-1)); err != nil {
33493349+ return err
33503350+ }
33513351+ }
33523352+33533353+ // t.Type (string) (string)
33543354+ if len("$type") > 8192 {
33553355+ return xerrors.Errorf("Value in field \"$type\" was too long")
33563356+ }
33573357+33583358+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
33593359+ return err
33603360+ }
33613361+ if _, err := cw.WriteString(string("$type")); err != nil {
33623362+ return err
33633363+ }
33643364+33653365+ if len(t.Type) > 8192 {
33663366+ return xerrors.Errorf("Value in field t.Type was too long")
33673367+ }
33683368+33693369+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Type))); err != nil {
33703370+ return err
33713371+ }
33723372+ if _, err := cw.WriteString(string(t.Type)); err != nil {
33733373+ return err
33743374+ }
33753375+33763376+ // t.MimeType (string) (string)
33773377+ if len("mimeType") > 8192 {
33783378+ return xerrors.Errorf("Value in field \"mimeType\" was too long")
33793379+ }
33803380+33813381+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mimeType"))); err != nil {
33823382+ return err
33833383+ }
33843384+ if _, err := cw.WriteString(string("mimeType")); err != nil {
33853385+ return err
33863386+ }
33873387+33883388+ if len(t.MimeType) > 8192 {
33893389+ return xerrors.Errorf("Value in field t.MimeType was too long")
33903390+ }
33913391+33923392+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.MimeType))); err != nil {
33933393+ return err
33943394+ }
33953395+ if _, err := cw.WriteString(string(t.MimeType)); err != nil {
33963396+ return err
33973397+ }
33983398+ return nil
33993399+}
34003400+34013401+func (t *ATProtoBlobRef) UnmarshalCBOR(r io.Reader) (err error) {
34023402+ *t = ATProtoBlobRef{}
34033403+34043404+ cr := cbg.NewCborReader(r)
34053405+34063406+ maj, extra, err := cr.ReadHeader()
34073407+ if err != nil {
34083408+ return err
34093409+ }
34103410+ defer func() {
34113411+ if err == io.EOF {
34123412+ err = io.ErrUnexpectedEOF
34133413+ }
34143414+ }()
34153415+34163416+ if maj != cbg.MajMap {
34173417+ return fmt.Errorf("cbor input should be of type map")
34183418+ }
34193419+34203420+ if extra > cbg.MaxLength {
34213421+ return fmt.Errorf("ATProtoBlobRef: map struct too large (%d)", extra)
34223422+ }
34233423+34243424+ n := extra
34253425+34263426+ nameBuf := make([]byte, 8)
34273427+ for i := uint64(0); i < n; i++ {
34283428+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
34293429+ if err != nil {
34303430+ return err
34313431+ }
34323432+34333433+ if !ok {
34343434+ // Field doesn't exist on this type, so ignore it
34353435+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
34363436+ return err
34373437+ }
34383438+ continue
34393439+ }
34403440+34413441+ switch string(nameBuf[:nameLen]) {
34423442+ // t.Ref (atproto.Link) (struct)
34433443+ case "ref":
34443444+34453445+ {
34463446+34473447+ if err := t.Ref.UnmarshalCBOR(cr); err != nil {
34483448+ return xerrors.Errorf("unmarshaling t.Ref: %w", err)
34493449+ }
34503450+34513451+ }
34523452+ // t.Size (int64) (int64)
34533453+ case "size":
34543454+ {
34553455+ maj, extra, err := cr.ReadHeader()
34563456+ if err != nil {
34573457+ return err
34583458+ }
34593459+ var extraI int64
34603460+ switch maj {
34613461+ case cbg.MajUnsignedInt:
34623462+ extraI = int64(extra)
34633463+ if extraI < 0 {
34643464+ return fmt.Errorf("int64 positive overflow")
34653465+ }
34663466+ case cbg.MajNegativeInt:
34673467+ extraI = int64(extra)
34683468+ if extraI < 0 {
34693469+ return fmt.Errorf("int64 negative overflow")
34703470+ }
34713471+ extraI = -1 - extraI
34723472+ default:
34733473+ return fmt.Errorf("wrong type for int64 field: %d", maj)
34743474+ }
34753475+34763476+ t.Size = int64(extraI)
34773477+ }
34783478+ // t.Type (string) (string)
34793479+ case "$type":
34803480+34813481+ {
34823482+ sval, err := cbg.ReadStringWithMax(cr, 8192)
34833483+ if err != nil {
34843484+ return err
34853485+ }
34863486+34873487+ t.Type = string(sval)
34883488+ }
34893489+ // t.MimeType (string) (string)
34903490+ case "mimeType":
34913491+34923492+ {
34933493+ sval, err := cbg.ReadStringWithMax(cr, 8192)
34943494+ if err != nil {
34953495+ return err
34963496+ }
34973497+34983498+ t.MimeType = string(sval)
34993499+ }
35003500+35013501+ default:
35023502+ // Field doesn't exist on this type, so ignore it
35033503+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
35043504+ return err
35053505+ }
35063506+ }
35073507+ }
35083508+35093509+ return nil
35103510+}
35113511+func (t *Link) MarshalCBOR(w io.Writer) error {
35123512+ if t == nil {
35133513+ _, err := w.Write(cbg.CborNull)
35143514+ return err
35153515+ }
35163516+35173517+ cw := cbg.NewCborWriter(w)
35183518+35193519+ if _, err := cw.Write([]byte{161}); err != nil {
35203520+ return err
35213521+ }
35223522+35233523+ // t.Link (string) (string)
35243524+ if len("$link") > 8192 {
35253525+ return xerrors.Errorf("Value in field \"$link\" was too long")
35263526+ }
35273527+35283528+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$link"))); err != nil {
35293529+ return err
35303530+ }
35313531+ if _, err := cw.WriteString(string("$link")); err != nil {
35323532+ return err
35333533+ }
35343534+35353535+ if len(t.Link) > 8192 {
35363536+ return xerrors.Errorf("Value in field t.Link was too long")
35373537+ }
35383538+35393539+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Link))); err != nil {
35403540+ return err
35413541+ }
35423542+ if _, err := cw.WriteString(string(t.Link)); err != nil {
35433543+ return err
35443544+ }
35453545+ return nil
35463546+}
35473547+35483548+func (t *Link) UnmarshalCBOR(r io.Reader) (err error) {
35493549+ *t = Link{}
35503550+35513551+ cr := cbg.NewCborReader(r)
35523552+35533553+ maj, extra, err := cr.ReadHeader()
35543554+ if err != nil {
35553555+ return err
35563556+ }
35573557+ defer func() {
35583558+ if err == io.EOF {
35593559+ err = io.ErrUnexpectedEOF
35603560+ }
35613561+ }()
35623562+35633563+ if maj != cbg.MajMap {
35643564+ return fmt.Errorf("cbor input should be of type map")
35653565+ }
35663566+35673567+ if extra > cbg.MaxLength {
35683568+ return fmt.Errorf("Link: map struct too large (%d)", extra)
35693569+ }
35703570+35713571+ n := extra
35723572+35733573+ nameBuf := make([]byte, 5)
35743574+ for i := uint64(0); i < n; i++ {
35753575+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
35763576+ if err != nil {
35773577+ return err
35783578+ }
35793579+35803580+ if !ok {
35813581+ // Field doesn't exist on this type, so ignore it
35823582+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
35833583+ return err
35843584+ }
35853585+ continue
35863586+ }
35873587+35883588+ switch string(nameBuf[:nameLen]) {
35893589+ // t.Link (string) (string)
35903590+ case "$link":
35913591+35923592+ {
35933593+ sval, err := cbg.ReadStringWithMax(cr, 8192)
35943594+ if err != nil {
35953595+ return err
35963596+ }
35973597+35983598+ t.Link = string(sval)
35993599+ }
36003600+36013601+ default:
36023602+ // Field doesn't exist on this type, so ignore it
36033603+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
36043604+ return err
36053605+ }
36063606+ }
36073607+ }
36083608+36093609+ return nil
36103610+}
+5-5
pkg/atproto/client.go
···266266// ATProtoBlobRef represents a reference to a blob in ATProto's native blob storage
267267// This is different from OCIBlobDescriptor which describes OCI image layers
268268type ATProtoBlobRef struct {
269269- Type string `json:"$type"`
270270- Ref Link `json:"ref"`
271271- MimeType string `json:"mimeType"`
272272- Size int64 `json:"size"`
269269+ Type string `json:"$type" cborgen:"$type"`
270270+ Ref Link `json:"ref" cborgen:"ref"`
271271+ MimeType string `json:"mimeType" cborgen:"mimeType"`
272272+ Size int64 `json:"size" cborgen:"size"`
273273}
274274275275// Link represents an IPFS link to blob content
276276type Link struct {
277277- Link string `json:"$link"`
277277+ Link string `json:"$link" cborgen:"$link"`
278278}
279279280280// UploadBlob uploads binary data to the PDS and returns a blob reference
···267267 return result, nil
268268}
269269270270+// CreateManifestRecord creates a manifest record with a specific rkey
271271+// Used by the scanner to store ORAS artifacts (SBOMs, vulnerability reports)
272272+func (p *HoldPDS) CreateManifestRecord(ctx context.Context, record *atproto.ManifestRecord, rkey string) (string, cid.Cid, error) {
273273+ return p.repomgr.PutRecord(ctx, p.uid, atproto.ManifestCollection, rkey, record)
274274+}
275275+270276// Close closes the carstore
271277func (p *HoldPDS) Close() error {
272278 // TODO: Close session properly
+235
pkg/hold/pds/xrpc.go
···55 "context"
66 "encoding/json"
77 "fmt"
88+ "sort"
89910 "atcr.io/pkg/atproto"
1011 "atcr.io/pkg/s3"
···171172 r.Get(atproto.IdentityResolveHandle, h.HandleResolveHandle)
172173 r.Get(atproto.ActorGetProfile, h.HandleGetProfile)
173174 r.Get(atproto.ActorGetProfiles, h.HandleGetProfiles)
175175+176176+ // ORAS/Scanner endpoints
177177+ r.Get(atproto.HoldGetReferrers, h.HandleGetReferrers)
174178 })
175179176180 // Blob read endpoints (conditional auth based on captain.public)
···14151419 // Clients should use multipart upload flow via com.atproto.repo.uploadBlob
14161420 return ""
14171421}
14221422+14231423+// HandleGetReferrers queries for ORAS artifacts (SBOMs, signatures, scan reports) that reference a subject manifest
14241424+// GET /xrpc/io.atcr.hold.getReferrers?digest=sha256:abc123&artifactType=application/vnd.atcr.vulnerabilities+json
14251425+func (h *XRPCHandler) HandleGetReferrers(w http.ResponseWriter, r *http.Request) {
14261426+ ctx := r.Context()
14271427+14281428+ // Parse query parameters
14291429+ digest := r.URL.Query().Get("digest")
14301430+ artifactType := r.URL.Query().Get("artifactType")
14311431+14321432+ if digest == "" {
14331433+ http.Error(w, "digest parameter required", http.StatusBadRequest)
14341434+ return
14351435+ }
14361436+14371437+ slog.Info("Querying referrers", "digest", digest, "artifactType", artifactType)
14381438+14391439+ // Query all manifest records from the hold's PDS using carstore
14401440+ session, err := h.pds.carstore.ReadOnlySession(h.pds.uid)
14411441+ if err != nil {
14421442+ slog.Error("Failed to create session", "error", err)
14431443+ http.Error(w, fmt.Sprintf("failed to create session: %v", err), http.StatusInternalServerError)
14441444+ return
14451445+ }
14461446+14471447+ head, err := h.pds.carstore.GetUserRepoHead(ctx, h.pds.uid)
14481448+ if err != nil {
14491449+ slog.Error("Failed to get repo head", "error", err)
14501450+ http.Error(w, fmt.Sprintf("failed to get repo head: %v", err), http.StatusInternalServerError)
14511451+ return
14521452+ }
14531453+14541454+ if !head.Defined() {
14551455+ // Empty repo, return empty referrers list
14561456+ slog.Info("Empty repo, no referrers found")
14571457+ response := map[string]interface{}{
14581458+ "referrers": []interface{}{},
14591459+ }
14601460+ w.Header().Set("Content-Type", "application/json")
14611461+ json.NewEncoder(w).Encode(response)
14621462+ return
14631463+ }
14641464+14651465+ repoHandle, err := repo.OpenRepo(ctx, session, head)
14661466+ if err != nil {
14671467+ slog.Error("Failed to open repo", "error", err)
14681468+ http.Error(w, fmt.Sprintf("failed to open repo: %v", err), http.StatusInternalServerError)
14691469+ return
14701470+ }
14711471+14721472+ // Filter for referrers with matching subject
14731473+ referrers := []map[string]interface{}{}
14741474+ totalManifests := 0
14751475+14761476+ // Iterate over all records in the manifest collection
14771477+ err = repoHandle.ForEach(ctx, atproto.ManifestCollection, func(k string, v cid.Cid) error {
14781478+ totalManifests++
14791479+14801480+ // Get the record bytes directly from the repo
14811481+ _, recBytes, err := repoHandle.GetRecordBytes(ctx, k)
14821482+ if err != nil {
14831483+ slog.Warn("Failed to get record", "key", k, "error", err)
14841484+ return nil // Skip this record
14851485+ }
14861486+14871487+ // Unmarshal the CBOR bytes into our concrete type
14881488+ var manifest atproto.ManifestRecord
14891489+ if err := manifest.UnmarshalCBOR(bytes.NewReader(*recBytes)); err != nil {
14901490+ slog.Warn("Failed to unmarshal ManifestRecord", "key", k, "error", err)
14911491+ return nil // Skip this record
14921492+ }
14931493+14941494+ // Debug: log what we found
14951495+ slog.Debug("Checking manifest",
14961496+ "key", k,
14971497+ "digest", manifest.Digest,
14981498+ "hasSubject", manifest.Subject != nil,
14991499+ "subjectDigest", func() string {
15001500+ if manifest.Subject != nil {
15011501+ return manifest.Subject.Digest
15021502+ }
15031503+ return "none"
15041504+ }(),
15051505+ "artifactType", manifest.ArtifactType,
15061506+ "mediaType", manifest.MediaType)
15071507+15081508+ // Check if this manifest has a subject that matches the requested digest
15091509+ if manifest.Subject != nil && manifest.Subject.Digest == digest {
15101510+ // If artifactType filter is specified, only include matching artifacts
15111511+ if artifactType != "" && manifest.ArtifactType != artifactType {
15121512+ slog.Debug("Skipping referrer due to artifactType mismatch",
15131513+ "key", k,
15141514+ "digest", manifest.Digest,
15151515+ "wantArtifactType", artifactType,
15161516+ "gotArtifactType", manifest.ArtifactType)
15171517+ return nil // Skip this record
15181518+ }
15191519+15201520+ // Build referrer response
15211521+ referrer := map[string]interface{}{
15221522+ "digest": manifest.Digest,
15231523+ "mediaType": manifest.MediaType,
15241524+ "size": 0, // We don't track manifest size currently
15251525+ "artifactType": manifest.ArtifactType,
15261526+ "annotations": manifest.Annotations,
15271527+ }
15281528+15291529+ // Add scanner metadata if available
15301530+ if manifest.ScannedAt != "" {
15311531+ referrer["scannedAt"] = manifest.ScannedAt
15321532+ }
15331533+ if manifest.ScannerVersion != "" {
15341534+ referrer["scannerVersion"] = manifest.ScannerVersion
15351535+ }
15361536+ if manifest.OwnerDID != "" {
15371537+ referrer["ownerDid"] = manifest.OwnerDID
15381538+ }
15391539+15401540+ // Fetch the actual vulnerability report blob data
15411541+ // manifest.Digest is the ORAS manifest digest, we need to:
15421542+ // 1. Fetch the ORAS manifest blob
15431543+ // 2. Parse it to get layers[0].digest (the actual vulnerability report)
15441544+ // 3. Fetch that blob
15451545+15461546+ // Step 1: Fetch ORAS manifest
15471547+ orasManifestPath := fmt.Sprintf("/docker/registry/v2/blobs/sha256/%s/%s/data",
15481548+ manifest.Digest[7:9], // First 2 chars after "sha256:"
15491549+ manifest.Digest[7:]) // Full hex after "sha256:"
15501550+15511551+ orasManifestData, err := h.storageDriver.GetContent(ctx, orasManifestPath)
15521552+ if err != nil {
15531553+ slog.Warn("Failed to fetch ORAS manifest blob",
15541554+ "digest", manifest.Digest,
15551555+ "path", orasManifestPath,
15561556+ "error", err)
15571557+ // Continue without the blob data
15581558+ } else {
15591559+ // Step 2: Parse ORAS manifest to get vulnerability report digest
15601560+ var orasManifest struct {
15611561+ Layers []struct {
15621562+ Digest string `json:"digest"`
15631563+ } `json:"layers"`
15641564+ }
15651565+ if err := json.Unmarshal(orasManifestData, &orasManifest); err != nil {
15661566+ slog.Warn("Failed to parse ORAS manifest JSON",
15671567+ "digest", manifest.Digest,
15681568+ "error", err)
15691569+ } else if len(orasManifest.Layers) > 0 {
15701570+ // Step 3: Fetch the vulnerability report blob from layers[0]
15711571+ vulnReportDigest := orasManifest.Layers[0].Digest
15721572+ vulnReportPath := fmt.Sprintf("/docker/registry/v2/blobs/sha256/%s/%s/data",
15731573+ vulnReportDigest[7:9],
15741574+ vulnReportDigest[7:])
15751575+15761576+ vulnReportData, err := h.storageDriver.GetContent(ctx, vulnReportPath)
15771577+ if err != nil {
15781578+ slog.Warn("Failed to fetch vulnerability report blob",
15791579+ "digest", vulnReportDigest,
15801580+ "path", vulnReportPath,
15811581+ "error", err)
15821582+ } else {
15831583+ // Parse and include the vulnerability report
15841584+ var reportData map[string]interface{}
15851585+ if err := json.Unmarshal(vulnReportData, &reportData); err != nil {
15861586+ slog.Warn("Failed to parse vulnerability report JSON",
15871587+ "digest", vulnReportDigest,
15881588+ "error", err)
15891589+ } else {
15901590+ referrer["reportData"] = reportData
15911591+ slog.Debug("Included vulnerability report data in referrer",
15921592+ "orasDigest", manifest.Digest,
15931593+ "reportDigest", vulnReportDigest,
15941594+ "reportSize", len(vulnReportData))
15951595+ }
15961596+ }
15971597+ }
15981598+ }
15991599+16001600+ slog.Debug("Found matching referrer",
16011601+ "key", k,
16021602+ "digest", manifest.Digest,
16031603+ "artifactType", manifest.ArtifactType,
16041604+ "annotations", manifest.Annotations,
16051605+ "annotationsLen", len(manifest.Annotations))
16061606+16071607+ referrers = append(referrers, referrer)
16081608+ }
16091609+16101610+ return nil // Continue iteration
16111611+ })
16121612+16131613+ if err != nil && err != repo.ErrDoneIterating && !strings.Contains(err.Error(), "done iterating") {
16141614+ slog.Error("Failed to iterate records", "error", err)
16151615+ http.Error(w, fmt.Sprintf("failed to iterate records: %v", err), http.StatusInternalServerError)
16161616+ return
16171617+ }
16181618+16191619+ // Sort referrers by scannedAt timestamp (descending, most recent first)
16201620+ // This ensures the AppView gets the latest scan result when it takes the first referrer
16211621+ sort.Slice(referrers, func(i, j int) bool {
16221622+ iScanned, iOk := referrers[i]["scannedAt"].(string)
16231623+ jScanned, jOk := referrers[j]["scannedAt"].(string)
16241624+ // If both have scannedAt, compare timestamps (reverse order for descending)
16251625+ if iOk && jOk {
16261626+ return iScanned > jScanned
16271627+ }
16281628+ // If only one has scannedAt, prefer that one
16291629+ if iOk {
16301630+ return true
16311631+ }
16321632+ if jOk {
16331633+ return false
16341634+ }
16351635+ // Neither has scannedAt, maintain original order
16361636+ return false
16371637+ })
16381638+16391639+ slog.Info("Found referrers",
16401640+ "count", len(referrers),
16411641+ "totalManifests", totalManifests,
16421642+ "digest", digest,
16431643+ "artifactType", artifactType)
16441644+16451645+ // Return response
16461646+ response := map[string]interface{}{
16471647+ "referrers": referrers,
16481648+ }
16491649+16501650+ w.Header().Set("Content-Type", "application/json")
16511651+ json.NewEncoder(w).Encode(response)
16521652+}