TUN-1604: Define Connect RPC call

This commit is contained in:
Chung-Ting Huang 2019-03-18 18:14:47 -05:00
parent 8560436487
commit 8c6cfa34aa
17 changed files with 1412 additions and 217 deletions

6
Gopkg.lock generated
View File

@ -186,12 +186,12 @@
version = "v1.1.0" version = "v1.1.0"
[[projects]] [[projects]]
digest = "1:8f8811f9be822914c3a25c6a071e93beb4c805d7b026cbf298bc577bc1cc945b" digest = "1:582b704bebaa06b48c29b0cec224a6058a09c86883aaddabde889cd1a5f73e1b"
name = "github.com/google/uuid" name = "github.com/google/uuid"
packages = ["."] packages = ["."]
pruneopts = "UT" pruneopts = "UT"
revision = "064e2069ce9c359c118179501254f67d7d37ba24" revision = "0cd6bf5da1e1c83f8b45653022c74f71af0538a4"
version = "0.2" version = "v1.1.1"
[[projects]] [[projects]]
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1" digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"

View File

@ -2,8 +2,10 @@ package pogs
import ( import (
"context" "context"
"time"
"github.com/cloudflare/cloudflared/tunnelrpc" "github.com/cloudflare/cloudflared/tunnelrpc"
"github.com/google/uuid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"zombiezen.com/go/capnproto2" "zombiezen.com/go/capnproto2"
@ -71,6 +73,80 @@ func UnmarshalRegistrationOptions(s tunnelrpc.RegistrationOptions) (*Registratio
return p, err return p, err
} }
type ServerHello struct {
ConnectResult *ConnectResult
CloudflaredID uuid.UUID
}
// CapnpServerHello is ServerHello respresented in Cap'n Proto build-in types
type CapnpServerHello struct {
ConnectResult *ConnectResult
CloudflaredID []byte
}
func MarshalServerHello(s tunnelrpc.CapnpServerHello, p *ServerHello) error {
cloudflaredIDBytes, err := p.CloudflaredID.MarshalBinary()
if err != nil {
return err
}
capnpServerHello := &CapnpServerHello{
ConnectResult: p.ConnectResult,
CloudflaredID: cloudflaredIDBytes,
}
return pogs.Insert(tunnelrpc.CapnpServerHello_TypeID, s.Struct, capnpServerHello)
}
func UnmarshalServerHello(s tunnelrpc.CapnpServerHello) (*ServerHello, error) {
p := new(CapnpServerHello)
err := pogs.Extract(p, tunnelrpc.CapnpServerHello_TypeID, s.Struct)
if err != nil {
return nil, err
}
cloudflaredID, err := uuid.FromBytes(p.CloudflaredID)
if err != nil {
log.Errorf("fail to unmarshal %+v", p.CloudflaredID)
return nil, err
}
return &ServerHello{
ConnectResult: p.ConnectResult,
CloudflaredID: cloudflaredID,
}, nil
}
type ConnectResult struct {
Err *ConnectError
ServerInfo ServerInfo
}
func MarshalConnectResult(s tunnelrpc.ConnectResult, p *ConnectResult) error {
return pogs.Insert(tunnelrpc.ConnectResult_TypeID, s.Struct, p)
}
func UnmarshalConnectResult(s tunnelrpc.ConnectResult) (*ConnectResult, error) {
p := new(ConnectResult)
err := pogs.Extract(p, tunnelrpc.ConnectResult_TypeID, s.Struct)
return p, err
}
type ConnectError struct {
Cause string
RetryAfter time.Duration
}
func MarshalConnectError(s tunnelrpc.ConnectError, p *ConnectError) error {
return pogs.Insert(tunnelrpc.ConnectError_TypeID, s.Struct, p)
}
func UnmarshalConnectError(s tunnelrpc.ConnectError) (*ConnectError, error) {
p := new(ConnectError)
err := pogs.Extract(p, tunnelrpc.ConnectError_TypeID, s.Struct)
return p, err
}
func (e *ConnectError) Error() string {
return e.Cause
}
type Tag struct { type Tag struct {
Name string `json:"name"` Name string `json:"name"`
Value string `json:"value"` Value string `json:"value"`
@ -90,10 +166,71 @@ func UnmarshalServerInfo(s tunnelrpc.ServerInfo) (*ServerInfo, error) {
return p, err return p, err
} }
type HelloParameters struct {
OriginCert []byte
Tags []Tag
NumPreviousAttempts uint8
}
func MarshalHelloParameters(s tunnelrpc.HelloParameters, p *HelloParameters) error {
return pogs.Insert(tunnelrpc.HelloParameters_TypeID, s.Struct, p)
}
func UnmarshalHelloParameters(s tunnelrpc.HelloParameters) (*HelloParameters, error) {
p := new(HelloParameters)
err := pogs.Extract(p, tunnelrpc.HelloParameters_TypeID, s.Struct)
return p, err
}
type ConnectParameters struct {
OriginCert []byte
CloudflaredID uuid.UUID
NumPreviousAttempts uint8
}
// CapnpConnectParameters is ConnectParameters represented in Cap'n Proto build-in types
type CapnpConnectParameters struct {
OriginCert []byte
CloudflaredID []byte
NumPreviousAttempts uint8
}
func MarshalConnectParameters(s tunnelrpc.CapnpConnectParameters, p *ConnectParameters) error {
cloudflaredIDBytes, err := p.CloudflaredID.MarshalBinary()
if err != nil {
return err
}
capnpConnectParameters := &CapnpConnectParameters{
OriginCert: p.OriginCert,
CloudflaredID: cloudflaredIDBytes,
NumPreviousAttempts: p.NumPreviousAttempts,
}
return pogs.Insert(tunnelrpc.CapnpConnectParameters_TypeID, s.Struct, capnpConnectParameters)
}
func UnmarshalConnectParameters(s tunnelrpc.CapnpConnectParameters) (*ConnectParameters, error) {
p := new(CapnpConnectParameters)
err := pogs.Extract(p, tunnelrpc.CapnpConnectParameters_TypeID, s.Struct)
if err != nil {
return nil, err
}
cloudflaredID, err := uuid.FromBytes(p.CloudflaredID)
if err != nil {
return nil, err
}
return &ConnectParameters{
OriginCert: p.OriginCert,
CloudflaredID: cloudflaredID,
NumPreviousAttempts: p.NumPreviousAttempts,
}, nil
}
type TunnelServer interface { type TunnelServer interface {
RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error) RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error)
GetServerInfo(ctx context.Context) (*ServerInfo, error) GetServerInfo(ctx context.Context) (*ServerInfo, error)
UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error
Hello(ctx context.Context, parameters *HelloParameters) (*ServerHello, error)
Connect(ctx context.Context, paramaters *ConnectParameters) (*ConnectResult, error)
} }
func TunnelServer_ServerToClient(s TunnelServer) tunnelrpc.TunnelServer { func TunnelServer_ServerToClient(s TunnelServer) tunnelrpc.TunnelServer {
@ -151,7 +288,48 @@ func (i TunnelServer_PogsImpl) UnregisterTunnel(p tunnelrpc.TunnelServer_unregis
gracePeriodNanoSec := p.Params.GracePeriodNanoSec() gracePeriodNanoSec := p.Params.GracePeriodNanoSec()
server.Ack(p.Options) server.Ack(p.Options)
return i.impl.UnregisterTunnel(p.Ctx, gracePeriodNanoSec) return i.impl.UnregisterTunnel(p.Ctx, gracePeriodNanoSec)
}
func (i TunnelServer_PogsImpl) Hello(p tunnelrpc.TunnelServer_hello) error {
parameters, err := p.Params.Parameters()
if err != nil {
return err
}
pogsParameters, err := UnmarshalHelloParameters(parameters)
if err != nil {
return err
}
server.Ack(p.Options)
serverHello, err := i.impl.Hello(p.Ctx, pogsParameters)
if err != nil {
return err
}
result, err := p.Results.NewResult()
if err != nil {
return err
}
return MarshalServerHello(result, serverHello)
}
func (i TunnelServer_PogsImpl) Connect(p tunnelrpc.TunnelServer_connect) error {
paramaters, err := p.Params.Parameters()
if err != nil {
return err
}
pogsParameters, err := UnmarshalConnectParameters(paramaters)
if err != nil {
return err
}
server.Ack(p.Options)
connectResult, err := i.impl.Connect(p.Ctx, pogsParameters)
if err != nil {
return err
}
result, err := p.Results.NewResult()
if err != nil {
return err
}
return MarshalConnectResult(result, connectResult)
} }
type TunnelServer_PogsClient struct { type TunnelServer_PogsClient struct {
@ -212,3 +390,47 @@ func (c TunnelServer_PogsClient) UnregisterTunnel(ctx context.Context, gracePeri
_, err := promise.Struct() _, err := promise.Struct()
return err return err
} }
func (c TunnelServer_PogsClient) Hello(ctx context.Context,
parameters *HelloParameters,
) (*ServerHello, error) {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.Hello(ctx, func(p tunnelrpc.TunnelServer_hello_Params) error {
helloParameters, err := p.NewParameters()
if err != nil {
return err
}
err = MarshalHelloParameters(helloParameters, parameters)
if err != nil {
return err
}
return nil
})
retval, err := promise.Result().Struct()
if err != nil {
return nil, err
}
return UnmarshalServerHello(retval)
}
func (c TunnelServer_PogsClient) Connect(ctx context.Context,
parameters *ConnectParameters,
) (*ConnectResult, error) {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.Connect(ctx, func(p tunnelrpc.TunnelServer_connect_Params) error {
connectParameters, err := p.NewParameters()
if err != nil {
return err
}
err = MarshalConnectParameters(connectParameters, parameters)
if err != nil {
return err
}
return nil
})
retval, err := promise.Result().Struct()
if err != nil {
return nil, err
}
return UnmarshalConnectResult(retval)
}

View File

@ -46,6 +46,43 @@ struct RegistrationOptions {
uuid @11 :Text; uuid @11 :Text;
} }
struct HelloParameters {
# certificate and token to prove ownership of a zone
originCert @0 :Data;
# user defined labels for this cloudflared
tags @1 :List(Tag);
# number of previous attempts to send Hello
numPreviousAttempts @2 :UInt8;
}
struct CapnpConnectParameters {
# certificate and token to prove ownership of a zone
originCert @0 :Data;
# UUID assigned to this cloudflared obtained from Hello
cloudflaredID @1 :Data;
# number of previous attempts to send Connect
numPreviousAttempts @2 :UInt8;
}
struct CapnpServerHello {
connectResult @0 :ConnectResult;
# UUID assigned to this cloudflared
cloudflaredID @1 :Data;
}
struct ConnectResult {
err @0 :ConnectError;
# Information about the server this connection is established with
serverInfo @1 :ServerInfo;
}
struct ConnectError {
cause @0 :Text;
# How long should this connection wait to retry in ns
retryAfter @1 :Int64;
shoudRetry @2 :Bool;
}
struct Tag { struct Tag {
name @0 :Text; name @0 :Text;
value @1 :Text; value @1 :Text;
@ -65,4 +102,8 @@ interface TunnelServer {
registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration); registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
getServerInfo @1 () -> (result :ServerInfo); getServerInfo @1 () -> (result :ServerInfo);
unregisterTunnel @2 (gracePeriodNanoSec :Int64) -> (); unregisterTunnel @2 (gracePeriodNanoSec :Int64) -> ();
# originCert is used to verify ownership of a zone and hostnames it can serve
# tags are client-defined tags to associate with the cloudflared
hello @3(parameters :HelloParameters) -> (result: CapnpServerHello);
connect @4(parameters :CapnpConnectParameters) -> (result :ConnectResult);
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,3 @@
**This package is currently in development and the API may not be stable.**
The API will become stable with v1.
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master) # uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on The uuid package generates and inspects UUIDs based on
[RFC 4122](http://tools.ietf.org/html/rfc4122) [RFC 4122](http://tools.ietf.org/html/rfc4122)

View File

@ -42,7 +42,7 @@ func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person // NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid. // domain with the id returned by os.Getuid.
// //
// NewDCEPerson(Person, uint32(os.Getuid())) // NewDCESecurity(Person, uint32(os.Getuid()))
func NewDCEPerson() (UUID, error) { func NewDCEPerson() (UUID, error) {
return NewDCESecurity(Person, uint32(os.Getuid())) return NewDCESecurity(Person, uint32(os.Getuid()))
} }
@ -50,7 +50,7 @@ func NewDCEPerson() (UUID, error) {
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group // NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid. // domain with the id returned by os.Getgid.
// //
// NewDCEGroup(Group, uint32(os.Getgid())) // NewDCESecurity(Group, uint32(os.Getgid()))
func NewDCEGroup() (UUID, error) { func NewDCEGroup() (UUID, error) {
return NewDCESecurity(Group, uint32(os.Getgid())) return NewDCESecurity(Group, uint32(os.Getgid()))
} }

1
vendor/github.com/google/uuid/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module github.com/google/uuid

View File

@ -27,7 +27,7 @@ var (
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset() h.Reset()
h.Write(space[:]) h.Write(space[:])
h.Write([]byte(data)) h.Write(data)
s := h.Sum(nil) s := h.Sum(nil)
var uuid UUID var uuid UUID
copy(uuid[:], s) copy(uuid[:], s)

View File

@ -15,8 +15,6 @@ func (uuid UUID) MarshalText() ([]byte, error) {
// UnmarshalText implements encoding.TextUnmarshaler. // UnmarshalText implements encoding.TextUnmarshaler.
func (uuid *UUID) UnmarshalText(data []byte) error { func (uuid *UUID) UnmarshalText(data []byte) error {
// See comment in ParseBytes why we do this.
// id, err := ParseBytes(data)
id, err := ParseBytes(data) id, err := ParseBytes(data)
if err == nil { if err == nil {
*uuid = id *uuid = id

View File

@ -5,16 +5,14 @@
package uuid package uuid
import ( import (
"net"
"sync" "sync"
) )
var ( var (
nodeMu sync.Mutex nodeMu sync.Mutex
interfaces []net.Interface // cached list of interfaces ifname string // name of interface being used
ifname string // name of interface being used nodeID [6]byte // hardware for version 1 UUIDs
nodeID [6]byte // hardware for version 1 UUIDs zeroID [6]byte // nodeID with only 0's
zeroID [6]byte // nodeID with only 0's
) )
// NodeInterface returns the name of the interface from which the NodeID was // NodeInterface returns the name of the interface from which the NodeID was
@ -39,26 +37,18 @@ func SetNodeInterface(name string) bool {
} }
func setNodeInterface(name string) bool { func setNodeInterface(name string) bool {
if interfaces == nil { iname, addr := getHardwareInterface(name) // null implementation for js
var err error if iname != "" && addr != nil {
interfaces, err = net.Interfaces() ifname = iname
if err != nil && name != "" { copy(nodeID[:], addr)
return false return true
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
copy(nodeID[:], ifs.HardwareAddr)
ifname = ifs.Name
return true
}
} }
// We found no interfaces with a valid hardware address. If name // We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID // does not specify a specific interface generate a random Node ID
// (section 4.1.6) // (section 4.1.6)
if name == "" { if name == "" {
ifname = "random"
randomBits(nodeID[:]) randomBits(nodeID[:])
return true return true
} }
@ -94,9 +84,6 @@ func SetNodeID(id []byte) bool {
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is // NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. // not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte { func (uuid UUID) NodeID() []byte {
if len(uuid) != 16 {
return nil
}
var node [6]byte var node [6]byte
copy(node[:], uuid[10:]) copy(node[:], uuid[10:])
return node[:] return node[:]

12
vendor/github.com/google/uuid/node_js.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build js
package uuid
// getHardwareInterface returns nil values for the JS version of the code.
// This remvoves the "net" dependency, because it is not used in the browser.
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
func getHardwareInterface(name string) (string, []byte) { return "", nil }

33
vendor/github.com/google/uuid/node_net.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !js
package uuid
import "net"
var interfaces []net.Interface // cached list of interfaces
// getHardwareInterface returns the name and hardware address of interface name.
// If name is "" then the name and hardware address of one of the system's
// interfaces is returned. If no interfaces are found (name does not exist or
// there are no interfaces) then "", nil is returned.
//
// Only addresses of at least 6 bytes are returned.
func getHardwareInterface(name string) (string, []byte) {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil {
return "", nil
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
return ifs.Name, ifs.HardwareAddr
}
}
return "", nil
}

21
vendor/github.com/google/uuid/sql.go generated vendored
View File

@ -13,35 +13,36 @@ import (
// Currently, database types that map to string and []byte are supported. Please // Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types. // consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error { func (uuid *UUID) Scan(src interface{}) error {
switch src.(type) { switch src := src.(type) {
case nil:
return nil
case string: case string:
// if an empty UUID comes from a table, we return a null UUID // if an empty UUID comes from a table, we return a null UUID
if src.(string) == "" { if src == "" {
return nil return nil
} }
// see Parse for required string format // see Parse for required string format
u, err := Parse(src.(string)) u, err := Parse(src)
if err != nil { if err != nil {
return fmt.Errorf("Scan: %v", err) return fmt.Errorf("Scan: %v", err)
} }
*uuid = u *uuid = u
case []byte:
b := src.([]byte)
case []byte:
// if an empty UUID comes from a table, we return a null UUID // if an empty UUID comes from a table, we return a null UUID
if len(b) == 0 { if len(src) == 0 {
return nil return nil
} }
// assumes a simple slice of bytes if 16 bytes // assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse // otherwise attempts to parse
if len(b) != 16 { if len(src) != 16 {
return uuid.Scan(string(b)) return uuid.Scan(string(src))
} }
copy((*uuid)[:], b) copy((*uuid)[:], src)
default: default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src) return fmt.Errorf("Scan: unable to scan type %T into UUID", src)

View File

@ -86,7 +86,7 @@ func clockSequence() int {
return int(clockSeq & 0x3fff) return int(clockSeq & 0x3fff)
} }
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to // SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated. // -1 causes a new sequence to be generated.
func SetClockSequence(seq int) { func SetClockSequence(seq int) {
defer timeMu.Unlock() defer timeMu.Unlock()
@ -100,9 +100,9 @@ func setClockSequence(seq int) {
randomBits(b[:]) // clock sequence randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1]) seq = int(b[0])<<8 | int(b[1])
} }
old_seq := clockSeq oldSeq := clockSeq
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if old_seq != clockSeq { if oldSeq != clockSeq {
lasttime = 0 lasttime = 0
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright 2016 Google Inc. All rights reserved. // Copyright 2018 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -35,20 +35,43 @@ const (
var rander = rand.Reader // random function var rander = rand.Reader // random function
// Parse decodes s into a UUID or returns an error. Both the UUID form of // Parse decodes s into a UUID or returns an error. Both the standard UUID
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and // forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded. // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
func Parse(s string) (UUID, error) { func Parse(s string) (UUID, error) {
var uuid UUID var uuid UUID
if len(s) != 36 { switch len(s) {
if len(s) != 36+9 { // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
return uuid, fmt.Errorf("invalid UUID length: %d", len(s)) case 36:
}
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9:
if strings.ToLower(s[:9]) != "urn:uuid:" { if strings.ToLower(s[:9]) != "urn:uuid:" {
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9]) return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
} }
s = s[9:] s = s[9:]
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
case 36 + 2:
s = s[1:]
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
case 32:
var ok bool
for i := range uuid {
uuid[i], ok = xtob(s[i*2], s[i*2+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
} }
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return uuid, errors.New("invalid UUID format") return uuid, errors.New("invalid UUID format")
} }
@ -58,11 +81,11 @@ func Parse(s string) (UUID, error) {
14, 16, 14, 16,
19, 21, 19, 21,
24, 26, 28, 30, 32, 34} { 24, 26, 28, 30, 32, 34} {
if v, ok := xtob(s[x], s[x+1]); !ok { v, ok := xtob(s[x], s[x+1])
if !ok {
return uuid, errors.New("invalid UUID format") return uuid, errors.New("invalid UUID format")
} else {
uuid[i] = v
} }
uuid[i] = v
} }
return uuid, nil return uuid, nil
} }
@ -70,15 +93,29 @@ func Parse(s string) (UUID, error) {
// ParseBytes is like Parse, except it parses a byte slice instead of a string. // ParseBytes is like Parse, except it parses a byte slice instead of a string.
func ParseBytes(b []byte) (UUID, error) { func ParseBytes(b []byte) (UUID, error) {
var uuid UUID var uuid UUID
if len(b) != 36 { switch len(b) {
if len(b) != 36+9 { case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
return uuid, fmt.Errorf("invalid UUID length: %d", len(b)) case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
}
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) { if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9]) return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
} }
b = b[9:] b = b[9:]
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
b = b[1:]
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
var ok bool
for i := 0; i < 32; i += 2 {
uuid[i/2], ok = xtob(b[i], b[i+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
} }
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
return uuid, errors.New("invalid UUID format") return uuid, errors.New("invalid UUID format")
} }
@ -88,15 +125,32 @@ func ParseBytes(b []byte) (UUID, error) {
14, 16, 14, 16,
19, 21, 19, 21,
24, 26, 28, 30, 32, 34} { 24, 26, 28, 30, 32, 34} {
if v, ok := xtob(b[x], b[x+1]); !ok { v, ok := xtob(b[x], b[x+1])
if !ok {
return uuid, errors.New("invalid UUID format") return uuid, errors.New("invalid UUID format")
} else {
uuid[i] = v
} }
uuid[i] = v
} }
return uuid, nil return uuid, nil
} }
// MustParse is like Parse but panics if the string cannot be parsed.
// It simplifies safe initialization of global variables holding compiled UUIDs.
func MustParse(s string) UUID {
uuid, err := Parse(s)
if err != nil {
panic(`uuid: Parse(` + s + `): ` + err.Error())
}
return uuid
}
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
// does not have a length of 16. The bytes are copied from the slice.
func FromBytes(b []byte) (uuid UUID, err error) {
err = uuid.UnmarshalBinary(b)
return uuid, err
}
// Must returns uuid if err is nil and panics otherwise. // Must returns uuid if err is nil and panics otherwise.
func Must(uuid UUID, err error) UUID { func Must(uuid UUID, err error) UUID {
if err != nil { if err != nil {
@ -123,7 +177,7 @@ func (uuid UUID) URN() string {
} }
func encodeHex(dst []byte, uuid UUID) { func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst[:], uuid[:4]) hex.Encode(dst, uuid[:4])
dst[8] = '-' dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6]) hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-' dst[13] = '-'
@ -176,7 +230,7 @@ func (v Variant) String() string {
return fmt.Sprintf("BadVariant%d", int(v)) return fmt.Sprintf("BadVariant%d", int(v))
} }
// SetRand sets the random number generator to r, which implents io.Reader. // SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then // If r.Read returns an error when the package requests random data then
// a panic will be issued. // a panic will be issued.
// //

View File

@ -13,7 +13,7 @@ import (
// or SetNodeInterface then it will be set automatically. If the NodeID cannot // or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by // be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to // SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns Nil and an error. // return the current NewUUID returns nil and an error.
// //
// In most cases, New should be used. // In most cases, New should be used.
func NewUUID() (UUID, error) { func NewUUID() (UUID, error) {

View File

@ -6,7 +6,7 @@ package uuid
import "io" import "io"
// New is creates a new random UUID or panics. New is equivalent to // New creates a new random UUID or panics. New is equivalent to
// the expression // the expression
// //
// uuid.Must(uuid.NewRandom()) // uuid.Must(uuid.NewRandom())
@ -14,12 +14,12 @@ func New() UUID {
return Must(NewRandom()) return Must(NewRandom())
} }
// NewRandom returns a Random (Version 4) UUID or panics. // NewRandom returns a Random (Version 4) UUID.
// //
// The strength of the UUIDs is based on the strength of the crypto/rand // The strength of the UUIDs is based on the strength of the crypto/rand
// package. // package.
// //
// A note about uniqueness derived from from the UUID Wikipedia entry: // A note about uniqueness derived from the UUID Wikipedia entry:
// //
// Randomly generated UUIDs have 122 random bits. One's annual risk of being // Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that // hit by a meteorite is estimated to be one chance in 17 billion, that