package server

import (
	"bytes"
	"crypto"
	"encoding/hex"
	"fmt"
	"strings"
	"testing"

	attestationpb "github.com/GoogleCloudPlatform/confidential-space/server/proto/gen/attestation"

	"github.com/containerd/containerd/protobuf/proto"
	"github.com/google/go-cmp/cmp"
	"github.com/google/go-configfs-tsm/configfs/fakertmr"
	configfstsmrtmr "github.com/google/go-configfs-tsm/rtmr"
	gecel "github.com/google/go-eventlog/cel"
	gepb "github.com/google/go-eventlog/proto/state"
	"github.com/google/go-eventlog/register"
	"github.com/google/go-tdx-guest/rtmr"
	"github.com/google/go-tpm-tools/cel"
	"github.com/google/go-tpm-tools/client"
	"github.com/google/go-tpm-tools/internal/test"
	attestpb "github.com/google/go-tpm-tools/proto/attest"
	pb "github.com/google/go-tpm-tools/proto/tpm"
	"github.com/google/go-tpm/legacy/tpm2"
	"github.com/google/go-tpm/tpmutil"
	"google.golang.org/protobuf/testing/protocmp"
)

type eventLog struct {
	RawLog                []byte
	Banks                 []*pb.PCRs
	ExpectedEFIAppDigests map[pb.HashAlgo][]string
}

// The Arch Linux event log has two known failures due to our parser's strict checks.
var archLinuxKnownParsingFailures = []string{
	"SecureBoot data len is 0, expected 1",
}

// Agile Event Log from a RHEL 8 GCE instance with Secure Boot enabled
var Rhel8GCE = eventLog{
	RawLog: test.Rhel8EventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("0f2d3a2a1adaa479aeeca8f5df76aadc41b862ea"),
			1:  decodeHex("5cc549378bafaa92e965c7e9c287925cfff33abd"),
			2:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4:  decodeHex("7fbe2df30156ca4934109f48d850ab327110f8fa"),
			5:  decodeHex("3258daa13f4cccf245c170481c76e2a4602e5a7b"),
			6:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7:  decodeHex("d7a632f8990b2171e987041b0a3c69fc1b2a4f27"),
			8:  decodeHex("15aab2077008f8325e7c61ee39fedd7118aad5d7"),
			9:  decodeHex("25de9455ef4e8180b76bbb9bb54a82f9a73abb0a"),
			14: decodeHex("1f5149668c40524e01be9cbc3ad527645943f148"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("24af52a4f429b71a3184a6d64cddad17e54ea030e2aa6576bf3a5a3d8bd3328f"),
			1:  decodeHex("454220afaa80c83c3839f6cccd8b3c88bf4f562316a9dda1121c578c9e005a53"),
			2:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4:  decodeHex("758a3d35f1b0ff5b135dacd07db0c8132c0ac665d944090d4bf96e66447a245c"),
			5:  decodeHex("53d0ee36163219201e686167bbb71ec505b3ba2917b9d9183ed84aad26cfeb89"),
			6:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7:  decodeHex("5fd54361d580eb7592adb8deb236ff35444ceeac7148f24b3de63c041f12b3da"),
			8:  decodeHex("25c3874041ebd4e9a21b6ed71b624a7bfa99907a8dcea7f129a4c64cbaf5829a"),
			9:  decodeHex("d43b2f61eb18b4791812ff5f20ab20e4ef621ba683370bedf5dbdf518b3a8078"),
			14: decodeHex("d8f57ebcc1a23cc46832696e1a657f720e1be8f5b405bb7204682114e363b455"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"95f400d9003b4e8c0cb4734efcf547e36fc4100c",
			"4f60d11ad6ac9a76837834f1371bc9521d018779",
			"075f3bc8c7363c35a87ce56c604fa9201a97f79d",
		},
		pb.HashAlgo_SHA256: {
			"40d6cae02973789080cf4c3a9ad11b5a0a4d8bba4438ab96e276cc784454dee7",
			"e8a268c431da72caaae407f729f602b9dbf5d1d43492d4a51cc2b688a08586e3",
			"e4c0382f98feaebfd43923a85fd6da9a20e1a48524a4d5928c31850ca1a96a6e",
		},
		pb.HashAlgo_SHA384: {
			"66de9a210659294720af06838309fc1f4d0de82c646a62c1dd9f068cd331d2e05fd666377dbc11e84a796ce00108ab19",
			"c1d031b07446588fa50f4eec3d8520d99ed01f21350b9c581e13f4c5a8c712cb5e3cbecc41ccab74465543439f7eb1e6",
			"d844e63b32a73aadde4f78dda7cb7df73d75114f3a5964401847eb716142a06607ea95efee20f51283e85afca8da3afd",
		},
	},
}

// Agile Event Log from a Ubuntu 18.04 GCE instance with Secure Boot and
// Confidential Computing enabled.
var UbuntuAmdSevGCE = eventLog{
	RawLog: test.Ubuntu1804AmdSevEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0: decodeHex("c032c3b51dbb6f96b047421512fd4b4dfde496f3"),
			1: decodeHex("35f38e5ce90728b02a0f66d836eef53d287e69bf"),
			2: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4: decodeHex("41c68947aeee8a59110c7989a9b7a55df547f003"),
			5: decodeHex("baee22b5cce9029300f909add54d75d5d7475cfd"),
			6: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7: decodeHex("6530ed2dcba68801c78ca08753f239118bead7c8"),
			8: decodeHex("4e5533d878287970f3ef8d374fb140d93bcb2c37"),
			9: decodeHex("1b79f2140a84462cb13d1a0c1904daefd24d7938"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0: decodeHex("0f35c214608d93c7a6e68ae7359b4a8be5a0e99eea9107ece427c4dea4e439cf"),
			1: decodeHex("add81cbc06b154716ac7bd5999c84cbc520184d57c58102657d270274508d9ce"),
			2: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4: decodeHex("b4b94e840fc9352e20bdb5b456b4c242af0fb146755b6935d8eda000ea368a31"),
			5: decodeHex("0b75168095fd6464ff1f9943b762ec009a3ae84c5e76cf67361e16b9db30d28e"),
			6: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7: decodeHex("61af3f499f1a86be54458fd30d193fa913a7e23ca3103fa3d0abaefd3cd4f9b8"),
			8: decodeHex("c324da9d0c54252c37af697cdd58b066f2bb0f4a69752d27623bc738d02e9486"),
			9: decodeHex("2d334f1eeb9a16dabaccaa746ff1c0dce2e9aeb3f3a4a314e5e1e61b01e940d0"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"21e79438580ec89df674dfe12653d77d132c3936",
			"9a4c7c895a5d40c3906121ff59c6fe267a4c32e0",
		},
		pb.HashAlgo_SHA256: {
			"2ea4cb6a1f1eb1d3dce82d54fde26ded243ba3e18de7c6d211902a594fe56788",
			"835f940e97bac2f7c171819b1fcc4bebe72a1c4ea7d7245088ef32d253085bb3",
		},
		pb.HashAlgo_SHA384: {
			"9b2baf7073fd9b7df3091b69ae7e48453450ae7b5311b37de11b79da75f175b8b2ed69f7d39406501653b35cbe90a030",
			"b0a19b24395a4690eea97916483dc291a38c6023df20aa296d85064194cebe9097f6b5e8490fd57a4e6b01167a8c9c7c",
		},
	},
}

// Agile Event Log from a Ubuntu 21.04 GCE instance without a DBX and with Secure Boot disabled
var Ubuntu2104NoDbxGCE = eventLog{
	RawLog: test.Ubuntu2104NoDbxEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("0f2d3a2a1adaa479aeeca8f5df76aadc41b862ea"),
			1:  decodeHex("36c6b7436c37243c5f6744b73ced4df1287cd16a"),
			2:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4:  decodeHex("8d9868b66afcf4039eaf8ef5228556d9f313659f"),
			5:  decodeHex("b0eaa45a496e0d933f63e97fd2362192dd48e369"),
			6:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7:  decodeHex("777795cbdeca679f7749d8d09fc12941dcc9912a"),
			8:  decodeHex("5dfae5320ea06ddd1c62d296844a9b4b32b49972"),
			9:  decodeHex("f53869ab9015b5ad736e5f00e44fdfee2fdfde27"),
			14: decodeHex("cd3734d2bdfcfba9e443ac02c03c812ffcceb255"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("24af52a4f429b71a3184a6d64cddad17e54ea030e2aa6576bf3a5a3d8bd3328f"),
			1:  decodeHex("f7dab5fda6b082e0ec1a12c43dd996ee409111422cda752a784620313039db19"),
			2:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4:  decodeHex("295aeaeacad1d507930bab18418f905eeda633ea67b2ab94c5e5fd3a4d47ac58"),
			5:  decodeHex("e4f1359accfe48b19af7d38e98a3f373116b55b7f7a6f58f826f409a91d9fd28"),
			6:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7:  decodeHex("ca37324eeffabd318d30a20f15bf27ce25dc33e2c9856279ff6c2ced58b02efa"),
			8:  decodeHex("2f2559cae74bb441d75afea5edb78d9a645db9f4bf8dea84bab0861ce6032e18"),
			9:  decodeHex("9f27883322aaaf043662c27542d9685790c687ea554e4e2ae30f0e099a2e4889"),
			14: decodeHex("8351c65483c5419079e8c96758dd2130bee075d71fea226f68ec4eb5bfc71983"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"92e6ec17937f600b9ec7f23adf4ea5553b4e2364",
			"4f9604e61091095594c206c8a404afe187a92586",
		},
		pb.HashAlgo_SHA256: {
			"d99c93fcb042dbe52707bbde371c75fcf081dd5b0c88a195d44cc57536f6f521",
			"b0a836fec2faf4a9bea0e1a5f1945bc86ddc03ac98ce0ae172ed9b1e536d7595",
		},
		pb.HashAlgo_SHA384: {
			"d8811e9c08119168b156255c6d695614d1593422bc5044186d29c1aaaa86fff0a633f324ac1ac1122e547479ce50a75a",
			"bbcdda8a6d872385b10802434eb8de1ac7b92dbaddf18bc1d7ea24fcc71b45291db5cc7b930a29c93405d6aecdb70683",
		},
	},
}

