This commit is contained in:
insanity 2018-08-13 16:48:32 +09:00
commit 1ad0793699
87 changed files with 11972 additions and 0 deletions

68
.gitignore vendored Normal file
View File

@ -0,0 +1,68 @@
# Created by .ignore support plugin (hsz.mobi)
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Go template
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
.idea/
*.iml
vendor/
glide.lock
.DS_Store
dist/
debug

42
Gopkg.lock generated Normal file
View File

@ -0,0 +1,42 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "git.loafle.net/commons/service_matcher-go"
packages = [
".",
"snmp"
]
revision = "a804b4824f2e65ab70a706ae62aa452b6f4fb9b6"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
name = "gopkg.in/asn1-ber.v1"
packages = ["."]
revision = "379148ca0225df7a432012b8df0355c2a2063ac0"
version = "v1.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "e411f01b744d615451da86fca1cda7559ac20eb10b59f76fe97f185639b1a788"
solver-name = "gps-cdcl"
solver-version = 1

42
Gopkg.toml Normal file
View File

@ -0,0 +1,42 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
branch = "master"
name = "git.loafle.net/commons/service_matcher-go"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.2"
[[constraint]]
name = "gopkg.in/asn1-ber.v1"
version = "1.2.0"
[prune]
go-tests = true
unused-packages = true

View File

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -0,0 +1,152 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build !js,!appengine,!safe,!disableunsafe
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
var (
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
// internal reflect.Value fields. These values are valid before golang
// commit ecccf07e7f9d which changed the format. The are also valid
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
offsetPtr = uintptr(ptrSize)
offsetScalar = uintptr(0)
offsetFlag = uintptr(ptrSize * 2)
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
//
// flagRO indicates whether or not the value field of a reflect.Value is
// read-only.
//
// flagIndir indicates whether the value field of a reflect.Value is
// the actual data or a pointer to the data.
//
// These values are valid before golang commit 90a7c3c86944 which
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
flagKindShift = uintptr(flagKindWidth - 1)
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
)
func init() {
// Older versions of reflect.Value stored small integers directly in the
// ptr field (which is named val in the older versions). Versions
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
// scalar for this purpose which unfortunately came before the flag
// field, so the offset of the flag field is different for those
// versions.
//
// This code constructs a new reflect.Value from a known small integer
// and checks if the size of the reflect.Value struct indicates it has
// the scalar field. When it does, the offsets are updated accordingly.
vv := reflect.ValueOf(0xf00)
if unsafe.Sizeof(vv) == (ptrSize * 4) {
offsetScalar = ptrSize * 2
offsetFlag = ptrSize * 3
}
// Commit 90a7c3c86944 changed the flag positions such that the low
// order bits are the kind. This code extracts the kind from the flags
// field and ensures it's the correct type. When it's not, the flag
// order has been changed to the newer format, so the flags are updated
// accordingly.
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
upfv := *(*uintptr)(upf)
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
flagKindShift = 0
flagRO = 1 << 5
flagIndir = 1 << 6
// Commit adf9b30e5594 modified the flags to separate the
// flagRO flag into two bits which specifies whether or not the
// field is embedded. This causes flagIndir to move over a bit
// and means that flagRO is the combination of either of the
// original flagRO bit and the new bit.
//
// This code detects the change by extracting what used to be
// the indirect bit to ensure it's set. When it's not, the flag
// order has been changed to the newer format, so the flags are
// updated accordingly.
if upfv&flagIndir == 0 {
flagRO = 3 << 5
flagIndir = 1 << 7
}
}
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
indirects := 1
vt := v.Type()
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
if rvf&flagIndir != 0 {
vt = reflect.PtrTo(v.Type())
indirects++
} else if offsetScalar != 0 {
// The value is in the scalar field when it's not one of the
// reference types.
switch vt.Kind() {
case reflect.Uintptr:
case reflect.Chan:
case reflect.Func:
case reflect.Map:
case reflect.Ptr:
case reflect.UnsafePointer:
default:
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
offsetScalar)
}
}
pv := reflect.NewAt(vt, upv)
rv = pv
for i := 0; i < indirects; i++ {
rv = rv.Elem()
}
return rv
}

View File

@ -0,0 +1,38 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

View File

@ -0,0 +1,341 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

View File

@ -0,0 +1,306 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
// DisablePointerAddresses specifies whether to disable the printing of
// pointer addresses. This is useful when diffing data structures in tests.
DisablePointerAddresses bool
// DisableCapacities specifies whether to disable the printing of capacities
// for arrays, slices, maps and channels. This is useful when diffing
// data structures in tests.
DisableCapacities bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

View File

@ -0,0 +1,211 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of
capacities for arrays, slices, maps and channels. This is useful when
diffing data structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

View File

@ -0,0 +1,509 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound == true:
d.w.Write(nilAngleBytes)
case cycleFound == true:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if !d.cs.DisableCapacities && valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

View File

@ -0,0 +1,419 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound == true:
f.fs.Write(nilAngleBytes)
case cycleFound == true:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

View File

@ -0,0 +1,27 @@
Copyright (c) 2013, Patrick Mezard
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,772 @@
// Package difflib is a partial port of Python difflib module.
//
// It provides tools to compare sequences of strings and generate textual diffs.
//
// The following class and functions have been ported:
//
// - SequenceMatcher
//
// - unified_diff
//
// - context_diff
//
// Getting unified diffs was the main goal of the port. Keep in mind this code
// is mostly suitable to output text differences in a human friendly way, there
// are no guarantees generated diffs are consumable by patch(1).
package difflib
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func calculateRatio(matches, length int) float64 {
if length > 0 {
return 2.0 * float64(matches) / float64(length)
}
return 1.0
}
type Match struct {
A int
B int
Size int
}
type OpCode struct {
Tag byte
I1 int
I2 int
J1 int
J2 int
}
// SequenceMatcher compares sequence of strings. The basic
// algorithm predates, and is a little fancier than, an algorithm
// published in the late 1980's by Ratcliff and Obershelp under the
// hyperbolic name "gestalt pattern matching". The basic idea is to find
// the longest contiguous matching subsequence that contains no "junk"
// elements (R-O doesn't address junk). The same idea is then applied
// recursively to the pieces of the sequences to the left and to the right
// of the matching subsequence. This does not yield minimal edit
// sequences, but does tend to yield matches that "look right" to people.
//
// SequenceMatcher tries to compute a "human-friendly diff" between two
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
// longest *contiguous* & junk-free matching subsequence. That's what
// catches peoples' eyes. The Windows(tm) windiff has another interesting
// notion, pairing up elements that appear uniquely in each sequence.
// That, and the method here, appear to yield more intuitive difference
// reports than does diff. This method appears to be the least vulnerable
// to synching up on blocks of "junk lines", though (like blank lines in
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
// because this is the only method of the 3 that has a *concept* of
// "junk" <wink>.
//
// Timing: Basic R-O is cubic time worst case and quadratic time expected
// case. SequenceMatcher is quadratic time for the worst case and has
// expected-case behavior dependent in a complicated way on how many
// elements the sequences have in common; best case time is linear.
type SequenceMatcher struct {
a []string
b []string
b2j map[string][]int
IsJunk func(string) bool
autoJunk bool
bJunk map[string]struct{}
matchingBlocks []Match
fullBCount map[string]int
bPopular map[string]struct{}
opCodes []OpCode
}
func NewMatcher(a, b []string) *SequenceMatcher {
m := SequenceMatcher{autoJunk: true}
m.SetSeqs(a, b)
return &m
}
func NewMatcherWithJunk(a, b []string, autoJunk bool,
isJunk func(string) bool) *SequenceMatcher {
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
m.SetSeqs(a, b)
return &m
}
// Set two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a)
m.SetSeq2(b)
}
// Set the first sequence to be compared. The second sequence to be compared is
// not changed.
//
// SequenceMatcher computes and caches detailed information about the second
// sequence, so if you want to compare one sequence S against many sequences,
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
// sequences.
//
// See also SetSeqs() and SetSeq2().
func (m *SequenceMatcher) SetSeq1(a []string) {
if &a == &m.a {
return
}
m.a = a
m.matchingBlocks = nil
m.opCodes = nil
}
// Set the second sequence to be compared. The first sequence to be compared is
// not changed.
func (m *SequenceMatcher) SetSeq2(b []string) {
if &b == &m.b {
return
}
m.b = b
m.matchingBlocks = nil
m.opCodes = nil
m.fullBCount = nil
m.chainB()
}
func (m *SequenceMatcher) chainB() {
// Populate line -> index mapping
b2j := map[string][]int{}
for i, s := range m.b {
indices := b2j[s]
indices = append(indices, i)
b2j[s] = indices
}
// Purge junk elements
m.bJunk = map[string]struct{}{}
if m.IsJunk != nil {
junk := m.bJunk
for s, _ := range b2j {
if m.IsJunk(s) {
junk[s] = struct{}{}
}
}
for s, _ := range junk {
delete(b2j, s)
}
}
// Purge remaining popular elements
popular := map[string]struct{}{}
n := len(m.b)
if m.autoJunk && n >= 200 {
ntest := n/100 + 1
for s, indices := range b2j {
if len(indices) > ntest {
popular[s] = struct{}{}
}
}
for s, _ := range popular {
delete(b2j, s)
}
}
m.bPopular = popular
m.b2j = b2j
}
func (m *SequenceMatcher) isBJunk(s string) bool {
_, ok := m.bJunk[s]
return ok
}
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
//
// If IsJunk is not defined:
//
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
// alo <= i <= i+k <= ahi
// blo <= j <= j+k <= bhi
// and for all (i',j',k') meeting those conditions,
// k >= k'
// i <= i'
// and if i == i', j <= j'
//
// In other words, of all maximal matching blocks, return one that
// starts earliest in a, and of all those maximal matching blocks that
// start earliest in a, return the one that starts earliest in b.
//
// If IsJunk is defined, first the longest matching block is
// determined as above, but with the additional restriction that no
// junk element appears in the block. Then that block is extended as
// far as possible by matching (only) junk elements on both sides. So
// the resulting block never matches on junk except as identical junk
// happens to be adjacent to an "interesting" match.
//
// If no blocks match, return (alo, blo, 0).
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
// CAUTION: stripping common prefix or suffix would be incorrect.
// E.g.,
// ab
// acab
// Longest matching block is "ab", but if common prefix is
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
// strip, so ends up claiming that ab is changed to acab by
// inserting "ca" in the middle. That's minimal but unintuitive:
// "it's obvious" that someone inserted "ac" at the front.
// Windiff ends up at the same place as diff, but by pairing up
// the unique 'b's and then matching the first two 'a's.
besti, bestj, bestsize := alo, blo, 0
// find longest junk-free match
// during an iteration of the loop, j2len[j] = length of longest
// junk-free match ending with a[i-1] and b[j]
j2len := map[int]int{}
for i := alo; i != ahi; i++ {
// look at all instances of a[i] in b; note that because
// b2j has no junk keys, the loop is skipped if a[i] is junk
newj2len := map[int]int{}
for _, j := range m.b2j[m.a[i]] {
// a[i] matches b[j]
if j < blo {
continue
}
if j >= bhi {
break
}
k := j2len[j-1] + 1
newj2len[j] = k
if k > bestsize {
besti, bestj, bestsize = i-k+1, j-k+1, k
}
}
j2len = newj2len
}
// Extend the best by non-junk elements on each end. In particular,
// "popular" non-junk elements aren't in b2j, which greatly speeds
// the inner loop above, but also means "the best" match so far
// doesn't contain any junk *or* popular non-junk elements.
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
!m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
// Now that we have a wholly interesting match (albeit possibly
// empty!), we may as well suck up the matching junk on each
// side of it too. Can't think of a good reason not to, and it
// saves post-processing the (possibly considerable) expense of
// figuring out what to do with it. In the case of an empty
// interesting match, this is clearly the right thing to do,
// because no other kind of match is possible in the regions.
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
return Match{A: besti, B: bestj, Size: bestsize}
}
// Return list of triples describing matching subsequences.
//
// Each triple is of the form (i, j, n), and means that
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
// adjacent triples in the list, and the second is not the last triple in the
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
// adjacent equal blocks.
//
// The last triple is a dummy, (len(a), len(b), 0), and is the only
// triple with n==0.
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
if m.matchingBlocks != nil {
return m.matchingBlocks
}
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
match := m.findLongestMatch(alo, ahi, blo, bhi)
i, j, k := match.A, match.B, match.Size
if match.Size > 0 {
if alo < i && blo < j {
matched = matchBlocks(alo, i, blo, j, matched)
}
matched = append(matched, match)
if i+k < ahi && j+k < bhi {
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
}
}
return matched
}
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
// It's possible that we have adjacent equal blocks in the
// matching_blocks list now.
nonAdjacent := []Match{}
i1, j1, k1 := 0, 0, 0
for _, b := range matched {
// Is this block adjacent to i1, j1, k1?
i2, j2, k2 := b.A, b.B, b.Size
if i1+k1 == i2 && j1+k1 == j2 {
// Yes, so collapse them -- this just increases the length of
// the first block by the length of the second, and the first
// block so lengthened remains the block to compare against.
k1 += k2
} else {
// Not adjacent. Remember the first block (k1==0 means it's
// the dummy we started with), and make the second block the
// new block to compare against.
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
i1, j1, k1 = i2, j2, k2
}
}
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
m.matchingBlocks = nonAdjacent
return m.matchingBlocks
}
// Return list of 5-tuples describing how to turn a into b.
//
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
// tuple preceding it, and likewise for j1 == the previous j2.
//
// The tags are characters, with these meanings:
//
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
//
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
//
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
//
// 'e' (equal): a[i1:i2] == b[j1:j2]
func (m *SequenceMatcher) GetOpCodes() []OpCode {
if m.opCodes != nil {
return m.opCodes
}
i, j := 0, 0
matching := m.GetMatchingBlocks()
opCodes := make([]OpCode, 0, len(matching))
for _, m := range matching {
// invariant: we've pumped out correct diffs to change
// a[:i] into b[:j], and the next matching block is
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
// out a diff to change a[i:ai] into b[j:bj], pump out
// the matching block, and move (i,j) beyond the match
ai, bj, size := m.A, m.B, m.Size
tag := byte(0)
if i < ai && j < bj {
tag = 'r'
} else if i < ai {
tag = 'd'
} else if j < bj {
tag = 'i'
}
if tag > 0 {
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
}
i, j = ai+size, bj+size
// the list of matching blocks is terminated by a
// sentinel with size 0
if size > 0 {
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
}
}
m.opCodes = opCodes
return m.opCodes
}
// Isolate change clusters by eliminating ranges with no changes.
//
// Return a generator of groups with up to n lines of context.
// Each group is in the same format as returned by GetOpCodes().
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
if n < 0 {
n = 3
}
codes := m.GetOpCodes()
if len(codes) == 0 {
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
}
// Fixup leading and trailing groups if they show no changes.
if codes[0].Tag == 'e' {
c := codes[0]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
}
if codes[len(codes)-1].Tag == 'e' {
c := codes[len(codes)-1]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
}
nn := n + n
groups := [][]OpCode{}
group := []OpCode{}
for _, c := range codes {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
// End the current group and start a new one whenever
// there is a large range with no changes.
if c.Tag == 'e' && i2-i1 > nn {
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
j1, min(j2, j1+n)})
groups = append(groups, group)
group = []OpCode{}
i1, j1 = max(i1, i2-n), max(j1, j2-n)
}
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
}
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
groups = append(groups, group)
}
return groups
}
// Return a measure of the sequences' similarity (float in [0,1]).
//
// Where T is the total number of elements in both sequences, and
// M is the number of matches, this is 2.0*M / T.
// Note that this is 1 if the sequences are identical, and 0 if
// they have nothing in common.
//
// .Ratio() is expensive to compute if you haven't already computed
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
// want to try .QuickRatio() or .RealQuickRation() first to get an
// upper bound.
func (m *SequenceMatcher) Ratio() float64 {
matches := 0
for _, m := range m.GetMatchingBlocks() {
matches += m.Size
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() relatively quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute.
func (m *SequenceMatcher) QuickRatio() float64 {
// viewing a and b as multisets, set matches to the cardinality
// of their intersection; this counts the number of matches
// without regard to order, so is clearly an upper bound
if m.fullBCount == nil {
m.fullBCount = map[string]int{}
for _, s := range m.b {
m.fullBCount[s] = m.fullBCount[s] + 1
}
}
// avail[x] is the number of times x appears in 'b' less the
// number of times we've seen it in 'a' so far ... kinda
avail := map[string]int{}
matches := 0
for _, s := range m.a {
n, ok := avail[s]
if !ok {
n = m.fullBCount[s]
}
avail[s] = n - 1
if n > 0 {
matches += 1
}
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() very quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute than either .Ratio() or .QuickRatio().
func (m *SequenceMatcher) RealQuickRatio() float64 {
la, lb := len(m.a), len(m.b)
return calculateRatio(min(la, lb), la+lb)
}
// Convert range to the "ed" format
func formatRangeUnified(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 1 {
return fmt.Sprintf("%d", beginning)
}
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
return fmt.Sprintf("%d,%d", beginning, length)
}
// Unified diff parameters
type UnifiedDiff struct {
A []string // First sequence lines
FromFile string // First file name
FromDate string // First file time
B []string // Second sequence lines
ToFile string // Second file name
ToDate string // Second file time
Eol string // Headers end of line, defaults to LF
Context int // Number of context lines
}
// Compare two sequences of lines; generate the delta as a unified diff.
//
// Unified diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by 'n' which
// defaults to three.
//
// By default, the diff control lines (those with ---, +++, or @@) are
// created with a trailing newline. This is helpful so that inputs
// created from file.readlines() result in diffs that are suitable for
// file.writelines() since both the inputs and outputs have trailing
// newlines.
//
// For inputs that do not have trailing newlines, set the lineterm
// argument to "" so that the output will be uniformly newline free.
//
// The unidiff format normally has a header for filenames and modification
// times. Any or all of these may be specified using strings for
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
// The modification times are normally expressed in the ISO 8601 format.
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
wf := func(format string, args ...interface{}) error {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
return err
}
ws := func(s string) error {
_, err := buf.WriteString(s)
return err
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
if err != nil {
return err
}
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
if err != nil {
return err
}
}
}
first, last := g[0], g[len(g)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
return err
}
for _, c := range g {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
if c.Tag == 'e' {
for _, line := range diff.A[i1:i2] {
if err := ws(" " + line); err != nil {
return err
}
}
continue
}
if c.Tag == 'r' || c.Tag == 'd' {
for _, line := range diff.A[i1:i2] {
if err := ws("-" + line); err != nil {
return err
}
}
}
if c.Tag == 'r' || c.Tag == 'i' {
for _, line := range diff.B[j1:j2] {
if err := ws("+" + line); err != nil {
return err
}
}
}
}
}
return nil
}
// Like WriteUnifiedDiff but returns the diff a string.
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteUnifiedDiff(w, diff)
return string(w.Bytes()), err
}
// Convert range to the "ed" format.
func formatRangeContext(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
if length <= 1 {
return fmt.Sprintf("%d", beginning)
}
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
}
type ContextDiff UnifiedDiff
// Compare two sequences of lines; generate the delta as a context diff.
//
// Context diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by diff.Context
// which defaults to three.
//
// By default, the diff control lines (those with *** or ---) are
// created with a trailing newline.
//
// For inputs that do not have trailing newlines, set the diff.Eol
// argument to "" so that the output will be uniformly newline free.
//
// The context diff format normally has a header for filenames and
// modification times. Any or all of these may be specified using
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
// The modification times are normally expressed in the ISO 8601 format.
// If not specified, the strings default to blanks.
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
var diffErr error
wf := func(format string, args ...interface{}) {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
if diffErr == nil && err != nil {
diffErr = err
}
}
ws := func(s string) {
_, err := buf.WriteString(s)
if diffErr == nil && err != nil {
diffErr = err
}
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
prefix := map[byte]string{
'i': "+ ",
'd': "- ",
'r': "! ",
'e': " ",
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
}
}
first, last := g[0], g[len(g)-1]
ws("***************" + diff.Eol)
range1 := formatRangeContext(first.I1, last.I2)
wf("*** %s ****%s", range1, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'd' {
for _, cc := range g {
if cc.Tag == 'i' {
continue
}
for _, line := range diff.A[cc.I1:cc.I2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
range2 := formatRangeContext(first.J1, last.J2)
wf("--- %s ----%s", range2, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'i' {
for _, cc := range g {
if cc.Tag == 'd' {
continue
}
for _, line := range diff.B[cc.J1:cc.J2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
}
return diffErr
}
// Like WriteContextDiff but returns the diff a string.
func GetContextDiffString(diff ContextDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteContextDiff(w, diff)
return string(w.Bytes()), err
}
// Split a string on "\n" while preserving them. The output can be used
// as input for UnifiedDiff and ContextDiff structures.
func SplitLines(s string) []string {
lines := strings.SplitAfter(s, "\n")
lines[len(lines)-1] += "\n"
return lines
}

View File

@ -0,0 +1,22 @@
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
Please consider promoting this project if you find it useful.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,484 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package assert
import (
http "net/http"
url "net/url"
time "time"
)
// Conditionf uses a Comparison to assert a complex condition.
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Condition(t, comp, append([]interface{}{msg}, args...)...)
}
// Containsf asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return DirExists(t, path, append([]interface{}{msg}, args...)...)
}
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
// the number of appearances of each of them in both lists should match.
//
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
}
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// assert.Emptyf(t, obj, "error message %s", "formatted")
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Empty(t, object, append([]interface{}{msg}, args...)...)
}
// Equalf asserts that two objects are equal.
//
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// and equal.
//
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
// if assert.Errorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedErrorf, err)
// }
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Error(t, err, append([]interface{}{msg}, args...)...)
}
// Exactlyf asserts that two objects are equal in value and type.
//
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Failf reports a failure through
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
}
// FailNowf fails test
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
}
// Falsef asserts that the specified value is false.
//
// assert.Falsef(t, myBool, "error message %s", "formatted")
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return False(t, value, append([]interface{}{msg}, args...)...)
}
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return FileExists(t, path, append([]interface{}{msg}, args...)...)
}
// HTTPBodyContainsf asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
}
// HTTPBodyNotContainsf asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
}
// HTTPErrorf asserts that a specified handler returns an error status code.
//
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// HTTPSuccessf asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// Implementsf asserts that an object is implemented by the specified interface.
//
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
}
// InDeltaf asserts that the two numerals are within delta of each other.
//
// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InDeltaSlicef is the same as InDelta, except it compares two slices.
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// IsTypef asserts that the specified objects are of the same type.
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
}
// JSONEqf asserts that two JSON strings are equivalent.
//
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Lenf asserts that the specified object has specific length.
// Lenf also fails if the object has a type that len() not accept.
//
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Len(t, object, length, append([]interface{}{msg}, args...)...)
}
// Nilf asserts that the specified object is nil.
//
// assert.Nilf(t, err, "error message %s", "formatted")
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Nil(t, object, append([]interface{}{msg}, args...)...)
}
// NoErrorf asserts that a function returned no error (i.e. `nil`).
//
// actualObj, err := SomeFunction()
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedObj, actualObj)
// }
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoError(t, err, append([]interface{}{msg}, args...)...)
}
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1])
// }
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
}
// NotEqualf asserts that the specified values are NOT equal.
//
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotNilf asserts that the specified object is not nil.
//
// assert.NotNilf(t, err, "error message %s", "formatted")
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotNil(t, object, append([]interface{}{msg}, args...)...)
}
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
//
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
}
// NotRegexpf asserts that a specified regexp does not match a string.
//
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
// NotSubsetf asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
//
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
}
// NotZerof asserts that i is not the zero value for its type.
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotZero(t, i, append([]interface{}{msg}, args...)...)
}
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
//
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Panics(t, f, append([]interface{}{msg}, args...)...)
}
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
// the recovered panic value equals the expected panic value.
//
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
}
// Regexpf asserts that a specified regexp matches a string.
//
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
// Subsetf asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
//
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
}
// Truef asserts that the specified value is true.
//
// assert.Truef(t, myBool, "error message %s", "formatted")
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return True(t, value, append([]interface{}{msg}, args...)...)
}
// WithinDurationf asserts that the two times are within duration delta of each other.
//
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// Zerof asserts that i is the zero value for its type.
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Zero(t, i, append([]interface{}{msg}, args...)...)
}

