18F/e-QIP-prototype

View on GitHub
api/cmd/fuzzer/fuzz.go

Summary

Maintainability
A
0 mins
Test Coverage
package main

import (
    "encoding/json"
    "math/rand"
    "time"

    "github.com/18F/e-QIP-prototype/api"
)

// Map is a map of JSON structures.
type Map map[string]json.RawMessage

// Array is an array of JSON structures.
type Array []json.RawMessage

// Fuzzer is a simple fuzzer implementation.
type Fuzzer struct {
    r         *rand.Rand
    errorRate float64
}

// NewFuzzer returns a configured `Fuzzer`.
func NewFuzzer() Fuzzer {
    s := rand.NewSource(time.Now().UnixNano())
    r := rand.New(s)
    return Fuzzer{
        r: r,
    }
}

// ErrorRate sets the rate of errors.
func (fuzzer *Fuzzer) ErrorRate(rate float64) {
    fuzzer.errorRate = rate
}

// WalkJSON will walk a JSON structure returning the potentially altered structure.
func (fuzzer Fuzzer) WalkJSON(raw json.RawMessage) json.RawMessage {
    if raw[0] == 123 {
        var content Map
        json.Unmarshal(raw, &content)
        for k, v := range content {
            if mutable(v) {
                content[k] = fuzzer.Mutate(v)
            } else {
                // Continue walking the JSON if it cannot be fuzzed
                content[k] = fuzzer.WalkJSON(v)
            }
        }
        raw = js(content)
    } else if raw[0] == 91 {
        var content Array
        json.Unmarshal(raw, &content)
        for i, v := range content {
            if mutable(v) {
                content[i] = fuzzer.Mutate(v)
            } else {
                // Continue walking the JSON if it cannot be fuzzed
                content[i] = fuzzer.WalkJSON(v)
            }
        }
        raw = js(content)
    } else {
        // NOTE: If we care about primitives then uncomment the following.
        // var val interface{}
        // json.Unmarshal(raw, &val)
        // switch v := val.(type) {
        // case float64:
        // case string:
        // case bool:
        // case nil:
        // default:
        // }
    }
    return raw
}

// Mutate will fuzz a JSON structure.
func (fuzzer Fuzzer) Mutate(raw json.RawMessage) json.RawMessage {
    payload := &api.Payload{}
    if err := payload.Unmarshal(raw); err != nil {
        return raw
    }

    // If there is a fuzzer defined for this type of payload then
    // we will execute and return the results from it.
    f, ok := fuzzers[payload.Type]
    if !ok {
        return raw
    }

    err := f(payload, fuzzer.errorRate, fuzzer.r)
    if err != nil {
        return raw
    }

    js, _ := json.Marshal(payload)
    return js
}

func mutable(raw json.RawMessage) bool {
    payload := &api.Payload{}
    if err := payload.Unmarshal(raw); err != nil {
        return false
    }

    _, ok := fuzzers[payload.Type]
    return ok
}

func js(val interface{}) json.RawMessage {
    marshalled, _ := json.Marshal(val)
    return marshalled
}

var fuzzers = map[string]func(payload *api.Payload, errorRate float64, r *rand.Rand) error{
    "branch":             fuzzBranch,
    "checkbox":           fuzzCheckbox,
    "checkboxgroup":      fuzzCheckboxGroup,
    "country":            fuzzCountry,
    "datecontrol":        fuzzDateControl,
    "email":              fuzzEmail,
    "employmentactivity": fuzzEmploymentActivity,
    "height":             fuzzHeight,
    "location":           fuzzLocation,
    "name":               fuzzName,
    "notapplicable":      fuzzNotApplicable,
    "number":             fuzzNumber,
    "radio":              fuzzRadio,
    "ssn":                fuzzSSN,
    "telephone":          fuzzTelephone,
    "text":               fuzzText,
    "textarea":           fuzzTextarea,
}

func fuzzBranch(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Branch{}
    if randomBool(r) {
        props.Value = "Yes"
    } else {
        props.Value = "No"
    }
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzCheckbox(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Checkbox{}
    if randomBool(r) {
        props.Value = "Checked"
        props.Checked = true
    } else {
        props.Value = "Unchecked"
        props.Checked = false
    }
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzCheckboxGroup(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.CheckboxGroup{}
    props.Values = []string{"One", "Two"}
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzCountry(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Country{}
    props.Value = []string{"United States"}
    props.Comments = ""
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzDateControl(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.DateControl{}
    props.Month = "1"
    props.Day = "1"
    props.Year = "2000"
    props.Estimated = randomBool(r)
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzEmail(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Email{}
    props.Value = "john.doe@dummy.gov"
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzEmploymentActivity(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.EmploymentActivity{}
    props.Value = "Other"
    props.OtherExplanation = "IDK"
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzHeight(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Height{}
    props.Feet = 6
    props.Inches = 11
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzLocation(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Location{}
    props.Layout = api.LayoutAddress
    props.Street1 = "123 main st"
    props.City = "springfield"
    props.State = "il"
    props.Zipcode = "12345"
    props.Country = "United States"
    props.Validated = true
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzName(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Name{}
    props.First = "John"
    props.FirstInitialOnly = false
    props.Middle = "Smith"
    props.MiddleInitialOnly = false
    props.NoMiddleName = false
    props.Last = "Doe"
    props.Suffix = ""
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzNotApplicable(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.NotApplicable{}
    props.Applicable = false
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzNumber(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Number{}
    props.Value = "1000"
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzRadio(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Radio{}
    props.Value = "Radio1"
    props.Checked = true
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzSSN(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.SSN{}
    props.First = "123"
    props.Middle = "12"
    props.Last = "123"
    props.NotApplicable = false
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzTelephone(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Telephone{}
    props.TimeOfDay = "Morning"
    props.Type = "Domestic"
    props.NumberType = "Cell"
    props.Number = "123-123-1234"
    props.Extension = ""
    props.NoNumber = false
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzText(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Text{}
    props.Value = randomString(r, 20)
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func fuzzTextarea(payload *api.Payload, errorRate float64, r *rand.Rand) error {
    if payload == nil {
        return nil
    }

    props := &api.Textarea{}
    props.Value = randomString(r, 255)
    js, _ := json.Marshal(props)
    payload.Props = js
    return nil
}

func randomBool(r *rand.Rand) bool {
    if r.Int()&1 == 1 {
        return true
    }
    return false
}

type charRange struct {
    first, last rune
}

func (r *charRange) choose(rand *rand.Rand) rune {
    count := int64(r.last - r.first)
    return r.first + rune(rand.Int63n(count))
}

var unicodeRanges = []charRange{
    {' ', '~'},
    // {'\u00a0', '\u02af'},
    // {'\u4e00', '\u9fff'},
}

func randomString(r *rand.Rand, maxLength int) string {
    n := r.Intn(maxLength)
    runes := make([]rune, n)
    for i := range runes {
        runes[i] = unicodeRanges[r.Intn(len(unicodeRanges))].choose(r)
    }
    return string(runes)
}

func randomInt(r *rand.Rand) int {
    return int(r.Uint32())<<32 | int(r.Uint32())
}