From 6e1497004ff4705a326ed5b4c8fa722e403b2914 Mon Sep 17 00:00:00 2001 From: Mathis Joffre Date: Mon, 3 Apr 2023 09:24:08 +0200 Subject: [PATCH] Rework on NLMF and overall SDK Signed-off-by: Mathis Joffre --- .github/workflows/test.yaml | 18 +++ .gitignore | 92 +----------- LICENSE | 222 ++++++++++++++++++++++++++--- Makefile | 16 +++ README.md | 70 +++++---- docs/CONTRIBUTING.md | 106 ++++++++++++++ docs/design-principles.md | 115 +++++++++++++++ docs/images/logo_no_bg.png | Bin 0 -> 19498 bytes docs/images/logo_white_bg.png | Bin 0 -> 22810 bytes docs/philosophy.md | 47 ++++++ fivegc/helper.go | 33 +++++ fivegc/nlmf/broadcast.go | 79 +++++----- fivegc/nlmf/client.go | 16 +++ fivegc/nlmf/examples/main.go | 43 ++++++ fivegc/nlmf/location.go | 154 ++++++++++++-------- fivegc/nlmf/mock/broadcast.go | 90 ++++++------ fivegc/nlmf/mock/generate.go | 4 + fivegc/nlmf/mock/location.go | 124 ++++++++++------ fivegc/nlmf/{nlmf.go => server.go} | 16 +-- fivegc/redirect.go | 4 +- fivegc/server.go | 11 ++ fivegc/status.go | 2 + go.mod | 14 +- go.sum | 128 +++++++++++++++++ internal/header/header_test.go | 24 ++++ 25 files changed, 1095 insertions(+), 333 deletions(-) create mode 100644 .github/workflows/test.yaml create mode 100644 Makefile create mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/design-principles.md create mode 100644 docs/images/logo_no_bg.png create mode 100644 docs/images/logo_white_bg.png create mode 100644 docs/philosophy.md create mode 100644 fivegc/helper.go create mode 100644 fivegc/nlmf/client.go create mode 100644 fivegc/nlmf/examples/main.go create mode 100644 fivegc/nlmf/mock/generate.go rename fivegc/nlmf/{nlmf.go => server.go} (73%) create mode 100644 fivegc/server.go create mode 100644 go.sum create mode 100644 internal/header/header_test.go diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..4a36c32 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,18 @@ +name: Run tests +run-name: ${{ github.event.pull_request.title }} (#${{ github.event.pull_request.number }}) - Run tests +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - master +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v4 + with: + go-version: '1.20.2' + - name: Test + run: make test \ No newline at end of file diff --git a/.gitignore b/.gitignore index b5d7bd7..d48c759 100644 --- a/.gitignore +++ b/.gitignore @@ -1,90 +1,2 @@ -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Go template -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - +.idea +.vscode \ No newline at end of file diff --git a/LICENSE b/LICENSE index 02e3889..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2022 5GCoreNet - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d4231d4 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +GOPATH := $(shell go env GOPATH) + +dependencies: + echo "Installing dependencies" + go install github.com/golang/mock/mockgen@v1.6.0 + go mod download + +mock-gen: dependencies + echo "Generating mock files" + PATH=$(PATH):$(GOPATH)/bin go generate ./... + +test: mock-gen + echo "Running tests" + go test -v ./... + +.PHONY: mock-gen diff --git a/README.md b/README.md index 59011a6..62b2e15 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,26 @@ # 5GCoreNetSDK -[![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/5GCoreNet/5GCoreNetSDK.svg)](https://github.com/5GCoreNet/5GCoreNetSDK) -[![GitHub license](https://badgen.net/github/license/5GCoreNet/5GCoreNetSDK)](https://github.com/5GCoreNet/5GCoreNetSDK/blob/main/LICENSE) -[![GitHub stars](https://img.shields.io/github/stars/5GCoreNet/5GCoreNetSDK.svg?style=social&label=Star&maxAge=2592000)](https://github.com/5GCoreNet/5GCoreNetSDK) +[![Go Reference](https://pkg.go.dev/badge/github.com/5GCoreNet/5GCoreNetSDK.svg)](https://pkg.go.dev/github.com/5GCoreNet/5GCoreNetSDK) +[![Go Report Card](https://goreportcard.com/badge/github.com/5GCoreNet/5GCoreNetSDK)](https://goreportcard.com/report/github.com/5GCoreNet/5GCoreNetSDK) +[![License](https://img.shields.io/github/license/5GCoreNet/5GCoreNetSDK)](LICENSE) +![GitHub stars](https://img.shields.io/github/stars/5GCoreNet/5GCoreNetSDK?style=social) +> At this moment, this SDK is in development. It is not ready for production use. +> Refers to the [Roadmap](#roadmap) section for more information on what has been done. +> +> See the [Contributing](#contributing) section if you would like to help make it better. +

- +

-> At this moment, this SDK is in development. It is not ready for production use. -> Refers to the [Roadmap](#roadmap) section for more information on what has been done. -> -> See the [Contributing](#contributing) section if you would like to help -> make it better. - -5GCoreNetSDK is an open source project that provides a set of APIs to access or provide services in 5G Core Network. The APIs are based on the 3GPP specifications and are implemented in Golang. +5GCoreNetSDK is an open source project that provides a set of APIs to access or expose a Network Function (NF) in 5G Core Network. +The APIs are based on the 3GPP specifications and are implemented in Golang. -Under the hood, the SDK exposes through a RESTful API the Network Function you've built, according to the standard. The RESTful API is implemented using [Gin](https://github.com/gin-gonic/gin) framework. +Under the hood, the SDK exposes the standard HTTP RESTful API for the Network Function you've built. +The RESTful APIs are implemented using [Gin](https://github.com/gin-gonic/gin) framework. -At the moment, the APIs are implemented for the release 18 of the 3GPP specifications. +The SDK follows the R18 3GPP specifications. ## Getting Started @@ -26,19 +28,37 @@ At the moment, the APIs are implemented for the release 18 of the 3GPP specifica go get github.com/5GCoreNet/5GCoreNetSDK ``` +## Features -## Contributing -Feel free to contribute to this project. You can do it by: -- Reporting bugs -- Suggesting new features -- Implementing new features -- Fixing bugs -- Improving documentation -- ... - -## License -Licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +- Simple and easy to use, just implement the interface you need, and you are ready to go +- Support for both Server and Client mode +- Follows the 3GPP specifications out of the box (R18) +- C compatible thanks to [cgo](https://golang.org/cmd/cgo/) +- Fully open source and free to use, modify, and distribute under the terms of the [Apache 2.0 license](LICENSE) ## Roadmap -TBD +As 5GCoreNetSDK is still in development, the following table is the roadmap representing what has been done and what is still to do. +The following table shows the status of the APIs and the order in which they will be implemented. If you want to see a specific API implemented, +you can open an issue (or you can implement it and open a pull request). + +Network Function | API | Status | Comments | Documentation +---------------- |------|-----------------|-----------------------------------------------------------------------------------------| ------------- +LMF | NLMF | In progress | NLMF is the first API proposal and is considered as a PoC. NLMF might change in future. | [Link](fivegc/nlmf/examples/main.go) +NRF | NNRF | Not implemented | | +AMF | NAMF | Not implemented | | +SMF | NSMF | Not implemented | | +UDM | NUDM | Not implemented | | +UDR | NUDR | Not implemented | | +AUSF | NAUSF | Not implemented | | +PCF | NPCF | Not implemented | | +NSSF | NNSSF | Not implemented | | +N3IWF | NN3IWF | Not implemented | | +UPF | NUPF | Not implemented | | + + +> Note that API is considered implemented if it is implemented in the SDK in Server and Client mode and if it is tested and documented. + +## Contributing + +Contributions are welcome! Please read the [Contributing Guide](docs/CONTRIBUTING.md) for more information. \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..c64997a --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,106 @@ +# Contributing + +When contributing to this repository, please ensure that your Pull Request is linked to an issue, +and that the issue is tagged with the appropriate labels (e.g. bug, enhancement, etc.). This will +help us to keep track of the changes and discuss them. + +Please note we have a code of conduct, please follow it in all your interactions with the project. + +If you want to contribute to 5GCoreNetSDK, please make sure that your code follows principles defined in [Design Principles](design-principles.md). +## Pull Request Process + +Firstly, thank you for contributing to this repository. Please ensure that you gather the following requirements to contribute: + +1. Ensure that the Pull Request is linked to an issue. +2. Ensure that the issue is tagged with the appropriate labels (e.g. bug, enhancement, etc.) and prefix your Pull Request with the domain of the issue and NF impacted (e.g. `bugfix/nlmf`, `enhancement/nlmf`, etc.). +3. Ensure that you only include one commit in your Pull Request. If you have multiple commits, please squash them into one commit. +4. Ensure you sign your commits. See [Signing your commits](#signing-your-commits) for more information. +5. Ensure that you have updated the documentation and the tests to reflect your changes. +6. Run the tests to ensure that your changes do not break any existing tests. +7. Ensure that your code is formatted using `gofmt` and linted using `golint`. +8. Prepare a Pull Request with the following information: + * A description of the changes you have made. + * A link to the issue you are fixing. + +### Signing your commits + +To sign your commits, just add the `-s` flag when you commit. This will add a `Signed-off-by` line to your commit message. For example: + +```bash +git commit -s -m "This is my commit message" +``` + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file diff --git a/docs/design-principles.md b/docs/design-principles.md new file mode 100644 index 0000000..387651c --- /dev/null +++ b/docs/design-principles.md @@ -0,0 +1,115 @@ +# Design principals + +As 5GCoreNetSDK aims to be a simple and easy to use SDK, we have defined some design principals that we want to follow. + +If you want to contribute to 5GCoreNetSDK, please make sure that your code follows these principals. + +## Simple and easy to use + +This is the main goal of 5GCoreNetSDK. We want to provide a simple and easy to use SDK to build 5G Core Network NFs following the R18 3GPP specifications. + +Indeed, some 3GPP specifications are very complex and hard to understand. We want to make it easy to use the 3GPP specifications in code. + +## Interface based + +5GCoreNetSDK is based on interfaces. This means that you need to implement the interfaces. To do so, you just need to +create a struct that implements the interface and that's it. + +This is very useful because it allows you to provide your own implementation of the Network Functions without bothering about the 3GPP specifications. + +Plus, it makes it easy to test your code, because you can mock the interfaces you need. + +So every Network Function connectivity layer must be based on an interface. + +Let's take an example, consider we want to implement the `Foo` endpoint. + +```go +package foo + +import ( + "context" + "github.com/5GCore/5GCoreNetSDK/fivegc" +) + +type Foo interface { + fivegc.CommonInterface + Foo(context.Context, string) string, ProblemDetails, fivegc.RedirectResponse, FooStatusCode +} +``` + +This interface is composed of the `CommonInterface` interface, which is the interface that every endpoint must implement. + +`CommonInterface` is here to provide a common interface wrapping error handling (maybe we will add more stuff in the future). + +Then, we have the `Foo` method, which is the method that will be called when a request is sent to the `Foo` endpoint. + +And that's it, you have implemented the `Foo` endpoint. Not so hard, right? + +## Client flow +Interfaces are used for the server part, whereas the client part is following a more classical flow. + +Client must follow the following flow to send a request to a service: + +1 - Create a new client +2 - Create a new query +3 - Fill the query with the parameters +3 - Execute the query + +```go +// Create a new client +client := client.NewClient(cfg) +// Client can be used to create a new query +query := client.NetworkFunctionEndpoint(ctx) +// Query can be used to fill the parameters +query = query.Paramaters(model) +// Query can be used to send a request to the server +response, err := client.NetworkFunctionEndpointExecute(query) +``` + +This flow is very simple and easy to use. It is also very easy to test, because you can mock the client and the query, +thus we decided to follow this flow for the client part. + +## Error handling + +As in 3GPP specifications, errors are handled using the `ProblemDetails` struct. + +Unmarshalling errors must be handled using the `ProblemDetails` struct as well. This struct is used to provide additional information in an error response. +That's why we decided to add na `Error()` method to all the interfaces by using the `CommonInterface` interface. + +```go +type CommonInterface interface { + // Error returns a problem details, it is used to handle errors when unmarshalling the request. + Error(ctx context.Context, err error) openapicommon.ProblemDetails +} +``` +Error is called when an error occurs during the unmarshalling of the request. It is used to provide additional information about the error. + +Thus, SDK users can handle by themselves the errors without effort. + +## Mocking + +Every interface implemented must be mocked. This is a very elegant way to provide user a way to test their code. + +In order to do so, we use the `mockgen` library. This library allows us to generate mocks from interfaces. + +Let's take an example, consider we want to mock the `Foo` interface. + +```go +package mock // located under foo/mock + +//go:generate mockgen -source=../foo.go -destination=foo.go -package=mock +``` + +This command will generate a `foo.go` file under the `foo/mock` package. This file will contain a mocked `Foo` interface. + +## Testing + +Every endpoint must be tested against the 3GPP specifications. Thus, we can ensure that our code is compliant with the 3GPP specifications and that we are not breaking anything. + +## Documentation + +Every endpoint must be documented. This is very important because it allows users to understand how to use the endpoint. + +You have to use GoDoc format to document your code. This is very easy to use, and it is very useful. You can find more information about GoDoc [here](https://blog.golang.org/godoc). + +Plus, you can add an example of how to use the endpoint. This is very useful because it allows users to better understand how to use the endpoint. \ No newline at end of file diff --git a/docs/images/logo_no_bg.png b/docs/images/logo_no_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..ab8141a20b16b0f7cc7f9a258e5479e1f52dd4f4 GIT binary patch literal 19498 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 n0s^ED|7ZB$mi!+l!!>=uFVLybt>ikIEZ9 literal 0 HcmV?d00001 diff --git a/docs/philosophy.md b/docs/philosophy.md new file mode 100644 index 0000000..10abe18 --- /dev/null +++ b/docs/philosophy.md @@ -0,0 +1,47 @@ +# Philosophy of 5GCoreNetSDK + +This document describes the philosophy of 5GCoreNetSDK and why we decided to start this project. + +Feel free to contribute to this document by creating a pull request or by opening an issue. + +## Motivation +5G Core Network is a complex network. It is composed of many Network Functions (NFs) that communicate with each other. +The 3GPP specifications are very complex and hard to understand. Thus, it is hard to develop inside this network. + +Many projects provide already built Network Functions (NFs) that you can use, but if you want to build your own Network Function (NF), +you need to implement the 3GPP specifications by yourself. This is very time-consuming and hard to do. + +Through this project, we want to provide a simple and easy to use SDK to build 5G Core Network NFs following the R18 3GPP specifications. + +The SDK is designed to be simple and easy to use, just implement the interface you need, and you are ready to go. + + +## What 5GCoreNetSDK is + +5GCoreNetSDK is a developer centric SDK to build 5G Core Network Functions (NFs) following the R18 3GPP specifications. + +It is written in [Golang](#why-golang) and is based on interfaces. This means that you only need to implement the interfaces you need to connect your Network Function (NF) to the 5G Core Network. + +Plus, thanks to the interfaces, even if you don't know the 3GPP specifications nor 5G Core design, you can easily develop a Network Function (NF) that will work with the 5G Core Network. + +## What 5GCoreNetSDK is not + +5GCoreNetSDK is not a : + +* Network Function (NF) implementation. +* 5G Core Network implementation. +* 5G Core Network simulator. +* 5G Core Network emulator. + +The goal of 5GCoreNetSDK is to provide a simply to use connectivity layer inside the 5G Core Network. + +## Technical choices + +### Why Golang? + +As you may know, many Network Functions (NFs) are implemented in C. This is because C is a very fast and secure language. +However, C is not easy to use. It is not easy to learn, and it is not easy to write code in C making it hard to maintain. + +Projects like [Free5GC](https://www.free5gc.org/) decided to use Golang to implement their Network Functions (NFs). This is because Golang is a modern programming language that is easy to learn and use. It is also a compiled language, which means that it is fast and +secure. Golang is also a C compatible language, which means that you can use C libraries in your Golang code, thus +you can easily import your C code to use it with 5GCoreNetSDK. diff --git a/fivegc/helper.go b/fivegc/helper.go new file mode 100644 index 0000000..78a5fb9 --- /dev/null +++ b/fivegc/helper.go @@ -0,0 +1,33 @@ +package fivegc + +func ToInt(i int) *int { + return &i +} + +func ToInt64(i int64) *int64 { + return &i +} + +func ToInt32(i int32) *int32 { + return &i +} + +func ToInt16(i int16) *int16 { + return &i +} + +func ToInt8(i int8) *int8 { + return &i +} + +func ToFloat64(f float64) *float64 { + return &f +} + +func ToFloat32(f float32) *float32 { + return &f +} + +func ToString(s string) *string { + return &s +} diff --git a/fivegc/nlmf/broadcast.go b/fivegc/nlmf/broadcast.go index d1f0cac..38e9a0c 100644 --- a/fivegc/nlmf/broadcast.go +++ b/fivegc/nlmf/broadcast.go @@ -4,71 +4,84 @@ import ( "context" "github.com/5GCoreNet/5GCoreNetSDK/fivegc" "github.com/5GCoreNet/5GCoreNetSDK/internal/header" - openapinlmfbroadcastclient "github.com/5GCoreNet/client-openapi/Nlmf_Broadcast" - openapinlmfbroadcastserver "github.com/5GCoreNet/server-openapi/Nlmf_Broadcast" + openapicommon "github.com/5GCoreNet/openapi/openapi_CommonData" + openapinlmfbroadcast "github.com/5GCoreNet/openapi/openapi_Nlmf_Broadcast" "github.com/gin-gonic/gin" ) +const ( + broadcastRouterGroup = "/nlmf-broadcast/v1" + cypherKeyEndpoint = "/cipher-key-data" +) + +// Broadcast is the interface that wraps the NLMF Broadcast service. type Broadcast interface { - // Error returns a problem details, it is used to handle errors when unmarshalling the request. - Error(ctx context.Context, err error) openapinlmfbroadcastserver.ProblemDetails - // CipherKeyData returns a cipher response data, a problem details, a redirect response and a status code. - CipherKeyData(context.Context, openapinlmfbroadcastserver.CipherRequestData) (openapinlmfbroadcastserver.CipherResponseData, openapinlmfbroadcastserver.ProblemDetails, fivegc.RedirectResponse, fivegc.StatusCode) + fivegc.CommonInterface + CipherKeyData(context.Context, openapinlmfbroadcast.CipherRequestData) (openapinlmfbroadcast.CipherResponseData, openapicommon.ProblemDetails, fivegc.RedirectResponse, CypherResponseStatusCode) } +type CypherResponseStatusCode fivegc.StatusCode + +const ( + // CypherResponseStatusCodeOK is the status code for a successful response. + CypherResponseStatusCodeOK CypherResponseStatusCode = CypherResponseStatusCode(fivegc.StatusOK) + CypherResponseStatusTemporaryRedirect CypherResponseStatusCode = CypherResponseStatusCode(fivegc.StatusTemporaryRedirect) + CypherResponseStatusPermanentRedirect CypherResponseStatusCode = CypherResponseStatusCode(fivegc.StatusPermanentRedirect) +) + func attachBroadcastHandler(router *gin.RouterGroup, b Broadcast) { - group := router.Group("/nlmf-broadcast/v1") + group := router.Group(broadcastRouterGroup) { - group.POST("/cipher-key-data", func(c *gin.Context) { - var req openapinlmfbroadcastserver.CipherRequestData + group.POST(cypherKeyEndpoint, func(c *gin.Context) { + var req openapinlmfbroadcast.CipherRequestData if err := c.ShouldBindJSON(&req); err != nil { problemDetails := b.Error(c, err) - c.JSON(int(problemDetails.Status), problemDetails) + c.JSON(int(*problemDetails.Status), problemDetails) return } res, problemDetails, redirectResponse, status := b.CipherKeyData(c, req) switch status { - case fivegc.StatusOK: - c.JSON(status.ToInt(), res) - case fivegc.StatusTemporaryRedirect: + case CypherResponseStatusCodeOK: + c.JSON(int(status), res) + case CypherResponseStatusTemporaryRedirect: header.BindRedirectHeader(c, redirectResponse.RedirectHeader) - c.JSON(status.ToInt(), redirectResponse) - case fivegc.StatusPermanentRedirect: + c.JSON(int(status), redirectResponse) + case CypherResponseStatusPermanentRedirect: header.BindRedirectHeader(c, redirectResponse.RedirectHeader) - c.JSON(status.ToInt(), redirectResponse) + c.JSON(int(status), redirectResponse) default: - c.JSON(status.ToInt(), problemDetails) + c.JSON(int(status), problemDetails) } return }) } } -// BroadcastClient is a client for the Nlmf_Broadcast service. +// BroadcastClient is a client for the NLMF Broadcast service. type BroadcastClient struct { - client *openapinlmfbroadcastclient.APIClient + client *openapinlmfbroadcast.APIClient } -// NewBroadcastClient creates a new client for the Nlmf_Broadcast service. +// NewBroadcastClient creates a new client for the NLMF Broadcast service. func NewBroadcastClient(cfg fivegc.ClientConfiguration) *BroadcastClient { - openapiCfg := &openapinlmfbroadcastclient.Configuration{ + openapiCfg := &openapinlmfbroadcast.Configuration{ Host: cfg.Host, Scheme: cfg.Scheme, DefaultHeader: cfg.DefaultHeader, UserAgent: cfg.UserAgent, Debug: cfg.Debug, - Servers: []openapinlmfbroadcastclient.ServerConfiguration{}, - OperationServers: make(map[string]openapinlmfbroadcastclient.ServerConfigurations), + Servers: []openapinlmfbroadcast.ServerConfiguration{}, + OperationServers: make(map[string]openapinlmfbroadcast.ServerConfigurations), HTTPClient: cfg.HTTPClient, } for _, server := range cfg.Servers { - openapiServer := openapinlmfbroadcastclient.ServerConfiguration{ + openapiServer := openapinlmfbroadcast.ServerConfiguration{ URL: server.URL, Description: server.Description, - Variables: make(map[string]openapinlmfbroadcastclient.ServerVariable), + Variables: make(map[string]openapinlmfbroadcast.ServerVariable), } for name, variable := range server.Variables { - openapiServer.Variables[name] = openapinlmfbroadcastclient.ServerVariable{ + openapiServer.Variables[name] = openapinlmfbroadcast.ServerVariable{ Description: variable.Description, DefaultValue: variable.DefaultValue, EnumValues: variable.EnumValues, @@ -77,15 +90,15 @@ func NewBroadcastClient(cfg fivegc.ClientConfiguration) *BroadcastClient { openapiCfg.Servers = append(openapiCfg.Servers, openapiServer) } for name, servers := range cfg.OperationServers { - openapiServers := make(openapinlmfbroadcastclient.ServerConfigurations, len(servers)) + openapiServers := make(openapinlmfbroadcast.ServerConfigurations, len(servers)) for i, server := range servers { - openapiServers[i] = openapinlmfbroadcastclient.ServerConfiguration{ + openapiServers[i] = openapinlmfbroadcast.ServerConfiguration{ URL: server.URL, Description: server.Description, - Variables: make(map[string]openapinlmfbroadcastclient.ServerVariable), + Variables: make(map[string]openapinlmfbroadcast.ServerVariable), } for name, variable := range server.Variables { - openapiServers[i].Variables[name] = openapinlmfbroadcastclient.ServerVariable{ + openapiServers[i].Variables[name] = openapinlmfbroadcast.ServerVariable{ Description: variable.Description, DefaultValue: variable.DefaultValue, EnumValues: variable.EnumValues, @@ -95,17 +108,17 @@ func NewBroadcastClient(cfg fivegc.ClientConfiguration) *BroadcastClient { openapiCfg.OperationServers[name] = openapiServers } return &BroadcastClient{ - client: openapinlmfbroadcastclient.NewAPIClient(openapiCfg), + client: openapinlmfbroadcast.NewAPIClient(openapiCfg), } } // CipheringKeyData returns a cipher request. -func (c *BroadcastClient) CipheringKeyData(ctx context.Context) openapinlmfbroadcastclient.ApiCipheringKeyDataRequest { +func (c *BroadcastClient) CipheringKeyData(ctx context.Context) openapinlmfbroadcast.ApiCipheringKeyDataRequest { return c.client.RequestCipheringKeyDataApi.CipheringKeyData(ctx) } // CipheringKeyDataExecute executes a cipher request. -func (c *BroadcastClient) CipheringKeyDataExecute(r openapinlmfbroadcastclient.ApiCipheringKeyDataRequest) (*openapinlmfbroadcastclient.CipherResponseData, error) { +func (c *BroadcastClient) CipheringKeyDataExecute(r openapinlmfbroadcast.ApiCipheringKeyDataRequest) (*openapinlmfbroadcast.CipherResponseData, error) { resp, _, err := r.Execute() return resp, err } diff --git a/fivegc/nlmf/client.go b/fivegc/nlmf/client.go new file mode 100644 index 0000000..dc9b906 --- /dev/null +++ b/fivegc/nlmf/client.go @@ -0,0 +1,16 @@ +package nlmf + +import "github.com/5GCoreNet/5GCoreNetSDK/fivegc" + +type Client struct { + *BroadcastClient + *LocationClient +} + +// NewClient returns a new client for an NLMF service. +func NewClient(config fivegc.ClientConfiguration) *Client { + return &Client{ + BroadcastClient: NewBroadcastClient(config), + LocationClient: NewLocationClient(config), + } +} diff --git a/fivegc/nlmf/examples/main.go b/fivegc/nlmf/examples/main.go new file mode 100644 index 0000000..5d17985 --- /dev/null +++ b/fivegc/nlmf/examples/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "context" + "github.com/5GCoreNet/5GCoreNetSDK/fivegc" + "github.com/5GCoreNet/5GCoreNetSDK/fivegc/nlmf" + openapicommon "github.com/5GCoreNet/openapi/openapi_CommonData" + nlmfbroadcast "github.com/5GCoreNet/openapi/openapi_Nlmf_Broadcast" + "log" +) + +type MyBroadcast struct { +} + +func (m MyBroadcast) Error(ctx context.Context, err error) openapicommon.ProblemDetails { + return openapicommon.ProblemDetails{ + Type: fivegc.ToString("error"), + Title: fivegc.ToString("error"), + Status: fivegc.ToInt32(int32(fivegc.StatusInternalServerError)), + Detail: fivegc.ToString(err.Error()), + Instance: fivegc.ToString("fake_instance"), + Cause: fivegc.ToString("unknown"), + InvalidParams: nil, + SupportedFeatures: fivegc.ToString(""), + AccessTokenError: &openapicommon.AccessTokenErr{}, + AccessTokenRequest: &openapicommon.AccessTokenReq{}, + NrfId: fivegc.ToString("1234567890"), + } +} + +func (m MyBroadcast) CipherKeyData(ctx context.Context, data nlmfbroadcast.CipherRequestData) (nlmfbroadcast.CipherResponseData, openapicommon.ProblemDetails, fivegc.RedirectResponse, nlmf.CypherResponseStatusCode) { + // Your code here ... + return nlmfbroadcast.CipherResponseData{}, openapicommon.ProblemDetails{}, fivegc.RedirectResponse{}, nlmf.CypherResponseStatusCodeOK +} + +func main() { + m := MyBroadcast{} + nlmfServer := nlmf.NewServer(":8080", "/v1/", log.Default()) + nlmfServer.AttachBroadcast(m) + nlmfServer.Start() + // Your code here ... + nlmfServer.Stop() +} diff --git a/fivegc/nlmf/location.go b/fivegc/nlmf/location.go index afe05b2..71edc12 100644 --- a/fivegc/nlmf/location.go +++ b/fivegc/nlmf/location.go @@ -4,122 +4,156 @@ import ( "context" "github.com/5GCoreNet/5GCoreNetSDK/fivegc" "github.com/5GCoreNet/5GCoreNetSDK/internal/header" - openapinlmflocationclient "github.com/5GCoreNet/client-openapi/Nlmf_Location" - openapinlmflocationserver "github.com/5GCoreNet/server-openapi/Nlmf_Location" + openapicommon "github.com/5GCoreNet/openapi/openapi_CommonData" + nlmfocation "github.com/5GCoreNet/openapi/openapi_Nlmf_Location" "github.com/gin-gonic/gin" "net/http" ) +const ( + locationRouterGroup = "/nlmf-loc/v1" + cancelLocationEndpoint = "/cancel-location" + determineLocationEndpoint = "/determine-location" + locationContextTransferEndpoint = "/location-context-transfer" +) + type Location interface { - // Error returns a problem details, it is used to handle errors when unmarshalling the request. - Error(ctx context.Context, err error) openapinlmflocationserver.ProblemDetails + fivegc.CommonInterface // CancelLocation cancels a location request. - CancelLocation(context.Context, openapinlmflocationserver.CancelLocData) (openapinlmflocationserver.ProblemDetails, fivegc.RedirectResponse, fivegc.StatusCode) + CancelLocation(context.Context, nlmfocation.CancelLocData) (openapicommon.ProblemDetails, fivegc.RedirectResponse, CancelLocationStatusCode) // DetermineLocation determines the location of a UE. - DetermineLocation(context.Context, openapinlmflocationserver.InputData) (openapinlmflocationserver.LocationData, openapinlmflocationserver.ProblemDetails, fivegc.RedirectResponse, fivegc.StatusCode) + DetermineLocation(context.Context, nlmfocation.InputData) (nlmfocation.LocationData, openapicommon.ProblemDetails, fivegc.RedirectResponse, DetermineLocationStatusCode) // LocationContextTransfer transfers the location context of a UE. - LocationContextTransfer(context.Context, openapinlmflocationserver.LocContextData) (openapinlmflocationserver.ProblemDetails, fivegc.RedirectResponse, fivegc.StatusCode) + LocationContextTransfer(context.Context, nlmfocation.LocContextData) (openapicommon.ProblemDetails, fivegc.RedirectResponse, LocationContextTransferStatusCode) } +type CancelLocationStatusCode fivegc.StatusCode + +const ( + // CancelLocationStatusNoContent is the status code for the response when the location request is successfully cancelled. + CancelLocationStatusNoContent CancelLocationStatusCode = CancelLocationStatusCode(fivegc.StatusNoContent) + CancelLocationStatusTemporaryRedirect CancelLocationStatusCode = CancelLocationStatusCode(fivegc.StatusTemporaryRedirect) + CancelLocationStatusPermanentRedirect CancelLocationStatusCode = CancelLocationStatusCode(fivegc.StatusPermanentRedirect) +) + +type DetermineLocationStatusCode fivegc.StatusCode + +const ( + // DetermineLocationStatusOK is the status code for a successful response. + DetermineLocationStatusOK DetermineLocationStatusCode = DetermineLocationStatusCode(fivegc.StatusOK) + DetermineLocationStatusNoContent DetermineLocationStatusCode = DetermineLocationStatusCode(fivegc.StatusNoContent) + DetermineLocationStatusTemporaryRedirect DetermineLocationStatusCode = DetermineLocationStatusCode(fivegc.StatusTemporaryRedirect) + DetermineLocationStatusPermanentRedirect DetermineLocationStatusCode = DetermineLocationStatusCode(fivegc.StatusPermanentRedirect) +) + +type LocationContextTransferStatusCode fivegc.StatusCode + +const ( + // LocationContextTransferStatusNoContent is the status code for the response when the location context transfer is successful. + LocationContextTransferStatusNoContent LocationContextTransferStatusCode = LocationContextTransferStatusCode(fivegc.StatusNoContent) + LocationContextTransferStatusTemporaryRedirect LocationContextTransferStatusCode = LocationContextTransferStatusCode(fivegc.StatusTemporaryRedirect) + LocationContextTransferStatusPermanentRedirect LocationContextTransferStatusCode = LocationContextTransferStatusCode(fivegc.StatusPermanentRedirect) +) + func attachLocationHandler(router *gin.RouterGroup, l Location) { - group := router.Group("/nlmf-loc/v1") + group := router.Group(locationRouterGroup) { - group.POST("/cancel-location", func(c *gin.Context) { - var req openapinlmflocationserver.CancelLocData + group.POST(cancelLocationEndpoint, func(c *gin.Context) { + var req nlmfocation.CancelLocData if err := c.ShouldBindJSON(&req); err != nil { problemDetails := l.Error(c, err) - c.JSON(int(problemDetails.Status), problemDetails) + c.JSON(int(*problemDetails.Status), problemDetails) return } problemDetails, redirectResponse, status := l.CancelLocation(c, req) switch status { - case fivegc.StatusNoContent: - c.JSON(status.ToInt(), nil) - case fivegc.StatusTemporaryRedirect: + case CancelLocationStatusNoContent: + c.JSON(int(status), nil) + case CancelLocationStatusTemporaryRedirect: header.BindRedirectHeader(c, redirectResponse.RedirectHeader) - c.JSON(status.ToInt(), redirectResponse) - case fivegc.StatusPermanentRedirect: + c.JSON(int(status), redirectResponse) + case CancelLocationStatusPermanentRedirect: header.BindRedirectHeader(c, redirectResponse.RedirectHeader) - c.JSON(status.ToInt(), redirectResponse) + c.JSON(int(status), redirectResponse) default: - c.JSON(status.ToInt(), problemDetails) + c.JSON(int(status), problemDetails) } return }) - group.POST("/determine-location", func(c *gin.Context) { - var req openapinlmflocationserver.InputData + group.POST(determineLocationEndpoint, func(c *gin.Context) { + var req nlmfocation.InputData if err := c.ShouldBindJSON(&req); err != nil { problemDetails := l.Error(c, err) - c.JSON(int(problemDetails.Status), problemDetails) + c.JSON(int(*problemDetails.Status), problemDetails) return } res, problemDetails, redirectResponse, status := l.DetermineLocation(c, req) switch status { - case fivegc.StatusOK: - c.JSON(status.ToInt(), res) - case fivegc.StatusNoContent: - c.JSON(status.ToInt(), nil) - case fivegc.StatusTemporaryRedirect: + case DetermineLocationStatusOK: + c.JSON(int(status), res) + case DetermineLocationStatusNoContent: + c.JSON(int(status), nil) + case DetermineLocationStatusTemporaryRedirect: header.BindRedirectHeader(c, redirectResponse.RedirectHeader) - c.JSON(status.ToInt(), redirectResponse) - case fivegc.StatusPermanentRedirect: + c.JSON(int(status), redirectResponse) + case DetermineLocationStatusPermanentRedirect: header.BindRedirectHeader(c, redirectResponse.RedirectHeader) - c.JSON(status.ToInt(), redirectResponse) + c.JSON(int(status), redirectResponse) default: - c.JSON(status.ToInt(), problemDetails) + c.JSON(int(status), problemDetails) } return }) - group.POST("/location-context-transfer", func(c *gin.Context) { - var req openapinlmflocationserver.LocContextData + group.POST(locationContextTransferEndpoint, func(c *gin.Context) { + var req nlmfocation.LocContextData if err := c.ShouldBindJSON(&req); err != nil { problemDetails := l.Error(c, err) - c.JSON(int(problemDetails.Status), problemDetails) + c.JSON(int(*problemDetails.Status), problemDetails) return } problemDetails, redirectResponse, status := l.LocationContextTransfer(c, req) switch status { - case fivegc.StatusNoContent: - c.JSON(status.ToInt(), nil) - case fivegc.StatusTemporaryRedirect: + case LocationContextTransferStatusNoContent: + c.JSON(int(status), nil) + case LocationContextTransferStatusTemporaryRedirect: header.BindRedirectHeader(c, redirectResponse.RedirectHeader) - c.JSON(status.ToInt(), redirectResponse) - case fivegc.StatusPermanentRedirect: + c.JSON(int(status), redirectResponse) + case LocationContextTransferStatusPermanentRedirect: header.BindRedirectHeader(c, redirectResponse.RedirectHeader) - c.JSON(status.ToInt(), redirectResponse) + c.JSON(int(status), redirectResponse) default: - c.JSON(status.ToInt(), problemDetails) + c.JSON(int(status), problemDetails) } return }) } } -// LocationClient is a client for the Nlmf_location service. +// LocationClient is a client for the NLMF Location service. type LocationClient struct { - client *openapinlmflocationclient.APIClient + client *nlmfocation.APIClient } -// NewLocationClient creates a new client for the Nlmf_location service. +// NewLocationClient creates a new client for the NLMF Location service. func NewLocationClient(cfg fivegc.ClientConfiguration) *LocationClient { - openapiCfg := &openapinlmflocationclient.Configuration{ + openapiCfg := &nlmfocation.Configuration{ Host: cfg.Host, Scheme: cfg.Scheme, DefaultHeader: cfg.DefaultHeader, UserAgent: cfg.UserAgent, Debug: cfg.Debug, - Servers: []openapinlmflocationclient.ServerConfiguration{}, - OperationServers: make(map[string]openapinlmflocationclient.ServerConfigurations), + Servers: []nlmfocation.ServerConfiguration{}, + OperationServers: make(map[string]nlmfocation.ServerConfigurations), HTTPClient: cfg.HTTPClient, } for _, server := range cfg.Servers { - openapiServer := openapinlmflocationclient.ServerConfiguration{ + openapiServer := nlmfocation.ServerConfiguration{ URL: server.URL, Description: server.Description, - Variables: make(map[string]openapinlmflocationclient.ServerVariable), + Variables: make(map[string]nlmfocation.ServerVariable), } for name, variable := range server.Variables { - openapiServer.Variables[name] = openapinlmflocationclient.ServerVariable{ + openapiServer.Variables[name] = nlmfocation.ServerVariable{ Description: variable.Description, DefaultValue: variable.DefaultValue, EnumValues: variable.EnumValues, @@ -128,15 +162,15 @@ func NewLocationClient(cfg fivegc.ClientConfiguration) *LocationClient { openapiCfg.Servers = append(openapiCfg.Servers, openapiServer) } for name, servers := range cfg.OperationServers { - openapiServers := make(openapinlmflocationclient.ServerConfigurations, len(servers)) + openapiServers := make(nlmfocation.ServerConfigurations, len(servers)) for i, server := range servers { - openapiServers[i] = openapinlmflocationclient.ServerConfiguration{ + openapiServers[i] = nlmfocation.ServerConfiguration{ URL: server.URL, Description: server.Description, - Variables: make(map[string]openapinlmflocationclient.ServerVariable), + Variables: make(map[string]nlmfocation.ServerVariable), } for name, variable := range server.Variables { - openapiServers[i].Variables[name] = openapinlmflocationclient.ServerVariable{ + openapiServers[i].Variables[name] = nlmfocation.ServerVariable{ Description: variable.Description, DefaultValue: variable.DefaultValue, EnumValues: variable.EnumValues, @@ -146,36 +180,36 @@ func NewLocationClient(cfg fivegc.ClientConfiguration) *LocationClient { openapiCfg.OperationServers[name] = openapiServers } return &LocationClient{ - client: openapinlmflocationclient.NewAPIClient(openapiCfg), + client: nlmfocation.NewAPIClient(openapiCfg), } } // LocationContextTransfer returns location context transfer request -func (l LocationClient) LocationContextTransfer(ctx context.Context) openapinlmflocationclient.ApiLocationContextTransferRequest { +func (l LocationClient) LocationContextTransfer(ctx context.Context) nlmfocation.ApiLocationContextTransferRequest { return l.client.LocationContextTransferApi.LocationContextTransfer(ctx) } // LocationContextTransferExecute executes the location context transfer request -func (l LocationClient) LocationContextTransferExecute(r openapinlmflocationclient.ApiLocationContextTransferRequest) (*http.Response, error) { +func (l LocationClient) LocationContextTransferExecute(r nlmfocation.ApiLocationContextTransferRequest) (*http.Response, error) { return r.Execute() } // DetermineLocation returns determine location request -func (l LocationClient) DetermineLocation(ctx context.Context) openapinlmflocationclient.ApiDetermineLocationRequest { +func (l LocationClient) DetermineLocation(ctx context.Context) nlmfocation.ApiDetermineLocationRequest { return l.client.DetermineLocationApi.DetermineLocation(ctx) } // DetermineLocationExecute executes the determine location request -func (l LocationClient) DetermineLocationExecute(r openapinlmflocationclient.ApiDetermineLocationRequest) (*openapinlmflocationclient.LocationData, *http.Response, error) { +func (l LocationClient) DetermineLocationExecute(r nlmfocation.ApiDetermineLocationRequest) (*nlmfocation.LocationData, *http.Response, error) { return r.Execute() } // CancelLocation returns cancel location request -func (l LocationClient) CancelLocation(ctx context.Context) openapinlmflocationclient.ApiCancelLocationRequest { +func (l LocationClient) CancelLocation(ctx context.Context) nlmfocation.ApiCancelLocationRequest { return l.client.CancelLocationApi.CancelLocation(ctx) } // CancelLocationExecute executes the cancel location request -func (l LocationClient) CancelLocationExecute(r openapinlmflocationclient.ApiCancelLocationRequest) (*http.Response, error) { +func (l LocationClient) CancelLocationExecute(r nlmfocation.ApiCancelLocationRequest) (*http.Response, error) { return r.Execute() } diff --git a/fivegc/nlmf/mock/broadcast.go b/fivegc/nlmf/mock/broadcast.go index 1674573..897d4a3 100644 --- a/fivegc/nlmf/mock/broadcast.go +++ b/fivegc/nlmf/mock/broadcast.go @@ -1,60 +1,70 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../broadcast.go + +// Package mock is a generated GoMock package. package mock import ( - "context" - "github.com/5GCoreNet/5GCoreNetSDK/fivegc" - openapinlmfbroadcast "github.com/5GCoreNet/server-openapi/Nlmf_Broadcast" + context "context" + reflect "reflect" + + fivegc "github.com/5GCoreNet/5GCoreNetSDK/fivegc" + nlmf "github.com/5GCoreNet/5GCoreNetSDK/fivegc/nlmf" + openapi_CommonData "github.com/5GCoreNet/openapi/openapi_CommonData" + openapi_Nlmf_Broadcast "github.com/5GCoreNet/openapi/openapi_Nlmf_Broadcast" + gomock "github.com/golang/mock/gomock" ) -// BroadcastMock is a mock of the Broadcast interface -type BroadcastMock struct { - cipherResponseData openapinlmfbroadcast.CipherResponseData - problemDetails openapinlmfbroadcast.ProblemDetails - redirectResponse fivegc.RedirectResponse - statusCode fivegc.StatusCode +// MockBroadcast is a mock of Broadcast interface. +type MockBroadcast struct { + ctrl *gomock.Controller + recorder *MockBroadcastMockRecorder } -// NewBroadcastMock creates a new mock of the Broadcast interface -func NewBroadcastMock( - cipherResponseData openapinlmfbroadcast.CipherResponseData, - problemDetails openapinlmfbroadcast.ProblemDetails, - redirectResponse fivegc.RedirectResponse, - statusCode fivegc.StatusCode, -) *BroadcastMock { - return &BroadcastMock{ - cipherResponseData: cipherResponseData, - problemDetails: problemDetails, - redirectResponse: redirectResponse, - statusCode: statusCode, - } +// MockBroadcastMockRecorder is the mock recorder for MockBroadcast. +type MockBroadcastMockRecorder struct { + mock *MockBroadcast } -// ProblemDetails allows to set the problem details of the mock -func (b *BroadcastMock) ProblemDetails(problemDetails openapinlmfbroadcast.ProblemDetails) { - b.problemDetails = problemDetails +// NewMockBroadcast creates a new mock instance. +func NewMockBroadcast(ctrl *gomock.Controller) *MockBroadcast { + mock := &MockBroadcast{ctrl: ctrl} + mock.recorder = &MockBroadcastMockRecorder{mock} + return mock } -// CipherResponseData allows to set the cipher response data of the mock -func (b *BroadcastMock) CipherResponseData(cipherResponseData openapinlmfbroadcast.CipherResponseData) { - b.cipherResponseData = cipherResponseData +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBroadcast) EXPECT() *MockBroadcastMockRecorder { + return m.recorder } -// RedirectResponse allows to set the redirect response of the mock -func (b *BroadcastMock) RedirectResponse(redirectResponse fivegc.RedirectResponse) { - b.redirectResponse = redirectResponse +// CipherKeyData mocks base method. +func (m *MockBroadcast) CipherKeyData(arg0 context.Context, arg1 openapi_Nlmf_Broadcast.CipherRequestData) (openapi_Nlmf_Broadcast.CipherResponseData, openapi_CommonData.ProblemDetails, fivegc.RedirectResponse, nlmf.CypherResponseStatusCode) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CipherKeyData", arg0, arg1) + ret0, _ := ret[0].(openapi_Nlmf_Broadcast.CipherResponseData) + ret1, _ := ret[1].(openapi_CommonData.ProblemDetails) + ret2, _ := ret[2].(fivegc.RedirectResponse) + ret3, _ := ret[3].(nlmf.CypherResponseStatusCode) + return ret0, ret1, ret2, ret3 } -// StatusCode allows to set the status code of the mock -func (b *BroadcastMock) StatusCode(statusCode fivegc.StatusCode) { - b.statusCode = statusCode +// CipherKeyData indicates an expected call of CipherKeyData. +func (mr *MockBroadcastMockRecorder) CipherKeyData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CipherKeyData", reflect.TypeOf((*MockBroadcast)(nil).CipherKeyData), arg0, arg1) } -// Error returns the problem details of the mock -func (b *BroadcastMock) Error(ctx context.Context, err error) openapinlmfbroadcast.ProblemDetails { - return b.problemDetails +// Error mocks base method. +func (m *MockBroadcast) Error(ctx context.Context, err error) openapi_CommonData.ProblemDetails { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Error", ctx, err) + ret0, _ := ret[0].(openapi_CommonData.ProblemDetails) + return ret0 } -// CipherKeyData returns the cipher response data, the problem details, the redirect response and the status code of the mock -func (b *BroadcastMock) CipherKeyData(ctx context.Context, data openapinlmfbroadcast.CipherRequestData) (openapinlmfbroadcast.CipherResponseData, openapinlmfbroadcast.ProblemDetails, fivegc.RedirectResponse, fivegc.StatusCode) { - return b.cipherResponseData, b.problemDetails, b.redirectResponse, b.statusCode +// Error indicates an expected call of Error. +func (mr *MockBroadcastMockRecorder) Error(ctx, err interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockBroadcast)(nil).Error), ctx, err) } diff --git a/fivegc/nlmf/mock/generate.go b/fivegc/nlmf/mock/generate.go new file mode 100644 index 0000000..7550837 --- /dev/null +++ b/fivegc/nlmf/mock/generate.go @@ -0,0 +1,4 @@ +package mock + +//go:generate mockgen -source=../broadcast.go -destination=broadcast.go -package=mock +//go:generate mockgen -source=../location.go -destination=location.go -package=mock diff --git a/fivegc/nlmf/mock/location.go b/fivegc/nlmf/mock/location.go index 39adaa6..a8b0104 100644 --- a/fivegc/nlmf/mock/location.go +++ b/fivegc/nlmf/mock/location.go @@ -1,70 +1,102 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../location.go + +// Package mock is a generated GoMock package. package mock import ( - "context" - "github.com/5GCoreNet/5GCoreNetSDK/fivegc" - openapinlmflocation "github.com/5GCoreNet/server-openapi/Nlmf_Location" + context "context" + reflect "reflect" + + fivegc "github.com/5GCoreNet/5GCoreNetSDK/fivegc" + nlmf "github.com/5GCoreNet/5GCoreNetSDK/fivegc/nlmf" + openapi_CommonData "github.com/5GCoreNet/openapi/openapi_CommonData" + openapi_Nlmf_Location "github.com/5GCoreNet/openapi/openapi_Nlmf_Location" + gomock "github.com/golang/mock/gomock" ) -// LocationMock is a mock of the Location interface -type LocationMock struct { - locationData openapinlmflocation.LocationData - problemDetails openapinlmflocation.ProblemDetails - redirectResponse fivegc.RedirectResponse - statusCode fivegc.StatusCode +// MockLocation is a mock of Location interface. +type MockLocation struct { + ctrl *gomock.Controller + recorder *MockLocationMockRecorder +} + +// MockLocationMockRecorder is the mock recorder for MockLocation. +type MockLocationMockRecorder struct { + mock *MockLocation +} + +// NewMockLocation creates a new mock instance. +func NewMockLocation(ctrl *gomock.Controller) *MockLocation { + mock := &MockLocation{ctrl: ctrl} + mock.recorder = &MockLocationMockRecorder{mock} + return mock } -// NewLocationMock creates a new mock of the Location interface -func NewLocationMock( - locationData openapinlmflocation.LocationData, - problemDetails openapinlmflocation.ProblemDetails, - redirectResponse fivegc.RedirectResponse, - statusCode fivegc.StatusCode, -) *LocationMock { - return &LocationMock{ - locationData: locationData, - problemDetails: problemDetails, - redirectResponse: redirectResponse, - statusCode: statusCode, - } +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLocation) EXPECT() *MockLocationMockRecorder { + return m.recorder } -// ProblemDetails allows to set the problem details of the mock -func (l *LocationMock) ProblemDetails(problemDetails openapinlmflocation.ProblemDetails) { - l.problemDetails = problemDetails +// CancelLocation mocks base method. +func (m *MockLocation) CancelLocation(arg0 context.Context, arg1 openapi_Nlmf_Location.CancelLocData) (openapi_CommonData.ProblemDetails, fivegc.RedirectResponse, nlmf.CancelLocationStatusCode) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CancelLocation", arg0, arg1) + ret0, _ := ret[0].(openapi_CommonData.ProblemDetails) + ret1, _ := ret[1].(fivegc.RedirectResponse) + ret2, _ := ret[2].(nlmf.CancelLocationStatusCode) + return ret0, ret1, ret2 } -// LocationData allows to set the location data of the mock -func (l *LocationMock) LocationData(locationData openapinlmflocation.LocationData) { - l.locationData = locationData +// CancelLocation indicates an expected call of CancelLocation. +func (mr *MockLocationMockRecorder) CancelLocation(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelLocation", reflect.TypeOf((*MockLocation)(nil).CancelLocation), arg0, arg1) } -// RedirectResponse allows to set the redirect response of the mock -func (l *LocationMock) RedirectResponse(redirectResponse fivegc.RedirectResponse) { - l.redirectResponse = redirectResponse +// DetermineLocation mocks base method. +func (m *MockLocation) DetermineLocation(arg0 context.Context, arg1 openapi_Nlmf_Location.InputData) (openapi_Nlmf_Location.LocationData, openapi_CommonData.ProblemDetails, fivegc.RedirectResponse, nlmf.DetermineLocationStatusCode) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DetermineLocation", arg0, arg1) + ret0, _ := ret[0].(openapi_Nlmf_Location.LocationData) + ret1, _ := ret[1].(openapi_CommonData.ProblemDetails) + ret2, _ := ret[2].(fivegc.RedirectResponse) + ret3, _ := ret[3].(nlmf.DetermineLocationStatusCode) + return ret0, ret1, ret2, ret3 } -// StatusCode allows to set the status code of the mock -func (l *LocationMock) StatusCode(statusCode fivegc.StatusCode) { - l.statusCode = statusCode +// DetermineLocation indicates an expected call of DetermineLocation. +func (mr *MockLocationMockRecorder) DetermineLocation(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetermineLocation", reflect.TypeOf((*MockLocation)(nil).DetermineLocation), arg0, arg1) } -// Error returns the problem details of the mock -func (l *LocationMock) Error(ctx context.Context, err error) openapinlmflocation.ProblemDetails { - return l.problemDetails +// Error mocks base method. +func (m *MockLocation) Error(ctx context.Context, err error) openapi_CommonData.ProblemDetails { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Error", ctx, err) + ret0, _ := ret[0].(openapi_CommonData.ProblemDetails) + return ret0 } -// CancelLocation returns the problem details, the redirect response and the status code of the mock -func (l *LocationMock) CancelLocation(ctx context.Context, data openapinlmflocation.CancelLocData) (openapinlmflocation.ProblemDetails, fivegc.RedirectResponse, fivegc.StatusCode) { - return l.problemDetails, l.redirectResponse, l.statusCode +// Error indicates an expected call of Error. +func (mr *MockLocationMockRecorder) Error(ctx, err interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLocation)(nil).Error), ctx, err) } -// DetermineLocation returns the location data, the problem details, the redirect response and the status code of the mock -func (l *LocationMock) DetermineLocation(ctx context.Context, data openapinlmflocation.InputData) (openapinlmflocation.LocationData, openapinlmflocation.ProblemDetails, fivegc.RedirectResponse, fivegc.StatusCode) { - return l.locationData, l.problemDetails, l.redirectResponse, l.statusCode +// LocationContextTransfer mocks base method. +func (m *MockLocation) LocationContextTransfer(arg0 context.Context, arg1 openapi_Nlmf_Location.LocContextData) (openapi_CommonData.ProblemDetails, fivegc.RedirectResponse, nlmf.LocationContextTransferStatusCode) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocationContextTransfer", arg0, arg1) + ret0, _ := ret[0].(openapi_CommonData.ProblemDetails) + ret1, _ := ret[1].(fivegc.RedirectResponse) + ret2, _ := ret[2].(nlmf.LocationContextTransferStatusCode) + return ret0, ret1, ret2 } -// LocationContextTransfer returns the problem details, the redirect response and the status code of the mock -func (l *LocationMock) LocationContextTransfer(ctx context.Context, data openapinlmflocation.LocContextData) (openapinlmflocation.ProblemDetails, fivegc.RedirectResponse, fivegc.StatusCode) { - return l.problemDetails, l.redirectResponse, l.statusCode +// LocationContextTransfer indicates an expected call of LocationContextTransfer. +func (mr *MockLocationMockRecorder) LocationContextTransfer(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocationContextTransfer", reflect.TypeOf((*MockLocation)(nil).LocationContextTransfer), arg0, arg1) } diff --git a/fivegc/nlmf/nlmf.go b/fivegc/nlmf/server.go similarity index 73% rename from fivegc/nlmf/nlmf.go rename to fivegc/nlmf/server.go index 4d30e37..8efc843 100644 --- a/fivegc/nlmf/nlmf.go +++ b/fivegc/nlmf/server.go @@ -1,15 +1,13 @@ package nlmf import ( - "fmt" "github.com/gin-gonic/gin" "log" ) // Server represents a NLMF server. type Server struct { - ip string - port string + address string // IP:PORT apiRoot string location Location broadcast Broadcast @@ -19,22 +17,22 @@ type Server struct { } // NewServer creates a new Server NLMF server instance. -func NewServer(ip string, port string, apiRoot string, logger *log.Logger) *Server { +// The address is the IP:PORT of the NLMF server. +func NewServer(address string, apiRoot string, logger *log.Logger) *Server { return &Server{ - ip: ip, - port: port, + address: address, apiRoot: apiRoot, logger: logger, stop: make(chan bool), } } -// AttachLocation attaches a Location client to the NLMF Server. +// AttachLocation attaches a Location handler to the NLMF Server. func (n *Server) AttachLocation(l Location) { n.location = l } -// AttachBroadcast attaches a Broadcast client to the NLMF Server. +// AttachBroadcast attaches a Broadcast handler to the NLMF Server. func (n *Server) AttachBroadcast(b Broadcast) { n.broadcast = b } @@ -51,7 +49,7 @@ func (n *Server) Start() { if n.broadcast != nil { attachBroadcastHandler(root, n.broadcast) } - go n.router.Run(fmt.Sprintf("%s:%s", n.ip, n.port)) + go n.router.Run(n.address) <-n.stop return } diff --git a/fivegc/redirect.go b/fivegc/redirect.go index c1cb29e..f779734 100644 --- a/fivegc/redirect.go +++ b/fivegc/redirect.go @@ -12,9 +12,9 @@ type RedirectResponse struct { Cause string `json:"cause,omitempty"` - // String providing an URI formatted according to RFC 3986. + // String providing a URI formatted according to RFC 3986. TargetScp string `json:"targetScp,omitempty"` - // String providing an URI formatted according to RFC 3986. + // String providing a URI formatted according to RFC 3986. TargetSepp string `json:"targetSepp,omitempty"` } diff --git a/fivegc/server.go b/fivegc/server.go new file mode 100644 index 0000000..73bcf68 --- /dev/null +++ b/fivegc/server.go @@ -0,0 +1,11 @@ +package fivegc + +import ( + "context" + openapicommon "github.com/5GCoreNet/openapi/openapi_CommonData" +) + +type CommonInterface interface { + // Error returns a problem details, it is used to handle errors when unmarshalling the request. + Error(ctx context.Context, err error) openapicommon.ProblemDetails +} diff --git a/fivegc/status.go b/fivegc/status.go index 706e796..419afa5 100644 --- a/fivegc/status.go +++ b/fivegc/status.go @@ -1,5 +1,7 @@ package fivegc +// Note that this file is a port of the file status.go from Go standard library. + // StatusCode represents the status code of the HTTP response. type StatusCode uint16 diff --git a/go.mod b/go.mod index 9bcd103..1a55bd3 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/5GCoreNet/5GCoreNetSDK -go 1.18 +go 1.20 require ( - github.com/5GCoreNet/client-openapi v0.0.0-20230128134546-a505b74025b3 - github.com/5GCoreNet/server-openapi v0.0.0-20230126185629-9202d02e7eea + github.com/5GCoreNet/openapi v1.18.2 github.com/gin-gonic/gin v1.8.2 + github.com/golang/mock v1.6.0 ) require ( @@ -23,10 +23,10 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/ugorji/go/codec v1.2.7 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/net v0.5.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..584c2f5 --- /dev/null +++ b/go.sum @@ -0,0 +1,128 @@ +github.com/5GCoreNet/openapi v1.18.2 h1:4HRDOTapVY+Xr49KmiS7R4TAuwMEpNwg6Z+oiXM0mnY= +github.com/5GCoreNet/openapi v1.18.2/go.mod h1:10RqDFgIY+AgfRk626E0+3e9NbooVSvE/SsZs4mQa1w= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/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.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY= +github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398= +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.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/header/header_test.go b/internal/header/header_test.go new file mode 100644 index 0000000..b07bbd9 --- /dev/null +++ b/internal/header/header_test.go @@ -0,0 +1,24 @@ +package header + +import ( + "github.com/5GCoreNet/5GCoreNetSDK/fivegc" + "github.com/gin-gonic/gin" + "net/http/httptest" + "testing" +) + +func TestBindRedirectHeader(t *testing.T) { + ginContext, _ := gin.CreateTestContext(&httptest.ResponseRecorder{}) + BindRedirectHeader(ginContext, fivegc.RedirectHeader{ + Location: "http://localhost:8080", + SbiTarget: "1234", + }) + + if ginContext.Writer.Header().Get("Location") != "http://localhost:8080" { + t.Errorf("Location header not set") + } + + if ginContext.Writer.Header().Get("3gpp-Sbi-Target-Nf-Id") != "1234" { + t.Errorf("3gpp-Sbi-Target-Nf-Id header not set") + } +}