Extend hcl2 support with more functions
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>pull/491/head
parent
74f76cf4e9
commit
96e7f3224a
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2015 Martin Atkins
|
||||||
|
|
||||||
|
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.
|
@ -0,0 +1,218 @@
|
|||||||
|
// Package cidr is a collection of assorted utilities for computing
|
||||||
|
// network and host addresses within network ranges.
|
||||||
|
//
|
||||||
|
// It expects a CIDR-type address structure where addresses are divided into
|
||||||
|
// some number of prefix bits representing the network and then the remaining
|
||||||
|
// suffix bits represent the host.
|
||||||
|
//
|
||||||
|
// For example, it can help to calculate addresses for sub-networks of a
|
||||||
|
// parent network, or to calculate host addresses within a particular prefix.
|
||||||
|
//
|
||||||
|
// At present this package is prioritizing simplicity of implementation and
|
||||||
|
// de-prioritizing speed and memory usage. Thus caution is advised before
|
||||||
|
// using this package in performance-critical applications or hot codepaths.
|
||||||
|
// Patches to improve the speed and memory usage may be accepted as long as
|
||||||
|
// they do not result in a significant increase in code complexity.
|
||||||
|
package cidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Subnet takes a parent CIDR range and creates a subnet within it
|
||||||
|
// with the given number of additional prefix bits and the given
|
||||||
|
// network number.
|
||||||
|
//
|
||||||
|
// For example, 10.3.0.0/16, extended by 8 bits, with a network number
|
||||||
|
// of 5, becomes 10.3.5.0/24 .
|
||||||
|
func Subnet(base *net.IPNet, newBits int, num int) (*net.IPNet, error) {
|
||||||
|
ip := base.IP
|
||||||
|
mask := base.Mask
|
||||||
|
|
||||||
|
parentLen, addrLen := mask.Size()
|
||||||
|
newPrefixLen := parentLen + newBits
|
||||||
|
|
||||||
|
if newPrefixLen > addrLen {
|
||||||
|
return nil, fmt.Errorf("insufficient address space to extend prefix of %d by %d", parentLen, newBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxNetNum := uint64(1<<uint64(newBits)) - 1
|
||||||
|
if uint64(num) > maxNetNum {
|
||||||
|
return nil, fmt.Errorf("prefix extension of %d does not accommodate a subnet numbered %d", newBits, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &net.IPNet{
|
||||||
|
IP: insertNumIntoIP(ip, big.NewInt(int64(num)), newPrefixLen),
|
||||||
|
Mask: net.CIDRMask(newPrefixLen, addrLen),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host takes a parent CIDR range and turns it into a host IP address with
|
||||||
|
// the given host number.
|
||||||
|
//
|
||||||
|
// For example, 10.3.0.0/16 with a host number of 2 gives 10.3.0.2.
|
||||||
|
func Host(base *net.IPNet, num int) (net.IP, error) {
|
||||||
|
ip := base.IP
|
||||||
|
mask := base.Mask
|
||||||
|
bigNum := big.NewInt(int64(num))
|
||||||
|
|
||||||
|
parentLen, addrLen := mask.Size()
|
||||||
|
hostLen := addrLen - parentLen
|
||||||
|
|
||||||
|
maxHostNum := big.NewInt(int64(1))
|
||||||
|
maxHostNum.Lsh(maxHostNum, uint(hostLen))
|
||||||
|
maxHostNum.Sub(maxHostNum, big.NewInt(1))
|
||||||
|
|
||||||
|
numUint64 := big.NewInt(int64(bigNum.Uint64()))
|
||||||
|
if bigNum.Cmp(big.NewInt(0)) == -1 {
|
||||||
|
numUint64.Neg(bigNum)
|
||||||
|
numUint64.Sub(numUint64, big.NewInt(int64(1)))
|
||||||
|
bigNum.Sub(maxHostNum, numUint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
if numUint64.Cmp(maxHostNum) == 1 {
|
||||||
|
return nil, fmt.Errorf("prefix of %d does not accommodate a host numbered %d", parentLen, num)
|
||||||
|
}
|
||||||
|
var bitlength int
|
||||||
|
if ip.To4() != nil {
|
||||||
|
bitlength = 32
|
||||||
|
} else {
|
||||||
|
bitlength = 128
|
||||||
|
}
|
||||||
|
return insertNumIntoIP(ip, bigNum, bitlength), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddressRange returns the first and last addresses in the given CIDR range.
|
||||||
|
func AddressRange(network *net.IPNet) (net.IP, net.IP) {
|
||||||
|
// the first IP is easy
|
||||||
|
firstIP := network.IP
|
||||||
|
|
||||||
|
// the last IP is the network address OR NOT the mask address
|
||||||
|
prefixLen, bits := network.Mask.Size()
|
||||||
|
if prefixLen == bits {
|
||||||
|
// Easy!
|
||||||
|
// But make sure that our two slices are distinct, since they
|
||||||
|
// would be in all other cases.
|
||||||
|
lastIP := make([]byte, len(firstIP))
|
||||||
|
copy(lastIP, firstIP)
|
||||||
|
return firstIP, lastIP
|
||||||
|
}
|
||||||
|
|
||||||
|
firstIPInt, bits := ipToInt(firstIP)
|
||||||
|
hostLen := uint(bits) - uint(prefixLen)
|
||||||
|
lastIPInt := big.NewInt(1)
|
||||||
|
lastIPInt.Lsh(lastIPInt, hostLen)
|
||||||
|
lastIPInt.Sub(lastIPInt, big.NewInt(1))
|
||||||
|
lastIPInt.Or(lastIPInt, firstIPInt)
|
||||||
|
|
||||||
|
return firstIP, intToIP(lastIPInt, bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddressCount returns the number of distinct host addresses within the given
|
||||||
|
// CIDR range.
|
||||||
|
//
|
||||||
|
// Since the result is a uint64, this function returns meaningful information
|
||||||
|
// only for IPv4 ranges and IPv6 ranges with a prefix size of at least 65.
|
||||||
|
func AddressCount(network *net.IPNet) uint64 {
|
||||||
|
prefixLen, bits := network.Mask.Size()
|
||||||
|
return 1 << (uint64(bits) - uint64(prefixLen))
|
||||||
|
}
|
||||||
|
|
||||||
|
//VerifyNoOverlap takes a list subnets and supernet (CIDRBlock) and verifies
|
||||||
|
//none of the subnets overlap and all subnets are in the supernet
|
||||||
|
//it returns an error if any of those conditions are not satisfied
|
||||||
|
func VerifyNoOverlap(subnets []*net.IPNet, CIDRBlock *net.IPNet) error {
|
||||||
|
firstLastIP := make([][]net.IP, len(subnets))
|
||||||
|
for i, s := range subnets {
|
||||||
|
first, last := AddressRange(s)
|
||||||
|
firstLastIP[i] = []net.IP{first, last}
|
||||||
|
}
|
||||||
|
for i, s := range subnets {
|
||||||
|
if !CIDRBlock.Contains(firstLastIP[i][0]) || !CIDRBlock.Contains(firstLastIP[i][1]) {
|
||||||
|
return fmt.Errorf("%s does not fully contain %s", CIDRBlock.String(), s.String())
|
||||||
|
}
|
||||||
|
for j := 0; j < len(subnets); j++ {
|
||||||
|
if i == j {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
first := firstLastIP[j][0]
|
||||||
|
last := firstLastIP[j][1]
|
||||||
|
if s.Contains(first) || s.Contains(last) {
|
||||||
|
return fmt.Errorf("%s overlaps with %s", subnets[j].String(), s.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreviousSubnet returns the subnet of the desired mask in the IP space
|
||||||
|
// just lower than the start of IPNet provided. If the IP space rolls over
|
||||||
|
// then the second return value is true
|
||||||
|
func PreviousSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, bool) {
|
||||||
|
startIP := checkIPv4(network.IP)
|
||||||
|
previousIP := make(net.IP, len(startIP))
|
||||||
|
copy(previousIP, startIP)
|
||||||
|
cMask := net.CIDRMask(prefixLen, 8*len(previousIP))
|
||||||
|
previousIP = Dec(previousIP)
|
||||||
|
previous := &net.IPNet{IP: previousIP.Mask(cMask), Mask: cMask}
|
||||||
|
if startIP.Equal(net.IPv4zero) || startIP.Equal(net.IPv6zero) {
|
||||||
|
return previous, true
|
||||||
|
}
|
||||||
|
return previous, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextSubnet returns the next available subnet of the desired mask size
|
||||||
|
// starting for the maximum IP of the offset subnet
|
||||||
|
// If the IP exceeds the maxium IP then the second return value is true
|
||||||
|
func NextSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, bool) {
|
||||||
|
_, currentLast := AddressRange(network)
|
||||||
|
mask := net.CIDRMask(prefixLen, 8*len(currentLast))
|
||||||
|
currentSubnet := &net.IPNet{IP: currentLast.Mask(mask), Mask: mask}
|
||||||
|
_, last := AddressRange(currentSubnet)
|
||||||
|
last = Inc(last)
|
||||||
|
next := &net.IPNet{IP: last.Mask(mask), Mask: mask}
|
||||||
|
if last.Equal(net.IPv4zero) || last.Equal(net.IPv6zero) {
|
||||||
|
return next, true
|
||||||
|
}
|
||||||
|
return next, false
|
||||||
|
}
|
||||||
|
|
||||||
|
//Inc increases the IP by one this returns a new []byte for the IP
|
||||||
|
func Inc(IP net.IP) net.IP {
|
||||||
|
IP = checkIPv4(IP)
|
||||||
|
incIP := make([]byte, len(IP))
|
||||||
|
copy(incIP, IP)
|
||||||
|
for j := len(incIP) - 1; j >= 0; j-- {
|
||||||
|
incIP[j]++
|
||||||
|
if incIP[j] > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return incIP
|
||||||
|
}
|
||||||
|
|
||||||
|
//Dec decreases the IP by one this returns a new []byte for the IP
|
||||||
|
func Dec(IP net.IP) net.IP {
|
||||||
|
IP = checkIPv4(IP)
|
||||||
|
decIP := make([]byte, len(IP))
|
||||||
|
copy(decIP, IP)
|
||||||
|
decIP = checkIPv4(decIP)
|
||||||
|
for j := len(decIP) - 1; j >= 0; j-- {
|
||||||
|
decIP[j]--
|
||||||
|
if decIP[j] < 255 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decIP
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkIPv4(ip net.IP) net.IP {
|
||||||
|
// Go for some reason allocs IPv6len for IPv4 so we have to correct it
|
||||||
|
if v4 := ip.To4(); v4 != nil {
|
||||||
|
return v4
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package cidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ipToInt(ip net.IP) (*big.Int, int) {
|
||||||
|
val := &big.Int{}
|
||||||
|
val.SetBytes([]byte(ip))
|
||||||
|
if len(ip) == net.IPv4len {
|
||||||
|
return val, 32
|
||||||
|
} else if len(ip) == net.IPv6len {
|
||||||
|
return val, 128
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("Unsupported address length %d", len(ip)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intToIP(ipInt *big.Int, bits int) net.IP {
|
||||||
|
ipBytes := ipInt.Bytes()
|
||||||
|
ret := make([]byte, bits/8)
|
||||||
|
// Pack our IP bytes into the end of the return array,
|
||||||
|
// since big.Int.Bytes() removes front zero padding.
|
||||||
|
for i := 1; i <= len(ipBytes); i++ {
|
||||||
|
ret[len(ret)-i] = ipBytes[len(ipBytes)-i]
|
||||||
|
}
|
||||||
|
return net.IP(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertNumIntoIP(ip net.IP, bigNum *big.Int, prefixLen int) net.IP {
|
||||||
|
ipInt, totalBits := ipToInt(ip)
|
||||||
|
bigNum.Lsh(bigNum, uint(totalBits-prefixLen))
|
||||||
|
ipInt.Or(ipInt, bigNum)
|
||||||
|
return intToIP(ipInt, totalBits)
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4.3
|
||||||
|
- 1.5.3
|
||||||
|
- tip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v ./...
|
@ -0,0 +1,10 @@
|
|||||||
|
# How to contribute
|
||||||
|
|
||||||
|
We definitely welcome patches and contribution to this project!
|
||||||
|
|
||||||
|
### Legal requirements
|
||||||
|
|
||||||
|
In order to protect both you and ourselves, you will need to sign the
|
||||||
|
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||||
|
|
||||||
|
You may have already signed it for other Google projects.
|
@ -0,0 +1,9 @@
|
|||||||
|
Paul Borman <borman@google.com>
|
||||||
|
bmatsuo
|
||||||
|
shawnps
|
||||||
|
theory
|
||||||
|
jboverfelt
|
||||||
|
dsymonds
|
||||||
|
cd1
|
||||||
|
wallclockbuilder
|
||||||
|
dansouza
|
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,19 @@
|
|||||||
|
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
|
||||||
|
The uuid package generates and inspects UUIDs based on
|
||||||
|
[RFC 4122](http://tools.ietf.org/html/rfc4122)
|
||||||
|
and DCE 1.1: Authentication and Security Services.
|
||||||
|
|
||||||
|
This package is based on the github.com/pborman/uuid package (previously named
|
||||||
|
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||||
|
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||||
|
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||||
|
|
||||||
|
###### Install
|
||||||
|
`go get github.com/google/uuid`
|
||||||
|
|
||||||
|
###### Documentation
|
||||||
|
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the package can be viewed online without
|
||||||
|
installing this package by using the GoDoc site here:
|
||||||
|
http://godoc.org/github.com/google/uuid
|
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Domain represents a Version 2 domain
|
||||||
|
type Domain byte
|
||||||
|
|
||||||
|
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||||
|
const (
|
||||||
|
Person = Domain(0)
|
||||||
|
Group = Domain(1)
|
||||||
|
Org = Domain(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||||
|
//
|
||||||
|
// The domain should be one of Person, Group or Org.
|
||||||
|
// On a POSIX system the id should be the users UID for the Person
|
||||||
|
// domain and the users GID for the Group. The meaning of id for
|
||||||
|
// the domain Org or on non-POSIX systems is site defined.
|
||||||
|
//
|
||||||
|
// For a given domain/id pair the same token may be returned for up to
|
||||||
|
// 7 minutes and 10 seconds.
|
||||||
|
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||||
|
uuid, err := NewUUID()
|
||||||
|
if err == nil {
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||||
|
uuid[9] = byte(domain)
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||||
|
}
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||||
|
// domain with the id returned by os.Getuid.
|
||||||
|
//
|
||||||
|
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||||
|
func NewDCEPerson() (UUID, error) {
|
||||||
|
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||||
|
// domain with the id returned by os.Getgid.
|
||||||
|
//
|
||||||
|
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||||
|
func NewDCEGroup() (UUID, error) {
|
||||||
|
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||||
|
// for Version 2 UUIDs.
|
||||||
|
func (uuid UUID) Domain() Domain {
|
||||||
|
return Domain(uuid[9])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||||
|
// UUIDs.
|
||||||
|
func (uuid UUID) ID() uint32 {
|
||||||
|
return binary.BigEndian.Uint32(uuid[0:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Domain) String() string {
|
||||||
|
switch d {
|
||||||
|
case Person:
|
||||||
|
return "Person"
|
||||||
|
case Group:
|
||||||
|
return "Group"
|
||||||
|
case Org:
|
||||||
|
return "Org"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Domain%d", int(d))
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// Package uuid generates and inspects UUIDs.
|
||||||
|
//
|
||||||
|
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||||
|
// Services.
|
||||||
|
//
|
||||||
|
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||||
|
// maps or compared directly.
|
||||||
|
package uuid
|
@ -0,0 +1 @@
|
|||||||
|
module github.com/google/uuid
|
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Well known namespace IDs and UUIDs
|
||||||
|
var (
|
||||||
|
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
Nil UUID // empty UUID, all zeros
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||||
|
// data generated by h. The hash should be at least 16 byte in length. The
|
||||||
|
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||||
|
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||||
|
// NewMD5 and NewSHA1.
|
||||||
|
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||||
|
h.Reset()
|
||||||
|
h.Write(space[:])
|
||||||
|
h.Write(data)
|
||||||
|
s := h.Sum(nil)
|
||||||
|
var uuid UUID
|
||||||
|
copy(uuid[:], s)
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||||
|
// supplied name space and data. It is the same as calling:
|
||||||
|
//
|
||||||
|
// NewHash(md5.New(), space, data, 3)
|
||||||
|
func NewMD5(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(md5.New(), space, data, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||||
|
// supplied name space and data. It is the same as calling:
|
||||||
|
//
|
||||||
|
// NewHash(sha1.New(), space, data, 5)
|
||||||
|
func NewSHA1(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(sha1.New(), space, data, 5)
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// MarshalText implements encoding.TextMarshaler.
|
||||||
|
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||||
|
var js [36]byte
|
||||||
|
encodeHex(js[:], uuid)
|
||||||
|
return js[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
|
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||||
|
id, err := ParseBytes(data)
|
||||||
|
if err == nil {
|
||||||
|
*uuid = id
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||||
|
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||||
|
return uuid[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||||
|
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||||
|
if len(data) != 16 {
|
||||||
|
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||||
|
}
|
||||||
|
copy(uuid[:], data)
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
nodeMu sync.Mutex
|
||||||
|
ifname string // name of interface being used
|
||||||
|
nodeID [6]byte // hardware for version 1 UUIDs
|
||||||
|
zeroID [6]byte // nodeID with only 0's
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeInterface returns the name of the interface from which the NodeID was
|
||||||
|
// derived. The interface "user" is returned if the NodeID was set by
|
||||||
|
// SetNodeID.
|
||||||
|
func NodeInterface() string {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
return ifname
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||||
|
// If name is "" then the first usable interface found will be used or a random
|
||||||
|
// Node ID will be generated. If a named interface cannot be found then false
|
||||||
|
// is returned.
|
||||||
|
//
|
||||||
|
// SetNodeInterface never fails when name is "".
|
||||||
|
func SetNodeInterface(name string) bool {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
return setNodeInterface(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNodeInterface(name string) bool {
|
||||||
|
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||||
|
if iname != "" && addr != nil {
|
||||||
|
ifname = iname
|
||||||
|
copy(nodeID[:], addr)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found no interfaces with a valid hardware address. If name
|
||||||
|
// does not specify a specific interface generate a random Node ID
|
||||||
|
// (section 4.1.6)
|
||||||
|
if name == "" {
|
||||||
|
ifname = "random"
|
||||||
|
randomBits(nodeID[:])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||||
|
// if not already set.
|
||||||
|
func NodeID() []byte {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
if nodeID == zeroID {
|
||||||
|
setNodeInterface("")
|
||||||
|
}
|
||||||
|
nid := nodeID
|
||||||
|
return nid[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||||
|
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||||
|
// Node ID is not set.
|
||||||
|
func SetNodeID(id []byte) bool {
|
||||||
|
if len(id) < 6 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
copy(nodeID[:], id)
|
||||||
|
ifname = "user"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (uuid UUID) NodeID() []byte {
|
||||||
|
var node [6]byte
|
||||||
|
copy(node[:], uuid[10:])
|
||||||
|
return node[:]
|
||||||
|
}
|
@ -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 }
|
@ -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
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
|
||||||
|
// Currently, database types that map to string and []byte are supported. Please
|
||||||
|
// consult database-specific driver documentation for matching types.
|
||||||
|
func (uuid *UUID) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case nil:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case string:
|
||||||
|
// if an empty UUID comes from a table, we return a null UUID
|
||||||
|
if src == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// see Parse for required string format
|
||||||
|
u, err := Parse(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Scan: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*uuid = u
|
||||||
|
|
||||||
|
case []byte:
|
||||||
|
// if an empty UUID comes from a table, we return a null UUID
|
||||||
|
if len(src) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes a simple slice of bytes if 16 bytes
|
||||||
|
// otherwise attempts to parse
|
||||||
|
if len(src) != 16 {
|
||||||
|
return uuid.Scan(string(src))
|
||||||
|
}
|
||||||
|
copy((*uuid)[:], src)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||||
|
// transparently. Currently, UUIDs map to strings. Please consult
|
||||||
|
// database-specific driver documentation for matching types.
|
||||||
|
func (uuid UUID) Value() (driver.Value, error) {
|
||||||
|
return uuid.String(), nil
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||||
|
// 1582.
|
||||||
|
type Time int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||||
|
unix = 2440587 // Julian day of 1 Jan 1970
|
||||||
|
epoch = unix - lillian // Days between epochs
|
||||||
|
g1582 = epoch * 86400 // seconds between epochs
|
||||||
|
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
timeMu sync.Mutex
|
||||||
|
lasttime uint64 // last time we returned
|
||||||
|
clockSeq uint16 // clock sequence for this run
|
||||||
|
|
||||||
|
timeNow = time.Now // for testing
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||||
|
// epoch of 1 Jan 1970.
|
||||||
|
func (t Time) UnixTime() (sec, nsec int64) {
|
||||||
|
sec = int64(t - g1582ns100)
|
||||||
|
nsec = (sec % 10000000) * 100
|
||||||
|
sec /= 10000000
|
||||||
|
return sec, nsec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||||
|
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||||
|
// is returned if the current time cannot be determined.
|
||||||
|
func GetTime() (Time, uint16, error) {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
return getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTime() (Time, uint16, error) {
|
||||||
|
t := timeNow()
|
||||||
|
|
||||||
|
// If we don't have a clock sequence already, set one.
|
||||||
|
if clockSeq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||||
|
|
||||||
|
// If time has gone backwards with this clock sequence then we
|
||||||
|
// increment the clock sequence
|
||||||
|
if now <= lasttime {
|
||||||
|
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||||
|
}
|
||||||
|
lasttime = now
|
||||||
|
return Time(now), clockSeq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the current clock sequence, generating one if not
|
||||||
|
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||||
|
//
|
||||||
|
// The uuid package does not use global static storage for the clock sequence or
|
||||||
|
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||||
|
// random clock sequence is generated the first time a clock sequence is
|
||||||
|
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||||
|
func ClockSequence() int {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
return clockSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clockSequence() int {
|
||||||
|
if clockSeq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
return int(clockSeq & 0x3fff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||||
|
// -1 causes a new sequence to be generated.
|
||||||
|
func SetClockSequence(seq int) {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
setClockSequence(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClockSequence(seq int) {
|
||||||
|
if seq == -1 {
|
||||||
|
var b [2]byte
|
||||||
|
randomBits(b[:]) // clock sequence
|
||||||
|
seq = int(b[0])<<8 | int(b[1])
|
||||||
|
}
|
||||||
|
oldSeq := clockSeq
|
||||||
|
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||||
|
if oldSeq != clockSeq {
|
||||||
|
lasttime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||||
|
// uuid. The time is only defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) Time() Time {
|
||||||
|
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||||
|
return Time(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the clock sequence encoded in uuid.
|
||||||
|
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) ClockSequence() int {
|
||||||
|
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// randomBits completely fills slice b with random data.
|
||||||
|
func randomBits(b []byte) {
|
||||||
|
if _, err := io.ReadFull(rander, b); err != nil {
|
||||||
|
panic(err.Error()) // rand should never fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||||
|
var xvalues = [256]byte{
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
// xtob converts hex characters x1 and x2 into a byte.
|
||||||
|
func xtob(x1, x2 byte) (byte, bool) {
|
||||||
|
b1 := xvalues[x1]
|
||||||
|
b2 := xvalues[x2]
|
||||||
|
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||||
|
}
|
@ -0,0 +1,245 @@
|
|||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||||
|
// 4122.
|
||||||
|
type UUID [16]byte
|
||||||
|
|
||||||
|
// A Version represents a UUID's version.
|
||||||
|
type Version byte
|
||||||
|
|
||||||
|
// A Variant represents a UUID's variant.
|
||||||
|
type Variant byte
|
||||||
|
|
||||||
|
// Constants returned by Variant.
|
||||||
|
const (
|
||||||
|
Invalid = Variant(iota) // Invalid UUID
|
||||||
|
RFC4122 // The variant specified in RFC4122
|
||||||
|
Reserved // Reserved, NCS backward compatibility.
|
||||||
|
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||||
|
Future // Reserved for future definition.
|
||||||
|
)
|
||||||
|
|
||||||
|
var rander = rand.Reader // random function
|
||||||
|
|
||||||
|
// Parse decodes s into a UUID or returns an error. Both the standard UUID
|
||||||
|
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||||
|
// 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) {
|
||||||
|
var uuid UUID
|
||||||
|
switch len(s) {
|
||||||
|
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36:
|
||||||
|
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36 + 9:
|
||||||
|
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||||
|
return uuid, fmt.Errorf("invalid urn prefix: %q", 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] != '-' {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
for i, x := range [16]int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34} {
|
||||||
|
v, ok := xtob(s[x], s[x+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
uuid[i] = v
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||||
|
func ParseBytes(b []byte) (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
switch len(b) {
|
||||||
|
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
|
||||||
|
return uuid, fmt.Errorf("invalid urn prefix: %q", 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] != '-' {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
for i, x := range [16]int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34} {
|
||||||
|
v, ok := xtob(b[x], b[x+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
uuid[i] = v
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
func Must(uuid UUID, err error) UUID {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
// , or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) String() string {
|
||||||
|
var buf [36]byte
|
||||||
|
encodeHex(buf[:], uuid)
|
||||||
|
return string(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// URN returns the RFC 2141 URN form of uuid,
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) URN() string {
|
||||||
|
var buf [36 + 9]byte
|
||||||
|
copy(buf[:], "urn:uuid:")
|
||||||
|
encodeHex(buf[9:], uuid)
|
||||||
|
return string(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeHex(dst []byte, uuid UUID) {
|
||||||
|
hex.Encode(dst, uuid[:4])
|
||||||
|
dst[8] = '-'
|
||||||
|
hex.Encode(dst[9:13], uuid[4:6])
|
||||||
|
dst[13] = '-'
|
||||||
|
hex.Encode(dst[14:18], uuid[6:8])
|
||||||
|
dst[18] = '-'
|
||||||
|
hex.Encode(dst[19:23], uuid[8:10])
|
||||||
|
dst[23] = '-'
|
||||||
|
hex.Encode(dst[24:], uuid[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the variant encoded in uuid.
|
||||||
|
func (uuid UUID) Variant() Variant {
|
||||||
|
switch {
|
||||||
|
case (uuid[8] & 0xc0) == 0x80:
|
||||||
|
return RFC4122
|
||||||
|
case (uuid[8] & 0xe0) == 0xc0:
|
||||||
|
return Microsoft
|
||||||
|
case (uuid[8] & 0xe0) == 0xe0:
|
||||||
|
return Future
|
||||||
|
default:
|
||||||
|
return Reserved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the version of uuid.
|
||||||
|
func (uuid UUID) Version() Version {
|
||||||
|
return Version(uuid[6] >> 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
if v > 15 {
|
||||||
|
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("VERSION_%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Variant) String() string {
|
||||||
|
switch v {
|
||||||
|
case RFC4122:
|
||||||
|
return "RFC4122"
|
||||||
|
case Reserved:
|
||||||
|
return "Reserved"
|
||||||
|
case Microsoft:
|
||||||
|
return "Microsoft"
|
||||||
|
case Future:
|
||||||
|
return "Future"
|
||||||
|
case Invalid:
|
||||||
|
return "Invalid"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("BadVariant%d", int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// a panic will be issued.
|
||||||
|
//
|
||||||
|
// Calling SetRand with nil sets the random number generator to the default
|
||||||
|
// generator.
|
||||||
|
func SetRand(r io.Reader) {
|
||||||
|
if r == nil {
|
||||||
|
rander = rand.Reader
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rander = r
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||||
|
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||||
|
// 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
|
||||||
|
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||||
|
// return the current NewUUID returns nil and an error.
|
||||||
|
//
|
||||||
|
// In most cases, New should be used.
|
||||||
|
func NewUUID() (UUID, error) {
|
||||||
|
nodeMu.Lock()
|
||||||
|
if nodeID == zeroID {
|
||||||
|
setNodeInterface("")
|
||||||
|
}
|
||||||
|
nodeMu.Unlock()
|
||||||
|
|
||||||
|
var uuid UUID
|
||||||
|
now, seq, err := GetTime()
|
||||||
|
if err != nil {
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeLow := uint32(now & 0xffffffff)
|
||||||
|
timeMid := uint16((now >> 32) & 0xffff)
|
||||||
|
timeHi := uint16((now >> 48) & 0x0fff)
|
||||||
|
timeHi |= 0x1000 // Version 1
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||||
|
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||||
|
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||||
|
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||||
|
copy(uuid[10:], nodeID[:])
|
||||||
|
|
||||||
|
return uuid, nil
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package cidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-cidr/cidr"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HostFunc is a function that calculates a full host IP address within a given
|
||||||
|
// IP network address prefix.
|
||||||
|
var HostFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "prefix",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "hostnum",
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
var hostNum int
|
||||||
|
if err := gocty.FromCtyValue(args[1], &hostNum); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := cidr.Host(network, hostNum)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.StringVal(ip.String()), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Host calculates a full host IP address within a given IP network address prefix.
|
||||||
|
func Host(prefix, hostnum cty.Value) (cty.Value, error) {
|
||||||
|
return HostFunc.Call([]cty.Value{prefix, hostnum})
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package cidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetmaskFunc is a function that converts an IPv4 address prefix given in CIDR
|
||||||
|
// notation into a subnet mask address.
|
||||||
|
var NetmaskFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "prefix",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.StringVal(net.IP(network.Mask).String()), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Netmask converts an IPv4 address prefix given in CIDR notation into a subnet mask address.
|
||||||
|
func Netmask(prefix cty.Value) (cty.Value, error) {
|
||||||
|
return NetmaskFunc.Call([]cty.Value{prefix})
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package cidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-cidr/cidr"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubnetFunc is a function that calculates a subnet address within a given
|
||||||
|
// IP network address prefix.
|
||||||
|
var SubnetFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "prefix",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "newbits",
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "netnum",
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
var newbits int
|
||||||
|
if err := gocty.FromCtyValue(args[1], &newbits); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
var netnum int
|
||||||
|
if err := gocty.FromCtyValue(args[2], &netnum); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For portability with 32-bit systems where the subnet number will be
|
||||||
|
// a 32-bit int, we only allow extension of 32 bits in one call even if
|
||||||
|
// we're running on a 64-bit machine. (Of course, this is significant
|
||||||
|
// only for IPv6.)
|
||||||
|
if newbits > 32 {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("may not extend prefix by more than 32 bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
newNetwork, err := cidr.Subnet(network, newbits, netnum)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.StringVal(newNetwork.String()), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Subnet calculates a subnet address within a given IP network address prefix.
|
||||||
|
func Subnet(prefix, newbits, netnum cty.Value) (cty.Value, error) {
|
||||||
|
return SubnetFunc.Call([]cty.Value{prefix, newbits, netnum})
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package cidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-cidr/cidr"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubnetsFunc is similar to SubnetFunc but calculates many consecutive subnet
|
||||||
|
// addresses at once, rather than just a single subnet extension.
|
||||||
|
var SubnetsFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "prefix",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VarParam: &function.Parameter{
|
||||||
|
Name: "newbits",
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.List(cty.String)),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err)
|
||||||
|
}
|
||||||
|
startPrefixLen, _ := network.Mask.Size()
|
||||||
|
|
||||||
|
prefixLengthArgs := args[1:]
|
||||||
|
if len(prefixLengthArgs) == 0 {
|
||||||
|
return cty.ListValEmpty(cty.String), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstLength int
|
||||||
|
if err := gocty.FromCtyValue(prefixLengthArgs[0], &firstLength); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgError(1, err)
|
||||||
|
}
|
||||||
|
firstLength += startPrefixLen
|
||||||
|
|
||||||
|
retVals := make([]cty.Value, len(prefixLengthArgs))
|
||||||
|
|
||||||
|
current, _ := cidr.PreviousSubnet(network, firstLength)
|
||||||
|
for i, lengthArg := range prefixLengthArgs {
|
||||||
|
var length int
|
||||||
|
if err := gocty.FromCtyValue(lengthArg, &length); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgError(i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if length < 1 {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "must extend prefix by at least one bit")
|
||||||
|
}
|
||||||
|
// For portability with 32-bit systems where the subnet number
|
||||||
|
// will be a 32-bit int, we only allow extension of 32 bits in
|
||||||
|
// one call even if we're running on a 64-bit machine.
|
||||||
|
// (Of course, this is significant only for IPv6.)
|
||||||
|
if length > 32 {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "may not extend prefix by more than 32 bits")
|
||||||
|
}
|
||||||
|
length += startPrefixLen
|
||||||
|
if length > (len(network.IP) * 8) {
|
||||||
|
protocol := "IP"
|
||||||
|
switch len(network.IP) * 8 {
|
||||||
|
case 32:
|
||||||
|
protocol = "IPv4"
|
||||||
|
case 128:
|
||||||
|
protocol = "IPv6"
|
||||||
|
}
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "would extend prefix to %d bits, which is too long for an %s address", length, protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
next, rollover := cidr.NextSubnet(current, length)
|
||||||
|
if rollover || !network.Contains(next.IP) {
|
||||||
|
// If we run out of suffix bits in the base CIDR prefix then
|
||||||
|
// NextSubnet will start incrementing the prefix bits, which
|
||||||
|
// we don't allow because it would then allocate addresses
|
||||||
|
// outside of the caller's given prefix.
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "not enough remaining address space for a subnet with a prefix of %d bits after %s", length, current.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
current = next
|
||||||
|
retVals[i] = cty.StringVal(current.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.ListVal(retVals), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Subnets calculates a sequence of consecutive subnet prefixes that may be of
|
||||||
|
// different prefix lengths under a common base prefix.
|
||||||
|
func Subnets(prefix cty.Value, newbits ...cty.Value) (cty.Value, error) {
|
||||||
|
args := make([]cty.Value, len(newbits)+1)
|
||||||
|
args[0] = prefix
|
||||||
|
copy(args[1:], newbits)
|
||||||
|
return SubnetsFunc.Call(args)
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BcryptFunc is a function that computes a hash of the given string using the
|
||||||
|
// Blowfish cipher.
|
||||||
|
var BcryptFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "str",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VarParam: &function.Parameter{
|
||||||
|
Name: "cost",
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
defaultCost := 10
|
||||||
|
|
||||||
|
if len(args) > 1 {
|
||||||
|
var val int
|
||||||
|
if err := gocty.FromCtyValue(args[1], &val); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
defaultCost = val
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 2 {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("bcrypt() takes no more than two arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
input := args[0].AsString()
|
||||||
|
out, err := bcrypt.GenerateFromPassword([]byte(input), defaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("error occured generating password %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.StringVal(string(out)), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bcrypt computes a hash of the given string using the Blowfish cipher,
|
||||||
|
// returning a string in the Modular Crypt Format usually expected in the
|
||||||
|
// shadow password file on many Unix systems.
|
||||||
|
func Bcrypt(str cty.Value, cost ...cty.Value) (cty.Value, error) {
|
||||||
|
args := make([]cty.Value, len(cost)+1)
|
||||||
|
args[0] = str
|
||||||
|
copy(args[1:], cost)
|
||||||
|
return BcryptFunc.Call(args)
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) function.Function {
|
||||||
|
return function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "str",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
s := args[0].AsString()
|
||||||
|
h := hf()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
rv := enc(h.Sum(nil))
|
||||||
|
return cty.StringVal(rv), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Md5Func is a function that computes the MD5 hash of a given string and
|
||||||
|
// encodes it with hexadecimal digits.
|
||||||
|
var Md5Func = makeStringHashFunction(md5.New, hex.EncodeToString)
|
||||||
|
|
||||||
|
// Md5 computes the MD5 hash of a given string and encodes it with hexadecimal
|
||||||
|
// digits.
|
||||||
|
func Md5(str cty.Value) (cty.Value, error) {
|
||||||
|
return Md5Func.Call([]cty.Value{str})
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RsaDecryptFunc is a function that decrypts an RSA-encrypted ciphertext.
|
||||||
|
var RsaDecryptFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "ciphertext",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "privatekey",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
s := args[0].AsString()
|
||||||
|
key := args[1].AsString()
|
||||||
|
|
||||||
|
b, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode input %q: cipher text must be base64-encoded", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(key))
|
||||||
|
if block == nil {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("failed to parse key: no key found")
|
||||||
|
}
|
||||||
|
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf(
|
||||||
|
"failed to parse key: password protected keys are not supported. Please decrypt the key prior to use",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
x509Key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := rsa.DecryptPKCS1v15(nil, x509Key, b)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.StringVal(string(out)), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// RsaDecrypt decrypts an RSA-encrypted ciphertext, returning the corresponding
|
||||||
|
// cleartext.
|
||||||
|
func RsaDecrypt(ciphertext, privatekey cty.Value) (cty.Value, error) {
|
||||||
|
return RsaDecryptFunc.Call([]cty.Value{ciphertext, privatekey})
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sha1Func is a function that computes the SHA1 hash of a given string and
|
||||||
|
// encodes it with hexadecimal digits.
|
||||||
|
var Sha1Func = makeStringHashFunction(sha1.New, hex.EncodeToString)
|
||||||
|
|
||||||
|
// Sha256Func is a function that computes the SHA256 hash of a given string and
|
||||||
|
// encodes it with hexadecimal digits.
|
||||||
|
var Sha256Func = makeStringHashFunction(sha256.New, hex.EncodeToString)
|
||||||
|
|
||||||
|
// Sha512Func is a function that computes the SHA512 hash of a given string and
|
||||||
|
// encodes it with hexadecimal digits.
|
||||||
|
var Sha512Func = makeStringHashFunction(sha512.New, hex.EncodeToString)
|
||||||
|
|
||||||
|
// Sha1 computes the SHA1 hash of a given string and encodes it with
|
||||||
|
// hexadecimal digits.
|
||||||
|
func Sha1(str cty.Value) (cty.Value, error) {
|
||||||
|
return Sha1Func.Call([]cty.Value{str})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sha256 computes the SHA256 hash of a given string and encodes it with
|
||||||
|
// hexadecimal digits.
|
||||||
|
func Sha256(str cty.Value) (cty.Value, error) {
|
||||||
|
return Sha256Func.Call([]cty.Value{str})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sha512 computes the SHA512 hash of a given string and encodes it with
|
||||||
|
// hexadecimal digits.
|
||||||
|
func Sha512(str cty.Value) (cty.Value, error) {
|
||||||
|
return Sha512Func.Call([]cty.Value{str})
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Base64DecodeFunc is a function that decodes a string containing a base64 sequence.
|
||||||
|
var Base64DecodeFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "str",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
s := args[0].AsString()
|
||||||
|
sDec, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode base64 data '%s'", s)
|
||||||
|
}
|
||||||
|
if !utf8.Valid([]byte(sDec)) {
|
||||||
|
log.Printf("[DEBUG] the result of decoding the the provided string is not valid UTF-8: %s", sDec)
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("the result of decoding the the provided string is not valid UTF-8")
|
||||||
|
}
|
||||||
|
return cty.StringVal(string(sDec)), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Base64EncodeFunc is a function that encodes a string to a base64 sequence.
|
||||||
|
var Base64EncodeFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "str",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
return cty.StringVal(base64.StdEncoding.EncodeToString([]byte(args[0].AsString()))), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Base64Decode decodes a string containing a base64 sequence.
|
||||||
|
//
|
||||||
|
// Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4.
|
||||||
|
//
|
||||||
|
// Strings in the Terraform language are sequences of unicode characters rather
|
||||||
|
// than bytes, so this function will also interpret the resulting bytes as
|
||||||
|
// UTF-8. If the bytes after Base64 decoding are _not_ valid UTF-8, this function
|
||||||
|
// produces an error.
|
||||||
|
func Base64Decode(str cty.Value) (cty.Value, error) {
|
||||||
|
return Base64DecodeFunc.Call([]cty.Value{str})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64Encode applies Base64 encoding to a string.
|
||||||
|
//
|
||||||
|
// Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4.
|
||||||
|
//
|
||||||
|
// Strings in the Terraform language are sequences of unicode characters rather
|
||||||
|
// than bytes, so this function will first encode the characters from the string
|
||||||
|
// as UTF-8, and then apply Base64 encoding to the result.
|
||||||
|
func Base64Encode(str cty.Value) (cty.Value, error) {
|
||||||
|
return Base64EncodeFunc.Call([]cty.Value{str})
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URLEncodeFunc is a function that applies URL encoding to a given string.
|
||||||
|
var URLEncodeFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "str",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
return cty.StringVal(url.QueryEscape(args[0].AsString())), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// URLEncode applies URL encoding to a given string.
|
||||||
|
//
|
||||||
|
// This function identifies characters in the given string that would have a
|
||||||
|
// special meaning when included as a query string argument in a URL and
|
||||||
|
// escapes them using RFC 3986 "percent encoding".
|
||||||
|
//
|
||||||
|
// If the given string contains non-ASCII characters, these are first encoded as
|
||||||
|
// UTF-8 and then percent encoding is applied separately to each UTF-8 byte.
|
||||||
|
func URLEncode(str cty.Value) (cty.Value, error) {
|
||||||
|
return URLEncodeFunc.Call([]cty.Value{str})
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
var V4Func = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
uuid, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), err
|
||||||
|
}
|
||||||
|
return cty.StringVal(uuid.String()), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// V4 generates and returns a Type-4 UUID in the standard hexadecimal string
|
||||||
|
// format.
|
||||||
|
//
|
||||||
|
// This is not a "pure" function: it will generate a different result for each
|
||||||
|
// call.
|
||||||
|
func V4() (cty.Value, error) {
|
||||||
|
return V4Func.Call(nil)
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
uuidv5 "github.com/google/uuid"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
var V5Func = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "namespace",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "name",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
var namespace uuidv5.UUID
|
||||||
|
switch {
|
||||||
|
case args[0].AsString() == "dns":
|
||||||
|
namespace = uuidv5.NameSpaceDNS
|
||||||
|
case args[0].AsString() == "url":
|
||||||
|
namespace = uuidv5.NameSpaceURL
|
||||||
|
case args[0].AsString() == "oid":
|
||||||
|
namespace = uuidv5.NameSpaceOID
|
||||||
|
case args[0].AsString() == "x500":
|
||||||
|
namespace = uuidv5.NameSpaceX500
|
||||||
|
default:
|
||||||
|
if namespace, err = uuidv5.Parse(args[0].AsString()); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), fmt.Errorf("uuidv5() doesn't support namespace %s (%v)", args[0].AsString(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val := args[1].AsString()
|
||||||
|
return cty.StringVal(uuidv5.NewSHA1(namespace, []byte(val)).String()), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// V5 generates and returns a Type-5 UUID in the standard hexadecimal
|
||||||
|
// string format.
|
||||||
|
//
|
||||||
|
// This is not a "pure" function: it will generate a different result for each
|
||||||
|
// call.
|
||||||
|
func V5(namespace cty.Value, name cty.Value) (cty.Value, error) {
|
||||||
|
return V5Func.Call([]cty.Value{namespace, name})
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
build: off
|
|
||||||
|
|
||||||
clone_folder: c:\gopath\src\github.com\hashicorp\hcl
|
|
||||||
|
|
||||||
environment:
|
|
||||||
GOPATH: c:\gopath
|
|
||||||
GO111MODULE: on
|
|
||||||
GOPROXY: https://goproxy.io
|
|
||||||
|
|
||||||
stack: go 1.12
|
|
||||||
|
|
||||||
test_script:
|
|
||||||
- go test ./...
|
|
@ -0,0 +1,44 @@
|
|||||||
|
# "Try" and "can" functions
|
||||||
|
|
||||||
|
This Go package contains two `cty` functions intended for use in an
|
||||||
|
`hcl.EvalContext` when evaluating HCL native syntax expressions.
|
||||||
|
|
||||||
|
The first function `try` attempts to evaluate each of its argument expressions
|
||||||
|
in order until one produces a result without any errors.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
try(non_existent_variable, 2) # returns 2
|
||||||
|
```
|
||||||
|
|
||||||
|
If none of the expressions succeed, the function call fails with all of the
|
||||||
|
errors it encountered.
|
||||||
|
|
||||||
|
The second function `can` is similar except that it ignores the result of
|
||||||
|
the given expression altogether and simply returns `true` if the expression
|
||||||
|
produced a successful result or `false` if it produced errors.
|
||||||
|
|
||||||
|
Both of these are primarily intended for working with deep data structures
|
||||||
|
which might not have a dependable shape. For example, we can use `try` to
|
||||||
|
attempt to fetch a value from deep inside a data structure but produce a
|
||||||
|
default value if any step of the traversal fails:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
result = try(foo.deep[0].lots.of["traversals"], null)
|
||||||
|
```
|
||||||
|
|
||||||
|
The final result to `try` should generally be some sort of constant value that
|
||||||
|
will always evaluate successfully.
|
||||||
|
|
||||||
|
## Using these functions
|
||||||
|
|
||||||
|
Languages built on HCL can make `try` and `can` available to user code by
|
||||||
|
exporting them in the `hcl.EvalContext` used for expression evaluation:
|
||||||
|
|
||||||
|
```go
|
||||||
|
ctx := &hcl.EvalContext{
|
||||||
|
Functions: map[string]function.Function{
|
||||||
|
"try": tryfunc.TryFunc,
|
||||||
|
"can": tryfunc.CanFunc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,150 @@
|
|||||||
|
// Package tryfunc contains some optional functions that can be exposed in
|
||||||
|
// HCL-based languages to allow authors to test whether a particular expression
|
||||||
|
// can succeed and take dynamic action based on that result.
|
||||||
|
//
|
||||||
|
// These functions are implemented in terms of the customdecode extension from
|
||||||
|
// the sibling directory "customdecode", and so they are only useful when
|
||||||
|
// used within an HCL EvalContext. Other systems using cty functions are
|
||||||
|
// unlikely to support the HCL-specific "customdecode" extension.
|
||||||
|
package tryfunc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TryFunc is a variadic function that tries to evaluate all of is arguments
|
||||||
|
// in sequence until one succeeds, in which case it returns that result, or
|
||||||
|
// returns an error if none of them succeed.
|
||||||
|
var TryFunc function.Function
|
||||||
|
|
||||||
|
// CanFunc tries to evaluate the expression given in its first argument.
|
||||||
|
var CanFunc function.Function
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
TryFunc = function.New(&function.Spec{
|
||||||
|
VarParam: &function.Parameter{
|
||||||
|
Name: "expressions",
|
||||||
|
Type: customdecode.ExpressionClosureType,
|
||||||
|
},
|
||||||
|
Type: func(args []cty.Value) (cty.Type, error) {
|
||||||
|
v, err := try(args)
|
||||||
|
if err != nil {
|
||||||
|
return cty.NilType, err
|
||||||
|
}
|
||||||
|
return v.Type(), nil
|
||||||
|
},
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
return try(args)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
CanFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "expression",
|
||||||
|
Type: customdecode.ExpressionClosureType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.Bool),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
return can(args[0])
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func try(args []cty.Value) (cty.Value, error) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return cty.NilVal, errors.New("at least one argument is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll collect up all of the diagnostics we encounter along the way
|
||||||
|
// and report them all if none of the expressions succeed, so that the
|
||||||
|
// user might get some hints on how to make at least one succeed.
|
||||||
|
var diags hcl.Diagnostics
|
||||||
|
for _, arg := range args {
|
||||||
|
closure := customdecode.ExpressionClosureFromVal(arg)
|
||||||
|
if dependsOnUnknowns(closure.Expression, closure.EvalContext) {
|
||||||
|
// We can't safely decide if this expression will succeed yet,
|
||||||
|
// and so our entire result must be unknown until we have
|
||||||
|
// more information.
|
||||||
|
return cty.DynamicVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, moreDiags := closure.Value()
|
||||||
|
diags = append(diags, moreDiags...)
|
||||||
|
if moreDiags.HasErrors() {
|
||||||
|
continue // try the next one, if there is one to try
|
||||||
|
}
|
||||||
|
return v, nil // ignore any accumulated diagnostics if one succeeds
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we fall out here then none of the expressions succeeded, and so
|
||||||
|
// we must have at least one diagnostic and we'll return all of them
|
||||||
|
// so that the user can see the errors related to whichever one they
|
||||||
|
// were expecting to have succeeded in this case.
|
||||||
|
//
|
||||||
|
// Because our function must return a single error value rather than
|
||||||
|
// diagnostics, we'll construct a suitable error message string
|
||||||
|
// that will make sense in the context of the function call failure
|
||||||
|
// diagnostic HCL will eventually wrap this in.
|
||||||
|
var buf strings.Builder
|
||||||
|
buf.WriteString("no expression succeeded:\n")
|
||||||
|
for _, diag := range diags {
|
||||||
|
if diag.Subject != nil {
|
||||||
|
buf.WriteString(fmt.Sprintf("- %s (at %s)\n %s\n", diag.Summary, diag.Subject, diag.Detail))
|
||||||
|
} else {
|
||||||
|
buf.WriteString(fmt.Sprintf("- %s\n %s\n", diag.Summary, diag.Detail))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString("\nAt least one expression must produce a successful result")
|
||||||
|
return cty.NilVal, errors.New(buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func can(arg cty.Value) (cty.Value, error) {
|
||||||
|
closure := customdecode.ExpressionClosureFromVal(arg)
|
||||||
|
if dependsOnUnknowns(closure.Expression, closure.EvalContext) {
|
||||||
|
// Can't decide yet, then.
|
||||||
|
return cty.UnknownVal(cty.Bool), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, diags := closure.Value()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return cty.False, nil
|
||||||
|
}
|
||||||
|
return cty.True, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dependsOnUnknowns returns true if any of the variables that the given
|
||||||
|
// expression might access are unknown values or contain unknown values.
|
||||||
|
//
|
||||||
|
// This is a conservative result that prefers to return true if there's any
|
||||||
|
// chance that the expression might derive from an unknown value during its
|
||||||
|
// evaluation; it is likely to produce false-positives for more complex
|
||||||
|
// expressions involving deep data structures.
|
||||||
|
func dependsOnUnknowns(expr hcl.Expression, ctx *hcl.EvalContext) bool {
|
||||||
|
for _, traversal := range expr.Variables() {
|
||||||
|
val, diags := traversal.TraverseAbs(ctx)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
// If the traversal returned a definitive error then it must
|
||||||
|
// not traverse through any unknowns.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !val.IsWhollyKnown() {
|
||||||
|
// The value will be unknown if either it refers directly to
|
||||||
|
// an unknown value or if the traversal moves through an unknown
|
||||||
|
// collection. We're using IsWhollyKnown, so this also catches
|
||||||
|
// situations where the traversal refers to a compound data
|
||||||
|
// structure that contains any unknown values. That's important,
|
||||||
|
// because during evaluation the expression might evaluate more
|
||||||
|
// deeply into this structure and encounter the unknowns.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
# HCL Type Expressions Extension
|
||||||
|
|
||||||
|
This HCL extension defines a convention for describing HCL types using function
|
||||||
|
call and variable reference syntax, allowing configuration formats to include
|
||||||
|
type information provided by users.
|
||||||
|
|
||||||
|
The type syntax is processed statically from a hcl.Expression, so it cannot
|
||||||
|
use any of the usual language operators. This is similar to type expressions
|
||||||
|
in statically-typed programming languages.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
variable "example" {
|
||||||
|
type = list(string)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The extension is built using the `hcl.ExprAsKeyword` and `hcl.ExprCall`
|
||||||
|
functions, and so it relies on the underlying syntax to define how "keyword"
|
||||||
|
and "call" are interpreted. The above shows how they are interpreted in
|
||||||
|
the HCL native syntax, while the following shows the same information
|
||||||
|
expressed in JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"example": {
|
||||||
|
"type": "list(string)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that since we have additional contextual information that we intend
|
||||||
|
to allow only calls and keywords the JSON syntax is able to parse the given
|
||||||
|
string directly as an expression, rather than as a template as would be
|
||||||
|
the case for normal expression evaluation.
|
||||||
|
|
||||||
|
For more information, see [the godoc reference](http://godoc.org/github.com/hashicorp/hcl/v2/ext/typeexpr).
|
||||||
|
|
||||||
|
## Type Expression Syntax
|
||||||
|
|
||||||
|
When expressed in the native syntax, the following expressions are permitted
|
||||||
|
in a type expression:
|
||||||
|
|
||||||
|
* `string` - string
|
||||||
|
* `bool` - boolean
|
||||||
|
* `number` - number
|
||||||
|
* `any` - `cty.DynamicPseudoType` (in function `TypeConstraint` only)
|
||||||
|
* `list(<type_expr>)` - list of the type given as an argument
|
||||||
|
* `set(<type_expr>)` - set of the type given as an argument
|
||||||
|
* `map(<type_expr>)` - map of the type given as an argument
|
||||||
|
* `tuple([<type_exprs...>])` - tuple with the element types given in the single list argument
|
||||||
|
* `object({<attr_name>=<type_expr>, ...}` - object with the attributes and corresponding types given in the single map argument
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
* `list(string)`
|
||||||
|
* `object({name=string,age=number})`
|
||||||
|
* `map(object({name=string,age=number}))`
|
||||||
|
|
||||||
|
Note that the object constructor syntax is not fully-general for all possible
|
||||||
|
object types because it requires the attribute names to be valid identifiers.
|
||||||
|
In practice it is expected that any time an object type is being fixed for
|
||||||
|
type checking it will be one that has identifiers as its attributes; object
|
||||||
|
types with weird attributes generally show up only from arbitrary object
|
||||||
|
constructors in configuration files, which are usually treated either as maps
|
||||||
|
or as the dynamic pseudo-type.
|
||||||
|
|
||||||
|
## Type Constraints as Values
|
||||||
|
|
||||||
|
Along with defining a convention for writing down types using HCL expression
|
||||||
|
constructs, this package also includes a mechanism for representing types as
|
||||||
|
values that can be used as data within an HCL-based language.
|
||||||
|
|
||||||
|
`typeexpr.TypeConstraintType` is a
|
||||||
|
[`cty` capsule type](https://github.com/zclconf/go-cty/blob/master/docs/types.md#capsule-types)
|
||||||
|
that encapsulates `cty.Type` values. You can construct such a value directly
|
||||||
|
using the `TypeConstraintVal` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
tyVal := typeexpr.TypeConstraintVal(cty.String)
|
||||||
|
|
||||||
|
// We can unpack the type from a value using TypeConstraintFromVal
|
||||||
|
ty := typeExpr.TypeConstraintFromVal(tyVal)
|
||||||
|
```
|
||||||
|
|
||||||
|
However, the primary purpose of `typeexpr.TypeConstraintType` is to be
|
||||||
|
specified as the type constraint for an argument, in which case it serves
|
||||||
|
as a signal for HCL to treat the argument expression as a type constraint
|
||||||
|
expression as defined above, rather than as a normal value expression.
|
||||||
|
|
||||||
|
"An argument" in the above in practice means the following two locations:
|
||||||
|
|
||||||
|
* As the type constraint for a parameter of a cty function that will be
|
||||||
|
used in an `hcl.EvalContext`. In that case, function calls in the HCL
|
||||||
|
native expression syntax will require the argument to be valid type constraint
|
||||||
|
expression syntax and the function implementation will receive a
|
||||||
|
`TypeConstraintType` value as the argument value for that parameter.
|
||||||
|
|
||||||
|
* As the type constraint for a `hcldec.AttrSpec` or `hcldec.BlockAttrsSpec`
|
||||||
|
when decoding an HCL body using `hcldec`. In that case, the attributes
|
||||||
|
with that type constraint will be required to be valid type constraint
|
||||||
|
expression syntax and the result will be a `TypeConstraintType` value.
|
||||||
|
|
||||||
|
Note that the special handling of these arguments means that an argument
|
||||||
|
marked in this way must use the type constraint syntax directly. It is not
|
||||||
|
valid to pass in a value of `TypeConstraintType` that has been obtained
|
||||||
|
dynamically via some other expression result.
|
||||||
|
|
||||||
|
`TypeConstraintType` is provided with the intent of using it internally within
|
||||||
|
application code when incorporating type constraint expression syntax into
|
||||||
|
an HCL-based language, not to be used for dynamic "programming with types". A
|
||||||
|
calling application could support programming with types by defining its _own_
|
||||||
|
capsule type, but that is not the purpose of `TypeConstraintType`.
|
||||||
|
|
||||||
|
## The "convert" `cty` Function
|
||||||
|
|
||||||
|
Building on the `TypeConstraintType` described in the previous section, this
|
||||||
|
package also provides `typeexpr.ConvertFunc` which is a cty function that
|
||||||
|
can be placed into a `cty.EvalContext` (conventionally named "convert") in
|
||||||
|
order to provide a general type conversion function in an HCL-based language:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
foo = convert("true", bool)
|
||||||
|
```
|
||||||
|
|
||||||
|
The second parameter uses the mechanism described in the previous section to
|
||||||
|
require its argument to be a type constraint expression rather than a value
|
||||||
|
expression. In doing so, it allows converting with any type constraint that
|
||||||
|
can be expressed in this package's type constraint syntax. In the above example,
|
||||||
|
the `foo` argument would receive a boolean true, or `cty.True` in `cty` terms.
|
||||||
|
|
||||||
|
The target type constraint must always be provided statically using inline
|
||||||
|
type constraint syntax. There is no way to _dynamically_ select a type
|
||||||
|
constraint using this function.
|
@ -0,0 +1,11 @@
|
|||||||
|
// Package typeexpr extends HCL with a convention for describing HCL types
|
||||||
|
// within configuration files.
|
||||||
|
//
|
||||||
|
// The type syntax is processed statically from a hcl.Expression, so it cannot
|
||||||
|
// use any of the usual language operators. This is similar to type expressions
|
||||||
|
// in statically-typed programming languages.
|
||||||
|
//
|
||||||
|
// variable "example" {
|
||||||
|
// type = list(string)
|
||||||
|
// }
|
||||||
|
package typeexpr
|
@ -0,0 +1,196 @@
|
|||||||
|
package typeexpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
const invalidTypeSummary = "Invalid type specification"
|
||||||
|
|
||||||
|
// getType is the internal implementation of both Type and TypeConstraint,
|
||||||
|
// using the passed flag to distinguish. When constraint is false, the "any"
|
||||||
|
// keyword will produce an error.
|
||||||
|
func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
|
||||||
|
// First we'll try for one of our keywords
|
||||||
|
kw := hcl.ExprAsKeyword(expr)
|
||||||
|
switch kw {
|
||||||
|
case "bool":
|
||||||
|
return cty.Bool, nil
|
||||||
|
case "string":
|
||||||
|
return cty.String, nil
|
||||||
|
case "number":
|
||||||
|
return cty.Number, nil
|
||||||
|
case "any":
|
||||||
|
if constraint {
|
||||||
|
return cty.DynamicPseudoType, nil
|
||||||
|
}
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: fmt.Sprintf("The keyword %q cannot be used in this type specification: an exact type is required.", kw),
|
||||||
|
Subject: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
case "list", "map", "set":
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", kw),
|
||||||
|
Subject: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
case "object":
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.",
|
||||||
|
Subject: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
case "tuple":
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: "The tuple type constructor requires one argument specifying the element types as a list.",
|
||||||
|
Subject: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
case "":
|
||||||
|
// okay! we'll fall through and try processing as a call, then.
|
||||||
|
default:
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: fmt.Sprintf("The keyword %q is not a valid type specification.", kw),
|
||||||
|
Subject: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get down here then our expression isn't just a keyword, so we'll
|
||||||
|
// try to process it as a call instead.
|
||||||
|
call, diags := hcl.ExprCall(expr)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: "A type specification is either a primitive type keyword (bool, number, string) or a complex type constructor call, like list(string).",
|
||||||
|
Subject: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch call.Name {
|
||||||
|
case "bool", "string", "number", "any":
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: fmt.Sprintf("Primitive type keyword %q does not expect arguments.", call.Name),
|
||||||
|
Subject: &call.ArgsRange,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(call.Arguments) != 1 {
|
||||||
|
contextRange := call.ArgsRange
|
||||||
|
subjectRange := call.ArgsRange
|
||||||
|
if len(call.Arguments) > 1 {
|
||||||
|
// If we have too many arguments (as opposed to too _few_) then
|
||||||
|
// we'll highlight the extraneous arguments as the diagnostic
|
||||||
|
// subject.
|
||||||
|
subjectRange = hcl.RangeBetween(call.Arguments[1].Range(), call.Arguments[len(call.Arguments)-1].Range())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch call.Name {
|
||||||
|
case "list", "set", "map":
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", call.Name),
|
||||||
|
Subject: &subjectRange,
|
||||||
|
Context: &contextRange,
|
||||||
|
}}
|
||||||
|
case "object":
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.",
|
||||||
|
Subject: &subjectRange,
|
||||||
|
Context: &contextRange,
|
||||||
|
}}
|
||||||
|
case "tuple":
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: "The tuple type constructor requires one argument specifying the element types as a list.",
|
||||||
|
Subject: &subjectRange,
|
||||||
|
Context: &contextRange,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch call.Name {
|
||||||
|
|
||||||
|
case "list":
|
||||||
|
ety, diags := getType(call.Arguments[0], constraint)
|
||||||
|
return cty.List(ety), diags
|
||||||
|
case "set":
|
||||||
|
ety, diags := getType(call.Arguments[0], constraint)
|
||||||
|
return cty.Set(ety), diags
|
||||||
|
case "map":
|
||||||
|
ety, diags := getType(call.Arguments[0], constraint)
|
||||||
|
return cty.Map(ety), diags
|
||||||
|
case "object":
|
||||||
|
attrDefs, diags := hcl.ExprMap(call.Arguments[0])
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: "Object type constructor requires a map whose keys are attribute names and whose values are the corresponding attribute types.",
|
||||||
|
Subject: call.Arguments[0].Range().Ptr(),
|
||||||
|
Context: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
atys := make(map[string]cty.Type)
|
||||||
|
for _, attrDef := range attrDefs {
|
||||||
|
attrName := hcl.ExprAsKeyword(attrDef.Key)
|
||||||
|
if attrName == "" {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: "Object constructor map keys must be attribute names.",
|
||||||
|
Subject: attrDef.Key.Range().Ptr(),
|
||||||
|
Context: expr.Range().Ptr(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
aty, attrDiags := getType(attrDef.Value, constraint)
|
||||||
|
diags = append(diags, attrDiags...)
|
||||||
|
atys[attrName] = aty
|
||||||
|
}
|
||||||
|
return cty.Object(atys), diags
|
||||||
|
case "tuple":
|
||||||
|
elemDefs, diags := hcl.ExprList(call.Arguments[0])
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: "Tuple type constructor requires a list of element types.",
|
||||||
|
Subject: call.Arguments[0].Range().Ptr(),
|
||||||
|
Context: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
etys := make([]cty.Type, len(elemDefs))
|
||||||
|
for i, defExpr := range elemDefs {
|
||||||
|
ety, elemDiags := getType(defExpr, constraint)
|
||||||
|
diags = append(diags, elemDiags...)
|
||||||
|
etys[i] = ety
|
||||||
|
}
|
||||||
|
return cty.Tuple(etys), diags
|
||||||
|
default:
|
||||||
|
// Can't access call.Arguments in this path because we've not validated
|
||||||
|
// that it contains exactly one expression here.
|
||||||
|
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: invalidTypeSummary,
|
||||||
|
Detail: fmt.Sprintf("Keyword %q is not a valid type constructor.", call.Name),
|
||||||
|
Subject: expr.Range().Ptr(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
package typeexpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type attempts to process the given expression as a type expression and, if
|
||||||
|
// successful, returns the resulting type. If unsuccessful, error diagnostics
|
||||||
|
// are returned.
|
||||||
|
func Type(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
|
||||||
|
return getType(expr, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeConstraint attempts to parse the given expression as a type constraint
|
||||||
|
// and, if successful, returns the resulting type. If unsuccessful, error
|
||||||
|
// diagnostics are returned.
|
||||||
|
//
|
||||||
|
// A type constraint has the same structure as a type, but it additionally
|
||||||
|
// allows the keyword "any" to represent cty.DynamicPseudoType, which is often
|
||||||
|
// used as a wildcard in type checking and type conversion operations.
|
||||||
|
func TypeConstraint(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
|
||||||
|
return getType(expr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeString returns a string rendering of the given type as it would be
|
||||||
|
// expected to appear in the HCL native syntax.
|
||||||
|
//
|
||||||
|
// This is primarily intended for showing types to the user in an application
|
||||||
|
// that uses typexpr, where the user can be assumed to be familiar with the
|
||||||
|
// type expression syntax. In applications that do not use typeexpr these
|
||||||
|
// results may be confusing to the user and so type.FriendlyName may be
|
||||||
|
// preferable, even though it's less precise.
|
||||||
|
//
|
||||||
|
// TypeString produces reasonable results only for types like what would be
|
||||||
|
// produced by the Type and TypeConstraint functions. In particular, it cannot
|
||||||
|
// support capsule types.
|
||||||
|
func TypeString(ty cty.Type) string {
|
||||||
|
// Easy cases first
|
||||||
|
switch ty {
|
||||||
|
case cty.String:
|
||||||
|
return "string"
|
||||||
|
case cty.Bool:
|
||||||
|
return "bool"
|
||||||
|
case cty.Number:
|
||||||
|
return "number"
|
||||||
|
case cty.DynamicPseudoType:
|
||||||
|
return "any"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ty.IsCapsuleType() {
|
||||||
|
panic("TypeString does not support capsule types")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ty.IsCollectionType() {
|
||||||
|
ety := ty.ElementType()
|
||||||
|
etyString := TypeString(ety)
|
||||||
|
switch {
|
||||||
|
case ty.IsListType():
|
||||||
|
return fmt.Sprintf("list(%s)", etyString)
|
||||||
|
case ty.IsSetType():
|
||||||
|
return fmt.Sprintf("set(%s)", etyString)
|
||||||
|
case ty.IsMapType():
|
||||||
|
return fmt.Sprintf("map(%s)", etyString)
|
||||||
|
default:
|
||||||
|
// Should never happen because the above is exhaustive
|
||||||
|
panic("unsupported collection type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ty.IsObjectType() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("object({")
|
||||||
|
atys := ty.AttributeTypes()
|
||||||
|
names := make([]string, 0, len(atys))
|
||||||
|
for name := range atys {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
first := true
|
||||||
|
for _, name := range names {
|
||||||
|
aty := atys[name]
|
||||||
|
if !first {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
}
|
||||||
|
if !hclsyntax.ValidIdentifier(name) {
|
||||||
|
// Should never happen for any type produced by this package,
|
||||||
|
// but we'll do something reasonable here just so we don't
|
||||||
|
// produce garbage if someone gives us a hand-assembled object
|
||||||
|
// type that has weird attribute names.
|
||||||
|
// Using Go-style quoting here isn't perfect, since it doesn't
|
||||||
|
// exactly match HCL syntax, but it's fine for an edge-case.
|
||||||
|
buf.WriteString(fmt.Sprintf("%q", name))
|
||||||
|
} else {
|
||||||
|
buf.WriteString(name)
|
||||||
|
}
|
||||||
|
buf.WriteByte('=')
|
||||||
|
buf.WriteString(TypeString(aty))
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
buf.WriteString("})")
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ty.IsTupleType() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("tuple([")
|
||||||
|
etys := ty.TupleElementTypes()
|
||||||
|
first := true
|
||||||
|
for _, ety := range etys {
|
||||||
|
if !first {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
}
|
||||||
|
buf.WriteString(TypeString(ety))
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
buf.WriteString("])")
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should never happen because we covered all cases above.
|
||||||
|
panic(fmt.Errorf("unsupported type %#v", ty))
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package typeexpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TypeConstraintType is a cty capsule type that allows cty type constraints to
|
||||||
|
// be used as values.
|
||||||
|
//
|
||||||
|
// If TypeConstraintType is used in a context supporting the
|
||||||
|
// customdecode.CustomExpressionDecoder extension then it will implement
|
||||||
|
// expression decoding using the TypeConstraint function, thus allowing
|
||||||
|
// type expressions to be used in contexts where value expressions might
|
||||||
|
// normally be expected, such as in arguments to function calls.
|
||||||
|
var TypeConstraintType cty.Type
|
||||||
|
|
||||||
|
// TypeConstraintVal constructs a cty.Value whose type is
|
||||||
|
// TypeConstraintType.
|
||||||
|
func TypeConstraintVal(ty cty.Type) cty.Value {
|
||||||
|
return cty.CapsuleVal(TypeConstraintType, &ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeConstraintFromVal extracts the type from a cty.Value of
|
||||||
|
// TypeConstraintType that was previously constructed using TypeConstraintVal.
|
||||||
|
//
|
||||||
|
// If the given value isn't a known, non-null value of TypeConstraintType
|
||||||
|
// then this function will panic.
|
||||||
|
func TypeConstraintFromVal(v cty.Value) cty.Type {
|
||||||
|
if !v.Type().Equals(TypeConstraintType) {
|
||||||
|
panic("value is not of TypeConstraintType")
|
||||||
|
}
|
||||||
|
ptr := v.EncapsulatedValue().(*cty.Type)
|
||||||
|
return *ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertFunc is a cty function that implements type conversions.
|
||||||
|
//
|
||||||
|
// Its signature is as follows:
|
||||||
|
// convert(value, type_constraint)
|
||||||
|
//
|
||||||
|
// ...where type_constraint is a type constraint expression as defined by
|
||||||
|
// typeexpr.TypeConstraint.
|
||||||
|
//
|
||||||
|
// It relies on HCL's customdecode extension and so it's not suitable for use
|
||||||
|
// in non-HCL contexts or if you are using a HCL syntax implementation that
|
||||||
|
// does not support customdecode for function arguments. However, it _is_
|
||||||
|
// supported for function calls in the HCL native expression syntax.
|
||||||
|
var ConvertFunc function.Function
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
TypeConstraintType = cty.CapsuleWithOps("type constraint", reflect.TypeOf(cty.Type{}), &cty.CapsuleOps{
|
||||||
|
ExtensionData: func(key interface{}) interface{} {
|
||||||
|
switch key {
|
||||||
|
case customdecode.CustomExpressionDecoder:
|
||||||
|
return customdecode.CustomExpressionDecoderFunc(
|
||||||
|
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
ty, diags := TypeConstraint(expr)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return cty.NilVal, diags
|
||||||
|
}
|
||||||
|
return TypeConstraintVal(ty), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TypeGoString: func(_ reflect.Type) string {
|
||||||
|
return "typeexpr.TypeConstraintType"
|
||||||
|
},
|
||||||
|
GoString: func(raw interface{}) string {
|
||||||
|
tyPtr := raw.(*cty.Type)
|
||||||
|
return fmt.Sprintf("typeexpr.TypeConstraintVal(%#v)", *tyPtr)
|
||||||
|
},
|
||||||
|
RawEquals: func(a, b interface{}) bool {
|
||||||
|
aPtr := a.(*cty.Type)
|
||||||
|
bPtr := b.(*cty.Type)
|
||||||
|
return (*aPtr).Equals(*bPtr)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
ConvertFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "value",
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
AllowNull: true,
|
||||||
|
AllowDynamicType: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "type",
|
||||||
|
Type: TypeConstraintType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: func(args []cty.Value) (cty.Type, error) {
|
||||||
|
wantTypePtr := args[1].EncapsulatedValue().(*cty.Type)
|
||||||
|
got, err := convert.Convert(args[0], *wantTypePtr)
|
||||||
|
if err != nil {
|
||||||
|
return cty.NilType, function.NewArgError(0, err)
|
||||||
|
}
|
||||||
|
return got.Type(), nil
|
||||||
|
},
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
v, err := convert.Convert(args[0], retType)
|
||||||
|
if err != nil {
|
||||||
|
return cty.NilVal, function.NewArgError(0, err)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package bcrypt
|
||||||
|
|
||||||
|
import "encoding/base64"
|
||||||
|
|
||||||
|
const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
|
var bcEncoding = base64.NewEncoding(alphabet)
|
||||||
|
|
||||||
|
func base64Encode(src []byte) []byte {
|
||||||
|
n := bcEncoding.EncodedLen(len(src))
|
||||||
|
dst := make([]byte, n)
|
||||||
|
bcEncoding.Encode(dst, src)
|
||||||
|
for dst[n-1] == '=' {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
return dst[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func base64Decode(src []byte) ([]byte, error) {
|
||||||
|
numOfEquals := 4 - (len(src) % 4)
|
||||||
|
for i := 0; i < numOfEquals; i++ {
|
||||||
|
src = append(src, '=')
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]byte, bcEncoding.DecodedLen(len(src)))
|
||||||
|
n, err := bcEncoding.Decode(dst, src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dst[:n], nil
|
||||||
|
}
|
@ -0,0 +1,295 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
|
||||||
|
// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
|
||||||
|
package bcrypt // import "golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
// The code is a port of Provos and Mazières's C implementation.
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blowfish"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword
|
||||||
|
MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
|
||||||
|
DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
|
||||||
|
)
|
||||||
|
|
||||||
|
// The error returned from CompareHashAndPassword when a password and hash do
|
||||||
|
// not match.
|
||||||
|
var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
|
||||||
|
|
||||||
|
// The error returned from CompareHashAndPassword when a hash is too short to
|
||||||
|
// be a bcrypt hash.
|
||||||
|
var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
|
||||||
|
|
||||||
|
// The error returned from CompareHashAndPassword when a hash was created with
|
||||||
|
// a bcrypt algorithm newer than this implementation.
|
||||||
|
type HashVersionTooNewError byte
|
||||||
|
|
||||||
|
func (hv HashVersionTooNewError) Error() string {
|
||||||
|
return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
|
||||||
|
type InvalidHashPrefixError byte
|
||||||
|
|
||||||
|
func (ih InvalidHashPrefixError) Error() string {
|
||||||
|
return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvalidCostError int
|
||||||
|
|
||||||
|
func (ic InvalidCostError) Error() string {
|
||||||
|
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost))
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
majorVersion = '2'
|
||||||
|
minorVersion = 'a'
|
||||||
|
maxSaltSize = 16
|
||||||
|
maxCryptedHashSize = 23
|
||||||
|
encodedSaltSize = 22
|
||||||
|
encodedHashSize = 31
|
||||||
|
minHashSize = 59
|
||||||
|
)
|
||||||
|
|
||||||
|
// magicCipherData is an IV for the 64 Blowfish encryption calls in
|
||||||
|
// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
|
||||||
|
var magicCipherData = []byte{
|
||||||
|
0x4f, 0x72, 0x70, 0x68,
|
||||||
|
0x65, 0x61, 0x6e, 0x42,
|
||||||
|
0x65, 0x68, 0x6f, 0x6c,
|
||||||
|
0x64, 0x65, 0x72, 0x53,
|
||||||
|
0x63, 0x72, 0x79, 0x44,
|
||||||
|
0x6f, 0x75, 0x62, 0x74,
|
||||||
|
}
|
||||||
|
|
||||||
|
type hashed struct {
|
||||||
|
hash []byte
|
||||||
|
salt []byte
|
||||||
|
cost int // allowed range is MinCost to MaxCost
|
||||||
|
major byte
|
||||||
|
minor byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateFromPassword returns the bcrypt hash of the password at the given
|
||||||
|
// cost. If the cost given is less than MinCost, the cost will be set to
|
||||||
|
// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
|
||||||
|
// to compare the returned hashed password with its cleartext version.
|
||||||
|
func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
|
||||||
|
p, err := newFromPassword(password, cost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p.Hash(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareHashAndPassword compares a bcrypt hashed password with its possible
|
||||||
|
// plaintext equivalent. Returns nil on success, or an error on failure.
|
||||||
|
func CompareHashAndPassword(hashedPassword, password []byte) error {
|
||||||
|
p, err := newFromHash(hashedPassword)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
otherHash, err := bcrypt(password, p.cost, p.salt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
|
||||||
|
if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrMismatchedHashAndPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cost returns the hashing cost used to create the given hashed
|
||||||
|
// password. When, in the future, the hashing cost of a password system needs
|
||||||
|
// to be increased in order to adjust for greater computational power, this
|
||||||
|
// function allows one to establish which passwords need to be updated.
|
||||||
|
func Cost(hashedPassword []byte) (int, error) {
|
||||||
|
p, err := newFromHash(hashedPassword)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return p.cost, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFromPassword(password []byte, cost int) (*hashed, error) {
|
||||||
|
if cost < MinCost {
|
||||||
|
cost = DefaultCost
|
||||||
|
}
|
||||||
|
p := new(hashed)
|
||||||
|
p.major = majorVersion
|
||||||
|
p.minor = minorVersion
|
||||||
|
|
||||||
|
err := checkCost(cost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.cost = cost
|
||||||
|
|
||||||
|
unencodedSalt := make([]byte, maxSaltSize)
|
||||||
|
_, err = io.ReadFull(rand.Reader, unencodedSalt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.salt = base64Encode(unencodedSalt)
|
||||||
|
hash, err := bcrypt(password, p.cost, p.salt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.hash = hash
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFromHash(hashedSecret []byte) (*hashed, error) {
|
||||||
|
if len(hashedSecret) < minHashSize {
|
||||||
|
return nil, ErrHashTooShort
|
||||||
|
}
|
||||||
|
p := new(hashed)
|
||||||
|
n, err := p.decodeVersion(hashedSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashedSecret = hashedSecret[n:]
|
||||||
|
n, err = p.decodeCost(hashedSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashedSecret = hashedSecret[n:]
|
||||||
|
|
||||||
|
// The "+2" is here because we'll have to append at most 2 '=' to the salt
|
||||||
|
// when base64 decoding it in expensiveBlowfishSetup().
|
||||||
|
p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
|
||||||
|
copy(p.salt, hashedSecret[:encodedSaltSize])
|
||||||
|
|
||||||
|
hashedSecret = hashedSecret[encodedSaltSize:]
|
||||||
|
p.hash = make([]byte, len(hashedSecret))
|
||||||
|
copy(p.hash, hashedSecret)
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
|
||||||
|
cipherData := make([]byte, len(magicCipherData))
|
||||||
|
copy(cipherData, magicCipherData)
|
||||||
|
|
||||||
|
c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 24; i += 8 {
|
||||||
|
for j := 0; j < 64; j++ {
|
||||||
|
c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bug compatibility with C bcrypt implementations. We only encode 23 of
|
||||||
|
// the 24 bytes encrypted.
|
||||||
|
hsh := base64Encode(cipherData[:maxCryptedHashSize])
|
||||||
|
return hsh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
|
||||||
|
csalt, err := base64Decode(salt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bug compatibility with C bcrypt implementations. They use the trailing
|
||||||
|
// NULL in the key string during expansion.
|
||||||
|
// We copy the key to prevent changing the underlying array.
|
||||||
|
ckey := append(key[:len(key):len(key)], 0)
|
||||||
|
|
||||||
|
c, err := blowfish.NewSaltedCipher(ckey, csalt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var i, rounds uint64
|
||||||
|
rounds = 1 << cost
|
||||||
|
for i = 0; i < rounds; i++ {
|
||||||
|
blowfish.ExpandKey(ckey, c)
|
||||||
|
blowfish.ExpandKey(csalt, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hashed) Hash() []byte {
|
||||||
|
arr := make([]byte, 60)
|
||||||
|
arr[0] = '$'
|
||||||
|
arr[1] = p.major
|
||||||
|
n := 2
|
||||||
|
if p.minor != 0 {
|
||||||
|
arr[2] = p.minor
|
||||||
|
n = 3
|
||||||
|
}
|
||||||
|
arr[n] = '$'
|
||||||
|
n++
|
||||||
|
copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
|
||||||
|
n += 2
|
||||||
|
arr[n] = '$'
|
||||||
|
n++
|
||||||
|
copy(arr[n:], p.salt)
|
||||||
|
n += encodedSaltSize
|
||||||
|
copy(arr[n:], p.hash)
|
||||||
|
n += encodedHashSize
|
||||||
|
return arr[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
|
||||||
|
if sbytes[0] != '$' {
|
||||||
|
return -1, InvalidHashPrefixError(sbytes[0])
|
||||||
|
}
|
||||||
|
if sbytes[1] > majorVersion {
|
||||||
|
return -1, HashVersionTooNewError(sbytes[1])
|
||||||
|
}
|
||||||
|
p.major = sbytes[1]
|
||||||
|
n := 3
|
||||||
|
if sbytes[2] != '$' {
|
||||||
|
p.minor = sbytes[2]
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sbytes should begin where decodeVersion left off.
|
||||||
|
func (p *hashed) decodeCost(sbytes []byte) (int, error) {
|
||||||
|
cost, err := strconv.Atoi(string(sbytes[0:2]))
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
err = checkCost(cost)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
p.cost = cost
|
||||||
|
return 3, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hashed) String() string {
|
||||||
|
return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCost(cost int) error {
|
||||||
|
if cost < MinCost || cost > MaxCost {
|
||||||
|
return InvalidCostError(cost)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue