// https://wiki.vuze.com/w/Message_Stream_Encryption

package mse

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/rc4"
	"crypto/sha1"
	"encoding/binary"
	"errors"
	"expvar"
	"fmt"
	"io"
	"math"
	"math/big"
	"strconv"
	"sync"

	"github.com/anacrolix/torrent/internal/ctxrw"
)

const (
	maxPadLen = 512

	CryptoMethodPlaintext CryptoMethod = 1 // After header obfuscation, drop into plaintext
	CryptoMethodRC4       CryptoMethod = 2 // After header obfuscation, use RC4 for the rest of the stream
	AllSupportedCrypto                 = CryptoMethodPlaintext | CryptoMethodRC4
)

type CryptoMethod uint32

var (
	// Prime P according to the spec, and G, the generator.
	p, specG big.Int
	// The rand.Int max arg for use in newPadLen()
	newPadLenMax big.Int
	// For use in initer's hashes
	req1 = []byte("req1")
	req2 = []byte("req2")
	req3 = []byte("req3")
	// Verification constant "VC" which is all zeroes in the bittorrent
	// implementation.
	vc [8]byte
	// Zero padding
	zeroPad [512]byte
	// Tracks counts of received crypto_provides
	cryptoProvidesCount = expvar.NewMap("mseCryptoProvides")
)

func init() {
	p.SetString("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A36210000000000090563", 0)
	specG.SetInt64(2)
	newPadLenMax.SetInt64(maxPadLen + 1)
}

func hash(parts ...[]byte) []byte {
	h := sha1.New()
	for _, p := range parts {
		n, err := h.Write(p)
		if err != nil {
			panic(err)
		}
		if n != len(p) {
			panic(n)
		}
	}
	return h.Sum(nil)
}

func newEncrypt(initer bool, s, skey []byte) (c *rc4.Cipher) {
	c, err := rc4.NewCipher(hash([]byte(func() string {
		if initer {
			return "keyA"
		} else {
			return "keyB"
		}
	}()), s, skey))
	if err != nil {
		panic(err)
	}
	var burnSrc, burnDst [1024]byte
	c.XORKeyStream(burnDst[:], burnSrc[:])
	return
}

type cipherReader struct {
	c  *rc4.Cipher
	r  io.Reader
	be []byte
}

func (cr *cipherReader) Read(b []byte) (n int, err error) {
	if cap(cr.be) < len(b) {
		cr.be = make([]byte, len(b))
	}
	n, err = cr.r.Read(cr.be[:len(b)])
	cr.c.XORKeyStream(b[:n], cr.be[:n])
	return
}

func newCipherReader(c *rc4.Cipher, r io.Reader) io.Reader {
	return &cipherReader{c: c, r: r}
}

type cipherWriter struct {
	c *rc4.Cipher
	w io.Writer
	b []byte
}

func (cr *cipherWriter) Write(b []byte) (n int, err error) {
	be := func() []byte {
		if len(cr.b) < len(b) {
			return make([]byte, len(b))
		} else {
			ret := cr.b
			cr.b = nil
			return ret
		}
	}()
	cr.c.XORKeyStream(be, b)
	n, err = cr.w.Write(be[:len(b)])
	if n != len(b) {
		// The cipher will have advanced beyond the callers stream position.
		// We can't use the cipher anymore.
		cr.c = nil
	}
	if len(be) > len(cr.b) {
		cr.b = be
	}
	return
}

func newX() big.Int {
	var X big.Int
	X.SetBytes(func() []byte {
		var b [20]byte
		_, err := rand.Read(b[:])
		if err != nil {
			panic(err)
		}
		return b[:]
	}())
	return X
}

func paddedLeft(b []byte, _len int) []byte {
	if len(b) == _len {
		return b
	}
	ret := make([]byte, _len)
	if n := copy(ret[_len-len(b):], b); n != len(b) {
		panic(n)
	}
	return ret
}

// Calculate, and send Y, our public key.
func (h *handshake) postY(x *big.Int) error {
	var y big.Int
	y.Exp(&specG, x, &p)
	return h.postWrite(paddedLeft(y.Bytes(), 96))
}