// Agile Event Log from a Ubuntu 21.04 GCE instance with Secure Boot disabled
var Ubuntu2104NoSecureBootGCE = eventLog{
	RawLog: test.Ubuntu2104NoSecureBootEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("0f2d3a2a1adaa479aeeca8f5df76aadc41b862ea"),
			1:  decodeHex("f5310dfcfcec5571cbf730064d526906c9cea2f0"),
			2:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4:  decodeHex("e53d909941dcbc699b273fc4c0d817a41c6ab975"),
			5:  decodeHex("9e2af4bac1432830594b1ae90c68c52a20a9700e"),
			6:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7:  decodeHex("ede7204673f41ac2592b0d3b4cd429b43f39dc61"),
			8:  decodeHex("bda59abe1c7d18e0b85edfcb4381f10d4dcc88f7"),
			9:  decodeHex("39fd49224476f4d7eea26a53e264c9c33e47649c"),
			14: decodeHex("cd3734d2bdfcfba9e443ac02c03c812ffcceb255"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("24af52a4f429b71a3184a6d64cddad17e54ea030e2aa6576bf3a5a3d8bd3328f"),
			1:  decodeHex("45ed8540f34db53220ef197e5fb8a3835b2095454349e445f397f13d91c509a5"),
			2:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4:  decodeHex("ebc7ae25d0347868250995c9a8fff16bf79e048453262d0ef2756e213c76181c"),
			5:  decodeHex("47715f9f2c10769da6ee23be5633fd88e247caf162f4eeb0b6f8482ccfeadfb5"),
			6:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7:  decodeHex("0d8847bc5eca06452df10e2f214363845c7ac11d47525a5474e225e72ce25dfe"),
			8:  decodeHex("b9a324947de94ec2fd4b04483ecfcb37dfdd520a7c0ecf73c77bf2595549c84f"),
			9:  decodeHex("adb87be3efd96cc3a2f66b8aa7564f9727563ef494a95d571a3f38ff4afb25dd"),
			14: decodeHex("8351c65483c5419079e8c96758dd2130bee075d71fea226f68ec4eb5bfc71983"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"22df40d6e32d4721f1b2406b2b4a3bb0ca10ead5",
			"4f9604e61091095594c206c8a404afe187a92586",
		},
		pb.HashAlgo_SHA256: {
			"6265b732b005b3f330bcd1843374e5ec6ec5aef27cdb97a23daeb8580abbf526",
			"b0a836fec2faf4a9bea0e1a5f1945bc86ddc03ac98ce0ae172ed9b1e536d7595",
		},
		pb.HashAlgo_SHA384: {
			"4f491210da8f59f09cd16523b44db22e83d8b611c3b14656d3b078dd451347ab195177fc78cf8d5578376f1f5f9bb821",
			"bbcdda8a6d872385b10802434eb8de1ac7b92dbaddf18bc1d7ea24fcc71b45291db5cc7b930a29c93405d6aecdb70683",
		},
	},
}

// Agile Event Log from a Ubuntu 24.04 GCE AMD_SEVSNP instance with Secure Boot disabled
var Ubuntu2404AmdSevSnp = eventLog{
	RawLog: test.Ubuntu2404AmdSevSnpEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("8124f09f069c7d2d9acf5ce4eab928a7103a0bb2"),
			1:  decodeHex("f00d6bbdea9ba55996f237a7f95f2b328a44e3f2"),
			2:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4:  decodeHex("175f4319fd7ac683bf49f2e7b837630e4fa8603f"),
			5:  decodeHex("f65b39c7aec83294f796c1ea4acc987f80914efe"),
			6:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7:  decodeHex("7067b17aa6b3de0d22d17a59dce1e17e649cb56a"),
			8:  decodeHex("5f4a1177c33521b0e48d855cf770520f8ab744de"),
			9:  decodeHex("c6ee69063ab752df6c4ab99a80b12f3e5c432535"),
			14: decodeHex("a482a15e112717d6a915b989a0ea6140a507e3e6"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("50597a27846e91d025eef597abbc89f72bff9af849094db97b0684d8bc4c515e"),
			1:  decodeHex("57344e1cc8c6619413df33013a7cd67915459f967395af41db21c1fa7ca9c307"),
			2:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4:  decodeHex("abe8b3fa6aecb36c2fd93c6f6edde661c21b353d007410a2739d69bfa7e1b9be"),
			5:  decodeHex("0b0e1903aeb1bff649b82dba2cdcf5c4ffb75027e54f151ab00b3b989f16a300"),
			6:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7:  decodeHex("33ad69850fb2c7f30b4f8b4bc10ed93fc954dc07fa726e84f50f3d192dc1c140"),
			8:  decodeHex("6932a3f71dc55ad3c1a6ac2196eeac26a1b7164b6bbfa106625d94088ec3ecc3"),
			9:  decodeHex("ce08798b283c7a0ddc5e9ad1d602304b945b741fc60c20e254eafa0f4782512b"),
			14: decodeHex("306f9d8b94f17d93dc6e7cf8f5c79d652eb4c6c4d13de2dddc24af416e13ecaf"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"7eac7a5171a01cf975bb6ac1b0eb6eb79a391d5e",
			"ec49599026c979912d8f18cfd4b260516a4d4ac1",
		},
		pb.HashAlgo_SHA256: {
			"724de6844dd0fe618ba5776c7bca0728be38a6544e24e44ef259b987b7abce80",
			"5e8cb75acdf8e09e5fc14cc2d6ce0c2288af208976d97309851c661e91ec1e03",
		},
		pb.HashAlgo_SHA384: {
			"4637fb5cd30847e5f09ae24f8a50ce1611c4d21afd0ecb69c8ec40bc82dc11bc48abda1f8044fe340bfb70b29606eb47",
			"c051991523ea083f466f13c2a2d11d77254f6110bc8ae3714f345cef8f33cde26082b49dda0f56ef324a62a10b556d1e",
		},
	},
}

// Agile Event Log from Alex's gLinux laptop with secure boot disabled
var GlinuxNoSecureBootLaptop = eventLog{
	RawLog: test.GlinuxAlexEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0: decodeHex("29d236609a5f9cc6912af44ba5f57b13a17c8a84"),
			1: decodeHex("db16852a369b2503d6cc6c0007501c837dbe1170"),
			2: decodeHex("0c8ef58d40b8cd1fe15f6b45fc1b385dd251eec0"),
			3: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4: decodeHex("c56cddf3dcf59a473a239efd17b130391e24b0df"),
			5: decodeHex("23606963a2813421f5b6e76e32a337ff8940e413"),
			6: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7: decodeHex("9221b8fc57b60cb7de507dc016f88d4600cde9c5"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0: decodeHex("0e5ea849d7647a1ac1becc096fee4df98f00f8015f934afadaab0b8aa20b38a5"),
			1: decodeHex("9750400838980c9419764b9cf19c975c0e159c18ebe21cb897c6e834a8d8d433"),
			2: decodeHex("970096d49105b0404999173e49c3f6b8597b9c4c5ff6a9e364b55ce01037578e"),
			3: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4: decodeHex("ddb124ca9013f1e42f98537f7f381e47c5e6caa988cf2b4088f452c5a8dd912d"),
			5: decodeHex("fb58603615cfec59c0428e71913d30d45f38e4280380cc814135a7659c246b13"),
			6: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7: decodeHex("9d1be46302bc4f5055c90a0376d9142e397ca8744f387c9824170f1bc855fde5"),
		},
	}},
}

// Agile Event Log from an Arch Linux worksation with systemd-boot and Secure Boot Disabled
var ArchLinuxWorkstation = eventLog{
	RawLog: test.ArchLinuxWorkstationEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0: decodeHex("a0487b0d95387d4a30560edf5f041307bf4a1dcc"),
			1: decodeHex("56b71c334a5b67d3b7b3343e3241dff5a1ad87bf"),
			2: decodeHex("01098a68e44e4fbd0af3b9a836b1b79e78c4f6f5"),
			3: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4: decodeHex("4c8b6f359b5e5cb9d09e825009a98e1281165b01"),
			5: decodeHex("0dfa5ca60508ac5214515b20ed3e66289514fcb6"),
			6: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7: decodeHex("029c700c2fa2bc83cbf3ce4ee501ad4d984ec5ae"),
			8: decodeHex("aa99fc93faa0777f42da6e1ae77a0653b5005619"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0: decodeHex("758b773d94feabf52ef5a4c00a7ad2c80d8d6e6d9d58756150be9bc973da9087"),
			1: decodeHex("bfda688a5d320123fddb3fc70b746bc17647e2e7f2f96e130d429542bf4622d5"),
			2: decodeHex("65dee4a48cde677aa89fa83c5c35e883fda658f743853e3ebad504ca6702f7c5"),
			3: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4: decodeHex("925d453d3dfef4ac0c72c957402163d45fa95d05e6d53f047263a3a60b598325"),
			5: decodeHex("202522f005ef625588bb7c9e21335ba96a63c5086306138885b3bb2c381730ca"),
			6: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7: decodeHex("3b4a4db44b7a872524055364e62e897ae678e0d47ab0809f65c3a4ed77f66ab9"),
			8: decodeHex("47591b43af431963eaeb5238a5c42eda1eb0014c27f7de7ae483066a2d2a2e61"),
		},
	}},
}

