/*
File Name:  Message Encoding Traverse.go
Copyright:  2021 Peernet s.r.o.
Author:     Peter Kleissner
*/

package protocol

import (
	"encoding/binary"
	"errors"
	"net"
	"time"

	"github.com/PeernetOfficial/core/btcec"
)

// MessageTraverse is the decoded traverse message.
// It is sent by an original sender to a relay, to a final receiver (targert peer).
type MessageTraverse struct {
	*MessageRaw                               // Underlying raw message.
	TargetPeer               *btcec.PublicKey // End receiver peer ID.
	AuthorizedRelayPeer      *btcec.PublicKey // Peer ID that is authorized to relay this message to the end receiver.
	Expires                  time.Time        // Expiration time when this forwarded message becomes invalid.
	EmbeddedPacketRaw        []byte           // Embedded packet.
	SignerPublicKey          *btcec.PublicKey // Public key that signed this message, ECDSA (secp256k1) 257-bit
	IPv4                     net.IP           // IPv4 address of the original sender. Set by authorized relay. 0 if not set.
	PortIPv4                 uint16           // Port (actual one used for connection) of the original sender. Set by authorized relay.
	PortIPv4ReportedExternal uint16           // External port as reported by the original sender. This is used in case of port forwarding (manual or automated).
	IPv6                     net.IP           // IPv6 address of the original sender. Set by authorized relay. 0 if not set.
	PortIPv6                 uint16           // Port (actual one used for connection) of the original sender. Set by authorized relay.
	PortIPv6ReportedExternal uint16           // External port as reported by the original sender. This is used in case of port forwarding (manual or automated).
}

const traversePayloadHeaderSize = 76 + 65 + 28

// DecodeTraverse decodes a traverse message.
// It does not verify if the receiver is authorized to read or forward this message.
// It validates the signature, but does not validate the signer.
func DecodeTraverse(msg *MessageRaw) (result *MessageTraverse, err error) {
	result = &MessageTraverse{
		MessageRaw: msg,
	}

	if len(msg.Payload) < traversePayloadHeaderSize {
		return nil, errors.New("traverse: invalid minimum length")
	}

	targetPeerIDcompressed := msg.Payload[0:33]
	authorizedRelayPeerIDcompressed := msg.Payload[33:66]

	if result.TargetPeer, err = btcec.ParsePubKey(targetPeerIDcompressed, btcec.S256()); err != nil {
		return nil, err
	}
	if result.AuthorizedRelayPeer, err = btcec.ParsePubKey(authorizedRelayPeerIDcompressed, btcec.S256()); err != nil {
		return nil, err
	}

	// receiver and target must not be the same
	if result.TargetPeer.IsEqual(result.AuthorizedRelayPeer) {
		return nil, errors.New("traverse: target and relay invalid")
	}

	expires64 := binary.LittleEndian.Uint64(msg.Payload[66 : 66+8])
	result.Expires = time.Unix(int64(expires64), 0)

	sizePacketEmbed := binary.LittleEndian.Uint16(msg.Payload[74 : 74+2])
	if int(sizePacketEmbed) != len(msg.Payload)-traversePayloadHeaderSize {
		return nil, errors.New("traverse: size embedded packet mismatch")
	}

	result.EmbeddedPacketRaw = msg.Payload[76 : 76+sizePacketEmbed]

	signature := msg.Payload[76+sizePacketEmbed : 76+sizePacketEmbed+65]

	result.SignerPublicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature, HashData(msg.Payload[:76+sizePacketEmbed]))
	if err != nil {
		return nil, err
	}

	// IPv4
	ipv4B := make([]byte, 4)
	copy(ipv4B[:], msg.Payload[76+sizePacketEmbed+65:76+sizePacketEmbed+65+4])

	result.IPv4 = ipv4B
	result.PortIPv4 = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+4 : 76+sizePacketEmbed+65+4+2])
	result.PortIPv4ReportedExternal = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+6 : 76+sizePacketEmbed+65+6+2])

	// IPv6
	ipv6B := make([]byte, 16)
	copy(ipv6B[:], msg.Payload[76+sizePacketEmbed+65+8:76+sizePacketEmbed+65+8+16])

	result.IPv6 = ipv6B
	result.PortIPv6 = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+24 : 76+sizePacketEmbed+65+24+2])
	result.PortIPv6ReportedExternal = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+26 : 76+sizePacketEmbed+65+26+2])

	// TODO: Validate IPv4 and IPv6. Only external ones allowed.
	if result.IPv6.To4() != nil {
		return nil, errors.New("traverse: ipv6 address mismatch")
	}

	return result, nil
}

