mirror of
https://github.com/livingcomputermuseum/IFS.git
synced 2026-01-13 15:27:25 +00:00
207 lines
7.4 KiB
C#
207 lines
7.4 KiB
C#
using IFS.Logging;
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace IFS.EFTP
|
|
{
|
|
/// <summary>
|
|
/// Represents an open EFTP channel and provides methods for sending data to it.
|
|
/// (Receiving is not yet implemented, the current implementation exists only to support Boot File requests.)
|
|
/// </summary>
|
|
public class EFTPChannel
|
|
{
|
|
public EFTPChannel(PUPPort destination, UInt32 socketID)
|
|
{
|
|
_clientConnectionPort = destination;
|
|
|
|
_outputAckEvent = new AutoResetEvent(false);
|
|
|
|
// We create our connection port using a unique socket address.
|
|
_serverConnectionPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, socketID);
|
|
|
|
_outputQueue = new Queue<byte>(65536);
|
|
_sendPos = 0;
|
|
}
|
|
|
|
public PUPPort ServerPort
|
|
{
|
|
get { return _serverConnectionPort; }
|
|
}
|
|
|
|
public delegate void DestroyDelegate();
|
|
|
|
public DestroyDelegate OnDestroy;
|
|
|
|
public void Destroy()
|
|
{
|
|
if (OnDestroy != null)
|
|
{
|
|
OnDestroy();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends data to the channel (i.e. to the client). Will block (waiting for an ACK) if an ACK is requested.
|
|
/// </summary>
|
|
/// <param name="data">The data to be sent</param>
|
|
/// <param name="flush">Whether to flush data out immediately or to wait for enough for a full PUP first.</param>
|
|
public void Send(byte[] data, int length, bool flush)
|
|
{
|
|
if (length > data.Length)
|
|
{
|
|
throw new InvalidOperationException("Length must be less than or equal to the size of data.");
|
|
}
|
|
|
|
// Add output data to output queue.
|
|
// Again, this is really inefficient.
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
_outputQueue.Enqueue(data[i]);
|
|
}
|
|
|
|
if (flush || _outputQueue.Count >= PUP.MAX_PUP_SIZE)
|
|
{
|
|
// Send data until all is used (for a flush) or until we have less than a full PUP (non-flush).
|
|
while (_outputQueue.Count >= (flush ? 1 : PUP.MAX_PUP_SIZE))
|
|
{
|
|
byte[] chunk = new byte[Math.Min(PUP.MAX_PUP_SIZE, _outputQueue.Count)];
|
|
|
|
// Ugh.
|
|
for (int i = 0; i < chunk.Length; i++)
|
|
{
|
|
chunk[i] = _outputQueue.Dequeue();
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
// Send the data.
|
|
PUP dataPup = new PUP(PupType.EFTPData, _sendPos, _clientConnectionPort, _serverConnectionPort, chunk);
|
|
|
|
PUPProtocolDispatcher.Instance.SendPup(dataPup);
|
|
|
|
// Await an ACK. We will retry several times and resend as necessary.
|
|
int retry = 0;
|
|
for (retry = 0; retry < EFTPRetryCount; retry++)
|
|
{
|
|
if (_outputAckEvent.WaitOne(EFTPAckTimeoutPeriod))
|
|
{
|
|
// done, we got our ACK.
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// timeout: resend the PUP and wait for an ACK again.
|
|
PUPProtocolDispatcher.Instance.SendPup(dataPup);
|
|
}
|
|
}
|
|
|
|
if (retry >= EFTPRetryCount)
|
|
{
|
|
Log.Write(LogType.Error, LogComponent.EFTP, "Timeout waiting for ACK, aborting connection.");
|
|
SendAbort("Client unresponsive.");
|
|
EFTPManager.DestroyChannel(this);
|
|
}
|
|
|
|
if (_lastRecvPos == _sendPos)
|
|
{
|
|
// The client is in sync with us, we are done with this packet.
|
|
break;
|
|
}
|
|
else if (_sendPos - _lastRecvPos > 1)
|
|
{
|
|
// We lost more than one packet, something is very broken.
|
|
Log.Write(LogType.Error, LogComponent.EFTP, "Client lost more than one packet, connection is broken. Aborting.");
|
|
SendAbort("Client lost too much data.");
|
|
EFTPManager.DestroyChannel(this);
|
|
}
|
|
else
|
|
{
|
|
// We lost one packet, move back and send it again.
|
|
Log.Write(LogType.Warning, LogComponent.EFTP, "Client lost a packet, resending.");
|
|
}
|
|
|
|
}
|
|
|
|
// Move to next packet.
|
|
_sendPos++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SendEnd()
|
|
{
|
|
PUP endPup = new PUP(PupType.EFTPEnd, _sendPos, _clientConnectionPort, _serverConnectionPort, new byte[0]);
|
|
PUPProtocolDispatcher.Instance.SendPup(endPup);
|
|
|
|
// Await an ack
|
|
_outputAckEvent.WaitOne(EFTPAckTimeoutPeriod);
|
|
|
|
_sendPos++;
|
|
|
|
// Send another end to close things off.
|
|
endPup = new PUP(PupType.EFTPEnd, _sendPos, _clientConnectionPort, _serverConnectionPort, new byte[0]);
|
|
PUPProtocolDispatcher.Instance.SendPup(endPup);
|
|
}
|
|
|
|
public void RecvData(PUP p)
|
|
{
|
|
// For now, receive is not implemented.
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void RecvAck(PUP p)
|
|
{
|
|
//
|
|
// Sanity check that the client's position matches ours.
|
|
//
|
|
_lastRecvPos = p.ID;
|
|
if (_lastRecvPos != _sendPos)
|
|
{
|
|
Log.Write(LogType.Error, LogComponent.EFTP, "Client position does not match server ({0} != {1}",
|
|
_lastRecvPos, _sendPos);
|
|
}
|
|
|
|
//
|
|
// Unblock those waiting for an ACK.
|
|
//
|
|
_outputAckEvent.Set();
|
|
}
|
|
|
|
|
|
public void End(PUP p)
|
|
{
|
|
|
|
}
|
|
|
|
private void SendAbort(string message)
|
|
{
|
|
/*
|
|
PUP abortPup = new PUP(PupType.EFTPAbort, _sendPos, _clientConnectionPort, _serverConnectionPort, Helpers.StringToArray(message));
|
|
|
|
//
|
|
// Send this directly, do not wait for the client to be ready (since it may be wedged, and we don't expect anyone to actually notice
|
|
// this anyway).
|
|
//
|
|
PUPProtocolDispatcher.Instance.SendPup(abortPup);
|
|
*/
|
|
}
|
|
|
|
private PUPPort _clientConnectionPort;
|
|
private PUPPort _serverConnectionPort;
|
|
|
|
private uint _sendPos;
|
|
private uint _lastRecvPos;
|
|
private Queue<byte> _outputQueue;
|
|
private AutoResetEvent _outputAckEvent;
|
|
|
|
|
|
// Timeouts and retries
|
|
private const int EFTPRetryCount = 5;
|
|
private const int EFTPAckTimeoutPeriod = 1000; // 1 second
|
|
}
|
|
}
|