2024-05-17 17:43:28 +02:00
|
|
|
package messages
|
|
|
|
|
|
|
|
import (
|
2024-06-25 17:36:04 +02:00
|
|
|
"bytes"
|
2024-07-04 13:42:27 +02:00
|
|
|
"encoding/gob"
|
2024-05-17 17:43:28 +02:00
|
|
|
"fmt"
|
2024-06-03 21:38:37 +02:00
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2024-05-17 17:43:28 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2024-05-23 13:24:02 +02:00
|
|
|
MsgTypeHello MsgType = 0
|
|
|
|
MsgTypeHelloResponse MsgType = 1
|
|
|
|
MsgTypeTransport MsgType = 2
|
2024-06-27 18:40:12 +02:00
|
|
|
MsgTypeClose MsgType = 3
|
|
|
|
MsgTypeHealthCheck MsgType = 4
|
2024-06-25 17:36:04 +02:00
|
|
|
|
2024-07-05 16:12:30 +02:00
|
|
|
sizeOfMsgType = 1
|
|
|
|
sizeOfMagicBye = 4
|
|
|
|
headerSizeTransport = sizeOfMsgType + IDSize // 1 byte for msg type, IDSize for peerID
|
|
|
|
headerSizeHello = sizeOfMsgType + sizeOfMagicBye + IDSize // 1 byte for msg type, 4 byte for magic header, IDSize for peerID
|
2024-06-26 16:22:26 +02:00
|
|
|
|
2024-07-10 13:21:50 +02:00
|
|
|
MaxHandshakeSize = 8192
|
2024-05-17 17:43:28 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrInvalidMessageLength = fmt.Errorf("invalid message length")
|
2024-06-25 17:36:04 +02:00
|
|
|
|
|
|
|
magicHeader = []byte{0x21, 0x12, 0xA4, 0x42}
|
2024-06-27 18:40:12 +02:00
|
|
|
|
|
|
|
healthCheckMsg = []byte{byte(MsgTypeHealthCheck)}
|
2024-05-17 17:43:28 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type MsgType byte
|
|
|
|
|
2024-05-19 12:41:06 +02:00
|
|
|
func (m MsgType) String() string {
|
|
|
|
switch m {
|
|
|
|
case MsgTypeHello:
|
|
|
|
return "hello"
|
2024-05-23 13:24:02 +02:00
|
|
|
case MsgTypeHelloResponse:
|
|
|
|
return "hello response"
|
2024-05-19 12:41:06 +02:00
|
|
|
case MsgTypeTransport:
|
|
|
|
return "transport"
|
2024-06-27 18:40:12 +02:00
|
|
|
case MsgTypeClose:
|
2024-06-05 19:49:30 +02:00
|
|
|
return "close"
|
2024-07-22 13:04:32 +02:00
|
|
|
case MsgTypeHealthCheck:
|
|
|
|
return "health check"
|
2024-05-19 12:41:06 +02:00
|
|
|
default:
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-02 11:57:17 +02:00
|
|
|
type HelloResponse struct {
|
2024-07-05 16:12:30 +02:00
|
|
|
InstanceAddress string
|
2024-07-02 11:57:17 +02:00
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// DetermineClientMsgType determines the message type from the first byte of the message
|
2024-05-17 17:43:28 +02:00
|
|
|
func DetermineClientMsgType(msg []byte) (MsgType, error) {
|
|
|
|
msgType := MsgType(msg[0])
|
|
|
|
switch msgType {
|
|
|
|
case MsgTypeHello:
|
|
|
|
return msgType, nil
|
|
|
|
case MsgTypeTransport:
|
|
|
|
return msgType, nil
|
2024-06-27 18:40:12 +02:00
|
|
|
case MsgTypeClose:
|
|
|
|
return msgType, nil
|
|
|
|
case MsgTypeHealthCheck:
|
2024-06-05 19:49:30 +02:00
|
|
|
return msgType, nil
|
2024-05-17 17:43:28 +02:00
|
|
|
default:
|
2024-06-03 20:14:39 +02:00
|
|
|
return 0, fmt.Errorf("invalid msg type, len: %d", len(msg))
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// DetermineServerMsgType determines the message type from the first byte of the message
|
2024-05-17 17:43:28 +02:00
|
|
|
func DetermineServerMsgType(msg []byte) (MsgType, error) {
|
|
|
|
msgType := MsgType(msg[0])
|
|
|
|
switch msgType {
|
2024-05-21 15:51:37 +02:00
|
|
|
case MsgTypeHelloResponse:
|
|
|
|
return msgType, nil
|
2024-05-17 17:43:28 +02:00
|
|
|
case MsgTypeTransport:
|
|
|
|
return msgType, nil
|
2024-06-27 18:40:12 +02:00
|
|
|
case MsgTypeClose:
|
|
|
|
return msgType, nil
|
|
|
|
case MsgTypeHealthCheck:
|
2024-06-05 19:49:30 +02:00
|
|
|
return msgType, nil
|
2024-05-17 17:43:28 +02:00
|
|
|
default:
|
2024-05-21 15:51:37 +02:00
|
|
|
return 0, fmt.Errorf("invalid msg type (len: %d)", len(msg))
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalHelloMsg initial hello message
|
2024-07-29 21:53:07 +02:00
|
|
|
// The Hello message is the first message sent by a client after establishing a connection with the Relay server. This
|
|
|
|
// message is used to authenticate the client with the server. The authentication is done using an HMAC method.
|
|
|
|
// The protocol does not limit to use HMAC, it can be any other method. If the authentication failed the server will
|
|
|
|
// close the network connection without any response.
|
2024-07-05 16:12:30 +02:00
|
|
|
func MarshalHelloMsg(peerID []byte, additions []byte) ([]byte, error) {
|
2024-05-23 13:24:02 +02:00
|
|
|
if len(peerID) != IDSize {
|
2024-06-25 17:36:04 +02:00
|
|
|
return nil, fmt.Errorf("invalid peerID length: %d", len(peerID))
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
2024-07-08 17:55:48 +02:00
|
|
|
|
|
|
|
// 5 = 1 byte for msg type, 4 byte for magic header
|
2024-07-05 16:12:30 +02:00
|
|
|
msg := make([]byte, 5, headerSizeHello+len(additions))
|
2024-05-17 17:43:28 +02:00
|
|
|
msg[0] = byte(MsgTypeHello)
|
2024-06-25 17:36:04 +02:00
|
|
|
copy(msg[1:5], magicHeader)
|
2024-05-23 13:24:02 +02:00
|
|
|
msg = append(msg, peerID...)
|
2024-07-05 16:12:30 +02:00
|
|
|
msg = append(msg, additions...)
|
2024-05-17 17:43:28 +02:00
|
|
|
return msg, nil
|
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// UnmarshalHelloMsg extracts the peerID and the additional data from the hello message. The Additional data is used to
|
|
|
|
// authenticate the client with the server.
|
2024-07-05 16:12:30 +02:00
|
|
|
func UnmarshalHelloMsg(msg []byte) ([]byte, []byte, error) {
|
2024-06-25 17:36:04 +02:00
|
|
|
if len(msg) < headerSizeHello {
|
2024-08-22 14:31:36 +02:00
|
|
|
return nil, nil, fmt.Errorf("invalid 'hello' message")
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
2024-07-08 17:55:48 +02:00
|
|
|
if !bytes.Equal(msg[1:5], magicHeader) {
|
|
|
|
return nil, nil, fmt.Errorf("invalid magic header")
|
|
|
|
}
|
|
|
|
return msg[5 : 5+IDSize], msg[headerSizeHello:], nil
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// MarshalHelloResponse creates a response message to the hello message.
|
|
|
|
// In case of success connection the server response with a Hello Response message. This message contains the server's
|
|
|
|
// instance URL. This URL will be used by choose the common Relay server in case if the peers are in different Relay
|
|
|
|
// servers.
|
2024-07-02 11:57:17 +02:00
|
|
|
func MarshalHelloResponse(DomainAddress string) ([]byte, error) {
|
|
|
|
payload := HelloResponse{
|
2024-07-05 16:12:30 +02:00
|
|
|
InstanceAddress: DomainAddress,
|
2024-07-02 11:57:17 +02:00
|
|
|
}
|
2024-07-04 13:42:27 +02:00
|
|
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
enc := gob.NewEncoder(buf)
|
|
|
|
|
|
|
|
err := enc.Encode(payload)
|
2024-07-02 11:57:17 +02:00
|
|
|
if err != nil {
|
2024-07-04 13:42:27 +02:00
|
|
|
log.Errorf("failed to gob encode hello response: %s", err)
|
2024-07-02 11:57:17 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
2024-07-04 13:42:27 +02:00
|
|
|
|
|
|
|
msg := make([]byte, 1, 1+buf.Len())
|
2024-05-21 15:51:37 +02:00
|
|
|
msg[0] = byte(MsgTypeHelloResponse)
|
2024-07-04 13:42:27 +02:00
|
|
|
msg = append(msg, buf.Bytes()...)
|
2024-07-02 11:57:17 +02:00
|
|
|
return msg, nil
|
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// UnmarshalHelloResponse extracts the instance address from the hello response message
|
2024-07-02 11:57:17 +02:00
|
|
|
func UnmarshalHelloResponse(msg []byte) (string, error) {
|
|
|
|
if len(msg) < 2 {
|
|
|
|
return "", fmt.Errorf("invalid 'hello response' message")
|
|
|
|
}
|
|
|
|
payload := HelloResponse{}
|
2024-07-04 13:42:27 +02:00
|
|
|
buf := bytes.NewBuffer(msg[1:])
|
|
|
|
dec := gob.NewDecoder(buf)
|
|
|
|
|
|
|
|
err := dec.Decode(&payload)
|
2024-07-02 11:57:17 +02:00
|
|
|
if err != nil {
|
2024-07-04 13:42:27 +02:00
|
|
|
log.Errorf("failed to gob decode hello response: %s", err)
|
2024-07-02 11:57:17 +02:00
|
|
|
return "", err
|
|
|
|
}
|
2024-07-05 16:12:30 +02:00
|
|
|
return payload.InstanceAddress, nil
|
2024-05-21 15:51:37 +02:00
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// MarshalCloseMsg creates a close message.
|
|
|
|
// The close message is used to close the connection gracefully between the client and the server. The server and the
|
|
|
|
// client can send this message. After receiving this message, the server or client will close the connection.
|
2024-06-05 19:49:30 +02:00
|
|
|
func MarshalCloseMsg() []byte {
|
|
|
|
msg := make([]byte, 1)
|
2024-06-27 18:40:12 +02:00
|
|
|
msg[0] = byte(MsgTypeClose)
|
2024-07-22 13:04:32 +02:00
|
|
|
return msg
|
2024-06-05 19:49:30 +02:00
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// MarshalTransportMsg creates a transport message.
|
|
|
|
// The transport message is used to exchange data between peers. The message contains the data to be exchanged and the
|
|
|
|
// destination peer hashed ID.
|
2024-06-25 17:36:04 +02:00
|
|
|
func MarshalTransportMsg(peerID []byte, payload []byte) ([]byte, error) {
|
2024-05-23 13:24:02 +02:00
|
|
|
if len(peerID) != IDSize {
|
2024-06-25 17:36:04 +02:00
|
|
|
return nil, fmt.Errorf("invalid peerID length: %d", len(peerID))
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
|
|
|
|
2024-06-25 17:36:04 +02:00
|
|
|
msg := make([]byte, headerSizeTransport, headerSizeTransport+len(payload))
|
2024-05-17 17:43:28 +02:00
|
|
|
msg[0] = byte(MsgTypeTransport)
|
2024-05-23 13:24:02 +02:00
|
|
|
copy(msg[1:], peerID)
|
2024-05-17 17:43:28 +02:00
|
|
|
msg = append(msg, payload...)
|
2024-06-25 17:36:04 +02:00
|
|
|
return msg, nil
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// UnmarshalTransportMsg extracts the peerID and the payload from the transport message.
|
2024-06-09 20:27:40 +02:00
|
|
|
func UnmarshalTransportMsg(buf []byte) ([]byte, []byte, error) {
|
2024-06-25 17:36:04 +02:00
|
|
|
if len(buf) < headerSizeTransport {
|
2024-06-09 20:27:40 +02:00
|
|
|
return nil, nil, ErrInvalidMessageLength
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
2024-06-09 20:27:40 +02:00
|
|
|
|
2024-06-25 17:36:04 +02:00
|
|
|
return buf[1:headerSizeTransport], buf[headerSizeTransport:], nil
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// UnmarshalTransportID extracts the peerID from the transport message.
|
2024-05-23 13:24:02 +02:00
|
|
|
func UnmarshalTransportID(buf []byte) ([]byte, error) {
|
2024-06-25 17:36:04 +02:00
|
|
|
if len(buf) < headerSizeTransport {
|
|
|
|
log.Debugf("invalid message length: %d, expected: %d, %x", len(buf), headerSizeTransport, buf)
|
2024-05-23 13:24:02 +02:00
|
|
|
return nil, ErrInvalidMessageLength
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
2024-06-25 17:36:04 +02:00
|
|
|
return buf[1:headerSizeTransport], nil
|
2024-05-17 17:43:28 +02:00
|
|
|
}
|
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// UpdateTransportMsg updates the peerID in the transport message.
|
|
|
|
// With this function the server can reuse the given byte slice to update the peerID in the transport message. So do
|
|
|
|
// need to allocate a new byte slice.
|
2024-05-23 13:24:02 +02:00
|
|
|
func UpdateTransportMsg(msg []byte, peerID []byte) error {
|
|
|
|
if len(msg) < 1+len(peerID) {
|
2024-05-17 17:43:28 +02:00
|
|
|
return ErrInvalidMessageLength
|
|
|
|
}
|
2024-05-23 13:24:02 +02:00
|
|
|
copy(msg[1:], peerID)
|
2024-05-17 17:43:28 +02:00
|
|
|
return nil
|
|
|
|
}
|
2024-06-27 18:40:12 +02:00
|
|
|
|
2024-07-29 21:53:07 +02:00
|
|
|
// MarshalHealthcheck creates a health check message.
|
|
|
|
// Health check message is sent by the server periodically. The client will respond with a health check response
|
|
|
|
// message. If the client does not respond to the health check message, the server will close the connection.
|
2024-06-27 18:40:12 +02:00
|
|
|
func MarshalHealthcheck() []byte {
|
|
|
|
return healthCheckMsg
|
|
|
|
}
|