mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
Add Run endpoint
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
This commit is contained in:
@@ -52,6 +52,23 @@ curl -i -X POST -H "Content-Type: application/json" localhost:9022/mfxkit -d '{"
|
||||
curl -i -X POST -H "Content-Type: application/json" localhost:9021/domain -d '{"pool":"/home/darko/go/src/github.com/ultravioletrs/manager/cmd/manager/xml/pool.xml", "volume":"/home/darko/go/src/github.com/ultravioletrs/manager/cmd/manager/xml/vol.xml", "domain":"/home/darko/go/src/github.com/ultravioletrs/manager/cmd/manager/xml/dom.xml"}'
|
||||
```
|
||||
|
||||
```sh
|
||||
curl -X POST \
|
||||
http://localhost:9021/run \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"name": "my-run",
|
||||
"description": "this is a test run",
|
||||
"owner": "John Doe",
|
||||
"datasets": ["dataset1", "dataset2"],
|
||||
"algorithms": ["algorithm1", "algorithm2"],
|
||||
"dataset_providers": ["provider1", "provider2"],
|
||||
"algorithm_providers": ["provider3", "provider4"],
|
||||
"result_consumers": ["consumer1", "consumer2"],
|
||||
"ttl": 3600
|
||||
}'
|
||||
```
|
||||
|
||||
## Virsh
|
||||
|
||||
```sh
|
||||
|
||||
+10
-8
@@ -19,19 +19,19 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/pkg/uuid"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
jconfig "github.com/uber/jaeger-client-go/config"
|
||||
"github.com/ultravioletrs/manager/manager"
|
||||
"github.com/ultravioletrs/manager/manager/api"
|
||||
managergrpc "github.com/ultravioletrs/manager/manager/api/manager/grpc"
|
||||
managerhttpapi "github.com/ultravioletrs/manager/manager/api/manager/http"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
jconfig "github.com/uber/jaeger-client-go/config"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
)
|
||||
|
||||
@@ -79,7 +79,9 @@ func main() {
|
||||
libvirtConn := initLibvirt(logger)
|
||||
defer libvirtConn.Disconnect()
|
||||
|
||||
svc := newService(cfg.secret, libvirtConn, logger)
|
||||
idProvider := uuid.New()
|
||||
|
||||
svc := newService(cfg.secret, libvirtConn, idProvider, logger)
|
||||
|
||||
errs := make(chan error, 2)
|
||||
go startgRPCServer(cfg, &svc, logger, errs)
|
||||
@@ -132,8 +134,8 @@ func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer,
|
||||
return tracer, closer
|
||||
}
|
||||
|
||||
func newService(secret string, libvirtConn *libvirt.Libvirt, logger logger.Logger) manager.Service {
|
||||
svc := manager.New(secret, libvirtConn)
|
||||
func newService(secret string, libvirtConn *libvirt.Libvirt, idp mainflux.IDProvider, logger logger.Logger) manager.Service {
|
||||
svc := manager.New(secret, libvirtConn, idp)
|
||||
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
|
||||
@@ -20,6 +20,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/go-kit/log v0.2.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
|
||||
@@ -80,6 +80,8 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-zoo/bone v1.3.0 h1:PY6sHq37FnQhj+4ZyqFIzJQHvrrGx0GEc3vTZZC/OsI=
|
||||
github.com/go-zoo/bone v1.3.0/go.mod h1:HI3Lhb7G3UQcAwEhOJ2WyNcsFtQX1WYHa0Hl4OBbhW8=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
||||
@@ -39,3 +39,16 @@ func (lm *loggingMiddleware) CreateDomain(pool, volume, domain string) (response
|
||||
|
||||
return lm.svc.CreateDomain(pool, volume, domain)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Run(comp manager.Computation) (id string, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method Run for computation %v took %s to complete", comp, time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s with ID: %s", message, id))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Run(comp)
|
||||
}
|
||||
|
||||
@@ -29,3 +29,41 @@ func createDomainEndpoint(svc manager.Service) endpoint.Endpoint {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
func runEndpoint(svc manager.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(runReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the computation
|
||||
computation := manager.Computation{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Status: "",
|
||||
Owner: req.Owner,
|
||||
Datasets: req.Datasets,
|
||||
Algorithms: req.Algorithms,
|
||||
DatasetProviders: req.DatasetProviders,
|
||||
AlgorithmProviders: req.AlgorithmProviders,
|
||||
ResultConsumers: req.ResultConsumers,
|
||||
TTL: req.TTL,
|
||||
StartTime: nil,
|
||||
EndTime: nil,
|
||||
}
|
||||
|
||||
// Call the Run method on the service
|
||||
runID, err := svc.Run(computation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the response
|
||||
res := runRes{
|
||||
ID: runID,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,3 +22,25 @@ func (req createDomainReq) validate() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ apiReq = (*runReq)(nil)
|
||||
|
||||
type runReq struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Owner string `json:"owner"`
|
||||
Datasets []string `json:"datasets"`
|
||||
Algorithms []string `json:"algorithms"`
|
||||
DatasetProviders []string `json:"dataset_providers"`
|
||||
AlgorithmProviders []string `json:"algorithm_providers"`
|
||||
ResultConsumers []string `json:"result_consumers"`
|
||||
TTL int32 `json:"ttl"`
|
||||
}
|
||||
|
||||
func (req runReq) validate() error {
|
||||
if req.Name == "" || req.Owner == "" || len(req.Datasets) == 0 || len(req.Algorithms) == 0 {
|
||||
return manager.ErrMalformedEntity
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,3 +26,21 @@ func (res createDomainRes) Headers() map[string]string {
|
||||
func (res createDomainRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var _ mainflux.Response = (*runRes)(nil)
|
||||
|
||||
type runRes struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (res runRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res runRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res runRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -45,6 +45,13 @@ func MakeHandler(tracer opentracing.Tracer, svc manager.Service) http.Handler {
|
||||
opts...,
|
||||
))
|
||||
|
||||
r.Post("/run", kithttp.NewServer(
|
||||
kitot.TraceServer(tracer, "run")(runEndpoint(svc)),
|
||||
decodeRun,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
))
|
||||
|
||||
r.GetFunc("/health", mainflux.Health("manager"))
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
@@ -64,6 +71,19 @@ func decodeCreateDomain(_ context.Context, r *http.Request) (interface{}, error)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeRun(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, errUnsupportedContentType
|
||||
}
|
||||
|
||||
var req runReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
|
||||
@@ -39,3 +39,12 @@ func (ms *metricsMiddleware) CreateDomain(pool, volume, domain string) (response
|
||||
|
||||
return ms.svc.CreateDomain(pool, volume, domain)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Run(comp manager.Computation) (string, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "Run").Add(1)
|
||||
ms.latency.With("method", "Run").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Run(comp)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package manager
|
||||
|
||||
import "github.com/golang/protobuf/ptypes/timestamp"
|
||||
|
||||
type Computation struct {
|
||||
ID string
|
||||
Name string
|
||||
Description string
|
||||
Status string
|
||||
Owner string
|
||||
Datasets []string
|
||||
Algorithms []string
|
||||
DatasetProviders []string
|
||||
AlgorithmProviders []string
|
||||
ResultConsumers []string
|
||||
TTL int32
|
||||
StartTime *timestamp.Timestamp
|
||||
EndTime *timestamp.Timestamp
|
||||
}
|
||||
+36
-7
@@ -8,6 +8,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -33,24 +35,27 @@ var (
|
||||
// implementation, and all of its decorators (e.g. logging & metrics).
|
||||
type Service interface {
|
||||
CreateDomain(pool, volume, domain string) (string, error)
|
||||
Run(comp Computation) (string, error)
|
||||
}
|
||||
|
||||
type managerService struct {
|
||||
secret string
|
||||
libvirt *libvirt.Libvirt
|
||||
secret string
|
||||
libvirt *libvirt.Libvirt
|
||||
idProvider mainflux.IDProvider
|
||||
}
|
||||
|
||||
var _ Service = (*managerService)(nil)
|
||||
|
||||
// New instantiates the manager service implementation.
|
||||
func New(secret string, libvirtConn *libvirt.Libvirt) Service {
|
||||
func New(secret string, libvirtConn *libvirt.Libvirt, idp mainflux.IDProvider) Service {
|
||||
return &managerService{
|
||||
secret: secret,
|
||||
libvirt: libvirtConn,
|
||||
secret: secret,
|
||||
libvirt: libvirtConn,
|
||||
idProvider: idp,
|
||||
}
|
||||
}
|
||||
|
||||
func (ks *managerService) CreateDomain(poolXML, volXML, domXML string) (string, error) {
|
||||
func (ms *managerService) CreateDomain(poolXML, volXML, domXML string) (string, error) {
|
||||
poolBytes, err := os.ReadFile(poolXML)
|
||||
if err != nil {
|
||||
return "", ErrNotFound
|
||||
@@ -69,10 +74,34 @@ func (ks *managerService) CreateDomain(poolXML, volXML, domXML string) (string,
|
||||
}
|
||||
domStr := string(domBytes)
|
||||
|
||||
dom, err := createDomain(ks.libvirt, poolStr, volStr, domStr)
|
||||
dom, err := createDomain(ms.libvirt, poolStr, volStr, domStr)
|
||||
if err != nil {
|
||||
return "", ErrMalformedEntity
|
||||
}
|
||||
|
||||
return dom.Name, nil
|
||||
}
|
||||
|
||||
func (ms *managerService) Run(comp Computation) (string, error) {
|
||||
// Generate a unique ID for the computation
|
||||
runID, err := ms.idProvider.ID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Initialize the Computation object
|
||||
comp.ID = runID
|
||||
comp.Status = ""
|
||||
comp.StartTime = ×tamp.Timestamp{}
|
||||
comp.EndTime = ×tamp.Timestamp{}
|
||||
|
||||
// // Save the Computation object to the database
|
||||
// if err := ms.db.SaveComputation(comp); err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
// // Start the computation process
|
||||
// go ms.processComputation(comp)
|
||||
|
||||
return runID, nil
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,6 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
// import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
package manager_proto;
|
||||
|
||||
@@ -15,20 +15,20 @@ service ManagerService {
|
||||
// message HealthRequest {}
|
||||
// message HealthResponse { string status = 1; }
|
||||
|
||||
// message RunRequest {
|
||||
// string name = 2;
|
||||
// string description = 3;
|
||||
// string status = 4;
|
||||
// string owner = 5;
|
||||
// google.protobuf.Timestamp start_time = 6;
|
||||
// google.protobuf.Timestamp end_time = 7;
|
||||
// repeated string datasets = 8;
|
||||
// repeated string algorithms = 9;
|
||||
// repeated string dataset_providers = 10;
|
||||
// repeated string algorithm_providers = 11;
|
||||
// repeated string result_consumers = 13;
|
||||
// int32 ttl = 12;
|
||||
// }
|
||||
message RunRequest {
|
||||
string name = 2;
|
||||
string description = 3;
|
||||
string status = 4;
|
||||
string owner = 5;
|
||||
google.protobuf.Timestamp start_time = 6;
|
||||
google.protobuf.Timestamp end_time = 7;
|
||||
repeated string datasets = 8;
|
||||
repeated string algorithms = 9;
|
||||
repeated string dataset_providers = 10;
|
||||
repeated string algorithm_providers = 11;
|
||||
repeated string result_consumers = 13;
|
||||
int32 ttl = 12;
|
||||
}
|
||||
|
||||
// message RunResponse { string message = 1; }
|
||||
|
||||
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# binary bundle generated by go-fuzz
|
||||
uuid-fuzz.zip
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
|
||||
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.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# UUID
|
||||
|
||||
[](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
||||
[](https://travis-ci.org/gofrs/uuid)
|
||||
[](http://godoc.org/github.com/gofrs/uuid)
|
||||
[](https://codecov.io/gh/gofrs/uuid/)
|
||||
[](https://goreportcard.com/report/github.com/gofrs/uuid)
|
||||
|
||||
Package uuid provides a pure Go implementation of Universally Unique Identifiers
|
||||
(UUID) variant as defined in RFC-4122. This package supports both the creation
|
||||
and parsing of UUIDs in different formats.
|
||||
|
||||
This package supports the following UUID versions:
|
||||
* Version 1, based on timestamp and MAC address (RFC-4122)
|
||||
* Version 3, based on MD5 hashing of a named value (RFC-4122)
|
||||
* Version 4, based on random numbers (RFC-4122)
|
||||
* Version 5, based on SHA-1 hashing of a named value (RFC-4122)
|
||||
|
||||
## Project History
|
||||
|
||||
This project was originally forked from the
|
||||
[github.com/satori/go.uuid](https://github.com/satori/go.uuid) repository after
|
||||
it appeared to be no longer maintained, while exhibiting [critical
|
||||
flaws](https://github.com/satori/go.uuid/issues/73). We have decided to take
|
||||
over this project to ensure it receives regular maintenance for the benefit of
|
||||
the larger Go community.
|
||||
|
||||
We'd like to thank Maxim Bublis for his hard work on the original iteration of
|
||||
the package.
|
||||
|
||||
## License
|
||||
|
||||
This source code of this package is released under the MIT License. Please see
|
||||
the [LICENSE](https://github.com/gofrs/uuid/blob/master/LICENSE) for the full
|
||||
content of the license.
|
||||
|
||||
## Recommended Package Version
|
||||
|
||||
We recommend using v2.0.0+ of this package, as versions prior to 2.0.0 were
|
||||
created before our fork of the original package and have some known
|
||||
deficiencies.
|
||||
|
||||
## Installation
|
||||
|
||||
It is recommended to use a package manager like `dep` that understands tagged
|
||||
releases of a package, as well as semantic versioning.
|
||||
|
||||
If you are unable to make use of a dependency manager with your project, you can
|
||||
use the `go get` command to download it directly:
|
||||
|
||||
```Shell
|
||||
$ go get github.com/gofrs/uuid
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
Due to subtests not being supported in older versions of Go, this package is
|
||||
only regularly tested against Go 1.7+. This package may work perfectly fine with
|
||||
Go 1.2+, but support for these older versions is not actively maintained.
|
||||
|
||||
## Go 1.11 Modules
|
||||
|
||||
As of v3.2.0, this repository no longer adopts Go modules, and v3.2.0 no longer has a `go.mod` file. As a result, v3.2.0 also drops support for the `github.com/gofrs/uuid/v3` import path. Only module-based consumers are impacted. With the v3.2.0 release, _all_ gofrs/uuid consumers should use the `github.com/gofrs/uuid` import path.
|
||||
|
||||
An existing module-based consumer will continue to be able to build using the `github.com/gofrs/uuid/v3` import path using any valid consumer `go.mod` that worked prior to the publishing of v3.2.0, but any module-based consumer should start using the `github.com/gofrs/uuid` import path when possible and _must_ use the `github.com/gofrs/uuid` import path prior to upgrading to v3.2.0.
|
||||
|
||||
Please refer to [Issue #61](https://github.com/gofrs/uuid/issues/61) and [Issue #66](https://github.com/gofrs/uuid/issues/66) for more details.
|
||||
|
||||
## Usage
|
||||
|
||||
Here is a quick overview of how to use this package. For more detailed
|
||||
documentation, please see the [GoDoc Page](http://godoc.org/github.com/gofrs/uuid).
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// Create a Version 4 UUID, panicking on error.
|
||||
// Use this form to initialize package-level variables.
|
||||
var u1 = uuid.Must(uuid.NewV4())
|
||||
|
||||
func main() {
|
||||
// Create a Version 4 UUID.
|
||||
u2, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to generate UUID: %v", err)
|
||||
}
|
||||
log.Printf("generated Version 4 UUID %v", u2)
|
||||
|
||||
// Parse a UUID from a string.
|
||||
s := "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
||||
u3, err := uuid.FromString(s)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse UUID %q: %v", s, err)
|
||||
}
|
||||
log.Printf("successfully parsed UUID %v", u3)
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
* [RFC-4122](https://tools.ietf.org/html/rfc4122)
|
||||
* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01)
|
||||
* [New UUID Formats RFC Draft (Peabody) Rev 02](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02)
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
//
|
||||
// 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.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// FromBytes returns a UUID generated from the raw byte slice input.
|
||||
// It will return an error if the slice isn't 16 bytes long.
|
||||
func FromBytes(input []byte) (UUID, error) {
|
||||
u := UUID{}
|
||||
err := u.UnmarshalBinary(input)
|
||||
return u, err
|
||||
}
|
||||
|
||||
// FromBytesOrNil returns a UUID generated from the raw byte slice input.
|
||||
// Same behavior as FromBytes(), but returns uuid.Nil instead of an error.
|
||||
func FromBytesOrNil(input []byte) UUID {
|
||||
uuid, err := FromBytes(input)
|
||||
if err != nil {
|
||||
return Nil
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromString returns a UUID parsed from the input string.
|
||||
// Input is expected in a form accepted by UnmarshalText.
|
||||
func FromString(input string) (UUID, error) {
|
||||
u := UUID{}
|
||||
err := u.UnmarshalText([]byte(input))
|
||||
return u, err
|
||||
}
|
||||
|
||||
// FromStringOrNil returns a UUID parsed from the input string.
|
||||
// Same behavior as FromString(), but returns uuid.Nil instead of an error.
|
||||
func FromStringOrNil(input string) UUID {
|
||||
uuid, err := FromString(input)
|
||||
if err != nil {
|
||||
return Nil
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
// The encoding is the same as returned by the String() method.
|
||||
func (u UUID) MarshalText() ([]byte, error) {
|
||||
return []byte(u.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
// Following formats are supported:
|
||||
//
|
||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
||||
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
|
||||
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
||||
// "6ba7b8109dad11d180b400c04fd430c8"
|
||||
// "{6ba7b8109dad11d180b400c04fd430c8}",
|
||||
// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8"
|
||||
//
|
||||
// ABNF for supported UUID text representation follows:
|
||||
//
|
||||
// URN := 'urn'
|
||||
// UUID-NID := 'uuid'
|
||||
//
|
||||
// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
|
||||
// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' |
|
||||
// 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
|
||||
//
|
||||
// hexoct := hexdig hexdig
|
||||
// 2hexoct := hexoct hexoct
|
||||
// 4hexoct := 2hexoct 2hexoct
|
||||
// 6hexoct := 4hexoct 2hexoct
|
||||
// 12hexoct := 6hexoct 6hexoct
|
||||
//
|
||||
// hashlike := 12hexoct
|
||||
// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct
|
||||
//
|
||||
// plain := canonical | hashlike
|
||||
// uuid := canonical | hashlike | braced | urn
|
||||
//
|
||||
// braced := '{' plain '}' | '{' hashlike '}'
|
||||
// urn := URN ':' UUID-NID ':' plain
|
||||
//
|
||||
func (u *UUID) UnmarshalText(text []byte) error {
|
||||
switch len(text) {
|
||||
case 32:
|
||||
return u.decodeHashLike(text)
|
||||
case 34, 38:
|
||||
return u.decodeBraced(text)
|
||||
case 36:
|
||||
return u.decodeCanonical(text)
|
||||
case 41, 45:
|
||||
return u.decodeURN(text)
|
||||
default:
|
||||
return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(text), text)
|
||||
}
|
||||
}
|
||||
|
||||
// decodeCanonical decodes UUID strings that are formatted as defined in RFC-4122 (section 3):
|
||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8".
|
||||
func (u *UUID) decodeCanonical(t []byte) error {
|
||||
if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' {
|
||||
return fmt.Errorf("uuid: incorrect UUID format in string %q", t)
|
||||
}
|
||||
|
||||
src := t
|
||||
dst := u[:]
|
||||
|
||||
for i, byteGroup := range byteGroups {
|
||||
if i > 0 {
|
||||
src = src[1:] // skip dash
|
||||
}
|
||||
_, err := hex.Decode(dst[:byteGroup/2], src[:byteGroup])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src = src[byteGroup:]
|
||||
dst = dst[byteGroup/2:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeHashLike decodes UUID strings that are using the following format:
|
||||
// "6ba7b8109dad11d180b400c04fd430c8".
|
||||
func (u *UUID) decodeHashLike(t []byte) error {
|
||||
src := t[:]
|
||||
dst := u[:]
|
||||
|
||||
_, err := hex.Decode(dst, src)
|
||||
return err
|
||||
}
|
||||
|
||||
// decodeBraced decodes UUID strings that are using the following formats:
|
||||
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}"
|
||||
// "{6ba7b8109dad11d180b400c04fd430c8}".
|
||||
func (u *UUID) decodeBraced(t []byte) error {
|
||||
l := len(t)
|
||||
|
||||
if t[0] != '{' || t[l-1] != '}' {
|
||||
return fmt.Errorf("uuid: incorrect UUID format in string %q", t)
|
||||
}
|
||||
|
||||
return u.decodePlain(t[1 : l-1])
|
||||
}
|
||||
|
||||
// decodeURN decodes UUID strings that are using the following formats:
|
||||
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
||||
// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8".
|
||||
func (u *UUID) decodeURN(t []byte) error {
|
||||
total := len(t)
|
||||
|
||||
urnUUIDPrefix := t[:9]
|
||||
|
||||
if !bytes.Equal(urnUUIDPrefix, urnPrefix) {
|
||||
return fmt.Errorf("uuid: incorrect UUID format in string %q", t)
|
||||
}
|
||||
|
||||
return u.decodePlain(t[9:total])
|
||||
}
|
||||
|
||||
// decodePlain decodes UUID strings that are using the following formats:
|
||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format
|
||||
// "6ba7b8109dad11d180b400c04fd430c8".
|
||||
func (u *UUID) decodePlain(t []byte) error {
|
||||
switch len(t) {
|
||||
case 32:
|
||||
return u.decodeHashLike(t)
|
||||
case 36:
|
||||
return u.decodeCanonical(t)
|
||||
default:
|
||||
return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(t), t)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
||||
func (u UUID) MarshalBinary() ([]byte, error) {
|
||||
return u.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
||||
// It will return an error if the slice isn't 16 bytes long.
|
||||
func (u *UUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != Size {
|
||||
return fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
|
||||
}
|
||||
copy(u[:], data)
|
||||
|
||||
return nil
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2018 Andrei Tudor Călin <mail@acln.ro>
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build gofuzz
|
||||
|
||||
package uuid
|
||||
|
||||
// Fuzz implements a simple fuzz test for FromString / UnmarshalText.
|
||||
//
|
||||
// To run:
|
||||
//
|
||||
// $ go get github.com/dvyukov/go-fuzz/...
|
||||
// $ cd $GOPATH/src/github.com/gofrs/uuid
|
||||
// $ go-fuzz-build github.com/gofrs/uuid
|
||||
// $ go-fuzz -bin=uuid-fuzz.zip -workdir=./testdata
|
||||
//
|
||||
// If you make significant changes to FromString / UnmarshalText and add
|
||||
// new cases to fromStringTests (in codec_test.go), please run
|
||||
//
|
||||
// $ go test -seed_fuzz_corpus
|
||||
//
|
||||
// to seed the corpus with the new interesting inputs, then run the fuzzer.
|
||||
func Fuzz(data []byte) int {
|
||||
_, err := FromString(string(data))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
+573
@@ -0,0 +1,573 @@
|
||||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
//
|
||||
// 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.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Difference in 100-nanosecond intervals between
|
||||
// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
|
||||
const epochStart = 122192928000000000
|
||||
|
||||
type epochFunc func() time.Time
|
||||
|
||||
// HWAddrFunc is the function type used to provide hardware (MAC) addresses.
|
||||
type HWAddrFunc func() (net.HardwareAddr, error)
|
||||
|
||||
// DefaultGenerator is the default UUID Generator used by this package.
|
||||
var DefaultGenerator Generator = NewGen()
|
||||
|
||||
// NewV1 returns a UUID based on the current timestamp and MAC address.
|
||||
func NewV1() (UUID, error) {
|
||||
return DefaultGenerator.NewV1()
|
||||
}
|
||||
|
||||
// NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name.
|
||||
func NewV3(ns UUID, name string) UUID {
|
||||
return DefaultGenerator.NewV3(ns, name)
|
||||
}
|
||||
|
||||
// NewV4 returns a randomly generated UUID.
|
||||
func NewV4() (UUID, error) {
|
||||
return DefaultGenerator.NewV4()
|
||||
}
|
||||
|
||||
// NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name.
|
||||
func NewV5(ns UUID, name string) UUID {
|
||||
return DefaultGenerator.NewV5(ns, name)
|
||||
}
|
||||
|
||||
// NewV6 returns a k-sortable UUID based on a timestamp and 48 bits of
|
||||
// pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit
|
||||
// order being adjusted to allow the UUID to be k-sortable.
|
||||
//
|
||||
// This is implemented based on revision 02 of the Peabody UUID draft, and may
|
||||
// be subject to change pending further revisions. Until the final specification
|
||||
// revision is finished, changes required to implement updates to the spec will
|
||||
// not be considered a breaking change. They will happen as a minor version
|
||||
// releases until the spec is final.
|
||||
func NewV6() (UUID, error) {
|
||||
return DefaultGenerator.NewV6()
|
||||
}
|
||||
|
||||
// NewV7 returns a k-sortable UUID based on the current UNIX epoch, with the
|
||||
// ability to configure the timestamp's precision from millisecond all the way
|
||||
// to nanosecond. The additional precision is supported by reducing the amount
|
||||
// of pseudorandom data that makes up the rest of the UUID.
|
||||
//
|
||||
// If an unknown Precision argument is passed to this method it will panic. As
|
||||
// such it's strongly encouraged to use the package-provided constants for this
|
||||
// value.
|
||||
//
|
||||
// This is implemented based on revision 02 of the Peabody UUID draft, and may
|
||||
// be subject to change pending further revisions. Until the final specification
|
||||
// revision is finished, changes required to implement updates to the spec will
|
||||
// not be considered a breaking change. They will happen as a minor version
|
||||
// releases until the spec is final.
|
||||
func NewV7(p Precision) (UUID, error) {
|
||||
return DefaultGenerator.NewV7(p)
|
||||
}
|
||||
|
||||
// Generator provides an interface for generating UUIDs.
|
||||
type Generator interface {
|
||||
NewV1() (UUID, error)
|
||||
NewV3(ns UUID, name string) UUID
|
||||
NewV4() (UUID, error)
|
||||
NewV5(ns UUID, name string) UUID
|
||||
NewV6() (UUID, error)
|
||||
NewV7(Precision) (UUID, error)
|
||||
}
|
||||
|
||||
// Gen is a reference UUID generator based on the specifications laid out in
|
||||
// RFC-4122 and DCE 1.1: Authentication and Security Services. This type
|
||||
// satisfies the Generator interface as defined in this package.
|
||||
//
|
||||
// For consumers who are generating V1 UUIDs, but don't want to expose the MAC
|
||||
// address of the node generating the UUIDs, the NewGenWithHWAF() function has been
|
||||
// provided as a convenience. See the function's documentation for more info.
|
||||
//
|
||||
// The authors of this package do not feel that the majority of users will need
|
||||
// to obfuscate their MAC address, and so we recommend using NewGen() to create
|
||||
// a new generator.
|
||||
type Gen struct {
|
||||
clockSequenceOnce sync.Once
|
||||
hardwareAddrOnce sync.Once
|
||||
storageMutex sync.Mutex
|
||||
|
||||
rand io.Reader
|
||||
|
||||
epochFunc epochFunc
|
||||
hwAddrFunc HWAddrFunc
|
||||
lastTime uint64
|
||||
clockSequence uint16
|
||||
hardwareAddr [6]byte
|
||||
|
||||
v7LastTime uint64
|
||||
v7LastSubsec uint64
|
||||
v7ClockSequence uint16
|
||||
}
|
||||
|
||||
// interface check -- build will fail if *Gen doesn't satisfy Generator
|
||||
var _ Generator = (*Gen)(nil)
|
||||
|
||||
// NewGen returns a new instance of Gen with some default values set. Most
|
||||
// people should use this.
|
||||
func NewGen() *Gen {
|
||||
return NewGenWithHWAF(defaultHWAddrFunc)
|
||||
}
|
||||
|
||||
// NewGenWithHWAF builds a new UUID generator with the HWAddrFunc provided. Most
|
||||
// consumers should use NewGen() instead.
|
||||
//
|
||||
// This is used so that consumers can generate their own MAC addresses, for use
|
||||
// in the generated UUIDs, if there is some concern about exposing the physical
|
||||
// address of the machine generating the UUID.
|
||||
//
|
||||
// The Gen generator will only invoke the HWAddrFunc once, and cache that MAC
|
||||
// address for all the future UUIDs generated by it. If you'd like to switch the
|
||||
// MAC address being used, you'll need to create a new generator using this
|
||||
// function.
|
||||
func NewGenWithHWAF(hwaf HWAddrFunc) *Gen {
|
||||
return &Gen{
|
||||
epochFunc: time.Now,
|
||||
hwAddrFunc: hwaf,
|
||||
rand: rand.Reader,
|
||||
}
|
||||
}
|
||||
|
||||
// NewV1 returns a UUID based on the current timestamp and MAC address.
|
||||
func (g *Gen) NewV1() (UUID, error) {
|
||||
u := UUID{}
|
||||
|
||||
timeNow, clockSeq, err := g.getClockSequence()
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
|
||||
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
|
||||
binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
|
||||
binary.BigEndian.PutUint16(u[8:], clockSeq)
|
||||
|
||||
hardwareAddr, err := g.getHardwareAddr()
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
copy(u[10:], hardwareAddr)
|
||||
|
||||
u.SetVersion(V1)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// NewV3 returns a UUID based on the MD5 hash of the namespace UUID and name.
|
||||
func (g *Gen) NewV3(ns UUID, name string) UUID {
|
||||
u := newFromHash(md5.New(), ns, name)
|
||||
u.SetVersion(V3)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// NewV4 returns a randomly generated UUID.
|
||||
func (g *Gen) NewV4() (UUID, error) {
|
||||
u := UUID{}
|
||||
if _, err := io.ReadFull(g.rand, u[:]); err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
u.SetVersion(V4)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// NewV5 returns a UUID based on SHA-1 hash of the namespace UUID and name.
|
||||
func (g *Gen) NewV5(ns UUID, name string) UUID {
|
||||
u := newFromHash(sha1.New(), ns, name)
|
||||
u.SetVersion(V5)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// NewV6 returns a k-sortable UUID based on a timestamp and 48 bits of
|
||||
// pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit
|
||||
// order being adjusted to allow the UUID to be k-sortable.
|
||||
//
|
||||
// This is implemented based on revision 02 of the Peabody UUID draft, and may
|
||||
// be subject to change pending further revisions. Until the final specification
|
||||
// revision is finished, changes required to implement updates to the spec will
|
||||
// not be considered a breaking change. They will happen as a minor version
|
||||
// releases until the spec is final.
|
||||
func (g *Gen) NewV6() (UUID, error) {
|
||||
var u UUID
|
||||
|
||||
if _, err := io.ReadFull(g.rand, u[10:]); err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
timeNow, clockSeq, err := g.getClockSequence()
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(u[0:], uint32(timeNow>>28)) // set time_high
|
||||
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>12)) // set time_mid
|
||||
binary.BigEndian.PutUint16(u[6:], uint16(timeNow&0xfff)) // set time_low (minus four version bits)
|
||||
binary.BigEndian.PutUint16(u[8:], clockSeq&0x3fff) // set clk_seq_hi_res (minus two variant bits)
|
||||
|
||||
u.SetVersion(V6)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// getClockSequence returns the epoch and clock sequence for V1 and V6 UUIDs.
|
||||
func (g *Gen) getClockSequence() (uint64, uint16, error) {
|
||||
var err error
|
||||
g.clockSequenceOnce.Do(func() {
|
||||
buf := make([]byte, 2)
|
||||
if _, err = io.ReadFull(g.rand, buf); err != nil {
|
||||
return
|
||||
}
|
||||
g.clockSequence = binary.BigEndian.Uint16(buf)
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
g.storageMutex.Lock()
|
||||
defer g.storageMutex.Unlock()
|
||||
|
||||
timeNow := g.getEpoch()
|
||||
// Clock didn't change since last UUID generation.
|
||||
// Should increase clock sequence.
|
||||
if timeNow <= g.lastTime {
|
||||
g.clockSequence++
|
||||
}
|
||||
g.lastTime = timeNow
|
||||
|
||||
return timeNow, g.clockSequence, nil
|
||||
}
|
||||
|
||||
// Precision is used to configure the V7 generator, to specify how precise the
|
||||
// timestamp within the UUID should be.
|
||||
type Precision byte
|
||||
|
||||
const (
|
||||
NanosecondPrecision Precision = iota
|
||||
MicrosecondPrecision
|
||||
MillisecondPrecision
|
||||
)
|
||||
|
||||
func (p Precision) String() string {
|
||||
switch p {
|
||||
case NanosecondPrecision:
|
||||
return "nanosecond"
|
||||
|
||||
case MicrosecondPrecision:
|
||||
return "microsecond"
|
||||
|
||||
case MillisecondPrecision:
|
||||
return "millisecond"
|
||||
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Duration returns the time.Duration for a specific precision. If the Precision
|
||||
// value is not known, this returns 0.
|
||||
func (p Precision) Duration() time.Duration {
|
||||
switch p {
|
||||
case NanosecondPrecision:
|
||||
return time.Nanosecond
|
||||
|
||||
case MicrosecondPrecision:
|
||||
return time.Microsecond
|
||||
|
||||
case MillisecondPrecision:
|
||||
return time.Millisecond
|
||||
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// NewV7 returns a k-sortable UUID based on the current UNIX epoch, with the
|
||||
// ability to configure the timestamp's precision from millisecond all the way
|
||||
// to nanosecond. The additional precision is supported by reducing the amount
|
||||
// of pseudorandom data that makes up the rest of the UUID.
|
||||
//
|
||||
// If an unknown Precision argument is passed to this method it will panic. As
|
||||
// such it's strongly encouraged to use the package-provided constants for this
|
||||
// value.
|
||||
//
|
||||
// This is implemented based on revision 02 of the Peabody UUID draft, and may
|
||||
// be subject to change pending further revisions. Until the final specification
|
||||
// revision is finished, changes required to implement updates to the spec will
|
||||
// not be considered a breaking change. They will happen as a minor version
|
||||
// releases until the spec is final.
|
||||
func (g *Gen) NewV7(p Precision) (UUID, error) {
|
||||
var u UUID
|
||||
var err error
|
||||
|
||||
switch p {
|
||||
case NanosecondPrecision:
|
||||
u, err = g.newV7Nano()
|
||||
|
||||
case MicrosecondPrecision:
|
||||
u, err = g.newV7Micro()
|
||||
|
||||
case MillisecondPrecision:
|
||||
u, err = g.newV7Milli()
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown precision value %d", p))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
u.SetVersion(V7)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (g *Gen) newV7Milli() (UUID, error) {
|
||||
var u UUID
|
||||
|
||||
if _, err := io.ReadFull(g.rand, u[8:]); err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
sec, nano, seq, err := g.getV7ClockSequence(MillisecondPrecision)
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
msec := (nano / 1000000) & 0xfff
|
||||
|
||||
d := (sec << 28) // set unixts field
|
||||
d |= (msec << 16) // set msec field
|
||||
d |= (uint64(seq) & 0xfff) // set seq field
|
||||
|
||||
binary.BigEndian.PutUint64(u[:], d)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (g *Gen) newV7Micro() (UUID, error) {
|
||||
var u UUID
|
||||
|
||||
if _, err := io.ReadFull(g.rand, u[10:]); err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
sec, nano, seq, err := g.getV7ClockSequence(MicrosecondPrecision)
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
usec := nano / 1000
|
||||
usech := (usec << 4) & 0xfff0000
|
||||
usecl := usec & 0xfff
|
||||
|
||||
d := (sec << 28) // set unixts field
|
||||
d |= usech | usecl // set usec fields
|
||||
|
||||
binary.BigEndian.PutUint64(u[:], d)
|
||||
binary.BigEndian.PutUint16(u[8:], seq)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (g *Gen) newV7Nano() (UUID, error) {
|
||||
var u UUID
|
||||
|
||||
if _, err := io.ReadFull(g.rand, u[11:]); err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
sec, nano, seq, err := g.getV7ClockSequence(NanosecondPrecision)
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
|
||||
nano &= 0x3fffffffff
|
||||
nanoh := nano >> 26
|
||||
nanom := (nano >> 14) & 0xfff
|
||||
nanol := uint16(nano & 0x3fff)
|
||||
|
||||
d := (sec << 28) // set unixts field
|
||||
d |= (nanoh << 16) | nanom // set nsec high and med fields
|
||||
|
||||
binary.BigEndian.PutUint64(u[:], d)
|
||||
binary.BigEndian.PutUint16(u[8:], nanol) // set nsec low field
|
||||
|
||||
u[10] = byte(seq) // set seq field
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
const (
|
||||
maxSeq14 = (1 << 14) - 1
|
||||
maxSeq12 = (1 << 12) - 1
|
||||
maxSeq8 = (1 << 8) - 1
|
||||
)
|
||||
|
||||
// getV7ClockSequence returns the unix epoch, nanoseconds of current second, and
|
||||
// the sequence for V7 UUIDs.
|
||||
func (g *Gen) getV7ClockSequence(p Precision) (epoch uint64, nano uint64, seq uint16, err error) {
|
||||
g.storageMutex.Lock()
|
||||
defer g.storageMutex.Unlock()
|
||||
|
||||
tn := g.epochFunc()
|
||||
unix := uint64(tn.Unix())
|
||||
nsec := uint64(tn.Nanosecond())
|
||||
|
||||
// V7 UUIDs have more precise requirements around how the clock sequence
|
||||
// value is generated and used. Specifically they require that the sequence
|
||||
// be zero, unless we've already generated a UUID within this unit of time
|
||||
// (millisecond, microsecond, or nanosecond) at which point you should
|
||||
// increment the sequence. Likewise if time has warped backwards for some reason (NTP
|
||||
// adjustment?), we also increment the clock sequence to reduce the risk of a
|
||||
// collision.
|
||||
switch {
|
||||
case unix < g.v7LastTime:
|
||||
g.v7ClockSequence++
|
||||
|
||||
case unix > g.v7LastTime:
|
||||
g.v7ClockSequence = 0
|
||||
|
||||
case unix == g.v7LastTime:
|
||||
switch p {
|
||||
case NanosecondPrecision:
|
||||
if nsec <= g.v7LastSubsec {
|
||||
if g.v7ClockSequence >= maxSeq8 {
|
||||
return 0, 0, 0, errors.New("generating nanosecond precision UUIDv7s too fast: internal clock sequence would roll over")
|
||||
}
|
||||
|
||||
g.v7ClockSequence++
|
||||
} else {
|
||||
g.v7ClockSequence = 0
|
||||
}
|
||||
|
||||
case MicrosecondPrecision:
|
||||
if nsec/1000 <= g.v7LastSubsec/1000 {
|
||||
if g.v7ClockSequence >= maxSeq14 {
|
||||
return 0, 0, 0, errors.New("generating microsecond precision UUIDv7s too fast: internal clock sequence would roll over")
|
||||
}
|
||||
|
||||
g.v7ClockSequence++
|
||||
} else {
|
||||
g.v7ClockSequence = 0
|
||||
}
|
||||
|
||||
case MillisecondPrecision:
|
||||
if nsec/1000000 <= g.v7LastSubsec/1000000 {
|
||||
if g.v7ClockSequence >= maxSeq12 {
|
||||
return 0, 0, 0, errors.New("generating millisecond precision UUIDv7s too fast: internal clock sequence would roll over")
|
||||
}
|
||||
|
||||
g.v7ClockSequence++
|
||||
} else {
|
||||
g.v7ClockSequence = 0
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown precision value %d", p))
|
||||
}
|
||||
}
|
||||
|
||||
g.v7LastTime = unix
|
||||
g.v7LastSubsec = nsec
|
||||
|
||||
return unix, nsec, g.v7ClockSequence, nil
|
||||
}
|
||||
|
||||
// Returns the hardware address.
|
||||
func (g *Gen) getHardwareAddr() ([]byte, error) {
|
||||
var err error
|
||||
g.hardwareAddrOnce.Do(func() {
|
||||
var hwAddr net.HardwareAddr
|
||||
if hwAddr, err = g.hwAddrFunc(); err == nil {
|
||||
copy(g.hardwareAddr[:], hwAddr)
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize hardwareAddr randomly in case
|
||||
// of real network interfaces absence.
|
||||
if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil {
|
||||
return
|
||||
}
|
||||
// Set multicast bit as recommended by RFC-4122
|
||||
g.hardwareAddr[0] |= 0x01
|
||||
})
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return g.hardwareAddr[:], nil
|
||||
}
|
||||
|
||||
// Returns the difference between UUID epoch (October 15, 1582)
|
||||
// and current time in 100-nanosecond intervals.
|
||||
func (g *Gen) getEpoch() uint64 {
|
||||
return epochStart + uint64(g.epochFunc().UnixNano()/100)
|
||||
}
|
||||
|
||||
// Returns the UUID based on the hashing of the namespace UUID and name.
|
||||
func newFromHash(h hash.Hash, ns UUID, name string) UUID {
|
||||
u := UUID{}
|
||||
h.Write(ns[:])
|
||||
h.Write([]byte(name))
|
||||
copy(u[:], h.Sum(nil))
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// Returns the hardware address.
|
||||
func defaultHWAddrFunc() (net.HardwareAddr, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
if len(iface.HardwareAddr) >= 6 {
|
||||
return iface.HardwareAddr, nil
|
||||
}
|
||||
}
|
||||
return []byte{}, fmt.Errorf("uuid: no HW address found")
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
//
|
||||
// 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.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (u UUID) Value() (driver.Value, error) {
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
// A 16-byte slice will be handled by UnmarshalBinary, while
|
||||
// a longer byte slice or a string will be handled by UnmarshalText.
|
||||
func (u *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case UUID: // support gorm convert from UUID to NullUUID
|
||||
*u = src
|
||||
return nil
|
||||
|
||||
case []byte:
|
||||
if len(src) == Size {
|
||||
return u.UnmarshalBinary(src)
|
||||
}
|
||||
return u.UnmarshalText(src)
|
||||
|
||||
case string:
|
||||
return u.UnmarshalText([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("uuid: cannot convert %T to UUID", src)
|
||||
}
|
||||
|
||||
// NullUUID can be used with the standard sql package to represent a
|
||||
// UUID value that can be NULL in the database.
|
||||
type NullUUID struct {
|
||||
UUID UUID
|
||||
Valid bool
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (u NullUUID) Value() (driver.Value, error) {
|
||||
if !u.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
// Delegate to UUID Value function
|
||||
return u.UUID.Value()
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (u *NullUUID) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
u.UUID, u.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delegate to UUID Scan function
|
||||
u.Valid = true
|
||||
return u.UUID.Scan(src)
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the NullUUID as null or the nested UUID
|
||||
func (u NullUUID) MarshalJSON() ([]byte, error) {
|
||||
if !u.Valid {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
|
||||
return json.Marshal(u.UUID)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a NullUUID
|
||||
func (u *NullUUID) UnmarshalJSON(b []byte) error {
|
||||
if bytes.Equal(b, []byte("null")) {
|
||||
u.UUID, u.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &u.UUID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Valid = true
|
||||
|
||||
return nil
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Package uuid provides implementations of the Universally Unique Identifier
|
||||
// (UUID), as specified in RFC-4122 and the Peabody RFC Draft (revision 02).
|
||||
//
|
||||
// RFC-4122[1] provides the specification for versions 1, 3, 4, and 5. The
|
||||
// Peabody UUID RFC Draft[2] provides the specification for the new k-sortable
|
||||
// UUIDs, versions 6 and 7.
|
||||
//
|
||||
// DCE 1.1[3] provides the specification for version 2, but version 2 support
|
||||
// was removed from this package in v4 due to some concerns with the
|
||||
// specification itself. Reading the spec, it seems that it would result in
|
||||
// generating UUIDs that aren't very unique. In having read the spec it seemed
|
||||
// that our implementation did not meet the spec. It also seems to be at-odds
|
||||
// with RFC 4122, meaning we would need quite a bit of special code to support
|
||||
// it. Lastly, there were no Version 2 implementations that we could find to
|
||||
// ensure we were understanding the specification correctly.
|
||||
//
|
||||
// [1] https://tools.ietf.org/html/rfc4122
|
||||
// [2] https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02
|
||||
// [3] http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Size of a UUID in bytes.
|
||||
const Size = 16
|
||||
|
||||
// UUID is an array type to represent the value of a UUID, as defined in RFC-4122.
|
||||
type UUID [Size]byte
|
||||
|
||||
// UUID versions.
|
||||
const (
|
||||
_ byte = iota
|
||||
V1 // Version 1 (date-time and MAC address)
|
||||
_ // Version 2 (date-time and MAC address, DCE security version) [removed]
|
||||
V3 // Version 3 (namespace name-based)
|
||||
V4 // Version 4 (random)
|
||||
V5 // Version 5 (namespace name-based)
|
||||
V6 // Version 6 (k-sortable timestamp and random data) [peabody draft]
|
||||
V7 // Version 7 (k-sortable timestamp, with configurable precision, and random data) [peabody draft]
|
||||
_ // Version 8 (k-sortable timestamp, meant for custom implementations) [peabody draft] [not implemented]
|
||||
)
|
||||
|
||||
// UUID layout variants.
|
||||
const (
|
||||
VariantNCS byte = iota
|
||||
VariantRFC4122
|
||||
VariantMicrosoft
|
||||
VariantFuture
|
||||
)
|
||||
|
||||
// UUID DCE domains.
|
||||
const (
|
||||
DomainPerson = iota
|
||||
DomainGroup
|
||||
DomainOrg
|
||||
)
|
||||
|
||||
// Timestamp is the count of 100-nanosecond intervals since 00:00:00.00,
|
||||
// 15 October 1582 within a V1 UUID. This type has no meaning for other
|
||||
// UUID versions since they don't have an embedded timestamp.
|
||||
type Timestamp uint64
|
||||
|
||||
const _100nsPerSecond = 10000000
|
||||
|
||||
// Time returns the UTC time.Time representation of a Timestamp
|
||||
func (t Timestamp) Time() (time.Time, error) {
|
||||
secs := uint64(t) / _100nsPerSecond
|
||||
nsecs := 100 * (uint64(t) % _100nsPerSecond)
|
||||
|
||||
return time.Unix(int64(secs)-(epochStart/_100nsPerSecond), int64(nsecs)), nil
|
||||
}
|
||||
|
||||
// TimestampFromV1 returns the Timestamp embedded within a V1 UUID.
|
||||
// Returns an error if the UUID is any version other than 1.
|
||||
func TimestampFromV1(u UUID) (Timestamp, error) {
|
||||
if u.Version() != 1 {
|
||||
err := fmt.Errorf("uuid: %s is version %d, not version 1", u, u.Version())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
low := binary.BigEndian.Uint32(u[0:4])
|
||||
mid := binary.BigEndian.Uint16(u[4:6])
|
||||
hi := binary.BigEndian.Uint16(u[6:8]) & 0xfff
|
||||
|
||||
return Timestamp(uint64(low) + (uint64(mid) << 32) + (uint64(hi) << 48)), nil
|
||||
}
|
||||
|
||||
// TimestampFromV6 returns the Timestamp embedded within a V6 UUID. This
|
||||
// function returns an error if the UUID is any version other than 6.
|
||||
//
|
||||
// This is implemented based on revision 01 of the Peabody UUID draft, and may
|
||||
// be subject to change pending further revisions. Until the final specification
|
||||
// revision is finished, changes required to implement updates to the spec will
|
||||
// not be considered a breaking change. They will happen as a minor version
|
||||
// releases until the spec is final.
|
||||
func TimestampFromV6(u UUID) (Timestamp, error) {
|
||||
if u.Version() != 6 {
|
||||
return 0, fmt.Errorf("uuid: %s is version %d, not version 6", u, u.Version())
|
||||
}
|
||||
|
||||
hi := binary.BigEndian.Uint32(u[0:4])
|
||||
mid := binary.BigEndian.Uint16(u[4:6])
|
||||
low := binary.BigEndian.Uint16(u[6:8]) & 0xfff
|
||||
|
||||
return Timestamp(uint64(low) + (uint64(mid) << 12) + (uint64(hi) << 28)), nil
|
||||
}
|
||||
|
||||
// String parse helpers.
|
||||
var (
|
||||
urnPrefix = []byte("urn:uuid:")
|
||||
byteGroups = []int{8, 4, 4, 4, 12}
|
||||
)
|
||||
|
||||
// Nil is the nil UUID, as specified in RFC-4122, that has all 128 bits set to
|
||||
// zero.
|
||||
var Nil = UUID{}
|
||||
|
||||
// Predefined namespace UUIDs.
|
||||
var (
|
||||
NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
)
|
||||
|
||||
// IsNil returns if the UUID is equal to the nil UUID
|
||||
func (u UUID) IsNil() bool {
|
||||
return u == Nil
|
||||
}
|
||||
|
||||
// Version returns the algorithm version used to generate the UUID.
|
||||
func (u UUID) Version() byte {
|
||||
return u[6] >> 4
|
||||
}
|
||||
|
||||
// Variant returns the UUID layout variant.
|
||||
func (u UUID) Variant() byte {
|
||||
switch {
|
||||
case (u[8] >> 7) == 0x00:
|
||||
return VariantNCS
|
||||
case (u[8] >> 6) == 0x02:
|
||||
return VariantRFC4122
|
||||
case (u[8] >> 5) == 0x06:
|
||||
return VariantMicrosoft
|
||||
case (u[8] >> 5) == 0x07:
|
||||
fallthrough
|
||||
default:
|
||||
return VariantFuture
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns a byte slice representation of the UUID.
|
||||
func (u UUID) Bytes() []byte {
|
||||
return u[:]
|
||||
}
|
||||
|
||||
// String returns a canonical RFC-4122 string representation of the UUID:
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
|
||||
func (u UUID) String() string {
|
||||
buf := make([]byte, 36)
|
||||
|
||||
hex.Encode(buf[0:8], u[0:4])
|
||||
buf[8] = '-'
|
||||
hex.Encode(buf[9:13], u[4:6])
|
||||
buf[13] = '-'
|
||||
hex.Encode(buf[14:18], u[6:8])
|
||||
buf[18] = '-'
|
||||
hex.Encode(buf[19:23], u[8:10])
|
||||
buf[23] = '-'
|
||||
hex.Encode(buf[24:], u[10:])
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter for UUID values.
|
||||
//
|
||||
// The behavior is as follows:
|
||||
// The 'x' and 'X' verbs output only the hex digits of the UUID, using a-f for 'x' and A-F for 'X'.
|
||||
// The 'v', '+v', 's' and 'q' verbs return the canonical RFC-4122 string representation.
|
||||
// The 'S' verb returns the RFC-4122 format, but with capital hex digits.
|
||||
// The '#v' verb returns the "Go syntax" representation, which is a 16 byte array initializer.
|
||||
// All other verbs not handled directly by the fmt package (like '%p') are unsupported and will return
|
||||
// "%!verb(uuid.UUID=value)" as recommended by the fmt package.
|
||||
func (u UUID) Format(f fmt.State, c rune) {
|
||||
switch c {
|
||||
case 'x', 'X':
|
||||
s := hex.EncodeToString(u.Bytes())
|
||||
if c == 'X' {
|
||||
s = strings.Map(toCapitalHexDigits, s)
|
||||
}
|
||||
_, _ = io.WriteString(f, s)
|
||||
case 'v':
|
||||
var s string
|
||||
if f.Flag('#') {
|
||||
s = fmt.Sprintf("%#v", [Size]byte(u))
|
||||
} else {
|
||||
s = u.String()
|
||||
}
|
||||
_, _ = io.WriteString(f, s)
|
||||
case 's', 'S':
|
||||
s := u.String()
|
||||
if c == 'S' {
|
||||
s = strings.Map(toCapitalHexDigits, s)
|
||||
}
|
||||
_, _ = io.WriteString(f, s)
|
||||
case 'q':
|
||||
_, _ = io.WriteString(f, `"`+u.String()+`"`)
|
||||
default:
|
||||
// invalid/unsupported format verb
|
||||
fmt.Fprintf(f, "%%!%c(uuid.UUID=%s)", c, u.String())
|
||||
}
|
||||
}
|
||||
|
||||
func toCapitalHexDigits(ch rune) rune {
|
||||
// convert a-f hex digits to A-F
|
||||
switch ch {
|
||||
case 'a':
|
||||
return 'A'
|
||||
case 'b':
|
||||
return 'B'
|
||||
case 'c':
|
||||
return 'C'
|
||||
case 'd':
|
||||
return 'D'
|
||||
case 'e':
|
||||
return 'E'
|
||||
case 'f':
|
||||
return 'F'
|
||||
default:
|
||||
return ch
|
||||
}
|
||||
}
|
||||
|
||||
// SetVersion sets the version bits.
|
||||
func (u *UUID) SetVersion(v byte) {
|
||||
u[6] = (u[6] & 0x0f) | (v << 4)
|
||||
}
|
||||
|
||||
// SetVariant sets the variant bits.
|
||||
func (u *UUID) SetVariant(v byte) {
|
||||
switch v {
|
||||
case VariantNCS:
|
||||
u[8] = (u[8]&(0xff>>1) | (0x00 << 7))
|
||||
case VariantRFC4122:
|
||||
u[8] = (u[8]&(0xff>>2) | (0x02 << 6))
|
||||
case VariantMicrosoft:
|
||||
u[8] = (u[8]&(0xff>>3) | (0x06 << 5))
|
||||
case VariantFuture:
|
||||
fallthrough
|
||||
default:
|
||||
u[8] = (u[8]&(0xff>>3) | (0x07 << 5))
|
||||
}
|
||||
}
|
||||
|
||||
// Must is a helper that wraps a call to a function returning (UUID, error)
|
||||
// and panics if the error is non-nil. It is intended for use in variable
|
||||
// initializations such as
|
||||
// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000"))
|
||||
func Must(u UUID, err error) UUID {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# Errors
|
||||
|
||||
`errors` package serve to build an arbitrary long error chain in order to capture errors returned from nested service calls.
|
||||
|
||||
`errors` package contains the custom Go `error` interface implementation, `Error`. You use the `Error` interface to **wrap** two errors in a containing error as well as to test recursively if a given error **contains** some other error.
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package errors
|
||||
|
||||
// Error specifies an API that must be fullfiled by error type
|
||||
type Error interface {
|
||||
|
||||
// Error implements the error interface.
|
||||
Error() string
|
||||
|
||||
// Msg returns error message
|
||||
Msg() string
|
||||
|
||||
// Err returns wrapped error
|
||||
Err() Error
|
||||
}
|
||||
|
||||
var _ Error = (*customError)(nil)
|
||||
|
||||
// customError represents a Mainflux error
|
||||
type customError struct {
|
||||
msg string
|
||||
err Error
|
||||
}
|
||||
|
||||
func (ce *customError) Error() string {
|
||||
if ce == nil {
|
||||
return ""
|
||||
}
|
||||
if ce.err == nil {
|
||||
return ce.msg
|
||||
}
|
||||
return ce.msg + " : " + ce.err.Error()
|
||||
}
|
||||
|
||||
func (ce *customError) Msg() string {
|
||||
return ce.msg
|
||||
}
|
||||
|
||||
func (ce *customError) Err() Error {
|
||||
return ce.err
|
||||
}
|
||||
|
||||
// Contains inspects if e2 error is contained in any layer of e1 error
|
||||
func Contains(e1 error, e2 error) bool {
|
||||
if e1 == nil || e2 == nil {
|
||||
return e2 == e1
|
||||
}
|
||||
ce, ok := e1.(Error)
|
||||
if ok {
|
||||
if ce.Msg() == e2.Error() {
|
||||
return true
|
||||
}
|
||||
return Contains(ce.Err(), e2)
|
||||
}
|
||||
return e1.Error() == e2.Error()
|
||||
}
|
||||
|
||||
// Wrap returns an Error that wrap err with wrapper
|
||||
func Wrap(wrapper error, err error) error {
|
||||
if wrapper == nil || err == nil {
|
||||
return wrapper
|
||||
}
|
||||
if w, ok := wrapper.(Error); ok {
|
||||
return &customError{
|
||||
msg: w.Msg(),
|
||||
err: cast(err),
|
||||
}
|
||||
}
|
||||
return &customError{
|
||||
msg: wrapper.Error(),
|
||||
err: cast(err),
|
||||
}
|
||||
}
|
||||
|
||||
func cast(err error) Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := err.(Error); ok {
|
||||
return e
|
||||
}
|
||||
return &customError{
|
||||
msg: err.Error(),
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// New returns an Error that formats as the given text.
|
||||
func New(text string) Error {
|
||||
return &customError{
|
||||
msg: text,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package errors
|
||||
|
||||
var (
|
||||
// ErrAuthentication indicates failure occurred while authenticating the entity.
|
||||
ErrAuthentication = New("failed to perform authentication over the entity")
|
||||
|
||||
// ErrAuthorization indicates failure occurred while authorizing the entity.
|
||||
ErrAuthorization = New("failed to perform authorization over the entity")
|
||||
|
||||
// ErrUnsupportedContentType indicates unacceptable or lack of Content-Type
|
||||
ErrUnsupportedContentType = New("unsupported content type")
|
||||
|
||||
// ErrInvalidQueryParams indicates invalid query parameters
|
||||
ErrInvalidQueryParams = New("invalid query parameters")
|
||||
|
||||
// ErrNotFoundParam indicates that the parameter was not found in the query
|
||||
ErrNotFoundParam = New("parameter not found in the query")
|
||||
|
||||
// ErrMalformedEntity indicates a malformed entity specification
|
||||
ErrMalformedEntity = New("malformed entity specification")
|
||||
|
||||
// ErrNotFound indicates a non-existent entity request.
|
||||
ErrNotFound = New("entity not found")
|
||||
|
||||
// ErrConflict indicates that entity already exists.
|
||||
ErrConflict = New("entity already exists")
|
||||
|
||||
// ErrCreateEntity indicates error in creating entity or entities
|
||||
ErrCreateEntity = New("failed to create entity in the db")
|
||||
|
||||
// ErrViewEntity indicates error in viewing entity or entities
|
||||
ErrViewEntity = New("view entity failed")
|
||||
|
||||
// ErrUpdateEntity indicates error in updating entity or entities
|
||||
ErrUpdateEntity = New("update entity failed")
|
||||
|
||||
// ErrRemoveEntity indicates error in removing entity
|
||||
ErrRemoveEntity = New("failed to remove entity")
|
||||
|
||||
// ErrScanMetadata indicates problem with metadata in db
|
||||
ErrScanMetadata = New("failed to scan metadata in db")
|
||||
)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# UUID identity provider
|
||||
|
||||
The UUID identity provider generates a random, universally unique identifier (UUID), unique for all practical purposes.
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
// Prefix represents the prefix used to generate UUID mocks
|
||||
const Prefix = "123e4567-e89b-12d3-a456-"
|
||||
|
||||
var _ mainflux.IDProvider = (*uuidProviderMock)(nil)
|
||||
|
||||
type uuidProviderMock struct {
|
||||
mu sync.Mutex
|
||||
counter int
|
||||
}
|
||||
|
||||
func (up *uuidProviderMock) ID() (string, error) {
|
||||
up.mu.Lock()
|
||||
defer up.mu.Unlock()
|
||||
|
||||
up.counter++
|
||||
return fmt.Sprintf("%s%012d", Prefix, up.counter), nil
|
||||
}
|
||||
|
||||
// NewMock creates "mirror" uuid provider, i.e. generated
|
||||
// token will hold value provided by the caller.
|
||||
func NewMock() mainflux.IDProvider {
|
||||
return &uuidProviderMock{}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package uuid provides a UUID identity provider.
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrGeneratingID indicates error in generating UUID
|
||||
var ErrGeneratingID = errors.New("failed to generate uuid")
|
||||
|
||||
var _ mainflux.IDProvider = (*uuidProvider)(nil)
|
||||
|
||||
type uuidProvider struct{}
|
||||
|
||||
// New instantiates a UUID provider.
|
||||
func New() mainflux.IDProvider {
|
||||
return &uuidProvider{}
|
||||
}
|
||||
|
||||
func (up *uuidProvider) ID() (string, error) {
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(ErrGeneratingID, err)
|
||||
}
|
||||
|
||||
return id.String(), nil
|
||||
}
|
||||
Vendored
+5
@@ -34,6 +34,9 @@ github.com/go-logfmt/logfmt
|
||||
# github.com/go-zoo/bone v1.3.0
|
||||
## explicit; go 1.9
|
||||
github.com/go-zoo/bone
|
||||
# github.com/gofrs/uuid v4.2.0+incompatible
|
||||
## explicit
|
||||
github.com/gofrs/uuid
|
||||
# github.com/golang/protobuf v1.5.2
|
||||
## explicit; go 1.9
|
||||
github.com/golang/protobuf/jsonpb
|
||||
@@ -47,6 +50,8 @@ github.com/golang/protobuf/ptypes/timestamp
|
||||
## explicit; go 1.17
|
||||
github.com/mainflux/mainflux
|
||||
github.com/mainflux/mainflux/logger
|
||||
github.com/mainflux/mainflux/pkg/errors
|
||||
github.com/mainflux/mainflux/pkg/uuid
|
||||
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||
## explicit
|
||||
github.com/matttproud/golang_protobuf_extensions/pbutil
|
||||
|
||||
Reference in New Issue
Block a user