// Legacy Event Log from a Debian 10 GCE instance with Secure Boot enabled
var Debian10GCE = eventLog{
	RawLog: test.Debian10EventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0: decodeHex("0f2d3a2a1adaa479aeeca8f5df76aadc41b862ea"),
			1: decodeHex("b1676439cac1531683990fefe2218a43239d6fe8"),
			2: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4: decodeHex("1eb30816474a3f144e99b24e4ad480b2e51fd9e1"),
			5: decodeHex("019079179dbc0eb5992c500dcf8a095910ac590d"),
			6: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7: decodeHex("9e6c57e850f371c2a7fe02bca552149363952318"),
		},
	}},
	// We shouldn't use these digests, as this Debian firmware does not measure
	// ExitBootService events, which means an attacker could extend additional
	// events after UEFI hands off the event log.
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"47263679db883d7ad9adbc93d6a1fbf8095f0133",
			"3fae23b18d72350207661af3875f2c492e97621c",
			"89b08941b47dcfbd4c8b3f2bc0fad984cd836b21",
		},
	},
}

// Agile Event Log from a Ubuntu 21.04 GCE instance with Secure Boot disabled
var COS85AmdSev = eventLog{
	RawLog: test.Cos85AmdSevEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0: decodeHex("c032c3b51dbb6f96b047421512fd4b4dfde496f3"),
			1: decodeHex("e3e9e1d9deacd95b289bbbd3a1717a57af7d211b"),
			2: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4: decodeHex("6168c9ce88a8658920f2cf2f9012d3c6bbfab79b"),
			5: decodeHex("fb6b3a15b220a74b0c4f73416919476702e930e2"),
			6: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7: decodeHex("42e669233f0e826df5093abfd6998c020df2de88"),
			8: decodeHex("72778b0ba3c491db25eb7c8368cb1fb51f0ce458"),
			9: decodeHex("08bd04f0dbadf591510340d94a0019c0ddcb779f"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0: decodeHex("0f35c214608d93c7a6e68ae7359b4a8be5a0e99eea9107ece427c4dea4e439cf"),
			1: decodeHex("6eb40f5b6bfafcb9914d486ce59404acd24bc13a6a3c45cda3b44c9d7053d638"),
			2: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4: decodeHex("d690bdac2aa8b73a1d718cb91990df07d0747b07ea57b3b2d0f0d511f0d90491"),
			5: decodeHex("e9e0b32564b6f8215b1bd43954d9f910682d39c3b18abd4737ac3b797cf269e0"),
			6: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7: decodeHex("3365d7fa2b024c852913c06e04ffbfa6ea5289f743bbf1a76f7ffdf21ed84793"),
			8: decodeHex("9e9b6511ae6ad443aae4c7bf998ffffbcd271c874f1efab9d692f129eb6e6c18"),
			9: decodeHex("f4f2d92d6d54f6c41f2706fd98091317642e0680a7902c72893d41e3464a93b7"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"bfeec15d9359fe0aa8b5fb6451d1f73e5144c6d3",
			"860848ad3f129051f1e252749011cf7f7df837ee",
			"91cd5aa9c3e407237e8aeb122d4ab94494034a90",
		},
		pb.HashAlgo_SHA256: {
			"dba8d69ffb244496ac8ab2950695d3da539d6ac5ec660fc6b4bdde245284cf23",
			"f7bad83f87940312e4642530a9a6242e88529dc37a497d7d4e7c1c070566d542",
			"6f6afb3caed004e727200a0c310731bd8ab4cd391b2d95cedf67d08e1e8e5e7e",
		},
		pb.HashAlgo_SHA384: {
			"778bd7d6385d8ca0da5e504e3e554b67d98d9a712d957cb4cbb4d9b2e66ca96e31ddc18680af02b03a3a8a1b08da6aca",
			"d014c8c69b17ceb0f46be22b928f52684e717f40288246a61dadba00b1368c883cdde4e98762cc6788d94d0bcbd3f7ca",
			"ff8ff1db8fc98d02d944a90c58103b1b2ad3ba893ba4f302a006a572951491622341bb9387de20dd072cb8b6b3583cd0",
		},
	},
}

var COS93AmdSev = eventLog{
	RawLog: test.Cos93AmdSevEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0: decodeHex("c032c3b51dbb6f96b047421512fd4b4dfde496f3"),
			1: decodeHex("e3e9e1d9deacd95b289bbbd3a1717a57af7d211b"),
			2: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4: decodeHex("1e4b998edfb4d62fb88337a66b3af8be26159498"),
			5: decodeHex("3421f02e05d71fe4bd002cbe22e68c230397821d"),
			6: decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7: decodeHex("42e669233f0e826df5093abfd6998c020df2de88"),
			8: decodeHex("ec84952e0c5c96cd4404122131b8f86d5ac7df7d"),
			9: decodeHex("7a406f847075a86a55aa184cfe3fcef7eaff40a7"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0: decodeHex("0f35c214608d93c7a6e68ae7359b4a8be5a0e99eea9107ece427c4dea4e439cf"),
			1: decodeHex("6eb40f5b6bfafcb9914d486ce59404acd24bc13a6a3c45cda3b44c9d7053d638"),
			2: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4: decodeHex("871e8343044ae4c87b402dcb94b5e49715b1b8dc1b19c43ba0801422fabb39d4"),
			5: decodeHex("74be59dc8066011eade913db9a3db7978f93852c04816cba9427dd59b87042cc"),
			6: decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7: decodeHex("3365d7fa2b024c852913c06e04ffbfa6ea5289f743bbf1a76f7ffdf21ed84793"),
			8: decodeHex("ba18b7028111f1f193967cad3c23b5050f73061c0f119182ac0f42efd6a9159e"),
			9: decodeHex("0b1e4f9ca7bc8535c4c33f0025969d7abea008aa51dcd7f7c2d1068470e4bce4"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"d582c2803fd716f09e50c82967079ff593e1bc6b",
			"e3de6a97421ba8f329d4ba55e39df80013415a23",
			"03221584436f78e488cdaec3c691b7a18ff2f621",
		},
		pb.HashAlgo_SHA256: {
			"27cce48e55b3bfb6eb6206a4cc2b53a497846496a6264495006ab28dffa5623e",
			"e3e226fb8c8e3b3fdb56c706a0fbfda080f34068aef5a1889c1bfa95f04c2e72",
			"dc0aca594caee03705bcfa817e7f666692d89b713815f4793b7abbc2a0e00b6c",
		},
		pb.HashAlgo_SHA384: {
			"da419d9c92eb55b6e14f5665d81644fa163b908b1b1e317740f7a605f1734994dd90f4ea3373400c59fd7683751e30ef",
			"794e6206fe520d3b0bcbfd3e14b0dc8e41f6a8c3b131faef69442a11625fde690a1b77c46dcddcb443a8d3c1e3ea669c",
			"64b218ab263625b49da1172a9ab37cedbcd20d668beac1c3baac4cae640a1a7f77a07c05682b4147ec649c51243f6bbf",
		},
	},
}

