code-intelligence.com/cifuzz@v0.40.0/internal/api/campaign_run.go (about) 1 package api 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/base64" 7 "encoding/hex" 8 "encoding/json" 9 "fmt" 10 "net/url" 11 "time" 12 13 "github.com/pkg/errors" 14 15 "code-intelligence.com/cifuzz/internal/config" 16 "code-intelligence.com/cifuzz/pkg/log" 17 "code-intelligence.com/cifuzz/pkg/report" 18 ) 19 20 type CampaignRunBody struct { 21 CampaignRun CampaignRun `json:"campaign_run"` 22 } 23 24 type CampaignRun struct { 25 Name string `json:"name"` 26 DisplayName string `json:"display_name"` 27 Campaign Campaign `json:"campaign"` 28 Runs []FuzzingRun `json:"runs"` 29 Status string `json:"status"` 30 Timestamp string `json:"timestamp"` 31 } 32 33 type Campaign struct { 34 MaxRunTime string `json:"max_run_time"` 35 } 36 37 // CreateCampaignRun creates a new campaign run for the given project and 38 // returns the name of the campaign and fuzzing run. The campaign and fuzzing 39 // run name is used to identify the campaign run in the API for consecutive 40 // calls. 41 func (client *APIClient) CreateCampaignRun(project string, token string, fuzzTarget string, buildSystem string, firstMetrics *report.FuzzingMetric, lastMetrics *report.FuzzingMetric) (string, string, error) { 42 fuzzTargetId := base64.URLEncoding.EncodeToString([]byte(fuzzTarget)) 43 44 // generate a short random string to use as the campaign run name 45 randBytes := make([]byte, 8) 46 _, err := rand.Read(randBytes) 47 if err != nil { 48 return "", "", errors.WithStack(err) 49 } 50 51 fuzzingRunName, err := url.JoinPath(project, "fuzzing_runs", fmt.Sprintf("cifuzz-fuzzing-run-%s", hex.EncodeToString(randBytes))) 52 if err != nil { 53 return "", "", err 54 } 55 fuzzTargetConfigName, err := url.JoinPath(project, "fuzz_targets", fuzzTargetId) 56 if err != nil { 57 return "", "", err 58 } 59 60 // FIXME: We don't have metrics except for the first run. Successive runs 61 // will reuse the corpus and inputs from the previous run and thus will not 62 // generate new metrics 63 var metricsList []*Metrics 64 // add metrics if available 65 if firstMetrics != nil && lastMetrics != nil { 66 metricsDuration := lastMetrics.Timestamp.Sub(firstMetrics.Timestamp) 67 execs := lastMetrics.TotalExecutions - firstMetrics.TotalExecutions 68 performance := int32(float64(execs) / (float64(metricsDuration.Milliseconds()) / 1000)) 69 70 metricsList = []*Metrics{ 71 { 72 Timestamp: lastMetrics.Timestamp.Format(time.RFC3339), 73 ExecutionsPerSecond: performance, 74 Features: lastMetrics.Features, 75 CorpusSize: lastMetrics.CorpusSize, 76 SecondsSinceLastCoverage: fmt.Sprintf("%d", lastMetrics.SecondsSinceLastFeature), 77 TotalExecutions: fmt.Sprintf("%d", lastMetrics.TotalExecutions), 78 Edges: lastMetrics.Edges, 79 SecondsSinceLastEdge: fmt.Sprintf("%d", lastMetrics.SecondsSinceLastEdge), 80 }, 81 { 82 Timestamp: firstMetrics.Timestamp.Format(time.RFC3339), 83 ExecutionsPerSecond: performance, 84 Features: firstMetrics.Features, 85 CorpusSize: firstMetrics.CorpusSize, 86 SecondsSinceLastCoverage: fmt.Sprintf("%d", firstMetrics.SecondsSinceLastFeature), 87 TotalExecutions: fmt.Sprintf("%d", firstMetrics.TotalExecutions), 88 Edges: firstMetrics.Edges, 89 SecondsSinceLastEdge: fmt.Sprintf("%d", firstMetrics.SecondsSinceLastEdge), 90 }, 91 } 92 } 93 94 apiFuzzTarget := APIFuzzTarget{ 95 RelativePath: fuzzTarget, 96 } 97 fuzzTargetConfig := FuzzTargetConfig{ 98 Name: fuzzTargetConfigName, 99 DisplayName: fuzzTarget, 100 } 101 switch buildSystem { 102 case config.BuildSystemBazel, config.BuildSystemCMake, config.BuildSystemOther: 103 fuzzTargetConfig.CAPIFuzzTarget = &CAPIFuzzTarget{APIFuzzTarget: apiFuzzTarget} 104 case config.BuildSystemMaven, config.BuildSystemGradle: 105 fuzzTargetConfig.JavaAPIFuzzTarget = &JavaAPIFuzzTarget{APIFuzzTarget: apiFuzzTarget} 106 default: 107 return "", "", errors.Errorf("Unsupported build system: %s", buildSystem) 108 } 109 110 fuzzingRun := FuzzingRun{ 111 Name: fuzzingRunName, 112 DisplayName: "cifuzz-fuzzing-run", 113 Status: "SUCCEEDED", 114 FuzzerRunConfigurations: FuzzerRunConfigurations{ 115 Engine: "LIBFUZZER", 116 NumberOfJobs: 4, 117 }, 118 Metrics: metricsList, 119 FuzzTargetConfig: fuzzTargetConfig, 120 } 121 122 campaignRunName, err := url.JoinPath(project, "campaign_runs", fmt.Sprintf("cifuzz-campaign-run-%s", hex.EncodeToString(randBytes))) 123 if err != nil { 124 return "", "", err 125 } 126 campaignRun := CampaignRun{ 127 Name: campaignRunName, 128 DisplayName: "cifuzz-campaign-run", 129 Campaign: Campaign{ 130 MaxRunTime: "120s", 131 }, 132 Runs: []FuzzingRun{fuzzingRun}, 133 Status: "SUCCEEDED", 134 Timestamp: time.Now().Format("2006-01-02T15:04:05.999999999Z07:00"), 135 } 136 campaignRunBody := &CampaignRunBody{ 137 CampaignRun: campaignRun, 138 } 139 140 body, err := json.Marshal(campaignRunBody) 141 if err != nil { 142 return "", "", errors.WithStack(err) 143 } 144 145 log.Debugf("Creating campaign run: %s\n", string(body)) 146 147 url, err := url.JoinPath("/v1", project, "campaign_runs") 148 if err != nil { 149 return "", "", err 150 } 151 resp, err := client.sendRequest("POST", url, bytes.NewReader(body), token) 152 if err != nil { 153 return "", "", err 154 } 155 defer resp.Body.Close() 156 157 if resp.StatusCode != 200 { 158 return "", "", responseToAPIError(resp) 159 } 160 161 return campaignRun.Name, fuzzingRun.Name, nil 162 }