// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !fips140v1.0

package cipher_test

import (
	"crypto/cipher"
	"crypto/internal/cryptotest"
	"crypto/internal/fips140"
	fipsaes "crypto/internal/fips140/aes"
	"crypto/internal/fips140/aes/gcm"
	"encoding/binary"
	"internal/testenv"
	"math"
	"testing"
)

func TestGCMNoncesFIPSV2(t *testing.T) {
	cryptotest.MustSupportFIPS140(t)
	if !fips140.Enabled {
		cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestGCMNoncesFIPSV2$", "-test.v")
		cmd.Env = append(cmd.Environ(), "GODEBUG=fips140=on")
		out, err := cmd.CombinedOutput()
		t.Logf("running with GODEBUG=fips140=on:\n%s", out)
		if err != nil {
			t.Errorf("fips140=on subprocess failed: %v", err)
		}
		return
	}

	tryNonce := func(aead cipher.AEAD, nonce []byte) bool {
		fips140.ResetServiceIndicator()
		aead.Seal(nil, nonce, []byte("x"), nil)
		return fips140.ServiceIndicator()
	}
	expectOK := func(t *testing.T, aead cipher.AEAD, nonce []byte) {
		t.Helper()
		if !tryNonce(aead, nonce) {
			t.Errorf("expected service indicator true for %x", nonce)
		}
	}
	expectPanic := func(t *testing.T, aead cipher.AEAD, nonce []byte) {
		t.Helper()
		defer func() {
			t.Helper()
			if recover() == nil {
				t.Errorf("expected panic for %x", nonce)
			}
		}()
		tryNonce(aead, nonce)
	}

	t.Run("NewGCMWithXORCounterNonce", func(t *testing.T) {
		newGCM := func() *gcm.GCMWithXORCounterNonce {
			key := make([]byte, 16)
			block, _ := fipsaes.New(key)
			aead, _ := gcm.NewGCMWithXORCounterNonce(block)
			return aead
		}
		nonce := func(mask []byte, counter uint64) []byte {
			nonce := make([]byte, 12)
			copy(nonce, mask)
			n := binary.BigEndian.AppendUint64(nil, counter)
			for i, b := range n {
				nonce[4+i] ^= b
			}
			return nonce
		}

		for _, mask := range [][]byte{
			decodeHex(t, "ffffffffffffffffffffffff"),
			decodeHex(t, "aabbccddeeff001122334455"),
			decodeHex(t, "000000000000000000000000"),
		} {
			g := newGCM()
			// Mask is derived from first invocation with zero nonce.
			expectOK(t, g, nonce(mask, 0))
			expectOK(t, g, nonce(mask, 1))
			expectOK(t, g, nonce(mask, 100))
			expectPanic(t, g, nonce(mask, 100))
			expectPanic(t, g, nonce(mask, 99))
			expectOK(t, g, nonce(mask, math.MaxUint64-2))
			expectOK(t, g, nonce(mask, math.MaxUint64-1))
			expectPanic(t, g, nonce(mask, math.MaxUint64))
			expectPanic(t, g, nonce(mask, 0))

			g = newGCM()
			g.SetNoncePrefixAndMask(mask)
			expectOK(t, g, nonce(mask, 0xFFFFFFFF))
			expectOK(t, g, nonce(mask, math.MaxUint64-2))
			expectOK(t, g, nonce(mask, math.MaxUint64-1))
			expectPanic(t, g, nonce(mask, math.MaxUint64))
			expectPanic(t, g, nonce(mask, 0))

			g = newGCM()
			g.SetNoncePrefixAndMask(mask)
			expectOK(t, g, nonce(mask, math.MaxUint64-1))
			expectPanic(t, g, nonce(mask, math.MaxUint64))
			expectPanic(t, g, nonce(mask, 0))
		}
	})
}