var COS101AmdSev = eventLog{
	RawLog: test.Cos101AmdSevEventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA1,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("c032c3b51dbb6f96b047421512fd4b4dfde496f3"),
			1:  decodeHex("e3e9e1d9deacd95b289bbbd3a1717a57af7d211b"),
			2:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			3:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			4:  decodeHex("1ebe08ea6c45e0dfbd2aad903d2e0d3ab69fd7ad"),
			5:  decodeHex("1c7ca47e5c09a78a747b0e0f051cc8cad6431400"),
			6:  decodeHex("b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236"),
			7:  decodeHex("6847f752ad1795c279f289e1eecf0040cd53c1d4"),
			8:  decodeHex("a243d82bd1fa01ae487b7ba77dd73ebb7a17800a"),
			9:  decodeHex("fbbb8a8f120369810e7e161504556f0080afadac"),
			14: decodeHex("1ba610b2d80967338649a8f88f45810448814bfc"),
		},
	}, {
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("0f35c214608d93c7a6e68ae7359b4a8be5a0e99eea9107ece427c4dea4e439cf"),
			1:  decodeHex("6eb40f5b6bfafcb9914d486ce59404acd24bc13a6a3c45cda3b44c9d7053d638"),
			2:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4:  decodeHex("6d9f1a1d461cf77517e8d4c488c53f338a71c5a8e2b81ab7011c14f72cbc9a80"),
			5:  decodeHex("d1a1ab23a5c3d98fbacff3891bad42d8e9257d61e1f683f42c6c9fa949bf96c5"),
			6:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7:  decodeHex("2bc6edaa921f953cec0ffb28dad4f87114886603d6a782036502d28e69d97a48"),
			8:  decodeHex("ebb7c847c4ade99849bcffca236d32331224a530087a7ae4cb9f7db4c2e571b5"),
			9:  decodeHex("b5ad662e5eb9165825ee39ad66e851a67a193e0b87b27858f25ac58afa72ac57"),
			14: decodeHex("d0d95459205afae879514db7b85630f5d6b8272ed8c731bf92933dbc9fe99969"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA1: {
			"dc41c297c4ed857e9b6354cad8b448995c3052ea",
			"06ae09413b5107bb26aa68602ba4fe787d22f82e",
			"f894ac3a351baa3a5ce4dd8d6f497eb616723461",
			"f894ac3a351baa3a5ce4dd8d6f497eb616723461",
		},
		pb.HashAlgo_SHA256: {
			"c7ac5d44444affd8d4a7c5d3dea0ce20a71e05812fc18777a428d092f78ae3ff",
			"c5d3b47de11a9a2a4a15ef5cb7202d7800a10609c0dcecc46e3e963d476b76ce",
			"af4161084115c9d5c1872f4473fe974b535e3a9a767688293720ac2cc6f7f9a3",
			"af4161084115c9d5c1872f4473fe974b535e3a9a767688293720ac2cc6f7f9a3",
		},
		pb.HashAlgo_SHA384: {
			"72bf185794a865eb14fcdf93a2daa8ed281c932e2a7009d8489c38056389b3f3776d755ec703c95fb9c396f79dbd52c7",
			"5b38df39c7beec3bfd9c4cbd40c217bcbee190d1fa099a64c5f063d20efc3def26e48cbbd86d730c8eb4696a29759490",
			"968f2f6cb5bae537adfca30942803ddcda773bae368c042258e8818788265cd0e119936c9fcdb782785154a6705c5143",
			"968f2f6cb5bae537adfca30942803ddcda773bae368c042258e8818788265cd0e119936c9fcdb782785154a6705c5143",
		},
	},
}

var GdcHost = eventLog{
	RawLog: test.GdcHost,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("dab77c454bd12c27ff6b6ce1f9adca90b7a330c1cef0b5cd01cb89fb3bd0dffa"),
			1:  decodeHex("e9c706539943b2d9770715914f9b3946fab0265327bace4c479913acb9014051"),
			2:  decodeHex("7fde57284c6a0eabdc9b829db4e2ab0bb565c4189410de2474dd116bc18bafcc"),
			3:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4:  decodeHex("ded8b5d91a09c328b9859d8c9db5a346f1065224616b0ba66d6c83dba2b465e8"),
			5:  decodeHex("163ee251955b844012f1493aa962b2a18acbec194ea4856cdc45cd54c8540058"),
			6:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7:  decodeHex("2c9252609eda09899d96abe16b947d0e736c43271997c1fa5189e9bcd37ba516"),
			8:  decodeHex("8edecd4daa5194ea70a2a9f2c71c7c816bd3b1e0a1ca6f4abea7306250191eba"),
			9:  decodeHex("731d336f9f3255e80b429de54fb77b2ad5e485829eb386d661c668245f30f44b"),
			14: decodeHex("306f9d8b94f17d93dc6e7cf8f5c79d652eb4c6c4d13de2dddc24af416e13ecaf"),
		},
	}},
	ExpectedEFIAppDigests: map[pb.HashAlgo][]string{
		pb.HashAlgo_SHA256: {
			"c7ac5d44444affd8d4a7c5d3dea0ce20a71e05812fc18777a428d092f78ae3ff",
			"c5d3b47de11a9a2a4a15ef5cb7202d7800a10609c0dcecc46e3e963d476b76ce",
			"af4161084115c9d5c1872f4473fe974b535e3a9a767688293720ac2cc6f7f9a3",
			"af4161084115c9d5c1872f4473fe974b535e3a9a767688293720ac2cc6f7f9a3",
		},
	},
}

var SP800155EventLog = eventLog{
	RawLog: test.SP800155EventLog,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("d0c70a9310cd0b55767084333022ce53f42befbb69c059ee6c0a32766f160783"),
			1:  decodeHex("f28e7dc5d058b98ba6f635c1de061a507426983b7ec370d3ba7ce90bdfd1a7c1"),
			2:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4:  decodeHex("18314f0c33adea45e461155e190199716256ca532cd89e9aa3b7d2f6407fa4c8"),
			5:  decodeHex("7da8fcc5689af6d0f2a089b19bbe146702372ccfdc3d5758bf6b49ba50c7366e"),
			6:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7:  decodeHex("8c5dc451764d0db9c1e17da03564bcae3a09fa3d7f8e467d9c381dc8698377d6"),
			8:  decodeHex("98effecabbbdc67dd7627d0c50f24bbce548ca6e6fcca30ad284afde91a042a4"),
			9:  decodeHex("06f41037a0f42d7308ff2d955ef6c0a5d2fe0a70f7c81ce288d3a158f4978b2f"),
			14: decodeHex("6eb05e8a8a6272a8e4b925a67c650daa13c12b1a80cc797d40fd345e29660161"),
		},
	}},
}

var CGKE251000 = eventLog{
	RawLog: test.CGKE251000,
	Banks: []*pb.PCRs{{
		Hash: pb.HashAlgo_SHA256,
		Pcrs: map[uint32][]byte{
			0:  decodeHex("d0c70a9310cd0b55767084333022ce53f42befbb69c059ee6c0a32766f160783"),
			1:  decodeHex("f28e7dc5d058b98ba6f635c1de061a507426983b7ec370d3ba7ce90bdfd1a7c1"),
			2:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			3:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			4:  decodeHex("18314f0c33adea45e461155e190199716256ca532cd89e9aa3b7d2f6407fa4c8"),
			5:  decodeHex("7da8fcc5689af6d0f2a089b19bbe146702372ccfdc3d5758bf6b49ba50c7366e"),
			6:  decodeHex("3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"),
			7:  decodeHex("8c5dc451764d0db9c1e17da03564bcae3a09fa3d7f8e467d9c381dc8698377d6"),
			8:  decodeHex("98effecabbbdc67dd7627d0c50f24bbce548ca6e6fcca30ad284afde91a042a4"),
			9:  decodeHex("4df4604236d15333b1a07acdb3c4655f3da5c0843b62e7a9df2f62844a9d92f1"),
			14: decodeHex("6eb05e8a8a6272a8e4b925a67c650daa13c12b1a80cc797d40fd345e29660161"),
		},
	}},
}

func TestParseEventLogs(t *testing.T) {
	logs := []struct {
		eventLog
		name string
		opts VerifyOpts
		// This field handles known issues with event log parsing or bad event
		// logs.
		// Set to nil when the event log has no known issues.
		errorSubstrs []string
	}{
		{Debian10GCE, "Debian10GCE", VerifyOpts{Loader: UnsupportedLoader}, nil},
		{Rhel8GCE, "Rhel8GCE", VerifyOpts{Loader: GRUB}, nil},
		{UbuntuAmdSevGCE, "UbuntuAmdSevGCE", VerifyOpts{Loader: GRUB}, nil},
		{Ubuntu2104NoDbxGCE, "Ubuntu2104NoDbxGCE", VerifyOpts{Loader: GRUB}, nil},
		{Ubuntu2104NoSecureBootGCE, "Ubuntu2104NoSecureBootGCE", VerifyOpts{Loader: GRUB}, nil},
		// This event log has a SecureBoot variable length of 0.
		{ArchLinuxWorkstation, "ArchLinuxWorkstation", VerifyOpts{Loader: UnsupportedLoader, AllowEFIAppBeforeCallingEvent: true}, archLinuxKnownParsingFailures},
		{COS85AmdSev, "COS85AmdSev", VerifyOpts{Loader: GRUB}, nil},
		{COS93AmdSev, "COS93AmdSev", VerifyOpts{Loader: GRUB}, nil},
		{COS101AmdSev, "COS101AmdSev", VerifyOpts{Loader: GRUB}, nil},
		{Ubuntu2404AmdSevSnp, "Ubuntu2404AmdSevSnp", VerifyOpts{Loader: GRUB}, nil},
		{GdcHost, "GdcHost", VerifyOpts{Loader: GRUB, AllowEFIAppBeforeCallingEvent: true}, []string{"invalid SCRTM version event for PCR0"}},
		{SP800155EventLog, "SP800155EventLog", VerifyOpts{Loader: GRUB}, nil},
		{CGKE251000, "CGKE251000", VerifyOpts{Loader: GRUB}, nil},
	}

	for _, log := range logs {
		rawLog := log.RawLog
		for _, bank := range log.Banks {
			hashName := pb.HashAlgo_name[int32(bank.Hash)]
			subtestName := fmt.Sprintf("%s-%s", log.name, hashName)
			t.Run(subtestName, func(t *testing.T) {
				if _, err := parsePCClientEventLog(rawLog, bank, log.opts); err != nil {
					if uw, ok := err.(interface{ Unwrap() []error }); ok {
						errs := uw.Unwrap()
						for _, err := range errs {
							for _, expectedErr := range log.errorSubstrs {
								if !strings.Contains(err.Error(), expectedErr) {
									t.Errorf("got unexpected error when parsing eventlog: %v", expectedErr)
								}
							}
						}
					} else {
						t.Errorf("got unexpected error when unwrapping err: %v", err)
					}
				} else {
					if log.errorSubstrs != nil {
						t.Errorf("expected errors when parsing %v, received nil", log.errorSubstrs)
					}
				}
			})
		}
	}
}

