github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libgit/id.go (about)

     1  // Copyright 2017 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libgit
     6  
     7  import (
     8  	"encoding"
     9  	"encoding/hex"
    10  	"fmt"
    11  
    12  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  const (
    17  	// idByteLen is the number of bytes in a git repo ID.
    18  	idByteLen = 16
    19  
    20  	idSuffix = 0x2c
    21  )
    22  
    23  // InvalidIDError indicates that a repo ID string is not parseable or
    24  // invalid.
    25  type InvalidIDError struct {
    26  	id string
    27  }
    28  
    29  func (e InvalidIDError) Error() string {
    30  	return fmt.Sprintf("Invalid repo ID %q", e.id)
    31  }
    32  
    33  // ID encapsulates a repo ID.
    34  type ID struct {
    35  	id [idByteLen]byte
    36  }
    37  
    38  var _ encoding.BinaryMarshaler = ID{}
    39  var _ encoding.BinaryUnmarshaler = (*ID)(nil)
    40  
    41  var _ encoding.TextMarshaler = ID{}
    42  var _ encoding.TextUnmarshaler = (*ID)(nil)
    43  
    44  // NullID is an empty ID
    45  var NullID = ID{}
    46  
    47  // Bytes returns the bytes of the ID.
    48  func (id ID) Bytes() []byte {
    49  	return id.id[:]
    50  }
    51  
    52  // String implements the Stringer interface for ID.
    53  func (id ID) String() string {
    54  	return hex.EncodeToString(id.id[:])
    55  }
    56  
    57  // MarshalBinary implements the encoding.BinaryMarshaler interface for ID.
    58  func (id ID) MarshalBinary() (data []byte, err error) {
    59  	suffix := id.id[idByteLen-1]
    60  	if suffix != idSuffix {
    61  		return nil, errors.WithStack(InvalidIDError{id.String()})
    62  	}
    63  	return id.id[:], nil
    64  }
    65  
    66  // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
    67  // for ID.
    68  func (id *ID) UnmarshalBinary(data []byte) error {
    69  	if len(data) != idByteLen {
    70  		return errors.WithStack(InvalidIDError{hex.EncodeToString(data)})
    71  	}
    72  	suffix := data[idByteLen-1]
    73  	if suffix != idSuffix {
    74  		return errors.WithStack(InvalidIDError{hex.EncodeToString(data)})
    75  	}
    76  	copy(id.id[:], data)
    77  	return nil
    78  }
    79  
    80  // MarshalText implements the encoding.TextMarshaler interface for ID.
    81  func (id ID) MarshalText() ([]byte, error) {
    82  	bytes, err := id.MarshalBinary()
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	return []byte(hex.EncodeToString(bytes)), nil
    87  }
    88  
    89  // UnmarshalText implements the encoding.TextUnmarshaler interface for
    90  // ID.
    91  func (id *ID) UnmarshalText(buf []byte) error {
    92  	s := string(buf)
    93  	bytes, err := hex.DecodeString(s)
    94  	if err != nil {
    95  		return errors.WithStack(InvalidIDError{s})
    96  	}
    97  	return id.UnmarshalBinary(bytes)
    98  }
    99  
   100  func makeRandomID() (id ID, err error) {
   101  	// Loop just in case we randomly pick the null ID.
   102  	for id == NullID {
   103  		err := kbfscrypto.RandRead(id.id[:idByteLen-1])
   104  		if err != nil {
   105  			return ID{}, err
   106  		}
   107  	}
   108  	id.id[idByteLen-1] = idSuffix
   109  	return id, nil
   110  }