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

     1  package agent
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  )
     8  
     9  // magicNumberBytes is a type capable of holding a Mutagen magic byte sequence.
    10  type magicNumberBytes [3]byte
    11  
    12  // serverMagicNumber is a byte sequence that is sent by an endpoint server to
    13  // identify the start of a Mutagen protocol stream. It is intentionally composed
    14  // of bytes that are not (all) printable ASCII characters. The purpose of adding
    15  // this magic number to the beginning of streams is to work around agent-style
    16  // transports where the underlying transport executable may write error output
    17  // to standard output, which would otherwise be interpreted as version
    18  // information. By identifying this magic number, we can be sure that we're
    19  // talking to a Mutagen stream before we start exchanging version information.
    20  var serverMagicNumber = magicNumberBytes{0x05, 0x27, 0x87}
    21  
    22  // clientMagicNumber serves the same purpose as serverMagicNumber, but it is
    23  // send by the endpoint client to the endpoint server. It is not as necessary,
    24  // since whatever connects to the server should already know what it's doing,
    25  // but it serves as an extra sanity check in the world of agent-style
    26  // transports.
    27  var clientMagicNumber = magicNumberBytes{0x87, 0x27, 0x05}
    28  
    29  // sendMagicNumber sends the Mutagen magic byte sequence to the specified
    30  // writer.
    31  func sendMagicNumber(writer io.Writer, magicNumber magicNumberBytes) error {
    32  	_, err := writer.Write(magicNumber[:])
    33  	return err
    34  }
    35  
    36  // receiveAndCompareMagicNumber reads a Mutagen magic byte sequence from the
    37  // specified reader and verifies that it matches what's expected.
    38  func receiveAndCompareMagicNumber(reader io.Reader, expected magicNumberBytes) (bool, error) {
    39  	// Read the bytes.
    40  	var received magicNumberBytes
    41  	if _, err := io.ReadFull(reader, received[:]); err != nil {
    42  		return false, err
    43  	}
    44  
    45  	// Compare the bytes.
    46  	return received == expected, nil
    47  }
    48  
    49  // ClientHandshake performs a client-side handshake on the stream.
    50  func ClientHandshake(stream io.ReadWriter) error {
    51  	// Receive the server's magic number.
    52  	if magicOk, err := receiveAndCompareMagicNumber(stream, serverMagicNumber); err != nil {
    53  		return fmt.Errorf("unable to receive server magic number: %w", err)
    54  	} else if !magicOk {
    55  		return errors.New("server magic number incorrect")
    56  	}
    57  
    58  	// Send our magic number to the server.
    59  	if err := sendMagicNumber(stream, clientMagicNumber); err != nil {
    60  		return fmt.Errorf("unable to send client magic number: %w", err)
    61  	}
    62  
    63  	// Success.
    64  	return nil
    65  }
    66  
    67  // ServerHandshake performs a server-side handshake on the stream.
    68  func ServerHandshake(stream io.ReadWriter) error {
    69  	// Send our magic number to the client.
    70  	if err := sendMagicNumber(stream, serverMagicNumber); err != nil {
    71  		return fmt.Errorf("unable to send server magic number: %w", err)
    72  	}
    73  
    74  	// Receive the client's magic number. We treat a mismatch of the magic
    75  	// number as a transport error as well, because it indicates that we're not
    76  	// actually talking to a Mutagen client.
    77  	if magicOk, err := receiveAndCompareMagicNumber(stream, clientMagicNumber); err != nil {
    78  		return fmt.Errorf("unable to receive client magic number: %w", err)
    79  	} else if !magicOk {
    80  		return errors.New("client magic number incorrect")
    81  	}
    82  
    83  	// Success.
    84  	return nil
    85  }