func TestParseMachineStateFail(t *testing.T) {
	badPcrs := pb.PCRs{Hash: pb.HashAlgo_SHA1}
	pcrMap := make(map[uint32][]byte)
	pcrMap[0] = []byte{0, 0, 0, 0}
	badPcrs.Pcrs = pcrMap

	_, err := parsePCClientEventLog(Debian10GCE.RawLog, &badPcrs, VerifyOpts{Loader: UnsupportedLoader})
	if err == nil {
		t.Errorf("parsePCClientEventLog should fail to replay the event log")
	}
}

func TestSystemParseEventLog(t *testing.T) {
	rwc := test.GetTPM(t)
	defer client.CheckedClose(t, rwc)

	evtLog, err := client.GetEventLog(rwc)
	if err != nil {
		t.Fatalf("failed to retrieve Event Log: %v", err)
	}

	sel := client.FullPcrSel(tpm2.AlgSHA1)
	pcrs, err := client.ReadPCRs(rwc, sel)
	if err != nil {
		t.Fatalf("failed to read PCRs: %v", err)
	}

	if _, err = parsePCClientEventLog(evtLog, pcrs, VerifyOpts{Loader: UnsupportedLoader}); err != nil {
		t.Errorf("failed to parse MachineState: %v", err)
	}
}

func TestEmptyEventlog(t *testing.T) {
	emptyLog := []byte{}
	emptyState := &attestpb.MachineState{
		Hash:       pb.HashAlgo_SHA1,
		Platform:   &attestpb.PlatformState{Firmware: &attestpb.PlatformState_ScrtmVersionId{}},
		SecureBoot: &attestpb.SecureBootState{},
	}

	// SHA-1 PCR data consisting of all zero digests (i.e. the reset state)
	zeroDigest := make([]byte, crypto.SHA1.Size())
	zeroPCRs := &pb.PCRs{Hash: pb.HashAlgo_SHA1, Pcrs: make(map[uint32][]byte)}
	for i := uint32(0); i < 24; i++ {
		zeroPCRs.Pcrs[i] = zeroDigest
	}

	// For our "Real" PCR data, use the simulated TPM (which has extended events)
	rwc := test.GetTPM(t)
	defer client.CheckedClose(t, rwc)
	realPCRs, err := client.ReadPCRs(rwc, client.FullPcrSel(tpm2.AlgSHA1))
	if err != nil {
		t.Fatalf("failed to read PCRs: %v", err)
	}

	cases := []struct {
		name string
		pcrs *pb.PCRs
	}{
		{"Empty", &pb.PCRs{Hash: pb.HashAlgo_SHA1}},
		{"AllZero", zeroPCRs},
		{"Real", realPCRs},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			state, err := parsePCClientEventLog(emptyLog, c.pcrs, VerifyOpts{Loader: UnsupportedLoader})
			if err != nil {
				t.Errorf("parsing empty eventlog: %v", err)
			}
			if diff := cmp.Diff(state, emptyState, protocmp.Transform(), protocmp.IgnoreEmptyMessages()); diff != "" {
				t.Errorf("unexpected non-empty MachineState:\n%v", diff)
			}
		})
	}
}

func TestParseSecureBootState(t *testing.T) {
	for _, bank := range UbuntuAmdSevGCE.Banks {
		msState, err := parsePCClientEventLog(UbuntuAmdSevGCE.RawLog, bank, VerifyOpts{Loader: UnsupportedLoader})
		if err != nil {
			t.Errorf("failed to parse and replay log: %v", err)
		}
		containsWinProdPCA := false
		contains3PUEFI := false
		if len(msState.GetSecureBoot().GetDb().GetHashes()) != 0 {
			t.Error("found hashes in db")
		}
		for _, cert := range msState.GetSecureBoot().GetDb().GetCerts() {
			switch c := cert.GetRepresentation().(type) {
			case *attestpb.Certificate_WellKnown:
				if c.WellKnown == attestpb.WellKnownCertificate_UNKNOWN {
					t.Error(("found WellKnownCertificate_UNKNOWN in db"))
				}
				if c.WellKnown == attestpb.WellKnownCertificate_MS_THIRD_PARTY_UEFI_CA_2011 {
					contains3PUEFI = true
				} else if c.WellKnown == attestpb.WellKnownCertificate_MS_WINDOWS_PROD_PCA_2011 {
					containsWinProdPCA = true
				}
			}
		}
		if !contains3PUEFI || !containsWinProdPCA {
			t.Error("expected to see both WinProdPCA and ThirdPartyUEFI certs")
		}

		if len(msState.GetSecureBoot().GetPk().GetHashes()) != 0 {
			t.Error("found hashes in pk")
		}
		pkCerts := msState.GetSecureBoot().GetPk().GetCerts()
		if len(pkCerts) != 1 {
			t.Errorf("expected to see exactly one cert in pk, but found %d", len(pkCerts))
		} else {
			switch c := pkCerts[0].GetRepresentation().(type) {
			case *attestpb.Certificate_WellKnown:
				if c.WellKnown != attestpb.WellKnownCertificate_GCE_DEFAULT_PK {
					t.Error("expected to see WellKnownCertificate_GCE_DEFAULT_PK in pk got a different well known cert")
				}
			default:
				t.Error("expected to see WellKnownCertificate_GCE_DEFAULT_PK in pk")
			}
		}

		if len(msState.GetSecureBoot().GetKek().GetHashes()) != 0 {
			t.Error("found hashes in kek")
		}
		kekCerts := msState.GetSecureBoot().GetKek().GetCerts()
		if len(kekCerts) != 1 {
			t.Errorf("expected to see exactly one cert in kek, but found %d", len(kekCerts))
		} else {
			switch c := kekCerts[0].GetRepresentation().(type) {
			case *attestpb.Certificate_WellKnown:
				if c.WellKnown != attestpb.WellKnownCertificate_MS_THIRD_PARTY_KEK_CA_2011 {
					t.Error("expected to see WellKnownCertificate_MS_THIRD_PARTY_KEK_CA_2011 in kek got a different well known cert")
				}
			default:
				t.Error("expected to see WellKnownCertificate_MS_THIRD_PARTY_KEK_CA_2011 in kek")
			}
		}
	}
}

func convertToPCRBank(t *testing.T, pcrs *pb.PCRs) register.PCRBank {
	pcrBank := register.PCRBank{TCGHashAlgo: gepb.HashAlgo(pcrs.Hash)}
	digestAlg, err := pcrBank.TCGHashAlgo.CryptoHash()
	if err != nil {
		t.Fatal(err)
	}
	for ind, dgst := range pcrs.GetPcrs() {
		pcrBank.PCRs = append(pcrBank.PCRs, register.PCR{
			Index:     int(ind),
			Digest:    dgst,
			DigestAlg: digestAlg},
		)
	}
	return pcrBank
}

func getRTMRBank(t *testing.T, fakeRTMR *fakertmr.RtmrSubsystem) register.RTMRBank {
	rtmrBank := register.RTMRBank{}
	// RTMR 0 to 3
	for i := 0; i < 4; i++ {
		digest, err := configfstsmrtmr.GetDigest(fakeRTMR, i)
		if err != nil {
			t.Fatal(err)
		}
		rtmrBank.RTMRs = append(rtmrBank.RTMRs, register.RTMR{Index: i, Digest: digest.Digest})
	}
	return rtmrBank
}

