Livt packages

Livt.Net

Version 1.4.1

Ethernet, IPv4, TCP, UDP, ARP, TFTP, and Telnet networking components for Livt hardware.

Published files (163)

tests/tftp/TftpWriteServerTest.lvt

namespace Livt.Net.Tests

using Livt.Net

/**
 * Independent wire vectors exercise loss, duplicates, completion and peer isolation.
 */
@Test
component TftpWriteServerTest
{
	// Wire request: WRQ, filename "scene", octet mode and terminating NULs.
	const WRQ: byte[14] = [0, 2, 115, 99, 101, 110, 101, 0, 111, 99, 116, 101, 116, 0]

	// Owned input, sink and upload engine with short manually supplied timer intervals.
	upload: TftpUploadBuffer
	input: ArrayPacketData<516>
	sink: TftpTestSink
	server: TftpWriteServer<ArrayPacketData<516>, TftpTestSink, 1069, 10, 2>

	/**
	 * Binds the protocol engine to deterministic datagram and storage doubles.
	 */
	new()
	{
		upload = new TftpUploadBuffer()
		input = new ArrayPacketData<516>()
		sink = new TftpTestSink(upload)
		server = new TftpWriteServer<ArrayPacketData<516>, TftpTestSink, 1069, 10, 2>(input, sink, upload)
	}

	/**
	 * Abandons any previous protocol session and resets observations.
	 */
	fn Reset()
	{
		server.Cancel()
		sink.Reset()
		ClearInput()
	}

	/**
	 * Releases and clears the packet before writing its next prefix.
	 */
	fn ClearInput()
	{
		if (input.IsReady()) {
			input.TryRelease()
		} else {
			input.TryClear()
		}
	}

	/**
	 * Supplies an independent complete WRQ vector.
	 */
	fn Request()
	{
		ClearInput()

		for (var index = 0; index < 14; index++) {
			assert input.TryWrite(index, WRQ[index]) == PacketDataResult.Success
		}

		input.TryPublish()
		assert server.Receive(0x0A000065, 40000, 69, 0)
	}

	/**
	 * Publishes a DATA block with a deterministic payload pattern.
	 */
	fn Data(block: int, length: int)
	{
		ClearInput()
		input.TryWrite(0, 0)
		input.TryWrite(1, 3)
		input.TryWrite(2, (block >> 8) as byte)
		input.TryWrite(3, block as byte)

		for (var index = 0; index < length; index++) {
			input.TryWrite(index + 4, 0xA5)
		}

		input.TryPublish()
	}

	/**
	 * Checks ACK/ERROR opcode and low code byte from the stable response.
	 */
	fn Expect(opcode: byte, code: byte)
	{
		var value: byte = 0
		assert server.TryRead(1, value) == PacketDataResult.Success
		assert value == opcode
		assert server.TryRead(3, value) == PacketDataResult.Success
		assert value == code
	}

	/**
	 * Retries WRQ and final DATA without repeating storage mutations.
	 */
	@Test
	fn CommitsShortUploadExactlyOnce()
	{
		Reset()
		Request()
		Expect(4, 0)
		server.Release()
		Request()
		assert sink.begins == 1
		server.Release()
		Data(1, 3)
		assert server.Receive(0x0A000065, 40000, 1069, 1)
		Expect(4, 1)
		assert sink.bytes == 3 && sink.firstByte == 0xA5
		assert sink.commits == 1
		server.Release()
		assert server.Receive(0x0A000065, 40000, 1069, 2)
		Expect(4, 1)
		assert sink.writes == 1 && sink.commits == 1
	}

	/**
	 * An exact multiple of 512 bytes requires a final empty block.
	 */
	@Test
	fn RequiresEmptyTerminatorAfterFullBlock()
	{
		Reset()
		Request()
		server.Release()
		Data(1, 512)
		assert server.Receive(0x0A000065, 40000, 1069, 1)
		assert sink.commits == 0 && sink.bytes == 512
		server.Release()
		assert server.Receive(0x0A000065, 40000, 1069, 2)
		assert sink.writes == 8
		server.Release()
		Data(2, 0)
		assert server.Receive(0x0A000065, 40000, 1069, 3)
		Expect(4, 2)
		assert sink.commits == 1 && sink.bytes == 512
	}

	/**
	 * Foreign transfer identifiers cannot write or cancel the active upload.
	 */
	@Test
	fn RejectsForeignPeerAndFutureBlock()
	{
		Reset()
		Request()
		server.Release()
		Data(1, 3)
		assert server.Receive(0x0A000066, 40000, 1069, 1)
		Expect(5, 5)
		assert sink.writes == 0
		server.Release()
		Data(2, 3)
		assert server.Receive(0x0A000065, 40000, 1069, 2)
		assert !server.IsReady() && sink.writes == 0
	}

	/**
	 * Retry exhaustion aborts an unfinished transaction and permits another upload.
	 */
	@Test
	fn AbortsAfterRetryBudget()
	{
		Reset()
		Request()
		server.Release()
		server.Poll(10)
		Expect(4, 0)
		server.Release()
		server.Poll(20)
		server.Release()
		server.Poll(30)
		assert sink.aborts == 1 && !server.IsReady()
		Request()
		assert sink.begins == 2
	}

	/**
	 * Failed validation produces ERROR rather than acknowledging completion.
	 */
	@Test
	fn DoesNotAcknowledgeFailedCommit()
	{
		Reset()
		Request()
		server.Release()
		sink.rejectCommit = true
		Data(1, 1)
		assert server.Receive(0x0A000065, 40000, 1069, 1)
		Expect(5, 3)
		assert sink.commits == 0 && sink.aborts == 1
	}

	/**
	 * Illegal packets from the active peer terminate its incomplete transaction.
	 */
	@Test
	fn AbortsMalformedActiveTransfer()
	{
		Reset()
		Request()
		server.Release()
		ClearInput()
		input.TryWrite(0, 0)
		input.TryWrite(1, 3)
		input.TryPublish()
		assert server.Receive(0x0A000065, 40000, 1069, 1)
		Expect(5, 4)
		assert sink.aborts == 1
	}

	/**
	 * Millisecond rollover preserves retry deadlines and response ownership.
	 */
	@Test
	fn HandlesTimerRolloverAndPinnedResponse()
	{
		Reset()
		Request()
		server.Release()
		Data(1, 512)
		assert server.Receive(0x0A000065, 40000, 1069, 4294967290)
		server.Poll(4)
		Expect(4, 1)
		assert !server.Receive(0x0A000065, 40000, 1069, 4)
		server.Release()
		server.Poll(4)
		Expect(4, 1)
		assert sink.writes == 8
	}

	/**
	 * Preserves all byte positions while streaming one wire block through smaller sink chunks.
	 */
	@Test
	fn StreamsOrderedBytesThroughBoundedChunks()
	{
		Reset()
		Request()
		server.Release()
		ClearInput()
		input.TryWrite(0, 0)
		input.TryWrite(1, 3)
		input.TryWrite(2, 0)
		input.TryWrite(3, 1)

		for (var index = 0; index < 512; index++) {
			input.TryWrite(index + 4, index as byte)
		}

		input.TryPublish()
		assert server.Receive(0x0A000065, 40000, 1069, 1)
		Expect(4, 1)
		assert sink.bytes == 512 && sink.writes == 8
		assert sink.largestChunk == 64 && sink.checksum == 65280
	}


	/**
	 * A competing filename cannot mutate the active request or cause a second Begin.
	 */
	@Test
	fn RetainsFilenameAcrossRejectedRequest()
	{
		Reset()
		Request()
		server.Release()
		ClearInput()

		for (var index = 0; index < 14; index++) {
			input.TryWrite(index, WRQ[index])
		}

		input.TryWrite(2, 120)
		input.TryPublish()
		assert server.Receive(0x0A000065, 40000, 69, 1)
		Expect(5, 0)
		assert upload.GetNameByte(0) == 115
		server.Release()
		ClearInput()
		var longer: byte[15] = [0, 2, 108, 111, 110, 103, 101, 114, 0, 111, 99, 116, 101, 116, 0]

		for (var index = 0; index < 15; index++) {
			input.TryWrite(index, longer[index])
		}

		input.TryPublish()
		assert server.Receive(0x0A000065, 40000, 69, 2)
		Expect(5, 0)
		assert upload.GetNameByte(0) == 115
		server.Release()
		Request()
		Expect(4, 0)
		assert sink.begins == 1
	}

	/**
	 * A later rejected chunk aborts the block without ACK or commit.
	 */
	@Test
	fn RejectsPartiallyAcceptedBlock()
	{
		Reset()
		Request()
		server.Release()
		sink.rejectWriteAt = 2
		Data(1, 512)
		assert server.Receive(0x0A000065, 40000, 1069, 1)
		Expect(5, 3)
		assert sink.writes == 2 && sink.bytes == 64
		assert sink.aborts == 1 && sink.commits == 0
	}
}