func (h *handshake) establishS() error {
	x := newX()
	h.postY(&x)
	var b [96]byte
	_, err := io.ReadFull(h.ctxConn, b[:])
	if err != nil {
		return fmt.Errorf("error reading Y: %w", err)
	}
	var Y, S big.Int
	Y.SetBytes(b[:])
	S.Exp(&Y, &x, &p)
	sBytes := S.Bytes()
	copy(h.s[96-len(sBytes):96], sBytes)
	return nil
}

func newPadLen() int64 {
	i, err := rand.Int(rand.Reader, &newPadLenMax)
	if err != nil {
		panic(err)
	}
	ret := i.Int64()
	if ret < 0 || ret > maxPadLen {
		panic(ret)
	}
	return ret
}

// Manages state for both initiating and receiving handshakes.
type handshake struct {
	conn io.ReadWriter
	// The conn with Reads and Writes wrapped to the context given in handshake.Do.
	ctxConn io.ReadWriter
	s       [96]byte
	initer  bool          // Whether we're initiating or receiving.
	skeys   SecretKeyIter // Skeys we'll accept if receiving.
	skey    []byte        // Skey we're initiating with.
	ia      []byte        // Initial payload. Only used by the initiator.
	// Return the bit for the crypto method the receiver wants to use.
	chooseMethod CryptoSelector
	// Sent to the receiver.
	cryptoProvides CryptoMethod

	writeMu    sync.Mutex
	writes     [][]byte
	writeErr   error
	writeCond  sync.Cond
	writeClose bool

	writerMu   sync.Mutex
	writerCond sync.Cond
	writerDone bool
}

func (h *handshake) finishWriting() {
	h.writeMu.Lock()
	h.writeClose = true
	h.writeCond.Broadcast()
	h.writeMu.Unlock()

	h.writerMu.Lock()
	for !h.writerDone {
		h.writerCond.Wait()
	}
	h.writerMu.Unlock()
}

func (h *handshake) writer() {
	defer func() {
		h.writerMu.Lock()
		h.writerDone = true
		h.writerCond.Broadcast()
		h.writerMu.Unlock()
	}()
	for {
		h.writeMu.Lock()
		for {
			if len(h.writes) != 0 {
				break
			}
			if h.writeClose {
				h.writeMu.Unlock()
				return
			}
			h.writeCond.Wait()
		}
		b := h.writes[0]
		h.writes = h.writes[1:]
		h.writeMu.Unlock()
		_, err := h.ctxConn.Write(b)
		if err != nil {
			h.writeMu.Lock()
			h.writeErr = err
			h.writeMu.Unlock()
			return
		}
	}
}

func (h *handshake) postWrite(b []byte) error {
	h.writeMu.Lock()
	defer h.writeMu.Unlock()
	if h.writeErr != nil {
		return h.writeErr
	}
	h.writes = append(h.writes, b)
	h.writeCond.Signal()
	return nil
}

func xor(a, b []byte) (ret []byte) {
	max := len(a)
	if max > len(b) {
		max = len(b)
	}
	ret = make([]byte, max)
	xorInPlace(ret, a, b)
	return
}

func xorInPlace(dst, a, b []byte) {
	for i := range dst {
		dst[i] = a[i] ^ b[i]
	}
}

func marshal(w io.Writer, data ...interface{}) (err error) {
	for _, data := range data {
		err = binary.Write(w, binary.BigEndian, data)
		if err != nil {
			break
		}
	}
	return
}

func unmarshal(r io.Reader, data ...interface{}) (err error) {
	for _, data := range data {
		err = binary.Read(r, binary.BigEndian, data)
		if err != nil {
			break
		}
	}
	return
}

// Looking for b at the end of a.
func suffixMatchLen(a, b []byte) int {
	if len(b) > len(a) {
		b = b[:len(a)]
	}
	// i is how much of b to try to match
	for i := len(b); i > 0; i-- {
		// j is how many chars we've compared
		j := 0
		for ; j < i; j++ {
			if b[i-1-j] != a[len(a)-1-j] {
				goto shorter
			}
		}
		return j
	shorter:
	}
	return 0
}

// Reads from r until b has been seen. Keeps the minimum amount of data in
// memory.
func readUntil(r io.Reader, b []byte) error {
	b1 := make([]byte, len(b))
	i := 0
	for {
		_, err := io.ReadFull(r, b1[i:])
		if err != nil {
			return err
		}
		i = suffixMatchLen(b1, b)
		if i == len(b) {
			break
		}
		if copy(b1, b1[len(b1)-i:]) != i {
			panic("wat")
		}
	}
	return nil
}

