Livt packages
Livt.Net
Version 1.4.1
Ethernet, IPv4, TCP, UDP, ARP, TFTP, and Telnet networking components for Livt hardware.
Published files (163)
src/tcp/TcpStreamServer.lvt
namespace Livt.Net
using Livt.IO
enum TcpStreamState
{
Listening
SynReceived
Established
CloseWait
LastAck
}
/**
* Bounded passive IPv4 TCP streams with one outstanding segment per connection. Two owned
* RAMs contain disjoint per-connection regions; parsing, checksum and transmission are shared. All
* scheduled methods have ONE serialized owner. TryHandle borrows the current RX frame only for
* that call. Poll publishes a response; no other operation may mutate the connection until
* ReleaseFrame ends the transmitter's borrow. Millisecond time is supplied by the owner; reset
* abandons the connection. An idle stream remains connected; only outstanding transmissions
* have a retry limit. The application explicitly selects the listening PORT and optional SESSIONS capacity.
* RX_STORAGE and TX_STORAGE independently select owned scheduled byte stores, defaulting
* to block RAM. Each needs a parameterless constructor and at least its per-session
* capacity times SESSIONS addresses. They share this context and have no fixed-cycle
* latency contract. Metadata protects unwritten bytes; no payload reset value is required.
*/
component TcpStreamServer<S: IPacketData, PORT: int, RX_CAPACITY: int = 512, TX_CAPACITY: int = 256, SESSIONS: int = 1,
RX_STORAGE: IRam<byte> & new() = BlockRam<byte, (RX_CAPACITY * SESSIONS)>,
TX_STORAGE: IRam<byte> & new() = BlockRam<byte, (TX_CAPACITY * SESSIONS)>> : IPacketData
{
static assert SESSIONS > 0 && SESSIONS <= 16, "TCP sessions must be 1..16"
static assert PORT > 0 && PORT <= 65535, "TCP port required"
static assert RX_CAPACITY > 0 && RX_CAPACITY <= 32768, "TCP receive capacity must be 1..32768"
// Ethernet protocol identifier for IPv4.
const ETHER_TYPE_IPV4: int = 0x0800
// IPv4 payload protocol identifier for TCP.
const IP_PROTOCOL_TCP: byte = 6
// IPv4 TCP default peer MSS when SYN omits the option.
const DEFAULT_MSS: int = 536
// Fixed TCP header length before SYN options.
const TCP_HEADER_LENGTH: int = 20
// Advertised-window offset relative to the TCP header.
const TCP_WINDOW_OFFSET: int = 14
// SYN admission mask and TCP flags used for receive classification and replies.
const SYN_REQUIRED_MASK: byte = 0x17
const FLAG_SYN: byte = 0x02
const FLAG_RST: byte = 0x04
const FLAG_ACK: byte = 0x10
const FLAG_FIN: byte = 0x01
const FLAGS_SYN_ACK: byte = 0x12
const FLAGS_DATA_ACK: byte = 0x18
const FLAGS_FIN_ACK: byte = 0x11
// Initial zero-window probe interval.
const INITIAL_PROBE_MS: uint = 1000
// Maximum zero-window probe interval.
const MAX_PROBE_MS: uint = 60000
// Minimum first-data timeout after a retransmitted handshake.
const LOST_HANDSHAKE_TIMEOUT_MS: uint = 3000
// Maximum initial output coalescing interval.
const COALESCE_MS: uint = 10
// Maximum retransmissions before abandoning the connection.
const MAX_RETRIES: logic[4] = 8
// Width of a receive/ring address; supports the configured capacity.
const INDEX_BITS: int = Bits.RequiredFor(RX_CAPACITY - 1)
// Width of the retained or received byte count, including full capacity.
const COUNT_BITS: int = Bits.RequiredFor(RX_CAPACITY)
// Borrowed shared parsers; their source is acquired only during TryHandle.
ethernet: EthernetFrameParser<S>
ipv4: Ipv4PacketParser<EthernetFrameParser<S>>
// Owned TCP parser, checksum engine, payload RAMs and composed response views.
tcp: TcpHeaderParser<Ipv4PacketParser<EthernetFrameParser<S>>>
checksum: InternetChecksum
rx: RX_STORAGE
tx: TcpSendBuffer<TX_CAPACITY, SESSIONS, TX_STORAGE>
segment: TcpSegment<TcpSendBuffer<TX_CAPACITY, SESSIONS, TX_STORAGE>>
packet: TcpIpv4Packet<TcpSendBuffer<TX_CAPACITY, SESSIONS, TX_STORAGE>>
frame: TcpIpv4Frame<TcpSendBuffer<TX_CAPACITY, SESSIONS, TX_STORAGE>>
timer: TcpRetryTimer<SESSIONS>
// Local address bindings; callers keep them stable for the server lifetime.
localMac: byte[6]
localIp: byte[4]
// Current peer identity and advertised transmit bounds.
peerMac: byte[SESSIONS, 6]
peerIp: byte[SESSIONS, 4]
peerPort: logic[SESSIONS, 16]
peerWindow: logic[SESSIONS, 16]
peerMss: logic[SESSIONS, 16]
// Connection state and owned RX ring positions.
phase: TcpStreamState[SESSIONS]
rxRead: logic[SESSIONS, INDEX_BITS]
rxWrite: logic[SESSIONS, INDEX_BITS]
rxCount: logic[SESSIONS, COUNT_BITS]
// Wrapping TCP receive and transmit sequence space.
receiveNext: uint[SESSIONS]
sendNext: uint[SESSIONS]
sendUna: uint[SESSIONS]
// Retained segment range, flags and transmission ownership.
flightLength: logic[SESSIONS, 16]
consumed: logic[SESSIONS, 16]
flightOffset: logic[SESSIONS, 16]
flightFlags: byte[SESSIONS]
inFlight: bool[SESSIONS]
ackPending: bool[SESSIONS]
published: bool
closing: bool[SESSIONS]
// Current API selection and fair next-transmission candidate; publication pins selection.
session: int
nextPoll: int
// Wrapping millisecond timestamps and bounded retry metadata.
queuedAt: uint[SESSIONS]
probeAt: uint[SESSIONS]
probeDelay: uint[SESSIONS]
retries: logic[SESSIONS, 4]
generation: uint[SESSIONS]
/**
* Borrows the shared Ethernet/IPv4 parsers and retains stable local addresses; owns TCP parsing,
* checksum state, RX/TX RAMs and response views. All dependencies share the inherited
* clock/reset context; construction starts listening.
*/
new(ethernet: EthernetFrameParser<S>, ipv4: Ipv4PacketParser<EthernetFrameParser<S>>, localMac: byte[6], localIp: byte[4])
{
this.ethernet = ethernet
this.ipv4 = ipv4
this.localMac = localMac
this.localIp = localIp
this.tcp = new TcpHeaderParser<Ipv4PacketParser<EthernetFrameParser<S>>>(ipv4)
this.checksum = new InternetChecksum()
this.rx = new RX_STORAGE()
this.tx = new TcpSendBuffer<TX_CAPACITY, SESSIONS, TX_STORAGE>()
this.segment = new TcpSegment<TcpSendBuffer<TX_CAPACITY, SESSIONS, TX_STORAGE>>(this.tx)
this.packet = new TcpIpv4Packet<TcpSendBuffer<TX_CAPACITY, SESSIONS, TX_STORAGE>>(this.segment)
this.frame = new TcpIpv4Frame<TcpSendBuffer<TX_CAPACITY, SESSIONS, TX_STORAGE>>(this.packet)
this.timer = new TcpRetryTimer<SESSIONS>()
}
/**
* Selects the connection used by byte operations, state queries, Close and Abort. Invalid
* indices or an outstanding frame return false without mutation. TryHandle and Poll may
* change this selection; select again before application operations.
*/
public fn SelectSession(index: int) bool
{
if (this.published || index < 0 || index >= SESSIONS) {
return false
}
// A single session has no runtime slot selection hardware.
static if (SESSIONS == 1) {
this.session = 0
} else {
this.session = index
}
this.tx.SelectSession(this.session)
this.timer.SelectSession(this.session)
return true
}
/**
* Returns the selected connection index, also identifying the currently published frame.
*/
public fn GetSession() int
{
return this.session
}
/**
* Abandons every connection without transmitting or clearing payload RAM. Requires no
* published frame; per-slot generations are retained and selection ends at the last slot.
*/
public fn AbortAll()
{
for (var i = 0; i < SESSIONS; i++) {
this.SelectSession(i)
this.Abort()
}
}
/**
* Returns the current connection state as a snapshot.
*/
public fn GetState() TcpStreamState
{
return this.phase[this.session]
}
/**
* Returns a wrapping connection generation incremented for each accepted SYN. Abort preserves
* it; coordinated reset returns it to zero.
*/
public fn GetGeneration() uint
{
return this.generation[this.session]
}
/**
* Reports an established stream, including the peer-closed state while responses drain.
*/
public fn IsConnected() bool
{
return this.phase[this.session] == TcpStreamState.Established || this.phase[this.session] == TcpStreamState.CloseWait
}
/**
* Reports receipt of peer FIN while application output may still drain.
*/
public fn HasPeerClosed() bool
{
return this.phase[this.session] == TcpStreamState.CloseWait
}
/**
* Reports retained output or an outstanding segment, including handshake/close traffic. A false
* result is a snapshot, not a capacity reservation.
*/
public fn HasPendingOutput() bool
{
return this.inFlight[this.session] || this.tx.GetLength() != 0
}
/**
* Requests FIN after queued output drains. Requires peer-initiated close and no published frame;
* does not actively close an established peer.
*/
public fn Close()
{
this.closing[this.session] = true
}
/**
* Abandons the connection and buffered data without sending RST or clearing payload RAM.
* Requires no published frame; preserves the connection generation.
*/
public fn Abort()
{
this.phase[this.session] = TcpStreamState.Listening
this.rxRead[this.session] = 0
this.rxWrite[this.session] = 0
this.rxCount[this.session] = 0
this.inFlight[this.session] = false
this.ackPending[this.session] = false
this.closing[this.session] = false
this.consumed[this.session] = 0
this.tx.Clear()
this.timer.Reset()
}
/**
* Consumes one ordered receive byte through scheduled RAM access and requests a window update.
* Returns false and assigns zero if empty or a frame is published.
*/
public fn TryReceive(value: out byte) bool
{
value = 0
if (this.published || this.rxCount[this.session] == 0) {
return false
}
value = this.rx.Read(this.session * RX_CAPACITY + ((this.rxRead[this.session] as uint) as int))
if (((this.rxRead[this.session] as uint) as int) == RX_CAPACITY - 1) {
this.rxRead[this.session] = 0
} else {
this.rxRead[this.session] = ((this.rxRead[this.session] as uint) + (1 as uint)) as logic[INDEX_BITS]
}
this.rxCount[this.session] = ((this.rxCount[this.session] as uint) - (1 as uint)) as logic[COUNT_BITS]
// Window updates are coalesced by the owner's Poll call.
this.ackPending[this.session] = true
return true
}
/**
* Accepts one byte into retained output RAM at wrapping millisecond time now. Failure requires
* retry; success is local acceptance, not delivery or peer acknowledgment. No mutation occurs
* while a frame is published.
*/
public fn TryTransmit(value: byte, now: uint) bool
{
if (this.published || !this.IsConnected() || this.inFlight[this.session] || this.consumed[this.session] != 0 || this.closing[this.session]) {
return false
}
var count = this.tx.GetLength()
if (count >= ((this.peerMss[this.session] as uint) as int)) {
return false
}
if (!this.tx.TryAppend(value)) {
return false
}
if (count == 0) {
this.queuedAt[this.session] = now
}
return true
}
/**
* Closes TCP, IPv4 and Ethernet parser views in borrow order before returning the acquired RX
* frame to its owner.
*/
fn EndParse()
{
this.tcp.Invalidate()
this.ipv4.Invalidate()
this.ethernet.Invalidate()
}
/**
* Examines the currently acquired RX frame at wrapping millisecond time now. Returns true for
* claimed local-port TCP traffic, including rejected packets; false permits other services to
* inspect it. Requires no published response. Shared parsers are invalidated before return; the
* source frame remains caller-owned.
*/
public fn TryHandle(now: uint) bool
{
if (this.published) {
return true
}
this.ethernet.TryParse()
if (!this.ethernet.IsReady() || this.ethernet.GetEtherType() != ETHER_TYPE_IPV4) {
this.EndParse()
return false
}
this.ipv4.TryParse()
if (this.ipv4.GetProtocol() != IP_PROTOCOL_TCP) {
this.EndParse()
return false
}
if (this.ipv4.GetStatus() != PacketParseResult.Success || !this.ipv4.IsHeaderChecksumValid()) {
this.EndParse()
return true
}
var destination = this.ipv4.GetDestinationIpv4()
var source = this.ipv4.GetSourceIpv4()
var mac = this.ethernet.GetSourceMac()
for (var i = 0; i < 4; i++) {
if (destination[i] != this.localIp[i]) {
this.EndParse()
return true
}
}
if (this.tcp.TryParse() != PacketParseResult.Success) {
this.EndParse()
return true
}
if (this.tcp.GetDestinationPort() != PORT) {
this.EndParse()
return false
}
this.checksum.Reset()
for (var i = 0; i < 4; i++) {
this.checksum.AddByte(source[i])
}
for (var i = 0; i < 4; i++) {
this.checksum.AddByte(destination[i])
}
this.checksum.AddByte(0)
this.checksum.AddByte(IP_PROTOCOL_TCP)
var total = this.ipv4.GetAvailableLength()
this.checksum.AddByte(NetworkOrder.HighByte(total))
this.checksum.AddByte(NetworkOrder.LowByte(total))
for (var i = 0; i < total; i++) {
var value: byte = 0
if (this.ipv4.TryRead(i, value) != PacketDataResult.Success) {
this.EndParse()
return true
}
this.checksum.AddByte(value)
}
if (this.checksum.GetChecksum() != 0) {
this.EndParse()
return true
}
var selected = -1
var free = -1
selected = -1
free = -1
for (var candidate = 0; candidate < SESSIONS; candidate++) {
if (this.phase[candidate] == TcpStreamState.Listening) {
if (free < 0) {
free = candidate
}
} else {
var matches = this.tcp.GetSourcePort() == ((this.peerPort[candidate] as uint) as int)
for (var octet = 0; octet < 4; octet++) {
if (source[octet] != this.peerIp[candidate, octet]) {
matches = false
}
}
if (matches) {
selected = candidate
}
}
}
if (selected < 0) {
selected = free
}
if (selected < 0) {
this.EndParse()
return true
}
this.SelectSession(selected)
var flags = this.tcp.GetFlags()
var sequence = this.tcp.GetSequenceNumber()
var acknowledgment = this.tcp.GetAcknowledgmentNumber()
var length = this.tcp.GetAvailableLength()
var high: byte = 0
var low: byte = 0
this.ipv4.TryRead(TCP_WINDOW_OFFSET, high)
this.ipv4.TryRead(TCP_WINDOW_OFFSET + 1, low)
var window = NetworkOrder.Word(high, low)
if (this.phase[this.session] == TcpStreamState.Listening) {
if ((flags & SYN_REQUIRED_MASK) != FLAG_SYN || length != 0) {
this.EndParse()
return true
}
// Parse bounded SYN options; unsupported options are skipped by length.
var mss = DEFAULT_MSS
mss = DEFAULT_MSS
var index = TCP_HEADER_LENGTH
index = TCP_HEADER_LENGTH
var headerLength = this.tcp.GetHeaderLength()
while (index < headerLength) {
var kind: byte = 0
this.ipv4.TryRead(index, kind)
if (kind == 0) {
break
}
if (kind == 1) {
index++
continue
}
var optionLength: byte = 0
if (index + 1 >= headerLength) {
this.EndParse()
return true
}
this.ipv4.TryRead(index + 1, optionLength)
var size = (optionLength as uint) as int
if (size < 2 || index + size > headerLength) {
this.EndParse()
return true
}
if (kind == 2) {
if (size != 4) {
this.EndParse()
return true
}
this.ipv4.TryRead(index + 2, high)
this.ipv4.TryRead(index + 3, low)
mss = NetworkOrder.Word(high, low)
if (mss == 0) {
this.EndParse()
return true
}
}
index = index + size
}
this.Abort()
this.peerMac[this.session] = mac
this.peerIp[this.session] = source
this.peerPort[this.session] = this.tcp.GetSourcePort() as logic[16]
this.peerMss[this.session] = mss as logic[16]
this.peerWindow[this.session] = window as logic[16]
this.receiveNext[this.session] = sequence + (1 as uint)
this.generation[this.session] = this.generation[this.session] + (1 as uint)
this.sendUna[this.session] = (now << 16) + (this.generation[this.session] << 10)
this.sendNext[this.session] = this.sendUna[this.session] + (1 as uint)
this.flightLength[this.session] = 0
this.flightOffset[this.session] = 0
this.flightFlags[this.session] = FLAGS_SYN_ACK
this.inFlight[this.session] = true
this.retries[this.session] = 0
this.phase[this.session] = TcpStreamState.SynReceived
this.probeAt[this.session] = now
this.probeDelay[this.session] = INITIAL_PROBE_MS
this.timer.Start(now)
this.ackPending[this.session] = true
this.EndParse()
return true
}
for (var i = 0; i < 4; i++) {
if (source[i] != this.peerIp[this.session, i]) {
this.EndParse()
return true
}
}
if (this.tcp.GetSourcePort() != ((this.peerPort[this.session] as uint) as int)) {
this.EndParse()
return true
}
if ((flags & FLAG_RST) != 0) {
if (sequence == this.receiveNext[this.session]) {
this.Abort()
} else {
this.ackPending[this.session] = true
}
this.EndParse()
return true
}
if (this.phase[this.session] == TcpStreamState.SynReceived && (flags & FLAG_SYN) != 0 && sequence + (1 as uint) == this.receiveNext[this.session]) {
this.ackPending[this.session] = true
this.EndParse()
return true
}
if ((flags & FLAG_ACK) == 0 || (flags & FLAG_SYN) != 0) {
this.EndParse()
return true
}
// Out-of-order segments are not stored; the cumulative ACK requests retry.
var skip = this.receiveNext[this.session] - sequence
if (skip > (length as uint)) {
this.ackPending[this.session] = true
this.EndParse()
return true
}
var advance = acknowledgment - this.sendUna[this.session]
if (advance > this.sendNext[this.session] - this.sendUna[this.session]) {
this.ackPending[this.session] = true
this.EndParse()
return true
}
this.peerWindow[this.session] = window as logic[16]
if (this.inFlight[this.session] && advance != 0) {
if (acknowledgment == this.sendNext[this.session]) {
this.timer.Acknowledge(now)
this.sendUna[this.session] = acknowledgment
this.inFlight[this.session] = false
if (this.flightFlags[this.session] == FLAGS_DATA_ACK) {
this.consumed[this.session] = ((this.flightOffset[this.session] as uint) + (this.flightLength[this.session] as uint)) as logic[16]
}
if (((this.consumed[this.session] as uint) as int) == this.tx.GetLength()) {
this.tx.Clear()
this.consumed[this.session] = 0
}
if (this.phase[this.session] == TcpStreamState.SynReceived) {
if (this.retries[this.session] != 0) {
this.timer.RequireMinimum(LOST_HANDSHAKE_TIMEOUT_MS)
}
this.phase[this.session] = TcpStreamState.Established
} elif (this.phase[this.session] == TcpStreamState.LastAck) {
this.Abort()
this.EndParse()
return true
}
} elif (this.flightFlags[this.session] == FLAGS_DATA_ACK) {
this.flightOffset[this.session] = ((this.flightOffset[this.session] as uint) + advance) as logic[16]
this.flightLength[this.session] = ((this.flightLength[this.session] as uint) - advance) as logic[16]
this.sendUna[this.session] = acknowledgment
// Keep the original timer; partial ACK must not postpone recovery.
}
}
if (!this.IsConnected()) {
this.EndParse()
return true
}
var remaining = length - (skip as int)
if (this.phase[this.session] == TcpStreamState.Established && remaining > 0) {
var available = RX_CAPACITY - ((this.rxCount[this.session] as uint) as int)
if (remaining > available) {
remaining = available
}
for (var i = 0; i < remaining; i++) {
var value: byte = 0
if (this.tcp.TryRead((skip as int) + i, value) != PacketDataResult.Success) {
this.Abort()
this.EndParse()
return true
}
this.rx.Write(this.session * RX_CAPACITY + ((this.rxWrite[this.session] as uint) as int), value)
if (((this.rxWrite[this.session] as uint) as int) == RX_CAPACITY - 1) {
this.rxWrite[this.session] = 0
} else {
this.rxWrite[this.session] = ((this.rxWrite[this.session] as uint) + (1 as uint)) as logic[INDEX_BITS]
}
this.rxCount[this.session] = ((this.rxCount[this.session] as uint) + (1 as uint)) as logic[COUNT_BITS]
}
this.receiveNext[this.session] = this.receiveNext[this.session] + (remaining as uint)
}
if ((flags & FLAG_FIN) != 0 && this.phase[this.session] == TcpStreamState.Established && sequence + (length as uint) == this.receiveNext[this.session]) {
this.receiveNext[this.session] = this.receiveNext[this.session] + (1 as uint)
this.phase[this.session] = TcpStreamState.CloseWait
}
if (length != 0 || (flags & FLAG_FIN) != 0) {
this.ackPending[this.session] = true
}
this.EndParse()
return true
}
/**
* Polls all connections in rotating order and publishes at most one Ethernet frame. The next
* call starts after the successful slot, preventing a busy peer from starving another. A
* published frame remains stable until ReleaseFrame; call regularly even without RX traffic.
*/
public fn Poll(now: uint) bool
{
if (this.published) {
return true
}
for (var attempt = 0; attempt < SESSIONS; attempt++) {
this.SelectSession(this.nextPoll)
this.nextPoll++
if (this.nextPoll == SESSIONS) {
this.nextPoll = 0
}
if (this.PollSession(now)) {
return true
}
}
return false
}
/**
* Advances timers and prepares autonomous ACK, data, handshake or close traffic at wrapping
* millisecond time now. True publishes a complete Ethernet frame, stable until ReleaseFrame();
* an already published frame remains unchanged. Call regularly even without incoming traffic.
*/
fn PollSession(now: uint) bool
{
if (this.published) {
return true
}
if (this.phase[this.session] == TcpStreamState.Listening) {
return false
}
// Reinitialize invocation-local emission decisions before applying priority rules.
var emit = false
emit = false
var flags: byte = FLAG_ACK
flags = FLAG_ACK
var sequence = this.sendNext[this.session]
var offset = 0
var length = 0
offset = 0
length = 0
// Retransmission takes priority over handshake, queued data, close and pure ACKs.
if (this.inFlight[this.session] && this.timer.IsExpired(now)) {
if (this.retries[this.session] == MAX_RETRIES) {
this.Abort()
return false
}
this.retries[this.session] = ((this.retries[this.session] as uint) + (1 as uint)) as logic[4]
this.timer.Retry(now)
emit = true
flags = this.flightFlags[this.session]
sequence = this.sendUna[this.session]
offset = (this.flightOffset[this.session] as uint) as int
length = (this.flightLength[this.session] as uint) as int
} elif (this.phase[this.session] == TcpStreamState.SynReceived && this.ackPending[this.session]) {
emit = true
flags = FLAGS_SYN_ACK
sequence = this.sendUna[this.session]
} elif (!this.inFlight[this.session] && this.IsConnected()) {
var count = this.tx.GetLength() - ((this.consumed[this.session] as uint) as int)
if (count != 0 && (now - this.queuedAt[this.session] >= COALESCE_MS || count == TX_CAPACITY || count == ((this.peerMss[this.session] as uint) as int))) {
// A single segment limits congestion to at most one MSS in flight.
if (this.peerWindow[this.session] != 0) {
length = count
if (length > ((this.peerWindow[this.session] as uint) as int)) {
length = (this.peerWindow[this.session] as uint) as int
}
if (length > ((this.peerMss[this.session] as uint) as int)) {
length = (this.peerMss[this.session] as uint) as int
}
offset = (this.consumed[this.session] as uint) as int
flags = FLAGS_DATA_ACK
emit = true
} elif (now - this.probeAt[this.session] >= this.probeDelay[this.session]) {
emit = true
sequence = this.sendUna[this.session] - (1 as uint)
this.probeAt[this.session] = now
if (this.probeDelay[this.session] < MAX_PROBE_MS) {
this.probeDelay[this.session] = this.probeDelay[this.session] << 1
}
if (this.probeDelay[this.session] > MAX_PROBE_MS) {
this.probeDelay[this.session] = MAX_PROBE_MS
}
}
} elif (count == 0 && this.closing[this.session] && this.phase[this.session] == TcpStreamState.CloseWait) {
flags = FLAGS_FIN_ACK
emit = true
this.phase[this.session] = TcpStreamState.LastAck
}
if (flags == FLAGS_DATA_ACK || flags == FLAGS_FIN_ACK) {
this.flightOffset[this.session] = offset as logic[16]
this.flightLength[this.session] = length as logic[16]
this.flightFlags[this.session] = flags
this.sendUna[this.session] = this.sendNext[this.session]
sequence = this.sendUna[this.session]
this.sendNext[this.session] = this.sendNext[this.session] + (length as uint)
if (flags == FLAGS_FIN_ACK) {
this.sendNext[this.session] = this.sendNext[this.session] + (1 as uint)
}
this.inFlight[this.session] = true
this.retries[this.session] = 0
this.timer.Start(now)
}
}
if (this.ackPending[this.session]) {
emit = true
}
if (!emit) {
return false
}
this.ackPending[this.session] = false
if (!this.tx.Open(offset, length)) {
this.Abort()
return false
}
var sourcePort: byte[2] = [NetworkOrder.HighByte(PORT), NetworkOrder.LowByte(PORT)]
var destinationPort: byte[2] = [(this.peerPort[this.session] >> 8) as byte, this.peerPort[this.session] as byte]
var seq: byte[4] = [(sequence >> 24) as byte, (sequence >> 16) as byte, (sequence >> 8) as byte, sequence as byte]
var ack: byte[4] = [(this.receiveNext[this.session] >> 24) as byte, (this.receiveNext[this.session] >> 16) as byte, (this.receiveNext[this.session] >> 8) as byte, this.receiveNext[this.session] as byte]
var available = RX_CAPACITY - ((this.rxCount[this.session] as uint) as int)
var window: byte[2] = [NetworkOrder.HighByte(available), NetworkOrder.LowByte(available)]
var id: byte[2] = [0, 0]
if (this.segment.TryPrepare(this.localIp, this.peerIp[this.session], sourcePort, destinationPort, seq, ack, flags, window) != PacketDataResult.Success
|| this.packet.TryPrepare(this.localIp, this.peerIp[this.session], id) != PacketDataResult.Success
|| this.frame.TryPrepare(this.localMac, this.peerMac[this.session]) != PacketDataResult.Success) {
this.ReleaseFrame()
this.Abort()
return false
}
this.published = true
return true
}
/**
* Ends the transmitter borrow after local transmission completes or is abandoned. Invalidates
* frame views but retains unacknowledged TCP payload for retry.
*/
public fn ReleaseFrame()
{
this.frame.Invalidate()
this.packet.Invalidate()
this.segment.Invalidate()
this.tx.Close()
this.published = false
}
/**
* Reports whether a complete Ethernet response is published for borrowing.
*/
override fn IsReady() bool
{
return this.published
}
/**
* Returns the published Ethernet frame length, or zero when no response is ready.
*/
override fn GetAvailableLength() int
{
return this.frame.GetAvailableLength()
}
/**
* Reads a published Ethernet-frame byte at index; propagates the frame view result and zero-on-
* failure contract. Reads do not consume or acknowledge retained payload.
*/
override fn TryRead(index: int, value: out byte) PacketDataResult
{
return this.frame.TryRead(index, value)
}
}