github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/pkg/service/report.go (about)

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