From 7acbf6951ab93b808bfd92478ec9c61a648e31a4 Mon Sep 17 00:00:00 2001 From: free5gc-org Date: Mon, 13 Sep 2021 09:19:56 +0000 Subject: [PATCH] Move and refactor util packages --- .gitignore | 15 + .golangci.yml | 331 ++++++++++++++ LICENSE => LICENSE.txt | 5 +- flowdesc/ip_filter_rule.go | 382 ++++++++++++++++ flowdesc/ip_filter_rule_field.go | 77 ++++ flowdesc/ip_filter_rule_field_test.go | 99 +++++ flowdesc/ip_filter_rule_test.go | 231 ++++++++++ fsm/fsm.go | 144 ++++++ fsm/fsm_test.go | 101 +++++ fsm/logger/logger.go | 42 ++ fsm/state.go | 41 ++ go.mod | 17 + go.sum | 180 ++++++++ httpwrapper/httpwrapper.go | 76 ++++ httpwrapper/httpwrapper_test.go | 33 ++ idgenerator/idgenerator.go | 69 +++ idgenerator/idgenerator_test.go | 154 +++++++ logger/logger.go | 246 +++++++++++ logger/logger_config.go | 60 +++ mapstruct/mapstruct.go | 34 ++ mapstruct/mapstruct_test.go | 81 ++++ milenage/milenage.go | 605 ++++++++++++++++++++++++++ milenage/milenage_test.go | 520 ++++++++++++++++++++++ mongoapi/mongoapi.go | 309 +++++++++++++ ueauth/ueauth.go | 53 +++ ueauth/ueauth_test.go | 69 +++ version/version.go | 39 ++ version/version_test.go | 86 ++++ 28 files changed, 4097 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .golangci.yml rename LICENSE => LICENSE.txt (98%) create mode 100644 flowdesc/ip_filter_rule.go create mode 100644 flowdesc/ip_filter_rule_field.go create mode 100644 flowdesc/ip_filter_rule_field_test.go create mode 100644 flowdesc/ip_filter_rule_test.go create mode 100644 fsm/fsm.go create mode 100644 fsm/fsm_test.go create mode 100644 fsm/logger/logger.go create mode 100644 fsm/state.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 httpwrapper/httpwrapper.go create mode 100644 httpwrapper/httpwrapper_test.go create mode 100644 idgenerator/idgenerator.go create mode 100644 idgenerator/idgenerator_test.go create mode 100644 logger/logger.go create mode 100644 logger/logger_config.go create mode 100644 mapstruct/mapstruct.go create mode 100644 mapstruct/mapstruct_test.go create mode 100644 milenage/milenage.go create mode 100644 milenage/milenage_test.go create mode 100644 mongoapi/mongoapi.go create mode 100644 ueauth/ueauth.go create mode 100644 ueauth/ueauth_test.go create mode 100644 version/version.go create mode 100644 version/version_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05fa7d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Toolchain +# Goland project folder +.idea/ +# Visual Studio Code +.vscode/ +# emacs/vim +GPATH +GRTAGS +GTAGS +TAGS +tags +cscope.* +# mac +.DS_Store + diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..fbb3db5 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,331 @@ +# This file contains all available configuration options +# with their default values. +# options for analysis running +run: + # default concurrency is a available CPU number + concurrency: 4 + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 1m + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + # include test files or not, default is true + tests: true + # list of build tags, all linters use it. Default is empty list. + build-tags: + # which dirs to skip: issues from them won't be reported; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but default dirs are skipped independently + # from this option's value (see skip-dirs-use-default). + # "/" will be replaced by current OS file path separator to properly work + # on Windows. + skip-dirs: + # default is true. Enables skipping of directories: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + skip-dirs-use-default: true + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. + # "/" will be replaced by current OS file path separator to properly work + # on Windows. + skip-files: + - "api_.*\\.go$" + - "model_.*\\.go$" + - "routers.go" + - "client.go" + - "configuration.go" + - "nas.go" + # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + #modules-download-mode: readonly|release|vendor + # Allow multiple parallel golangci-lint instances running. + # If false (default) - golangci-lint acquires file lock on start. + allow-parallel-runners: true +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" + format: colored-line-number + # print lines of code with issue, default is true + print-issued-lines: true + # print linter name in the end of issue text, default is true + print-linter-name: true + # make issues output unique by line, default is true + uniq-by-line: true +# all available settings of specific linters +linters-settings: + errcheck: + # report about not checking of errors in type assertions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: false + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: true + # [deprecated] comma-separated list of pairs of the form pkg:regex + # the regex is used to ignore names within pkg. (default "fmt:.*"). + # see https://github.com/kisielk/errcheck#the-deprecated-method for details + #ignore: fmt:.*,io/ioutil:^Read.* + # path to a file containing a list of functions to exclude from checking + # see https://github.com/kisielk/errcheck#excluding-functions for details + #exclude: /path/to/file.txt + funlen: + lines: 60 + statements: 40 + gocognit: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 10 + nestif: + # minimal complexity of if statements to report, 5 by default + min-complexity: 4 + goconst: + # minimal length of string constant, 3 by default + min-len: 3 + # minimal occurrences count to trigger, 3 by default + min-occurrences: 3 + gocritic: + # Which checks should be enabled; can't be combined with 'disabled-checks'; + # See https://go-critic.github.io/overview#checks-overview + # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run` + # By default list of stable checks is used. + enabled-checks: + #- rangeValCopy + # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty + disabled-checks: + - regexpMust + # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks. + # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". + enabled-tags: + - performance + disabled-tags: + - experimental + settings: # settings passed to gocritic + captLocal: # must be valid enabled check name + paramsOnly: true + rangeValCopy: + sizeThreshold: 32 + gocyclo: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 10 + godox: + # report any comments starting with keywords, this is useful for TODO or FIXME comments that + # might be left in the code accidentally and should be resolved before merging + keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting + #- TODO + - FIXME + - BUG + #- NOTE + #- OPTIMIZE # marks code that should be optimized before merging + #- HACK # marks hack-arounds that should be removed before merging + - XXX # Fatal! Important problem + gofmt: + # simplify code: gofmt with `-s` option, true by default + simplify: true + goimports: + # put imports beginning with prefix after 3rd-party packages; + # it's a comma-separated list of prefixes + local-prefixes: github.com/org/project + golint: + # minimal confidence for issues, default is 0.8 + min-confidence: 0.8 + gomnd: + settings: + mnd: + # the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. + checks: argument,case,condition,operation,return,assign + gomodguard: + allowed: + modules: # List of allowed modules + # - gopkg.in/yaml.v2 + domains: # List of allowed module domains + # - golang.org + blocked: + modules: # List of blocked modules + # - github.com/uudashr/go-module: # Blocked module + # recommendations: # Recommended modules that should be used instead (Optional) + # - golang.org/x/mod + # reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional) + versions: # List of blocked module version constraints + # - github.com/mitchellh/go-homedir: # Blocked module with version constraint + # version: "< 1.1.0" # Version constraint, see https://github.com/Masterminds/semver#basic-comparisons + # reason: "testing if blocked version constraint works." # Reason why the version constraint exists. (Optional) + govet: + # report about shadowed variables + check-shadowing: true + # settings per analyzer + settings: + printf: # analyzer name, run `go tool vet help` to see all analyzers + funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf + # enable or disable analyzers by name + enable: + - atomicalign + enable-all: false + disable: + - shadow + disable-all: false + depguard: + list-type: blacklist + include-go-root: false + packages: + - github.com/sirupsen/logrus + packages-with-error-message: + # specify an error message to output when a blacklisted package is used + - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log" + lll: + # max line length, lines longer will be reported. Default is 120. + # '\t' is counted as 1 character by default, and can be changed with the tab-width option + line-length: 120 + # tab width in spaces. Default to 1. + tab-width: 1 + maligned: + # print struct with more effective memory layout or not, false by default + suggest-new: true + nakedret: + # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 + max-func-lines: 30 + testpackage: + # regexp pattern to skip files + skip-regexp: (export|internal)_test\.go + unused: + # treat code as a program (not a library) and report unused exported identifiers; default is false. + # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: + # if it's called for subdir of a project it can't find funcs usages. All text editor integrations + # with golangci-lint call it on a directory with the changed file. + check-exported: false + whitespace: + multi-if: false # Enforces newlines (or comments) after every multi-line if statement + multi-func: false # Enforces newlines (or comments) after every multi-line function signature + gci: + local-prefixes: "github.com/free5gc" + misspell: + #locale: US + ignore-words: + whitespace: + multi-if: false # Enforces newlines (or comments) after every multi-line if statement + multi-func: false # Enforces newlines (or comments) after every multi-line function signature + wsl: + # If true append is only allowed to be cuddled if appending value is + # matching variables, fields or types on line above. Default is true. + strict-append: true + # Allow calls and assignments to be cuddled as long as the lines have any + # matching variables, fields or types. Default is true. + allow-assign-and-call: true + # Allow multiline assignments to be cuddled. Default is true. + allow-multiline-assign: true + # Allow declarations (var) to be cuddled. + allow-cuddle-declarations: false + # Allow trailing comments in ending of blocks + allow-trailing-comment: true + # Force newlines in end of case at this limit (0 = never). + force-case-trailing-whitespace: 0 + # Force cuddling of err checks with err var assignment + force-err-cuddling: false + # Allow leading comments to be separated with empty liens + allow-separated-leading-comment: false + custom: + # Each custom linter should have a unique name. + +linters: + enable: + - gofmt + - govet + - errcheck + - staticcheck + - unused + - gosimple + - structcheck + - varcheck + - ineffassign + - deadcode + - typecheck + # Additional + - lll + - godox + #- gomnd + #- goconst + # - gocognit + # - maligned + # - nestif + # - gomodguard + - nakedret + - gci + - misspell + - gofumpt + - whitespace + - unconvert + - predeclared + - noctx + - dogsled + - bodyclose + - asciicheck + #- stylecheck + # - unparam + # - wsl + + #disable-all: false + fast: true +issues: + # List of regexps of issue texts to exclude, empty list by default. + # But independently from this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. To list all + # excluded by default patterns execute `golangci-lint run --help` + exclude: + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + # Exclude some linters from running on tests files. + # Independently from option `exclude` we use default exclude patterns, + # it can be disabled by this option. To list all + # excluded by default patterns execute `golangci-lint run --help`. + # Default value for this option is true. + exclude-use-default: false + # The default value is false. If set to true exclude and exclude-rules + # regular expressions become case sensitive. + exclude-case-sensitive: false + # The list of ids of default excludes to include or disable. By default it's empty. + include: + #- EXC0002 # disable excluding of issues about comments from golint + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + #max-issues-per-linter: 0 + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + #max-same-issues: 0 + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing + # large codebase. It's not practical to fix all existing issues at the moment + # of integration: much better don't allow issues in new code. + # Default is false. + new: false + # Show only new issues created after git revision `REV` + new-from-rev: "" + # Show only new issues created in git patch with set file path. + #new-from-patch: path/to/patch/file +severity: + # Default value is empty string. + # Set the default severity for issues. If severity rules are defined and the issues + # do not match or no severity is provided to the rule this will be the default + # severity applied. Severities should match the supported severity names of the + # selected out format. + # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity + # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity + # - Github: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + default-severity: error + # The default value is false. + # If set to true severity-rules regular expressions become case sensitive. + case-sensitive: false + # Default value is empty list. + # When a list of severity rules are provided, severity information will be added to lint + # issues. Severity rules have the same filtering capability as exclude rules except you + # are allowed to specify one matcher per severity rule. + # Only affects out formats that support setting severity information. + rules: + - linters: + - gomnd + severity: ignore diff --git a/LICENSE b/LICENSE.txt similarity index 98% rename from LICENSE rename to LICENSE.txt index 261eeb9..4124f39 100644 --- a/LICENSE +++ b/LICENSE.txt @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -186,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,4 +199,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. + limitations under the License. \ No newline at end of file diff --git a/flowdesc/ip_filter_rule.go b/flowdesc/ip_filter_rule.go new file mode 100644 index 0000000..5123b79 --- /dev/null +++ b/flowdesc/ip_filter_rule.go @@ -0,0 +1,382 @@ +package flowdesc + +import ( + "errors" + "fmt" + "net" + "regexp" + "strconv" + "strings" +) + +const ProtocolNumberAny = 0xfc + +// Action - Action of IPFilterRule +type Action string + +// Action const +const ( + Permit Action = "permit" + Deny Action = "deny" +) + +// Direction - direction of IPFilterRule +type Direction string + +// Direction const +const ( + In Direction = "in" + Out Direction = "out" +) + +func flowDescErrorf(format string, a ...interface{}) error { + msg := fmt.Sprintf(format, a...) + return fmt.Errorf("flowdesc: %s", msg) +} + +// IPFilterRule define RFC 3588 that referd by TS 29.212 +type IPFilterRule struct { + action Action + dir Direction + proto uint8 // protocol number + srcIP string //
+ srcPorts string // [ports] + dstIP string //
+ dstPorts string // [ports] +} + +// NewIPFilterRule returns a new IPFilterRule instance +func NewIPFilterRule() *IPFilterRule { + r := &IPFilterRule{ + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + srcIP: "", + srcPorts: "", + dstIP: "", + dstPorts: "", + } + return r +} + +// SetAction sets action of the IPFilterRule +func (r *IPFilterRule) SetAction(action Action) error { + switch action { + case Permit: + r.action = action + case Deny: + r.action = action + default: + return flowDescErrorf("'%s' is not allow, action only accept 'permit' or 'deny'", action) + } + return nil +} + +// GetAction returns action of the IPFilterRule +func (r *IPFilterRule) GetAction() Action { + return r.action +} + +// SetDirection sets direction of the IPFilterRule +func (r *IPFilterRule) SetDirection(dir Direction) error { + switch dir { + case Out: + r.dir = dir + case In: + return flowDescErrorf("dir cannot be 'in' in core-network") + default: + return flowDescErrorf("'%s' is not allow, dir only accept 'out'", dir) + } + return nil +} + +// GetDirection returns direction of the IPFilterRule +func (r *IPFilterRule) GetDirection() Direction { + return r.dir +} + +// SetProtocol sets IP protocol number of the IPFilterRule +// 0xfc stand for ip (any) +func (r *IPFilterRule) SetProtocol(proto uint8) error { + r.proto = proto + return nil +} + +// GetProtocol returns the ip protocol number of the IPFilterRule +func (r *IPFilterRule) GetProtocol() uint8 { + return r.proto +} + +// SetSourceIP sets source IP of the IPFilterRule +func (r *IPFilterRule) SetSourceIP(networkStr string) error { + if networkStr == "any" || networkStr == "assigned" { + r.srcIP = networkStr + return nil + } + if networkStr[0] == '!' { + return flowDescErrorf("Base on TS 29.212, ! expression shall not be used") + } + + var ipStr string + + ip := net.ParseIP(networkStr) + if ip == nil { + _, ipNet, err := net.ParseCIDR(networkStr) + if err != nil { + return flowDescErrorf("Source IP format error") + } + ipStr = ipNet.String() + } else { + ipStr = ip.String() + } + + r.srcIP = ipStr + return nil +} + +// GetSourceIP returns src of the IPFilterRule +func (r *IPFilterRule) GetSourceIP() string { + return r.srcIP +} + +// SetSourcePorts sets source ports of the IPFilterRule +func (r *IPFilterRule) SetSourcePorts(ports string) error { + if ports == "" { + r.srcPorts = "" + return nil + } + + if match, err := regexp.MatchString("^[0-9]+(-[0-9]+)?(,[0-9]+)*$", ports); err != nil || !match { + return flowDescErrorf("not valid format of port number") + } + + // Check port range + portSlice := regexp.MustCompile(`[\\,\\-]+`).Split(ports, -1) + for _, portStr := range portSlice { + port, err := strconv.Atoi(portStr) + if err != nil { + return err + } + if port < 0 || port > 65535 { + return errors.New("Invalid port number") + } + } + + r.srcPorts = ports + return nil +} + +// GetSourcePorts returns src ports of the IPFilterRule +func (r *IPFilterRule) GetSourcePorts() string { + return r.srcPorts +} + +// SetDestinationIP sets destination IP of the IPFilterRule +func (r *IPFilterRule) SetDestinationIP(networkStr string) error { + if networkStr == "any" || networkStr == "assigned" { + r.dstIP = networkStr + return nil + } + if networkStr[0] == '!' { + return flowDescErrorf("Base on TS 29.212, ! expression shall not be used") + } + + var ipDst string + + ip := net.ParseIP(networkStr) + if ip == nil { + _, ipNet, err := net.ParseCIDR(networkStr) + if err != nil { + return flowDescErrorf("Source IP format error") + } + ipDst = ipNet.String() + } else { + ipDst = ip.String() + } + + r.dstIP = ipDst + return nil +} + +// GetDestinationIP returns dst of the IPFilterRule +func (r *IPFilterRule) GetDestinationIP() string { + return r.dstIP +} + +// SetDestinationPorts sets destination ports of the IPFilterRule +func (r *IPFilterRule) SetDestinationPorts(ports string) error { + if ports == "" { + r.dstPorts = ports + return nil + } + + match, err := regexp.MatchString("^[0-9]+(-[0-9]+)?(,[0-9]+)*$", ports) + if err != nil { + return flowDescErrorf("Regex match error") + } + if !match { + return flowDescErrorf("Ports format error") + } + + // Check port range + portSlice := regexp.MustCompile(`[\\,\\-]+`).Split(ports, -1) + for _, portStr := range portSlice { + port, err := strconv.Atoi(portStr) + if err != nil { + return err + } + if port < 0 || port > 65535 { + return flowDescErrorf("Invalid port number") + } + } + + r.dstPorts = ports + return nil +} + +// GetDestinationPorts returns src ports of the IPFilterRule +func (r *IPFilterRule) GetDestinationPorts() string { + return r.dstPorts +} + +// SwapSourceAndDestination swap the src and dst of the IPFilterRule +func (r *IPFilterRule) SwapSourceAndDestination() { + r.srcIP, r.dstIP = r.dstIP, r.srcIP + r.srcPorts, r.dstPorts = r.dstPorts, r.srcPorts +} + +// Encode function out put the IPFilterRule from the struct +func Encode(r *IPFilterRule) (string, error) { + var ipFilterRuleStr []string + + // pre-allocate seven element + ipFilterRuleStr = make([]string, 0, 9) + + // action + switch r.action { + case Permit: + ipFilterRuleStr = append(ipFilterRuleStr, "permit") + case Deny: + ipFilterRuleStr = append(ipFilterRuleStr, "deny") + } + + // dir + switch r.dir { + case Out: + ipFilterRuleStr = append(ipFilterRuleStr, "out") + } + + // proto + if r.proto == ProtocolNumberAny { + ipFilterRuleStr = append(ipFilterRuleStr, "ip") + } else { + ipFilterRuleStr = append(ipFilterRuleStr, strconv.Itoa(int(r.proto))) + } + + // from + ipFilterRuleStr = append(ipFilterRuleStr, "from") + + // src + if r.srcIP != "" { + ipFilterRuleStr = append(ipFilterRuleStr, r.srcIP) + } else { + ipFilterRuleStr = append(ipFilterRuleStr, "any") + } + if r.srcPorts != "" { + ipFilterRuleStr = append(ipFilterRuleStr, r.srcPorts) + } + + // to + ipFilterRuleStr = append(ipFilterRuleStr, "to") + + // dst + if r.dstIP != "" { + ipFilterRuleStr = append(ipFilterRuleStr, r.dstIP) + } else { + ipFilterRuleStr = append(ipFilterRuleStr, "any") + } + if r.dstPorts != "" { + ipFilterRuleStr = append(ipFilterRuleStr, r.dstPorts) + } + + // according TS 29.212 IPFilterRule cannot use [options] + + return strings.Join(ipFilterRuleStr, " "), nil +} + +// Decode parsing the string to IPFilterRule +func Decode(s string, r *IPFilterRule) error { + parts := strings.Split(s, " ") + + var ptr int + + // action + if err := r.SetAction(Action(parts[ptr])); err != nil { + return err + } + ptr++ + + // dir + if err := r.SetDirection(Direction(parts[ptr])); err != nil { + return err + } + ptr++ + + // proto + var protoNumber uint8 + if parts[ptr] == "ip" { + r.proto = ProtocolNumberAny + } else { + if proto, err := strconv.Atoi(parts[ptr]); err != nil { + return flowDescErrorf("parse proto failed: %s", err) + } else { + protoNumber = uint8(proto) + } + if err := r.SetProtocol(protoNumber); err != nil { + return flowDescErrorf("parse proto failed: %s", err) + } + } + ptr++ + + // from + if from := parts[ptr]; from != "from" { + return flowDescErrorf("parse faild: must have 'from'") + } + ptr++ + + // src + if err := r.SetSourceIP(parts[ptr]); err != nil { + return err + } + ptr++ + + if err := r.SetSourcePorts(parts[ptr]); err != nil { + } else { + ptr++ + } + + // to + if to := parts[ptr]; to != "to" { + return flowDescErrorf("parse faild: must have 'to'") + } + ptr++ + + // dst + if err := r.SetDestinationIP(parts[ptr]); err != nil { + return err + } + ptr++ + + // if end of parts + if !(len(parts) > ptr) { + return nil + } + + if err := r.SetDestinationPorts(parts[ptr]); err != nil { + return err + } // else { + //ptr++ + //} + + return nil +} diff --git a/flowdesc/ip_filter_rule_field.go b/flowdesc/ip_filter_rule_field.go new file mode 100644 index 0000000..3cda18f --- /dev/null +++ b/flowdesc/ip_filter_rule_field.go @@ -0,0 +1,77 @@ +package flowdesc + +type IPFilterRuleFieldList []IPFilterRuleField + +type IPFilterRuleField interface { + Set(*IPFilterRule) error +} + +type IPFilterAction struct { + Action Action +} + +func (i *IPFilterAction) Set(r *IPFilterRule) error { + return r.SetAction(i.Action) +} + +type IPFilterDirection struct { + Direction Direction +} + +func (i *IPFilterDirection) Set(r *IPFilterRule) error { + return r.SetDirection(i.Direction) +} + +type IPFilterProto struct { + Proto uint8 +} + +func (i *IPFilterProto) Set(r *IPFilterRule) error { + return r.SetProtocol(i.Proto) +} + +type IPFilterSourceIP struct { + Src string +} + +func (i *IPFilterSourceIP) Set(r *IPFilterRule) error { + return r.SetSourceIP(i.Src) +} + +type IPFilterSourcePorts struct { + Ports string +} + +func (i *IPFilterSourcePorts) Set(r *IPFilterRule) error { + return r.SetSourcePorts(i.Ports) +} + +type IPFilterDestinationIP struct { + Src string +} + +func (i *IPFilterDestinationIP) Set(r *IPFilterRule) error { + return r.SetDestinationIP(i.Src) +} + +type IPFilterDestinationPorts struct { + Ports string +} + +func (i *IPFilterDestinationPorts) Set(r *IPFilterRule) error { + return r.SetDestinationPorts(i.Ports) +} + +func BuildIPFilterRuleFromField(cl IPFilterRuleFieldList) (*IPFilterRule, error) { + rule := NewIPFilterRule() + + var err error + for _, config := range cl { + err = config.Set(rule) + if err != nil { + return nil, flowDescErrorf("build ip filter rule failed by %s", err) + } + } + + return rule, nil +} diff --git a/flowdesc/ip_filter_rule_field_test.go b/flowdesc/ip_filter_rule_field_test.go new file mode 100644 index 0000000..5c514e8 --- /dev/null +++ b/flowdesc/ip_filter_rule_field_test.go @@ -0,0 +1,99 @@ +package flowdesc + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBuildIPFilterRuleFromField(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + configList IPFilterRuleFieldList + ipFilterRule string + }{ + { + name: "default", + configList: IPFilterRuleFieldList{}, + ipFilterRule: "permit out ip from any to any", + }, + { + name: "srcIP", + configList: IPFilterRuleFieldList{ + &IPFilterProto{ + Proto: 17, + }, + &IPFilterSourceIP{ + Src: "192.168.0.0/24", + }, + }, + ipFilterRule: "permit out 17 from 192.168.0.0/24 to any", + }, + { + name: "dstIP", + configList: IPFilterRuleFieldList{ + &IPFilterProto{ + Proto: 17, + }, + &IPFilterSourceIP{ + Src: "192.168.0.0/24", + }, + &IPFilterDestinationIP{ + Src: "10.60.0.0/16", + }, + }, + ipFilterRule: "permit out 17 from 192.168.0.0/24 to 10.60.0.0/16", + }, + { + name: "SinglePort", + configList: IPFilterRuleFieldList{ + &IPFilterProto{ + Proto: 17, + }, + &IPFilterSourceIP{ + Src: "192.168.0.0/24", + }, + &IPFilterSourcePorts{ + Ports: "3000", + }, + &IPFilterDestinationIP{ + Src: "10.60.0.0/16", + }, + }, + ipFilterRule: "permit out 17 from 192.168.0.0/24 3000 to 10.60.0.0/16", + }, + { + name: "PortRange", + configList: IPFilterRuleFieldList{ + &IPFilterProto{ + Proto: ProtocolNumberAny, + }, + &IPFilterSourceIP{ + Src: "192.168.0.0/24", + }, + &IPFilterSourcePorts{ + Ports: "3000", + }, + &IPFilterDestinationIP{ + Src: "10.60.0.0/16", + }, + &IPFilterDestinationPorts{ + Ports: "10000,65535", + }, + }, + ipFilterRule: "permit out ip from 192.168.0.0/24 3000 to 10.60.0.0/16 10000,65535", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ipFilterRule, err := BuildIPFilterRuleFromField(tc.configList) + require.NoError(t, err) + filterRuleContent, err := Encode(ipFilterRule) + require.NoError(t, err) + require.Equal(t, tc.ipFilterRule, filterRuleContent) + }) + } +} diff --git a/flowdesc/ip_filter_rule_test.go b/flowdesc/ip_filter_rule_test.go new file mode 100644 index 0000000..4328dfc --- /dev/null +++ b/flowdesc/ip_filter_rule_test.go @@ -0,0 +1,231 @@ +package flowdesc + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIPFilterRuleEncode(t *testing.T) { + testStr1 := "permit out ip from any to assigned 655" + + rule := NewIPFilterRule() + if rule == nil { + t.Fatal("IP Filter Rule Create Error") + } + + if err := rule.SetAction(Permit); err != nil { + assert.Nil(t, err) + } + + if err := rule.SetDirection(Out); err != nil { + assert.Nil(t, err) + } + + if err := rule.SetProtocol(0xfc); err != nil { + assert.Nil(t, err) + } + + if err := rule.SetSourceIP("any"); err != nil { + assert.Nil(t, err) + } + + if err := rule.SetDestinationIP("assigned"); err != nil { + assert.Nil(t, err) + } + + if err := rule.SetDestinationPorts("655"); err != nil { + assert.Nil(t, err) + } + + result, err := Encode(rule) + if err != nil { + assert.Nil(t, err) + } + if result != testStr1 { + t.Fatalf("Encode error, \n\t expect: %s,\n\t get: %s", testStr1, result) + } +} + +func TestIPFilterRuleDecode(t *testing.T) { + testCases := map[string]struct { + filterRule string + action Action + dir Direction + proto uint8 + src string + srcPorts string + dst string + dstPorts string + }{ + "fully": { + filterRule: "permit out ip from 60.60.0.100 8080 to 60.60.0.1 80", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.100", + srcPorts: "8080", + dst: "60.60.0.1", + dstPorts: "80", + }, + "withoutPorts": { + filterRule: "permit out ip from 60.60.0.100 to 60.60.0.1", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.100", + srcPorts: "", + dst: "60.60.0.1", + dstPorts: "", + }, + "withoutOnePorts": { + filterRule: "permit out ip from 60.60.0.100 8080 to 60.60.0.1", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.100", + srcPorts: "8080", + dst: "60.60.0.1", + dstPorts: "", + }, + "withSrcAny": { + filterRule: "permit out ip from any to 60.60.0.1 8080", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "any", + srcPorts: "", + dst: "60.60.0.1", + dstPorts: "8080", + }, + "withDstAny": { + filterRule: "permit out ip from 60.60.0.1 8080 to any", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.1", + srcPorts: "8080", + dst: "any", + dstPorts: "", + }, + "withAssigned": { + filterRule: "permit out ip from assigned to 60.60.0.1 8080", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "assigned", + srcPorts: "", + dst: "60.60.0.1", + dstPorts: "8080", + }, + } + + for testName, expected := range testCases { + t.Run(testName, func(t *testing.T) { + r := NewIPFilterRule() + err := Decode(expected.filterRule, r) + + require.Equal(t, expected.action, r.GetAction()) + require.Equal(t, expected.dir, r.GetDirection()) + require.Equal(t, expected.proto, r.GetProtocol()) + require.Equal(t, expected.src, r.GetSourceIP()) + require.Equal(t, expected.srcPorts, r.GetSourcePorts()) + require.Equal(t, expected.dst, r.GetDestinationIP()) + require.Equal(t, expected.dstPorts, r.GetDestinationPorts()) + + require.NoError(t, err) + }) + } +} + +func TestIPFilterRuleSwapSourceAndDestination(t *testing.T) { + testCases := map[string]struct { + filterRule string + action Action + dir Direction + proto uint8 + src string + srcPorts string + dst string + dstPorts string + }{ + "fully": { + filterRule: "permit out ip from 60.60.0.100 8080 to 60.60.0.1 80", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.1", + srcPorts: "80", + dst: "60.60.0.100", + dstPorts: "8080", + }, + "withoutPorts": { + filterRule: "permit out ip from 60.60.0.100 to 60.60.0.1", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.1", + srcPorts: "", + dst: "60.60.0.100", + dstPorts: "", + }, + "withoutOnePorts": { + filterRule: "permit out ip from 60.60.0.100 8080 to 60.60.0.1", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.1", + srcPorts: "", + dst: "60.60.0.100", + dstPorts: "8080", + }, + "withSrcAny": { + filterRule: "permit out ip from any to 60.60.0.1 8080", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.1", + srcPorts: "8080", + dst: "any", + dstPorts: "", + }, + "withDstAny": { + filterRule: "permit out ip from 60.60.0.1 8080 to any", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "any", + srcPorts: "", + dst: "60.60.0.1", + dstPorts: "8080", + }, + "withAssigned": { + filterRule: "permit out ip from assigned to 60.60.0.1 8080", + action: Permit, + dir: Out, + proto: ProtocolNumberAny, + src: "60.60.0.1", + srcPorts: "8080", + dst: "assigned", + dstPorts: "", + }, + } + + for testName, expected := range testCases { + t.Run(testName, func(t *testing.T) { + r := NewIPFilterRule() + err := Decode(expected.filterRule, r) + r.SwapSourceAndDestination() + require.Equal(t, expected.action, r.GetAction()) + require.Equal(t, expected.dir, r.GetDirection()) + require.Equal(t, expected.proto, r.GetProtocol()) + require.Equal(t, expected.src, r.GetSourceIP()) + require.Equal(t, expected.srcPorts, r.GetSourcePorts()) + require.Equal(t, expected.dst, r.GetDestinationIP()) + require.Equal(t, expected.dstPorts, r.GetDestinationPorts()) + + require.NoError(t, err) + }) + } +} diff --git a/fsm/fsm.go b/fsm/fsm.go new file mode 100644 index 0000000..a2b221a --- /dev/null +++ b/fsm/fsm.go @@ -0,0 +1,144 @@ +package fsm + +import ( + "fmt" + "os" + "strings" + + "github.com/free5gc/util/fsm/logger" +) + +type ( + EventType string + ArgsType map[string]interface{} +) + +type ( + Callback func(*State, EventType, ArgsType) + Callbacks map[StateType]Callback +) + +// Transition defines a transition +// that a Event is triggered at From state, +// and transfer to To state after the Event +type Transition struct { + Event EventType + From StateType + To StateType +} + +type Transitions []Transition + +type eventKey struct { + Event EventType + From StateType +} + +// Entry/Exit event are defined by fsm package +const ( + EntryEvent EventType = "Entry event" + ExitEvent EventType = "Exit event" +) + +type FSM struct { + // transitions stores one transition for each event + transitions map[eventKey]Transition + // callbacks stores one callback function for one state + callbacks map[StateType]Callback +} + +// NewFSM create a new FSM object then registers transitions and callbacks to it +func NewFSM(transitions Transitions, callbacks Callbacks) (*FSM, error) { + fsm := &FSM{ + transitions: make(map[eventKey]Transition), + callbacks: make(map[StateType]Callback), + } + + allStates := make(map[StateType]bool) + + for _, transition := range transitions { + key := eventKey{ + Event: transition.Event, + From: transition.From, + } + if _, ok := fsm.transitions[key]; ok { + return nil, fmt.Errorf("Duplicate transition: %+v", transition) + } else { + fsm.transitions[key] = transition + allStates[transition.From] = true + allStates[transition.To] = true + } + } + + for state, callback := range callbacks { + if _, ok := allStates[state]; !ok { + return nil, fmt.Errorf("Unknown state: %+v", state) + } else { + fsm.callbacks[state] = callback + } + } + return fsm, nil +} + +// SendEvent triggers a callback with an event, and do transition after callback if need +// There are 3 types of callback: +// - on exit callback: call when fsm leave one state, with ExitEvent event +// - event callback: call when user trigger a user-defined event +// - on entry callback: call when fsm enter one state, with EntryEvent event +func (fsm *FSM) SendEvent(state *State, event EventType, args ArgsType) error { + key := eventKey{ + From: state.Current(), + Event: event, + } + + if trans, ok := fsm.transitions[key]; ok { + logger.FsmLog.Infof("Handle event[%s], transition from [%s] to [%s]", event, trans.From, trans.To) + + // exit callback + if trans.From != trans.To { + fsm.callbacks[trans.From](state, ExitEvent, args) + } + + // event callback + fsm.callbacks[trans.From](state, event, args) + + // entry callback + if trans.From != trans.To { + state.Set(trans.To) + fsm.callbacks[trans.To](state, EntryEvent, args) + } + return nil + } else { + return fmt.Errorf("Unknown transition[From: %s, Event: %s]", state.Current(), event) + } +} + +// ExportDot export fsm in dot format to outfile, which can be visualize by graphviz +func ExportDot(fsm *FSM, outfile string) error { + dot := `digraph FSM { + rankdir=LR + size="100" + node[width=1 fixedsize=false shape=ellipse style=filled fillcolor="skyblue"] + ` + + for _, trans := range fsm.transitions { + link := fmt.Sprintf("\t%s -> %s [label=\"%s\"]", trans.From, trans.To, trans.Event) + dot = dot + "\r\n" + link + } + + dot = dot + "\r\n}\n" + + if !strings.HasSuffix(outfile, ".dot") { + outfile = fmt.Sprintf("%s.dot", outfile) + } + + if file, err := os.Create(outfile); err != nil { + return err + } else { + if _, err = file.WriteString(dot); err != nil { + return err + } + fmt.Printf("Output the FSM to \"%s\"\n", outfile) + return file.Close() + } +} diff --git a/fsm/fsm_test.go b/fsm/fsm_test.go new file mode 100644 index 0000000..b6215eb --- /dev/null +++ b/fsm/fsm_test.go @@ -0,0 +1,101 @@ +package fsm + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + Opened StateType = "Opened" + Closed StateType = "Closed" +) + +const ( + Open EventType = "Open" + Close EventType = "Close" +) + +func TestState(t *testing.T) { + s := NewState(Closed) + + assert.Equal(t, Closed, s.Current(), "Current() failed") + assert.True(t, s.Is(Closed), "Is() failed") + + s.Set(Opened) + + assert.Equal(t, Opened, s.Current(), "Current() failed") + assert.True(t, s.Is(Opened), "Is() failed") +} + +func TestFSM(t *testing.T) { + f, err := NewFSM(Transitions{ + {Event: Open, From: Closed, To: Opened}, + {Event: Close, From: Opened, To: Closed}, + {Event: Open, From: Opened, To: Opened}, + {Event: Close, From: Closed, To: Closed}, + }, Callbacks{ + Opened: func(state *State, event EventType, args ArgsType) { + fmt.Printf("event [%+v] at state [%+v]\n", event, state.Current()) + }, + Closed: func(state *State, event EventType, args ArgsType) { + fmt.Printf("event [%+v] at state [%+v]\n", event, state.Current()) + }, + }) + + s := NewState(Closed) + + assert.Nil(t, err, "NewFSM() failed") + + assert.Nil(t, f.SendEvent(s, Open, ArgsType{"TestArg": "test arg"}), "SendEvent() failed") + assert.Nil(t, f.SendEvent(s, Close, ArgsType{"TestArg": "test arg"}), "SendEvent() failed") + assert.True(t, s.Is(Closed), "Transition failed") + + fakeEvent := EventType("fake event") + assert.EqualError(t, f.SendEvent(s, fakeEvent, nil), + fmt.Sprintf("Unknown transition[From: %s, Event: %s]", s.Current(), fakeEvent)) +} + +func TestFSMInitFail(t *testing.T) { + duplicateTrans := Transition{ + Event: Close, From: Opened, To: Closed, + } + _, err := NewFSM(Transitions{ + {Event: Open, From: Closed, To: Opened}, + duplicateTrans, + duplicateTrans, + {Event: Open, From: Opened, To: Opened}, + {Event: Close, From: Closed, To: Closed}, + }, Callbacks{ + Opened: func(state *State, event EventType, args ArgsType) { + fmt.Printf("event [%+v] at state [%+v]\n", event, state.Current()) + }, + Closed: func(state *State, event EventType, args ArgsType) { + fmt.Printf("event [%+v] at state [%+v]\n", event, state.Current()) + }, + }) + + assert.EqualError(t, err, fmt.Sprintf("Duplicate transition: %+v", duplicateTrans)) + + fakeState := StateType("fake state") + + _, err = NewFSM(Transitions{ + {Event: Open, From: Closed, To: Opened}, + {Event: Close, From: Opened, To: Closed}, + {Event: Open, From: Opened, To: Opened}, + {Event: Close, From: Closed, To: Closed}, + }, Callbacks{ + Opened: func(state *State, event EventType, args ArgsType) { + fmt.Printf("event [%+v] at state [%+v]\n", event, state.Current()) + }, + Closed: func(state *State, event EventType, args ArgsType) { + fmt.Printf("event [%+v] at state [%+v]\n", event, state.Current()) + }, + fakeState: func(state *State, event EventType, args ArgsType) { + fmt.Printf("event [%+v] at state [%+v]\n", event, state.Current()) + }, + }) + + assert.EqualError(t, err, fmt.Sprintf("Unknown state: %+v", fakeState)) +} diff --git a/fsm/logger/logger.go b/fsm/logger/logger.go new file mode 100644 index 0000000..5b6f915 --- /dev/null +++ b/fsm/logger/logger.go @@ -0,0 +1,42 @@ +package logger + +import ( + "time" + + formatter "github.com/antonfisher/nested-logrus-formatter" + "github.com/sirupsen/logrus" +) + +var ( + log *logrus.Logger + FsmLog *logrus.Entry +) + +func init() { + log = logrus.New() + log.SetReportCaller(false) + + log.Formatter = &formatter.Formatter{ + TimestampFormat: time.RFC3339, + TrimMessages: true, + NoFieldsSpace: true, + HideKeys: true, + FieldsOrder: []string{"component", "category"}, + } + + FsmLog = log.WithFields(logrus.Fields{"component": "LIB", "category": "FSM"}) +} + +func GetLogger() *logrus.Logger { + return log +} + +func SetLogLevel(level logrus.Level) { + FsmLog.Infoln("set log level :", level) + log.SetLevel(level) +} + +func SetReportCaller(enable bool) { + FsmLog.Infoln("set report call :", enable) + log.SetReportCaller(enable) +} diff --git a/fsm/state.go b/fsm/state.go new file mode 100644 index 0000000..a75b870 --- /dev/null +++ b/fsm/state.go @@ -0,0 +1,41 @@ +package fsm + +import "sync" + +type StateType string + +// State is a thread-safe structure that represents the state of a object +// and it can be used for FSM +type State struct { + // current state of the State object + current StateType + // stateMutex ensures that all operations to current is thread-safe + stateMutex sync.RWMutex +} + +// NewState create a State object with current state set to initState +func NewState(initState StateType) *State { + s := &State{current: initState} + return s +} + +// Current get the current state +func (state *State) Current() StateType { + state.stateMutex.RLock() + defer state.stateMutex.RUnlock() + return state.current +} + +// Is return true if the current state is equal to target +func (state *State) Is(target StateType) bool { + state.stateMutex.RLock() + defer state.stateMutex.RUnlock() + return state.current == target +} + +// Set current state to next +func (state *State) Set(next StateType) { + state.stateMutex.Lock() + defer state.stateMutex.Unlock() + state.current = next +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4de56ea --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/free5gc/util + +go 1.14 + +require ( + github.com/antonfisher/nested-logrus-formatter v1.3.1 + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d + github.com/evanphx/json-patch v0.5.2 + github.com/gin-gonic/gin v1.7.3 + github.com/mitchellh/mapstructure v1.4.1 + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.8.1 + github.com/smartystreets/goconvey v1.6.4 + github.com/stretchr/testify v1.7.0 + go.mongodb.org/mongo-driver v1.7.1 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7105b63 --- /dev/null +++ b/go.sum @@ -0,0 +1,180 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ= +github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.3 h1:aMBzLJ/GMEYmv1UWs2FFTcPISLrQH2mRgL9Glz8xows= +github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +go.mongodb.org/mongo-driver v1.7.1 h1:jwqTeEM3x6L9xDXrCxN0Hbg7vdGfPBOTIkr0+/LYZDA= +go.mongodb.org/mongo-driver v1.7.1/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/httpwrapper/httpwrapper.go b/httpwrapper/httpwrapper.go new file mode 100644 index 0000000..a841cf3 --- /dev/null +++ b/httpwrapper/httpwrapper.go @@ -0,0 +1,76 @@ +package httpwrapper + +import ( + "crypto/tls" + "fmt" + "net/http" + "net/url" + "os" + "time" + + "github.com/pkg/errors" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +type Request struct { + Params map[string]string + Header http.Header + Query url.Values + Body interface{} + URL *url.URL +} + +func NewRequest(req *http.Request, body interface{}) *Request { + ret := &Request{} + ret.Query = req.URL.Query() + ret.Header = req.Header + ret.Body = body + ret.Params = make(map[string]string) + ret.URL = req.URL + return ret +} + +type Response struct { + Header http.Header + Status int + Body interface{} +} + +func NewResponse(code int, h http.Header, body interface{}) *Response { + ret := &Response{} + ret.Status = code + ret.Header = h + ret.Body = body + return ret +} + +// NewHttp2Server returns a server instance with HTTP/2.0 and HTTP/2.0 cleartext support +// If this function cannot open or create the secret log file, +// **it still returns server instance** but without the secret log and error indication +func NewHttp2Server(bindAddr string, preMasterSecretLogPath string, handler http.Handler) (*http.Server, error) { + if handler == nil { + return nil, errors.New("server needs handler to handle request") + } + + h2Server := &http2.Server{ + // TODO: extends the idle time after re-use openapi client + IdleTimeout: 1 * time.Millisecond, + } + server := &http.Server{ + Addr: bindAddr, + Handler: h2c.NewHandler(handler, h2Server), + } + + if preMasterSecretLogPath != "" { + preMasterSecretFile, err := os.OpenFile(preMasterSecretLogPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + if err != nil { + return nil, fmt.Errorf("create pre-master-secret log [%s] fail: %s", preMasterSecretLogPath, err) + } + server.TLSConfig = &tls.Config{ + KeyLogWriter: preMasterSecretFile, + } + } + + return server, nil +} diff --git a/httpwrapper/httpwrapper_test.go b/httpwrapper/httpwrapper_test.go new file mode 100644 index 0000000..3d46f2d --- /dev/null +++ b/httpwrapper/httpwrapper_test.go @@ -0,0 +1,33 @@ +package httpwrapper + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewRequest(t *testing.T) { + req, err := http.NewRequestWithContext(context.Background(), + "GET", "http://localhost:8080?name=NCTU&location=Hsinchu", nil) + if err != nil { + t.Errorf("TestNewRequest error: %+v", err) + } + req.Header.Set("Location", "https://www.nctu.edu.tw/") + request := NewRequest(req, 1000) + assert.Equal(t, "https://www.nctu.edu.tw/", request.Header.Get("Location")) + assert.Equal(t, "NCTU", request.Query.Get("name")) + assert.Equal(t, "Hsinchu", request.Query.Get("location")) + assert.Equal(t, 1000, request.Body) +} + +func TestNewResponse(t *testing.T) { + response := NewResponse(http.StatusCreated, map[string][]string{ + "Location": {"https://www.nctu.edu.tw/"}, + "Refresh": {"url=https://free5gc.org"}, + }, 1000) + assert.Equal(t, "https://www.nctu.edu.tw/", response.Header.Get("Location")) + assert.Equal(t, "url=https://free5gc.org", response.Header.Get("Refresh")) + assert.Equal(t, 1000, response.Body) +} diff --git a/idgenerator/idgenerator.go b/idgenerator/idgenerator.go new file mode 100644 index 0000000..4407478 --- /dev/null +++ b/idgenerator/idgenerator.go @@ -0,0 +1,69 @@ +// idgenerator is used for generating ID from minValue to maxValue. +// It will allocate IDs in range [minValue, maxValue] +// It is thread-safe when allocating IDs +package idgenerator + +import ( + "errors" + "sync" +) + +type IDGenerator struct { + lock sync.Mutex + minValue int64 + maxValue int64 + valueRange int64 + offset int64 + usedMap map[int64]bool +} + +// Initialize an IDGenerator with minValue and maxValue. +func NewGenerator(minValue, maxValue int64) *IDGenerator { + idGenerator := &IDGenerator{} + idGenerator.init(minValue, maxValue) + return idGenerator +} + +func (idGenerator *IDGenerator) init(minValue, maxValue int64) { + idGenerator.minValue = minValue + idGenerator.maxValue = maxValue + idGenerator.valueRange = maxValue - minValue + 1 + idGenerator.offset = 0 + idGenerator.usedMap = make(map[int64]bool) +} + +// Allocate and return an id in range [minValue, maxValue] +func (idGenerator *IDGenerator) Allocate() (int64, error) { + idGenerator.lock.Lock() + defer idGenerator.lock.Unlock() + + offsetBegin := idGenerator.offset + for { + if _, ok := idGenerator.usedMap[idGenerator.offset]; ok { + idGenerator.updateOffset() + + if idGenerator.offset == offsetBegin { + return 0, errors.New("No available value range to allocate id") + } + } else { + break + } + } + idGenerator.usedMap[idGenerator.offset] = true + id := idGenerator.offset + idGenerator.minValue + idGenerator.updateOffset() + return id, nil +} + +// param: +// - id: id to free +func (idGenerator *IDGenerator) FreeID(id int64) { + idGenerator.lock.Lock() + defer idGenerator.lock.Unlock() + delete(idGenerator.usedMap, id-idGenerator.minValue) +} + +func (idGenerator *IDGenerator) updateOffset() { + idGenerator.offset++ + idGenerator.offset = idGenerator.offset % idGenerator.valueRange +} diff --git a/idgenerator/idgenerator_test.go b/idgenerator/idgenerator_test.go new file mode 100644 index 0000000..d8285b1 --- /dev/null +++ b/idgenerator/idgenerator_test.go @@ -0,0 +1,154 @@ +package idgenerator + +import ( + "fmt" + "math/rand" + "sync" + "testing" + "time" +) + +func TestAllocate(t *testing.T) { + testCases := []struct { + minValue int64 + maxValue int64 + }{ + {1, 20}, + {11, 50}, + {1, 12345678}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("minValue: %d, maxValue: %d", testCase.minValue, testCase.maxValue), func(t *testing.T) { + idGenerator := NewGenerator(testCase.minValue, testCase.maxValue) + + for i := testCase.minValue; i <= testCase.maxValue; i++ { + id, err := idGenerator.Allocate() + if id != i { + t.Errorf("expected id: %d, output id: %d", i, id) + t.FailNow() + } else if err != nil { + t.Error(err) + t.FailNow() + } + } + + for i := testCase.minValue; i <= testCase.maxValue; i++ { + idGenerator.FreeID(i) + } + }) + } +} + +func TestConcurrency(t *testing.T) { + var usedMap sync.Map + + idGenerator := NewGenerator(1, 12345678) + + wg := sync.WaitGroup{} + for routineID := 1; routineID <= 10; routineID++ { + wg.Add(1) + go func(routineID int) { + for i := 0; i < 1000; i++ { + id, err := idGenerator.Allocate() + if err != nil { + t.Errorf("idGenerator.Allocate fail: %+v", err) + } + if value, ok := usedMap.Load(id); ok { + t.Errorf("ID %d has been allocated at routine[%d], concurrent test failed", id, value) + } else { + usedMap.Store(id, routineID) + } + } + usedMap.Range(func(key, value interface{}) bool { + id := key.(int64) + idGenerator.FreeID(id) + return true + }) + wg.Done() + }(routineID) + } + wg.Wait() +} + +func TestUnique(t *testing.T) { + testCases := []struct { + minValue int64 + maxValue int64 + }{ + {1, 10}, + {11, 1567}, + } + + rand.Seed(time.Now().Unix()) + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("minValue: %d, maxValue: %d", testCase.minValue, testCase.maxValue), func(t *testing.T) { + usedMap := make(map[int64]bool) + + valueRange := testCase.maxValue - testCase.minValue + 1 + testRange := int(valueRange * 3) + idGenerator := NewGenerator(testCase.minValue, testCase.maxValue) + + for i := 0; i < testRange; i++ { + id, err := idGenerator.Allocate() + if err != nil { + t.Error(err) + t.FailNow() + } + + if _, ok := usedMap[id]; ok { + t.Errorf("ID %d has been allocated, test failed", id) + t.FailNow() + } else { + usedMap[id] = true + } + + // do one free operation from the beginning of second round + if i >= int(valueRange-1) { + // retrieve all id to keys + var keys []int64 + for key := range usedMap { + keys = append(keys, key) + } + + keyIdx := rand.Intn(len(keys)) + idToFree := keys[keyIdx] + idGenerator.FreeID(idToFree) + delete(usedMap, idToFree) + } + } + }) + } +} + +func TestTriggerNoSpaceToAllocateError(t *testing.T) { + testCases := []struct { + minValue int64 + maxValue int64 + }{ + {1, 10}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("minValue: %d, maxValue: %d", testCase.minValue, testCase.maxValue), func(t *testing.T) { + valueRange := int(testCase.maxValue - testCase.minValue + 1) + idGenerator := NewGenerator(testCase.minValue, testCase.maxValue) + + for i := 0; i < valueRange; i++ { + _, err := idGenerator.Allocate() + if err != nil { + t.Error(err) + t.FailNow() + } + } + + // trigger "No available value range to allocate id" error + _, err := idGenerator.Allocate() + if err == nil { + t.Error("expect return error, but error is nil") + t.FailNow() + } + }) + } +} diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..37503dc --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,246 @@ +package logger + +import ( + "fmt" + "io/ioutil" + "net" + "net/http" + "net/http/httputil" + "os" + "path/filepath" + "runtime/debug" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +type FileHook struct { + file *os.File + flag int + chmod os.FileMode + formatter *logrus.TextFormatter +} + +// Fire(*Entry) implementation for logrus Hook interface +func (hook *FileHook) Fire(entry *logrus.Entry) error { + var line string + if plainformat, err := hook.formatter.Format(entry); err != nil { + return fmt.Errorf("FileHook formatter error: %+v\n", err) + } else { + line = string(plainformat) + } + if _, err := hook.file.WriteString(line); err != nil { + return fmt.Errorf("unable to write file on filehook(%s): %+v\n", line, err) + } + + return nil +} + +// Levels() implementation for logrus Hook interface +func (hook *FileHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + logrus.InfoLevel, + logrus.DebugLevel, + logrus.TraceLevel, + } +} + +func NewFileHook(file string, flag int, chmod os.FileMode) (*FileHook, error) { + plainFormatter := &logrus.TextFormatter{DisableColors: true} + logFile, err := os.OpenFile(file, flag, chmod) + if err != nil { + return nil, fmt.Errorf("unable to open file(%s): %+v\n", file, err) + } + + return &FileHook{logFile, flag, chmod, plainFormatter}, nil +} + +func CreateFree5gcLogFile(file string) (string, error) { + // Because free5gc log file will be used by multiple NFs, it is not recommended to rename. + return createLogFile(file, "", false) +} + +func CreateNfLogFile(file string, defaultName string) (string, error) { + return createLogFile(file, defaultName, true) +} + +/* + * createLogFile + * @param file, The full file path from arguments input by user. + * @param defaultName, Default log file name (if it is empty, it means no default log file will be created) + * @param rename, Modify the file name if the file exists + * @return error, fullPath + */ +func createLogFile(file string, defaultName string, rename bool) (string, error) { + var fullPath string + directory, fileName := filepath.Split(file) + + if directory == "" || fileName == "" { + directory = "./log/" + fileName = defaultName + } + + if fileName == "" { + return "", nil + } + + fullPath = filepath.Join(directory, fileName) + + if rename { + if err := renameOldLogFile(fullPath); err != nil { + return "", err + } + } + + if err := os.MkdirAll(directory, 0775); err != nil { + return "", fmt.Errorf("Make directory(%s) failed: %+v\n", directory, err) + } + + sudoUID, errUID := strconv.Atoi(os.Getenv("SUDO_UID")) + sudoGID, errGID := strconv.Atoi(os.Getenv("SUDO_GID")) + if errUID == nil && errGID == nil { + // if using sudo to run the program, errUID will be nil and sudoUID will get the uid who run sudo + // else errUID will not be nil and sudoUID will be nil + // If user using sudo to run the program and create log file, log will own by root, + // here we change own to user so user can view and reuse the file + if err := os.Chown(directory, sudoUID, sudoGID); err != nil { + return "", fmt.Errorf("Directory(%s) chown to [%d:%d] error: %+v\n", directory, sudoUID, sudoGID, err) + } + + // Create log file or if it already exist, check if user can access it + if f, err := os.OpenFile(fullPath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666); err != nil { + // user cannot access it. + return "", fmt.Errorf("Cannot Open [%s] error: %+v\n", fullPath, err) + } else { + // user can access it + if err := f.Close(); err != nil { + return "", fmt.Errorf("File [%s] cannot been closed\n", fullPath) + } + if err := os.Chown(fullPath, sudoUID, sudoGID); err != nil { + return "", fmt.Errorf("File [%s] chown to [%d:%d] error: %+v\n", fullPath, sudoUID, sudoGID, err) + } + } + } + + return fullPath, nil +} + +func renameOldLogFile(fullPath string) error { + _, err := os.Stat(fullPath) + + if os.IsNotExist(err) { + return nil + } + + counter := 0 + sep := "." + fileDir, fileName := filepath.Split(fullPath) + + if contents, err := ioutil.ReadDir(fileDir); err != nil { + return fmt.Errorf("Reads the directory(%s) error %+v\n", fileDir, err) + } else { + for _, content := range contents { + if !content.IsDir() { + if strings.Contains(content.Name(), (fileName + sep)) { + counter++ + } + } + } + } + + newFullPath := fmt.Sprintf("%s%s%s%d", fileDir, fileName, sep, (counter + 1)) + if err := os.Rename(fullPath, newFullPath); err != nil { + return fmt.Errorf("Unable to rename file(%s) %+v\n", newFullPath, err) + } + + return nil +} + +// NewGinWithLogrus - returns an Engine instance with the ginToLogrus and Recovery middleware already attached. +func NewGinWithLogrus(log *logrus.Entry) *gin.Engine { + engine := gin.New() + engine.Use(ginToLogrus(log), ginRecover(log)) + return engine +} + +// The Middleware will write the Gin logs to logrus. +func ginToLogrus(log *logrus.Entry) gin.HandlerFunc { + return func(c *gin.Context) { + path := c.Request.URL.Path + raw := c.Request.URL.RawQuery + + // Process request + c.Next() + + clientIP := c.ClientIP() + method := c.Request.Method + statusCode := c.Writer.Status() + errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String() + + if raw != "" { + path = path + "?" + raw + } + + log.Infof("| %3d | %15s | %-7s | %s | %s", + statusCode, clientIP, method, path, errorMessage) + } +} + +// The Middleware will recover the Gin panic to logrus. +func ginRecover(log *logrus.Entry) gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if p := recover(); p != nil { + // Check for a broken connection, as it is not really a condition that warrants a panic stack trace. + var brokenPipe bool + if ne, ok := p.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || + strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + if log != nil { + stack := string(debug.Stack()) + if httpRequest, err := httputil.DumpRequest(c.Request, false); err != nil { + log.Errorf("Dump http request error: %v\n", err) + } else { + headers := strings.Split(string(httpRequest), "\r\n") + for idx, header := range headers { + current := strings.Split(header, ":") + if current[0] == "Authorization" { + headers[idx] = current[0] + ": *" + } + } + + // changing Fatalf to Errorf to let program not be exited + if brokenPipe { + log.Errorf("%v\n%s", p, string(httpRequest)) + } else if gin.IsDebugging() { + log.Errorf("[Debugging] panic:\n%s\n%v\n%s", strings.Join(headers, "\r\n"), p, stack) + } else { + log.Errorf("panic: %v\n%s", p, stack) + } + } + } + + // If the connection is dead, we can't write a status to it. + if brokenPipe { + c.Error(p.(error)) // nolint: errcheck + c.Abort() + } else { + c.AbortWithStatus(http.StatusInternalServerError) + } + } + }() + c.Next() + } +} diff --git a/logger/logger_config.go b/logger/logger_config.go new file mode 100644 index 0000000..1b906c9 --- /dev/null +++ b/logger/logger_config.go @@ -0,0 +1,60 @@ +package logger + +import ( + "reflect" + + "github.com/asaskevich/govalidator" +) + +type Logger struct { + AMF *LogSetting `yaml:"AMF" valid:"optional"` + AUSF *LogSetting `yaml:"AUSF" valid:"optional"` + N3IWF *LogSetting `yaml:"N3IWF" valid:"optional"` + NRF *LogSetting `yaml:"NRF" valid:"optional"` + NSSF *LogSetting `yaml:"NSSF" valid:"optional"` + PCF *LogSetting `yaml:"PCF" valid:"optional"` + SMF *LogSetting `yaml:"SMF" valid:"optional"` + UDM *LogSetting `yaml:"UDM" valid:"optional"` + UDR *LogSetting `yaml:"UDR" valid:"optional"` + UPF *LogSetting `yaml:"UPF" valid:"optional"` + NEF *LogSetting `yaml:"NEF" valid:"optional"` + WEBUI *LogSetting `yaml:"WEBUI" valid:"optional"` + + Aper *LogSetting `yaml:"Aper" valid:"optional"` + FSM *LogSetting `yaml:"FSM" valid:"optional"` + NAS *LogSetting `yaml:"NAS" valid:"optional"` + NGAP *LogSetting `yaml:"NGAP" valid:"optional"` + PFCP *LogSetting `yaml:"PFCP" valid:"optional"` +} + +func (l *Logger) Validate() (bool, error) { + logger := reflect.ValueOf(l).Elem() + for i := 0; i < logger.NumField(); i++ { + if logSetting := logger.Field(i).Interface().(*LogSetting); logSetting != nil { + result, err := logSetting.validate() + return result, err + } + } + + result, err := govalidator.ValidateStruct(l) + return result, err +} + +type LogSetting struct { + DebugLevel string `yaml:"debugLevel" valid:"debugLevel"` + ReportCaller bool `yaml:"ReportCaller" valid:"type(bool)"` +} + +func (l *LogSetting) validate() (bool, error) { + govalidator.TagMap["debugLevel"] = govalidator.Validator(func(str string) bool { + if str == "panic" || str == "fatal" || str == "error" || str == "warn" || + str == "info" || str == "debug" || str == "trace" { + return true + } else { + return false + } + }) + + result, err := govalidator.ValidateStruct(l) + return result, err +} diff --git a/mapstruct/mapstruct.go b/mapstruct/mapstruct.go new file mode 100644 index 0000000..44be4b9 --- /dev/null +++ b/mapstruct/mapstruct.go @@ -0,0 +1,34 @@ +package mapstruct + +import ( + "reflect" + "time" + + "github.com/mitchellh/mapstructure" +) + +// Decode takes an input structure and uses reflection to translate it to +// the output structure with time.Time type. output must be a pointer to a map or struct. +func Decode(input interface{}, output interface{}) error { + stringToDateTimeHook := func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if t == reflect.TypeOf(time.Time{}) && f == reflect.TypeOf("") { + return time.Parse(time.RFC3339, data.(string)) + } + return data, nil + } + + config := mapstructure.DecoderConfig{ + DecodeHook: stringToDateTimeHook, + Result: output, + } + + decoder, err := mapstructure.NewDecoder(&config) + if err != nil { + return err + } + + return decoder.Decode(input) +} diff --git a/mapstruct/mapstruct_test.go b/mapstruct/mapstruct_test.go new file mode 100644 index 0000000..effa960 --- /dev/null +++ b/mapstruct/mapstruct_test.go @@ -0,0 +1,81 @@ +package mapstruct + +import ( + "reflect" + "testing" + "time" + + . "github.com/smartystreets/goconvey/convey" +) + +type TestProfile struct { + RecoveryTime *time.Time `mapstructure:"RecoveryTime"` +} + +func TestDecode(t *testing.T) { + date, err := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) + if err != nil { + t.Errorf("time parse fail: %+v", err) + } + + testCases := []struct { + description string + source map[string]interface{} + decodeError bool + resultStr string + expectedTarget TestProfile + }{ + { + description: "Param: RFC3339 time format string", + source: map[string]interface{}{ + "RecoveryTime": date.Format(time.RFC3339), + }, + decodeError: false, + resultStr: "Time field check should pass", + expectedTarget: TestProfile{ + RecoveryTime: &date, + }, + }, + { + description: "Param: no time field entry", + source: map[string]interface{}{}, + decodeError: false, + resultStr: "Time field should be nil", + expectedTarget: TestProfile{ + RecoveryTime: nil, + }, + }, + { + description: "Param: ANSIC time format string", + source: map[string]interface{}{ + "RecoveryTime": date.Format(time.ANSIC), + }, + decodeError: true, + resultStr: "Time field should be nil", + expectedTarget: TestProfile{ + RecoveryTime: nil, + }, + }, + } + + Convey("Decode Test", t, func() { + for _, tc := range testCases { + Convey(tc.description, func() { + var target TestProfile + err := Decode(tc.source, &target) + if tc.decodeError { + Convey("Decode should fail", func() { + So(err, ShouldNotBeNil) + }) + } else { + Convey("Decode should succeed", func() { + So(err, ShouldBeNil) + }) + } + Convey(tc.resultStr, func() { + So(true, ShouldEqual, reflect.DeepEqual(target.RecoveryTime, tc.expectedTarget.RecoveryTime)) + }) + }) + } + }) +} diff --git a/milenage/milenage.go b/milenage/milenage.go new file mode 100644 index 0000000..cbaa669 --- /dev/null +++ b/milenage/milenage.go @@ -0,0 +1,605 @@ +package milenage + +import ( + "crypto/aes" + "reflect" + "strconv" +) + +/** + * milenage_f1 - Milenage f1 and f1* algorithms + * @opc: OPc = 128-bit value derived from OP and K + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @sqn: SQN = 48-bit sequence number + * @amf: AMF = 16-bit authentication management field + * @mac_a: Buffer for MAC-A = 64-bit network authentication code, or %NULL + * @mac_s: Buffer for MAC-S = 64-bit resync authentication code, or %NULL + * Returns: 0 on success, -1 on failure + */ +func milenageF1(opc, k, _rand, sqn, amf, mac_a, mac_s []uint8) error { + tmp2, tmp3 := make([]uint8, 16), make([]uint8, 16) + // var tmp1, tmp2, tmp3 [16]uint8 + + rijndaelInput := make([]uint8, 16) + + /* tmp1 = TEMP = E_K(RAND XOR OP_C) */ + for i := 0; i < 16; i++ { + rijndaelInput[i] = _rand[i] ^ opc[i] + } + // RijndaelEncrypt( OP, op_c ); + + block, err := aes.NewCipher(k) + if err != nil { + return err + } + + tmp1 := make([]byte, block.BlockSize()) + block.Encrypt(tmp1, rijndaelInput) + + // fmt.Printf("tmp1: %x\n", tmp1) + + /* tmp2 = IN1 = SQN || AMF || SQN || AMF */ + copy(tmp2[0:], sqn[0:6]) + copy(tmp2[6:], amf[0:2]) + copy(tmp2[8:], tmp2[0:8]) + /* + os_memcpy(tmp2, sqn, 6); + os_memcpy(tmp2 + 6, amf, 2); + os_memcpy(tmp2 + 8, tmp2, 8); + */ + + /* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */ + + /* rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) */ + for i := 0; i < 16; i++ { + tmp3[(i+8)%16] = tmp2[i] ^ opc[i] + } + + // fmt.Printf("tmp3: %x\n", tmp3) + + /* XOR with TEMP = E_K(RAND XOR OP_C) */ + for i := 0; i < 16; i++ { + tmp3[i] ^= tmp1[i] + } + // fmt.Printf("tmp3 XOR with TEMP: %x\n", tmp3) + + /* XOR with c1 (= ..00, i.e., NOP) */ + /* f1 || f1* = E_K(tmp3) XOR OP_c */ + + tmp1 = make([]byte, block.BlockSize()) + block.Encrypt(tmp1, tmp3) + + // fmt.Printf("XOR with c1 (: %x\n", tmp1) + + for i := 0; i < 16; i++ { + tmp1[i] ^= opc[i] + } + // fmt.Printf("tmp1[i] ^= opc[i] %x\n", tmp1) + if mac_a != nil { + copy(mac_a[0:], tmp1[0:8]) + } + + if mac_s != nil { + copy(mac_s[0:], tmp1[8:16]) + } + + return nil +} + +/** + * milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms + * @opc: OPc = 128-bit value derived from OP and K + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @res: Buffer for RES = 64-bit signed response (f2), or %NULL + * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL + * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL + * @ak: Buffer for AK = 48-bit anonymity key (f5), or %NULL + * @akstar: Buffer for AK = 48-bit anonymity key (f5*), or %NULL + * Returns: 0 on success, -1 on failure + */ +func milenageF2345(opc, k, _rand, res, ck, ik, ak, akstar []uint8) error { + tmp1 := make([]uint8, 16) + + /* tmp2 = TEMP = E_K(RAND XOR OP_C) */ + for i := 0; i < 16; i++ { + tmp1[i] = _rand[i] ^ opc[i] + } + + block, err := aes.NewCipher(k) + if err != nil { + return err + } + + tmp2 := make([]byte, block.BlockSize()) + block.Encrypt(tmp2, tmp1) + + /* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */ + /* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */ + /* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */ + /* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */ + + /* f2 and f5 */ + /* rotate by r2 (= 0, i.e., NOP) */ + for i := 0; i < 16; i++ { + tmp1[i] = tmp2[i] ^ opc[i] + } + tmp1[15] ^= 1 // XOR c2 (= ..01) + /* + for (i = 0; i < 16; i++) + tmp1[i] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 1; // XOR c2 (= ..01) + */ + + /* f5 || f2 = E_K(tmp1) XOR OP_c */ + tmp3 := make([]byte, block.BlockSize()) + block.Encrypt(tmp3, tmp1) + + for i := 0; i < 16; i++ { + tmp3[i] ^= opc[i] + } + + if res != nil { + copy(res[0:], tmp3[8:16]) // f2 + } + + if ak != nil { + copy(ak[0:], tmp3[0:6]) // f5 + } + /* + if (aes_128_encrypt_block(k, tmp1, tmp3)) + return -1; + for (i = 0; i < 16; i++) + tmp3[i] ^= opc[i]; + if (res) + os_memcpy(res, tmp3 + 8, 8); // f2 + if (ak) + os_memcpy(ak, tmp3, 6); // f5 + */ + + /* f3 */ + if ck != nil { + // rotate by r3 = 0x20 = 4 bytes + for i := 0; i < 16; i++ { + tmp1[(i+12)%16] = tmp2[i] ^ opc[i] + } + tmp1[15] ^= 2 // XOR c3 (= ..02) + + block.Encrypt(ck, tmp1) + + for i := 0; i < 16; i++ { + ck[i] ^= opc[i] + } + } + /* + if (ck) { + // rotate by r3 = 0x20 = 4 bytes + for (i = 0; i < 16; i++) + tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 2; // XOR c3 (= ..02) + if (aes_128_encrypt_block(k, tmp1, ck)) + return -1; + for (i = 0; i < 16; i++) + ck[i] ^= opc[i]; + } + */ + + /* f4 */ + if ik != nil { + // rotate by r4 = 0x40 = 8 bytes + for i := 0; i < 16; i++ { + tmp1[(i+8)%16] = tmp2[i] ^ opc[i] + } + tmp1[15] ^= 4 // XOR c4 (= ..04) + + block.Encrypt(ik, tmp1) + + for i := 0; i < 16; i++ { + ik[i] ^= opc[i] + } + } + /* + if (ik) { + //rotate by r4 = 0x40 = 8 bytes + for (i = 0; i < 16; i++) + tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 4; // XOR c4 (= ..04) + if (aes_128_encrypt_block(k, tmp1, ik)) + return -1; + for (i = 0; i < 16; i++) + ik[i] ^= opc[i]; + } + */ + + /* f5* */ + if akstar != nil { + // rotate by r5 = 0x60 = 12 bytes + for i := 0; i < 16; i++ { + tmp1[(i+4)%16] = tmp2[i] ^ opc[i] + } + tmp1[15] ^= 8 // XOR c5 (= ..08) + + block.Encrypt(tmp1, tmp1) + + for i := 0; i < 6; i++ { + akstar[i] = tmp1[i] ^ opc[i] + } + } + /* + if (akstar) { + // rotate by r5 = 0x60 = 12 bytes + for (i = 0; i < 16; i++) + tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 8; // XOR c5 (= ..08) + if (aes_128_encrypt_block(k, tmp1, tmp1)) + return -1; + for (i = 0; i < 6; i++) + akstar[i] = tmp1[i] ^ opc[i]; + } + */ + + return nil +} + +func MilenageGenerate(opc, amf, k, sqn, _rand, autn, ik, ck, ak, res []uint8, res_len *uint) { + // var i int + mac_a := make([]uint8, 8) + + // fmt.Println(i) + // fmt.Println(mac_a) + + if (*res_len) < 8 { + *res_len = 0 + return + } + + if milenageF1(opc, k, _rand, sqn, amf, mac_a, nil) != nil || + milenageF2345(opc, k, _rand, res, ck, ik, ak, nil) != nil { + *res_len = 0 + return + } + + *res_len = 8 + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for i := 0; i < 6; i++ { + autn[i] = sqn[i] ^ ak[i] + copy(autn[6:], amf[0:2]) + copy(autn[8:], mac_a[0:8]) + } + /* + for (i = 0; i < 6; i++) + autn[i] = sqn[i] ^ ak[i]; + os_memcpy(autn + 6, amf, 2); + os_memcpy(autn + 8, mac_a, 8); + */ +} + +/** + * milenage_auts - Milenage AUTS validation + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @auts: AUTS = 112-bit authentication token from client + * @sqn: Buffer for SQN = 48-bit sequence number + * Returns: 0 = success (sqn filled), -1 on failure + */ +//int milenage_auts(const c_uint8_t *opc, const c_uint8_t *k, const c_uint8_t *_rand, +// const c_uint8_t *auts, c_uint8_t *sqn) +func Milenage_auts(opc, k, _rand, auts, sqn []uint8) int { + amf := []uint8{0x00, 0x00} // TS 33.102 v7.0.0, 6.3.3 + ak := make([]uint8, 6) + mac_s := make([]uint8, 8) + /* + c_uint8_t amf[2] = { 0x00, 0x00 }; // TS 33.102 v7.0.0, 6.3.3 + c_uint8_t ak[6], mac_s[8]; + int i; + */ + + if milenageF2345(opc, k, _rand, nil, nil, nil, nil, ak) != nil { + return -1 + } + + for i := 0; i < 6; i++ { + sqn[i] = auts[i] ^ ak[i] + } + + if milenageF1(opc, k, _rand, sqn, amf, nil, mac_s) != nil || !reflect.DeepEqual(mac_s, auts[6:14]) { + return -1 + } + + /* + if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak)) + return -1; + for (i = 0; i < 6; i++) + sqn[i] = auts[i] ^ ak[i]; + if (milenage_f1(opc, k, _rand, sqn, amf, NULL, mac_s) || + os_memcmp_const(mac_s, auts + 6, 8) != 0) + return -1; + */ + return 0 +} + +/** + * gsm_milenage - Generate GSM-Milenage (3GPP TS 55.205) authentication triplet + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @sres: Buffer for SRES = 32-bit SRES + * @kc: Buffer for Kc = 64-bit Kc + * Returns: 0 on success, -1 on failure + */ +func Gsm_milenage(opc, k, _rand, sres, kc []uint8) int { + res, ck, ik := make([]uint8, 8), make([]uint8, 16), make([]uint8, 16) + + if milenageF2345(opc, k, _rand, res, ck, ik, nil, nil) != nil { + return -1 + } + /* + if (milenage_f2345(opc, k, _rand, res, ck, ik, NULL, NULL)) + return -1; + */ + + for i := 0; i < 8; i++ { + kc[i] = ck[i] ^ ck[i+8] ^ ik[i] ^ ik[i+8] + } + /* + for (i = 0; i < 8; i++) + kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8]; + */ + + // if GSM_MILENAGE_ALT_SRES + // copy(sres, res[0:4]) + + // if not GSM_MILENAGE_ALT_SRES + for i := 0; i < 4; i++ { + sres[i] = res[i] ^ res[i+4] + } + /* + #ifdef GSM_MILENAGE_ALT_SRES + os_memcpy(sres, res, 4); + #else // GSM_MILENAGE_ALT_SRES + for (i = 0; i < 4; i++) + sres[i] = res[i] ^ res[i + 4]; + #endif // GSM_MILENAGE_ALT_SRES + */ + return 0 +} + +/** + * milenage_generate - Generate AKA AUTN,IK,CK,RES + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @sqn: SQN = 48-bit sequence number + * @_rand: RAND = 128-bit random challenge + * @autn: AUTN = 128-bit authentication token + * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL + * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL + * @res: Buffer for RES = 64-bit signed response (f2), or %NULL + * @res_len: Variable that will be set to RES length + * @auts: 112-bit buffer for AUTS + * Returns: 0 on success, -1 on failure, or -2 on synchronization failure + */ +func Milenage_check(opc, k, sqn, _rand, autn, ik, ck, res []uint8, res_len *uint, auts []uint8) int { + mac_a, ak, rx_sqn := make([]uint8, 8), make([]uint8, 6), make([]uint8, 6) + var amf []uint8 + + // fmt.Println(mac_a, amf) + + /* TODO + d_trace(1, "Milenage: AUTN\n"); d_trace_hex(1, autn, 16); + d_trace(1, "Milenage: RAND\n"); d_trace_hex(1, _rand, 16); + */ + + if milenageF2345(opc, k, _rand, res, ck, ik, ak, nil) != nil { + return -1 + } + /* + if (milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL)) + return -1; + */ + + *res_len = 8 + /* TODO + d_trace(1, "Milenage: RES\n"); d_trace_hex(1, res, *res_len); + d_trace(1, "Milenage: CK\n"); d_trace_hex(1, ck, 16); + d_trace(1, "Milenage: IK\n"); d_trace_hex(1, ik, 16); + d_trace(1, "Milenage: AK\n"); d_trace_hex(1, ak, 6); + */ + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for i := 0; i < 6; i++ { + rx_sqn[i] = autn[i] ^ ak[i] + } + /* + for (i = 0; i < 6; i++) + rx_sqn[i] = autn[i] ^ ak[i]; + */ + + // TODO d_trace(1, "Milenage: SQN\n"); d_trace_hex(1, rx_sqn, 6); + + if os_memcmp(rx_sqn, sqn, 6) <= 0 { + auts_amf := []uint8{0x00, 0x00} // TS 33.102 v7.0.0, 6.3.3 + + if milenageF2345(opc, k, _rand, nil, nil, nil, nil, ak) != nil { + return -1 + } + + // TODO d_trace(1, "Milenage: AK*\n"); d_trace_hex(1, ak, 6); + + for i := 0; i < 6; i++ { + auts[i] = sqn[i] ^ ak[i] + } + + if milenageF1(opc, k, _rand, sqn, auts_amf, nil, auts[6:]) != nil { + return -1 + } + + // TODO d_trace(1, "Milenage: AUTS*\n"); d_trace_hex(1, auts, 14); + + return -2 + } + /* + if (os_memcmp(rx_sqn, sqn, 6) <= 0) { + c_uint8_t auts_amf[2] = { 0x00, 0x00 }; // TS 33.102 v7.0.0, 6.3.3 + if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak)) + return -1; + d_trace(1, "Milenage: AK*\n"); d_trace_hex(1, ak, 6); + + + + for (i = 0; i < 6; i++) + auts[i] = sqn[i] ^ ak[i]; + if (milenage_f1(opc, k, _rand, sqn, auts_amf, NULL, auts + 6)) + return -1; + d_trace(1, "Milenage: AUTS*\n"); d_trace_hex(1, auts, 14); + return -2; + } + */ + + amf = autn[6:] + // TODO d_trace(1, "Milenage: AMF\n"); d_trace_hex(1, amf, 2); + + if milenageF1(opc, k, _rand, rx_sqn, amf, mac_a, nil) != nil { + return -1 + } + // TODO d_trace(1, "Milenage: MAC_A\n"); d_trace_hex(1, mac_a, 8); + + if os_memcmp(mac_a, autn[8:], 8) != 0 { + // TODO d_trace(1, "Milenage: MAC mismatch\n"); + // TODO d_trace(1, "Milenage: Received MAC_A\n"); d_trace_hex(1, autn + 8, 8); + + return -1 + } + /* + amf = autn + 6; + d_trace(1, "Milenage: AMF\n"); d_trace_hex(1, amf, 2); + if (milenage_f1(opc, k, _rand, rx_sqn, amf, mac_a, NULL)) + return -1; + + d_trace(1, "Milenage: MAC_A\n"); d_trace_hex(1, mac_a, 8); + + if (os_memcmp_const(mac_a, autn + 8, 8) != 0) { + d_trace(1, "Milenage: MAC mismatch\n"); + d_trace(1, "Milenage: Received MAC_A\n"); d_trace_hex(1, autn + 8, 8); + return -1; + } + */ + + return 0 +} + +// implementation of os_memcmp +func os_memcmp(a, b []uint8, num int) int { + for i := 0; i < num; i++ { + if a[i] < b[i] { + return -i + } + + if a[i] > b[i] { + return i + } + } + + return 0 +} + +func F1(opc, k, _rand, sqn, amf, mac_a, mac_s []uint8) error { + return milenageF1(opc, k, _rand, sqn, amf, mac_a, mac_s) +} + +func F2345(opc, k, _rand, res, ck, ik, ak, akstar []uint8) error { + return milenageF2345(opc, k, _rand, res, ck, ik, ak, akstar) +} + +func GenerateOPC(k, op []uint8) ([]uint8, error) { + block, err := aes.NewCipher(k) + if err != nil { + return nil, err + } + + opc := make([]byte, block.BlockSize()) + + block.Encrypt(opc, op) + + for i := 0; i < 16; i++ { + opc[i] ^= op[i] + } + + return opc, nil +} + +func InsertData(op, k, _rand, sqn, amf []uint8, OP, K, RAND, SQN, AMF string) { + var res uint64 + var err error + + // load op + // fmt.Print("OP: ") + for i := 0; i < 16; i++ { + res, err = strconv.ParseUint(OP[i*2:i*2+2], 16, 8) + + if err == nil { + op[i] = uint8(res) + // fmt.Printf("%02x ", op[i]) + } + } + + // fmt.Println() + // fmt.Printf("OP: %x\n", op) + + // load k + // fmt.Print("K: ") + for i := 0; i < 16; i++ { + res, err = strconv.ParseUint(K[i*2:i*2+2], 16, 8) + + if err == nil { + k[i] = uint8(res) + // fmt.Printf("%02x ", k[i]) + } + } + + // fmt.Println() + // fmt.Printf("K: %x\n", k) + + // load _rand + // fmt.Print("RAND: ") + for i := 0; i < 16; i++ { + res, err = strconv.ParseUint(RAND[i*2:i*2+2], 16, 8) + + if err == nil { + _rand[i] = uint8(res) + // fmt.Printf("%02x ", _rand[i]) + } + } + + // fmt.Println() + // load sqn + // fmt.Print("SQN: ") + for i := 0; i < 6; i++ { + res, err = strconv.ParseUint(SQN[i*2:i*2+2], 16, 8) + + if err == nil { + sqn[i] = uint8(res) + // fmt.Printf("%02x ", sqn[i]) + } + } + + // fmt.Println() + // fmt.Printf("SQN: %x\n", sqn) + + // load amf + // fmt.Print("AMF: ") + for i := 0; i < 2; i++ { + res, err = strconv.ParseUint(AMF[i*2:i*2+2], 16, 8) + + if err == nil { + amf[i] = uint8(res) + // fmt.Printf("%02x ", amf[i]) + } + } + + // fmt.Println() + // fmt.Printf("AMF: %x\n", amf) + // fmt.Println() +} diff --git a/milenage/milenage_test.go b/milenage/milenage_test.go new file mode 100644 index 0000000..da7bf34 --- /dev/null +++ b/milenage/milenage_test.go @@ -0,0 +1,520 @@ +package milenage + +import ( + "encoding/hex" + "fmt" + "reflect" + "strings" + "testing" +) + +type f1Test struct { + K string + RAND string + SQN string + AMF string + OP string + ExpectedOPc string + f1 string + f1Start string +} + +type f2f5f3Test struct { + K string + RAND string + OP string + ExpectedOPc string + ExpectedRES string + ExpectedAK string + ExpectedCK string +} + +type f4f5StarTest struct { + K string + RAND string + OP string + ExpectedOPc string + ExpectedIK string + ExpectedAKStar string +} + +func TestF1Test35207(t *testing.T) { + Testf1TestTable := []f1Test{ + { + K: "465b5ce8b199b49faa5f0a2ee238a6bc", + RAND: "23553cbe9637a89d218ae64dae47bf35", + SQN: "ff9bb4d0b607", + AMF: "b9b9", + OP: "cdc202d5123e20f62b6d676ac72cb318", + ExpectedOPc: "cd63cb71954a9f4e48a5994e37a02baf", + f1: "4a9ffac354dfafb3", + f1Start: "01cfaf9ec4e871e9", + }, + { + K: "0396eb317b6d1c36f19c1c84cd6ffd16", + RAND: "c00d603103dcee52c4478119494202e8", + SQN: "fd8eef40df7d", + AMF: "af17", + OP: "ff53bade17df5d4e793073ce9d7579fa", + ExpectedOPc: "53c15671c60a4b731c55b4a441c0bde2", + f1: "5df5b31807e258b0", + f1Start: "a8c016e51ef4a343", + }, + { + K: "fec86ba6eb707ed08905757b1bb44b8f", + RAND: "9f7c8d021accf4db213ccff0c7f71a6a", + SQN: "9d0277595ffc", + AMF: "725c", + OP: "dbc59adcb6f9a0ef735477b7fadf8374", + ExpectedOPc: "1006020f0a478bf6b699f15c062e42b3", + f1: "9cabc3e99baf7281", + f1Start: "95814ba2b3044324", + }, + { + K: "9e5944aea94b81165c82fbf9f32db751", + RAND: "ce83dbc54ac0274a157c17f80d017bd6", + SQN: "0b604a81eca8", + AMF: "9e09", + OP: "223014c5806694c007ca1eeef57f004f", + ExpectedOPc: "a64a507ae1a2a98bb88eb4210135dc87", + f1: "74a58220cba84c49", + f1Start: "ac2cc74a96871837", + }, + { + K: "4ab1deb05ca6ceb051fc98e77d026a84", + RAND: "74b0cd6031a1c8339b2b6ce2b8c4a186", + SQN: "e880a1b580b6", + AMF: "9f07", + OP: "2d16c5cd1fdf6b22383584e3bef2a8d8", + ExpectedOPc: "dcf07cbd51855290b92a07a9891e523e", + f1: "49e785dd12626ef2", + f1Start: "9e85790336bb3fa2", + }, + { + K: "6c38a116ac280c454f59332ee35c8c4f", + RAND: "ee6466bc96202c5a557abbeff8babf63", + SQN: "414b98222181", + AMF: "4464", + OP: "1ba00a1a7c6700ac8c3ff3e96ad08725", + ExpectedOPc: "3803ef5363b947c6aaa225e58fae3934", + f1: "078adfb488241a57", + f1Start: "80246b8d0186bcf1", + }, + } + + for i, testTable := range Testf1TestTable { + K, err := hex.DecodeString(strings.Repeat(testTable.K, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + RAND, err := hex.DecodeString(strings.Repeat(testTable.RAND, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + SQN, err := hex.DecodeString(strings.Repeat(testTable.SQN, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + AMF, err := hex.DecodeString(strings.Repeat(testTable.AMF, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + OP, err := hex.DecodeString(strings.Repeat(testTable.OP, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + ExpectedOPc, err := hex.DecodeString(strings.Repeat(testTable.ExpectedOPc, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + f1, err := hex.DecodeString(strings.Repeat(testTable.f1, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + f1Start, err := hex.DecodeString(strings.Repeat(testTable.f1Start, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + OPC, err := GenerateOPC(K, OP) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + fmt.Printf("K=%x\nSQN=%x\nOP=%x\nOPC=%x\n", K, SQN, OP, OPC) + if !reflect.DeepEqual(OPC, ExpectedOPc) { + t.Errorf("Testf1Test35207[%d] \t OPC[0x%x] \t ExpectedOPc[0x%x]\n", i, OPC, ExpectedOPc) + } + MAC_A, MAC_S := make([]byte, 8), make([]byte, 8) + err = F1(OPC, K, RAND, SQN, AMF, MAC_A, MAC_S) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + if !reflect.DeepEqual(MAC_A, f1) { + t.Errorf("Testf1Test35207[%d] \t MAC_A[0x%x] \t f1[0x%x]\n", i, MAC_A, f1) + } + if !reflect.DeepEqual(MAC_S, f1Start) { + t.Errorf("Testf1Test35207[%d] \t MAC_S[0x%x] \t f1Start[0x%x]\n", i, MAC_S, f1Start) + } + } +} + +func TestF2F5F3Test35207(t *testing.T) { + Testf2f5f3TestTable := []f2f5f3Test{ + { + K: "465b5ce8b199b49faa5f0a2ee238a6bc", + RAND: "23553cbe9637a89d218ae64dae47bf35", + OP: "cdc202d5123e20f62b6d676ac72cb318", + ExpectedOPc: "cd63cb71954a9f4e48a5994e37a02baf", + ExpectedRES: "a54211d5e3ba50bf", + ExpectedAK: "aa689c648370", + ExpectedCK: "b40ba9a3c58b2a05bbf0d987b21bf8cb", + }, + { + K: "0396eb317b6d1c36f19c1c84cd6ffd16", + RAND: "c00d603103dcee52c4478119494202e8", + OP: "ff53bade17df5d4e793073ce9d7579fa", + ExpectedOPc: "53c15671c60a4b731c55b4a441c0bde2", + ExpectedRES: "d3a628ed988620f0", + ExpectedAK: "c47783995f72", + ExpectedCK: "58c433ff7a7082acd424220f2b67c556", + }, + { + K: "fec86ba6eb707ed08905757b1bb44b8f", + RAND: "9f7c8d021accf4db213ccff0c7f71a6a", + OP: "dbc59adcb6f9a0ef735477b7fadf8374", + ExpectedOPc: "1006020f0a478bf6b699f15c062e42b3", + ExpectedRES: "8011c48c0c214ed2", + ExpectedAK: "33484dc2136b", + ExpectedCK: "5dbdbb2954e8f3cde665b046179a5098", + }, + { + K: "9e5944aea94b81165c82fbf9f32db751", + RAND: "ce83dbc54ac0274a157c17f80d017bd6", + OP: "223014c5806694c007ca1eeef57f004f", + ExpectedOPc: "a64a507ae1a2a98bb88eb4210135dc87", + ExpectedRES: "f365cd683cd92e96", + ExpectedAK: "f0b9c08ad02e", + ExpectedCK: "e203edb3971574f5a94b0d61b816345d", + }, + { + K: "4ab1deb05ca6ceb051fc98e77d026a84", + RAND: "74b0cd6031a1c8339b2b6ce2b8c4a186", + OP: "2d16c5cd1fdf6b22383584e3bef2a8d8", + ExpectedOPc: "dcf07cbd51855290b92a07a9891e523e", + ExpectedRES: "5860fc1bce351e7e", + ExpectedAK: "31e11a609118", + ExpectedCK: "7657766b373d1c2138f307e3de9242f9", + }, + } + + for i, testTable := range Testf2f5f3TestTable { + K, err := hex.DecodeString(strings.Repeat(testTable.K, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + OP, err := hex.DecodeString(strings.Repeat(testTable.OP, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + ExpectedOPc, err := hex.DecodeString(strings.Repeat(testTable.ExpectedOPc, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + ExpectedRES, err := hex.DecodeString(strings.Repeat(testTable.ExpectedRES, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + ExpectedAK, err := hex.DecodeString(strings.Repeat(testTable.ExpectedAK, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + ExpectedCK, err := hex.DecodeString(strings.Repeat(testTable.ExpectedCK, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + RAND, err := hex.DecodeString(strings.Repeat(testTable.RAND, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + OPC, err := GenerateOPC(K, OP) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + if !reflect.DeepEqual(OPC, ExpectedOPc) { + t.Errorf("TestF2F5F3Test35207[%d] \t OPC[0x%x] \t ExpectedOPc[0x%x]\n", i, OPC, ExpectedOPc) + } + CK, IK := make([]byte, 16), make([]byte, 16) + RES := make([]byte, 8) + AK, AKstar := make([]byte, 6), make([]byte, 6) + err = F2345(OPC, K, RAND, RES, CK, IK, AK, AKstar) + if err != nil { + t.Errorf("err: %+v\n", err) + } + if !reflect.DeepEqual(RES, ExpectedRES) { + t.Errorf("TestF2F5F3Test35207[%d] \t RES[0x%x] \t ExpectedRES[0x%x]\n", i, RES, ExpectedRES) + } + if !reflect.DeepEqual(AK, ExpectedAK) { + t.Errorf("TestF2F5F3Test35207[%d] \t AK[0x%x] \t ExpectedAK[0x%x]\n", i, AK, ExpectedAK) + } + if !reflect.DeepEqual(CK, ExpectedCK) { + t.Errorf("TestF2F5F3Test35207[%d] \t CK[0x%x] \t ExpectedCK[0x%x]\n", i, CK, ExpectedCK) + } + } +} + +func TestF4F5StarTest35207(t *testing.T) { + Testf4f5StarTestTable := []f4f5StarTest{ + { + K: "465b5ce8b199b49faa5f0a2ee238a6bc", + RAND: "23553cbe9637a89d218ae64dae47bf35", + OP: "cdc202d5123e20f62b6d676ac72cb318", + ExpectedOPc: "cd63cb71954a9f4e48a5994e37a02baf", + ExpectedIK: "f769bcd751044604127672711c6d3441", + ExpectedAKStar: "451e8beca43b", + }, + { + K: "0396eb317b6d1c36f19c1c84cd6ffd16", + RAND: "c00d603103dcee52c4478119494202e8", + OP: "ff53bade17df5d4e793073ce9d7579fa", + ExpectedOPc: "53c15671c60a4b731c55b4a441c0bde2", + ExpectedIK: "21a8c1f929702adb3e738488b9f5c5da", + ExpectedAKStar: "30f1197061c1", + }, + { + K: "fec86ba6eb707ed08905757b1bb44b8f", + RAND: "9f7c8d021accf4db213ccff0c7f71a6a", + OP: "dbc59adcb6f9a0ef735477b7fadf8374", + ExpectedOPc: "1006020f0a478bf6b699f15c062e42b3", + ExpectedIK: "59a92d3b476a0443487055cf88b2307b", + ExpectedAKStar: "deacdd848cc6", + }, + { + K: "9e5944aea94b81165c82fbf9f32db751", + RAND: "ce83dbc54ac0274a157c17f80d017bd6", + OP: "223014c5806694c007ca1eeef57f004f", + ExpectedOPc: "a64a507ae1a2a98bb88eb4210135dc87", + ExpectedIK: "0c4524adeac041c4dd830d20854fc46b", + ExpectedAKStar: "6085a86c6f63", + }, + { + K: "4ab1deb05ca6ceb051fc98e77d026a84", + RAND: "74b0cd6031a1c8339b2b6ce2b8c4a186", + OP: "2d16c5cd1fdf6b22383584e3bef2a8d8", + ExpectedOPc: "dcf07cbd51855290b92a07a9891e523e", + ExpectedIK: "1c42e960d89b8fa99f2744e0708ccb53", + ExpectedAKStar: "fe2555e54aa9", + }, + { + K: "6c38a116ac280c454f59332ee35c8c4f", + RAND: "ee6466bc96202c5a557abbeff8babf63", + OP: "1ba00a1a7c6700ac8c3ff3e96ad08725", + ExpectedOPc: "3803ef5363b947c6aaa225e58fae3934", + ExpectedIK: "a7466cc1e6b2a1337d49d3b66e95d7b4", + ExpectedAKStar: "1f53cd2b1113", + }, + } + + for i, testTable := range Testf4f5StarTestTable { + K, err := hex.DecodeString(strings.Repeat(testTable.K, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + OP, err := hex.DecodeString(strings.Repeat(testTable.OP, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + ExpectedOPc, err := hex.DecodeString(strings.Repeat(testTable.ExpectedOPc, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + ExpectedAKStar, err := hex.DecodeString(strings.Repeat(testTable.ExpectedAKStar, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + ExpectedIK, err := hex.DecodeString(strings.Repeat(testTable.ExpectedIK, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + RAND, err := hex.DecodeString(strings.Repeat(testTable.RAND, 1)) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + OPC, err := GenerateOPC(K, OP) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + if !reflect.DeepEqual(OPC, ExpectedOPc) { + t.Errorf("TestF4F5StarTest35207[%d] \t OPC[0x%x] \t ExpectedOPc[0x%x]\n", i, OPC, ExpectedOPc) + } + CK, IK := make([]byte, 16), make([]byte, 16) + RES := make([]byte, 8) + AK, AKstar := make([]byte, 6), make([]byte, 6) + err = F2345(OPC, K, RAND, RES, CK, IK, AK, AKstar) + if err != nil { + t.Errorf("err: %+v\n", err) + } + if !reflect.DeepEqual(AKstar, ExpectedAKStar) { + t.Errorf("TestF4F5StarTest35207[%d] \t AKstar[0x%x] \t ExpectedAKStar[0x%x]\n", i, AKstar, ExpectedAKStar) + } + if !reflect.DeepEqual(IK, ExpectedIK) { + t.Errorf("TestF4F5StarTest35207[%d] \t IK[0x%x] \t ExpectedIK[0x%x]\n", i, IK, ExpectedIK) + } + } +} + +func TestGenerateOPC(t *testing.T) { + // K_str := "3016ebeae2c45bd0060923dbbb402be6" + K_str := "000102030405060708090a0b0c0d0e0f" // CHT + K, err := hex.DecodeString(K_str) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + // OP_str := "00000000000000000000000000000000" + OP_str := "00112233445566778899aabbccddeeff" // CHT + OP, err := hex.DecodeString(OP_str) + if err != nil { + t.Errorf("err: %+v\n", err) + } + fmt.Println("K:", K) + + fmt.Println("OP:", OP) + + OPCbyGo, err := GenerateOPC(K, OP) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + fmt.Println("OPCbyGo:", OPCbyGo) +} + +func TestRAND(t *testing.T) { + /* + K, RAND, CK, IK: 128 bits (16 bytes) (hex len = 32) + SQN, AK: 48 bits (6 bytes) (hex len = 12) TS33.102 - 6.3.2 + AMF: 16 bits (2 bytes) (hex len = 4) TS33.102 - Annex H + */ + + K_str := "5122250214c33e723a5dd523fc145fc0" + OP_str := "c9e8763286b5b9ffbdf56e1297d0887b" + SQN_str := "16f3b3f70fc2" + + K, err := hex.DecodeString(K_str) + if err != nil { + t.Errorf("err: %+v\n", err) + } + OP, err := hex.DecodeString(OP_str) + if err != nil { + t.Errorf("err: %+v\n", err) + } + SQN, err := hex.DecodeString(SQN_str) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + OPC, err := GenerateOPC(K, OP) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + fmt.Printf("K=%x\nSQN=%x\nOP=%x\nOPC=%x\n", K, SQN, OP, OPC) + RAND, err := hex.DecodeString("81e92b6c0ee0e12ebceba8d92a99dfa5") + if err != nil { + t.Errorf("err: %+v\n", err) + } + + AMF, err := hex.DecodeString("c3ab") + if err != nil { + t.Errorf("err: %+v\n", err) + } + fmt.Printf("RAND=%x\nAMF=%x\n", RAND, AMF) + + // for test + // RAND, _ = hex.DecodeString(TestGenAuthData.MilenageTestSet19.RAND) + // AMF, _ = hex.DecodeString(TestGenAuthData.MilenageTestSet19.AMF) + fmt.Printf("For test: RAND=%x, AMF=%x\n", RAND, AMF) + + // Run milenage + MAC_A, MAC_S := make([]byte, 8), make([]byte, 8) + CK, IK := make([]byte, 16), make([]byte, 16) + RES := make([]byte, 8) + AK, AKstar := make([]byte, 6), make([]byte, 6) + + // Generate MAC_A, MAC_S + + err = F1(OPC, K, RAND, SQN, AMF, MAC_A, MAC_S) + if err != nil { + t.Errorf("err: %+v\n", err) + } + + // Generate RES, CK, IK, AK, AKstar + // RES == XRES (expected RES) for server + err = F2345(OPC, K, RAND, RES, CK, IK, AK, AKstar) + if err != nil { + t.Errorf("err: %+v\n", err) + } + fmt.Printf("milenage RES = %s\n", hex.EncodeToString(RES)) + // + fmt.Printf("RES=%x\n", RES) + expRES, err := hex.DecodeString("28d7b0f2a2ec3de5") + if err != nil { + t.Errorf("err: %+v\n", err) + } + if !reflect.DeepEqual(RES, RES) { + t.Errorf("RES[0x%x] \t expected[0x%x]\n", RES, expRES) + } + // Generate AUTN + fmt.Printf("CK=%x\n", CK) + expCK, err := hex.DecodeString("5349fbe098649f948f5d2e973a81c00f") + if err != nil { + t.Errorf("err: %+v\n", err) + } + if !reflect.DeepEqual(CK, expCK) { + t.Errorf("CK[0x%x] \t expected[0x%x]\n", CK, expCK) + } + fmt.Printf("IK=%x\n", IK) + expIK, err := hex.DecodeString("9744871ad32bf9bbd1dd5ce54e3e2e5a") + if err != nil { + t.Errorf("err: %+v\n", err) + } + if !reflect.DeepEqual(IK, expIK) { + t.Errorf("IK[0x%x] \t expected[0x%x]\n", IK, expIK) + } + // fmt.Printf("SQN=%x\nAK =%x\n", SQN, AK) + // fmt.Printf("AK=%x\n", AK) + expAK, err := hex.DecodeString("ada15aeb7bb8") + if err != nil { + t.Errorf("err: %+v\n", err) + } + if !reflect.DeepEqual(AK, expAK) { + t.Errorf("AK[0x%x] \t expected[0x%x]\n", AK, expAK) + } + // fmt.Printf("AMF=%x, MAC_A=%x\n", AMF, MAC_A) + SQNxorAK := make([]byte, 6) + for i := 0; i < len(SQN); i++ { + SQNxorAK[i] = SQN[i] ^ AK[i] + } + + fmt.Printf("SQN xor AK = %x\n", SQNxorAK) + AUTN := append(append(SQNxorAK, AMF...), MAC_A...) + + // fmt.Printf("MAC_A = %x\n", MAC_A) + // fmt.Printf("MAC_S = %x\n", MAC_S) + + // fmt.Printf("AUTN = %x\n", AUTN) + + expAUTN, err := hex.DecodeString("bb52e91c747ac3ab2a5c23d15ee351d5") + if err != nil { + t.Errorf("err: %+v\n", err) + } + if !reflect.DeepEqual(AUTN, expAUTN) { + t.Errorf("AUTN[0x%x] \t expected[0x%x]\n", AUTN, expAUTN) + } +} diff --git a/mongoapi/mongoapi.go b/mongoapi/mongoapi.go new file mode 100644 index 0000000..0c2d932 --- /dev/null +++ b/mongoapi/mongoapi.go @@ -0,0 +1,309 @@ +package mongoapi + +import ( + "context" + "encoding/json" + "fmt" + "time" + + jsonpatch "github.com/evanphx/json-patch" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var ( + Client *mongo.Client = nil + dbName string +) + +func SetMongoDB(setdbName string, url string) error { + if Client != nil { + return nil + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + client, err := mongo.Connect(ctx, options.Client().ApplyURI(url)) + if err != nil { + return fmt.Errorf("SetMongoDB err: %+v", err) + } + Client = client + dbName = setdbName + return nil +} + +func findOneAndDecode(collection *mongo.Collection, filter bson.M) (map[string]interface{}, error) { + var result map[string]interface{} + if err := collection.FindOne(context.TODO(), filter).Decode(&result); err != nil { + // ErrNoDocuments means that the filter did not match any documents in + // the collection. + if err == mongo.ErrNoDocuments { + return nil, nil + } + return nil, err + } + return result, nil +} + +func getOrigData(collection *mongo.Collection, filter bson.M) (map[string]interface{}, error) { + result, err := findOneAndDecode(collection, filter) + if err != nil { + return nil, err + } + if result != nil { + // Delete "_id" entry which is auto-inserted by MongoDB + delete(result, "_id") + } + return result, nil +} + +func checkDataExisted(collection *mongo.Collection, filter bson.M) (bool, error) { + result, err := findOneAndDecode(collection, filter) + if err != nil { + return false, err + } + if result == nil { + return false, nil + } + return true, nil +} + +func RestfulAPIGetOne(collName string, filter bson.M) (map[string]interface{}, error) { + collection := Client.Database(dbName).Collection(collName) + result, err := getOrigData(collection, filter) + if err != nil { + return nil, fmt.Errorf("RestfulAPIGetOne err: %+v", err) + } + return result, nil +} + +func RestfulAPIGetMany(collName string, filter bson.M) ([]map[string]interface{}, error) { + collection := Client.Database(dbName).Collection(collName) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + cur, err := collection.Find(ctx, filter) + if err != nil { + return nil, fmt.Errorf("RestfulAPIGetMany err: %+v", err) + } + defer func(ctx context.Context) { + if err := cur.Close(ctx); err != nil { + return + } + }(ctx) + + var resultArray []map[string]interface{} + for cur.Next(ctx) { + var result map[string]interface{} + if err := cur.Decode(&result); err != nil { + return nil, fmt.Errorf("RestfulAPIGetMany err: %+v", err) + } + + // Delete "_id" entry which is auto-inserted by MongoDB + delete(result, "_id") + resultArray = append(resultArray, result) + } + if err := cur.Err(); err != nil { + return nil, fmt.Errorf("RestfulAPIGetMany err: %+v", err) + } + + return resultArray, nil +} + +// if no error happened, return true means data existed and false means data not existed +func RestfulAPIPutOne(collName string, filter bson.M, putData map[string]interface{}) (bool, error) { + collection := Client.Database(dbName).Collection(collName) + existed, err := checkDataExisted(collection, filter) + if err != nil { + return false, fmt.Errorf("RestfulAPIPutOne err: %+v", err) + } + + if existed { + if _, err := collection.UpdateOne(context.TODO(), filter, bson.M{"$set": putData}); err != nil { + return false, fmt.Errorf("RestfulAPIPutOne UpdateOne err: %+v", err) + } + return true, nil + } + + if _, err := collection.InsertOne(context.TODO(), putData); err != nil { + return false, fmt.Errorf("RestfulAPIPutOne InsertOne err: %+v", err) + } + return false, nil +} + +// if no error happened, return true means data existed (not updated) and false means data not existed +func RestfulAPIPutOneNotUpdate(collName string, filter bson.M, putData map[string]interface{}) (bool, error) { + collection := Client.Database(dbName).Collection(collName) + existed, err := checkDataExisted(collection, filter) + if err != nil { + return false, fmt.Errorf("RestfulAPIPutOneNotUpdate err: %+v", err) + } + + if existed { + return true, nil + } + + if _, err := collection.InsertOne(context.TODO(), putData); err != nil { + return false, fmt.Errorf("RestfulAPIPutOneNotUpdate InsertOne err: %+v", err) + } + return false, nil +} + +func RestfulAPIPutMany(collName string, filterArray []bson.M, putDataArray []map[string]interface{}) error { + collection := Client.Database(dbName).Collection(collName) + + for i, putData := range putDataArray { + filter := filterArray[i] + existed, err := checkDataExisted(collection, filter) + if err != nil { + return fmt.Errorf("RestfulAPIPutMany err: %+v", err) + } + + if existed { + if _, err := collection.UpdateOne(context.TODO(), filter, bson.M{"$set": putData}); err != nil { + return fmt.Errorf("RestfulAPIPutMany UpdateOne err: %+v", err) + } + } else { + if _, err := collection.InsertOne(context.TODO(), putData); err != nil { + return fmt.Errorf("RestfulAPIPutMany InsertOne err: %+v", err) + } + } + } + return nil +} + +func RestfulAPIDeleteOne(collName string, filter bson.M) error { + collection := Client.Database(dbName).Collection(collName) + + if _, err := collection.DeleteOne(context.TODO(), filter); err != nil { + return fmt.Errorf("RestfulAPIDeleteOne err: %+v", err) + } + return nil +} + +func RestfulAPIDeleteMany(collName string, filter bson.M) error { + collection := Client.Database(dbName).Collection(collName) + + if _, err := collection.DeleteMany(context.TODO(), filter); err != nil { + return fmt.Errorf("RestfulAPIDeleteMany err: %+v", err) + } + return nil +} + +func RestfulAPIMergePatch(collName string, filter bson.M, patchData map[string]interface{}) error { + collection := Client.Database(dbName).Collection(collName) + + originalData, err := getOrigData(collection, filter) + if err != nil { + return fmt.Errorf("RestfulAPIMergePatch getOrigData err: %+v", err) + } + + original, err := json.Marshal(originalData) + if err != nil { + return fmt.Errorf("RestfulAPIMergePatch Marshal err: %+v", err) + } + + patchDataByte, err := json.Marshal(patchData) + if err != nil { + return fmt.Errorf("RestfulAPIMergePatch Marshal err: %+v", err) + } + + modifiedAlternative, err := jsonpatch.MergePatch(original, patchDataByte) + if err != nil { + return fmt.Errorf("RestfulAPIMergePatch MergePatch err: %+v", err) + } + + var modifiedData map[string]interface{} + if err := json.Unmarshal(modifiedAlternative, &modifiedData); err != nil { + return fmt.Errorf("RestfulAPIMergePatch Unmarshal err: %+v", err) + } + if _, err := collection.UpdateOne(context.TODO(), filter, bson.M{"$set": modifiedData}); err != nil { + return fmt.Errorf("RestfulAPIMergePatch UpdateOne err: %+v", err) + } + return nil +} + +func RestfulAPIJSONPatch(collName string, filter bson.M, patchJSON []byte) error { + collection := Client.Database(dbName).Collection(collName) + + originalData, err := getOrigData(collection, filter) + if err != nil { + return fmt.Errorf("RestfulAPIJSONPatch getOrigData err: %+v", err) + } + + original, err := json.Marshal(originalData) + if err != nil { + return fmt.Errorf("RestfulAPIJSONPatch Marshal err: %+v", err) + } + + patch, err := jsonpatch.DecodePatch(patchJSON) + if err != nil { + return fmt.Errorf("RestfulAPIJSONPatch DecodePatch err: %+v", err) + } + + modified, err := patch.Apply(original) + if err != nil { + return fmt.Errorf("RestfulAPIJSONPatch Apply err: %+v", err) + } + + var modifiedData map[string]interface{} + if err := json.Unmarshal(modified, &modifiedData); err != nil { + return fmt.Errorf("RestfulAPIJSONPatch Unmarshal err: %+v", err) + } + if _, err := collection.UpdateOne(context.TODO(), filter, bson.M{"$set": modifiedData}); err != nil { + return fmt.Errorf("RestfulAPIJSONPatch UpdateOne err: %+v", err) + } + return nil +} + +func RestfulAPIJSONPatchExtend(collName string, filter bson.M, patchJSON []byte, dataName string) error { + collection := Client.Database(dbName).Collection(collName) + + originalDataCover, err := getOrigData(collection, filter) + if err != nil { + return fmt.Errorf("RestfulAPIJSONPatchExtend getOrigData err: %+v", err) + } + + originalData := originalDataCover[dataName] + original, err := json.Marshal(originalData) + if err != nil { + return fmt.Errorf("RestfulAPIJSONPatchExtend Marshal err: %+v", err) + } + + patch, err := jsonpatch.DecodePatch(patchJSON) + if err != nil { + return fmt.Errorf("RestfulAPIJSONPatchExtend DecodePatch err: %+v", err) + } + + modified, err := patch.Apply(original) + if err != nil { + return fmt.Errorf("RestfulAPIJSONPatchExtend Apply err: %+v", err) + } + + var modifiedData map[string]interface{} + if err := json.Unmarshal(modified, &modifiedData); err != nil { + return fmt.Errorf("RestfulAPIJSONPatchExtend Unmarshal err: %+v", err) + } + if _, err := collection.UpdateOne(context.TODO(), filter, bson.M{"$set": bson.M{dataName: modifiedData}}); err != nil { + return fmt.Errorf("RestfulAPIJSONPatchExtend UpdateOne err: %+v", err) + } + return nil +} + +func RestfulAPIPost(collName string, filter bson.M, postData map[string]interface{}) (bool, error) { + return RestfulAPIPutOne(collName, filter, postData) +} + +func RestfulAPIPostMany(collName string, filter bson.M, postDataArray []interface{}) error { + collection := Client.Database(dbName).Collection(collName) + + if _, err := collection.InsertMany(context.TODO(), postDataArray); err != nil { + return fmt.Errorf("RestfulAPIPostMany err: %+v", err) + } + return nil +} + +func Drop(collName string) error { + collection := Client.Database(dbName).Collection(collName) + return collection.Drop(context.TODO()) +} diff --git a/ueauth/ueauth.go b/ueauth/ueauth.go new file mode 100644 index 0000000..6d7f19d --- /dev/null +++ b/ueauth/ueauth.go @@ -0,0 +1,53 @@ +package ueauth + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" +) + +const ( + FC_FOR_CK_PRIME_IK_PRIME_DERIVATION = "20" + FC_FOR_KSEAF_DERIVATION = "6C" + FC_FOR_RES_STAR_XRES_STAR_DERIVATION = "6B" + FC_FOR_KAUSF_DERIVATION = "6A" + FC_FOR_KAMF_DERIVATION = "6D" + FC_FOR_KGNB_KN3IWF_DERIVATION = "6E" + FC_FOR_NH_DERIVATION = "6F" + FC_FOR_ALGORITHM_KEY_DERIVATION = "69" +) + +func KDFLen(input []byte) []byte { + r := make([]byte, 2) + binary.BigEndian.PutUint16(r, uint16(len(input))) + return r +} + +// This function implements the KDF defined in TS.33220 cluase B.2.0. +// +// For P0-Pn, the ones that will be used directly as a string (e.g. "WLAN") should be type-casted by []byte(), +// and the ones originally in hex (e.g. "bb52e91c747a") should be converted by using hex.DecodeString(). +// +// For L0-Ln, use KDFLen() function to calculate them (e.g. KDFLen(P0)). +func GetKDFValue(key []byte, FC string, param ...[]byte) ([]byte, error) { + kdf := hmac.New(sha256.New, key) + + var S []byte + if STmp, err := hex.DecodeString(FC); err != nil { + return nil, fmt.Errorf("GetKDFValue FC decode failed: %+v", err) + } else { + S = STmp + } + + for _, p := range param { + S = append(S, p...) + } + + if _, err := kdf.Write(S); err != nil { + return nil, fmt.Errorf("GetKDFValue KDF write failed: %+v", err) + } + sum := kdf.Sum(nil) + return sum, nil +} diff --git a/ueauth/ueauth_test.go b/ueauth/ueauth_test.go new file mode 100644 index 0000000..4ad16ff --- /dev/null +++ b/ueauth/ueauth_test.go @@ -0,0 +1,69 @@ +package ueauth + +import ( + "bytes" + "encoding/hex" + "testing" +) + +type TestKDF struct { + NetworkName string + SQNxorAK string + CK string + IK string + FC string + DerivedKey string +} + +func TestGetKDFValue(t *testing.T) { + // Only the network name is different, which should yet yield different derived results + // RFC 5448 Test Vector 1 + TestKDFTable := []TestKDF{ + // SUCCESS case + { + NetworkName: "WLAN", + SQNxorAK: "bb52e91c747a", + CK: "5349fbe098649f948f5d2e973a81c00f", + IK: "9744871ad32bf9bbd1dd5ce54e3e2e5a", + FC: FC_FOR_CK_PRIME_IK_PRIME_DERIVATION, + DerivedKey: "0093962d0dd84aa5684b045c9edffa04ccfc230ca74fcc96c0a5d61164f5a76c", + }, + // FAILURE case + { + NetworkName: "WLANNNNNNNNNNNNNNN", + SQNxorAK: "bb52e91c747a", + CK: "5349fbe098649f948f5d2e973a81c00f", + IK: "9744871ad32bf9bbd1dd5ce54e3e2e5a", + FC: FC_FOR_CK_PRIME_IK_PRIME_DERIVATION, + DerivedKey: "0093962d0dd84aa5684b045c9edffa04ccfc230ca74fcc96c0a5d61164f5a76c", + }, + } + + for i, tc := range TestKDFTable { + P0 := []byte(tc.NetworkName) + + P1, err := hex.DecodeString(tc.SQNxorAK) + if err != nil { + t.Errorf("TestGetKDFValue[%d] error: %+v\n", i, err) + } + + ckik, err := hex.DecodeString(tc.CK + tc.IK) + if err != nil { + t.Errorf("TestGetKDFValue[%d] error: %+v\n", i, err) + } + + val, err := GetKDFValue(ckik, tc.FC, P0, KDFLen(P0), P1, KDFLen(P1)) + if err != nil { + t.Errorf("TestGetKDFValue[%d] error: %+v\n", i, err) + } + // fmt.Printf("val = %x\n", val) + + dk, err := hex.DecodeString(tc.DerivedKey) + if err != nil { + t.Errorf("TestGetKDFValue[%d] error: %+v\n", i, err) + } + if (i == 0 && !bytes.Equal(val, dk)) || (i == 1 && bytes.Equal(val, dk)) { + t.Errorf("TestGetKDFValue[%d] failed\n", i) + } + } +} diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..1f12181 --- /dev/null +++ b/version/version.go @@ -0,0 +1,39 @@ +package version + +import ( + "fmt" + "runtime" +) + +var ( + VERSION string + BUILD_TIME string + COMMIT_HASH string + COMMIT_TIME string +) + +func GetVersion() string { + if VERSION != "" { + return fmt.Sprintf( + "\n\tfree5GC version: %s"+ + "\n\tbuild time: %s"+ + "\n\tcommit hash: %s"+ + "\n\tcommit time: %s"+ + "\n\tgo version: %s %s/%s", + VERSION, + BUILD_TIME, + COMMIT_HASH, + COMMIT_TIME, + runtime.Version(), + runtime.GOOS, + runtime.GOARCH, + ) + } else { + return fmt.Sprintf( + "\n\tNot specify ldflags (which link version) during go build\n\tgo version: %s %s/%s", + runtime.Version(), + runtime.GOOS, + runtime.GOARCH, + ) + } +} diff --git a/version/version_test.go b/version/version_test.go new file mode 100644 index 0000000..f754aa2 --- /dev/null +++ b/version/version_test.go @@ -0,0 +1,86 @@ +package version + +import ( + "fmt" + "os/exec" + "runtime" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVersion(t *testing.T) { + t.Run("VERSION not specified", func(t *testing.T) { + expected := fmt.Sprintf( + "\n\tNot specify ldflags (which link version) during go build\n\tgo version: %s %s/%s", + runtime.Version(), + runtime.GOOS, + runtime.GOARCH) + assert.Equal(t, expected, GetVersion()) + }) + + t.Run("VERSION constly specified", func(t *testing.T) { + VERSION = "Release-v3.100.200" + BUILD_TIME = "2020-09-11T07:05:04Z" + COMMIT_HASH = "fb2481c2" + COMMIT_TIME = "2020-09-11T07:00:29Z" + + expected := fmt.Sprintf( + "\n\tfree5GC version: %s"+ + "\n\tbuild time: %s"+ + "\n\tcommit hash: %s"+ + "\n\tcommit time: %s"+ + "\n\tgo version: %s %s/%s", + VERSION, + BUILD_TIME, + COMMIT_HASH, + COMMIT_TIME, + runtime.Version(), + runtime.GOOS, + runtime.GOARCH) + + assert.Equal(t, expected, GetVersion()) + fmt.Println(VERSION) + }) + + t.Run("VERSION capture by system", func(t *testing.T) { + var stdout []byte + var err error + VERSION = "Release-v3.100.200" // VERSION using free5gc's version (git tag), we static set it here + stdout, err = exec.Command("bash", "-c", "date -u +\"%Y-%m-%dT%H:%M:%SZ\"").Output() + if err != nil { + t.Errorf("err: %+v\n", err) + } + BUILD_TIME = strings.TrimSuffix(string(stdout), "\n") + stdout, err = exec.Command("bash", "-c", "git log --pretty=\"%H\" -1 | cut -c1-8").Output() + if err != nil { + t.Errorf("err: %+v\n", err) + } + COMMIT_HASH = strings.TrimSuffix(string(stdout), "\n") + stdout, err = exec.Command("bash", "-c", + "git log --pretty=\"%ai\" -1 | awk '{time=$1\"T\"$2\"Z\"; print time}'").Output() + if err != nil { + t.Errorf("err: %+v\n", err) + } + fmt.Println("Insert Data") + COMMIT_TIME = strings.TrimSuffix(string(stdout), "\n") + + expected := fmt.Sprintf( + "\n\tfree5GC version: %s"+ + "\n\tbuild time: %s"+ + "\n\tcommit hash: %s"+ + "\n\tcommit time: %s"+ + "\n\tgo version: %s %s/%s", + VERSION, + BUILD_TIME, + COMMIT_HASH, + COMMIT_TIME, + runtime.Version(), + runtime.GOOS, + runtime.GOARCH) + + assert.Equal(t, expected, GetVersion()) + fmt.Println(VERSION) + }) +}