package object

import "bytes"

const (
	signatureTypeUnknown signatureType = iota
	signatureTypeOpenPGP
	signatureTypeX509
	signatureTypeSSH
)

var (
	// openPGPSignatureFormat is the format of an OpenPGP signature.
	openPGPSignatureFormat = signatureFormat{
		[]byte("-----BEGIN PGP SIGNATURE-----"),
		[]byte("-----BEGIN PGP MESSAGE-----"),
	}
	// x509SignatureFormat is the format of an X509 signature, which is
	// a PKCS#7 (S/MIME) signature.
	x509SignatureFormat = signatureFormat{
		[]byte("-----BEGIN SIGNED MESSAGE-----"),
	}

	// sshSignatureFormat is the format of an SSH signature.
	sshSignatureFormat = signatureFormat{
		[]byte("-----BEGIN SSH SIGNATURE-----"),
	}
)

// knownSignatureFormats is a map of known signature formats, indexed by
// their signatureType.
var knownSignatureFormats = map[signatureType]signatureFormat{
	signatureTypeOpenPGP: openPGPSignatureFormat,
	signatureTypeX509:    x509SignatureFormat,
	signatureTypeSSH:     sshSignatureFormat,
}

// signatureType represents the type of the signature.
type signatureType int8

// signatureFormat represents the beginning of a signature.
type signatureFormat [][]byte

// typeForSignature returns the type of the signature based on its format.
func typeForSignature(b []byte) signatureType {
	for t, i := range knownSignatureFormats {
		for _, begin := range i {
			if bytes.HasPrefix(b, begin) {
				return t
			}
		}
	}
	return signatureTypeUnknown
}

// parseSignedBytes returns the position of the last signature block found in
// the given bytes. If no signature block is found, it returns -1.
//
// When multiple signature blocks are found, the position of the last one is
// returned. Any tailing bytes after this signature block start should be
// considered part of the signature.
//
// Given this, it would be safe to use the returned position to split the bytes
// into two parts: the first part containing the message, the second part
// containing the signature.
//
// Example:
//
//	message := []byte(`Message with signature
//
//	-----BEGIN SSH SIGNATURE-----
//	...`)
//
//	var signature string
//	if pos, _ := parseSignedBytes(message); pos != -1 {
//		signature = string(message[pos:])
//		message = message[:pos]
//	}
//
// This logic is on par with git's gpg-interface.c:parse_signed_buffer().
// https://github.com/git/git/blob/7c2ef319c52c4997256f5807564523dfd4acdfc7/gpg-interface.c#L668
func parseSignedBytes(b []byte) (int, signatureType) {
	n, match := 0, -1
	var t signatureType
	for n < len(b) {
		i := b[n:]
		if st := typeForSignature(i); st != signatureTypeUnknown {
			match = n
			t = st
		}
		if eol := bytes.IndexByte(i, '\n'); eol >= 0 {
			n += eol + 1
			continue
		}
		// If we reach this point, we've reached the end.
		break
	}
	return match, t
}