func TestParsingRTMREventlog(t *testing.T) {
	coscel := gecel.NewConfComputeMR()
	emptyCosState := attestpb.ContainerState{}
	emptyHealthMonitoringState := attestpb.HealthMonitoringState{}
	emptyGpuDeviceState := attestpb.GpuDeviceState{}

	report := &attestationpb.NvidiaAttestationReport{
		CcFeature: &attestationpb.NvidiaAttestationReport_Spt{
			Spt: &attestationpb.NvidiaAttestationReport_SinglePassthroughAttestation{
				GpuQuote: &attestationpb.GpuInfo{
					//
					Uuid:                "fake-gpu-uuid",
					VbiosVersion:        "fake-vbios-version",
					DriverVersion:       "fake-driver-version",
					GpuArchitectureType: attestationpb.GpuArchitectureType_GPU_ARCHITECTURE_TYPE_BLACKWELL,
				},
			},
		},
	}

	gpuEvidenceBytes, err := proto.Marshal(report)
	if err != nil {
		t.Fatalf("failed to marshal mock GPU evidence: %v", err)
	}

	var buf bytes.Buffer
	// First, encode an empty CEL and try to parse it.
	if err := coscel.EncodeCEL(&buf); err != nil {
		t.Fatal(err)
	}

	fakeRTMR := fakertmr.CreateRtmrSubsystem(t.TempDir())
	rtmrBank := getRTMRBank(t, fakeRTMR)

	acosState, err := ParseCosCELRTMR(buf.Bytes(), rtmrBank)
	if err != nil {
		t.Errorf("expecting no error from ParseCosCELRTMR(), but get %v", err)
	}
	if diff := cmp.Diff(acosState.Container, &emptyCosState, protocmp.Transform()); diff != "" {
		t.Errorf("unexpected container state difference:\n%v", diff)
	}
	if diff := cmp.Diff(acosState.HealthMonitoring, &emptyHealthMonitoringState, protocmp.Transform()); diff != "" {
		t.Errorf("unexpected health monitoring difference:\n%v", diff)
	}
	if acosState.HealthMonitoring.MemoryEnabled != nil {
		t.Errorf("unexpected MemoryEnabled state, want nil, but got %v", *acosState.HealthMonitoring.MemoryEnabled)
	}
	if diff := cmp.Diff(acosState.GpuDeviceState, &emptyGpuDeviceState, protocmp.Transform()); diff != "" {
		t.Errorf("unexpected GPU device state difference:\n%v", diff)
	}

	// add events
	testCELEvents := []struct {
		cosNestedEventType cel.CosType
		register           int
		eventPayload       []byte
	}{
		{cel.ImageRefType, cel.CosCCELMRIndex, []byte("docker.io/bazel/experimental/test:latest")},
		{cel.ImageDigestType, cel.CosCCELMRIndex, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")},
		{cel.RestartPolicyType, cel.CosCCELMRIndex, []byte(attestpb.RestartPolicy_Always.String())},
		{cel.ImageIDType, cel.CosCCELMRIndex, []byte("sha256:5DF4A1AC347DCF8CF5E9D0ABC04B04DB847D1B88D3B1CC1006F0ACB68E5A1F4B")},
		{cel.EnvVarType, cel.CosCCELMRIndex, []byte("foo=bar")},
		{cel.EnvVarType, cel.CosCCELMRIndex, []byte("bar=baz")},
		{cel.EnvVarType, cel.CosCCELMRIndex, []byte("baz=foo=bar")},
		{cel.EnvVarType, cel.CosCCELMRIndex, []byte("empty=")},
		{cel.ArgType, cel.CosCCELMRIndex, []byte("--x")},
		{cel.ArgType, cel.CosCCELMRIndex, []byte("--y")},
		{cel.ArgType, cel.CosCCELMRIndex, []byte("")},
		{cel.MemoryMonitorType, cel.CosCCELMRIndex, []byte{1}},
		{cel.GpuCCModeType, cel.CosCCELMRIndex, []byte(attestpb.GPUDeviceCCMode_ON.String())},
		{cel.GPUDeviceAttestationBindingType, cel.CosCCELMRIndex, gpuEvidenceBytes},
	}

	expectedEnvVars := make(map[string]string)
	expectedEnvVars["foo"] = "bar"
	expectedEnvVars["bar"] = "baz"
	expectedEnvVars["baz"] = "foo=bar"
	expectedEnvVars["empty"] = ""

	wantContainerState := attestpb.ContainerState{
		ImageReference: string(testCELEvents[0].eventPayload),
		ImageDigest:    string(testCELEvents[1].eventPayload),
		RestartPolicy:  attestpb.RestartPolicy_Always,
		ImageId:        string(testCELEvents[3].eventPayload),
		EnvVars:        expectedEnvVars,
		Args:           []string{string(testCELEvents[8].eventPayload), string(testCELEvents[9].eventPayload), string(testCELEvents[10].eventPayload)},
	}
	enabled := true
	wantHealthMonitoringState := attestpb.HealthMonitoringState{
		MemoryEnabled: &enabled,
	}
	wantGpuDeviceState := attestpb.GpuDeviceState{
		CcMode: attestpb.GPUDeviceCCMode_ON,
	}

	for _, testEvent := range testCELEvents {
		cosEvent := cel.CosTlv{EventType: testEvent.cosNestedEventType, EventContent: testEvent.eventPayload}
		coscel.AppendEvent(cosEvent, []crypto.Hash{crypto.SHA384}, testEvent.register, func(_ crypto.Hash, mrIndex int, digest []byte) error {
			return rtmr.ExtendDigestClient(fakeRTMR, mrIndex-1, digest) // MR_INDEX - 1 == RTMR_INDEX
		})
	}

	buf = bytes.Buffer{}
	if err := coscel.EncodeCEL(&buf); err != nil {
		t.Fatal(err)
	}

	rtmrBank = getRTMRBank(t, fakeRTMR)

	if acosState, err := ParseCosCELRTMR(buf.Bytes(), rtmrBank); err != nil {
		t.Errorf("expecting no error from ParseCosCELRTMR(), but get %v", err)
	} else {
		if diff := cmp.Diff(acosState.Container, &wantContainerState, protocmp.Transform()); diff != "" {
			t.Errorf("unexpected container state difference:\n%v", diff)
		}
		if diff := cmp.Diff(acosState.HealthMonitoring, &wantHealthMonitoringState, protocmp.Transform()); diff != "" {
			t.Errorf("unexpected health monitoring state difference:\n%v", diff)
		}
		if diff := cmp.Diff(acosState.GpuDeviceState, &wantGpuDeviceState, protocmp.Transform()); diff != "" {
			t.Errorf("unexpected GPU device state difference:\n%v", diff)
		}
	}

	// Faking PCR with RTMR should fail
	imposterPcrBank := map[uint32][]byte{}
	imposterPcrBank[1] = rtmrBank.RTMRs[0].Digest
	imposterPcrBank[2] = rtmrBank.RTMRs[1].Digest
	imposterPcrBank[3] = rtmrBank.RTMRs[2].Digest
	imposterPcrBank[4] = rtmrBank.RTMRs[3].Digest
	imposterPcrs := &pb.PCRs{Hash: pb.HashAlgo_SHA384, Pcrs: imposterPcrBank}
	hackedPCRBank := convertToPCRBank(t, imposterPcrs)
	if _, err = ParseCosCELPCR(buf.Bytes(), hackedPCRBank); err == nil {
		t.Errorf("expecting error from ParseCosCELPCR() when using RTMR CEL Log, but get nil")
	}
}

func TestParsingCELEventLog(t *testing.T) {
	test.SkipForRealTPM(t)
	tpm := test.GetTPM(t)
	defer client.CheckedClose(t, tpm)

	coscel := gecel.NewPCR()
	emptyCosState := attestpb.ContainerState{}
	emptyHealthMonitoringState := attestpb.HealthMonitoringState{}
	emptyGpuDeviceState := attestpb.GpuDeviceState{}

	var buf bytes.Buffer
	// First, encode an empty CEL and try to parse it.
	if err := coscel.EncodeCEL(&buf); err != nil {
		t.Fatal(err)
	}
	banks, err := client.ReadAllPCRs(tpm)
	if err != nil {
		t.Fatal(err)
	}

	implementedHashes := []crypto.Hash{}
	// get all implmented hash algo in the TPM
	for _, h := range banks {
		hsh, err := tpm2.Algorithm(h.Hash).Hash()
		if err != nil {
			t.Fatal(err)
		}
		implementedHashes = append(implementedHashes, crypto.Hash(hsh))
	}

	for _, bank := range banks {
		pcrBank := convertToPCRBank(t, bank)
		// pcrs can have any value here, since the coscel has no records, the replay should always success.
		acosState, err := ParseCosCELPCR(buf.Bytes(), pcrBank)
		if err != nil {
			t.Errorf("expecting no error from ParseCosCELPCR(), but get %v", err)
		}
		if diff := cmp.Diff(acosState.Container, &emptyCosState, protocmp.Transform()); diff != "" {
			t.Errorf("unexpected container state difference:\n%v", diff)
		}
		if diff := cmp.Diff(acosState.HealthMonitoring, &emptyHealthMonitoringState, protocmp.Transform()); diff != "" {
			t.Errorf("unexpected health monitoring difference:\n%v", diff)
		}
		if acosState.HealthMonitoring.MemoryEnabled != nil {
			t.Errorf("unexpected MemoryEnabled state, want nil, but got %v", *acosState.HealthMonitoring.MemoryEnabled)
		}
		if diff := cmp.Diff(acosState.GpuDeviceState, &emptyGpuDeviceState, protocmp.Transform()); diff != "" {
			t.Errorf("unexpected GPU device state difference:\n%v", diff)
		}
	}

	// Secondly, append some real COS events to the CEL. This time we should get content in the CosState.
	testCELEvents := []struct {
		cosNestedEventType cel.CosType
		pcr                int
		eventPayload       []byte
	}{
		{cel.ImageRefType, cel.CosEventPCR, []byte("docker.io/bazel/experimental/test:latest")},
		{cel.ImageDigestType, cel.CosEventPCR, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")},
		{cel.RestartPolicyType, cel.CosEventPCR, []byte(attestpb.RestartPolicy_Always.String())},
		{cel.ImageIDType, cel.CosEventPCR, []byte("sha256:5DF4A1AC347DCF8CF5E9D0ABC04B04DB847D1B88D3B1CC1006F0ACB68E5A1F4B")},
		{cel.EnvVarType, cel.CosEventPCR, []byte("foo=bar")},
		{cel.EnvVarType, cel.CosEventPCR, []byte("bar=baz")},
		{cel.EnvVarType, cel.CosEventPCR, []byte("baz=foo=bar")},
		{cel.EnvVarType, cel.CosEventPCR, []byte("empty=")},
		{cel.ArgType, cel.CosEventPCR, []byte("--x")},
		{cel.ArgType, cel.CosEventPCR, []byte("--y")},
		{cel.ArgType, cel.CosEventPCR, []byte("")},
		{cel.MemoryMonitorType, cel.CosEventPCR, []byte{1}},
		{cel.GpuCCModeType, cel.CosEventPCR, []byte(attestpb.GPUDeviceCCMode_OFF.String())},
	}

	expectedEnvVars := make(map[string]string)
	expectedEnvVars["foo"] = "bar"
	expectedEnvVars["bar"] = "baz"
	expectedEnvVars["baz"] = "foo=bar"
	expectedEnvVars["empty"] = ""

	wantContainerState := attestpb.ContainerState{
		ImageReference: string(testCELEvents[0].eventPayload),
		ImageDigest:    string(testCELEvents[1].eventPayload),
		RestartPolicy:  attestpb.RestartPolicy_Always,
		ImageId:        string(testCELEvents[3].eventPayload),
		EnvVars:        expectedEnvVars,
		Args:           []string{string(testCELEvents[8].eventPayload), string(testCELEvents[9].eventPayload), string(testCELEvents[10].eventPayload)},
	}
	enabled := true
	wantHealthMonitoringState := attestpb.HealthMonitoringState{
		MemoryEnabled: &enabled,
	}
	wantGpuDeviceState := attestpb.GpuDeviceState{
		CcMode: attestpb.GPUDeviceCCMode_OFF,
	}
	for _, testEvent := range testCELEvents {
		cosEvent := cel.CosTlv{EventType: testEvent.cosNestedEventType, EventContent: testEvent.eventPayload}

		coscel.AppendEvent(cosEvent, implementedHashes, testEvent.pcr, func(hs crypto.Hash, pcr int, digest []byte) error {
			tpm2Alg, err := tpm2.HashToAlgorithm(hs)
			if err != nil {
				return err
			}
			if err := tpm2.PCRExtend(tpm, tpmutil.Handle(pcr), tpm2Alg, digest, ""); err != nil {
				return fmt.Errorf("failed to extend event to PCR%d: %v", pcr, err)
			}
			return nil
		})
	}
	buf = bytes.Buffer{}
	if err := coscel.EncodeCEL(&buf); err != nil {
		t.Fatal(err)
	}
	banks, err = client.ReadAllPCRs(tpm)
	if err != nil {
		t.Fatal(err)
	}
	for _, bank := range banks {
		pcrBank := convertToPCRBank(t, bank)

		if acosState, err := ParseCosCELPCR(buf.Bytes(), pcrBank); err != nil {
			t.Errorf("expecting no error from ParseCosCELPCR(), but get %v", err)
		} else {
			if diff := cmp.Diff(acosState.Container, &wantContainerState, protocmp.Transform()); diff != "" {
				t.Errorf("unexpected container state difference:\n%v", diff)
			}
			if diff := cmp.Diff(acosState.HealthMonitoring, &wantHealthMonitoringState, protocmp.Transform()); diff != "" {
				t.Errorf("unexpected health monitoring state difference:\n%v", diff)
			}
			if diff := cmp.Diff(acosState.GpuDeviceState, &wantGpuDeviceState, protocmp.Transform()); diff != "" {
				t.Errorf("unexpected GPU device state difference:\n%v", diff)
			}
		}
	}

	// Thirdly, append a random non-COS event, encode and try to parse it.
	// Because there is no COS TLV event, attestation should fail as we do not
	// understand the content type.
	event := generateNonCosCelEvent()

	if err := coscel.AppendEvent(event, implementedHashes, cel.CosEventPCR, func(hs crypto.Hash, pcr int, digest []byte) error {
		tpm2Alg, err := tpm2.HashToAlgorithm(hs)
		if err != nil {
			return err
		}

		if err := tpm2.PCRExtend(tpm, tpmutil.Handle(pcr), tpm2Alg, digest, ""); err != nil {
			return fmt.Errorf("failed to extend event to PCR%d: %v", pcr, err)
		}
		return nil
	}); err != nil {
		t.Error(err)
	}
	buf = bytes.Buffer{}
	if err := coscel.EncodeCEL(&buf); err != nil {
		t.Fatal(err)
	}
	banks, err = client.ReadAllPCRs(tpm)
	if err != nil {
		t.Fatal(err)
	}
	for _, bank := range banks {
		pcrBank := convertToPCRBank(t, bank)
		_, err := ParseCosCELPCR(buf.Bytes(), pcrBank)
		if err == nil {
			t.Errorf("expected error when parsing event log with unknown content type")
		}
	}
}

func TestMatchWellKnownCert(t *testing.T) {
	tests := []struct {
		name    string
		cert    *gepb.Certificate
		want    attestpb.WellKnownCertificate
		wantErr bool
	}{
		{
			name: "Well-known certificate",
			cert: &gepb.Certificate{
				Representation: &gepb.Certificate_WellKnown{
					WellKnown: gepb.WellKnownCertificate_GCE_DEFAULT_PK,
				}},
			want:    attestpb.WellKnownCertificate_GCE_DEFAULT_PK,
			wantErr: false,
		},
		{
			name: "Unknown well-known certificate",
			cert: &gepb.Certificate{
				Representation: &gepb.Certificate_WellKnown{
					WellKnown: gepb.WellKnownCertificate_UNKNOWN,
				}},
			want:    attestpb.WellKnownCertificate_UNKNOWN,
			wantErr: true,
		},
		{
			name: "Certificate with DER",
			cert: &gepb.Certificate{
				Representation: &gepb.Certificate_Der{
					Der: []byte("some DER bytes"),
				}},
			want:    attestpb.WellKnownCertificate_UNKNOWN,
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := matchWellKnownCert(tt.cert)
			if (err != nil) != tt.wantErr {
				t.Errorf("matchWellKnownCert() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("matchWellKnownCert() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConvertToPbDatabase(t *testing.T) {
	tests := []struct {
		name   string
		gepbdb *gepb.Database
		want   *attestpb.Database
	}{
		{
			name:   "Empty database",
			gepbdb: &gepb.Database{},
			want: &attestpb.Database{
				Certs:  []*attestpb.Certificate{},
				Hashes: [][]byte{},
			},
		},
		{
			name: "Database with DER certificate",
			gepbdb: &gepb.Database{
				Certs: []*gepb.Certificate{
					{
						Representation: &gepb.Certificate_Der{
							Der: []byte("test_der_cert"),
						},
					},
				},
				Hashes: [][]byte{[]byte("test_hash")},
			},
			want: &attestpb.Database{
				Certs: []*attestpb.Certificate{
					{
						Representation: &attestpb.Certificate_Der{
							Der: []byte("test_der_cert"),
						},
					},
				},
				Hashes: [][]byte{[]byte("test_hash")},
			},
		},
		{
			name: "Database with well-known certificate",
			gepbdb: &gepb.Database{
				Certs: []*gepb.Certificate{
					{
						Representation: &gepb.Certificate_WellKnown{
							WellKnown: gepb.WellKnownCertificate_GCE_DEFAULT_PK,
						},
					},
				},
			},
			want: &attestpb.Database{
				Certs: []*attestpb.Certificate{
					{
						Representation: &attestpb.Certificate_WellKnown{
							WellKnown: attestpb.WellKnownCertificate_GCE_DEFAULT_PK,
						},
					},
				},
				Hashes: [][]byte{},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := convertToPbDatabase(tt.gepbdb)
			if diff := cmp.Diff(got, tt.want, protocmp.Transform()); diff != "" {
				t.Errorf("unexpected convertToPbDatabase diff: %s", diff)
			}
		})
	}
}

func TestConvertToMachineState(t *testing.T) {
	tests := []struct {
		name   string
		gepbdb *gepb.FirmwareLogState
		want   *attestpb.MachineState
	}{
		{
			name:   "Empty state",
			gepbdb: &gepb.FirmwareLogState{},
			want:   &attestpb.MachineState{},
		},
		{
			name: "Full state",
			gepbdb: &gepb.FirmwareLogState{
				Platform: &gepb.PlatformState{
					Technology: gepb.GCEConfidentialTechnology_AMD_SEV,
					Firmware:   &gepb.PlatformState_GceVersion{GceVersion: 1},
				},
				SecureBoot: &gepb.SecureBootState{
					Enabled: true,
					Db: &gepb.Database{
						Certs: []*gepb.Certificate{
							{Representation: &gepb.Certificate_Der{Der: []byte("db_cert_der")}},
						},
					},
					Kek: &gepb.Database{
						Certs: []*gepb.Certificate{
							{Representation: &gepb.Certificate_WellKnown{WellKnown: gepb.WellKnownCertificate_MS_THIRD_PARTY_KEK_CA_2011}},
						},
					},
				},
				RawEvents: []*gepb.Event{{
					PcrIndex: 0,
					Data:     []byte("12345678"),
					Digest:   []byte("aabbccdd"),
				}},
				Efi: &gepb.EfiState{
					Apps: []*gepb.EfiApp{
						{
							Digest: []byte("aabbccdd"),
						},
					},
				},
				Hash: gepb.HashAlgo_SHA256,
				Grub: &gepb.GrubState{
					Files: []*gepb.GrubFile{{Digest: []byte("aabbcc")}},
				},
				LinuxKernel: &gepb.LinuxKernelState{
					CommandLine: "abcdefge",
				},
				LogType: gepb.LogType_LOG_TYPE_TCG2, // this will be ignored by the conversion
			},
			want: &attestpb.MachineState{
				Platform: &attestpb.PlatformState{
					Technology: attestpb.GCEConfidentialTechnology_AMD_SEV,
					Firmware:   &attestpb.PlatformState_GceVersion{GceVersion: 1},
				},
				SecureBoot: &attestpb.SecureBootState{
					Enabled: true,
					Db: &attestpb.Database{
						Certs: []*attestpb.Certificate{
							{Representation: &attestpb.Certificate_Der{Der: []byte("db_cert_der")}},
						},
					},
					Kek: &attestpb.Database{
						Certs: []*attestpb.Certificate{
							{Representation: &attestpb.Certificate_WellKnown{WellKnown: attestpb.WellKnownCertificate_MS_THIRD_PARTY_KEK_CA_2011}},
						},
					},
				},
				RawEvents: []*attestpb.Event{{
					PcrIndex: 0,
					Data:     []byte("12345678"),
					Digest:   []byte("aabbccdd"),
				}},
				Efi: &attestpb.EfiState{
					Apps: []*attestpb.EfiApp{
						{
							Digest: []byte("aabbccdd"),
						},
					},
				},
				Hash: pb.HashAlgo_SHA256,
				Grub: &attestpb.GrubState{
					Files: []*attestpb.GrubFile{{Digest: []byte("aabbcc")}},
				},
				LinuxKernel: &attestpb.LinuxKernelState{
					CommandLine: "abcdefge",
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ConvertToMachineState(tt.gepbdb)
			if err != nil {
				t.Fatal(err)
			}
			if diff := cmp.Diff(got, tt.want, protocmp.Transform()); diff != "" {
				t.Errorf("unexpected converted machinestate diff: %s", diff)
			}

			// convert the result machine state back to a firmware state for safety check
			ms, err := ConvertToFirmwareState(got)
			if err != nil {
				t.Fatal(err)
			}
			// ignore the log_type as it is lost during the conversion
			if diff := cmp.Diff(ms, tt.gepbdb, protocmp.Transform(), protocmp.IgnoreFields(&gepb.FirmwareLogState{}, "log_type")); diff != "" {
				t.Errorf("unexpected inverse converted machinestate diff: %s", diff)
			}
		})
	}
}

func generateNonCosCelEvent() gecel.Content {
	r := randomTLV{gecel.TLV{Type: 123, Value: []byte("12345")}}
	return r
}

type randomTLV struct {
	tlv gecel.TLV
}

func (r randomTLV) GenerateDigest(ch crypto.Hash) ([]byte, error) {
	h := ch.New()
	h.Write(r.tlv.Value)
	return h.Sum(nil), nil
}

func (r randomTLV) TLV() (gecel.TLV, error) {
	return r.tlv, nil
}

func TestParseLinuxKernelState(t *testing.T) {
	logs := []struct {
		eventLog
		name            string
		expectedCmdline string
	}{
		{COS85AmdSev, "COS85AmdSev", test.Cos85AmdSevCmdline},
		{COS93AmdSev, "COS93AmdSev", test.Cos93AmdSevCmdline},
		{COS101AmdSev, "COS101AmdSev", test.Cos101AmdSevCmdline},
		{Ubuntu2404AmdSevSnp, "Ubuntu2404AmdSevSnp", test.Ubuntu2404AmdSevSnpCmdline},
	}
	for _, log := range logs {
		for _, bank := range log.Banks {
			hashName := pb.HashAlgo_name[int32(bank.Hash)]
			subtestName := fmt.Sprintf("%s-%s", log.name, hashName)
			t.Run(subtestName, func(t *testing.T) {
				msState, err := parsePCClientEventLog(log.RawLog, bank, VerifyOpts{Loader: GRUB})
				if err != nil {
					t.Errorf("failed to parse and replay log: %v", err)
				}

				if msState.LinuxKernel == nil || len(msState.LinuxKernel.CommandLine) == 0 {
					t.Errorf("expected %s to have a LinuxKernelState", log.name)
				}

				if msState.LinuxKernel.CommandLine != log.expectedCmdline {
					t.Errorf("kernel command line for log %s:\n'%s'\n did not match expected cmdline:\n'%s'",
						log.name,
						msState.LinuxKernel.CommandLine,
						log.expectedCmdline)
				}
			})
		}
	}
}

func TestParseGrubState(t *testing.T) {
	logs := []struct {
		eventLog
		name string
	}{
		{COS85AmdSev, "COS85AmdSev"},
		{COS93AmdSev, "COS93AmdSev"},
		{COS101AmdSev, "COS101AmdSev"},
		{Ubuntu2404AmdSevSnp, "Ubuntu2404AmdSevSnp"},
	}
	for _, log := range logs {
		for _, bank := range log.Banks {
			hashName := pb.HashAlgo_name[int32(bank.Hash)]
			subtestName := fmt.Sprintf("%s-%s", log.name, hashName)
			t.Run(subtestName, func(t *testing.T) {
				msState, err := parsePCClientEventLog(log.RawLog, bank, VerifyOpts{Loader: GRUB})
				if err != nil {
					t.Errorf("failed to parse and replay log: %v", err)
				}

				if len(msState.Grub.GetCommands()) == 0 {
					t.Errorf("expected COS85 to run GRUB commands!")
				}
				if strings.HasPrefix(subtestName, "COS") && len(msState.Grub.GetFiles()) != 2 {
					t.Errorf("expected COS85 to read two files (grub.cfg and kernel)!")
				}

				// check the absence of EV_EVENT_TAG in the GRUB files.
				for _, f := range msState.Grub.GetFiles() {
					if bytes.Equal(f.GetUntrustedFilename(), decodeHex(EventTagLoadedImageHex)) {
						t.Error("EV_EVENT_TAG should not be in the GRUB files")
					}
				}
			})
		}
	}
}

func TestParseGrubStateFail(t *testing.T) {
	// No GRUB measurements for this event log.
	eventlog := GlinuxNoSecureBootLaptop
	for _, bank := range eventlog.Banks {
		hashName := pb.HashAlgo_name[int32(bank.Hash)]
		subtestName := fmt.Sprintf("GlinuxNoSecureBootLaptop-%s", hashName)
		t.Run(subtestName, func(t *testing.T) {
			if _, err := parsePCClientEventLog(eventlog.RawLog, bank, VerifyOpts{Loader: GRUB}); err == nil {
				t.Errorf("expected error when parsing GRUB state")
			} else {
				if !strings.Contains(err.Error(), "no GRUB measurements found") {
					t.Errorf("expected error containing 'no GRUB measurements found'")
				}
			}
		})
	}
}

func TestParseEfiState(t *testing.T) {
	logs := []struct {
		eventLog
		name string
	}{
		{Rhel8GCE, "Rhel8GCE"},
		{UbuntuAmdSevGCE, "UbuntuAmdSevGCE"},
		{Ubuntu2104NoSecureBootGCE, "Ubuntu2104NoSecureBootGCE"},
		{COS85AmdSev, "COS85AmdSev"},
		{COS93AmdSev, "COS93AmdSev"},
		{COS101AmdSev, "COS101AmdSev"},
	}
	for _, log := range logs {
		for _, bank := range log.Banks {
			hashName := pb.HashAlgo_name[int32(bank.Hash)]
			subtestName := fmt.Sprintf("%s-%s", log.name, hashName)
			t.Run(subtestName, func(t *testing.T) {
				msState, err := parsePCClientEventLog(log.RawLog, bank, VerifyOpts{Loader: UnsupportedLoader})
				if err != nil {
					t.Errorf("parsePCClientEventLog(%v, %v) got err = %v, want nil", log.name, bank.GetHash().String(), err)
				}

				if msState.GetEfi() == nil {
					t.Error("msState.GetEfi() returned nil, want EFI state")
				}
				efiApps := msState.GetEfi().GetApps()
				if len(efiApps) == 0 {
					t.Error("msState.GetEfi().GetApps() returned empty, want non-zero length")
				}
				expectedDigestStrs := log.ExpectedEFIAppDigests[bank.Hash]
				if len(expectedDigestStrs) == 0 {
					t.Fatalf("%v log used to test EFIState, but it has no expected EFI App digests", log.name)
				}
				expectedDigests := make([][]byte, 0, len(expectedDigestStrs))
				for _, digestStr := range log.ExpectedEFIAppDigests[bank.Hash] {
					expectedDigests = append(expectedDigests, decodeHex(digestStr))
				}
				gotDigests := make([][]byte, 0, len(efiApps))
				for _, app := range efiApps {
					gotDigests = append(gotDigests, app.GetDigest())
				}
				if !cmp.Equal(gotDigests, expectedDigests) {
					t.Errorf("msState.GetEfi().GetApps() digests got %v, want %v", gotDigests, expectedDigests)
				}
			})
		}
	}
}

func TestParseEventLogCallingEFIAppError(t *testing.T) {
	tests := []struct {
		eventLog
		name string
	}{
		{ArchLinuxWorkstation, "ArchLinuxWorkstation"},
		{GdcHost, "GdcHost"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, bank := range test.Banks {
				if _, err := parsePCClientEventLog(test.RawLog, bank, VerifyOpts{AllowEFIAppBeforeCallingEvent: false}); err == nil || !strings.Contains(err.Error(), "before CallingEFIApp event") {
					t.Errorf("parsePCClientEventLog(%s): expected Calling EFI App error, received %v", test.name, err)
				}
			}
		})
	}
}

func decodeHex(hexStr string) []byte {
	bytes, err := hex.DecodeString(hexStr)
	if err != nil {
		panic(err)
	}
	return bytes
}