View File

@ -0,0 +1,5 @@
{{.CommentFormat}}
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
if h, ok := t.(tHelper); ok { h.Helper() }
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
}

View File

@ -0,0 +1,956 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package assert
import (
http "net/http"
url "net/url"
time "time"
)
// Condition uses a Comparison to assert a complex condition.
func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Condition(a.t, comp, msgAndArgs...)
}
// Conditionf uses a Comparison to assert a complex condition.
func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Conditionf(a.t, comp, msg, args...)
}
// Contains asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
// a.Contains("Hello World", "World")
// a.Contains(["Hello", "World"], "World")
// a.Contains({"Hello": "World"}, "Hello")
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Contains(a.t, s, contains, msgAndArgs...)
}
// Containsf asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
// a.Containsf("Hello World", "World", "error message %s", "formatted")
// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted")
// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted")
func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Containsf(a.t, s, contains, msg, args...)
}
// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return DirExists(a.t, path, msgAndArgs...)
}
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return DirExistsf(a.t, path, msg, args...)
}
// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
// the number of appearances of each of them in both lists should match.
//
// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2])
func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ElementsMatch(a.t, listA, listB, msgAndArgs...)
}
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
// the number of appearances of each of them in both lists should match.
//
// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ElementsMatchf(a.t, listA, listB, msg, args...)
}
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// a.Empty(obj)
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Empty(a.t, object, msgAndArgs...)
}
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// a.Emptyf(obj, "error message %s", "formatted")
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Emptyf(a.t, object, msg, args...)
}
// Equal asserts that two objects are equal.
//
// a.Equal(123, 123)
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Equal(a.t, expected, actual, msgAndArgs...)
}
// EqualError asserts that a function returned an error (i.e. not `nil`)
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
// a.EqualError(err, expectedErrorString)
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EqualError(a.t, theError, errString, msgAndArgs...)
}
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted")
func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EqualErrorf(a.t, theError, errString, msg, args...)
}
// EqualValues asserts that two objects are equal or convertable to the same types
// and equal.
//
// a.EqualValues(uint32(123), int32(123))
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EqualValues(a.t, expected, actual, msgAndArgs...)
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// and equal.
//
// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EqualValuesf(a.t, expected, actual, msg, args...)
}
// Equalf asserts that two objects are equal.
//
// a.Equalf(123, 123, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Equalf(a.t, expected, actual, msg, args...)
}
// Error asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
// if a.Error(err) {
// assert.Equal(t, expectedError, err)
// }
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Error(a.t, err, msgAndArgs...)
}
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
// if a.Errorf(err, "error message %s", "formatted") {
// assert.Equal(t, expectedErrorf, err)
// }
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Errorf(a.t, err, msg, args...)
}
// Exactly asserts that two objects are equal in value and type.
//
// a.Exactly(int32(123), int64(123))
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Exactly(a.t, expected, actual, msgAndArgs...)
}
// Exactlyf asserts that two objects are equal in value and type.
//
// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Exactlyf(a.t, expected, actual, msg, args...)
}
// Fail reports a failure through
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Fail(a.t, failureMessage, msgAndArgs...)
}
// FailNow fails test
func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return FailNow(a.t, failureMessage, msgAndArgs...)
}
// FailNowf fails test
func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return FailNowf(a.t, failureMessage, msg, args...)
}
// Failf reports a failure through
func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Failf(a.t, failureMessage, msg, args...)
}
// False asserts that the specified value is false.
//
// a.False(myBool)
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return False(a.t, value, msgAndArgs...)
}
// Falsef asserts that the specified value is false.
//
// a.Falsef(myBool, "error message %s", "formatted")
func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Falsef(a.t, value, msg, args...)
}
// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return FileExists(a.t, path, msgAndArgs...)
}
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return FileExistsf(a.t, path, msg, args...)
}
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...)
}
// HTTPBodyContainsf asserts that a specified handler returns a
// body that contains a string.
//
// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...)
}
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...)
}
// HTTPBodyNotContainsf asserts that a specified handler returns a
// body that does not contain a string.
//
// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...)
}
// HTTPError asserts that a specified handler returns an error status code.
//
// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPError(a.t, handler, method, url, values, msgAndArgs...)
}
// HTTPErrorf asserts that a specified handler returns an error status code.
//
// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPErrorf(a.t, handler, method, url, values, msg, args...)
}
// HTTPRedirect asserts that a specified handler returns a redirect status code.
//
// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...)
}
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
//
// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
}
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...)
}
// HTTPSuccessf asserts that a specified handler returns a success status code.
//
// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return HTTPSuccessf(a.t, handler, method, url, values, msg, args...)
}
// Implements asserts that an object is implemented by the specified interface.
//
// a.Implements((*MyInterface)(nil), new(MyObject))
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Implements(a.t, interfaceObject, object, msgAndArgs...)
}
// Implementsf asserts that an object is implemented by the specified interface.
//
// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Implementsf(a.t, interfaceObject, object, msg, args...)
}
// InDelta asserts that the two numerals are within delta of each other.
//
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InDelta(a.t, expected, actual, delta, msgAndArgs...)
}
// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...)
}
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...)
}
// InDeltaSlice is the same as InDelta, except it compares two slices.
func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
}
// InDeltaSlicef is the same as InDelta, except it compares two slices.
func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InDeltaSlicef(a.t, expected, actual, delta, msg, args...)
}
// InDeltaf asserts that the two numerals are within delta of each other.
//
// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InDeltaf(a.t, expected, actual, delta, msg, args...)
}
// InEpsilon asserts that expected and actual have a relative error less than epsilon
func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
}
// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
}
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...)
}
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
}
// IsType asserts that the specified objects are of the same type.
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsType(a.t, expectedType, object, msgAndArgs...)
}
// IsTypef asserts that the specified objects are of the same type.
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsTypef(a.t, expectedType, object, msg, args...)
}
// JSONEq asserts that two JSON strings are equivalent.
//
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return JSONEq(a.t, expected, actual, msgAndArgs...)
}
// JSONEqf asserts that two JSON strings are equivalent.
//
// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return JSONEqf(a.t, expected, actual, msg, args...)
}
// Len asserts that the specified object has specific length.
// Len also fails if the object has a type that len() not accept.
//
// a.Len(mySlice, 3)
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Len(a.t, object, length, msgAndArgs...)
}
// Lenf asserts that the specified object has specific length.
// Lenf also fails if the object has a type that len() not accept.
//
// a.Lenf(mySlice, 3, "error message %s", "formatted")
func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Lenf(a.t, object, length, msg, args...)
}
// Nil asserts that the specified object is nil.
//
// a.Nil(err)
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Nil(a.t, object, msgAndArgs...)
}
// Nilf asserts that the specified object is nil.
//
// a.Nilf(err, "error message %s", "formatted")
func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Nilf(a.t, object, msg, args...)
}
// NoError asserts that a function returned no error (i.e. `nil`).
//
// actualObj, err := SomeFunction()
// if a.NoError(err) {
// assert.Equal(t, expectedObj, actualObj)
// }
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NoError(a.t, err, msgAndArgs...)
}
// NoErrorf asserts that a function returned no error (i.e. `nil`).
//
// actualObj, err := SomeFunction()
// if a.NoErrorf(err, "error message %s", "formatted") {
// assert.Equal(t, expectedObj, actualObj)
// }
func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NoErrorf(a.t, err, msg, args...)
}
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
// a.NotContains("Hello World", "Earth")
// a.NotContains(["Hello", "World"], "Earth")
// a.NotContains({"Hello": "World"}, "Earth")
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotContains(a.t, s, contains, msgAndArgs...)
}
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted")
// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted")
// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted")
func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotContainsf(a.t, s, contains, msg, args...)
}
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// if a.NotEmpty(obj) {
// assert.Equal(t, "two", obj[1])
// }
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotEmpty(a.t, object, msgAndArgs...)
}
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// if a.NotEmptyf(obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1])
// }
func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotEmptyf(a.t, object, msg, args...)
}
// NotEqual asserts that the specified values are NOT equal.
//
// a.NotEqual(obj1, obj2)
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotEqual(a.t, expected, actual, msgAndArgs...)
}
// NotEqualf asserts that the specified values are NOT equal.
//
// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotEqualf(a.t, expected, actual, msg, args...)
}
// NotNil asserts that the specified object is not nil.
//
// a.NotNil(err)
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotNil(a.t, object, msgAndArgs...)
}
// NotNilf asserts that the specified object is not nil.
//
// a.NotNilf(err, "error message %s", "formatted")
func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotNilf(a.t, object, msg, args...)
}
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
//
// a.NotPanics(func(){ RemainCalm() })
func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotPanics(a.t, f, msgAndArgs...)
}
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
//
// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted")
func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotPanicsf(a.t, f, msg, args...)
}
// NotRegexp asserts that a specified regexp does not match a string.
//
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
// a.NotRegexp("^start", "it's not starting")
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotRegexp(a.t, rx, str, msgAndArgs...)
}
// NotRegexpf asserts that a specified regexp does not match a string.
//
// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotRegexpf(a.t, rx, str, msg, args...)
}
// NotSubset asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
//
// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotSubset(a.t, list, subset, msgAndArgs...)
}
// NotSubsetf asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
//
// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotSubsetf(a.t, list, subset, msg, args...)
}
// NotZero asserts that i is not the zero value for its type.
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotZero(a.t, i, msgAndArgs...)
}
// NotZerof asserts that i is not the zero value for its type.
func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotZerof(a.t, i, msg, args...)
}
// Panics asserts that the code inside the specified PanicTestFunc panics.
//
// a.Panics(func(){ GoCrazy() })
func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Panics(a.t, f, msgAndArgs...)
}
// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
// the recovered panic value equals the expected panic value.
//
// a.PanicsWithValue("crazy error", func(){ GoCrazy() })
func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return PanicsWithValue(a.t, expected, f, msgAndArgs...)
}
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
// the recovered panic value equals the expected panic value.
//
// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return PanicsWithValuef(a.t, expected, f, msg, args...)
}
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
//
// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted")
func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Panicsf(a.t, f, msg, args...)
}
// Regexp asserts that a specified regexp matches a string.
//
// a.Regexp(regexp.MustCompile("start"), "it's starting")
// a.Regexp("start...$", "it's not starting")
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Regexp(a.t, rx, str, msgAndArgs...)
}
// Regexpf asserts that a specified regexp matches a string.
//
// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Regexpf(a.t, rx, str, msg, args...)
}
// Subset asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
//
// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Subset(a.t, list, subset, msgAndArgs...)
}
// Subsetf asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
//
// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Subsetf(a.t, list, subset, msg, args...)
}
// True asserts that the specified value is true.
//
// a.True(myBool)
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return True(a.t, value, msgAndArgs...)
}
// Truef asserts that the specified value is true.
//
// a.Truef(myBool, "error message %s", "formatted")
func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Truef(a.t, value, msg, args...)
}
// WithinDuration asserts that the two times are within duration delta of each other.
//
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second)
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
}
// WithinDurationf asserts that the two times are within duration delta of each other.
//
// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return WithinDurationf(a.t, expected, actual, delta, msg, args...)
}
// Zero asserts that i is the zero value for its type.
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Zero(a.t, i, msgAndArgs...)
}
// Zerof asserts that i is the zero value for its type.
func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Zerof(a.t, i, msg, args...)
}

View File

@ -0,0 +1,5 @@
{{.CommentWithoutT "a"}}
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
if h, ok := a.t.(tHelper); ok { h.Helper() }
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
//
// Example Usage
//
// The following is a complete example using assert in a standard test function:
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
//
// var a string = "Hello"
// var b string = "Hello"
//
// assert.Equal(t, a, b, "The two words should be the same.")
//
// }
//
// if you assert many times, use the format below:
//
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
// assert := assert.New(t)
//
// var a string = "Hello"
// var b string = "Hello"
//
// assert.Equal(a, b, "The two words should be the same.")
// }
//
// Assertions
//
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
// testing framework. This allows the assertion funcs to write the failings and other details to
// the correct place.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.
package assert

View File

@ -0,0 +1,10 @@
package assert
import (
"errors"
)
// AnError is an error instance useful for testing. If the code does not care
// about error specifics, and only needs to return the error for example, this
// error should be used to make the test code more readable.
var AnError = errors.New("assert.AnError general error for testing")

View File

@ -0,0 +1,16 @@
package assert
// Assertions provides assertion methods around the
// TestingT interface.
type Assertions struct {
t TestingT
}
// New makes a new Assertions object for the specified TestingT.
func New(t TestingT) *Assertions {
return &Assertions{
t: t,
}
}
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs

View File

@ -0,0 +1,143 @@
package assert
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
)
// httpCode is a helper that returns HTTP code of the response. It returns -1 and
// an error if building a new request fails.
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)
if err != nil {
return -1, err
}
req.URL.RawQuery = values.Encode()
handler(w, req)
return w.Code, nil
}
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
}
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
if !isSuccessCode {
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
}
return isSuccessCode
}
// HTTPRedirect asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
}
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
if !isRedirectCode {
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
}
return isRedirectCode
}
// HTTPError asserts that a specified handler returns an error status code.
//
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
}
isErrorCode := code >= http.StatusBadRequest
if !isErrorCode {
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
}
return isErrorCode
}
// HTTPBody is a helper that returns HTTP body of the response. It returns
// empty string if building a new request fails.
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
if err != nil {
return ""
}
handler(w, req)
return w.Body.String()
}
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
body := HTTPBody(handler, method, url, values)
contains := strings.Contains(body, fmt.Sprint(str))
if !contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
}
return contains
}
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
body := HTTPBody(handler, method, url, values)
contains := strings.Contains(body, fmt.Sprint(str))
if contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
}
return !contains
}

View File

@ -0,0 +1,18 @@
language: go
go:
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- tip
go_import_path: gopkg.in/asn-ber.v1
install:
- go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v
- go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v
- go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
- go build -v ./...
script:
- go test -v -cover ./...

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
Portions copyright (c) 2015-2016 go-asn1-ber Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,24 @@
[![GoDoc](https://godoc.org/gopkg.in/asn1-ber.v1?status.svg)](https://godoc.org/gopkg.in/asn1-ber.v1) [![Build Status](https://travis-ci.org/go-asn1-ber/asn1-ber.svg)](https://travis-ci.org/go-asn1-ber/asn1-ber)
ASN1 BER Encoding / Decoding Library for the GO programming language.
---------------------------------------------------------------------
Required libraries:
None
Working:
Very basic encoding / decoding needed for LDAP protocol
Tests Implemented:
A few
TODO:
Fix all encoding / decoding to conform to ASN1 BER spec
Implement Tests / Benchmarks
---
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
The design is licensed under the Creative Commons 3.0 Attributions license.
Read this article for more details: http://blog.golang.org/gopher

View File

@ -0,0 +1,504 @@
package ber
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"reflect"
)
type Packet struct {
Identifier
Value interface{}
ByteValue []byte
Data *bytes.Buffer
Children []*Packet
Description string
}
type Identifier struct {
ClassType Class
TagType Type
Tag Tag
}
type Tag uint64
const (
TagEOC Tag = 0x00
TagBoolean Tag = 0x01
TagInteger Tag = 0x02
TagBitString Tag = 0x03
TagOctetString Tag = 0x04
TagNULL Tag = 0x05
TagObjectIdentifier Tag = 0x06
TagObjectDescriptor Tag = 0x07
TagExternal Tag = 0x08
TagRealFloat Tag = 0x09
TagEnumerated Tag = 0x0a
TagEmbeddedPDV Tag = 0x0b
TagUTF8String Tag = 0x0c
TagRelativeOID Tag = 0x0d
TagSequence Tag = 0x10
TagSet Tag = 0x11
TagNumericString Tag = 0x12
TagPrintableString Tag = 0x13
TagT61String Tag = 0x14
TagVideotexString Tag = 0x15
TagIA5String Tag = 0x16
TagUTCTime Tag = 0x17
TagGeneralizedTime Tag = 0x18
TagGraphicString Tag = 0x19
TagVisibleString Tag = 0x1a
TagGeneralString Tag = 0x1b
TagUniversalString Tag = 0x1c
TagCharacterString Tag = 0x1d
TagBMPString Tag = 0x1e
TagBitmask Tag = 0x1f // xxx11111b
// HighTag indicates the start of a high-tag byte sequence
HighTag Tag = 0x1f // xxx11111b
// HighTagContinueBitmask indicates the high-tag byte sequence should continue
HighTagContinueBitmask Tag = 0x80 // 10000000b
// HighTagValueBitmask obtains the tag value from a high-tag byte sequence byte
HighTagValueBitmask Tag = 0x7f // 01111111b
)
const (
// LengthLongFormBitmask is the mask to apply to the length byte to see if a long-form byte sequence is used
LengthLongFormBitmask = 0x80
// LengthValueBitmask is the mask to apply to the length byte to get the number of bytes in the long-form byte sequence
LengthValueBitmask = 0x7f
// LengthIndefinite is returned from readLength to indicate an indefinite length
LengthIndefinite = -1
)
var tagMap = map[Tag]string{
TagEOC: "EOC (End-of-Content)",
TagBoolean: "Boolean",
TagInteger: "Integer",
TagBitString: "Bit String",
TagOctetString: "Octet String",
TagNULL: "NULL",
TagObjectIdentifier: "Object Identifier",
TagObjectDescriptor: "Object Descriptor",
TagExternal: "External",
TagRealFloat: "Real (float)",
TagEnumerated: "Enumerated",
TagEmbeddedPDV: "Embedded PDV",
TagUTF8String: "UTF8 String",
TagRelativeOID: "Relative-OID",
TagSequence: "Sequence and Sequence of",
TagSet: "Set and Set OF",
TagNumericString: "Numeric String",
TagPrintableString: "Printable String",
TagT61String: "T61 String",
TagVideotexString: "Videotex String",
TagIA5String: "IA5 String",
TagUTCTime: "UTC Time",
TagGeneralizedTime: "Generalized Time",
TagGraphicString: "Graphic String",
TagVisibleString: "Visible String",
TagGeneralString: "General String",
TagUniversalString: "Universal String",
TagCharacterString: "Character String",
TagBMPString: "BMP String",
}
type Class uint8
const (
ClassUniversal Class = 0 // 00xxxxxxb
ClassApplication Class = 64 // 01xxxxxxb
ClassContext Class = 128 // 10xxxxxxb
ClassPrivate Class = 192 // 11xxxxxxb
ClassBitmask Class = 192 // 11xxxxxxb
)
var ClassMap = map[Class]string{
ClassUniversal: "Universal",
ClassApplication: "Application",
ClassContext: "Context",
ClassPrivate: "Private",
}
type Type uint8
const (
TypePrimitive Type = 0 // xx0xxxxxb
TypeConstructed Type = 32 // xx1xxxxxb
TypeBitmask Type = 32 // xx1xxxxxb
)
var TypeMap = map[Type]string{
TypePrimitive: "Primitive",
TypeConstructed: "Constructed",
}
var Debug bool = false
func PrintBytes(out io.Writer, buf []byte, indent string) {
data_lines := make([]string, (len(buf)/30)+1)
num_lines := make([]string, (len(buf)/30)+1)
for i, b := range buf {
data_lines[i/30] += fmt.Sprintf("%02x ", b)
num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100)
}
for i := 0; i < len(data_lines); i++ {
out.Write([]byte(indent + data_lines[i] + "\n"))
out.Write([]byte(indent + num_lines[i] + "\n\n"))
}
}
func PrintPacket(p *Packet) {
printPacket(os.Stdout, p, 0, false)
}
func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) {
indent_str := ""
for len(indent_str) != indent {
indent_str += " "
}
class_str := ClassMap[p.ClassType]
tagtype_str := TypeMap[p.TagType]
tag_str := fmt.Sprintf("0x%02X", p.Tag)
if p.ClassType == ClassUniversal {
tag_str = tagMap[p.Tag]
}
value := fmt.Sprint(p.Value)
description := ""
if p.Description != "" {
description = p.Description + ": "
}
fmt.Fprintf(out, "%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value)
if printBytes {
PrintBytes(out, p.Bytes(), indent_str)
}
for _, child := range p.Children {
printPacket(out, child, indent+1, printBytes)
}
}
// ReadPacket reads a single Packet from the reader
func ReadPacket(reader io.Reader) (*Packet, error) {
p, _, err := readPacket(reader)
if err != nil {
return nil, err
}
return p, nil
}
func DecodeString(data []byte) string {
return string(data)
}
func parseInt64(bytes []byte) (ret int64, err error) {
if len(bytes) > 8 {
// We'll overflow an int64 in this case.
err = fmt.Errorf("integer too large")
return
}
for bytesRead := 0; bytesRead < len(bytes); bytesRead++ {
ret <<= 8
ret |= int64(bytes[bytesRead])
}
// Shift up and down in order to sign extend the result.
ret <<= 64 - uint8(len(bytes))*8
ret >>= 64 - uint8(len(bytes))*8
return
}
func encodeInteger(i int64) []byte {
n := int64Length(i)
out := make([]byte, n)
var j int
for ; n > 0; n-- {
out[j] = (byte(i >> uint((n-1)*8)))
j++
}
return out
}
func int64Length(i int64) (numBytes int) {
numBytes = 1
for i > 127 {
numBytes++
i >>= 8
}
for i < -128 {
numBytes++
i >>= 8
}
return
}
// DecodePacket decodes the given bytes into a single Packet
// If a decode error is encountered, nil is returned.
func DecodePacket(data []byte) *Packet {
p, _, _ := readPacket(bytes.NewBuffer(data))
return p
}
// DecodePacketErr decodes the given bytes into a single Packet
// If a decode error is encountered, nil is returned
func DecodePacketErr(data []byte) (*Packet, error) {
p, _, err := readPacket(bytes.NewBuffer(data))
if err != nil {
return nil, err
}
return p, nil
}
// readPacket reads a single Packet from the reader, returning the number of bytes read
func readPacket(reader io.Reader) (*Packet, int, error) {
identifier, length, read, err := readHeader(reader)
if err != nil {
return nil, read, err
}
p := &Packet{
Identifier: identifier,
}
p.Data = new(bytes.Buffer)
p.Children = make([]*Packet, 0, 2)
p.Value = nil
if p.TagType == TypeConstructed {
// TODO: if universal, ensure tag type is allowed to be constructed
// Track how much content we've read
contentRead := 0
for {
if length != LengthIndefinite {
// End if we've read what we've been told to
if contentRead == length {
break
}
// Detect if a packet boundary didn't fall on the expected length
if contentRead > length {
return nil, read, fmt.Errorf("expected to read %d bytes, read %d", length, contentRead)
}
}
// Read the next packet
child, r, err := readPacket(reader)
if err != nil {
return nil, read, err
}
contentRead += r
read += r
// Test is this is the EOC marker for our packet
if isEOCPacket(child) {
if length == LengthIndefinite {
break
}
return nil, read, errors.New("eoc child not allowed with definite length")
}
// Append and continue
p.AppendChild(child)
}
return p, read, nil
}
if length == LengthIndefinite {
return nil, read, errors.New("indefinite length used with primitive type")
}
// Read definite-length content
content := make([]byte, length, length)
if length > 0 {
_, err := io.ReadFull(reader, content)
if err != nil {
if err == io.EOF {
return nil, read, io.ErrUnexpectedEOF
}
return nil, read, err
}
read += length
}
if p.ClassType == ClassUniversal {
p.Data.Write(content)
p.ByteValue = content
switch p.Tag {
case TagEOC:
case TagBoolean:
val, _ := parseInt64(content)
p.Value = val != 0
case TagInteger:
p.Value, _ = parseInt64(content)
case TagBitString:
case TagOctetString:
// the actual string encoding is not known here
// (e.g. for LDAP content is already an UTF8-encoded
// string). Return the data without further processing
p.Value = DecodeString(content)
case TagNULL:
case TagObjectIdentifier:
case TagObjectDescriptor:
case TagExternal:
case TagRealFloat:
case TagEnumerated:
p.Value, _ = parseInt64(content)
case TagEmbeddedPDV:
case TagUTF8String:
p.Value = DecodeString(content)
case TagRelativeOID:
case TagSequence:
case TagSet:
case TagNumericString:
case TagPrintableString:
p.Value = DecodeString(content)
case TagT61String:
case TagVideotexString:
case TagIA5String:
case TagUTCTime:
case TagGeneralizedTime:
case TagGraphicString:
case TagVisibleString:
case TagGeneralString:
case TagUniversalString:
case TagCharacterString:
case TagBMPString:
}
} else {
p.Data.Write(content)
}
return p, read, nil
}
func (p *Packet) Bytes() []byte {
var out bytes.Buffer
out.Write(encodeIdentifier(p.Identifier))
out.Write(encodeLength(p.Data.Len()))
out.Write(p.Data.Bytes())
return out.Bytes()
}
func (p *Packet) AppendChild(child *Packet) {
p.Data.Write(child.Bytes())
p.Children = append(p.Children, child)
}
func Encode(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
p := new(Packet)
p.ClassType = ClassType
p.TagType = TagType
p.Tag = Tag
p.Data = new(bytes.Buffer)
p.Children = make([]*Packet, 0, 2)
p.Value = Value
p.Description = Description
if Value != nil {
v := reflect.ValueOf(Value)
if ClassType == ClassUniversal {
switch Tag {
case TagOctetString:
sv, ok := v.Interface().(string)
if ok {
p.Data.Write([]byte(sv))
}
}
}
}
return p
}
func NewSequence(Description string) *Packet {
return Encode(ClassUniversal, TypeConstructed, TagSequence, nil, Description)
}
func NewBoolean(ClassType Class, TagType Type, Tag Tag, Value bool, Description string) *Packet {
intValue := int64(0)
if Value {
intValue = 1
}
p := Encode(ClassType, TagType, Tag, nil, Description)
p.Value = Value
p.Data.Write(encodeInteger(intValue))
return p
}
func NewInteger(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
p := Encode(ClassType, TagType, Tag, nil, Description)
p.Value = Value
switch v := Value.(type) {
case int:
p.Data.Write(encodeInteger(int64(v)))
case uint:
p.Data.Write(encodeInteger(int64(v)))
case int64:
p.Data.Write(encodeInteger(v))
case uint64:
// TODO : check range or add encodeUInt...
p.Data.Write(encodeInteger(int64(v)))
case int32:
p.Data.Write(encodeInteger(int64(v)))
case uint32:
p.Data.Write(encodeInteger(int64(v)))
case int16:
p.Data.Write(encodeInteger(int64(v)))
case uint16:
p.Data.Write(encodeInteger(int64(v)))
case int8:
p.Data.Write(encodeInteger(int64(v)))
case uint8:
p.Data.Write(encodeInteger(int64(v)))
default:
// TODO : add support for big.Int ?
panic(fmt.Sprintf("Invalid type %T, expected {u|}int{64|32|16|8}", v))
}
return p
}
func NewString(ClassType Class, TagType Type, Tag Tag, Value, Description string) *Packet {
p := Encode(ClassType, TagType, Tag, nil, Description)
p.Value = Value
p.Data.Write([]byte(Value))
return p
}

View File

@ -0,0 +1,25 @@
package ber
func encodeUnsignedInteger(i uint64) []byte {
n := uint64Length(i)
out := make([]byte, n)
var j int
for ; n > 0; n-- {
out[j] = (byte(i >> uint((n-1)*8)))
j++
}
return out
}
func uint64Length(i uint64) (numBytes int) {
numBytes = 1
for i > 255 {
numBytes++
i >>= 8
}
return
}

View File

@ -0,0 +1,29 @@
package ber
import (
"errors"
"io"
)
func readHeader(reader io.Reader) (identifier Identifier, length int, read int, err error) {
if i, c, err := readIdentifier(reader); err != nil {
return Identifier{}, 0, read, err
} else {
identifier = i
read += c
}
if l, c, err := readLength(reader); err != nil {
return Identifier{}, 0, read, err
} else {
length = l
read += c
}
// Validate length type with identifier (x.600, 8.1.3.2.a)
if length == LengthIndefinite && identifier.TagType == TypePrimitive {
return Identifier{}, 0, read, errors.New("indefinite length used with primitive type")
}
return identifier, length, read, nil
}

View File

@ -0,0 +1,103 @@
package ber
import (
"errors"
"fmt"
"io"
"math"
)
func readIdentifier(reader io.Reader) (Identifier, int, error) {
identifier := Identifier{}
read := 0
// identifier byte
b, err := readByte(reader)
if err != nil {
if Debug {
fmt.Printf("error reading identifier byte: %v\n", err)
}
return Identifier{}, read, err
}
read++
identifier.ClassType = Class(b) & ClassBitmask
identifier.TagType = Type(b) & TypeBitmask
if tag := Tag(b) & TagBitmask; tag != HighTag {
// short-form tag
identifier.Tag = tag
return identifier, read, nil
}
// high-tag-number tag
tagBytes := 0
for {
b, err := readByte(reader)
if err != nil {
if Debug {
fmt.Printf("error reading high-tag-number tag byte %d: %v\n", tagBytes, err)
}
return Identifier{}, read, err
}
tagBytes++
read++
// Lowest 7 bits get appended to the tag value (x.690, 8.1.2.4.2.b)
identifier.Tag <<= 7
identifier.Tag |= Tag(b) & HighTagValueBitmask
// First byte may not be all zeros (x.690, 8.1.2.4.2.c)
if tagBytes == 1 && identifier.Tag == 0 {
return Identifier{}, read, errors.New("invalid first high-tag-number tag byte")
}
// Overflow of int64
// TODO: support big int tags?
if tagBytes > 9 {
return Identifier{}, read, errors.New("high-tag-number tag overflow")
}
// Top bit of 0 means this is the last byte in the high-tag-number tag (x.690, 8.1.2.4.2.a)
if Tag(b)&HighTagContinueBitmask == 0 {
break
}
}
return identifier, read, nil
}
func encodeIdentifier(identifier Identifier) []byte {
b := []byte{0x0}
b[0] |= byte(identifier.ClassType)
b[0] |= byte(identifier.TagType)
if identifier.Tag < HighTag {
// Short-form
b[0] |= byte(identifier.Tag)
} else {
// high-tag-number
b[0] |= byte(HighTag)
tag := identifier.Tag
highBit := uint(63)
for {
if tag&(1<<highBit) != 0 {
break
}
highBit--
}
tagBytes := int(math.Ceil(float64(highBit) / 7.0))
for i := tagBytes - 1; i >= 0; i-- {
offset := uint(i) * 7
mask := Tag(0x7f) << offset
tagByte := (tag & mask) >> offset
if i != 0 {
tagByte |= 0x80
}
b = append(b, byte(tagByte))
}
}
return b
}

View File

@ -0,0 +1,81 @@
package ber
import (
"errors"
"fmt"
"io"
)
func readLength(reader io.Reader) (length int, read int, err error) {
// length byte
b, err := readByte(reader)
if err != nil {
if Debug {
fmt.Printf("error reading length byte: %v\n", err)
}
return 0, 0, err
}
read++
switch {
case b == 0xFF:
// Invalid 0xFF (x.600, 8.1.3.5.c)
return 0, read, errors.New("invalid length byte 0xff")
case b == LengthLongFormBitmask:
// Indefinite form, we have to decode packets until we encounter an EOC packet (x.600, 8.1.3.6)
length = LengthIndefinite
case b&LengthLongFormBitmask == 0:
// Short definite form, extract the length from the bottom 7 bits (x.600, 8.1.3.4)
length = int(b) & LengthValueBitmask
case b&LengthLongFormBitmask != 0:
// Long definite form, extract the number of length bytes to follow from the bottom 7 bits (x.600, 8.1.3.5.b)
lengthBytes := int(b) & LengthValueBitmask
// Protect against overflow
// TODO: support big int length?
if lengthBytes > 8 {
return 0, read, errors.New("long-form length overflow")
}
// Accumulate into a 64-bit variable
var length64 int64
for i := 0; i < lengthBytes; i++ {
b, err = readByte(reader)
if err != nil {
if Debug {
fmt.Printf("error reading long-form length byte %d: %v\n", i, err)
}
return 0, read, err
}
read++
// x.600, 8.1.3.5
length64 <<= 8
length64 |= int64(b)
}
// Cast to a platform-specific integer
length = int(length64)
// Ensure we didn't overflow
if int64(length) != length64 {
return 0, read, errors.New("long-form length overflow")
}
default:
return 0, read, errors.New("invalid length byte")
}
return length, read, nil
}
func encodeLength(length int) []byte {
length_bytes := encodeUnsignedInteger(uint64(length))
if length > 127 || len(length_bytes) > 1 {
longFormBytes := []byte{(LengthLongFormBitmask | byte(len(length_bytes)))}
longFormBytes = append(longFormBytes, length_bytes...)
length_bytes = longFormBytes
}
return length_bytes
}

View File

@ -0,0 +1,24 @@
package ber
import "io"
func readByte(reader io.Reader) (byte, error) {
bytes := make([]byte, 1, 1)
_, err := io.ReadFull(reader, bytes)
if err != nil {
if err == io.EOF {
return 0, io.ErrUnexpectedEOF
}
return 0, err
}
return bytes[0], nil
}
func isEOCPacket(p *Packet) bool {
return p != nil &&
p.Tag == TagEOC &&
p.ClassType == ClassUniversal &&
p.TagType == TypePrimitive &&
len(p.ByteValue) == 0 &&
len(p.Children) == 0
}

View File

@ -0,0 +1,335 @@
package activedirectory
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
AD_MESSAGE_ID = 0x99
AD_MESSAGE_ID_QUIT = 0x89
LDAP_VERSION3 = 3
LDAP_SUCCESS = 0x00
LDAP_REQ_BIND = 0x60
LDAP_RES_SEARCH_ENTRY = 0x64
LDAP_REQ_UNBIND = 0x42
LDAP_REQ_SEARCH = 0x63
LDAP_SCOPE_BASE = 0x00
LDAP_DEREF_NEVER = 0x00
LDAP_FILTER_PRESENT = 0x87
LDAP_RES_BIND = 0x61
LDAP_AUTH_SIMPLE = 0x80
AD_TYPE_STR = "supportedCapabilities"
)
type AD_SENDaaa struct {
DefaultCode uint8
PackLenFlag uint8
PacketLen uint32
NextType1 uint8
NextTypeLength1 uint8
MessageId uint32
ProtocolOp uint8
PtLenFlag uint8
PtPacketLen uint32
NextType2 uint8
NextTypeLength2 uint8
Version uint8
NextType3 uint8
NextTypeLength3 uint8
Auth uint8
AuthLength uint8
}
type AD_SEND struct {
DefaultCode uint8
PackLenFlag uint8
PacketLen uint32
NextType1 uint8
NextType1Len uint8
MessageId uint32
ProtocolOp uint8
PtPackLenFlag uint8
PtPacketLen uint32
NextType2 uint8
NextType2Len uint8
NextType3 uint8
NextType3Len uint8
Scope uint8
NextType4 uint8
NextType4Len uint8
DerefAliases uint8
NextType5 uint8
NextType5Len uint8
SizeLimit uint8
NextType6 uint8
NextType6Len uint8
TimeLimit uint8
NextType7 uint8
NextType7Len uint8
TypesOnly uint8
Filter1 uint8
PresentLen uint8
Present [11]byte
DefaultCode2 uint8
Pack2LenFlag uint8
Packet2Len uint32
UnknwonCode8 uint8
ItemLength uint8
AttributeDescription [21]byte
}
type AD_QUIT struct {
DefaultCode uint8
PackLenFlag uint8
PacketLength uint32
NextType1 uint8
NextTypeLength1 uint8
MessageId uint32
ProtocolOp uint8
PtLenFlag uint8
PtPacketLen uint32
}
type AD_RECV struct {
DefaultCode uint8
PackLenFlag uint8
PacketLength uint32
NextType1 uint8
NextType1Len uint8
MessageId uint16
ProtocolOp uint8
PtPackLenFlag uint8
PtPacketLen uint32
NextType2 uint8
NextType2Len uint8
UnknwonCode21 uint8
UnknwonCode22 uint8
UnknwonCode23 uint8
UnknwonCode24 uint8
UnknwonCode25 uint8
UnknwonCode26 uint8
UnknwonCode31 uint8
UnknwonCode32 uint8
UnknwonCode33 uint8
UnknwonCode34 uint8
UnknwonCode35 uint8
UnknwonCode36 uint8
UnknwonCode37 uint8
TypeLength uint8
}
type ActiveDirectoryMatcher struct {
csm.Matchers
}
func (m *ActiveDirectoryMatcher) Key() string {
return "ACTIVEDIRECTORY"
}
func (m *ActiveDirectoryMatcher) Name() string {
return "ActiveDirectory"
}
func (m *ActiveDirectoryMatcher) Meta() csm.Metadata {
return nil
}
func (m *ActiveDirectoryMatcher) IsPrePacket() bool {
return false
}
func (m *ActiveDirectoryMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (m *ActiveDirectoryMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
buf := new(bytes.Buffer)
buf.Write(packet.Buffer)
adRecv := AD_RECV{}
binary.Read(buf, binary.BigEndian, &adRecv)
if adRecv.MessageId != AD_MESSAGE_ID {
return csm.NotMatchedError()
}
if adRecv.ProtocolOp != LDAP_RES_SEARCH_ENTRY {
return csm.NotMatchedError()
}
///AD_TYPE_STR
//
//if(packet->readCount_ < sizeof(AD_RECV) + recv->typeLength) {
// return false;
//}
//char* type = new char[recv->typeLength];
//memcpy(type, packet->buffer_+sizeof(AD_RECV), recv->typeLength);
//std::string typeStr = type;
//
//delete[] type;
//if(typeStr.compare(AD_TYPE_STR) != 0) {
//return false;
//}
return nil
}
func NewMatcher() csm.Matcher {
ls := AD_SEND{
DefaultCode: 0x30,
PackLenFlag: 0x84,
PacketLen: 0x47,
NextType1: 0x02,
NextType1Len: 0x04,
MessageId: AD_MESSAGE_ID,
ProtocolOp: LDAP_REQ_SEARCH,
PtPackLenFlag: 0x84,
PtPacketLen: 0x3b,
NextType2: 0x04,
NextType2Len: 0x00,
NextType3: 0x0a,
NextType3Len: 0x01,
Scope: LDAP_SCOPE_BASE,
NextType4: 0x0a,
NextType4Len: 0x01,
DerefAliases: LDAP_DEREF_NEVER,
NextType5: 0x02,
NextType5Len: 0x01,
SizeLimit: 0,
NextType6: 0x02,
NextType6Len: 0x01,
TimeLimit: 0x78,
NextType7: 0x01,
NextType7Len: 0x01,
TypesOnly: 0,
Filter1: LDAP_FILTER_PRESENT,
PresentLen: 0x0b,
//Present :0000,
DefaultCode2: 0x30,
Pack2LenFlag: 0x84,
Packet2Len: 0x17,
UnknwonCode8: 0x04,
ItemLength: 0x15,
//AttributeDescription:,
}
copy(ls.Present[:], "objectclass")
copy(ls.AttributeDescription[:], AD_TYPE_STR)
mCache := new(bytes.Buffer)
binary.Write(mCache, binary.BigEndian, ls)
sendByte1 := mCache.Bytes()
m := &ActiveDirectoryMatcher{
//sendPackets: make([][]byte, 2),
}
pp := csm.NewPacket(sendByte1, len(sendByte1))
m.AddPacket(pp)
aq := AD_QUIT{
DefaultCode: 0x30,
PackLenFlag: 0x84,
PacketLength: 0x0c,
NextType1: 0x02,
NextTypeLength1: 0x04,
MessageId: AD_MESSAGE_ID_QUIT,
ProtocolOp: LDAP_REQ_UNBIND,
PtLenFlag: 0x84,
PtPacketLen: 0x00,
}
lqBuffer := new(bytes.Buffer)
binary.Write(lqBuffer, binary.BigEndian, aq)
quBytes := lqBuffer.Bytes()
pp2 := csm.NewPacket(quBytes, len(quBytes))
m.AddPacket(pp2)
return m
}

View File

@ -0,0 +1,35 @@
package activedirectory
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestAD(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.10:10389")
if err != nil {
t.Error(err)
return
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

92
cassandra/cassandra.go Normal file
View File

@ -0,0 +1,92 @@
package cassandra
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
)
type cassandra struct {
Version uint8
Flags uint8
Stream uint16
Opcode uint8
Length uint32
}
type CassandraMatcher struct {
csm.Matchers
}
func (m *CassandraMatcher) Key() string {
return "CASSANDRA"
}
func (m *CassandraMatcher) Name() string {
return "Cassandra"
}
func (m *CassandraMatcher) Meta() csm.Metadata {
return nil
}
func (m *CassandraMatcher) IsPrePacket() bool {
return false
}
func (m *CassandraMatcher) HasResponse(index int) bool {
return true
}
func (m *CassandraMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (m *CassandraMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
c := cassandra{}
if err := binary.Read(reader, binary.BigEndian, &c); err != nil {
return err
}
if c.Version != 0x84 {
return csm.NotMatchedError()
}
if c.Flags != 0x00 {
return csm.NotMatchedError()
}
if c.Stream != 0x00 {
return csm.NotMatchedError()
}
if c.Opcode != 0x06 {
return csm.NotMatchedError()
}
return nil
}
func NewMatcher() csm.Matcher {
m := &CassandraMatcher{}
c := cassandra{
Version: 4,
Flags: 0,
Stream: 0,
Opcode: 5,
Length: 0,
}
writer := new(bytes.Buffer)
binary.Write(writer, binary.LittleEndian, c)
m.AddPacket(csm.NewPacket(writer.Bytes(), writer.Len()))
return m
}

View File

@ -0,0 +1,34 @@
package cassandra
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestCassandra(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.103:8080")
if err != nil {
t.Error(err)
return
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Meta())
}

150
dns/dns.go Normal file
View File

@ -0,0 +1,150 @@
package dns
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
)
type Dns_frame_header struct {
Transaction_id uint16
Flags uint16
Questions uint16
Answer_rrs uint16
Authority_rrs uint16
Additional_rrs uint16
}
type Dns_query_section struct {
Name uint8
Query_type uint16
Class_type uint16
}
type Dns_authority_section struct {
Name uint8
Auth_type uint16
Class_type uint16
Time_to_live uint32
Data_length uint16
Primary_name_server [20]uint8
Responsible_authority_mailbox [24]uint8
Serial_number uint32
Refresh_interval uint32
Retry_interval uint32
Expire_limit uint32
Minium_ttl uint32
}
type DNSMatcher struct {
csm.Matchers
}
func (t *DNSMatcher) Key() string {
return "DNS"
}
func (t *DNSMatcher) String() string {
return "DNS"
}
func (t *DNSMatcher) Meta() csm.Metadata {
return nil
}
func (t *DNSMatcher) IsPrePacket() bool {
return false
}
func (t *DNSMatcher) HasResponse(index int) bool {
return true
}
func (t *DNSMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (t *DNSMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
h := Dns_frame_header{}
if err := binary.Read(reader, binary.BigEndian, &h); err != nil {
return err
}
if h.Transaction_id != 0x2a88 {
return csm.NotMatchedError()
}
if h.Flags != 0x8180 && h.Flags != 0x8182 {
return csm.NotMatchedError()
}
if h.Questions != 1 {
return csm.NotMatchedError()
}
if h.Answer_rrs != 0 {
return csm.NotMatchedError()
}
if h.Authority_rrs != 0 && h.Authority_rrs != 1 {
return csm.NotMatchedError()
}
if h.Additional_rrs != 0 && h.Additional_rrs != 1 {
return csm.NotMatchedError()
}
q := Dns_query_section{}
if err := binary.Read(reader, binary.BigEndian, &q); err != nil {
return csm.NotMatchedError()
}
if q.Name != 0 {
return csm.NotMatchedError()
}
if q.Query_type != 1 {
return csm.NotMatchedError()
}
if q.Class_type != 1 {
return csm.NotMatchedError()
}
return nil
}
func (t *DNSMatcher) IsSend(port int) bool {
if 53 == port {
return true
}
return false
}
func NewMatcher() csm.UDPMatcher {
m := &DNSMatcher{}
header := Dns_frame_header{
Transaction_id: 0x2a88,
Flags: 0x0100,
Questions: 1,
Answer_rrs: 0,
Authority_rrs: 0,
Additional_rrs: 0,
}
query := Dns_query_section{
Name: 0,
Query_type: 1,
Class_type: 1,
}
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, header)
binary.Write(buf, binary.BigEndian, query)
m.AddPacket(csm.NewPacket(buf.Bytes(), buf.Len()))
return m
}

33
dns/dns_test.go Normal file
View File

@ -0,0 +1,33 @@
package dns
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestDns(t *testing.T) {
m := NewMatcher()
conn, _ := net.Dial("udp", "192.168.1.254:53")
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
if m.IsSend(53) != true {
t.Error("not port")
}
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, 0, p); err != nil {
t.Error(err)
}
}
t.Log(m.Meta())
}

