@ -1,27 +1,30 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: BSD-3-Clause
// Package logid contains ID types for interacting with the log service.
package logid
package logid
import (
import (
"bytes"
"crypto/rand"
"crypto/rand"
"crypto/sha256"
"crypto/sha256"
"encoding/binary"
"encoding/binary"
"encoding/hex"
"encoding/hex"
"errors"
"fmt"
"fmt"
"unicode/utf8"
"golang.org/x/exp/slices"
)
)
// PrivateID represents an instance that write logs.
// PrivateID represents a log steam for writing .
// Private IDs are only shared with the server when writing logs.
// Private IDs are only shared with the server when writing logs.
type PrivateID [ 32 ] byte
type PrivateID [ 32 ] byte
// Safely generate a new PrivateId for use in Config objects .
// NewPrivateID generates a new random PrivateID .
// You should persist this across runs of an instance of your app, so that
// This should persist across runs of an instance of the application,
// it can append to the same log file on each ru n.
// so that it can append to the same log stream for each invocatio n.
func NewPrivateID ( ) ( id PrivateID , err error ) {
func NewPrivateID ( ) ( id PrivateID , err error ) {
_ , err = rand . Read ( id [ : ] )
if _ , err := rand . Read ( id [ : ] ) ; err != nil {
if err != nil {
return PrivateID { } , err
return PrivateID { } , err
}
}
// Clamping, for future use.
// Clamping, for future use.
@ -30,137 +33,58 @@ func NewPrivateID() (id PrivateID, err error) {
return id , nil
return id , nil
}
}
func ( id PrivateID ) MarshalText ( ) ( [ ] byte , error ) {
// ParsePrivateID returns a PrivateID from its hex representation.
b := make ( [ ] byte , hex . EncodedLen ( len ( id ) ) )
func ParsePrivateID ( in string ) ( out PrivateID , err error ) {
if i := hex . Encode ( b , id [ : ] ) ; i != len ( b ) {
err = parseID ( "logid.ParsePublicID" , ( * [ 32 ] byte ) ( & out ) , in )
return nil , fmt . Errorf ( "logid.PrivateID.MarshalText: i=%d" , i )
return out , err
}
return b , nil
}
}
// ParsePrivateID returns a PrivateID from its hex (String) representation.
func ( id PrivateID ) MarshalText ( ) ( [ ] byte , error ) {
func ParsePrivateID ( s string ) ( PrivateID , error ) {
return formatID ( id ) , nil
if len ( s ) != 64 {
return PrivateID { } , errors . New ( "invalid length" )
}
var p PrivateID
for i := range p {
a , ok1 := fromHexChar ( s [ i * 2 + 0 ] )
b , ok2 := fromHexChar ( s [ i * 2 + 1 ] )
if ! ok1 || ! ok2 {
return PrivateID { } , errors . New ( "invalid hex character" )
}
p [ i ] = ( a << 4 ) | b
}
return p , nil
}
}
// IsZero reports whether id is the zero value.
func ( id * PrivateID ) UnmarshalText ( in [ ] byte ) error {
func ( id PrivateID ) IsZero ( ) bool { return id == PrivateID { } }
return parseID ( "logid.PrivateID" , ( * [ 32 ] byte ) ( id ) , in )
func ( id * PrivateID ) UnmarshalText ( s [ ] byte ) error {
b , err := hex . DecodeString ( string ( s ) )
if err != nil {
return fmt . Errorf ( "logid.PrivateID.UnmarshalText: %v" , err )
}
if len ( b ) != len ( id ) {
return fmt . Errorf ( "logid.PrivateID.UnmarshalText: invalid hex length: %d" , len ( b ) )
}
copy ( id [ : ] , b )
return nil
}
}
func ( id PrivateID ) String ( ) string {
func ( id PrivateID ) String ( ) string {
b , err := id . MarshalText ( )
return string ( formatID ( id ) )
if err != nil {
panic ( err )
}
}
return string ( b )
func ( id PrivateID ) IsZero ( ) bool {
return id == PrivateID { }
}
}
// Public returns the public ID of the private ID,
// which is the SHA-256 hash of the private ID.
func ( id PrivateID ) Public ( ) ( pub PublicID ) {
func ( id PrivateID ) Public ( ) ( pub PublicID ) {
h := sha256 . New ( )
return PublicID ( sha256 . Sum256 ( id [ : ] ) )
h . Write ( id [ : ] )
if n := copy ( pub [ : ] , h . Sum ( pub [ : 0 ] ) ) ; n != len ( pub ) {
panic ( fmt . Sprintf ( "public id short copy: %d" , n ) )
}
return pub
}
}
// PublicID represents a n instance in the logs service for reading and adoption .
// PublicID represents a log stream for reading.
// The public ID value is a SHA-256 hash of a private ID.
// The PrivateID cannot be feasibly reversed from the PublicID.
type PublicID [ sha256 . Size ] byte
type PublicID [ sha256 . Size ] byte
// ParsePublicID returns a PublicID from its hex (String) representation.
// ParsePublicID returns a PublicID from its hex representation.
func ParsePublicID ( s string ) ( PublicID , error ) {
func ParsePublicID ( in string ) ( out PublicID , err error ) {
if len ( s ) != sha256 . Size * 2 {
err = parseID ( "logid.ParsePublicID" , ( * [ 32 ] byte ) ( & out ) , in )
return PublicID { } , errors . New ( "invalid length" )
return out , err
}
var p PublicID
for i := range p {
a , ok1 := fromHexChar ( s [ i * 2 + 0 ] )
b , ok2 := fromHexChar ( s [ i * 2 + 1 ] )
if ! ok1 || ! ok2 {
return PublicID { } , errors . New ( "invalid hex character" )
}
p [ i ] = ( a << 4 ) | b
}
return p , nil
}
}
func ( id PublicID ) MarshalText ( ) ( [ ] byte , error ) {
func ( id PublicID ) MarshalText ( ) ( [ ] byte , error ) {
b := make ( [ ] byte , hex . EncodedLen ( len ( id ) ) )
return formatID ( id ) , nil
if i := hex . Encode ( b , id [ : ] ) ; i != len ( b ) {
return nil , fmt . Errorf ( "logid.PublicID.MarshalText: i=%d" , i )
}
return b , nil
}
}
func ( id * PublicID ) UnmarshalText ( s [ ] byte ) error {
func ( id * PublicID ) UnmarshalText ( in [ ] byte ) error {
if len ( s ) != len ( id ) * 2 {
return parseID ( "logid.ParsePublicID" , ( * [ 32 ] byte ) ( id ) , in )
return fmt . Errorf ( "logid.PublicID.UnmarshalText: invalid hex length: %d" , len ( s ) )
}
for i := range id {
a , ok1 := fromHexChar ( s [ i * 2 + 0 ] )
b , ok2 := fromHexChar ( s [ i * 2 + 1 ] )
if ! ok1 || ! ok2 {
return errors . New ( "invalid hex character" )
}
id [ i ] = ( a << 4 ) | b
}
return nil
}
}
func ( id PublicID ) String ( ) string {
func ( id PublicID ) String ( ) string {
b , err := id . MarshalText ( )
return string ( formatID ( id ) )
if err != nil {
panic ( err )
}
return string ( b )
}
// fromHexChar converts a hex character into its value and a success flag.
func fromHexChar ( c byte ) ( byte , bool ) {
switch {
case '0' <= c && c <= '9' :
return c - '0' , true
case 'a' <= c && c <= 'f' :
return c - 'a' + 10 , true
case 'A' <= c && c <= 'F' :
return c - 'A' + 10 , true
}
return 0 , false
}
}
func ( id1 PublicID ) Less ( id2 PublicID ) bool {
func ( id1 PublicID ) Less ( id2 PublicID ) bool {
for i , c1 := range id1 [ : ] {
return slices . Compare ( id1 [ : ] , id2 [ : ] ) < 0
c2 := id2 [ i ]
if c1 != c2 {
return c1 < c2
}
}
return false // equal
}
}
func ( id PublicID ) IsZero ( ) bool {
func ( id PublicID ) IsZero ( ) bool {
@ -170,3 +94,22 @@ func (id PublicID) IsZero() bool {
func ( id PublicID ) Prefix64 ( ) uint64 {
func ( id PublicID ) Prefix64 ( ) uint64 {
return binary . BigEndian . Uint64 ( id [ : 8 ] )
return binary . BigEndian . Uint64 ( id [ : 8 ] )
}
}
func formatID ( in [ 32 ] byte ) [ ] byte {
var hexArr [ 2 * len ( in ) ] byte
hex . Encode ( hexArr [ : ] , in [ : ] )
return hexArr [ : ]
}
func parseID [ Bytes [ ] byte | string ] ( funcName string , out * [ 32 ] byte , in Bytes ) ( err error ) {
if len ( in ) != 2 * len ( out ) {
return fmt . Errorf ( "%s: invalid hex length: %d" , funcName , len ( in ) )
}
var hexArr [ 2 * len ( out ) ] byte
copy ( hexArr [ : ] , in )
if _ , err := hex . Decode ( out [ : ] , hexArr [ : ] ) ; err != nil {
r , _ := utf8 . DecodeRune ( bytes . TrimLeft ( [ ] byte ( in ) , "0123456789abcdefABCDEF" ) )
return fmt . Errorf ( "%s: invalid hex character: %c" , funcName , r )
}
return nil
}