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  }