View File

@ -0,0 +1,134 @@
package elasticsearch
import (
"bufio"
"encoding/json"
"strconv"
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
type ElasticSearchMatcher struct {
csm.Matchers
meta csm.Metadata
}
func (es *ElasticSearchMatcher) Key() string {
return "ElasticSearch"
}
func (es *ElasticSearchMatcher) Name() string {
name := "ElasticSearch"
if v, ok := es.meta["number"]; ok {
name = name + " (" + v + ")"
}
return name
}
func (es *ElasticSearchMatcher) Meta() csm.Metadata {
return es.meta
}
func (es *ElasticSearchMatcher) IsPrePacket() bool {
return false
}
func (es *ElasticSearchMatcher) HasResponse(index int) bool {
return true
}
func (es *ElasticSearchMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (es *ElasticSearchMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
str := string(packet.Buffer)
hnb := strings.Split(str, "\r\n\r\n")
header := hnb[0]
body := hnb[1]
lineNo := 0
scanner := bufio.NewScanner(strings.NewReader(header))
contentLen := 0
for scanner.Scan() {
line := scanner.Text()
if strings.Compare(line, "") == 0 {
continue
}
if lineNo == 0 && !strings.HasPrefix(line, "HTTP/") {
return csm.NotMatchedError()
}
if strings.Contains(line, ":") {
kv := strings.Split(line, ": ")
if kv[0] == "content-type" && !strings.Contains(kv[1], "application/json") {
return csm.NotMatchedError()
}
if kv[0] == "content-length" {
len, err := strconv.Atoi(kv[1])
if err != nil {
return csm.NotMatchedError()
}
contentLen = len
}
}
lineNo++
}
content := body[:contentLen]
if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
return csm.NotMatchedError()
}
es.parseJson(content)
if _, ok := es.meta["cluster_name"]; !ok {
return csm.NotMatchedError()
}
if _, ok := es.meta["cluster_uuid"]; !ok {
return csm.NotMatchedError()
}
return nil
}
func (es *ElasticSearchMatcher) parseJson(jsonstr string) error {
jsonMap := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonstr), &jsonMap)
if err != nil {
return err
}
es.dumpMap(jsonMap)
return nil
}
func (es *ElasticSearchMatcher) dumpMap(m map[string]interface{}) {
for k, v := range m {
if mv, ok := v.(map[string]interface{}); ok {
es.dumpMap(mv)
} else {
s, ok := v.(string)
if ok {
es.meta[k] = s
}
}
}
}
func NewMatcher() csm.Matcher {
m := &ElasticSearchMatcher{}
m.meta = csm.NewMetadata()
reqStr := "GET / HTTP/1.1\r\n\r\n"
byte := make([]byte, len(reqStr))
copy(byte[:], reqStr)
m.AddPacket(csm.NewPacket(byte, len(reqStr)))
return m
}

View File