type readWriter struct {
	io.Reader
	io.Writer
}

func (h *handshake) newEncrypt(initer bool) *rc4.Cipher {
	return newEncrypt(initer, h.s[:], h.skey)
}

func (h *handshake) initerSteps(ctx context.Context) (ret io.ReadWriter, selected CryptoMethod, err error) {
	h.postWrite(hash(req1, h.s[:]))
	h.postWrite(xor(hash(req2, h.skey), hash(req3, h.s[:])))
	buf := &bytes.Buffer{}
	padLen := uint16(newPadLen())
	if len(h.ia) > math.MaxUint16 {
		err = errors.New("initial payload too large")
		return
	}
	err = marshal(buf, vc[:], h.cryptoProvides, padLen, zeroPad[:padLen], uint16(len(h.ia)), h.ia)
	if err != nil {
		return
	}
	e := h.newEncrypt(true)
	be := make([]byte, buf.Len())
	e.XORKeyStream(be, buf.Bytes())
	h.postWrite(be)
	bC := h.newEncrypt(false)
	var eVC [8]byte
	bC.XORKeyStream(eVC[:], vc[:])
	// Read until the all zero VC. At this point we've only read the 96 byte
	// public key, Y. There is potentially 512 byte padding, between us and
	// the 8 byte verification constant.
	err = readUntil(io.LimitReader(h.ctxConn, 520), eVC[:])
	if err != nil {
		if err == io.EOF {
			err = errors.New("failed to synchronize on VC")
		} else {
			err = fmt.Errorf("error reading until VC: %w", err)
		}
		return
	}
	ctxReader := newCipherReader(bC, h.ctxConn)
	var method CryptoMethod
	err = unmarshal(ctxReader, &method, &padLen)
	if err != nil {
		return
	}
	_, err = io.CopyN(io.Discard, ctxReader, int64(padLen))
	if err != nil {
		return
	}
	selected = method & h.cryptoProvides
	switch selected {
	case CryptoMethodRC4:
		ret = readWriter{
			newCipherReader(bC, h.conn),
			&cipherWriter{e, h.conn, nil},
		}
	case CryptoMethodPlaintext:
		ret = h.conn
	default:
		err = fmt.Errorf("receiver chose unsupported method: %x", method)
	}
	return
}

var ErrNoSecretKeyMatch = errors.New("no skey matched")

func (h *handshake) receiverSteps(ctx context.Context) (ret io.ReadWriter, chosen CryptoMethod, err error) {
	// There is up to 512 bytes of padding, then the 20 byte hash.
	err = readUntil(io.LimitReader(h.ctxConn, 532), hash(req1, h.s[:]))
	if err != nil {
		if err == io.EOF {
			err = errors.New("failed to synchronize on S hash")
		}
		return
	}
	var b [20]byte
	_, err = io.ReadFull(h.ctxConn, b[:])
	if err != nil {
		return
	}
	expectedHash := hash(req3, h.s[:])
	eachHash := sha1.New()
	var sum, xored [sha1.Size]byte
	err = ErrNoSecretKeyMatch
	h.skeys(func(skey []byte) bool {
		eachHash.Reset()
		eachHash.Write(req2)
		eachHash.Write(skey)
		eachHash.Sum(sum[:0])
		xorInPlace(xored[:], sum[:], expectedHash)
		if bytes.Equal(xored[:], b[:]) {
			h.skey = skey
			err = nil
			return false
		}
		return true
	})
	if err != nil {
		return
	}
	cipher := newEncrypt(true, h.s[:], h.skey)
	ctxReader := newCipherReader(cipher, h.ctxConn)
	var (
		vc       [8]byte
		provides CryptoMethod
		padLen   uint16
	)

	err = unmarshal(ctxReader, vc[:], &provides, &padLen)
	if err != nil {
		return
	}
	cryptoProvidesCount.Add(strconv.FormatUint(uint64(provides), 16), 1)
	chosen = h.chooseMethod(provides)
	_, err = io.CopyN(io.Discard, ctxReader, int64(padLen))
	if err != nil {
		return
	}
	var lenIA uint16
	unmarshal(ctxReader, &lenIA)
	if lenIA != 0 {
		h.ia = make([]byte, lenIA)
		unmarshal(ctxReader, h.ia)
	}
	buf := &bytes.Buffer{}
	w := cipherWriter{h.newEncrypt(false), buf, nil}
	padLen = uint16(newPadLen())
	err = marshal(&w, &vc, uint32(chosen), padLen, zeroPad[:padLen])
	if err != nil {
		return
	}
	err = h.postWrite(buf.Bytes())
	if err != nil {
		return
	}
	switch chosen {
	case CryptoMethodRC4:
		ret = readWriter{
			io.MultiReader(bytes.NewReader(h.ia), newCipherReader(cipher, h.conn)),
			&cipherWriter{w.c, h.conn, nil},
		}
	case CryptoMethodPlaintext:
		ret = readWriter{
			io.MultiReader(bytes.NewReader(h.ia), h.conn),
			h.conn,
		}
	default:
		err = errors.New("chosen crypto method is not supported")
	}
	return
}

