github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/pkg/controller/api.go (about) 1 // Copyright 2024 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 controller provides the server part of the *api.Client interface. 5 // nolint: dupl 6 package controller 7 8 import ( 9 "errors" 10 "fmt" 11 "net/http" 12 13 "github.com/google/syzkaller/syz-cluster/pkg/api" 14 "github.com/google/syzkaller/syz-cluster/pkg/app" 15 "github.com/google/syzkaller/syz-cluster/pkg/service" 16 ) 17 18 type APIServer struct { 19 seriesService *service.SeriesService 20 sessionService *service.SessionService 21 buildService *service.BuildService 22 testService *service.SessionTestService 23 findingService *service.FindingService 24 baseFindingService *service.BaseFindingService 25 } 26 27 func NewAPIServer(env *app.AppEnvironment) *APIServer { 28 return &APIServer{ 29 seriesService: service.NewSeriesService(env), 30 sessionService: service.NewSessionService(env), 31 buildService: service.NewBuildService(env), 32 testService: service.NewSessionTestService(env), 33 findingService: service.NewFindingService(env), 34 baseFindingService: service.NewBaseFindingService(env), 35 } 36 } 37 38 func (c APIServer) Mux() *http.ServeMux { 39 mux := http.NewServeMux() 40 mux.HandleFunc("/builds/last", c.getLastBuild) 41 mux.HandleFunc("/builds/upload", c.uploadBuild) 42 mux.HandleFunc("/findings/upload", c.uploadFinding) 43 mux.HandleFunc("/series/upload", c.uploadSeries) 44 mux.HandleFunc("/series/{series_id}", c.getSeries) 45 mux.HandleFunc("/sessions/upload", c.uploadSession) 46 mux.HandleFunc("/sessions/{session_id}/series", c.getSessionSeries) 47 mux.HandleFunc("/sessions/{session_id}/triage_result", c.triageResult) 48 mux.HandleFunc("/tests/upload_artifacts", c.uploadTestArtifact) 49 mux.HandleFunc("/tests/upload", c.uploadTest) 50 mux.HandleFunc("/trees", c.getTrees) 51 mux.HandleFunc("/base_findings/upload", c.uploadBaseFinding) 52 mux.HandleFunc("/base_findings/status", c.baseFindingStatus) 53 return mux 54 } 55 56 func (c APIServer) getSessionSeries(w http.ResponseWriter, r *http.Request) { 57 resp, err := c.seriesService.GetSessionSeries(r.Context(), r.PathValue("session_id")) 58 if err == service.ErrSeriesNotFound || err == service.ErrSessionNotFound { 59 http.Error(w, fmt.Sprint(err), http.StatusNotFound) 60 return 61 } else if err != nil { 62 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 63 return 64 } 65 api.ReplyJSON(w, resp) 66 } 67 68 func (c APIServer) triageResult(w http.ResponseWriter, r *http.Request) { 69 req := api.ParseJSON[api.UploadTriageResultReq](w, r) 70 if req == nil { 71 return 72 } 73 err := c.sessionService.TriageResult(r.Context(), r.PathValue("session_id"), req) 74 if errors.Is(err, service.ErrSessionNotFound) { 75 http.Error(w, fmt.Sprint(err), http.StatusNotFound) 76 return 77 } else if err != nil { 78 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 79 return 80 } 81 api.ReplyJSON[interface{}](w, nil) 82 } 83 84 func (c APIServer) getSeries(w http.ResponseWriter, r *http.Request) { 85 resp, err := c.seriesService.GetSeries(r.Context(), r.PathValue("series_id")) 86 if errors.Is(err, service.ErrSeriesNotFound) { 87 http.Error(w, fmt.Sprint(err), http.StatusNotFound) 88 return 89 } else if err != nil { 90 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 91 return 92 } 93 api.ReplyJSON(w, resp) 94 } 95 96 func (c APIServer) uploadBuild(w http.ResponseWriter, r *http.Request) { 97 req := api.ParseJSON[api.UploadBuildReq](w, r) 98 if req == nil { 99 return 100 } 101 resp, err := c.buildService.Upload(r.Context(), req) 102 if err != nil { 103 // TODO: sometimes it's not StatusInternalServerError. 104 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 105 return 106 } 107 api.ReplyJSON(w, resp) 108 } 109 110 func (c APIServer) uploadTest(w http.ResponseWriter, r *http.Request) { 111 req := api.ParseJSON[api.TestResult](w, r) 112 if req == nil { 113 return 114 } 115 // TODO: add parameters validation (and also of the Log size). 116 err := c.testService.Save(r.Context(), req) 117 if err != nil { 118 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 119 return 120 } 121 api.ReplyJSON[interface{}](w, nil) 122 } 123 124 func (c APIServer) uploadTestArtifact(w http.ResponseWriter, r *http.Request) { 125 const maxMemory = 16 * 1000 * 1000 // 16 MB. 126 if err := r.ParseMultipartForm(maxMemory); err != nil { 127 http.Error(w, "could not parse the multipart form", http.StatusBadRequest) 128 return 129 } 130 defer r.MultipartForm.RemoveAll() 131 132 file, _, err := r.FormFile("content") 133 if err != nil { 134 if err == http.ErrMissingFile { 135 http.Error(w, "the 'content' file must be present", http.StatusBadRequest) 136 return 137 } 138 http.Error(w, fmt.Sprintf("failed to query the file: %s", err), http.StatusInternalServerError) 139 return 140 } 141 defer file.Close() 142 143 err = c.testService.SaveArtifacts(r.Context(), 144 r.FormValue("session"), 145 r.FormValue("test"), 146 file) 147 if err != nil { 148 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 149 return 150 } 151 api.ReplyJSON[interface{}](w, nil) 152 } 153 154 func (c APIServer) uploadFinding(w http.ResponseWriter, r *http.Request) { 155 req := api.ParseJSON[api.NewFinding](w, r) 156 if req == nil { 157 return 158 } 159 // TODO: add parameters validation. 160 err := c.findingService.Save(r.Context(), req) 161 if err != nil { 162 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 163 return 164 } 165 api.ReplyJSON[interface{}](w, nil) 166 } 167 168 func (c APIServer) getLastBuild(w http.ResponseWriter, r *http.Request) { 169 req := api.ParseJSON[api.LastBuildReq](w, r) 170 if req == nil { 171 return 172 } 173 resp, err := c.buildService.LastBuild(r.Context(), req) 174 if err != nil { 175 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 176 return 177 } 178 api.ReplyJSON[*api.Build](w, resp) 179 } 180 181 func (c APIServer) uploadSeries(w http.ResponseWriter, r *http.Request) { 182 req := api.ParseJSON[api.Series](w, r) 183 if req == nil { 184 return 185 } 186 resp, err := c.seriesService.UploadSeries(r.Context(), req) 187 if err != nil { 188 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 189 return 190 } 191 api.ReplyJSON[*api.UploadSeriesResp](w, resp) 192 } 193 194 func (c APIServer) uploadSession(w http.ResponseWriter, r *http.Request) { 195 req := api.ParseJSON[api.NewSession](w, r) 196 if req == nil { 197 return 198 } 199 resp, err := c.sessionService.UploadSession(r.Context(), req) 200 if err != nil { 201 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 202 return 203 } 204 api.ReplyJSON[*api.UploadSessionResp](w, resp) 205 } 206 207 func (c APIServer) getTrees(w http.ResponseWriter, r *http.Request) { 208 api.ReplyJSON(w, &api.TreesResp{ 209 Trees: api.DefaultTrees, 210 FuzzTargets: api.FuzzTargets, 211 }) 212 } 213 214 func (c APIServer) uploadBaseFinding(w http.ResponseWriter, r *http.Request) { 215 req := api.ParseJSON[api.BaseFindingInfo](w, r) 216 if req == nil { 217 return 218 } 219 err := c.baseFindingService.Upload(r.Context(), req) 220 if errors.Is(err, service.ErrBuildNotFound) { 221 http.Error(w, fmt.Sprint(err), http.StatusNotFound) 222 return 223 } else if err != nil { 224 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 225 return 226 } 227 api.ReplyJSON[interface{}](w, nil) 228 } 229 230 func (c APIServer) baseFindingStatus(w http.ResponseWriter, r *http.Request) { 231 req := api.ParseJSON[api.BaseFindingInfo](w, r) 232 if req == nil { 233 return 234 } 235 resp, err := c.baseFindingService.Status(r.Context(), req) 236 if err != nil { 237 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 238 return 239 } 240 api.ReplyJSON[*api.BaseFindingStatus](w, resp) 241 }