feat: Improve DSL function UX #1295

dev
forgedhallpass 2021-11-26 17:14:25 +02:00
parent dfe284664c
commit c61ec5f673
2 changed files with 353 additions and 344 deletions

View File

@ -15,6 +15,7 @@ import (
"math/rand" "math/rand"
"net/url" "net/url"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
@ -26,369 +27,378 @@ import (
) )
const ( const (
numbers = "1234567890" numbers = "1234567890"
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
withCutSetArgsSize = 2
withBaseRandArgsSize = 3
withMaxRandArgsSize = withCutSetArgsSize
) )
var ErrDSLArguments = errors.New("invalid arguments provided to dsl") var invalidDslFunctionError = errors.New("invalid DSL function signature")
var invalidDslFunctionMessageTemplate = "correct method signature '%s'. %w"
var functions = map[string]govaluate.ExpressionFunction{ var dslFunctions map[string]dslFunction
"len": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, ErrDSLArguments
}
length := len(types.ToString(args[0]))
return float64(length), nil
},
"toupper": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, ErrDSLArguments
}
return strings.ToUpper(types.ToString(args[0])), nil
},
"tolower": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, ErrDSLArguments
}
return strings.ToLower(types.ToString(args[0])), nil
},
"replace": func(args ...interface{}) (interface{}, error) {
if len(args) != 3 {
return nil, ErrDSLArguments
}
return strings.ReplaceAll(types.ToString(args[0]), types.ToString(args[1]), types.ToString(args[2])), nil
},
"replace_regex": func(args ...interface{}) (interface{}, error) {
if len(args) != 3 {
return nil, ErrDSLArguments
}
compiled, err := regexp.Compile(types.ToString(args[1]))
if err != nil {
return nil, err
}
return compiled.ReplaceAllString(types.ToString(args[0]), types.ToString(args[2])), nil
},
"trim": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, ErrDSLArguments
}
return strings.Trim(types.ToString(args[0]), types.ToString(args[1])), nil
},
"trimleft": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, ErrDSLArguments
}
return strings.TrimLeft(types.ToString(args[0]), types.ToString(args[1])), nil
},
"trimright": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, ErrDSLArguments
}
return strings.TrimRight(types.ToString(args[0]), types.ToString(args[1])), nil
},
"trimspace": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, ErrDSLArguments
}
return strings.TrimSpace(types.ToString(args[0])), nil
},
"trimprefix": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, ErrDSLArguments
}
return strings.TrimPrefix(types.ToString(args[0]), types.ToString(args[1])), nil
},
"trimsuffix": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, ErrDSLArguments
}
return strings.TrimSuffix(types.ToString(args[0]), types.ToString(args[1])), nil
},
"reverse": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, ErrDSLArguments
}
return reverseString(types.ToString(args[0])), nil
},
// encoding
"base64": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, ErrDSLArguments
}
sEnc := base64.StdEncoding.EncodeToString([]byte(types.ToString(args[0])))
return sEnc, nil type dslFunction struct {
}, signature string
"gzip": func(args ...interface{}) (interface{}, error) { expressFunc govaluate.ExpressionFunction
if len(args) != 1 { }
return nil, ErrDSLArguments
}
buffer := &bytes.Buffer{}
writer := gzip.NewWriter(buffer)
if _, err := writer.Write([]byte(args[0].(string))); err != nil {
return "", err
}
_ = writer.Close()
return buffer.String(), nil func init() {
}, tempDslFunctions := map[string]func(string) dslFunction{
// python encodes to base64 with lines of 76 bytes terminated by new line "\n" "len": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
"base64_py": func(args ...interface{}) (interface{}, error) { length := len(types.ToString(args[0]))
if len(args) != 1 { return float64(length), nil
return nil, ErrDSLArguments }),
} "toupper": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
sEnc := base64.StdEncoding.EncodeToString([]byte(types.ToString(args[0]))) return strings.ToUpper(types.ToString(args[0])), nil
return deserialization.InsertInto(sEnc, 76, '\n'), nil }),
}, "tolower": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
"base64_decode": func(args ...interface{}) (interface{}, error) { return strings.ToLower(types.ToString(args[0])), nil
if len(args) != 1 { }),
return nil, ErrDSLArguments "replace": makeDslFunction(3, func(args ...interface{}) (interface{}, error) {
} return strings.ReplaceAll(types.ToString(args[0]), types.ToString(args[1]), types.ToString(args[2])), nil
return base64.StdEncoding.DecodeString(types.ToString(args[0])) }),
}, "replace_regex": makeDslFunction(3, func(args ...interface{}) (interface{}, error) {
"url_encode": func(args ...interface{}) (interface{}, error) { compiled, err := regexp.Compile(types.ToString(args[1]))
if len(args) != 1 { if err != nil {
return nil, ErrDSLArguments return nil, err
} }
return url.QueryEscape(types.ToString(args[0])), nil return compiled.ReplaceAllString(types.ToString(args[0]), types.ToString(args[2])), nil
}, }),
"url_decode": func(args ...interface{}) (interface{}, error) { "trim": makeDslFunction(2, func(args ...interface{}) (interface{}, error) {
if len(args) != 1 { return strings.Trim(types.ToString(args[0]), types.ToString(args[1])), nil
return nil, ErrDSLArguments }),
} "trimleft": makeDslFunction(2, func(args ...interface{}) (interface{}, error) {
return url.QueryUnescape(types.ToString(args[0])) return strings.TrimLeft(types.ToString(args[0]), types.ToString(args[1])), nil
}, }),
"hex_encode": func(args ...interface{}) (interface{}, error) { "trimright": makeDslFunction(2, func(args ...interface{}) (interface{}, error) {
if len(args) != 1 { return strings.TrimRight(types.ToString(args[0]), types.ToString(args[1])), nil
return nil, ErrDSLArguments }),
} "trimspace": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
return hex.EncodeToString([]byte(types.ToString(args[0]))), nil return strings.TrimSpace(types.ToString(args[0])), nil
}, }),
"hex_decode": func(args ...interface{}) (interface{}, error) { "trimprefix": makeDslFunction(2, func(args ...interface{}) (interface{}, error) {
if len(args) != 1 { return strings.TrimPrefix(types.ToString(args[0]), types.ToString(args[1])), nil
return nil, ErrDSLArguments }),
} "trimsuffix": makeDslFunction(2, func(args ...interface{}) (interface{}, error) {
hx, _ := hex.DecodeString(types.ToString(args[0])) return strings.TrimSuffix(types.ToString(args[0]), types.ToString(args[1])), nil
return string(hx), nil }),
}, "reverse": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
"html_escape": func(args ...interface{}) (interface{}, error) { return reverseString(types.ToString(args[0])), nil
if len(args) != 1 { }),
return nil, ErrDSLArguments "base64": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
} return base64.StdEncoding.EncodeToString([]byte(types.ToString(args[0]))), nil
return html.EscapeString(types.ToString(args[0])), nil }),
}, "gzip": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
"html_unescape": func(args ...interface{}) (interface{}, error) { buffer := &bytes.Buffer{}
if len(args) != 1 { writer := gzip.NewWriter(buffer)
return nil, ErrDSLArguments if _, err := writer.Write([]byte(args[0].(string))); err != nil {
} return "", err
return html.UnescapeString(types.ToString(args[0])), nil }
}, _ = writer.Close()
// hashing
"md5": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, ErrDSLArguments
}
hash := md5.Sum([]byte(types.ToString(args[0])))
return hex.EncodeToString(hash[:]), nil return buffer.String(), nil
}, }),
"sha256": func(args ...interface{}) (interface{}, error) { "base64_py": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
if len(args) != 1 { stdBase64 := base64.StdEncoding.EncodeToString([]byte(types.ToString(args[0])))
return nil, ErrDSLArguments return deserialization.InsertInto(stdBase64, 76, '\n'), nil
} }),
h := sha256.New() "base64_decode": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
if _, err := h.Write([]byte(types.ToString(args[0]))); err != nil { return base64.StdEncoding.DecodeString(types.ToString(args[0]))
return nil, err }),
} "url_encode": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
return hex.EncodeToString(h.Sum(nil)), nil return url.QueryEscape(types.ToString(args[0])), nil
}, }),
"sha1": func(args ...interface{}) (interface{}, error) { "url_decode": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
if len(args) != 1 { return url.QueryUnescape(types.ToString(args[0]))
return nil, ErrDSLArguments }),
} "hex_encode": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
h := sha1.New() return hex.EncodeToString([]byte(types.ToString(args[0]))), nil
if _, err := h.Write([]byte(types.ToString(args[0]))); err != nil { }),
return nil, err "hex_decode": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
} decodeString, err := hex.DecodeString(types.ToString(args[0]))
return hex.EncodeToString(h.Sum(nil)), nil return decodeString, err
}, }),
"mmh3": func(args ...interface{}) (interface{}, error) { "html_escape": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
if len(args) != 1 { return html.EscapeString(types.ToString(args[0])), nil
return nil, ErrDSLArguments }),
} "html_unescape": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
return fmt.Sprintf("%d", int32(murmur3.Sum32WithSeed([]byte(types.ToString(args[0])), 0))), nil return html.UnescapeString(types.ToString(args[0])), nil
}, }),
// search "md5": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
"contains": func(args ...interface{}) (interface{}, error) { hash := md5.Sum([]byte(types.ToString(args[0])))
if len(args) != 2 { return hex.EncodeToString(hash[:]), nil
return nil, ErrDSLArguments }),
} "sha256": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
return strings.Contains(types.ToString(args[0]), types.ToString(args[1])), nil hash := sha256.New()
}, if _, err := hash.Write([]byte(types.ToString(args[0]))); err != nil {
"regex": func(args ...interface{}) (interface{}, error) { return nil, err
if len(args) != 2 { }
return nil, ErrDSLArguments return hex.EncodeToString(hash.Sum(nil)), nil
} }),
compiled, err := regexp.Compile(types.ToString(args[0])) "sha1": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
if err != nil { hash := sha1.New()
return nil, err if _, err := hash.Write([]byte(types.ToString(args[0]))); err != nil {
} return nil, err
return compiled.MatchString(types.ToString(args[1])), nil }
}, return hex.EncodeToString(hash.Sum(nil)), nil
// random generators }),
"rand_char": func(args ...interface{}) (interface{}, error) { "mmh3": makeDslFunction(1, func(args ...interface{}) (interface{}, error) {
if len(args) != 2 { return fmt.Sprintf("%d", int32(murmur3.Sum32WithSeed([]byte(types.ToString(args[0])), 0))), nil
return nil, ErrDSLArguments }),
} "contains": makeDslFunction(2, func(args ...interface{}) (interface{}, error) {
chars := letters + numbers return strings.Contains(types.ToString(args[0]), types.ToString(args[1])), nil
bad := "" }),
if len(args) >= 1 { "regex": makeDslFunction(2, func(args ...interface{}) (interface{}, error) {
chars = types.ToString(args[0]) compiled, err := regexp.Compile(types.ToString(args[0]))
} if err != nil {
if len(args) >= withCutSetArgsSize { return nil, err
bad = types.ToString(args[1]) }
} return compiled.MatchString(types.ToString(args[1])), nil
chars = trimAll(chars, bad) }),
return chars[rand.Intn(len(chars))], nil "rand_char": makeDslWithOptionalArgsFunction(
}, "(optionalCharSet, optionalBachChars) string",
"rand_base": func(args ...interface{}) (interface{}, error) { func(args ...interface{}) (interface{}, error) {
if len(args) != 3 { charSet := letters + numbers
return nil, ErrDSLArguments badChars := ""
}
l := 0
bad := ""
base := letters + numbers
if len(args) >= 1 { argSize := len(args)
l = int(args[0].(float64)) if argSize != 1 && argSize != 2 {
} return nil, invalidDslFunctionError
if len(args) >= withCutSetArgsSize { }
bad = types.ToString(args[1])
}
if len(args) >= withBaseRandArgsSize {
base = types.ToString(args[2])
}
base = trimAll(base, bad)
return randSeq(base, l), nil
},
"rand_text_alphanumeric": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, ErrDSLArguments
}
l := 0
bad := ""
chars := letters + numbers
if len(args) >= 1 { if argSize >= 1 {
l = int(args[0].(float64)) charSet = types.ToString(args[0])
} }
if len(args) >= withCutSetArgsSize { if argSize == 2 {
bad = types.ToString(args[1]) badChars = types.ToString(args[1])
} }
chars = trimAll(chars, bad)
return randSeq(chars, l), nil
},
"rand_text_alpha": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, ErrDSLArguments
}
l := 0
bad := ""
chars := letters
if len(args) >= 1 { charSet = trimAll(charSet, badChars)
l = int(args[0].(float64)) return charSet[rand.Intn(len(charSet))], nil
} },
if len(args) >= withCutSetArgsSize { ),
bad = types.ToString(args[1]) "rand_base": makeDslWithOptionalArgsFunction(
} "(length, optionalCharSet, optionalBadChars) string",
chars = trimAll(chars, bad) func(args ...interface{}) (interface{}, error) {
return randSeq(chars, l), nil var length int
}, badChars := ""
"rand_text_numeric": func(args ...interface{}) (interface{}, error) { charSet := letters + numbers
if len(args) != 2 {
return nil, ErrDSLArguments
}
l := 0
bad := ""
chars := numbers
if len(args) >= 1 { argSize := len(args)
l = int(args[0].(float64)) if argSize < 1 || argSize > 3 {
} return nil, invalidDslFunctionError
if len(args) >= withCutSetArgsSize { }
bad = types.ToString(args[1])
}
chars = trimAll(chars, bad)
return randSeq(chars, l), nil
},
"rand_int": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, ErrDSLArguments
}
min := 0
max := math.MaxInt32
if len(args) >= 1 { length = int(args[0].(float64))
min = int(args[0].(float64))
}
if len(args) >= withMaxRandArgsSize {
max = int(args[1].(float64))
}
return rand.Intn(max-min) + min, nil
},
"unixtime": func(args ...interface{}) (interface{}, error) {
seconds := 0
if len(args) >= 1 {
seconds = int(args[0].(float64))
}
now := time.Now()
offset := now.Add(time.Duration(seconds) * time.Second)
return float64(offset.Unix()), nil
},
// Time Functions
"waitfor": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, ErrDSLArguments
}
seconds := args[0].(float64)
time.Sleep(time.Duration(seconds) * time.Second)
return true, nil
},
// deserialization Functions
"generate_java_gadget": func(args ...interface{}) (interface{}, error) {
if len(args) != 3 {
return nil, ErrDSLArguments
}
gadget := args[0].(string)
cmd := args[1].(string)
var encoding string if argSize >= 2 {
if len(args) > 2 { badChars = types.ToString(args[1])
encoding = args[2].(string) }
if argSize == 3 {
charSet = types.ToString(args[2])
}
charSet = trimAll(charSet, badChars)
return randSeq(charSet, length), nil
},
),
"rand_text_alphanumeric": makeDslWithOptionalArgsFunction(
"(length, optionalBadChars) string",
func(args ...interface{}) (interface{}, error) {
length := 0
badChars := ""
argSize := len(args)
if argSize != 1 && argSize != 2 {
return nil, invalidDslFunctionError
}
length = int(args[0].(float64))
if argSize == 2 {
badChars = types.ToString(args[1])
}
chars := trimAll(letters+numbers, badChars)
return randSeq(chars, length), nil
},
),
"rand_text_alpha": makeDslWithOptionalArgsFunction(
"(length, optionalBadChars) string",
func(args ...interface{}) (interface{}, error) {
var length int
badChars := ""
argSize := len(args)
if argSize != 1 && argSize != 2 {
return nil, invalidDslFunctionError
}
length = int(args[0].(float64))
if argSize == 2 {
badChars = types.ToString(args[1])
}
chars := trimAll(letters, badChars)
return randSeq(chars, length), nil
},
),
"rand_text_numeric": makeDslWithOptionalArgsFunction(
"(size int, optionalBadNumbers string) string",
func(args ...interface{}) (interface{}, error) {
argSize := len(args)
if argSize != 1 && argSize != 2 {
return nil, invalidDslFunctionError
}
length := args[0].(int)
var badNumbers = ""
if argSize == 2 {
badNumbers = types.ToString(args[1])
}
chars := trimAll(numbers, badNumbers)
return randSeq(chars, length), nil
},
),
"rand_int": makeDslWithOptionalArgsFunction(
"(optionalMin, optionalMax int) int",
func(args ...interface{}) (interface{}, error) {
argSize := len(args)
if argSize >= 2 {
return nil, invalidDslFunctionError
}
min := 0
max := math.MaxInt32
if argSize >= 1 {
min = args[0].(int)
}
if argSize == 2 {
max = args[1].(int)
}
return rand.Intn(max-min) + min, nil
},
),
"generate_java_gadget": makeDslFunction(3, func(args ...interface{}) (interface{}, error) {
gadget := args[0].(string)
cmd := args[1].(string)
encoding := args[2].(string)
data := deserialization.GenerateJavaGadget(gadget, cmd, encoding)
return data, nil
}),
"unixtime": makeDslWithOptionalArgsFunction(
"(optionalSeconds uint) float64",
func(args ...interface{}) (interface{}, error) {
seconds := 0
argSize := len(args)
if argSize != 0 && argSize != 1 {
return nil, invalidDslFunctionError
} else if argSize == 1 {
seconds = int(args[0].(uint))
}
offset := time.Now().Add(time.Duration(seconds) * time.Second)
return float64(offset.Unix()), nil
},
),
"waitfor": makeDslWithOptionalArgsFunction(
"(seconds uint)",
func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, invalidDslFunctionError
}
seconds := args[0].(uint)
time.Sleep(time.Duration(seconds) * time.Second)
return true, nil
},
),
"print_debug": makeDslWithOptionalArgsFunction(
"(args ...interface{})",
func(args ...interface{}) (interface{}, error) {
if len(args) < 1 {
return nil, invalidDslFunctionError
}
gologger.Info().Msgf("print_debug value: %s", fmt.Sprint(args))
return true, nil
},
),
"time_now": makeDslWithOptionalArgsFunction(
"() float64",
func(args ...interface{}) (interface{}, error) {
if len(args) == 0 {
return nil, invalidDslFunctionError
}
return float64(time.Now().Unix()), nil
},
),
}
dslFunctions = make(map[string]dslFunction, len(tempDslFunctions))
for funcName, dslFunc := range tempDslFunctions {
dslFunctions[funcName] = dslFunc(funcName)
}
}
func createSignaturePart(numberOfParameters int) string {
params := make([]string, 0, numberOfParameters)
for i := 1; i <= numberOfParameters; i++ {
params = append(params, "arg"+strconv.Itoa(i))
}
return fmt.Sprintf("(%s interface{}) interface{}", strings.Join(params, ", "))
}
func makeDslWithOptionalArgsFunction(signaturePart string, dslFunctionLogic govaluate.ExpressionFunction) func(functionName string) dslFunction {
return func(functionName string) dslFunction {
return dslFunction{
functionName + signaturePart,
dslFunctionLogic,
} }
data := deserialization.GenerateJavaGadget(gadget, cmd, encoding) }
return data, nil }
},
// for debug purposes func makeDslFunction(numberOfParameters int, dslFunctionLogic govaluate.ExpressionFunction) func(functionName string) dslFunction {
"print_debug": func(args ...interface{}) (interface{}, error) { return func(functionName string) dslFunction {
gologger.Info().Msgf("print_debug value: %s", fmt.Sprint(args)) signature := functionName + createSignaturePart(numberOfParameters)
return true, nil return dslFunction{
}, signature,
func(args ...interface{}) (interface{}, error) {
if len(args) != numberOfParameters {
return nil, fmt.Errorf(invalidDslFunctionMessageTemplate, signature, invalidDslFunctionError)
}
return dslFunctionLogic(args...)
},
}
}
} }
// HelperFunctions returns the dsl helper functions // HelperFunctions returns the dsl helper functions
func HelperFunctions() map[string]govaluate.ExpressionFunction { func HelperFunctions() map[string]govaluate.ExpressionFunction {
return functions helperFunctions := make(map[string]govaluate.ExpressionFunction, len(dslFunctions))
for functionName, dslFunction := range dslFunctions {
helperFunctions[functionName] = dslFunction.expressFunc
}
return helperFunctions
}
func GetDslFunctionSignatures() []string {
result := make([]string, 0, len(dslFunctions))
for _, dslFunction := range dslFunctions {
result = append(result, dslFunction.signature)
}
return result
} }
// AddHelperFunction allows creation of additional helper functions to be supported with templates // AddHelperFunction allows creation of additional helper functions to be supported with templates
func AddHelperFunction(key string, value func(args ...interface{}) (interface{}, error)) error { func AddHelperFunction(key string, value func(args ...interface{}) (interface{}, error)) error {
if _, ok := functions[key]; !ok { if _, ok := dslFunctions[key]; !ok {
functions[key] = value dslFunction := dslFunctions[key]
dslFunction.signature = "(args ...interface{}) interface{}"
dslFunction.expressFunc = value
return nil return nil
} }
return errors.New("duplicate helper function key defined") return errors.New("duplicate helper function key defined")

View File

@ -36,7 +36,6 @@ func (m *Matcher) CompileMatchers() error {
m.Part = "body" m.Part = "body"
} }
// Compile the regexes // Compile the regexes
for _, regex := range m.Regex { for _, regex := range m.Regex {
compiled, err := regexp.Compile(regex) compiled, err := regexp.Compile(regex)
@ -59,7 +58,7 @@ func (m *Matcher) CompileMatchers() error {
for _, expr := range m.DSL { for _, expr := range m.DSL {
compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expr, dsl.HelperFunctions()) compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expr, dsl.HelperFunctions())
if err != nil { if err != nil {
return fmt.Errorf("could not compile dsl: %s", expr) return fmt.Errorf("could not compile dsl: %s. %w", expr, err)
} }
m.dslCompiled = append(m.dslCompiled, compiled) m.dslCompiled = append(m.dslCompiled, compiled)
} }