func (h *handshake) Do(ctx context.Context) (ret io.ReadWriter, method CryptoMethod, err error) {
	h.writeCond.L = &h.writeMu
	h.writerCond.L = &h.writerMu
	go h.writer()
	defer func() {
		h.finishWriting()
		if err == nil {
			err = h.writeErr
		}
	}()
	err = h.establishS()
	if err != nil {
		err = fmt.Errorf("error while establishing secret: %w", err)
		return
	}
	pad := make([]byte, newPadLen())
	io.ReadFull(rand.Reader, pad)
	err = h.postWrite(pad)
	if err != nil {
		return
	}
	if h.initer {
		ret, method, err = h.initerSteps(ctx)
	} else {
		ret, method, err = h.receiverSteps(ctx)
	}
	return
}

func InitiateHandshake(
	rw io.ReadWriter,
	skey, initialPayload []byte,
	cryptoProvides CryptoMethod,
) (
	ret io.ReadWriter, method CryptoMethod, err error,
) {
	return InitiateHandshakeContext(context.TODO(), rw, skey, initialPayload, cryptoProvides)
}

func InitiateHandshakeContext(
	ctx context.Context,
	rw io.ReadWriter,
	skey, initialPayload []byte,
	cryptoProvides CryptoMethod,
) (
	ret io.ReadWriter, method CryptoMethod, err error,
) {
	h := handshake{
		conn:           rw,
		ctxConn:        ctxrw.WrapReadWriter(ctx, rw),
		initer:         true,
		skey:           skey,
		ia:             initialPayload,
		cryptoProvides: cryptoProvides,
	}
	return h.Do(ctx)
}

type HandshakeResult struct {
	io.ReadWriter
	CryptoMethod
	error
	SecretKey []byte
}

func ReceiveHandshake(
	ctx context.Context,
	rw io.ReadWriter,
	skeys SecretKeyIter,
	selectCrypto CryptoSelector,
) (io.ReadWriter, CryptoMethod, error) {
	res := ReceiveHandshakeEx(ctx, rw, skeys, selectCrypto)
	return res.ReadWriter, res.CryptoMethod, res.error
}

func ReceiveHandshakeEx(
	ctx context.Context,
	rw io.ReadWriter,
	skeys SecretKeyIter,
	selectCrypto CryptoSelector,
) (ret HandshakeResult) {
	h := handshake{
		conn:         rw,
		ctxConn:      ctxrw.WrapReadWriter(ctx, rw),
		initer:       false,
		skeys:        skeys,
		chooseMethod: selectCrypto,
	}
	ret.ReadWriter, ret.CryptoMethod, ret.error = h.Do(ctx)
	ret.SecretKey = h.skey
	return
}

// A function that given a function, calls it with secret keys until it
// returns false or exhausted.
type SecretKeyIter func(callback func(skey []byte) (more bool))

func DefaultCryptoSelector(provided CryptoMethod) CryptoMethod {
	// We prefer plaintext for performance reasons.
	if provided&CryptoMethodPlaintext != 0 {
		return CryptoMethodPlaintext
	}
	return CryptoMethodRC4
}

type CryptoSelector func(CryptoMethod) CryptoMethod
