// Copyright 2023 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.

package quic

import (
	"testing"
	"testing/synctest"
)

func TestKeyUpdatePeerUpdates(t *testing.T) {
	synctest.Test(t, testKeyUpdatePeerUpdates)
}
func testKeyUpdatePeerUpdates(t *testing.T) {
	tc := newTestConn(t, serverSide)
	tc.handshake()
	tc.ignoreFrames = nil // ignore nothing

	// Peer initiates a key update.
	tc.sendKeyNumber = 1
	tc.sendKeyPhaseBit = true
	tc.writeFrames(packetType1RTT, debugFramePing{})

	// We update to the new key.
	tc.advanceToTimer()
	tc.wantFrameType("conn ACKs last packet",
		packetType1RTT, debugFrameAck{})
	tc.wantFrame("first packet after a key update is always ack-eliciting",
		packetType1RTT, debugFramePing{})
	if got, want := tc.lastPacket.keyNumber, 1; got != want {
		t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want)
	}
	if !tc.lastPacket.keyPhaseBit {
		t.Errorf("after key rotation, conn failed to change Key Phase bit")
	}
	tc.wantIdle("conn has nothing to send")

	// Peer's ACK of a packet we sent in the new phase completes the update.
	tc.writeAckForAll()

	// Peer initiates a second key update.
	tc.sendKeyNumber = 2
	tc.sendKeyPhaseBit = false
	tc.writeFrames(packetType1RTT, debugFramePing{})

	// We update to the new key.
	tc.advanceToTimer()
	tc.wantFrameType("conn ACKs last packet",
		packetType1RTT, debugFrameAck{})
	tc.wantFrame("first packet after a key update is always ack-eliciting",
		packetType1RTT, debugFramePing{})
	if got, want := tc.lastPacket.keyNumber, 2; got != want {
		t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want)
	}
	if tc.lastPacket.keyPhaseBit {
		t.Errorf("after second key rotation, conn failed to change Key Phase bit")
	}
	tc.wantIdle("conn has nothing to send")
}

func TestKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) {
	synctest.Test(t, testKeyUpdateAcceptPreviousPhaseKeys)
}
func testKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) {
	// "An endpoint SHOULD retain old keys for some time after
	// unprotecting a packet sent using the new keys."
	// https://www.rfc-editor.org/rfc/rfc9001#section-6.1-8
	tc := newTestConn(t, serverSide)
	tc.handshake()
	tc.ignoreFrames = nil // ignore nothing

	// Peer initiates a key update, skipping one packet number.
	pnum0 := tc.peerNextPacketNum[appDataSpace]
	tc.peerNextPacketNum[appDataSpace]++
	tc.sendKeyNumber = 1
	tc.sendKeyPhaseBit = true
	tc.writeFrames(packetType1RTT, debugFramePing{})

	// We update to the new key.
	// This ACK is not delayed, because we've skipped a packet number.
	tc.wantFrame("conn ACKs last packet",
		packetType1RTT, debugFrameAck{
			ranges: []i64range[packetNumber]{
				{0, pnum0},
				{pnum0 + 1, pnum0 + 2},
			},
		})
	tc.wantFrame("first packet after a key update is always ack-eliciting",
		packetType1RTT, debugFramePing{})
	if got, want := tc.lastPacket.keyNumber, 1; got != want {
		t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want)
	}
	if !tc.lastPacket.keyPhaseBit {
		t.Errorf("after key rotation, conn failed to change Key Phase bit")
	}
	tc.wantIdle("conn has nothing to send")

	// We receive the previously-skipped packet in the earlier key phase.
	tc.peerNextPacketNum[appDataSpace] = pnum0
	tc.sendKeyNumber = 0
	tc.sendKeyPhaseBit = false
	tc.writeFrames(packetType1RTT, debugFramePing{})

	// We ack the reordered packet immediately, still in the new key phase.
	tc.wantFrame("conn ACKs reordered packet",
		packetType1RTT, debugFrameAck{
			ranges: []i64range[packetNumber]{
				{0, pnum0 + 2},
			},
		})
	tc.wantIdle("packet is not ack-eliciting")
	if got, want := tc.lastPacket.keyNumber, 1; got != want {
		t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want)
	}
	if !tc.lastPacket.keyPhaseBit {
		t.Errorf("after key rotation, conn failed to change Key Phase bit")
	}
}

func TestKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
	synctest.Test(t, testKeyUpdateRejectPacketFromPriorPhase)
}
func testKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
	// "Packets with higher packet numbers MUST be protected with either
	// the same or newer packet protection keys than packets with lower packet numbers."
	// https://www.rfc-editor.org/rfc/rfc9001#section-6.4-2
	tc := newTestConn(t, serverSide)
	tc.handshake()
	tc.ignoreFrames = nil // ignore nothing

	// Peer initiates a key update.
	tc.sendKeyNumber = 1
	tc.sendKeyPhaseBit = true
	tc.writeFrames(packetType1RTT, debugFramePing{})

	// We update to the new key.
	tc.advanceToTimer()
	tc.wantFrameType("conn ACKs last packet",
		packetType1RTT, debugFrameAck{})
	tc.wantFrame("first packet after a key update is always ack-eliciting",
		packetType1RTT, debugFramePing{})
	if got, want := tc.lastPacket.keyNumber, 1; got != want {
		t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want)
	}
	if !tc.lastPacket.keyPhaseBit {
		t.Errorf("after key rotation, conn failed to change Key Phase bit")
	}
	tc.wantIdle("conn has nothing to send")

	// Peer sends an ack-eliciting packet using the prior phase keys.
	// We fail to unprotect the packet and ignore it.
	skipped := tc.peerNextPacketNum[appDataSpace]
	tc.sendKeyNumber = 0
	tc.sendKeyPhaseBit = false
	tc.writeFrames(packetType1RTT, debugFramePing{})

	// Peer sends an ack-eliciting packet using the current phase keys.
	tc.sendKeyNumber = 1
	tc.sendKeyPhaseBit = true
	tc.writeFrames(packetType1RTT, debugFramePing{})

	// We ack the peer's packets, not including the one sent with the wrong keys.
	tc.wantFrame("conn ACKs packets, not including packet sent with wrong keys",
		packetType1RTT, debugFrameAck{
			ranges: []i64range[packetNumber]{
				{0, skipped},
				{skipped + 1, skipped + 2},
			},
		})
}

func TestKeyUpdateLocallyInitiated(t *testing.T) {
	synctest.Test(t, testKeyUpdateLocallyInitiated)
}
func testKeyUpdateLocallyInitiated(t *testing.T) {
	const updateAfter = 4 // initiate key update after 1-RTT packet 4
	tc := newTestConn(t, serverSide)
	tc.conn.keysAppData.updateAfter = updateAfter
	tc.handshake()

	for {
		tc.writeFrames(packetType1RTT, debugFramePing{})
		tc.advanceToTimer()
		tc.wantFrameType("conn ACKs last packet",
			packetType1RTT, debugFrameAck{})
		if tc.lastPacket.num > updateAfter {
			break
		}
		if got, want := tc.lastPacket.keyNumber, 0; got != want {
			t.Errorf("before key update, conn sent packet with key %v, want %v", got, want)
		}
		if tc.lastPacket.keyPhaseBit {
			t.Errorf("before key update, keyPhaseBit is set, want unset")
		}
	}
	if got, want := tc.lastPacket.keyNumber, 1; got != want {
		t.Errorf("after key update, conn sent packet with key %v, want %v", got, want)
	}
	if !tc.lastPacket.keyPhaseBit {
		t.Errorf("after key update, keyPhaseBit is unset, want set")
	}
	tc.wantFrame("first packet after a key update is always ack-eliciting",
		packetType1RTT, debugFramePing{})
	tc.wantIdle("no more frames")

	// Peer sends another packet using the prior phase keys.
	tc.writeFrames(packetType1RTT, debugFramePing{})
	tc.advanceToTimer()
	tc.wantFrameType("conn ACKs packet in prior phase",
		packetType1RTT, debugFrameAck{})
	tc.wantIdle("packet is not ack-eliciting")
	if got, want := tc.lastPacket.keyNumber, 1; got != want {
		t.Errorf("after key update, conn sent packet with key %v, want %v", got, want)
	}

	// Peer updates to the next phase.
	tc.sendKeyNumber = 1
	tc.sendKeyPhaseBit = true
	tc.writeAckForAll()
	tc.writeFrames(packetType1RTT, debugFramePing{})
	tc.advanceToTimer()
	tc.wantFrameType("conn ACKs packet in current phase",
		packetType1RTT, debugFrameAck{})
	tc.wantIdle("packet is not ack-eliciting")
	if got, want := tc.lastPacket.keyNumber, 1; got != want {
		t.Errorf("after key update, conn sent packet with key %v, want %v", got, want)
	}

	// Peer initiates its own update.
	tc.sendKeyNumber = 2
	tc.sendKeyPhaseBit = false
	tc.writeFrames(packetType1RTT, debugFramePing{})
	tc.advanceToTimer()
	tc.wantFrameType("conn ACKs packet in current phase",
		packetType1RTT, debugFrameAck{})
	tc.wantFrame("first packet after a key update is always ack-eliciting",
		packetType1RTT, debugFramePing{})
	if got, want := tc.lastPacket.keyNumber, 2; got != want {
		t.Errorf("after peer key update, conn sent packet with key %v, want %v", got, want)
	}
	if tc.lastPacket.keyPhaseBit {
		t.Errorf("after peer key update, keyPhaseBit is unset, want set")
	}
}