// EncodeTraverse encodes a traverse message
func EncodeTraverse(senderPrivateKey *btcec.PrivateKey, embeddedPacketRaw []byte, receiverEnd *btcec.PublicKey, relayPeer *btcec.PublicKey) (packetRaw []byte, err error) {
	sizePacketEmbed := len(embeddedPacketRaw)
	if isPacketSizeExceed(traversePayloadHeaderSize, sizePacketEmbed) {
		return nil, errors.New("traverse encode: embedded packet too big")
	}

	raw := make([]byte, traversePayloadHeaderSize+sizePacketEmbed)

	targetPeerID := receiverEnd.SerializeCompressed()
	copy(raw[0:33], targetPeerID)
	authorizedRelayPeerID := relayPeer.SerializeCompressed()
	copy(raw[33:66], authorizedRelayPeerID)

	expires64 := time.Now().Add(time.Hour).UTC().Unix()
	binary.LittleEndian.PutUint64(raw[66:66+8], uint64(expires64))

	binary.LittleEndian.PutUint16(raw[74:74+2], uint16(sizePacketEmbed))
	copy(raw[76:76+sizePacketEmbed], embeddedPacketRaw)

	// add signature
	signature, err := btcec.SignCompact(btcec.S256(), senderPrivateKey, HashData(raw[:76+sizePacketEmbed]), true)
	if err != nil {
		return nil, err
	}
	copy(raw[76+sizePacketEmbed:76+sizePacketEmbed+65], signature)

	// IP and ports are to be filled by authorized relay peer

	return raw, nil
}

// EncodeTraverseSetAddress sets the IP and Port in a traverse message that shall be forwarded to another peer
func EncodeTraverseSetAddress(raw []byte, IPv4 net.IP, PortIPv4, PortIPv4ReportedExternal uint16, IPv6 net.IP, PortIPv6, PortIPv6ReportedExternal uint16) (err error) {
	if isPacketSizeExceed(len(raw), 0) {
		return errors.New("traverse encode 2: embedded packet too big")
	} else if len(raw) < traversePayloadHeaderSize {
		return errors.New("traverse encode 2: invalid packet")
	}

	sizePacketEmbed := binary.LittleEndian.Uint16(raw[74 : 74+2])
	if int(sizePacketEmbed) != len(raw)-traversePayloadHeaderSize {
		return errors.New("traverse encode 2: size embedded packet mismatch")
	}

	// IPv4
	if IPv4 != nil && len(IPv4) == net.IPv4len {
		copy(raw[76+sizePacketEmbed+65:76+sizePacketEmbed+65+4], IPv4.To4())
		binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+4:76+sizePacketEmbed+65+4+2], PortIPv4)
		binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+6:76+sizePacketEmbed+65+6+2], PortIPv4ReportedExternal)
	}

	// IPv6
	if IPv6 != nil && len(IPv6) == net.IPv6len {
		copy(raw[76+sizePacketEmbed+65+8:76+sizePacketEmbed+65+8+16], IPv6.To16())
		binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+24:76+sizePacketEmbed+65+24+2], PortIPv6)
		binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+26:76+sizePacketEmbed+65+26+2], PortIPv6ReportedExternal)
	}

	return nil
}
