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