1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
// Copyright 2025 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package service
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/syzkaller/syz-cluster/pkg/api"
"github.com/google/syzkaller/syz-cluster/pkg/app"
"github.com/google/syzkaller/syz-cluster/pkg/db"
)
type ReportService struct {
reportRepo *db.ReportRepository
seriesService *SeriesService
findingService *FindingService
urls *api.URLGenerator
}
func NewReportService(env *app.AppEnvironment) *ReportService {
return &ReportService{
urls: env.URLs,
reportRepo: db.NewReportRepository(env.Spanner),
seriesService: NewSeriesService(env),
findingService: NewFindingService(env),
}
}
var ErrReportNotFound = errors.New("report is not found")
func (rs *ReportService) Confirm(ctx context.Context, id string) error {
err := rs.reportRepo.Update(ctx, id, func(rep *db.SessionReport) error {
if rep.ReportedAt.IsNull() {
rep.SetReportedAt(time.Now())
}
// TODO: fail if already confirmed?
return nil
})
if errors.Is(err, db.ErrEntityNotFound) {
return ErrReportNotFound
}
return err
}
var ErrNotOnModeration = errors.New("the report is not on moderation")
func (rs *ReportService) Upstream(ctx context.Context, id string, req *api.UpstreamReportReq) error {
rep, err := rs.query(ctx, id)
if err != nil {
return nil
} else if !rep.Moderation {
return ErrNotOnModeration
}
// In case of a concurrent Upstream() call or an Upstream() invocation on
// an already upstreamed report, the "NoDupSessionReports" index should
// prevent duplications.
err = rs.reportRepo.Insert(ctx, &db.SessionReport{
SessionID: rep.SessionID,
Reporter: rep.Reporter,
})
if err != nil {
return fmt.Errorf("failed to schedule a new report: %w", err)
}
return nil
}
const maxFindingsPerReport = 5
func (rs *ReportService) Next(ctx context.Context, reporter string) (*api.NextReportResp, error) {
list, err := rs.reportRepo.ListNotReported(ctx, reporter, 1)
if err != nil {
return nil, err
} else if len(list) != 1 {
return &api.NextReportResp{}, nil
}
report := list[0]
series, err := rs.seriesService.GetSessionSeriesShort(ctx, report.SessionID)
if err != nil {
return nil, fmt.Errorf("failed to query series: %w", err)
}
findings, err := rs.findingService.List(ctx, report.SessionID, maxFindingsPerReport)
if err != nil {
return nil, fmt.Errorf("failed to query findings: %w", err)
}
return &api.NextReportResp{
Report: &api.SessionReport{
ID: report.ID,
Moderation: report.Moderation,
Series: series,
Link: rs.urls.Series(series.ID),
Findings: findings,
},
}, nil
}
func (rs *ReportService) query(ctx context.Context, id string) (*db.SessionReport, error) {
rep, err := rs.reportRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to query the report: %w", err)
} else if rep == nil {
return nil, ErrReportNotFound
}
return rep, err
}
|