github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/mutagen/version.go (about)

     1  //go:build go1.21
     2  
     3  package mutagen
     4  
     5  import (
     6  	"encoding/binary"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  )
    11  
    12  const (
    13  	// VersionMajor represents the current major version of Mutagen.
    14  	VersionMajor = 0
    15  	// VersionMinor represents the current minor version of Mutagen.
    16  	VersionMinor = 18
    17  	// VersionPatch represents the current patch version of Mutagen.
    18  	VersionPatch = 0
    19  	// VersionTag represents a tag to be appended to the Mutagen version string.
    20  	// It must not contain spaces. If empty, no tag is appended to the version
    21  	// string.
    22  	VersionTag = "rc1"
    23  )
    24  
    25  // DevelopmentModeEnabled indicates that development mode is active. This is
    26  // regulated via VersionTag and should not be set or updated explicitly.
    27  const DevelopmentModeEnabled = VersionTag == "dev"
    28  
    29  // Version provides a stringified version of the current Mutagen version.
    30  var Version string
    31  
    32  // init performs global initialization.
    33  func init() {
    34  	// Compute the stringified version.
    35  	if VersionTag != "" {
    36  		Version = fmt.Sprintf("%d.%d.%d-%s", VersionMajor, VersionMinor, VersionPatch, VersionTag)
    37  	} else {
    38  		Version = fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch)
    39  	}
    40  }
    41  
    42  // versionBytes is a type that can be used to send and receive version
    43  // information over the wire.
    44  type versionBytes [12]byte
    45  
    46  // sendVersion writes the current version to the specified writer. Version tag
    47  // components are neither transmitted nor received.
    48  func sendVersion(writer io.Writer) error {
    49  	// Compute the version bytes.
    50  	var data versionBytes
    51  	binary.BigEndian.PutUint32(data[:4], VersionMajor)
    52  	binary.BigEndian.PutUint32(data[4:8], VersionMinor)
    53  	binary.BigEndian.PutUint32(data[8:], VersionPatch)
    54  
    55  	// Transmit the bytes.
    56  	_, err := writer.Write(data[:])
    57  	return err
    58  }
    59  
    60  // receiveVersion reads version information from the specified reader. Version
    61  // tag components are neither transmitted nor received.
    62  func receiveVersion(reader io.Reader) (uint32, uint32, uint32, error) {
    63  	// Read the bytes.
    64  	var data versionBytes
    65  	if _, err := io.ReadFull(reader, data[:]); err != nil {
    66  		return 0, 0, 0, err
    67  	}
    68  
    69  	// Decode components.
    70  	major := binary.BigEndian.Uint32(data[:4])
    71  	minor := binary.BigEndian.Uint32(data[4:8])
    72  	patch := binary.BigEndian.Uint32(data[8:])
    73  
    74  	// Done.
    75  	return major, minor, patch, nil
    76  }
    77  
    78  // ClientVersionHandshake performs the client side of a version handshake,
    79  // returning an error if the received server version is not compatible with the
    80  // client version.
    81  //
    82  // TODO: Add some ability to support version skew in this function.
    83  func ClientVersionHandshake(stream io.ReadWriteCloser) error {
    84  	// Receive the server's version.
    85  	serverMajor, serverMinor, serverPatch, err := receiveVersion(stream)
    86  	if err != nil {
    87  		return fmt.Errorf("unable to receive server version: %w", err)
    88  	}
    89  
    90  	// Send our version to the server.
    91  	if err := sendVersion(stream); err != nil {
    92  		return fmt.Errorf("unable to send client version: %w", err)
    93  	}
    94  
    95  	// Ensure that our Mutagen versions are compatible. For now, we enforce that
    96  	// they're equal.
    97  	// TODO: Once we lock-in an internal protocol that we're going to support
    98  	// for some time, we can allow some version skew. On the client side in
    99  	// particular, we'll probably want to look out for the specific "locked-in"
   100  	// server protocol that we support and instantiate some frozen client
   101  	// implementation from that version.
   102  	versionMatch := serverMajor == VersionMajor &&
   103  		serverMinor == VersionMinor &&
   104  		serverPatch == VersionPatch
   105  	if !versionMatch {
   106  		return errors.New("version mismatch")
   107  	}
   108  
   109  	// Success.
   110  	return nil
   111  }
   112  
   113  // ServerVersionHandshake performs the server side of a version handshake,
   114  // returning an error if the received client version is not compatible with the
   115  // server version.
   116  //
   117  // TODO: Add some ability to support version skew in this function.
   118  func ServerVersionHandshake(stream io.ReadWriteCloser) error {
   119  	// Send our version to the client.
   120  	if err := sendVersion(stream); err != nil {
   121  		return fmt.Errorf("unable to send server version: %w", err)
   122  	}
   123  
   124  	// Receive the client's version.
   125  	clientMajor, clientMinor, clientPatch, err := receiveVersion(stream)
   126  	if err != nil {
   127  		return fmt.Errorf("unable to receive client version: %w", err)
   128  	}
   129  
   130  	// Ensure that our versions are compatible. For now, we enforce that they're
   131  	// equal.
   132  	versionMatch := clientMajor == VersionMajor &&
   133  		clientMinor == VersionMinor &&
   134  		clientPatch == VersionPatch
   135  	if !versionMatch {
   136  		return errors.New("version mismatch")
   137  	}
   138  
   139  	// Success.
   140  	return nil
   141  }