@ -0,0 +1,35 @@
package elasticsearch
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestES(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "localhost:9200")
if err != nil {
t.Errorf("ERR %s", err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

96
ftp/ftp.go Normal file
View File

@ -0,0 +1,96 @@
package ftp
import (
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
// FTP Status codes, defined in RFC 959
const (
statusReadyServer = "120"
statusOK = "200"
statusNewConnectOK = "220"
statusSystemNameOK = "215"
statusCloseConnect = "221"
statusUnkownCMD = "202"
statusTlsUseOK = "234"
statusCloseControlConnect = "421"
statusSyntaxErr = "500"
statusParamSyntaxErr = "501"
statusNotUseCMD = "502"
statusIncorrectCMD = "503"
statusNotLoggedIn = "530"
statusTlsNotUse = "534"
statusNeedUserId = "332"
)
type FTPMatcher struct {
csm.Matchers
meta csm.Metadata
}
func (ftp *FTPMatcher) Key() string {
return "FTP"
}
func (ftp *FTPMatcher) Name() string {
return "FTP"
}
func (ftp *FTPMatcher) Meta() csm.Metadata {
return ftp.meta
}
func (ftp *FTPMatcher) IsPrePacket() bool {
return true
}
func (ftp *FTPMatcher) HasResponse(index int) bool {
return true
}
func (ftp *FTPMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (ftp *FTPMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
str := strings.Split(string(packet.Buffer), "\r\n")[0]
if len(str) < 4 {
return csm.NotMatchedError()
}
code := str[:3]
if index == 0 {
switch code {
case statusNewConnectOK, statusReadyServer:
ftp.meta["comment"] = str[4:]
return nil
}
} else if index == 1 {
switch code {
case statusCloseConnect, statusSyntaxErr:
return nil
}
}
return csm.NotMatchedError()
}
func NewMatcher() csm.Matcher {
m := &FTPMatcher{}
m.meta = csm.NewMetadata()
quitStr := "QUIT\r\n"
quitByte := make([]byte, len(quitStr))
copy(quitByte[:], quitStr)
m.AddPacket(csm.NewPacket(quitByte, len(quitStr)))
return m
}

79
ftp/ftp_test.go Normal file
View File

@ -0,0 +1,79 @@
package ftp
import (
"crypto/tls"
"net"
"testing"
"time"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestFTP(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.229:21")
if err != nil {
t.Error(err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
return
}
}
t.Log(m.Name())
t.Log(m.Meta())
}
func TestFTPS(t *testing.T) {
m := NewMatcher()
dialer := &net.Dialer{
Timeout: 5 * time.Second,
}
conn, err := tls.DialWithDialer(
dialer,
"tcp",
"192.168.1.201:990",
&tls.Config{
InsecureSkipVerify: true,
ServerName: "192.168.1.201",
},
)
if err != nil {
t.Error(err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

91
http/http.go Normal file
View File

@ -0,0 +1,91 @@
package http
import (
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
type HTTPMatcher struct {
csm.Matchers
meta csm.Metadata
}
func (h *HTTPMatcher) Key() string {
return "HTTP"
}
func (h *HTTPMatcher) Name() string {
name := "HTTP"
if v, ok := h.meta["server"]; ok {
name = name + " (" + v + ")"
}
return name
}
func (h *HTTPMatcher) Meta() csm.Metadata {
return h.meta
}
func (h *HTTPMatcher) IsPrePacket() bool {
return false
}
func (h *HTTPMatcher) HasResponse(index int) bool {
return true
}
func (h *HTTPMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (h *HTTPMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
str := string(packet.Buffer)
elems := strings.Split(str, "\r\n")
if len(elems) <= 0 || 9 > len(elems[0]) {
return csm.NotMatchedError()
}
protocol := (elems[0])[:8]
if !strings.HasPrefix(protocol, "HTTP/") {
return csm.NotMatchedError()
}
serverName := ""
for _, valueStr := range elems {
tempElems := strings.Split(valueStr, ":")
if 0 == strings.Compare(tempElems[0], "Server") {
serverName = strings.TrimSpace(tempElems[1])
break
}
}
h.meta["protocol_version"] = protocol
if len(serverName) > 0 {
h.meta["server"] = serverName
}
return nil
}
func NewMatcher() csm.Matcher {
m := &HTTPMatcher{}
m.meta = csm.NewMetadata()
reqStr := "GET / HTTP/1.1\r\n\r\n"
byte := make([]byte, len(reqStr))
copy(byte[:], reqStr)
m.AddPacket(csm.NewPacket(byte, len(reqStr)))
return m
}

76
http/http_test.go Normal file
View File

@ -0,0 +1,76 @@
package http
import (
"crypto/tls"
"net"
"testing"
"time"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestHTTP(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
t.Errorf("ERR %s", err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}
func TestHTTPS(t *testing.T) {
m := NewMatcher()
dialer := &net.Dialer{
Timeout: 5 * time.Second,
}
conn, err := tls.DialWithDialer(
dialer,
"tcp",
"192.168.1.1:443",
&tls.Config{
InsecureSkipVerify: true,
ServerName: "192.168.1.1",
},
)
if err != nil {
t.Errorf("ERR %s", err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

84
imap/imap.go Normal file
View File

@ -0,0 +1,84 @@
package imap
import (
csm "git.loafle.net/commons/service_matcher-go"
)
const (
PRE_COMPARE_STR = "* OK"
SEND_COMPARE_STR = "* BYE"
)
type IMAPMatcher struct {
csm.Matchers
}
func (i *IMAPMatcher) Key() string {
return "IMAP"
}
func (i *IMAPMatcher) String() string {
return "IMAP"
}
func (i *IMAPMatcher) IsPrePacket() bool {
return true
}
func (i *IMAPMatcher) HasResponse(index int) bool {
return true
}
func (i *IMAPMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (i *IMAPMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) bool {
switch index {
case 0:
recvStr := string(packet.Buffer)
if len(recvStr) < 3 {
return false
}
compareStr := recvStr[0:4]
if compareStr == PRE_COMPARE_STR {
return true
}
case 1:
recvStr := string(packet.Buffer)
if len(recvStr) < 5 {
return false
}
compareStr := recvStr[0:5]
if compareStr == SEND_COMPARE_STR {
return true
}
}
return false
}
func NewMatcher() csm.Matcher {
m := &IMAPMatcher{}
reqStr := "A0001 LOGOUT\r\n"
byte := make([]byte, len(reqStr))
copy(byte[:], reqStr)
m.AddPacket(csm.NewPacket(byte, len(reqStr)))
return m
}

149
imap/imap_test.go Normal file
View File

@ -0,0 +1,149 @@
package imap
import (
"crypto/tls"
"fmt"
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func ImapRun(client net.Conn, t *testing.T) {
lm := NewMatcher()
//port := types.NewPort("143", types.NewHost("192.168.1.215"), types.TYPE_TCP)
//
//scanInfo := types.NewServiceScanInfo(port)
//
//var ipport string
//ipport = port.Host.Ip + ":" + string(port.Port)
//
//fmt.Println(ipport)
//client, _ := net.Dial("tcp", ipport)
//defer client.Close()
bytett := make([]byte, 1024)
rr, _ := client.Read(bytett)
bb := lm.Match(nil, 0, csm.NewPacket(bytett, rr))
if bb {
t.Log("good!")
}
fmt.Println(lm.PacketCount())
for ii := 0; ii < lm.PacketCount(); ii++ {
pack := lm.Packet(ii)
//fmt.Println(pack)
client.Write(pack.Buffer)
bytes := make([]byte, 1024)
read, _ := client.Read(bytes)
fmt.Println(cap(bytes))
//fmt.Println(bytes)
b := lm.Match(nil, ii+1, csm.NewPacket(bytes, read))
if b {
t.Log("send Good!")
}
}
}
func TestIMapTls(t *testing.T) {
conn, _ := tls.Dial(
"tcp",
"192.168.1.15:993",
&tls.Config{
InsecureSkipVerify: true,
ServerName: "192.168.1.15",
},
)
defer conn.Close()
ImapRun(conn, t)
}
func TestIMapNormal(t *testing.T) {
client, err := net.Dial("tcp", "192.168.1.15:143")
if err != nil {
t.Fatal(err)
}
defer client.Close()
ImapRun(client, t)
}
func TestImap(t *testing.T) {
lm := NewMatcher()
//port := types.NewPort("143", types.NewHost("192.168.1.215"), types.TYPE_TCP)
//scanInfo := scaninfo.NewServiceScanInfo(port)
var ipport string
//ipport = port.Host.Ip + ":" + port.Port_
fmt.Println(ipport)
client, _ := net.Dial("tcp", ipport)
defer client.Close()
bytett := make([]byte, 1024)
rr, _ := client.Read(bytett)
//bb := lm.Match(0, csm.NewPacket(bytett, rr), scanInfo)
bb := lm.Match(nil, 0, csm.NewPacket(bytett, rr))
if bb {
t.Log("good!")
}
fmt.Println(lm.PacketCount())
for ii := 0; ii < lm.PacketCount(); ii++ {
pack := lm.Packet(ii)
//fmt.Println(pack)
client.Write(pack.Buffer)
bytes := make([]byte, 1024)
read, _ := client.Read(bytes)
fmt.Println(cap(bytes))
//fmt.Println(bytes)
b := lm.Match(nil, ii+1, csm.NewPacket(bytes, read))
if b {
t.Log("send Good!")
}
}
//t.Log(scanInfo)
}

82
ldap/ldap.go Normal file
View File

@ -0,0 +1,82 @@
package ldap
import (
"math/rand"
"time"
csm "git.loafle.net/commons/service_matcher-go"
ber "gopkg.in/asn1-ber.v1"
)
const (
ApplicationBindRequest = 0
ApplicationBindResponse = 1
)
type LDAPMatcher struct {
csm.Matchers
reqID int64
}
func (l *LDAPMatcher) Key() string {
return "LDAP"
}
func (l *LDAPMatcher) Name() string {
return "LDAP"
}
func (l *LDAPMatcher) Meta() csm.Metadata {
return nil
}
func (l *LDAPMatcher) IsPrePacket() bool {
return false
}
func (l *LDAPMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (l *LDAPMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
p := ber.DecodePacket(packet.Buffer)
respID, ok := p.Children[0].Value.(int64)
if !ok {
return csm.NotMatchedError()
}
if respID != l.reqID {
return csm.NotMatchedError()
}
if p.Children[1].Tag != ApplicationBindResponse {
return csm.NotMatchedError()
}
return nil
}
func NewMatcher() csm.Matcher {
m := &LDAPMatcher{}
rand.Seed(time.Now().UnixNano())
m.reqID = rand.Int63n(1000)
p := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
p.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, m.reqID, "MessageID"))
bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 2, "Version"))
bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "LOAFLEOVERFLOW", "User Name"))
bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "LOAFLEOVERFLOW", "Password"))
p.AppendChild(bindRequest)
m.AddPacket(csm.NewPacket(p.Bytes(), len(p.Bytes())))
return m
}

80
ldap/ldap_test.go Normal file
View File

@ -0,0 +1,80 @@
package ldap
import (
"net"
"testing"
"time"
"math/rand"
csm "git.loafle.net/commons/service_matcher-go"
"gopkg.in/asn1-ber.v1"
)
func TestLdap(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.10:10389")
if err != nil {
t.Errorf("ERR %s", err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}
func TestBer(t *testing.T) {
const (
ApplicationBindRequest = 0
ApplicationBindResponse = 1
)
rand.Seed(time.Now().UnixNano())
reqID := rand.Int63n(1000)
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, reqID, "MessageID"))
bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 2, "Version"))
bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "LOAFLEOVERFLOW", "User Name"))
bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "LOAFLEOVERFLOW", "Password"))
packet.AppendChild(bindRequest)
conn, err := net.Dial("tcp", "192.168.1.10:10389")
if err != nil {
t.Errorf("ERR %s", err)
}
conn.Write(packet.Bytes())
p, err := ber.ReadPacket(conn)
if err != nil {
t.Errorf("ERR %s", err)
}
respID, ok := p.Children[0].Value.(int64)
if !ok {
t.Errorf("%s", "cannot cast response ID")
}
if respID != reqID {
t.Error("not matched message ID")
}
if p.Children[1].Tag != ApplicationBindResponse {
t.Error("Not match")
}
}

59
lpd/lpd.go Normal file
View File

@ -0,0 +1,59 @@
package lpd
import (
csm "git.loafle.net/commons/service_matcher-go"
)
type LPDMatcher struct {
csm.Matchers
meta csm.Metadata
}
func (l *LPDMatcher) Key() string {
return "LPD"
}
func (l *LPDMatcher) Meta() csm.Metadata {
return l.meta
}
func (l *LPDMatcher) Name() string {
return "LPD (Printer)"
}
func (l *LPDMatcher) IsPrePacket() bool {
return false
}
func (l *LPDMatcher) HasResponse(index int) bool {
return true
}
func (l *LPDMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (l *LPDMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
if packet.Len != 1 {
return csm.NotMatchedError()
}
return nil
}
func NewMatcher() csm.Matcher {
m := &LPDMatcher{}
reqStr := "GET / HTTP/1.1\r\n\r\n"
rbyte := make([]byte, len(reqStr))
copy(rbyte[:], reqStr)
m.AddPacket(csm.NewPacket(rbyte, len(reqStr)))
return m
}

36
lpd/lpd_test.go Normal file
View File

@ -0,0 +1,36 @@
package lpd
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestLDP(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.99:515")
if err != nil {
t.Errorf("ERR %s", err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

85
matcher.go Normal file
View File

@ -0,0 +1,85 @@
package matcher
import "fmt"
type Matcher interface {
Key() string
Name() string
Meta() Metadata
IsPrePacket() bool
PacketCount() int
Packet(index int) *Packet
HasResponse(index int) bool
Match(info MatchInfo, index int, packet *Packet) error
}
type Metadata map[string]string
func NewMetadata() Metadata {
return make(map[string]string)
}
type UDPMatcher interface {
Matcher
IsSend(port int) bool
}
type Matchers struct {
packets []*Packet
}
func (m *Matchers) PacketCount() int {
return len(m.packets)
}
func (m *Matchers) Packet(index int) *Packet {
return m.packets[index]
}
func (m *Matchers) HasResponse(index int) bool {
return len(m.packets)-1 > index
}
func (m *Matchers) AddPacket(packet *Packet) {
m.packets = append(m.packets, packet)
}
type MatchInfo interface {
IP() string
Port() int
}
type simpleMatchInfo struct {
ip string
port int
}
func (mi *simpleMatchInfo) IP() string {
return mi.ip
}
func (mi *simpleMatchInfo) Port() int {
return mi.port
}
func NewMatchInfo(ip string, port int) MatchInfo {
return &simpleMatchInfo{
ip: ip,
port: port,
}
}
const (
noPacketReceived = 1000 + iota
notMatched
)
func NoPacketReceivedError() error {
return fmt.Errorf("[%d] %s", noPacketReceived, "No packet received.")
}
func NotMatchedError() error {
return fmt.Errorf("[%d] %s", notMatched, "Not matched packet.")
}

145
mongodb/mongodb.go Normal file
View File

@ -0,0 +1,145 @@
package mongodb
import (
"bytes"
"encoding/binary"
"math/rand"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
MONGO_OP_REQUEST uint32 = 2004
MONGO_OP_REPLY uint32 = 1
MONGO_FCNAME string = "admin.$cmd"
MONGO_ELEMENT string = "ismaster"
)
var MONGO_REQUEST_ID uint32
type MsgHeader struct {
MessageLength uint32
RequestId uint32
ResponseTo uint32
OpCode uint32
}
type OP_request struct {
Header MsgHeader
Flags uint32
FullCollectionName [11]byte
NumberToSkip uint32
NumberToReturn int32
DocumentLength uint32
Type_ uint8
Element [9]byte
Value uint8
_ uint8
}
type OP_reply struct {
Header MsgHeader
ResponseFlags int32
CursorID int64
StartingFrom int32
NumberReturned int32
Documents [512]byte
}
type MongoDBMatcher struct {
csm.Matchers
meta csm.Metadata
}
func (m *MongoDBMatcher) Key() string {
return "MONGODB"
}
func (m *MongoDBMatcher) Name() string {
return "MongoDB"
}
func (m *MongoDBMatcher) Meta() csm.Metadata {
return m.meta
}
func (m *MongoDBMatcher) IsPrePacket() bool {
return false
}
func (m *MongoDBMatcher) HasResponse(index int) bool {
return true
}
func (m *MongoDBMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (m *MongoDBMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
reply := OP_reply{}
if err := binary.Read(reader, binary.LittleEndian, &reply); err != nil {
return err
}
if uint32(packet.Len) != reply.Header.MessageLength {
return csm.NotMatchedError()
}
if reply.Header.ResponseTo != MONGO_REQUEST_ID {
return csm.NotMatchedError()
}
if reply.Header.OpCode != MONGO_OP_REPLY {
return csm.NotMatchedError()
}
return nil
}
func NewMatcher() csm.Matcher {
mm := &MongoDBMatcher{}
mm.meta = csm.NewMetadata()
tempBuf := new(bytes.Buffer)
binary.Write(tempBuf, binary.BigEndian, OP_request{})
var fcn [11]byte
copy(fcn[:], MONGO_FCNAME)
var elem [9]byte
copy(elem[:], MONGO_ELEMENT)
MONGO_REQUEST_ID = rand.Uint32()
m := OP_request{
Header: MsgHeader{
MessageLength: uint32(len(tempBuf.Bytes())),
RequestId: MONGO_REQUEST_ID,
ResponseTo: 0,
OpCode: MONGO_OP_REQUEST,
},
Flags: 0,
FullCollectionName: fcn,
NumberToSkip: 0,
NumberToReturn: -1,
DocumentLength: 16,
Type_: 0x08,
Element: elem,
Value: 1,
}
writer := new(bytes.Buffer)
binary.Write(writer, binary.LittleEndian, m)
mm.AddPacket(csm.NewPacket(writer.Bytes(), writer.Len()))
return mm
}

55
mongodb/mongodb_test.go Normal file
View File

@ -0,0 +1,55 @@
package mongodb
import (
"crypto/tls"
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestMongoNor(t *testing.T) {
conn, _ := net.Dial("tcp", "192.168.1.229:27036")
defer conn.Close()
MongoRun(conn, t)
}
func TestMongoTLS(t *testing.T) {
conn, _ := tls.Dial(
"tcp",
"192.168.1.229:27036",
&tls.Config{
InsecureSkipVerify: true,
ServerName: "192.168.1.229",
},
)
defer conn.Close()
MongoRun(conn, t)
}
func MongoRun(conn net.Conn, t *testing.T) {
m := NewMatcher()
for i := 0; i < m.PacketCount(); i++ {
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

110
mysql/mysql.go Normal file
View File

@ -0,0 +1,110 @@
package mysql
import (
"bytes"
"encoding/binary"
"io"
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
type MySqlMatcher struct {
csm.Matchers
meta csm.Metadata
}
func (m *MySqlMatcher) Key() string {
return "MYSQL"
}
func (m *MySqlMatcher) Name() string {
name := "MySQL"
if v, ok := m.meta["version"]; ok {
if strings.Contains(v, "MariaDB") {
name = "MariaDB"
}
name = name + " (" + v + ")"
}
return name
}
func (m *MySqlMatcher) Meta() csm.Metadata {
return m.meta
}
func (m *MySqlMatcher) IsPrePacket() bool {
return true
}
func (m *MySqlMatcher) HasResponse(index int) bool {
return true
}
func (m *MySqlMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
type serverSettings struct {
protocol byte
version string
flags uint32
charset uint8
scrambleBuff []byte
threadID uint32
keepalive int64
}
func (m *MySqlMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
buf := bytes.NewBuffer(packet.Buffer[:3])
packetLen, _ := binary.ReadUvarint(buf)
if packetLen != uint64(packet.Len-4) {
return csm.NotMatchedError()
}
pos := 4
p := new(serverSettings)
p.protocol = packet.Buffer[pos]
if p.protocol != 9 && p.protocol != 10 {
return csm.NotMatchedError()
}
pos++
slice, err := readSlice(packet.Buffer[pos:], 0x00)
if err != nil {
return csm.NotMatchedError()
}
m.meta["version"] = string(slice)
pos += len(slice) + 1
p.threadID = bytesToUint32(packet.Buffer[pos : pos+4])
pos += 4
return nil
}
func readSlice(data []byte, delim byte) (slice []byte, e error) {
pos := bytes.IndexByte(data, delim)
if pos > -1 {
slice = data[:pos]
} else {
slice = data
e = io.EOF
}
return
}
func bytesToUint32(b []byte) (n uint32) {
for i := uint8(0); i < 4; i++ {
n |= uint32(b[i]) << (i * 8)
}
return
}
func NewMatcher() csm.Matcher {
m := &MySqlMatcher{}
m.meta = csm.NewMetadata()
return m
}

28
mysql/mysql_test.go Normal file
View File

@ -0,0 +1,28 @@
package mysql
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestMySql(t *testing.T) {
m := NewMatcher()
conn, _ := net.Dial("tcp", "192.168.1.201:23306")
defer conn.Close()
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, 0, p); err != nil {
t.Error(err)
}
t.Log(m.Name())
t.Log(m.Meta())
}

109
nbss/nbss.go Normal file
View File

@ -0,0 +1,109 @@
package nbss
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
NBSS_SESSION_REQUEST uint8 = 0x81
NBSS_POSITIVE_SESSION_RESPONSE uint8 = 0x82
NBSS_NEGATIVE_SESSION_RESPONSE uint8 = 0x83
)
type NBSS struct {
MsgType uint8
Flags uint8 //0-6 : Reserved, must be zero. 7 : Length extension.
Length uint16
CalledNameLen uint8
CalledName [16]uint16
_ uint8
CallingNameLen uint8
CallingName [16]uint16
_ uint8
}
type NBSSMatcher struct {
csm.Matchers
}
func (t *NBSSMatcher) Key() string {
return "NBSS"
}
func (t *NBSSMatcher) Name() string {
return "NBSS"
}
func (t *NBSSMatcher) Meta() csm.Metadata {
return nil
}
func (t *NBSSMatcher) IsPrePacket() bool {
return false
}
func (t *NBSSMatcher) HasResponse(index int) bool {
return true
}
func (t *NBSSMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (t *NBSSMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
n := NBSS{}
if err := binary.Read(reader, binary.LittleEndian, &n); err != nil {
return csm.NotMatchedError()
}
if NBSS_NEGATIVE_SESSION_RESPONSE != n.MsgType {
return csm.NotMatchedError()
}
return nil
}
func NewMatcher() csm.Matcher {
m := &NBSSMatcher{}
tempBuf := new(bytes.Buffer)
binary.Write(tempBuf, binary.BigEndian, NBSS{})
query := NBSS{
MsgType: NBSS_SESSION_REQUEST,
Flags: 0x00,
Length: 0x4400,
CalledNameLen: 0x20,
CallingNameLen: 0x20,
}
query.CalledName[0] = 0x4D45 // L
query.CalledName[1] = 0x4745 // F
query.CallingName[0] = 0x4D45
query.CallingName[1] = 0x4745
for i := 2; i < 16; i++ {
query.CalledName[i] = 0x4143 //Space
query.CallingName[i] = 0x4143
}
writer := new(bytes.Buffer)
binary.Write(writer, binary.LittleEndian, query)
m.AddPacket(csm.NewPacket(writer.Bytes(), writer.Len()))
return m
}

32
nbss/nbss_test.go Normal file
View File

@ -0,0 +1,32 @@
package nbss
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestNBSS(t *testing.T) {
m := NewMatcher()
conn, _ := net.Dial("tcp", "192.168.1.102:139")
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, 0, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

188
oracle/oracle.go Normal file
View File

@ -0,0 +1,188 @@
package oracle
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
)
type OracleMatcher struct {
csm.Matchers
}
func (o *OracleMatcher) Key() string {
return "ORACLE"
}
func (o *OracleMatcher) String() string {
return "Oracle"
}
func (o *OracleMatcher) IsPrePacket() bool {
return false
}
func (o *OracleMatcher) HasResponse(index int) bool {
return true
}
func (o *OracleMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (o *OracleMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) bool {
if packet == nil {
return false
}
header := header_packet{}
refuse := body_refuse{}
buf := new(bytes.Buffer)
buf.Write(packet.Buffer)
binary.Read(buf, binary.BigEndian, &header)
binary.Read(buf, binary.BigEndian, &refuse)
//fmt.Println(header)
//fmt.Println(refuse)
if header.Check_sum != 0 {
return false
}
if header.Types != 4 {
return false
}
if header.Reserved_byte != 0 {
return false
}
if header.Header_sum != 0 {
return false
}
if refuse.Reason_user != 34 {
return false
}
if refuse.Reason_system != 0 {
return false
}
var dataLen int = int(refuse.Data_len)
if dataLen != packet.Len-12 { //
if dataLen != packet.Len-22 { // morformed packet error not user not service
return false
}
}
return true
}
func NewMatcher() csm.Matcher {
m := &OracleMatcher{}
hp := header_packet{
Length: 247,
Check_sum: 0,
Types: 1,
Reserved_byte: 0,
Header_sum: 0,
}
bc := body_connect{
Version: 315,
Version_compatible: 300,
//Service_options:
Session_unit_size: 8192,
Maxumum_trans_data_unit_size: 65535,
//Nt_protocol_characteristics:
Line_turnaround_value: 0,
Value_of_1_in_hardware: 1,
Length_of_connect_data: 177,
Offset_to_connect_data: 70,
Maximum_receivable_connect_data: 0,
//Connect_flag0:
//Connect_flag1:
Trace_cross_facility_item_1: 0,
Trace_cross_facility_item_2: 0,
Trace_unique_connection_id: 0,
//Unknown_data:
//Connect_data:
}
bc.Service_options[0] = 0x0c
bc.Service_options[1] = 0x41
bc.Nt_protocol_characteristics[0] = 0x4f
bc.Nt_protocol_characteristics[1] = 0x98
bc.Connect_flag0 = 0x81
bc.Connect_flag1 = 0x81
bc.Unknown_data[10] = 0x20
bc.Unknown_data[13] = 0x20
conDataStr := "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.30)(PORT=1521))(CONNECT_DATA=(CID=(PROGRAM=JDBC Thin Client)(HOST=__jdbc__)(USER=loafle.match))(SERVICE_NAME=oracle.loafle.com1)))"
//conDataStr := "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.30)(PORT=1521))(CONNECT_DATA=(CID=(PROGRAM=JDBC Thin Client)(HOST=__jdbc__)(USER=Jackdaw))(SERVICE_NAME=oracle.loafle.co1m)))"
connect_data := make([]byte, len(conDataStr))
copy(connect_data, conDataStr)
hpBuf := new(bytes.Buffer)
binary.Write(hpBuf, binary.BigEndian, hp)
hpBt := hpBuf.Bytes()
bcBuf := new(bytes.Buffer)
binary.Write(bcBuf, binary.BigEndian, bc)
bcBt := bcBuf.Bytes()
byteSize := len(hpBt) + len(bcBt) + len(conDataStr)
sendByte := make([]byte, byteSize)
copy(sendByte[0:], hpBt)
copy(sendByte[len(hpBt):], bcBt)
copy(sendByte[len(hpBt)+len(bcBt):], connect_data)
m.AddPacket(csm.NewPacket(sendByte, byteSize))
return m
}
type header_packet struct {
Length uint16
Check_sum uint16
Types byte
Reserved_byte byte
Header_sum uint16
}
type body_connect struct {
Version uint16
Version_compatible uint16
Service_options [2]byte
Session_unit_size uint16
Maxumum_trans_data_unit_size uint16
Nt_protocol_characteristics [2]byte
Line_turnaround_value uint16
Value_of_1_in_hardware uint16
Length_of_connect_data uint16
Offset_to_connect_data uint16
Maximum_receivable_connect_data uint32
Connect_flag0 byte
Connect_flag1 byte
Trace_cross_facility_item_1 uint32
Trace_cross_facility_item_2 uint32
Trace_unique_connection_id uint64
Unknown_data [20]byte
//Connect_data []byte
}
type body_refuse struct {
Reason_user byte
Reason_system byte
Data_len uint16
//Data []byte
}

47
oracle/oracle_test.go Normal file
View File

@ -0,0 +1,47 @@
package oracle
import (
csm "git.loafle.net/commons/service_matcher-go"
"net"
"testing"
)
func TestOracle(t *testing.T) {
lm := NewOracleMatcher()
//port := types.NewPort("1521", types.NewHost("192.168.1.30"), types.TYPE_TCP)
//scanInfo := scaninfo.NewServiceScanInfo(port)
//var ipport string
//ipport = port.Host.Ip + ":" + string(port.Port)
client, _ := net.Dial("tcp", "192.168.1.15:1521")
defer client.Close()
t.Log(lm.PacketCount())
for ii := 0; ii < lm.PacketCount(); ii++ {
pack := lm.Packet(ii)
t.Log(pack)
client.Write(pack.Buffer)
bytes := make([]byte, 1024)
read, _ := client.Read(bytes)
t.Log(bytes)
b := lm.Match(ii, csm.NewPacket(bytes, read), nil)
if b {
t.Log("Good")
}
}
}

13
packet.go Normal file
View File

@ -0,0 +1,13 @@
package matcher
type Packet struct {
Buffer []byte
Len int
}
func NewPacket(buf []byte, len int) *Packet {
return &Packet{
Buffer: buf,
Len: len,
}
}

69
pop/pop.go Normal file
View File

@ -0,0 +1,69 @@
package pop
import (
csm "git.loafle.net/commons/service_matcher-go"
)
const (
COMPARE_STR = "+OK"
)
type POPMatcher struct {
csm.Matchers
}
func (p *POPMatcher) Key() string {
return "POP3"
}
func (p *POPMatcher) String() string {
return "POP3"
}
func (p *POPMatcher) IsPrePacket() bool {
return true
}
func (p *POPMatcher) HasResponse(index int) bool {
return true
}
func (p *POPMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (p *POPMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) bool {
switch index {
case 0:
fallthrough
case 1:
recvStr := string(packet.Buffer)
if len(recvStr) < 3 {
return false
}
compareStr := recvStr[0:3]
if compareStr == COMPARE_STR {
return true
}
}
return false
}
func NewMatcher() csm.Matcher {
m := &POPMatcher{}
reqStr := "QUIT\r\n"
byte := make([]byte, len(reqStr))
copy(byte[:], reqStr)
m.AddPacket(csm.NewPacket(byte, len(reqStr)))
return m
}

83
pop/pop_test.go Normal file
View File

@ -0,0 +1,83 @@
package pop
import (
"crypto/tls"
"fmt"
csm "git.loafle.net/commons/service_matcher-go"
"net"
"testing"
)
func TestPopTLS(t *testing.T) {
conn, _ := tls.Dial(
"tcp",
"192.168.1.15:995",
&tls.Config{
InsecureSkipVerify: true,
ServerName: "192.168.1.15",
},
)
defer conn.Close()
pop3Run(conn, t)
}
func TestPopNor(t *testing.T) {
client, _ := net.Dial("tcp", "192.168.1.15:110")
defer client.Close()
pop3Run(client, t)
}
func pop3Run(client net.Conn, t *testing.T) {
lm := NewPOPMatcher()
//port := types.NewPort("110", types.NewHost("192.168.1.215"), types.TYPE_TCP)
//scanInfo := scaninfo.NewServiceScanInfo(port)
//var ipport string
//ipport = port.Host.Ip + ":" + string(port.Port)
//
//fmt.Println(ipport)
bytett := make([]byte, 1024)
read, _ := client.Read(bytett)
bb := lm.Match(0, csm.NewPacket(bytett, read), nil)
if bb {
t.Log("good!")
}
fmt.Println(lm.PacketCount())
for ii := 0; ii < lm.PacketCount(); ii++ {
pack := lm.Packet(ii)
//fmt.Println(pack)
client.Write(pack.Buffer)
bytes := make([]byte, 1024)
rr, _ := client.Read(bytes)
//fmt.Println(bytes)
b := lm.Match(ii+1, csm.NewPacket(bytes, rr), nil)
if b {
t.Log("send Good!")
}
}
}

169
postgresql/postgresql.go Normal file
View File

@ -0,0 +1,169 @@
package postgresql
import (
"bytes"
"encoding/binary"
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
RESPONSE_TYPE_ERR uint8 = 0x45
)
type pgsql struct {
Len uint32
MessageType uint16
_ uint16
Name [5]byte
NameValue byte
Db [9]byte
DBValue byte
Encoding [16]byte
EncodingValue [5]byte
DateStyle [10]byte
DateStyleValue [4]byte
TimeZone [9]byte
TimeZoneValue [11]byte
ExtraDigits [19]byte
ExtraDigitsValue uint16
End byte
}
type pgsqlErrResponse struct {
ResponseType uint8
Len [4]byte
Data [128]byte
}
type PostgreSQLMatcher struct {
csm.Matchers
meta csm.Metadata
}
func (p *PostgreSQLMatcher) Key() string {
return "POSTGRESQL"
}
func (p *PostgreSQLMatcher) Name() string {
return "PostgreSQL"
}
func (p *PostgreSQLMatcher) Meta() csm.Metadata {
return p.meta
}
func (p *PostgreSQLMatcher) IsPrePacket() bool {
return false
}
func (p *PostgreSQLMatcher) HasResponse(index int) bool {
return true
}
func (p *PostgreSQLMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return true
}
func (p *PostgreSQLMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
pg := pgsqlErrResponse{}
if err := binary.Read(reader, binary.BigEndian, &pg); err != nil {
return err
}
if pg.ResponseType != RESPONSE_TYPE_ERR {
return csm.NotMatchedError()
}
length := binary.BigEndian.Uint32(pg.Len[:])
if length+1 != uint32(packet.Len) {
return csm.NotMatchedError()
}
data := string(pg.Data[:])
splits := strings.Split(data, "\x00")
var findSeverity bool = false
var findErrorCode bool = false
for _, s := range splits {
if strings.Contains(s, "FATAL") {
findSeverity = true
}
if strings.Contains(s, "28000") {
findErrorCode = true
}
}
if !findSeverity || !findErrorCode {
return csm.NotMatchedError()
}
return nil
}
func NewMatcher() csm.Matcher {
m := &PostgreSQLMatcher{}
pg := pgsql{}
pg.Len = 0x00000065
pg.MessageType = 0x0003
var name [5]byte
copy(name[:], "user")
pg.Name = name
pg.NameValue = 0x00
var db [9]byte
copy(db[:], "database")
pg.Db = db
pg.DBValue = 0x00
var encoding [16]byte
copy(encoding[:], "client_encoding")
pg.Encoding = encoding
var encodingValue [5]byte
copy(encodingValue[:], "UTF8")
pg.EncodingValue = encodingValue
var dateStyle [10]byte
copy(dateStyle[:], "DateStyle")
pg.DateStyle = dateStyle
var dateStyleValue [4]byte
copy(dateStyleValue[:], "ISO")
pg.DateStyleValue = dateStyleValue
var timeZone [9]byte
copy(timeZone[:], "TimeZone")
pg.TimeZone = timeZone
var timeZoneValue [11]byte
copy(timeZoneValue[:], "Asia/Seoul")
pg.TimeZoneValue = timeZoneValue
var extraDigit [19]byte
copy(extraDigit[:], "extra_float_digits")
pg.ExtraDigits = extraDigit
pg.ExtraDigitsValue = 0x3200
writer := new(bytes.Buffer)
binary.Write(writer, binary.BigEndian, pg)
m.AddPacket(csm.NewPacket(writer.Bytes(), writer.Len()))
return m
}

View File

@ -0,0 +1,34 @@
package postgresql
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestPG(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.229:5432")
if err != nil {
t.Error(err)
return
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

168
redis/redis.go Normal file
View File

@ -0,0 +1,168 @@
package redis
import (
"bufio"
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
const REDIS_PING string = "*1\r\n$4\r\nPING\r\n"
const REDIS_INFO string = "*1\r\n$4\r\nINFO\r\n"
const REDIS_QUIT string = "*1\r\n$4\r\nQUIT\r\n"
type RedisMatcher struct {
csm.Matchers
meta csm.Metadata
protected bool
}
func (r *RedisMatcher) Key() string {
return "REDIS"
}
func (r *RedisMatcher) Name() string {
name := "Redis"
if r.protected {
return name + " (protected)"
}
if v, ok := r.meta["redis_mode"]; ok {
name = name + " " + v
}
if v, ok := r.meta["redis_version"]; ok {
name = name + " (" + v + ")"
}
return name
}
func (r *RedisMatcher) Meta() csm.Metadata {
return r.meta
}
func (r *RedisMatcher) IsPrePacket() bool {
return false
}
func (r *RedisMatcher) HasResponse(index int) bool {
return true
}
func (r *RedisMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (r *RedisMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
resp := strings.Split(string(packet.Buffer), "\r\n")[0]
if len(resp) <= 0 {
return csm.NotMatchedError()
}
switch index {
case 0:
sign := string([]rune(resp)[0])
if len(sign) <= 0 {
return csm.NotMatchedError()
}
if sign == "+" {
if resp == "+PONG" || resp == "+OK" {
return nil
}
}
if sign == "-" {
if resp == "-NOAUTH" || resp == "-ERR" {
return nil
}
}
r.protected = r.checkProtectedMode(packet)
if r.protected {
return nil
}
case 1: // INFO
info := string(packet.Buffer)
if !r.protected {
r.parseInfo(info)
}
return nil
case 2:
sign := string([]rune(resp)[0])
if sign == "+" || sign == "-" {
return nil
}
return nil
default:
return csm.NotMatchedError()
}
return csm.NotMatchedError()
}
func (r *RedisMatcher) checkProtectedMode(packet *csm.Packet) bool {
var (
compareSign = "-"
compareMsg = "DENIED"
)
str := string(packet.Buffer[:packet.Len])
if str == "" {
return false
}
if 0 >= len(str) || len(compareMsg)+2 > len(str) {
return false
}
firstCompare := str[0:1]
seconcdCompare := str[1 : len(compareMsg)+1]
if firstCompare != compareSign {
return false
}
if seconcdCompare != compareMsg {
return false
}
r.protected = true
return true
}
func (r *RedisMatcher) parseInfo(info string) {
scanner := bufio.NewScanner(strings.NewReader(info))
for scanner.Scan() {
line := scanner.Text()
if strings.Compare(line, "") == 0 {
break
}
if len(line) > 0 && strings.Contains(line, ":") {
kv := strings.Split(line, ":")
if len(kv[0]) > 0 && len(kv[1]) > 0 {
r.meta[kv[0]] = kv[1]
}
}
}
}
func (r *RedisMatcher) PacketCount() int {
if r.protected {
return 1
}
return 3
}
func NewMatcher() csm.Matcher {
m := &RedisMatcher{}
m.meta = csm.NewMetadata()
m.AddPacket(csm.NewPacket([]byte(REDIS_PING), len(REDIS_PING)))
m.AddPacket(csm.NewPacket([]byte(REDIS_INFO), len(REDIS_INFO)))
m.AddPacket(csm.NewPacket([]byte(REDIS_QUIT), len(REDIS_QUIT)))
return m
}

39
redis/redis_test.go Normal file
View File

@ -0,0 +1,39 @@
package redis
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
ADDR string = "192.168.1.229:6379"
ADDR_protected string = "192.168.1.201:6379"
)
func TestRedisMatcher(t *testing.T) {
m := NewMatcher()
conn, _ := net.Dial("tcp", ADDR)
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
return
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

106
rmi/rmi.go Normal file
View File

@ -0,0 +1,106 @@
package rmi
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
MAGIC_NUMBER = 0x4a524d49
STREAM_PROTOCOL = 0x4b
VERSION = 0x0002
ACK_PROTOCOL = 0x4e
)
type RMI_SEND_MESSAGE struct {
magic uint32
version uint16
protocol uint8
}
type RMI_RECV_MESSAGE struct {
streamMessage uint8
packetLen uint16
host []byte
port [2]byte
}
type RMIMatcher struct {
csm.Matchers
}
func (r *RMIMatcher) Key() string {
return "RMI"
}
func (r *RMIMatcher) Name() string {
return "RMI"
}
func (r *RMIMatcher) Meta() csm.Metadata {
return nil
}
func (r *RMIMatcher) IsPrePacket() bool {
return false
}
func (r *RMIMatcher) HasResponse(index int) bool {
return true
}
func (r *RMIMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (r *RMIMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
rmiRecv := RMI_RECV_MESSAGE{}
buf := bytes.NewReader(packet.Buffer)
binary.Read(buf, binary.BigEndian, &rmiRecv.streamMessage)
binary.Read(buf, binary.BigEndian, &rmiRecv.packetLen)
lenInt := int(rmiRecv.packetLen)
var tempHost = make([]byte, lenInt, lenInt)
copy(rmiRecv.host, tempHost)
rmiRecv.host = tempHost
binary.Read(buf, binary.BigEndian, &rmiRecv.host)
binary.Read(buf, binary.BigEndian, &rmiRecv.port)
hostIp := string(rmiRecv.host[:lenInt])
if rmiRecv.streamMessage == ACK_PROTOCOL && lenInt == len(hostIp) {
return nil
}
return csm.NotMatchedError()
}
func NewMatcher() csm.Matcher {
m := &RMIMatcher{}
rsm := RMI_SEND_MESSAGE{
magic: MAGIC_NUMBER,
version: VERSION,
protocol: STREAM_PROTOCOL,
}
mCache := new(bytes.Buffer)
binary.Write(mCache, binary.BigEndian, rsm)
sendByte := mCache.Bytes()
m.AddPacket(csm.NewPacket(sendByte, len(sendByte)))
return m
}

36
rmi/rmi_test.go Normal file
View File

@ -0,0 +1,36 @@
package rmi
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestRMIMatcher_Match(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.102:9840")
if err != nil {
t.Error(err)
return
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

166
smb/smb.go Normal file
View File

@ -0,0 +1,166 @@
package smb
import (
"bytes"
"encoding/binary"
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
SMB_COM_NEGOTIATE uint8 = 0x72
SMB_SUCCESS uint8 = 0x00
)
type netBIOS struct {
MsgType byte
MsgLength [3]uint8
}
type smb struct {
NetBios netBIOS
Component [4]uint8
SmbCommand uint8
NtStatus [4]uint8
Flags uint8
Flags2 [2]uint8
ProcessId uint16
Signature uint64
Reserved uint16
Tid uint16
Pid uint16
Uid uint16
Mid uint16
Wct uint8
Bcc uint16
Bf1 uint8
Name1 [23]uint8
Bf2 uint8
Name2 [10]uint8
Bf3 uint8
Name3 [28]uint8
Bf4 uint8
Name4 [10]uint8
Bf5 uint8
Name5 [10]uint8
Bf6 uint8
Name6 [11]uint8
}
type SMBMatcher struct {
csm.Matchers
}
func (t *SMBMatcher) Key() string {
return "SMB"
}
func (t *SMBMatcher) Name() string {
return "SMB"
}
func (t *SMBMatcher) Meta() csm.Metadata {
return nil
}
func (t *SMBMatcher) IsPrePacket() bool {
return false
}
func (t *SMBMatcher) HasResponse(index int) bool {
return true
}
func (t *SMBMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (t *SMBMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
s := smb{}
if err := binary.Read(reader, binary.BigEndian, &s); err != nil {
return err
}
var des [4]byte
copy(des[1:], s.NetBios.MsgLength[:])
packetLen := binary.BigEndian.Uint32(des[:])
if packetLen != uint32(packet.Len-4) {
return csm.NotMatchedError()
}
if !strings.Contains(string(s.Component[:]), "SMB") {
return csm.NotMatchedError()
}
if s.SmbCommand != SMB_COM_NEGOTIATE {
return csm.NotMatchedError()
}
return nil
}
func NewMatcher() csm.Matcher {
m := &SMBMatcher{}
query := smb{}
query.NetBios.MsgType = 0x00
query.NetBios.MsgLength[2] = 0x85
query.Component[0] = 0xff
query.Component[1] = 'S'
query.Component[2] = 'M'
query.Component[3] = 'B'
query.SmbCommand = SMB_COM_NEGOTIATE
query.NtStatus[3] = SMB_SUCCESS
query.Flags = 0x18
query.Flags2[0] = 0x53
query.Flags2[1] = 0xC8
query.ProcessId = 0x00
query.Signature = 0x00
query.Reserved = 0
query.Tid = 0
query.Pid = 0xfeff
query.Uid = 0
query.Mid = 0
query.Wct = 0
query.Bcc = 0x0062
query.Bf1 = 0x02
copy(query.Name1[:], "PC NETWORK PROGRAM 1.0")
query.Bf2 = 0x02
copy(query.Name2[:], "LANMAN1.0")
query.Bf3 = 0x02
copy(query.Name3[:], "Windows for Workgroups 3.1a")
query.Bf4 = 0x02
copy(query.Name4[:], "LM1.2X002")
query.Bf5 = 0x02
copy(query.Name5[:], "LANMAN2.1")
query.Bf6 = 0x02
copy(query.Name6[:], "NT LM 0.12")
writer := new(bytes.Buffer)
binary.Write(writer, binary.LittleEndian, query)
m.AddPacket(csm.NewPacket(writer.Bytes(), writer.Len()))
return m
}

41
smb/smb_test.go Normal file
View File

@ -0,0 +1,41 @@
package smb
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
ADDR string = "192.168.1.101:445"
)
func TestSMBMatcher(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", ADDR)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, 0, p); err != nil {
t.Error(err)
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

71
smtp/smtp.go Normal file
View File

@ -0,0 +1,71 @@
package smtp
import (
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
type SmtpMatcher struct {
csm.Matchers
}
func (t *SmtpMatcher) Key() string {
return "SMTP"
}
func (t *SmtpMatcher) String() string {
return "SMTP"
}
func (t *SmtpMatcher) IsPrePacket() bool {
return true
}
func (t *SmtpMatcher) HasResponse(index int) bool {
return true
}
func (t *SmtpMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (t *SmtpMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) bool {
if packet == nil {
return false
}
buf := string(packet.Buffer)
if len(buf) == 0 || len(buf) < 5 {
return false
}
splits := strings.Split(buf, "\r\n")
splits = strings.Split(buf, " ")
if index == 0 {
if splits[0] == "220" {
return true
}
} else if index == 1 {
if splits[0] == "250" {
return true
}
} else if index == 2 {
if splits[0] == "221" {
return true
}
}
return false
}
func NewMatcher() csm.Matcher {
m := &SmtpMatcher{}
b := []byte("helo test\r\n")
m.AddPacket(csm.NewPacket(b, len(b)))
b = []byte("quit\r\n")
m.AddPacket(csm.NewPacket(b, len(b)))
return m
}

68
smtp/smtp_test.go Normal file
View File

@ -0,0 +1,68 @@
package smtp
import (
"crypto/tls"
"fmt"
"net"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSMTPTLS(t *testing.T) {
conn, err := tls.Dial("tcp",
"192.168.1.215:465",
&tls.Config{
InsecureSkipVerify: true,
ServerName: "192.168.1.215",
},
)
if err != nil {
t.Log(err)
return
}
b := make([]byte, 1024)
check(t, b, conn, "", "220")
check(t, b, conn, "helo test\r\n", "250")
check(t, b, conn, "quit\r\n", "221")
conn.Close()
}
func TestSMTP(t *testing.T) {
conn, _ := net.Dial("tcp", "192.168.1.15:25")
b := make([]byte, 1024)
check(t, b, conn, "", "220")
check(t, b, conn, "helo test\r\n", "250")
check(t, b, conn, "quit\r\n", "221")
conn.Close()
}
func check(t *testing.T, b []byte, conn net.Conn, cmd string, compare string) {
if cmd != "" {
wlen, _ := conn.Write([]byte(cmd))
assert.Equal(t, wlen, len(cmd))
}
rlen, _ := conn.Read(b)
fmt.Println(rlen)
fmt.Println(len(b))
data := string(b[:rlen])
fmt.Println(data)
assert.Equal(t, true, rlen > 4)
splits := strings.Split(data, " ")
assert.Equal(t, compare, splits[0])
}

16
snmp/snmp.go Normal file
View File

@ -0,0 +1,16 @@
package snmp
const (
SNMP_START_SEQUENCE uint8 = 0X30
SNMP_TYPE_INTEGER uint8 = 0X02
SNMP_TYPE_STRING uint8 = 0X04
SNMP_TYPE_NULL uint8 = 0X05
SNMP_TYPE_OBJECT uint8 = 0X06
SNMP_GET_REQUEST uint8 = 0XA0
SNMP_RESPONSE uint8 = 0XA2
SNMP_NO_DESC uint16 = 0X0004
SNMP_END_SEQUENCE uint8 = 0X30
SNMP_PROTOCOL_VERSION_3 uint8 = 0X03
SNMP_PROTOCOL_VERSION_2c uint8 = 0X01
SNMP_MSG_ID_MAX_VALUE uint32 = 0xFFFFFF7F
)

145
snmp/v2/snmpv2.go Normal file
View File

@ -0,0 +1,145 @@
package v2
import (
"encoding/asn1"
"math/rand"
csm "git.loafle.net/commons/service_matcher-go"
)
type snmpv2 struct {
Version int
Community []byte
Data struct {
RequestID int32
ErrorStatus int
ErrorIndex int
Bindings []binding
} `asn1:"tag:0"`
}
type response struct {
ID int32
ErrorStatus int
ErrorIndex int
Bindings []binding
}
type binding struct {
Name asn1.ObjectIdentifier
Value asn1.RawValue
}
var (
null = asn1.RawValue{Class: 0, Tag: 5}
noSuchObject = asn1.RawValue{Class: 2, Tag: 0}
noSuchInstance = asn1.RawValue{Class: 2, Tag: 1}
endOfMibView = asn1.RawValue{Class: 2, Tag: 2}
)
type SNMPMatcher struct {
csm.Matchers
requestID int32
meta csm.Metadata
}
func (s *SNMPMatcher) Key() string {
return "SNMP"
}
func (s *SNMPMatcher) Name() string {
return "SNMP"
}
func (s *SNMPMatcher) Meta() csm.Metadata {
return s.meta
}
func (s *SNMPMatcher) IsPrePacket() bool {
return false
}
func (s *SNMPMatcher) HasResponse(index int) bool {
return true
}
func (s *SNMPMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
var p struct {
Version int
Community []byte
Data struct {
RequestID int32
ErrorStatus int
ErrorIndex int
Bindings []binding
} `asn1:"tag:2"`
}
if _, err := asn1.Unmarshal(packet.Buffer[0:packet.Len], &p); err != nil {
return err
}
resp := &response{p.Data.RequestID, p.Data.ErrorStatus, p.Data.ErrorIndex, p.Data.Bindings}
if s.requestID != resp.ID {
return csm.NotMatchedError()
}
if len(resp.Bindings) == 0 {
return csm.NotMatchedError()
}
for _, binding := range resp.Bindings {
if len(binding.Value.Bytes) <= 0 {
continue
}
// if binding.Name.String() == "1.3.6.1.2.1.1.5.0" {
s.meta[binding.Name.String()] = string(binding.Value.Bytes)
// }
}
return nil
}
func (s *SNMPMatcher) IsSend(port int) bool {
if 161 == port {
return true
}
return false
}
func NewMatcher() csm.UDPMatcher {
m := &SNMPMatcher{}
m.meta = csm.NewMetadata()
m.requestID = rand.Int31()
p := snmpv2{}
p.Version = 0
p.Community = []byte("test1252serc")
p.Data.RequestID = m.requestID
p.Data.Bindings = []binding{
binding{
Name: []int{1, 3, 6, 1, 2, 1, 1, 5, 0},
Value: null,
},
binding{
Name: []int{1, 3, 6, 1, 2, 1, 1, 1, 0},
Value: null,
},
}
buf, _ := asn1.Marshal(p)
m.AddPacket(csm.NewPacket(buf, len(buf)))
return m
}

32
snmp/v2/snmpv2_test.go Normal file
View File

@ -0,0 +1,32 @@
package v2
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestSNMPv2(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("udp", "192.168.1.99:161")
if err != nil {
t.Error(err)
return
}
defer conn.Close()
pack := m.Packet(0)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, 0, p); err != nil {
t.Error(err)
}
t.Log(m.Meta())
}

216
snmp/v3/snmpv3.go Normal file
View File

@ -0,0 +1,216 @@
package v3
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
csms "git.loafle.net/commons/service_matcher-go/snmp"
)
type snmpv3GlobalData struct {
GlobalDataStartSeq uint8
GlobalDataLen uint8
MsgIdType uint8
MsgIdLen uint8
MsgId uint32
MsgMaxSizeType uint8
MsgMaxSizeLen uint8
MsgMaxSize [3]uint8
MsgFlagsType uint8
MsgFlagsTypeLen uint8
MsgFlags uint8
MsgSecurityModelType uint8
MsgSecurityModelLen uint8
MsgSecurityModel uint8
}
type snmpv3MsgData struct {
MsgDataStartSeq uint8
MsgDataLen uint8
ContextEngineId uint16
ContextEngineName uint16
SnmpType uint8
Len uint8
RequestIdType uint8
RequestIdLen uint8
RequestId uint32
ErrorStatusType uint8
ErrorStatusLen uint8
ErrorStatus uint8
ErrorIndexType uint8
ErrorIndexLen uint8
ErrorIndex uint8
EndSeq uint8
EndIndicator uint8
}
type snmpv3 struct {
StartSeq uint8
SeqLen uint8
SNMPVersionType uint8
SNMPVersionLen uint8
SNMPVersion uint8
MsgGlobalData snmpv3GlobalData
Unk1 uint16
Unk2 uint16
MsgAuthoritativeEngineId uint16
MsgAuthoritativeEngineBootsType uint8
MsgAuthoritativeEngineBootsLen uint8
MsgAuthoritativeEngineBoots uint8
MsgAuthoritativeEngineTimeType uint8
MsgAuthoritativeEngineTimeLen uint8
MsgAuthoritativeEngineTime uint8
MsgUserName uint16
MsgAuthenticationParam uint16
MsgPrivacyParam uint16
MsgData snmpv3MsgData
}
type SNMPMatcher struct {
csm.Matchers
}
func (t *SNMPMatcher) Key() string {
return "SNMP_V3"
}
func (t *SNMPMatcher) Meta() map[string]string {
return nil
}
func (t *SNMPMatcher) IsPrePacket() bool {
return false
}
func (t *SNMPMatcher) HasResponse(index int) bool {
return true
}
func (t *SNMPMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (t *SNMPMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) bool {
if packet == nil {
return false
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
s := snmpv3{}
if err := binary.Read(reader, binary.LittleEndian, &s); err != nil {
return false
}
if s.StartSeq != csms.SNMP_START_SEQUENCE {
return false
}
var p uint8
r := new(bytes.Buffer)
r.Write(packet.Buffer)
for {
binary.Read(r, binary.LittleEndian, &p)
if p == csms.SNMP_TYPE_INTEGER {
break
}
}
binary.Read(r, binary.BigEndian, &p)
if p == 0x01 {
binary.Read(r, binary.BigEndian, &p)
if p == 0x03 {
return true
}
}
return false
}
func (t *SNMPMatcher) IsSend(port int) bool {
if 161 == port {
return true
}
return false
}
func NewMatcher() csm.UDPMatcher {
m := &SNMPMatcher{}
snmpTempBuf := new(bytes.Buffer)
binary.Write(snmpTempBuf, binary.BigEndian, snmpv3{}) //For getting the struct size
snmpMsgDataTempBuf := new(bytes.Buffer)
binary.Write(snmpMsgDataTempBuf, binary.BigEndian, snmpv3MsgData{}) //For getting the struct size
snmpGlobalTempBuf := new(bytes.Buffer)
binary.Write(snmpGlobalTempBuf, binary.BigEndian, snmpv3GlobalData{}) //For getting the struct size
q := snmpv3{}
q.StartSeq = csms.SNMP_START_SEQUENCE
q.SeqLen = uint8(len(snmpTempBuf.Bytes())) - 2
q.SNMPVersionType = csms.SNMP_TYPE_INTEGER
q.SNMPVersionLen = 0x01
q.SNMPVersion = csms.SNMP_PROTOCOL_VERSION_3
q.MsgGlobalData.GlobalDataStartSeq = csms.SNMP_START_SEQUENCE
q.MsgGlobalData.GlobalDataLen = uint8(len(snmpGlobalTempBuf.Bytes())) - 2
q.MsgGlobalData.MsgIdType = csms.SNMP_TYPE_INTEGER
q.MsgGlobalData.MsgIdLen = 0x04
q.MsgGlobalData.MsgId = csms.SNMP_MSG_ID_MAX_VALUE
q.MsgGlobalData.MsgMaxSizeType = csms.SNMP_TYPE_INTEGER
q.MsgGlobalData.MsgMaxSizeLen = 0x03
q.MsgGlobalData.MsgMaxSize[2] = 0xe3
q.MsgGlobalData.MsgMaxSize[1] = 0xff
q.MsgGlobalData.MsgMaxSize[0] = 0x00
q.MsgGlobalData.MsgFlagsType = csms.SNMP_TYPE_STRING
q.MsgGlobalData.MsgFlagsTypeLen = 0x01
q.MsgGlobalData.MsgFlags = 0x04
q.MsgGlobalData.MsgSecurityModelType = csms.SNMP_TYPE_INTEGER
q.MsgGlobalData.MsgSecurityModelLen = 0x01
q.MsgGlobalData.MsgSecurityModel = 0x03
q.Unk1 = 0x1004
q.Unk2 = 0x0e30
q.MsgAuthoritativeEngineId = csms.SNMP_NO_DESC
q.MsgAuthoritativeEngineBootsType = csms.SNMP_TYPE_INTEGER
q.MsgAuthoritativeEngineBootsLen = 0x01
q.MsgAuthoritativeEngineBoots = 0x00
q.MsgAuthoritativeEngineTimeType = csms.SNMP_TYPE_INTEGER
q.MsgAuthoritativeEngineTimeLen = 0x01
q.MsgAuthoritativeEngineTime = 0x00
q.MsgUserName = csms.SNMP_NO_DESC
q.MsgAuthenticationParam = csms.SNMP_NO_DESC
q.MsgPrivacyParam = csms.SNMP_NO_DESC
q.MsgData.MsgDataStartSeq = csms.SNMP_START_SEQUENCE
q.MsgData.MsgDataLen = uint8(len(snmpMsgDataTempBuf.Bytes())) - 2
q.MsgData.ContextEngineId = csms.SNMP_NO_DESC
q.MsgData.ContextEngineName = csms.SNMP_NO_DESC
q.MsgData.SnmpType = csms.SNMP_GET_REQUEST
q.MsgData.Len = 0x0E
q.MsgData.RequestIdType = csms.SNMP_TYPE_INTEGER
q.MsgData.RequestIdLen = 0x04
q.MsgData.RequestId = 0x00 //
q.MsgData.ErrorStatusType = csms.SNMP_TYPE_INTEGER
q.MsgData.ErrorStatusLen = 0x01
q.MsgData.ErrorStatus = 0x00
q.MsgData.ErrorIndexType = csms.SNMP_TYPE_INTEGER
q.MsgData.ErrorIndexLen = 0x01
q.MsgData.ErrorIndex = 0x00
q.MsgData.EndSeq = csms.SNMP_END_SEQUENCE
q.MsgData.EndIndicator = 0x00
writer := new(bytes.Buffer)
binary.Write(writer, binary.LittleEndian, q)
m.AddPacket(csm.NewPacket(writer.Bytes(), writer.Len()))
return m
}

216
snmp/v3/snmpv3_old.go Normal file
View File

@ -0,0 +1,216 @@
package v3
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
csms "git.loafle.net/commons/service_matcher-go/snmp"
)
type snmpv3GlobalData struct {
GlobalDataStartSeq uint8
GlobalDataLen uint8
MsgIdType uint8
MsgIdLen uint8
MsgId uint32
MsgMaxSizeType uint8
MsgMaxSizeLen uint8
MsgMaxSize [3]uint8
MsgFlagsType uint8
MsgFlagsTypeLen uint8
MsgFlags uint8
MsgSecurityModelType uint8
MsgSecurityModelLen uint8
MsgSecurityModel uint8
}
type snmpv3MsgData struct {
MsgDataStartSeq uint8
MsgDataLen uint8
ContextEngineId uint16
ContextEngineName uint16
SnmpType uint8
Len uint8
RequestIdType uint8
RequestIdLen uint8
RequestId uint32
ErrorStatusType uint8
ErrorStatusLen uint8
ErrorStatus uint8
ErrorIndexType uint8
ErrorIndexLen uint8
ErrorIndex uint8
EndSeq uint8
EndIndicator uint8
}
type snmpv3 struct {
StartSeq uint8
SeqLen uint8
SNMPVersionType uint8
SNMPVersionLen uint8
SNMPVersion uint8
MsgGlobalData snmpv3GlobalData
Unk1 uint16
Unk2 uint16
MsgAuthoritativeEngineId uint16
MsgAuthoritativeEngineBootsType uint8
MsgAuthoritativeEngineBootsLen uint8
MsgAuthoritativeEngineBoots uint8
MsgAuthoritativeEngineTimeType uint8
MsgAuthoritativeEngineTimeLen uint8
MsgAuthoritativeEngineTime uint8
MsgUserName uint16
MsgAuthenticationParam uint16
MsgPrivacyParam uint16
MsgData snmpv3MsgData
}
type SNMPMatcher struct {
csm.Matchers
}
func (t *SNMPMatcher) Key() string {
return "SNMP_V3"
}
func (t *SNMPMatcher) Meta() map[string]string {
return nil
}
func (t *SNMPMatcher) IsPrePacket() bool {
return false
}
func (t *SNMPMatcher) HasResponse(index int) bool {
return true
}
func (t *SNMPMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (t *SNMPMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) bool {
if packet == nil {
return false
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
s := snmpv3{}
if err := binary.Read(reader, binary.LittleEndian, &s); err != nil {
return false
}
if s.StartSeq != csms.SNMP_START_SEQUENCE {
return false
}
var p uint8
r := new(bytes.Buffer)
r.Write(packet.Buffer)
for {
binary.Read(r, binary.LittleEndian, &p)
if p == csms.SNMP_TYPE_INTEGER {
break
}
}
binary.Read(r, binary.BigEndian, &p)
if p == 0x01 {
binary.Read(r, binary.BigEndian, &p)
if p == 0x03 {
return true
}
}
return false
}
func (t *SNMPMatcher) IsSend(port int) bool {
if 161 == port {
return true
}
return false
}
func NewMatcher() csm.UDPMatcher {
m := &SNMPMatcher{}
snmpTempBuf := new(bytes.Buffer)
binary.Write(snmpTempBuf, binary.BigEndian, snmpv3{}) //For getting the struct size
snmpMsgDataTempBuf := new(bytes.Buffer)
binary.Write(snmpMsgDataTempBuf, binary.BigEndian, snmpv3MsgData{}) //For getting the struct size
snmpGlobalTempBuf := new(bytes.Buffer)
binary.Write(snmpGlobalTempBuf, binary.BigEndian, snmpv3GlobalData{}) //For getting the struct size
q := snmpv3{}
q.StartSeq = csms.SNMP_START_SEQUENCE
q.SeqLen = uint8(len(snmpTempBuf.Bytes())) - 2
q.SNMPVersionType = csms.SNMP_TYPE_INTEGER
q.SNMPVersionLen = 0x01
q.SNMPVersion = csms.SNMP_PROTOCOL_VERSION_3
q.MsgGlobalData.GlobalDataStartSeq = csms.SNMP_START_SEQUENCE
q.MsgGlobalData.GlobalDataLen = uint8(len(snmpGlobalTempBuf.Bytes())) - 2
q.MsgGlobalData.MsgIdType = csms.SNMP_TYPE_INTEGER
q.MsgGlobalData.MsgIdLen = 0x04
q.MsgGlobalData.MsgId = csms.SNMP_MSG_ID_MAX_VALUE
q.MsgGlobalData.MsgMaxSizeType = csms.SNMP_TYPE_INTEGER
q.MsgGlobalData.MsgMaxSizeLen = 0x03
q.MsgGlobalData.MsgMaxSize[2] = 0xe3
q.MsgGlobalData.MsgMaxSize[1] = 0xff
q.MsgGlobalData.MsgMaxSize[0] = 0x00
q.MsgGlobalData.MsgFlagsType = csms.SNMP_TYPE_STRING
q.MsgGlobalData.MsgFlagsTypeLen = 0x01
q.MsgGlobalData.MsgFlags = 0x04
q.MsgGlobalData.MsgSecurityModelType = csms.SNMP_TYPE_INTEGER
q.MsgGlobalData.MsgSecurityModelLen = 0x01
q.MsgGlobalData.MsgSecurityModel = 0x03
q.Unk1 = 0x1004
q.Unk2 = 0x0e30
q.MsgAuthoritativeEngineId = csms.SNMP_NO_DESC
q.MsgAuthoritativeEngineBootsType = csms.SNMP_TYPE_INTEGER
q.MsgAuthoritativeEngineBootsLen = 0x01
q.MsgAuthoritativeEngineBoots = 0x00
q.MsgAuthoritativeEngineTimeType = csms.SNMP_TYPE_INTEGER
q.MsgAuthoritativeEngineTimeLen = 0x01
q.MsgAuthoritativeEngineTime = 0x00
q.MsgUserName = csms.SNMP_NO_DESC
q.MsgAuthenticationParam = csms.SNMP_NO_DESC
q.MsgPrivacyParam = csms.SNMP_NO_DESC
q.MsgData.MsgDataStartSeq = csms.SNMP_START_SEQUENCE
q.MsgData.MsgDataLen = uint8(len(snmpMsgDataTempBuf.Bytes())) - 2
q.MsgData.ContextEngineId = csms.SNMP_NO_DESC
q.MsgData.ContextEngineName = csms.SNMP_NO_DESC
q.MsgData.SnmpType = csms.SNMP_GET_REQUEST
q.MsgData.Len = 0x0E
q.MsgData.RequestIdType = csms.SNMP_TYPE_INTEGER
q.MsgData.RequestIdLen = 0x04
q.MsgData.RequestId = 0x00 //
q.MsgData.ErrorStatusType = csms.SNMP_TYPE_INTEGER
q.MsgData.ErrorStatusLen = 0x01
q.MsgData.ErrorStatus = 0x00
q.MsgData.ErrorIndexType = csms.SNMP_TYPE_INTEGER
q.MsgData.ErrorIndexLen = 0x01
q.MsgData.ErrorIndex = 0x00
q.MsgData.EndSeq = csms.SNMP_END_SEQUENCE
q.MsgData.EndIndicator = 0x00
writer := new(bytes.Buffer)
binary.Write(writer, binary.LittleEndian, q)
m.AddPacket(csm.NewPacket(writer.Bytes(), writer.Len()))
return m
}

37
snmp/v3/snmpv3_test.go Normal file
View File

@ -0,0 +1,37 @@
package v3
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestSNMP3(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("udp", "192.168.1.204:161")
if err != nil {
t.Error(err)
return
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if m.Match(nil, i, p) {
t.Log("SNMP found")
return
}
t.Error("SNMP not found")
}
}

170
sqlserver/sqlserver.go Normal file
View File

@ -0,0 +1,170 @@
package sqlserver
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
HEADER_TYPE_PRELOGIN uint8 = 0x12
HEADER_TYPE_RESPONSE uint8 = 0x4
PL_OPTION_TOKEN_VERSION uint8 = 0x00
PL_OPTION_TOKEN_ENCRYPTION uint8 = 0x01
PL_OPTION_TOKEN_TRACEID uint8 = 0x05
PL_OPTION_TOKEN_TERMINATOR uint8 = 0xff
ENCRYPT_OFF string = "Encryption is available but off."
ENCRYPT_ON string = "Encryption is available and on."
ENCRYPT_NOT_SUP string = "Encryption is not available."
ENCRYPT_REQ string = "Encryption is required."
)
type PreloginMsg struct {
VersionToken uint8
VersionOffset uint16
VersionLength uint16
EncryptionToken uint8
EncryptionOffset uint16
EncryptionLength uint16
TraceIdToken uint8
TraceIdOffset uint16
TraceIdLength uint16
Terminator uint8
Options [7]uint8
TraceId [36]uint8
}
type mssql struct {
Type_ uint8
Status uint8
Length uint16
Channel uint16
PacketNum uint8
Window uint8
Prelogin PreloginMsg
}
type PreloginResponse struct {
Msg [256]uint8
}
type mssqlResponse struct {
Type_ uint8
Status uint8
Length uint16
Channel uint16
PacketNum uint8
Window uint8
PreLoginResp PreloginResponse
}
type SQLServerMatcher struct {
csm.Matchers
isSSL bool
}
func (t *SQLServerMatcher) Key() string {
return "SQLSERVER"
}
func (t *SQLServerMatcher) String() string {
if t.isSSL {
return "SQL Server (SSL)"
}
return "SQL Server"
}
func (t *SQLServerMatcher) IsPrePacket() bool {
return false
}
func (t *SQLServerMatcher) HasResponse(index int) bool {
return true
}
func (t *SQLServerMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (t *SQLServerMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) bool {
if packet == nil {
return false
}
reader := new(bytes.Buffer)
reader.Write(packet.Buffer)
m := mssqlResponse{}
if err := binary.Read(reader, binary.BigEndian, &m); err != nil {
return false
}
if m.Type_ != HEADER_TYPE_RESPONSE {
return false
}
if m.Length != uint16(packet.Len) {
return false
}
switch m.PreLoginResp.Msg[m.Length-9 : m.Length-8][0] {
case 0:
return true
case 1:
t.isSSL = true
return true
case 2:
return true
case 3:
t.isSSL = true
return true
default:
return false
}
return false
}
func NewMatcher() csm.Matcher {
mm := &SQLServerMatcher{}
tempBuf := new(bytes.Buffer)
binary.Write(tempBuf, binary.BigEndian, mssql{})
m := mssql{
Type_: HEADER_TYPE_PRELOGIN,
Status: 0x01,
Length: uint16(len(tempBuf.Bytes())),
Channel: 0,
PacketNum: 0,
Window: 0,
Prelogin: PreloginMsg{
VersionToken: PL_OPTION_TOKEN_VERSION,
VersionOffset: 0x0010,
VersionLength: 0x0006,
EncryptionToken: PL_OPTION_TOKEN_ENCRYPTION,
EncryptionOffset: 0x0016,
EncryptionLength: 0x0001,
TraceIdToken: PL_OPTION_TOKEN_TRACEID,
TraceIdOffset: 0x0017,
TraceIdLength: 0x0024,
Terminator: PL_OPTION_TOKEN_TERMINATOR,
},
}
writer := new(bytes.Buffer)
binary.Write(writer, binary.BigEndian, m)
mm.AddPacket(csm.NewPacket(writer.Bytes(), writer.Len()))
return mm
}

View File

@ -0,0 +1,64 @@
package sqlserver
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
/*
192.168.1.106:1433 - normal
192.168.1.103:1433 - ssl
*/
func TestSqlNor(t *testing.T) {
conn, _ := net.Dial("tcp", "192.168.1.16:11433")
defer conn.Close()
sqlServerRun(conn, t)
}
//func TestSqlTLS(t *testing.T) {
// conn, err := tls.Dial(
// "tcp",
// "192.168.1.103:7680",
// &tls.Config{
// InsecureSkipVerify: true,
// ServerName: "192.168.1.103",
// },
// )
//
// if err != nil {
// t.Log(err)
// return
// }
//
// defer conn.Close()
//
// sqlServerRun(conn, t)
//}
func sqlServerRun(conn net.Conn, t *testing.T) {
m := NewMatcher()
for i := 0; i < m.PacketCount(); i++ {
pack := m.Packet(i)
conn.Write(pack.Buffer)
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if m.Match(nil, i, p) {
t.Log(m.Key())
return
}
t.Error("SQL Server not found")
}
}

85
ssh/ssh.go Normal file
View File

@ -0,0 +1,85 @@
package ssh
import (
"bufio"
"bytes"
"strings"
csm "git.loafle.net/commons/service_matcher-go"
)
type SSHMatcher struct {
csm.Matchers
meta csm.Metadata
}
func (ssh *SSHMatcher) Key() string {
return "SSH"
}
func (ssh *SSHMatcher) Name() string {
name := "SSH"
if v, ok := ssh.meta["softwareversion"]; ok {
name = name + " (" + v + ")"
}
return name
}
func (ssh *SSHMatcher) Meta() csm.Metadata {
return ssh.meta
}
func (ssh *SSHMatcher) IsPrePacket() bool {
return true
}
func (ssh *SSHMatcher) HasResponse(index int) bool {
return true
}
func (ssh *SSHMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (ssh *SSHMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
// SSH-protoversion-softwareversion SP comments CR LF
// e.g. ) SSH-2.0-OpenSSH_7.5p1 Ubuntu-10ubuntu0.1\n
scanner := bufio.NewScanner(bytes.NewReader(packet.Buffer))
for scanner.Scan() {
exchange := scanner.Text()
if !strings.HasPrefix(exchange, "SSH") {
return csm.NotMatchedError()
}
temp := strings.Split(exchange, " ")
versions := strings.Split(temp[0], "-")
protoversion := versions[1]
softwareversion := versions[2]
if strings.HasPrefix(protoversion, "1") || strings.HasPrefix(protoversion, "2") {
ssh.meta["protoversion"] = protoversion
ssh.meta["softwareversion"] = softwareversion
if len(temp) > 1 {
ssh.meta["comments"] = temp[1]
}
return nil
}
break
}
return csm.NotMatchedError()
}
func NewMatcher() csm.Matcher {
m := &SSHMatcher{}
m.meta = csm.NewMetadata()
return m
}

37
ssh/ssh_test.go Normal file
View File

@ -0,0 +1,37 @@
package ssh
import (
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestSSH(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.10:22")
if err != nil {
t.Error(err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
return
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

77
telnet/telnet.go Normal file
View File

@ -0,0 +1,77 @@
package telnet
import (
csm "git.loafle.net/commons/service_matcher-go"
)
const (
WILL = 251
WONT = 252
DO = 253
DONT = 254
CMD = 255
)
type TelnetMatcher struct {
csm.Matchers
}
func (tel *TelnetMatcher) Key() string {
return "TELNET"
}
func (tel *TelnetMatcher) Name() string {
return "Telnet"
}
func (tel *TelnetMatcher) Meta() csm.Metadata {
return nil
}
func (tel *TelnetMatcher) IsPrePacket() bool {
return true
}
func (tel *TelnetMatcher) HasResponse(index int) bool {
return true
}
func (tel *TelnetMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (tel *TelnetMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) error {
if packet == nil || packet.Buffer == nil || packet.Len == 0 {
return csm.NoPacketReceivedError()
}
buf := make([]byte, 0, 0)
count := 0
for i := 0; i < len(packet.Buffer); i++ {
if packet.Buffer[i] > 0 {
buf = append(buf, packet.Buffer[i])
} else if count > 2 {
break
} else {
count++
}
}
for idx := 0; idx < len(buf); idx += 3 {
if buf[idx] == CMD && (buf[idx+1] == DO || buf[idx+1] == WONT || buf[idx+1] == WILL || buf[idx+1] == DONT) {
return nil
}
}
return csm.NotMatchedError()
}
func NewMatcher() csm.Matcher {
m := &TelnetMatcher{}
m.AddPacket(csm.NewPacket([]byte{CMD, DO, 37}, 3))
return m
}

38
telnet/telnet_test.go Normal file
View File

@ -0,0 +1,38 @@
package telnet
import (
csm "git.loafle.net/commons/service_matcher-go"
"net"
"testing"
)
func TestTelnetMatcher_Match(t *testing.T) {
m := NewMatcher()
conn, err := net.Dial("tcp", "192.168.1.201:23")
if err != nil {
t.Error(err)
}
defer conn.Close()
for i := 0; i < m.PacketCount(); i++ {
_, err := conn.Write(m.Packet(i).Buffer)
if err != nil {
t.Error(err)
}
bytes := make([]byte, 1024)
n, _ := conn.Read(bytes)
p := csm.NewPacket(bytes, n)
if err := m.Match(nil, i, p); err != nil {
t.Error(err)
return
}
}
t.Log(m.Name())
t.Log(m.Meta())
}

248
wmi/wmi.go Normal file
View File

@ -0,0 +1,248 @@
package wmi
import (
"bytes"
"encoding/binary"
csm "git.loafle.net/commons/service_matcher-go"
)
const (
PDU_BIND = 11
PDU_BIND_ACK = 12
PDU_REQ = 0
PDU_RESP = 2
WMI_CALL_ID_1 = 0x95
WMI_CALL_ID_2 = 0x96
)
type WMIMatcher struct {
csm.Matchers
}
func (w *WMIMatcher) Key() string {
return "WMI"
}
func (w *WMIMatcher) String() string {
return "WMI"
}
func (w *WMIMatcher) IsPrePacket() bool {
return false
}
func (w *WMIMatcher) HasResponse(index int) bool {
return true
}
func (w *WMIMatcher) IsError(info csm.MatchInfo, index int, packet *csm.Packet) bool {
return false
}
func (w *WMIMatcher) Match(info csm.MatchInfo, index int, packet *csm.Packet) bool {
if packet == nil {
return false
}
buf := new(bytes.Buffer)
buf.Write(packet.Buffer)
wmiRecv := DCERPC_DEFAULT{}
binary.Read(buf, binary.LittleEndian, &wmiRecv)
switch index {
case 0:
if wmiRecv.Call_id != WMI_CALL_ID_1 {
return false
}
if wmiRecv.Ptype != PDU_BIND_ACK {
return false
}
return true
case 1:
if wmiRecv.Call_id != WMI_CALL_ID_2 {
return false
}
if wmiRecv.Ptype != PDU_RESP {
return false
}
return true
}
return false
}
func NewMatcher() csm.Matcher {
m := &WMIMatcher{}
ds1 := DCERPC_DEFAULT{
Rpc_ver: 5,
Rpc_ver_minor: 0,
Ptype: PDU_BIND,
Flags: 0x03,
Drep: 0x10,
Frag_len: 16 + 56,
Auth_len: 0,
Call_id: WMI_CALL_ID_1,
}
ds2 := DCERPC_DEFAULT{
Rpc_ver: 5,
Rpc_ver_minor: 0,
Ptype: PDU_REQ,
Flags: 0x03,
Drep: 0x10,
Frag_len: 16 + 8,
Auth_len: 0,
Call_id: WMI_CALL_ID_2,
}
ioxidr := DCERPC_IOXIDResolver{
MaxXmitFrag: 0x16d0,
MaxRecvFrag: 0x16d0,
AssocGroup: 0,
NumCtxItem: 1,
ContextId: 0,
NumTransItem: 1,
//interfaces
InterfaceVer: 0,
InterfaceVerMinor: 0,
//transSyntax
TransSyntaxVer: 2,
}
ioxidr.Interfaces[0] = 0xc4
ioxidr.Interfaces[1] = 0xfe
ioxidr.Interfaces[2] = 0xfc
ioxidr.Interfaces[3] = 0x99
ioxidr.Interfaces[4] = 0x60
ioxidr.Interfaces[5] = 0x52
ioxidr.Interfaces[6] = 0x1b
ioxidr.Interfaces[7] = 0x10
ioxidr.Interfaces[8] = 0xbb
ioxidr.Interfaces[9] = 0xcb
ioxidr.Interfaces[10] = 0x00
ioxidr.Interfaces[11] = 0xaa
ioxidr.Interfaces[12] = 0x00
ioxidr.Interfaces[13] = 0x21
ioxidr.Interfaces[14] = 0x34
ioxidr.Interfaces[15] = 0x7a
ioxidr.TransSyntax[0] = 0x04
ioxidr.TransSyntax[1] = 0x5d
ioxidr.TransSyntax[2] = 0x88
ioxidr.TransSyntax[3] = 0x8a
ioxidr.TransSyntax[4] = 0xeb
ioxidr.TransSyntax[5] = 0x1c
ioxidr.TransSyntax[6] = 0xc9
ioxidr.TransSyntax[7] = 0x11
ioxidr.TransSyntax[8] = 0x9f
ioxidr.TransSyntax[9] = 0xe8
ioxidr.TransSyntax[10] = 0x08
ioxidr.TransSyntax[11] = 0x00
ioxidr.TransSyntax[12] = 0x2b
ioxidr.TransSyntax[13] = 0x10
ioxidr.TransSyntax[14] = 0x48
ioxidr.TransSyntax[15] = 0x60
da := DCERPC_ALIVE{
AllocHint: 0,
ContextId: 0,
OpNum: 3,
}
buf1 := new(bytes.Buffer)
binary.Write(buf1, binary.LittleEndian, ds1)
ds1Bytes := buf1.Bytes()
buf2 := new(bytes.Buffer)
binary.Write(buf2, binary.LittleEndian, ds2)
ds2Bytes := buf2.Bytes()
buf3 := new(bytes.Buffer)
binary.Write(buf3, binary.LittleEndian, ioxidr)
ioxidrBytes := buf3.Bytes()
buf4 := new(bytes.Buffer)
binary.Write(buf4, binary.LittleEndian, da)
daBytes := buf4.Bytes()
firstByte := make([]byte, len(ds1Bytes)+len(ioxidrBytes))
copy(firstByte[0:], ds1Bytes)
copy(firstByte[len(ds1Bytes):], ioxidrBytes)
secondByte := make([]byte, len(ds2Bytes)+len(daBytes))
copy(secondByte[0:], ds2Bytes)
copy(secondByte[len(ds2Bytes):], daBytes)
m.AddPacket(csm.NewPacket(firstByte, len(ds1Bytes)+len(ioxidrBytes)))
m.AddPacket(csm.NewPacket(secondByte, len(ds2Bytes)+len(daBytes)))
return m
}
type DCERPC_DEFAULT struct {
Rpc_ver uint8
Rpc_ver_minor uint8
Ptype uint8
Flags uint8
Drep uint32
Frag_len uint16
Auth_len uint16
Call_id uint32
}
type DCERPC_ALIVE struct {
AllocHint uint32
ContextId uint16
OpNum uint16
}
type DCERPC_IOXIDResolver struct {
MaxXmitFrag uint16
MaxRecvFrag uint16
AssocGroup uint32
NumCtxItem uint8
UnknownCode [3]uint8
ContextId uint16
NumTransItem uint8
UnknownCode2 uint8
Interfaces [16]uint8
InterfaceVer uint16
InterfaceVerMinor uint16
TransSyntax [16]uint8
TransSyntaxVer uint32
}

49
wmi/wmi_test.go Normal file
View File

@ -0,0 +1,49 @@
package wmi
import (
"fmt"
"net"
"testing"
csm "git.loafle.net/commons/service_matcher-go"
)
func TestWMI(t *testing.T) {
lm := NewMatcher()
//port := types.NewPort("135", types.NewHost("192.168.1.1"), types.TYPE_TCP)
//scanInfo := scaninfo.NewServiceScanInfo(port)
//var ipport string
//ipport = port.Host.Ip + ":" + string(port.Port)
//fmt.Println(ipport)
client, _ := net.Dial("tcp", "192.168.1.106:135")
defer client.Close()
fmt.Println(lm.PacketCount())
for ii := 0; ii < lm.PacketCount(); ii++ {
pack := lm.Packet(ii)
fmt.Println(pack)
client.Write(pack.Buffer)
bytes := make([]byte, 1024)
read, _ := client.Read(bytes)
//fmt.Println(bytes)
b := lm.Match(nil, ii, csm.NewPacket(bytes, read))
if b {
fmt.Println("Good")
}
}
}