github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/dashapi/dashapi.go (about)

     1  // Copyright 2017 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 dashapi defines data structures used in dashboard communication
     5  // and provides client interface.
     6  package dashapi
     7  
     8  import (
     9  	"bytes"
    10  	"compress/gzip"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"mime/multipart"
    15  	"net/http"
    16  	"net/mail"
    17  	"reflect"
    18  	"time"
    19  
    20  	"github.com/google/syzkaller/pkg/auth"
    21  )
    22  
    23  type Dashboard struct {
    24  	Client       string
    25  	Addr         string
    26  	Key          string
    27  	ctor         RequestCtor
    28  	doer         RequestDoer
    29  	logger       RequestLogger
    30  	errorHandler func(error)
    31  }
    32  
    33  type DashboardOpts any
    34  type UserAgent string
    35  
    36  func New(client, addr, key string, opts ...DashboardOpts) (*Dashboard, error) {
    37  	ctor := http.NewRequest
    38  	for _, o := range opts {
    39  		switch opt := o.(type) {
    40  		case UserAgent:
    41  			ctor = func(method, url string, body io.Reader) (*http.Request, error) {
    42  				req, err := http.NewRequest(method, url, body)
    43  				if err != nil {
    44  					return nil, err
    45  				}
    46  				req.Header.Add("User-Agent", string(opt))
    47  				return req, nil
    48  			}
    49  		}
    50  	}
    51  	return NewCustom(client, addr, key, ctor, http.DefaultClient.Do, nil, nil)
    52  }
    53  
    54  type (
    55  	RequestCtor   func(method, url string, body io.Reader) (*http.Request, error)
    56  	RequestDoer   func(req *http.Request) (*http.Response, error)
    57  	RequestLogger func(msg string, args ...interface{})
    58  )
    59  
    60  // key == "" indicates that the ambient GCE service account authority
    61  // should be used as a bearer token.
    62  func NewCustom(client, addr, key string, ctor RequestCtor, doer RequestDoer,
    63  	logger RequestLogger, errorHandler func(error)) (*Dashboard, error) {
    64  	wrappedDoer := doer
    65  	if key == "" {
    66  		tokenCache, err := auth.MakeCache(ctor, doer)
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  		wrappedDoer = func(req *http.Request) (*http.Response, error) {
    71  			token, err := tokenCache.Get(time.Now())
    72  			if err != nil {
    73  				return nil, err
    74  			}
    75  			req.Header.Add("Authorization", token)
    76  			return doer(req)
    77  		}
    78  	}
    79  	return &Dashboard{
    80  		Client:       client,
    81  		Addr:         addr,
    82  		Key:          key,
    83  		ctor:         ctor,
    84  		doer:         wrappedDoer,
    85  		logger:       logger,
    86  		errorHandler: errorHandler,
    87  	}, nil
    88  }
    89  
    90  // Build describes all aspects of a kernel build.
    91  type Build struct {
    92  	Manager             string
    93  	ID                  string
    94  	OS                  string
    95  	Arch                string
    96  	VMArch              string
    97  	SyzkallerCommit     string
    98  	SyzkallerCommitDate time.Time
    99  	CompilerID          string
   100  	KernelRepo          string
   101  	KernelBranch        string
   102  	KernelCommit        string
   103  	KernelCommitTitle   string
   104  	KernelCommitDate    time.Time
   105  	KernelConfig        []byte
   106  	Commits             []string // see BuilderPoll
   107  	FixCommits          []Commit
   108  	Assets              []NewAsset
   109  }
   110  
   111  type Commit struct {
   112  	Hash       string
   113  	Title      string
   114  	Author     string
   115  	AuthorName string
   116  	CC         []string // deprecated in favor of Recipients
   117  	Recipients Recipients
   118  	BugIDs     []string // ID's extracted from Reported-by tags
   119  	Date       time.Time
   120  	Link       string // set if the commit is a part of a reply
   121  }
   122  
   123  func (dash *Dashboard) UploadBuild(build *Build) error {
   124  	return dash.Query("upload_build", build, nil)
   125  }
   126  
   127  // BuilderPoll request is done by kernel builder before uploading a new build
   128  // with UploadBuild request. Response contains list of commit titles that
   129  // dashboard is interested in (i.e. commits that fix open bugs) and email that
   130  // appears in Reported-by tags for bug ID extraction. When uploading a new build
   131  // builder will pass subset of the commit titles that are present in the build
   132  // in Build.Commits field and list of {bug ID, commit title} pairs extracted
   133  // from git log.
   134  
   135  type BuilderPollReq struct {
   136  	Manager string
   137  }
   138  
   139  type BuilderPollResp struct {
   140  	PendingCommits []string
   141  	ReportEmail    string
   142  }
   143  
   144  func (dash *Dashboard) BuilderPoll(manager string) (*BuilderPollResp, error) {
   145  	req := &BuilderPollReq{
   146  		Manager: manager,
   147  	}
   148  	resp := new(BuilderPollResp)
   149  	err := dash.Query("builder_poll", req, resp)
   150  	return resp, err
   151  }
   152  
   153  // Jobs workflow:
   154  //   - syz-ci sends JobResetReq to indicate that no previously started jobs
   155  //     are any longer in progress.
   156  //   - syz-ci sends JobPollReq periodically to check for new jobs,
   157  //     request contains list of managers that this syz-ci runs.
   158  //   - dashboard replies with JobPollResp that contains job details,
   159  //     if no new jobs available ID is set to empty string.
   160  //   - when syz-ci finishes the job, it sends JobDoneReq which contains
   161  //     job execution result (Build, Crash or Error details),
   162  //     ID must match JobPollResp.ID.
   163  
   164  type JobResetReq struct {
   165  	Managers []string
   166  }
   167  
   168  type JobPollReq struct {
   169  	Managers map[string]ManagerJobs
   170  }
   171  
   172  type ManagerJobs struct {
   173  	TestPatches bool
   174  	BisectCause bool
   175  	BisectFix   bool
   176  }
   177  
   178  func (m ManagerJobs) Any() bool {
   179  	return m.TestPatches || m.BisectCause || m.BisectFix
   180  }
   181  
   182  type JobPollResp struct {
   183  	ID         string
   184  	Type       JobType
   185  	Manager    string
   186  	KernelRepo string
   187  	// KernelBranch is used for patch testing and serves as the current HEAD
   188  	// for bisections.
   189  	KernelBranch    string
   190  	MergeBaseRepo   string
   191  	MergeBaseBranch string
   192  	// Bisection starts from KernelCommit.
   193  	KernelCommit      string
   194  	KernelCommitTitle string
   195  	KernelConfig      []byte
   196  	SyzkallerCommit   string
   197  	Patch             []byte
   198  	ReproOpts         []byte
   199  	ReproSyz          []byte
   200  	ReproC            []byte
   201  }
   202  
   203  type JobDoneReq struct {
   204  	ID             string
   205  	Build          Build
   206  	Error          []byte
   207  	Log            []byte // bisection log
   208  	CrashTitle     string
   209  	CrashAltTitles []string
   210  	CrashLog       []byte
   211  	CrashReport    []byte
   212  	// Bisection results:
   213  	// If there is 0 commits:
   214  	//  - still happens on HEAD for fix bisection
   215  	//  - already happened on the oldest release
   216  	// If there is 1 commits: bisection result (cause or fix).
   217  	// If there are more than 1: suspected commits due to skips (broken build/boot).
   218  	Commits []Commit
   219  	Flags   JobDoneFlags
   220  }
   221  
   222  type JobType int
   223  
   224  const (
   225  	JobTestPatch JobType = iota
   226  	JobBisectCause
   227  	JobBisectFix
   228  )
   229  
   230  type JobDoneFlags int64
   231  
   232  const (
   233  	BisectResultMerge      JobDoneFlags = 1 << iota // bisected to a merge commit
   234  	BisectResultNoop                                // commit does not affect resulting kernel binary
   235  	BisectResultRelease                             // commit is a kernel release
   236  	BisectResultIgnore                              // this particular commit should be ignored, see syz-ci/jobs.go
   237  	BisectResultInfraError                          // the bisect failed due to an infrastructure problem
   238  )
   239  
   240  func (flags JobDoneFlags) String() string {
   241  	if flags&BisectResultInfraError != 0 {
   242  		return "[infra failure]"
   243  	}
   244  	res := ""
   245  	if flags&BisectResultMerge != 0 {
   246  		res += "merge "
   247  	}
   248  	if flags&BisectResultNoop != 0 {
   249  		res += "no-op "
   250  	}
   251  	if flags&BisectResultRelease != 0 {
   252  		res += "release "
   253  	}
   254  	if flags&BisectResultIgnore != 0 {
   255  		res += "ignored "
   256  	}
   257  	if res == "" {
   258  		return res
   259  	}
   260  	return "[" + res + "commit]"
   261  }
   262  
   263  func (dash *Dashboard) JobPoll(req *JobPollReq) (*JobPollResp, error) {
   264  	resp := new(JobPollResp)
   265  	err := dash.Query("job_poll", req, resp)
   266  	return resp, err
   267  }
   268  
   269  func (dash *Dashboard) JobDone(req *JobDoneReq) error {
   270  	return dash.Query("job_done", req, nil)
   271  }
   272  
   273  func (dash *Dashboard) JobReset(req *JobResetReq) error {
   274  	return dash.Query("job_reset", req, nil)
   275  }
   276  
   277  type BuildErrorReq struct {
   278  	Build Build
   279  	Crash Crash
   280  }
   281  
   282  func (dash *Dashboard) ReportBuildError(req *BuildErrorReq) error {
   283  	return dash.Query("report_build_error", req, nil)
   284  }
   285  
   286  type CommitPollResp struct {
   287  	ReportEmail string
   288  	Repos       []Repo
   289  	Commits     []string
   290  }
   291  
   292  type CommitPollResultReq struct {
   293  	Commits []Commit
   294  }
   295  
   296  type Repo struct {
   297  	URL    string
   298  	Branch string
   299  }
   300  
   301  func (dash *Dashboard) CommitPoll() (*CommitPollResp, error) {
   302  	resp := new(CommitPollResp)
   303  	err := dash.Query("commit_poll", nil, resp)
   304  	return resp, err
   305  }
   306  
   307  func (dash *Dashboard) UploadCommits(commits []Commit) error {
   308  	if len(commits) == 0 {
   309  		return nil
   310  	}
   311  	return dash.Query("upload_commits", &CommitPollResultReq{commits}, nil)
   312  }
   313  
   314  type CrashFlags int64
   315  
   316  const (
   317  	CrashUnderStrace CrashFlags = 1 << iota
   318  )
   319  
   320  // Crash describes a single kernel crash (potentially with repro).
   321  type Crash struct {
   322  	BuildID     string // refers to Build.ID
   323  	Title       string
   324  	AltTitles   []string // alternative titles, used for better deduplication
   325  	Corrupted   bool     // report is corrupted (corrupted title, no stacks, etc)
   326  	Suppressed  bool
   327  	Maintainers []string // deprecated in favor of Recipients
   328  	Recipients  Recipients
   329  	Log         []byte
   330  	Flags       CrashFlags
   331  	Report      []byte
   332  	MachineInfo []byte
   333  	Assets      []NewAsset
   334  	GuiltyFiles []string
   335  	// The following is optional and is filled only after repro.
   336  	ReproOpts     []byte
   337  	ReproSyz      []byte
   338  	ReproC        []byte
   339  	ReproLog      []byte
   340  	OriginalTitle string // Title before we began bug reproduction.
   341  }
   342  
   343  type ReportCrashResp struct {
   344  	NeedRepro bool
   345  }
   346  
   347  func (dash *Dashboard) ReportCrash(crash *Crash) (*ReportCrashResp, error) {
   348  	resp := new(ReportCrashResp)
   349  	err := dash.Query("report_crash", crash, resp)
   350  	return resp, err
   351  }
   352  
   353  // CrashID is a short summary of a crash for repro queries.
   354  type CrashID struct {
   355  	BuildID      string
   356  	Title        string
   357  	Corrupted    bool
   358  	Suppressed   bool
   359  	MayBeMissing bool
   360  	ReproLog     []byte
   361  }
   362  
   363  type NeedReproResp struct {
   364  	NeedRepro bool
   365  }
   366  
   367  // NeedRepro checks if dashboard needs a repro for this crash or not.
   368  func (dash *Dashboard) NeedRepro(crash *CrashID) (bool, error) {
   369  	resp := new(NeedReproResp)
   370  	err := dash.Query("need_repro", crash, resp)
   371  	return resp.NeedRepro, err
   372  }
   373  
   374  // ReportFailedRepro notifies dashboard about a failed repro attempt for the crash.
   375  func (dash *Dashboard) ReportFailedRepro(crash *CrashID) error {
   376  	return dash.Query("report_failed_repro", crash, nil)
   377  }
   378  
   379  type LogToReproReq struct {
   380  	BuildID string
   381  }
   382  
   383  type LogToReproType string
   384  
   385  const (
   386  	ManualLog     LogToReproType = "manual"
   387  	RetryReproLog LogToReproType = "retry"
   388  )
   389  
   390  type LogToReproResp struct {
   391  	Title    string
   392  	CrashLog []byte
   393  	Type     LogToReproType
   394  }
   395  
   396  // LogToRepro are crash logs for older bugs that need to be reproduced on the
   397  // querying instance.
   398  func (dash *Dashboard) LogToRepro(req *LogToReproReq) (*LogToReproResp, error) {
   399  	resp := new(LogToReproResp)
   400  	err := dash.Query("log_to_repro", req, resp)
   401  	return resp, err
   402  }
   403  
   404  type LogEntry struct {
   405  	Name string
   406  	Text string
   407  }
   408  
   409  // Centralized logging on dashboard.
   410  func (dash *Dashboard) LogError(name, msg string, args ...interface{}) {
   411  	req := &LogEntry{
   412  		Name: name,
   413  		Text: fmt.Sprintf(msg, args...),
   414  	}
   415  	dash.Query("log_error", req, nil)
   416  }
   417  
   418  // BugReport describes a single bug.
   419  // Used by dashboard external reporting.
   420  type BugReport struct {
   421  	Type              ReportType
   422  	BugStatus         BugStatus
   423  	Namespace         string
   424  	Config            []byte
   425  	ID                string
   426  	JobID             string
   427  	ExtID             string // arbitrary reporting ID forwarded from BugUpdate.ExtID
   428  	First             bool   // Set for first report for this bug (Type == ReportNew).
   429  	Moderation        bool
   430  	NoRepro           bool // We don't expect repro (e.g. for build/boot errors).
   431  	Title             string
   432  	Link              string   // link to the bug on dashboard
   433  	CreditEmail       string   // email for the Reported-by tag
   434  	Maintainers       []string // deprecated in favor of Recipients
   435  	CC                []string // deprecated in favor of Recipients
   436  	Recipients        Recipients
   437  	OS                string
   438  	Arch              string
   439  	VMArch            string
   440  	UserSpaceArch     string // user-space arch as kernel developers know it (rather than Go names)
   441  	BuildID           string
   442  	BuildTime         time.Time
   443  	CompilerID        string
   444  	KernelRepo        string
   445  	KernelRepoAlias   string
   446  	KernelBranch      string
   447  	KernelCommit      string
   448  	KernelCommitTitle string
   449  	KernelCommitDate  time.Time
   450  	KernelConfig      []byte
   451  	KernelConfigLink  string
   452  	SyzkallerCommit   string
   453  	Log               []byte
   454  	LogLink           string
   455  	LogHasStrace      bool
   456  	Report            []byte
   457  	ReportLink        string
   458  	ReproC            []byte
   459  	ReproCLink        string
   460  	ReproSyz          []byte
   461  	ReproSyzLink      string
   462  	ReproIsRevoked    bool
   463  	ReproOpts         []byte
   464  	MachineInfo       []byte
   465  	MachineInfoLink   string
   466  	Manager           string
   467  	CrashID           int64 // returned back in BugUpdate
   468  	CrashTime         time.Time
   469  	NumCrashes        int64
   470  	HappenedOn        []string // list of kernel repo aliases
   471  
   472  	CrashTitle     string // job execution crash title
   473  	Error          []byte // job execution error
   474  	ErrorLink      string
   475  	ErrorTruncated bool // full Error text is too large and was truncated
   476  	PatchLink      string
   477  	BisectCause    *BisectResult
   478  	BisectFix      *BisectResult
   479  	Assets         []Asset
   480  	Subsystems     []BugSubsystem
   481  	ReportElements *ReportElements
   482  	LabelMessages  map[string]string // notification messages for bug labels
   483  }
   484  
   485  type ReportElements struct {
   486  	GuiltyFiles []string
   487  }
   488  
   489  type BugSubsystem struct {
   490  	Name  string
   491  	Link  string
   492  	SetBy string
   493  }
   494  
   495  type Asset struct {
   496  	Title       string
   497  	DownloadURL string
   498  	Type        AssetType
   499  	FsckLogURL  string
   500  	FsIsClean   bool
   501  }
   502  
   503  type AssetType string
   504  
   505  // Asset types used throughout the system.
   506  // DO NOT change them, this will break compatibility with DB content.
   507  const (
   508  	BootableDisk       AssetType = "bootable_disk"
   509  	NonBootableDisk    AssetType = "non_bootable_disk"
   510  	KernelObject       AssetType = "kernel_object"
   511  	KernelImage        AssetType = "kernel_image"
   512  	HTMLCoverageReport AssetType = "html_coverage_report"
   513  	MountInRepro       AssetType = "mount_in_repro"
   514  )
   515  
   516  type BisectResult struct {
   517  	Commit          *Commit   // for conclusive bisection
   518  	Commits         []*Commit // for inconclusive bisection
   519  	LogLink         string
   520  	CrashLogLink    string
   521  	CrashReportLink string
   522  	Fix             bool
   523  	CrossTree       bool
   524  	// In case a missing backport was backported.
   525  	Backported *Commit
   526  }
   527  
   528  type BugListReport struct {
   529  	ID          string
   530  	Created     time.Time
   531  	Config      []byte
   532  	Bugs        []BugListItem
   533  	TotalStats  BugListReportStats
   534  	PeriodStats BugListReportStats
   535  	PeriodDays  int
   536  	Link        string
   537  	Subsystem   string
   538  	Maintainers []string
   539  	Moderation  bool
   540  }
   541  
   542  type BugListReportStats struct {
   543  	Reported int
   544  	LowPrio  int
   545  	Fixed    int
   546  }
   547  
   548  // BugListItem represents a single bug from the BugListReport entity.
   549  type BugListItem struct {
   550  	ID         string
   551  	Title      string
   552  	Link       string
   553  	ReproLevel ReproLevel
   554  	Hits       int64
   555  }
   556  
   557  type BugListUpdate struct {
   558  	ID      string // copied from BugListReport
   559  	ExtID   string
   560  	Link    string
   561  	Command BugListUpdateCommand
   562  }
   563  
   564  type BugListUpdateCommand string
   565  
   566  const (
   567  	BugListSentCmd       BugListUpdateCommand = "sent"
   568  	BugListUpdateCmd     BugListUpdateCommand = "update"
   569  	BugListUpstreamCmd   BugListUpdateCommand = "upstream"
   570  	BugListRegenerateCmd BugListUpdateCommand = "regenerate"
   571  )
   572  
   573  type BugUpdate struct {
   574  	ID              string // copied from BugReport
   575  	JobID           string // copied from BugReport
   576  	ExtID           string
   577  	Link            string
   578  	Status          BugStatus
   579  	StatusReason    BugStatusReason
   580  	Labels          []string // the reported labels
   581  	ReproLevel      ReproLevel
   582  	DupOf           string
   583  	OnHold          bool     // If set for open bugs, don't upstream this bug.
   584  	Notification    bool     // Reply to a notification.
   585  	ResetFixCommits bool     // Remove all commits (empty FixCommits means leave intact).
   586  	FixCommits      []string // Titles of commits that fix this bug.
   587  	CC              []string // Additional emails to add to CC list in future emails.
   588  
   589  	CrashID int64 // This is a deprecated field, left here for backward compatibility.
   590  
   591  	// The new interface that allows to report and unreport several crashes at the same time.
   592  	// This is not relevant for emails, but may be important for external reportings.
   593  	ReportCrashIDs   []int64
   594  	UnreportCrashIDs []int64
   595  }
   596  
   597  type BugUpdateReply struct {
   598  	// Bug update can fail for 2 reason:
   599  	//  - update does not pass logical validataion, in this case OK=false
   600  	//  - internal/datastore error, in this case Error=true
   601  	OK    bool
   602  	Error bool
   603  	Text  string
   604  }
   605  
   606  type PollBugsRequest struct {
   607  	Type string
   608  }
   609  
   610  type PollBugsResponse struct {
   611  	Reports []*BugReport
   612  }
   613  
   614  type BugNotification struct {
   615  	Type        BugNotif
   616  	Namespace   string
   617  	Config      []byte
   618  	ID          string
   619  	ExtID       string // arbitrary reporting ID forwarded from BugUpdate.ExtID
   620  	Title       string
   621  	Text        string   // meaning depends on Type
   622  	Label       string   // for BugNotifLabel Type specifies the exact label
   623  	CC          []string // deprecated in favor of Recipients
   624  	Maintainers []string // deprecated in favor of Recipients
   625  	Link        string
   626  	Recipients  Recipients
   627  	TreeJobs    []*JobInfo // set for some BugNotifLabel
   628  	// Public is what we want all involved people to see (e.g. if we notify about a wrong commit title,
   629  	// people need to see it and provide the right title). Not public is what we want to send only
   630  	// to a minimal set of recipients (our mailing list) (e.g. notification about an obsoleted bug
   631  	// is mostly "for the record").
   632  	Public bool
   633  }
   634  
   635  type PollNotificationsRequest struct {
   636  	Type string
   637  }
   638  
   639  type PollNotificationsResponse struct {
   640  	Notifications []*BugNotification
   641  }
   642  
   643  type PollClosedRequest struct {
   644  	IDs []string
   645  }
   646  
   647  type PollClosedResponse struct {
   648  	IDs []string
   649  }
   650  
   651  type DiscussionSource string
   652  
   653  const (
   654  	NoDiscussion   DiscussionSource = ""
   655  	DiscussionLore DiscussionSource = "lore"
   656  )
   657  
   658  type DiscussionType string
   659  
   660  const (
   661  	DiscussionReport   DiscussionType = "report"
   662  	DiscussionPatch    DiscussionType = "patch"
   663  	DiscussionReminder DiscussionType = "reminder"
   664  	DiscussionMention  DiscussionType = "mention"
   665  )
   666  
   667  type Discussion struct {
   668  	ID       string
   669  	Source   DiscussionSource
   670  	Type     DiscussionType
   671  	Subject  string
   672  	BugIDs   []string
   673  	Messages []DiscussionMessage
   674  }
   675  
   676  type DiscussionMessage struct {
   677  	ID       string
   678  	External bool // true if the message is not from the bot itself
   679  	Time     time.Time
   680  	Email    string // not saved to the DB
   681  }
   682  
   683  type SaveDiscussionReq struct {
   684  	// If the discussion already exists, Messages and BugIDs will be appended to it.
   685  	Discussion *Discussion
   686  }
   687  
   688  func (dash *Dashboard) SaveDiscussion(req *SaveDiscussionReq) error {
   689  	return dash.Query("save_discussion", req, nil)
   690  }
   691  
   692  func (dash *Dashboard) CreateUploadURL() (string, error) {
   693  	uploadURL := new(string)
   694  	if err := dash.Query("create_upload_url", nil, uploadURL); err != nil {
   695  		return "", fmt.Errorf("create_upload_url: %w", err)
   696  	}
   697  	return *uploadURL, nil
   698  }
   699  
   700  // SaveCoverage returns amount of records created in db.
   701  func (dash *Dashboard) SaveCoverage(gcpURL string) (int, error) {
   702  	rowsWritten := new(int)
   703  	if err := dash.Query("save_coverage", gcpURL, rowsWritten); err != nil {
   704  		return 0, fmt.Errorf("save_coverage: %w", err)
   705  	}
   706  	return *rowsWritten, nil
   707  }
   708  
   709  type TestPatchRequest struct {
   710  	BugID  string
   711  	Link   string
   712  	User   string
   713  	Repo   string
   714  	Branch string
   715  	Patch  []byte
   716  }
   717  
   718  type TestPatchReply struct {
   719  	ErrorText string
   720  }
   721  
   722  func (dash *Dashboard) ReportingPollBugs(typ string) (*PollBugsResponse, error) {
   723  	req := &PollBugsRequest{
   724  		Type: typ,
   725  	}
   726  	resp := new(PollBugsResponse)
   727  	if err := dash.Query("reporting_poll_bugs", req, resp); err != nil {
   728  		return nil, err
   729  	}
   730  	return resp, nil
   731  }
   732  
   733  func (dash *Dashboard) ReportingPollNotifications(typ string) (*PollNotificationsResponse, error) {
   734  	req := &PollNotificationsRequest{
   735  		Type: typ,
   736  	}
   737  	resp := new(PollNotificationsResponse)
   738  	if err := dash.Query("reporting_poll_notifs", req, resp); err != nil {
   739  		return nil, err
   740  	}
   741  	return resp, nil
   742  }
   743  
   744  func (dash *Dashboard) ReportingPollClosed(ids []string) ([]string, error) {
   745  	req := &PollClosedRequest{
   746  		IDs: ids,
   747  	}
   748  	resp := new(PollClosedResponse)
   749  	if err := dash.Query("reporting_poll_closed", req, resp); err != nil {
   750  		return nil, err
   751  	}
   752  	return resp.IDs, nil
   753  }
   754  
   755  func (dash *Dashboard) ReportingUpdate(upd *BugUpdate) (*BugUpdateReply, error) {
   756  	resp := new(BugUpdateReply)
   757  	if err := dash.Query("reporting_update", upd, resp); err != nil {
   758  		return nil, err
   759  	}
   760  	return resp, nil
   761  }
   762  
   763  func (dash *Dashboard) NewTestJob(upd *TestPatchRequest) (*TestPatchReply, error) {
   764  	resp := new(TestPatchReply)
   765  	if err := dash.Query("new_test_job", upd, resp); err != nil {
   766  		return nil, err
   767  	}
   768  	return resp, nil
   769  }
   770  
   771  type ManagerStatsReq struct {
   772  	Name string
   773  	Addr string
   774  
   775  	// Current level:
   776  	UpTime     time.Duration
   777  	Corpus     uint64
   778  	PCs        uint64 // coverage
   779  	Cover      uint64 // what we call feedback signal everywhere else
   780  	CrashTypes uint64
   781  
   782  	// Delta since last sync:
   783  	FuzzingTime       time.Duration
   784  	Crashes           uint64
   785  	SuppressedCrashes uint64
   786  	Execs             uint64
   787  
   788  	// Non-zero only when set.
   789  	TriagedCoverage uint64
   790  	TriagedPCs      uint64
   791  }
   792  
   793  func (dash *Dashboard) UploadManagerStats(req *ManagerStatsReq) error {
   794  	return dash.Query("manager_stats", req, nil)
   795  }
   796  
   797  // Asset lifetime:
   798  // 1. syz-ci uploads it to GCS and reports to the dashboard via add_build_asset.
   799  // 2. dashboard periodically checks if the asset is still needed.
   800  // 3. syz-ci queries needed_assets to figure out which assets are still needed.
   801  // 4. Once an asset is not needed, syz-ci removes the corresponding file.
   802  type NewAsset struct {
   803  	DownloadURL string
   804  	Type        AssetType
   805  	FsckLog     []byte
   806  	FsIsClean   bool
   807  }
   808  
   809  type AddBuildAssetsReq struct {
   810  	BuildID string
   811  	Assets  []NewAsset
   812  }
   813  
   814  func (dash *Dashboard) AddBuildAssets(req *AddBuildAssetsReq) error {
   815  	return dash.Query("add_build_assets", req, nil)
   816  }
   817  
   818  type NeededAssetsResp struct {
   819  	DownloadURLs []string
   820  }
   821  
   822  func (dash *Dashboard) NeededAssetsList() (*NeededAssetsResp, error) {
   823  	resp := new(NeededAssetsResp)
   824  	err := dash.Query("needed_assets", nil, resp)
   825  	return resp, err
   826  }
   827  
   828  type BugListResp struct {
   829  	List []string
   830  }
   831  
   832  func (dash *Dashboard) BugList() (*BugListResp, error) {
   833  	resp := new(BugListResp)
   834  	err := dash.Query("bug_list", nil, resp)
   835  	return resp, err
   836  }
   837  
   838  type LoadBugReq struct {
   839  	ID string
   840  }
   841  
   842  func (dash *Dashboard) LoadBug(id string) (*BugReport, error) {
   843  	req := LoadBugReq{id}
   844  	resp := new(BugReport)
   845  	err := dash.Query("load_bug", req, resp)
   846  	return resp, err
   847  }
   848  
   849  type LoadFullBugReq struct {
   850  	BugID string
   851  }
   852  
   853  type FullBugInfo struct {
   854  	SimilarBugs  []*SimilarBugInfo
   855  	BisectCause  *BugReport
   856  	BisectFix    *BugReport
   857  	Crashes      []*BugReport
   858  	TreeJobs     []*JobInfo
   859  	FixCandidate *BugReport
   860  }
   861  
   862  type SimilarBugInfo struct {
   863  	Title      string
   864  	Status     BugStatus
   865  	Namespace  string
   866  	Link       string
   867  	ReportLink string
   868  	Closed     time.Time
   869  	ReproLevel ReproLevel
   870  }
   871  
   872  func (dash *Dashboard) LoadFullBug(req *LoadFullBugReq) (*FullBugInfo, error) {
   873  	resp := new(FullBugInfo)
   874  	err := dash.Query("load_full_bug", req, resp)
   875  	return resp, err
   876  }
   877  
   878  type UpdateReportReq struct {
   879  	BugID       string
   880  	CrashID     int64
   881  	GuiltyFiles *[]string
   882  }
   883  
   884  func (dash *Dashboard) UpdateReport(req *UpdateReportReq) error {
   885  	return dash.Query("update_report", req, nil)
   886  }
   887  
   888  type SendEmailReq struct {
   889  	Sender    string
   890  	To        []string
   891  	Cc        []string
   892  	Subject   string
   893  	InReplyTo string
   894  	Body      string
   895  }
   896  
   897  func (dash *Dashboard) SendEmail(req *SendEmailReq) error {
   898  	return dash.Query("send_email", req, nil)
   899  }
   900  
   901  type (
   902  	BugStatus       int
   903  	BugStatusReason string
   904  	BugNotif        int
   905  	ReproLevel      int
   906  	ReportType      int
   907  )
   908  
   909  const (
   910  	BugStatusOpen BugStatus = iota
   911  	BugStatusUpstream
   912  	BugStatusInvalid
   913  	BugStatusDup
   914  	BugStatusUpdate // aux info update (i.e. ExtID/Link/CC)
   915  	BugStatusUnCC   // don't CC sender on any future communication
   916  	BugStatusFixed
   917  )
   918  
   919  const (
   920  	InvalidatedByRevokedRepro = BugStatusReason("invalid_no_repro")
   921  	InvalidatedByNoActivity   = BugStatusReason("invalid_no_activity")
   922  )
   923  
   924  const (
   925  	// Upstream bug into next reporting.
   926  	// If the action succeeds, reporting sends BugStatusUpstream update.
   927  	BugNotifUpstream BugNotif = iota
   928  	// Bug needs to be closed as obsoleted.
   929  	// If the action succeeds, reporting sends BugStatusInvalid update.
   930  	BugNotifObsoleted
   931  	// Bug fixing commit can't be discovered (wrong commit title).
   932  	BugNotifBadCommit
   933  	// New bug label has been assigned (only if enabled).
   934  	// Text contains the custome message that needs to be delivered to the user.
   935  	BugNotifLabel
   936  )
   937  
   938  const (
   939  	ReproLevelNone ReproLevel = iota
   940  	ReproLevelSyz
   941  	ReproLevelC
   942  )
   943  
   944  const (
   945  	ReportNew         ReportType = iota // First report for this bug in the reporting stage.
   946  	ReportRepro                         // Found repro for an already reported bug.
   947  	ReportTestPatch                     // Patch testing result.
   948  	ReportBisectCause                   // Cause bisection result for an already reported bug.
   949  	ReportBisectFix                     // Fix bisection result for an already reported bug.
   950  )
   951  
   952  type JobInfo struct {
   953  	JobKey           string
   954  	Type             JobType
   955  	Flags            JobDoneFlags
   956  	Created          time.Time
   957  	BugLink          string
   958  	ExternalLink     string
   959  	User             string
   960  	Reporting        string
   961  	Namespace        string
   962  	Manager          string
   963  	BugTitle         string
   964  	BugID            string
   965  	KernelRepo       string
   966  	KernelBranch     string
   967  	KernelAlias      string
   968  	KernelCommit     string
   969  	KernelCommitLink string
   970  	KernelLink       string
   971  	PatchLink        string
   972  	Attempts         int
   973  	Started          time.Time
   974  	Finished         time.Time
   975  	Duration         time.Duration
   976  	CrashTitle       string
   977  	CrashLogLink     string
   978  	CrashReportLink  string
   979  	LogLink          string
   980  	ErrorLink        string
   981  	ReproCLink       string
   982  	ReproSyzLink     string
   983  	Commit           *Commit   // for conclusive bisection
   984  	Commits          []*Commit // for inconclusive bisection
   985  	Reported         bool
   986  	InvalidatedBy    string
   987  	TreeOrigin       bool
   988  	OnMergeBase      bool
   989  }
   990  
   991  func (dash *Dashboard) Query(method string, req, reply interface{}) error {
   992  	if dash.logger != nil {
   993  		dash.logger("API(%v): %#v", method, req)
   994  	}
   995  	err := dash.queryImpl(method, req, reply)
   996  	if err != nil {
   997  		if dash.logger != nil {
   998  			dash.logger("API(%v): ERROR: %v", method, err)
   999  		}
  1000  		if dash.errorHandler != nil {
  1001  			dash.errorHandler(err)
  1002  		}
  1003  		return err
  1004  	}
  1005  	if dash.logger != nil {
  1006  		dash.logger("API(%v): REPLY: %#v", method, reply)
  1007  	}
  1008  	return nil
  1009  }
  1010  
  1011  func (dash *Dashboard) queryImpl(method string, req, reply interface{}) error {
  1012  	if reply != nil {
  1013  		// json decoding behavior is somewhat surprising
  1014  		// (see // https://github.com/golang/go/issues/21092).
  1015  		// To avoid any surprises, we zero the reply.
  1016  		typ := reflect.TypeOf(reply)
  1017  		if typ.Kind() != reflect.Ptr {
  1018  			return fmt.Errorf("resp must be a pointer")
  1019  		}
  1020  		reflect.ValueOf(reply).Elem().Set(reflect.New(typ.Elem()).Elem())
  1021  	}
  1022  	body := &bytes.Buffer{}
  1023  	mWriter := multipart.NewWriter(body)
  1024  	err := mWriter.WriteField("client", dash.Client)
  1025  	if err != nil {
  1026  		return err
  1027  	}
  1028  	err = mWriter.WriteField("key", dash.Key)
  1029  	if err != nil {
  1030  		return err
  1031  	}
  1032  	err = mWriter.WriteField("method", method)
  1033  	if err != nil {
  1034  		return err
  1035  	}
  1036  	if req != nil {
  1037  		w, err := mWriter.CreateFormField("payload")
  1038  		if err != nil {
  1039  			return err
  1040  		}
  1041  		gz := gzip.NewWriter(w)
  1042  		encoder := json.NewEncoder(gz)
  1043  		if err := encoder.Encode(req); err != nil {
  1044  			return fmt.Errorf("failed to marshal request: %w", err)
  1045  		}
  1046  		if err := gz.Close(); err != nil {
  1047  			return err
  1048  		}
  1049  	}
  1050  	mWriter.Close()
  1051  	r, err := dash.ctor("POST", fmt.Sprintf("%v/api", dash.Addr), body)
  1052  	if err != nil {
  1053  		return err
  1054  	}
  1055  	r.Header.Set("Content-Type", mWriter.FormDataContentType())
  1056  	resp, err := dash.doer(r)
  1057  	if err != nil {
  1058  		return fmt.Errorf("http request failed: %w", err)
  1059  	}
  1060  	defer resp.Body.Close()
  1061  	if resp.StatusCode != http.StatusOK {
  1062  		data, _ := io.ReadAll(resp.Body)
  1063  		return fmt.Errorf("request failed with %v: %s", resp.Status, data)
  1064  	}
  1065  	if reply != nil {
  1066  		if err := json.NewDecoder(resp.Body).Decode(reply); err != nil {
  1067  			return fmt.Errorf("failed to unmarshal response: %w", err)
  1068  		}
  1069  	}
  1070  	return nil
  1071  }
  1072  
  1073  type RecipientType int
  1074  
  1075  const (
  1076  	To RecipientType = iota
  1077  	Cc
  1078  )
  1079  
  1080  func (t RecipientType) String() string {
  1081  	return [...]string{"To", "Cc"}[t]
  1082  }
  1083  
  1084  type RecipientInfo struct {
  1085  	Address mail.Address
  1086  	Type    RecipientType
  1087  }
  1088  
  1089  type Recipients []RecipientInfo
  1090  
  1091  func (r Recipients) Len() int           { return len(r) }
  1092  func (r Recipients) Less(i, j int) bool { return r[i].Address.Address < r[j].Address.Address }
  1093  func (r Recipients) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }