gitea

Development moved to Codeberg

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
  116. 116
  117. 117
  118. 118
  119. 119
  120. 120
  121. 121
  122. 122
  123. 123
  124. 124
  125. 125
  126. 126
  127. 127
  128. 128
  129. 129
  130. 130
  131. 131
  132. 132
  133. 133
  134. 134
  135. 135
  136. 136
  137. 137
  138. 138
  139. 139
  140. 140
  141. 141
  142. 142
  143. 143
  144. 144
  145. 145
  146. 146
  147. 147
  148. 148
  149. 149
  150. 150
  151. 151
  152. 152
  153. 153
  154. 154
  155. 155
  156. 156
  157. 157
  158. 158
  159. 159
  160. 160
  161. 161
//  Copyright (c) 2016 Marty Schoch

//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the
//  License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing,
//  software distributed under the License is distributed on an "AS
//  IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//  express or implied. See the License for the specific language
//  governing permissions and limitations under the License.

package smat

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
)

// Logger is a configurable logger used by this package
// by default output is discarded
var Logger = log.New(ioutil.Discard, "smat ", log.LstdFlags)

// Context is a container for any user state
type Context interface{}

// State is a function which describes which action to perform in the event
// that a particular byte is seen
type State func(next byte) ActionID

// PercentAction describes the frequency with which an action should occur
// for example: Action{Percent:10, Action:DonateMoney} means that 10% of
// the time you should donate money.
type PercentAction struct {
	Percent int
	Action  ActionID
}

// Action is any function which returns the next state to transition to
// it can optionally mutate the provided context object
// if any error occurs, it may return an error which will abort execution
type Action func(Context) (State, error)

// ActionID is a unique identifier for an action
type ActionID int

// NopAction does nothing and simply continues to the next input
var NopAction ActionID = -1

// ActionMap is a mapping form ActionID to Action
type ActionMap map[ActionID]Action

func (a ActionMap) findSetupTeardown(setup, teardown ActionID) (Action, Action, error) {
	setupFunc, ok := a[setup]
	if !ok {
		return nil, nil, ErrSetupMissing
	}
	teardownFunc, ok := a[teardown]
	if !ok {
		return nil, nil, ErrTeardownMissing
	}
	return setupFunc, teardownFunc, nil
}

// Fuzz runs the fuzzing state machine with the provided context
// first, the setup action is executed unconditionally
// the start state is determined by this action
// actionMap is a lookup table for all actions
// the data byte slice determines all future state transitions
// finally, the teardown action is executed unconditionally for cleanup
func Fuzz(ctx Context, setup, teardown ActionID, actionMap ActionMap, data []byte) int {
	reader := bytes.NewReader(data)
	err := runReader(ctx, setup, teardown, actionMap, reader, nil)
	if err != nil {
		panic(err)
	}
	return 1
}

// Longevity runs the state machine with the provided context
// first, the setup action is executed unconditionally
// the start state is determined by this action
// actionMap is a lookup table for all actions
// random bytes are generated to determine all future state transitions
// finally, the teardown action is executed unconditionally for cleanup
func Longevity(ctx Context, setup, teardown ActionID, actionMap ActionMap, seed int64, closeChan chan struct{}) error {
	source := rand.NewSource(seed)
	return runReader(ctx, setup, teardown, actionMap, rand.New(source), closeChan)
}

var (
	// ErrSetupMissing is returned when the setup action cannot be found
	ErrSetupMissing = fmt.Errorf("setup action missing")
	// ErrTeardownMissing is returned when the teardown action cannot be found
	ErrTeardownMissing = fmt.Errorf("teardown action missing")
	// ErrClosed is returned when the closeChan was closed to cancel the op
	ErrClosed = fmt.Errorf("closed")
	// ErrActionNotPossible is returned when an action is encountered in a
	// FuzzCase that is not possible in the current state
	ErrActionNotPossible = fmt.Errorf("action not possible in state")
)

func runReader(ctx Context, setup, teardown ActionID, actionMap ActionMap, r io.Reader, closeChan chan struct{}) error {
	setupFunc, teardownFunc, err := actionMap.findSetupTeardown(setup, teardown)
	if err != nil {
		return err
	}
	Logger.Printf("invoking setup action")
	state, err := setupFunc(ctx)
	if err != nil {
		return err
	}
	defer func() {
		Logger.Printf("invoking teardown action")
		_, _ = teardownFunc(ctx)
	}()

	reader := bufio.NewReader(r)
	for next, err := reader.ReadByte(); err == nil; next, err = reader.ReadByte() {
		select {
		case <-closeChan:
			return ErrClosed
		default:
			actionID := state(next)
			action, ok := actionMap[actionID]
			if !ok {
				Logger.Printf("no such action defined, continuing")
				continue
			}
			Logger.Printf("invoking action - %d", actionID)
			state, err = action(ctx)
			if err != nil {
				Logger.Printf("it was action %d that returned err %v", actionID, err)
				return err
			}
		}
	}
	return err
}

// PercentExecute interprets the next byte as a random value and normalizes it
// to values 0-99, it then looks to see which action should be execued based
// on the action distributions
func PercentExecute(next byte, pas ...PercentAction) ActionID {
	percent := int(99 * int(next) / 255)

	sofar := 0
	for _, pa := range pas {
		sofar = sofar + pa.Percent
		if percent < sofar {
			return pa.Action
		}

	}
	return NopAction
}