diff --git a/PUP/BCPLString.cs b/PUP/BCPLString.cs
index 8eca421..668bd3c 100644
--- a/PUP/BCPLString.cs
+++ b/PUP/BCPLString.cs
@@ -111,12 +111,14 @@ namespace IFS
}
///
- /// Returns the raw representation of the BCPL string
+ /// Returns the raw representation of the BCPL string.
+ /// This returned array is padded to a word boundary.
///
///
public byte[] ToArray()
{
- byte[] a = new byte[_string.Length + 1];
+ int length = _string.Length + ((_string.Length % 2) == 0 ? 2 : 1);
+ byte[] a = new byte[length];
a[0] = (byte)_string.Length;
_string.CopyTo(a, 1);
diff --git a/PUP/BSPManager.cs b/PUP/BSPManager.cs
index 7190276..ad6533f 100644
--- a/PUP/BSPManager.cs
+++ b/PUP/BSPManager.cs
@@ -10,6 +10,13 @@ using System.Threading.Tasks;
namespace IFS
{
+ public struct BSPAck
+ {
+ public ushort MaxBytes;
+ public ushort MaxPups;
+ public ushort BytesSent;
+ }
+
public abstract class BSPProtocol : PUPProtocolBase
{
public abstract void InitializeServerForChannel(BSPChannel channel);
@@ -29,10 +36,10 @@ namespace IFS
_outputLock = new ReaderWriterLockSlim();
_inputWriteEvent = new AutoResetEvent(false);
-
_inputQueue = new Queue(65536);
_outputAckEvent = new AutoResetEvent(false);
+ _outputQueue = new Queue(65536);
_protocolHandler = protocolHandler;
@@ -66,7 +73,23 @@ namespace IFS
public void Destroy()
{
-
+ if (OnDestroy != null)
+ {
+ OnDestroy();
+ }
+ }
+
+ public void End(PUP p)
+ {
+ PUP endReplyPup = new PUP(PupType.EndReply, p.ID, _clientConnectionPort, _serverConnectionPort, new byte[0]);
+ PUPProtocolDispatcher.Instance.SendPup(endReplyPup);
+
+ // "The receiver of the End PUP responds by returning an EndReply Pup with matching ID and then
+ // _dallying_ up to some reasonably long timeout interval (say, 10 seconds) in order to respond to
+ // a retransmitted End Pup should its initial EndReply be lost. If and when the dallying end of the
+ // stream connection receives its EndReply, it may immediately self destruct."
+ // TODO: actually make this happen...
+
}
///
@@ -118,7 +141,13 @@ namespace IFS
// Not enough data in the queue.
// Wait until we have received more data, then try again.
- _inputWriteEvent.WaitOne(); // TODO: timeout and fail
+ if (!_inputWriteEvent.WaitOne(BSPReadTimeoutPeriod))
+ {
+ Log.Write(LogType.Error, LogComponent.BSP, "Timed out waiting for data on read, aborting connection.");
+ // We timed out waiting for data, abort the connection.
+ SendAbort("Timeout on read.");
+ BSPManager.DestroyChannel(this);
+ }
}
}
@@ -153,16 +182,19 @@ namespace IFS
///
/// Appends incoming client data into the input queue (called from BSPManager to place new PUP data into the BSP stream)
///
- public void WriteQueue(PUP dataPUP)
+ public void RecvWriteQueue(PUP dataPUP)
{
// If we are over our high watermark, we will drop the data (and not send an ACK even if requested).
// Clients should be honoring the limits we set in the RFC packets.
_inputLock.EnterUpgradeableReadLock();
+
+ /*
if (_inputQueue.Count + dataPUP.Contents.Length > MaxBytes)
{
+ Log.Write(LogLevel.Error, "Queue larger than {0} bytes, dropping.");
_inputLock.ExitUpgradeableReadLock();
return;
- }
+ } */
// Sanity check on expected position from sender vs. received data on our end.
// If they don't match then we've lost a packet somewhere.
@@ -173,6 +205,7 @@ namespace IFS
// must eventually be resent. This is far simpler than accepting out-of-order data and keeping track
// of where it goes in the queue, though less efficient.
_inputLock.ExitUpgradeableReadLock();
+ Log.Write(LogType.Error, LogComponent.BSP, "Lost Packet, client ID does not match our receive position ({0} != {1})", dataPUP.ID, _recv_pos);
return;
}
@@ -186,7 +219,7 @@ namespace IFS
_inputQueue.Enqueue(dataPUP.Contents[i]);
//Console.Write("{0:x} ({1}), ", dataPUP.Contents[i], (char)dataPUP.Contents[i]);
- }
+ }
_recv_pos += (UInt32)dataPUP.Contents.Length;
@@ -198,48 +231,102 @@ namespace IFS
// If the client wants an ACK, send it now.
if ((PupType)dataPUP.Type == PupType.AData)
- {
- SendAck();
+ {
+ SendAck();
}
}
+ ///
+ /// Sends data, with immediate flush.
+ ///
+ ///
+ public void Send(byte[] data)
+ {
+ Send(data, true /* flush */);
+ }
+
///
/// Sends data to the channel (i.e. to the client). Will block (waiting for an ACK) if an ACK is requested.
///
/// The data to be sent
- public void Send(byte[] data)
+ /// Whether to flush data out immediately or to wait for enough for a full PUP first.
+ public void Send(byte[] data, bool flush)
{
- // Write data to the output stream.
- // For now, we request ACKs for every pup sent.
- // TODO: should buffer data until an entire PUP's worth is ready
- // (and split data that's too large into multiple PUPs.)
- PUP dataPup = new PUP(PupType.AData, _send_pos, _clientConnectionPort, _serverConnectionPort, data);
+ // Add output data to output queue.
+ // Again, this is really inefficient
+ for (int i = 0; i < data.Length; i++)
+ {
+ _outputQueue.Enqueue(data[i]);
+ }
- PUPProtocolDispatcher.Instance.SendPup(dataPup);
+ 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)];
- _send_pos += (uint)data.Length;
+ // Ugh.
+ for (int i = 0; i < chunk.Length; i++)
+ {
+ chunk[i] = _outputQueue.Dequeue();
+ }
- // Await an ack for the PUP we just sent
- _outputAckEvent.WaitOne(); // TODO: timeout and fail
+ // Send the data, retrying as necessary.
+ int retry;
+ for (retry = 0; retry < BSPRetryCount; retry++)
+ {
+ PUP dataPup = new PUP(PupType.AData, _send_pos, _clientConnectionPort, _serverConnectionPort, chunk);
+ PUPProtocolDispatcher.Instance.SendPup(dataPup);
+
+ _send_pos += (uint)chunk.Length;
+
+ // Await an ack for the PUP we just sent. If we timeout, we will retry.
+ //
+ if (_outputAckEvent.WaitOne(BSPAckTimeoutPeriod))
+ {
+ break;
+ }
+
+ Log.Write(LogType.Warning, LogComponent.BSP, "ACK not received for sent data, retrying.");
+ }
+
+ if (retry >= BSPRetryCount)
+ {
+ Log.Write(LogType.Error, LogComponent.BSP, "ACK not received after retries, aborting connection.");
+ SendAbort("ACK not received for sent data.");
+ BSPManager.DestroyChannel(this);
+ }
+
+ }
+ }
+ }
+
+ public void SendAbort(string message)
+ {
+ PUP abortPup = new PUP(PupType.Abort, _start_pos, _clientConnectionPort, _serverConnectionPort, Helpers.StringToArray(message));
+ PUPProtocolDispatcher.Instance.SendPup(abortPup);
}
///
/// Invoked when the client sends an ACK
///
///
- public void Ack(PUP ackPUP)
+ public void RecvAck(PUP ackPUP)
{
// Update receiving end stats (max PUPs, etc.)
// Ensure client's position matches ours
if (ackPUP.ID != _send_pos)
{
- Log.Write(LogLevel.BSPLostPacket,
- String.Format("Client position != server position for BSP {0} ({1} != {2})",
- _serverConnectionPort.Socket,
- ackPUP.ID,
- _send_pos));
- }
+ Log.Write(LogType.Warning, LogComponent.BSP,
+ "Client position != server position for BSP {0} ({1} != {2})",
+ _serverConnectionPort.Socket,
+ ackPUP.ID,
+ _send_pos);
+ }
+
+ BSPAck ack = (BSPAck)Serializer.Deserialize(ackPUP.Contents, typeof(BSPAck));
// Let any waiting threads continue
@@ -263,9 +350,20 @@ namespace IFS
// to allow protocols consuming BSP streams to be alerted when things happen.
//
+ public delegate void DestroyDelegate();
+
+ public DestroyDelegate OnDestroy;
+
private void SendAck()
{
- PUP ackPup = new PUP(PupType.Ack, _recv_pos, _clientConnectionPort, _serverConnectionPort);
+ _inputLock.EnterReadLock();
+ BSPAck ack = new BSPAck();
+ ack.MaxBytes = MaxBytes; //(ushort)(MaxBytes - _inputQueue.Count);
+ ack.MaxPups = MaxPups;
+ ack.BytesSent = MaxBytes; //(ushort)(MaxBytes - _inputQueue.Count);
+ _inputLock.ExitReadLock();
+
+ PUP ackPup = new PUP(PupType.Ack, _recv_pos, _clientConnectionPort, _serverConnectionPort, Serializer.Serialize(ack));
PUPProtocolDispatcher.Instance.SendPup(ackPup);
}
@@ -288,6 +386,7 @@ namespace IFS
// TODO: replace this with a more efficient structure for buffering data
private Queue _inputQueue;
+ private Queue _outputQueue;
// Constants
@@ -295,6 +394,11 @@ namespace IFS
private const int MaxPups = 1;
private const int MaxPupSize = 532;
private const int MaxBytes = 1 * 532;
+
+
+ private const int BSPRetryCount = 5;
+ private const int BSPAckTimeoutPeriod = 1000; // 1 second
+ private const int BSPReadTimeoutPeriod = 60000; // 1 minute
}
///
@@ -324,7 +428,7 @@ namespace IFS
{
if (p.Type != PupType.RFC)
{
- Log.Write(LogLevel.Error, String.Format("Expected RFC pup, got {0}", p.Type));
+ Log.Write(LogType.Error, LogComponent.RTP, "Expected RFC pup, got {0}", p.Type);
return;
}
@@ -343,16 +447,16 @@ namespace IFS
sourcePort.Network = DirectoryServices.Instance.LocalNetwork;
PUP rfcResponse = new PUP(PupType.RFC, p.ID, newChannel.ClientPort, sourcePort, newChannel.ServerPort.ToArray());
- PUPProtocolDispatcher.Instance.SendPup(rfcResponse);
+ Log.Write(LogComponent.RTP,
+ "Establishing Rendezvous, ID {0}, Server port {1}, Client port {2}.",
+ p.ID, newChannel.ServerPort, newChannel.ClientPort);
+
+ PUPProtocolDispatcher.Instance.SendPup(rfcResponse);
}
///
/// Called when BSP-based protocols receive data.
///
- ///
- /// null if no new channel is created due to the sent PUP (not an RFC)
- /// a new BSPChannel if one has been created based on the PUP (new RFC)
- ///
///
public static void RecvData(PUP p)
{
@@ -360,43 +464,50 @@ namespace IFS
if (channel == null)
{
- Log.Write(LogLevel.Error, "Received BSP PUP on an unconnected socket, ignoring.");
+ Log.Write(LogType.Error, LogComponent.BSP, "Received BSP PUP on an unconnected socket, ignoring.");
return;
}
switch (p.Type)
{
case PupType.RFC:
- Log.Write(LogLevel.Error, "Received RFC on established channel, ignoring.");
+ Log.Write(LogType.Error, LogComponent.BSP, "Received RFC on established channel, ignoring.");
break;
case PupType.Data:
case PupType.AData:
- {
- channel.WriteQueue(p);
+ {
+ channel.RecvWriteQueue(p);
}
break;
case PupType.Ack:
{
- channel.Ack(p);
+ channel.RecvAck(p);
+ }
+ break;
+
+ case PupType.End:
+ {
+ // Second step of tearing down a connection, the End from the client, to which we will
+ // send an EndReply, expecting a second EndReply.
+ channel.End(p);
}
break;
- case PupType.End:
- {
- //channel.EndReply();
+ case PupType.EndReply:
+ {
+ // Last step of tearing down a connection, the EndReply from the client.
+ DestroyChannel(channel);
}
break;
case PupType.Abort:
- {
- // TODO: tear down the channel
- DestroyChannel(channel);
-
+ {
string abortMessage = Helpers.ArrayToString(p.Contents);
+ Log.Write(LogType.Warning, LogComponent.RTP, String.Format("BSP aborted, message: '{0}'", abortMessage));
- Log.Write(LogLevel.Warning, String.Format("BSP aborted, message: '{0}'", abortMessage));
+ DestroyChannel(channel);
}
break;
diff --git a/PUP/CopyDisk/CopyDiskServer.cs b/PUP/CopyDisk/CopyDiskServer.cs
new file mode 100644
index 0000000..a3246aa
--- /dev/null
+++ b/PUP/CopyDisk/CopyDiskServer.cs
@@ -0,0 +1,573 @@
+using IFS.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+namespace IFS.CopyDisk
+{
+ public enum CopyDiskBlock
+ {
+ Version = 1,
+ SendDiskParamsR = 2,
+ HereAreDiskParams = 3,
+ StoreDisk = 4,
+ RetrieveDisk = 5,
+ HereIsDiskPage = 6,
+ EndOfTransfer = 7,
+ SendErrors = 8,
+ HereAreErrors = 9,
+ No = 10,
+ Yes = 11,
+ Comment = 12,
+ Login = 13,
+ SendDiskParamsW = 14,
+ }
+
+ public enum NoCode
+ {
+ UnitNotReady = 1,
+ UnitWriteProtected = 2,
+ OverwriteNotAllowed = 3,
+ UnknownCommand = 4,
+ }
+
+ struct VersionYesNoBlock
+ {
+ public VersionYesNoBlock(CopyDiskBlock command, ushort code, string herald)
+ {
+ Code = code;
+ Herald = new BCPLString(herald);
+
+ Length = (ushort)((6 + herald.Length + 2) / 2); // +2 for length of BCPL string and to round up to next word length
+ Command = (ushort)command;
+ }
+
+ public ushort Length;
+ public ushort Command;
+ public ushort Code;
+ public BCPLString Herald;
+ }
+
+ struct LoginBlock
+ {
+ public ushort Length;
+ public ushort Command;
+ public BCPLString UserName;
+
+ [WordAligned]
+ public BCPLString UserPassword;
+
+ [WordAligned]
+ public BCPLString ConnName;
+
+ [WordAligned]
+ public BCPLString ConnPassword;
+ }
+
+ struct SendDiskParamsBlock
+ {
+ public ushort Length;
+ public ushort Command;
+ public BCPLString UnitName;
+ }
+
+ struct HereAreDiskParamsBFSBlock
+ {
+ public HereAreDiskParamsBFSBlock(DiskGeometry geometry)
+ {
+ Length = 6;
+ Command = (ushort)CopyDiskBlock.HereAreDiskParams;
+
+ DiskType = 10; // 12(octal) - BFS disk types
+ Cylinders = (ushort)geometry.Cylinders;
+ Heads = (ushort)geometry.Tracks;
+ Sectors = (ushort)geometry.Sectors;
+ }
+
+ public ushort Length;
+ public ushort Command;
+ public ushort DiskType;
+ public ushort Cylinders;
+ public ushort Heads;
+ public ushort Sectors;
+ }
+
+ struct HereAreErrorsBFSBlock
+ {
+ public HereAreErrorsBFSBlock(ushort hardErrors, ushort softErrors)
+ {
+ Length = 4;
+ Command = (ushort)CopyDiskBlock.HereAreErrors;
+ HardErrorCount = hardErrors;
+ SoftErrorCount = softErrors;
+ }
+
+ public ushort Length;
+ public ushort Command;
+ public ushort HardErrorCount;
+ public ushort SoftErrorCount;
+ }
+
+
+ struct TransferParametersBlock
+ {
+ public ushort Length;
+ public ushort Command;
+ public ushort StartAddress;
+ public ushort EndAddress;
+ }
+
+ struct HereIsDiskPageBlock
+ {
+ public HereIsDiskPageBlock(byte[] header, byte[] label, byte[] data)
+ {
+ if (header.Length != 4 ||
+ label.Length != 16 ||
+ data.Length != 512)
+ {
+ throw new InvalidOperationException("Page data is incorrectly sized.");
+ }
+
+ Length = (512 + 16 + 4 + 4) / 2;
+ Command = (ushort)CopyDiskBlock.HereIsDiskPage;
+
+ Header = header;
+ Label = label;
+ Data = data;
+ }
+
+ public ushort Length;
+ public ushort Command;
+
+ [ArrayLength(4)]
+ public byte[] Header;
+
+ [ArrayLength(16)]
+ public byte[] Label;
+
+ [ArrayLength(512)]
+ public byte[] Data;
+ }
+
+ struct HereIsDiskPageIncorrigableBlock
+ {
+ public HereIsDiskPageIncorrigableBlock(byte[] header, byte[] label)
+ {
+ if (header.Length != 4 ||
+ label.Length != 16)
+ {
+ throw new InvalidOperationException("Page data is incorrectly sized.");
+ }
+
+ Length = (16 + 4 + 4) / 2;
+ Command = (ushort)CopyDiskBlock.HereIsDiskPage;
+
+ Header = header;
+ Label = label;
+ }
+
+ public ushort Length;
+ public ushort Command;
+
+ [ArrayLength(4)]
+ public byte[] Header;
+
+ [ArrayLength(16)]
+ public byte[] Label;
+ }
+
+ struct EndOfTransferBlock
+ {
+ public EndOfTransferBlock(int dummy) /* can't have parameterless constructor for struct */
+ {
+ Length = 2;
+ Command = (ushort)CopyDiskBlock.EndOfTransfer;
+ }
+
+ public ushort Length;
+ public ushort Command;
+ }
+
+ public class CopyDiskServer : BSPProtocol
+ {
+ ///
+ /// Called by dispatcher to send incoming data destined for this protocol.
+ ///
+ ///
+ public override void RecvData(PUP p)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void InitializeServerForChannel(BSPChannel channel)
+ {
+ // Spawn new worker
+ CopyDiskWorker worker = new CopyDiskWorker(channel);
+ }
+ }
+
+ public class CopyDiskWorker
+ {
+ public CopyDiskWorker(BSPChannel channel)
+ {
+ // Register for channel events
+ channel.OnDestroy += OnChannelDestroyed;
+
+ _running = true;
+
+ _workerThread = new Thread(new ParameterizedThreadStart(CopyDiskWorkerThreadInit));
+ _workerThread.Start(channel);
+ }
+
+ private void OnChannelDestroyed()
+ {
+ // Tell the thread to exit and give it a short period to do so...
+ _running = false;
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Asking CopyDisk worker thread to exit...");
+ _workerThread.Join(1000);
+
+ if (_workerThread.IsAlive)
+ {
+ Logging.Log.Write(LogType.Verbose, LogComponent.CopyDisk, "CopyDisk worker thread did not exit, terminating.");
+ _workerThread.Abort();
+ }
+ }
+
+ private void CopyDiskWorkerThreadInit(object obj)
+ {
+ BSPChannel channel = (BSPChannel)obj;
+
+ //
+ // Run the worker thread.
+ // If anything goes wrong, log the exception and tear down the BSP connection.
+ //
+ try
+ {
+ CopyDiskWorkerThread(channel);
+ }
+ catch(Exception e)
+ {
+ if (!(e is ThreadAbortException))
+ {
+ Logging.Log.Write(LogType.Error, LogComponent.CopyDisk, "CopyDisk worker thread terminated with exception '{0}'.", e.Message);
+ channel.SendAbort("Server encountered an error.");
+ }
+ }
+ }
+
+ private void CopyDiskWorkerThread(BSPChannel channel)
+ {
+ // TODO: enforce state (i.e. reject out-of-order block types.)
+ while (_running)
+ {
+ // Retrieve length of this block (in bytes):
+ int length = channel.ReadUShort() * 2;
+
+ // Sanity check that length is a reasonable value.
+ if (length > 2048)
+ {
+ // TODO: shut down channel
+ throw new InvalidOperationException(String.Format("Insane block length ({0})", length));
+ }
+
+ // Retrieve type
+ CopyDiskBlock blockType = (CopyDiskBlock)channel.ReadUShort();
+
+ // Read rest of block starting at offset 4 (so deserialization works)
+ byte[] data = new byte[length];
+ channel.Read(ref data, data.Length - 4, 4);
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Copydisk block type is {0}", blockType);
+
+ switch(blockType)
+ {
+ case CopyDiskBlock.Version:
+ {
+ VersionYesNoBlock vbIn = (VersionYesNoBlock)Serializer.Deserialize(data, typeof(VersionYesNoBlock));
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Copydisk client is version {0}, '{1}'", vbIn.Code, vbIn.Herald.ToString());
+
+ // Send the response:
+ VersionYesNoBlock vbOut = new VersionYesNoBlock(CopyDiskBlock.Version, vbIn.Code, "IFS CopyDisk of 26-Jan-2016!");
+ channel.Send(Serializer.Serialize(vbOut));
+ }
+ break;
+
+ case CopyDiskBlock.Login:
+ {
+ LoginBlock login = (LoginBlock)Serializer.Deserialize(data, typeof(LoginBlock));
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Login is for user {0}, password {1}, connection {2}, connection password {3}.",
+ login.UserName,
+ login.UserPassword,
+ login.ConnName,
+ login.ConnPassword);
+
+ //
+ // TODO: for now we allow anyone in with any username and password, this needs to be fixed once
+ // an authentication mechanism is set up.
+ //
+
+ // Send a "Yes" response back.
+ //
+ VersionYesNoBlock yes = new VersionYesNoBlock(CopyDiskBlock.Yes, 0, "Come on in, the water's fine.");
+ channel.Send(Serializer.Serialize(yes));
+ }
+ break;
+
+ case CopyDiskBlock.SendDiskParamsR:
+ {
+ SendDiskParamsBlock p = (SendDiskParamsBlock)Serializer.Deserialize(data, typeof(SendDiskParamsBlock));
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Requested unit for reading is '{0}'", p.UnitName);
+
+ //
+ // See if the pack image exists, return HereAreDiskParams if so, or No if not.
+ // If the image exists, save the path for future use.
+ //
+ // Some sanity (and security) checks:
+ // Name must be a filename only, no paths of any kind allowed.
+ // Oh, and the file must exist in the directory holding disk packs.
+ //
+ string diskPath = GetPathForDiskImage(p.UnitName.ToString());
+ if (!String.IsNullOrEmpty(Path.GetDirectoryName(p.UnitName.ToString())) ||
+ !File.Exists(diskPath))
+ {
+ // Invalid name, return No reponse.
+ VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Invalid unit name.");
+ channel.Send(Serializer.Serialize(no));
+ }
+ else
+ {
+ //
+ // Attempt to open the image file and read it into memory.
+ //
+ try
+ {
+ using (FileStream packStream = new FileStream(diskPath, FileMode.Open, FileAccess.Read))
+ {
+ // TODO: determine pack type rather than assuming Diablo 31
+ _pack = new DiabloPack(DiabloDiskType.Diablo31);
+ _pack.Load(packStream, diskPath, true /* reverse byte order */);
+ }
+
+ // Send a "HereAreDiskParams" response indicating success.
+ //
+ HereAreDiskParamsBFSBlock diskParams = new HereAreDiskParamsBFSBlock(_pack.Geometry);
+ channel.Send(Serializer.Serialize(diskParams));
+ }
+ catch
+ {
+ // If we fail for any reason, return a "No" response.
+ // TODO: can we be more helpful here?
+ VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Image could not be opened.");
+ channel.Send(Serializer.Serialize(no));
+ }
+ }
+ }
+ break;
+
+ case CopyDiskBlock.SendDiskParamsW:
+ {
+ SendDiskParamsBlock p = (SendDiskParamsBlock)Serializer.Deserialize(data, typeof(SendDiskParamsBlock));
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Requested unit for writing is '{0}'", p.UnitName);
+
+ //
+ // Some sanity (and security) checks:
+ // Name must be a filename only, no paths of any kind allowed.
+ // Oh, and the file must not exist in the directory holding disk packs.
+ //
+ string diskPath = GetPathForDiskImage(p.UnitName.ToString());
+ if (!String.IsNullOrEmpty(Path.GetDirectoryName(p.UnitName.ToString())) ||
+ File.Exists(diskPath))
+ {
+ // Invalid name, return No reponse.
+ VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Invalid unit name or image already exists.");
+ channel.Send(Serializer.Serialize(no));
+ }
+ else
+ {
+ //
+ // Create a new in-memory disk image. We will write it out to disk when the transfer is completed.
+ //
+ // TODO: determine pack type based on disk params rather than assuming Diablo 31
+ _pack = new DiabloPack(DiabloDiskType.Diablo31);
+ _pack.PackName = diskPath;
+
+
+ // Send a "HereAreDiskParams" response indicating success.
+ //
+ HereAreDiskParamsBFSBlock diskParams = new HereAreDiskParamsBFSBlock(_pack.Geometry);
+ channel.Send(Serializer.Serialize(diskParams));
+ }
+ }
+ break;
+
+
+ case CopyDiskBlock.RetrieveDisk:
+ case CopyDiskBlock.StoreDisk:
+ {
+ TransferParametersBlock transferParameters = (TransferParametersBlock)Serializer.Deserialize(data, typeof(TransferParametersBlock));
+
+ _startAddress = _pack.DiskAddressToVirtualAddress(transferParameters.StartAddress);
+ _endAddress = _pack.DiskAddressToVirtualAddress(transferParameters.EndAddress);
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Transfer is from block {0} to block {1}", transferParameters.StartAddress, transferParameters.EndAddress);
+
+ // Validate start/end parameters
+ if (_endAddress <= _startAddress ||
+ _startAddress > _pack.MaxAddress ||
+ _endAddress > _pack.MaxAddress)
+ {
+ VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnknownCommand, "Transfer parameters are invalid.");
+ channel.Send(Serializer.Serialize(no));
+ }
+ else
+ {
+ // We're OK. Save the parameters and send a Yes response.
+ VersionYesNoBlock yes = new VersionYesNoBlock(CopyDiskBlock.Yes, 0, "You are cleared for launch.");
+ channel.Send(Serializer.Serialize(yes));
+
+ //
+ // And send the requested range of pages if this is a Retrieve operation
+ // (otherwise wait for a HereIsDiskPage block from the client.)
+ //
+ if (blockType == CopyDiskBlock.RetrieveDisk)
+ {
+ for (int i = _startAddress; i < _endAddress + 1; i++)
+ {
+ DiabloDiskSector sector = _pack.GetSector(i);
+ HereIsDiskPageBlock block = new HereIsDiskPageBlock(sector.Header, sector.Label, sector.Data);
+ channel.Send(Serializer.Serialize(block), false /* do not flush */);
+
+ if ((i % 100) == 0)
+ {
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Sent page {0}", i);
+ }
+ }
+
+ // Send "EndOfTransfer" block to finish the transfer.
+ EndOfTransferBlock endTransfer = new EndOfTransferBlock(0);
+ channel.Send(Serializer.Serialize(endTransfer));
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Send done.");
+ }
+ else
+ {
+ _currentAddress = _startAddress;
+ }
+ }
+ }
+ break;
+
+ case CopyDiskBlock.HereIsDiskPage:
+ {
+ if (_currentAddress > _endAddress)
+ {
+ channel.SendAbort("Invalid address for page.");
+ _running = false;
+ break;
+ }
+
+ if ((_currentAddress % 100) == 0)
+ {
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Received page {0}", _currentAddress);
+ }
+
+ if (data.Length < 512 + 16 + 4 + 4)
+ {
+ // Incomplete ("incorrigable") page, indicating an unreadable or empty sector, just copy
+ // the header/label data in and leave an empty data page.
+ HereIsDiskPageIncorrigableBlock diskPage = (HereIsDiskPageIncorrigableBlock)Serializer.Deserialize(data, typeof(HereIsDiskPageIncorrigableBlock));
+ DiabloDiskSector sector = new DiabloDiskSector(diskPage.Header, diskPage.Label, new byte[512]);
+ _pack.SetSector(_currentAddress, sector);
+
+ _currentAddress++;
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Page is empty / incorrigable.");
+ }
+ else
+ {
+ HereIsDiskPageBlock diskPage = (HereIsDiskPageBlock)Serializer.Deserialize(data, typeof(HereIsDiskPageBlock));
+ DiabloDiskSector sector = new DiabloDiskSector(diskPage.Header, diskPage.Label, diskPage.Data);
+ _pack.SetSector(_currentAddress, sector);
+
+ _currentAddress++;
+ }
+ }
+ break;
+
+ case CopyDiskBlock.EndOfTransfer:
+ {
+ // No data in block. If we aren't currently at the end of the transfer, the transfer has been aborted.
+ // Do nothing right now.
+ if (_currentAddress < _endAddress)
+ {
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Transfer was aborted.");
+ _running = false;
+ }
+ else
+ {
+ try
+ {
+ // Commit disk image to disk.
+ using (FileStream packStream = new FileStream(_pack.PackName, FileMode.OpenOrCreate, FileAccess.Write))
+ {
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Saving {0}...", _pack.PackName);
+ _pack.Save(packStream, true /* reverse byte order */);
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Saved.");
+ }
+ }
+ catch(Exception e)
+ {
+ // Log error, reset state.
+ Log.Write(LogType.Error, LogComponent.CopyDisk, "Failed to save pack {0} - {1}", _pack.PackName, e.Message);
+ }
+ }
+ }
+ break;
+
+ case CopyDiskBlock.SendErrors:
+ {
+ // No data in block. Send list of errors we encountered. (There should always be none since we're perfect and have no disk errors.)
+ HereAreErrorsBFSBlock errorBlock = new HereAreErrorsBFSBlock(0, 0);
+ channel.Send(Serializer.Serialize(errorBlock));
+ }
+ break;
+
+ default:
+ Log.Write(LogType.Warning, LogComponent.CopyDisk, "Unhandled CopyDisk block {0}", blockType);
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Builds a relative path to the directory that holds the disk images.
+ ///
+ ///
+ ///
+ private static string GetPathForDiskImage(string packName)
+ {
+ // TODO:
+ // Make this path configurable?
+ return Path.Combine("Disks", packName);
+ }
+
+ private Thread _workerThread;
+ private bool _running;
+
+ // The pack being read / stored by this server
+ private DiabloPack _pack = null;
+
+ // Current position and range of a write operation
+ private int _currentAddress = 0;
+ private int _startAddress = 0;
+ private int _endAddress = 0;
+ }
+}
diff --git a/PUP/CopyDisk/DiabloPack.cs b/PUP/CopyDisk/DiabloPack.cs
new file mode 100644
index 0000000..5e96dad
--- /dev/null
+++ b/PUP/CopyDisk/DiabloPack.cs
@@ -0,0 +1,242 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS.CopyDisk
+{
+ public struct DiskGeometry
+ {
+ public DiskGeometry(int cylinders, int tracks, int sectors)
+ {
+ Cylinders = cylinders;
+ Tracks = tracks;
+ Sectors = sectors;
+ }
+
+ public int Cylinders;
+ public int Tracks;
+ public int Sectors;
+ }
+
+ public enum DiabloDiskType
+ {
+ Diablo31,
+ Diablo44
+ }
+
+ public class DiabloDiskSector
+ {
+ public DiabloDiskSector(byte[] header, byte[] label, byte[] data)
+ {
+ if (header.Length != 4 ||
+ label.Length != 16 ||
+ data.Length != 512)
+ {
+ throw new InvalidOperationException("Invalid sector header/label/data length.");
+ }
+
+ Header = header;
+ Label = label;
+ Data = data;
+ }
+
+ public byte[] Header;
+ public byte[] Label;
+ public byte[] Data;
+
+ public static DiabloDiskSector Empty = new DiabloDiskSector(new byte[4], new byte[16], new byte[512]);
+ }
+
+ ///
+ /// Encapsulates disk image data for all disk packs used with the
+ /// standard Alto Disk Controller (i.e. the 31 and 44, which differ
+ /// only in the number of cylinders)
+ ///
+ public class DiabloPack
+ {
+ public DiabloPack(DiabloDiskType type)
+ {
+ _diskType = type;
+ _packName = null;
+ _geometry = new DiskGeometry(type == DiabloDiskType.Diablo31 ? 203 : 406, 2, 12);
+ _sectors = new DiabloDiskSector[_geometry.Cylinders, _geometry.Tracks, _geometry.Sectors];
+ }
+
+ public DiskGeometry Geometry
+ {
+ get { return _geometry; }
+ }
+
+ public string PackName
+ {
+ get { return _packName; }
+ set { _packName = value; }
+ }
+
+ public int MaxAddress
+ {
+ get
+ {
+ return _geometry.Sectors * _geometry.Tracks * _geometry.Cylinders;
+ }
+ }
+
+ public void Load(Stream imageStream, string path, bool reverseByteOrder)
+ {
+ _packName = path;
+ for(int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++)
+ {
+ for(int track = 0; track < _geometry.Tracks; track++)
+ {
+ for(int sector = 0; sector < _geometry.Sectors; sector++)
+ {
+ byte[] header = new byte[4]; // 2 words
+ byte[] label = new byte[16]; // 8 words
+ byte[] data = new byte[512]; // 256 words
+
+ //
+ // Bitsavers images have an extra word in the header for some reason.
+ // ignore it.
+ // TODO: should support different formats ("correct" raw, Alto CopyDisk format, etc.)
+ //
+ imageStream.Seek(2, SeekOrigin.Current);
+
+ if (imageStream.Read(header, 0, header.Length) != header.Length)
+ {
+ throw new InvalidOperationException("Short read while reading sector header.");
+ }
+
+ if (imageStream.Read(label, 0, label.Length) != label.Length)
+ {
+ throw new InvalidOperationException("Short read while reading sector label.");
+ }
+
+ if (imageStream.Read(data, 0, data.Length) != data.Length)
+ {
+ throw new InvalidOperationException("Short read while reading sector data.");
+ }
+
+ _sectors[cylinder, track, sector] =
+ new DiabloDiskSector(
+ reverseByteOrder ? SwapBytes(header) : header,
+ reverseByteOrder ? SwapBytes(label) : label,
+ reverseByteOrder ? SwapBytes(data) : data);
+ }
+ }
+ }
+
+ if (imageStream.Position != imageStream.Length)
+ {
+ throw new InvalidOperationException("Extra data at end of image file.");
+ }
+ }
+
+ public void Save(Stream imageStream, bool reverseByteOrder)
+ {
+ byte[] emptyHeader = new byte[4]; // 2 words
+ byte[] emptyLabel = new byte[16]; // 8 words
+ byte[] emptyData = new byte[512]; // 256 words
+
+ for (int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++)
+ {
+ for (int track = 0; track < _geometry.Tracks; track++)
+ {
+ for (int sector = 0; sector < _geometry.Sectors; sector++)
+ {
+
+ //
+ // Bitsavers images have an extra word in the header for some reason.
+ // We will follow this 'standard' when writing out.
+ // TODO: should support different formats ("correct" raw, Alto CopyDisk format, etc.)
+ //
+ byte[] dummy = new byte[2];
+ imageStream.Write(dummy, 0, 2);
+
+ DiabloDiskSector s = GetSector(cylinder, track, sector);
+
+ imageStream.Write(reverseByteOrder ? SwapBytes(s.Header) : s.Header, 0, s.Header.Length);
+ imageStream.Write(reverseByteOrder ? SwapBytes(s.Label) : s.Label, 0, s.Label.Length);
+ imageStream.Write(reverseByteOrder ? SwapBytes(s.Data) : s.Data, 0, s.Data.Length);
+
+ }
+ }
+ }
+ }
+
+ public int DiskAddressToVirtualAddress(ushort diskAddress)
+ {
+ int head = (diskAddress & 0x4) >> 2;
+ int cylinder = (diskAddress & 0xff8) >> 3;
+ int sector = (diskAddress & 0xf000) >> 12;
+
+ return cylinder * (_geometry.Sectors * _geometry.Tracks) + head * _geometry.Sectors + sector;
+ }
+
+ public DiabloDiskSector GetSector(int cylinder, int track, int sector)
+ {
+ DiabloDiskSector s = _sectors[cylinder, track, sector];
+
+ // For invalid / empty sectors, return an Empty sector.
+ if (s == null)
+ {
+ s = DiabloDiskSector.Empty;
+ }
+
+ return s;
+ }
+
+ public void SetSector(int cylinder, int track, int sector, DiabloDiskSector newSector)
+ {
+ _sectors[cylinder, track, sector] = newSector;
+ }
+
+ public DiabloDiskSector GetSector(int address)
+ {
+ if (address < 0 || address >= MaxAddress)
+ {
+ throw new InvalidOperationException("Disk address is out of range.");
+ }
+
+ // TODO: factor this logic out
+ int sector = address % _geometry.Sectors;
+ int track = (address / _geometry.Sectors) % _geometry.Tracks;
+ int cylinder = (address / (_geometry.Sectors * _geometry.Tracks));
+
+ return GetSector(cylinder, track, sector);
+ }
+
+ public void SetSector(int address, DiabloDiskSector newSector)
+ {
+ if (address < 0 || address >= MaxAddress)
+ {
+ throw new InvalidOperationException("Disk address is out of range.");
+ }
+
+ int sector = address % _geometry.Sectors;
+ int track = (address / _geometry.Sectors) % _geometry.Tracks;
+ int cylinder = (address / (_geometry.Sectors * _geometry.Tracks));
+
+ SetSector(cylinder, track, sector, newSector);
+ }
+
+ private byte[] SwapBytes(byte[] data)
+ {
+ byte[] swapped = new byte[data.Length];
+ for(int i=0;i
- /// Called by dispatcher to send incoming data destined for this protocol.
- ///
- ///
- public override void RecvData(PUP p)
- {
- throw new NotImplementedException();
- }
-
- public override void InitializeServerForChannel(BSPChannel channel)
- {
- // spwan new worker thread with new BSP channel
- Thread newThread = new Thread(new ParameterizedThreadStart(CopyDiskServerThread));
- newThread.Start(channel);
- }
-
- private void CopyDiskServerThread(object obj)
- {
- BSPChannel channel = (BSPChannel)obj;
-
- while(true)
- {
- // Retrieve length of this block (in bytes):
- int length = channel.ReadUShort() * 2;
-
- // Sanity check that length is a reasonable value.
- if (length > 2048)
- {
- // TODO: shut down channel
- throw new InvalidOperationException(String.Format("Insane block length ({0})", length));
- }
-
- // Retrieve type
- CopyDiskBlock blockType = (CopyDiskBlock)channel.ReadUShort();
-
- // Read rest of block
- byte[] data = new byte[length];
-
- channel.Read(ref data, data.Length - 4, 4);
-
- switch(blockType)
- {
- case CopyDiskBlock.Version:
- VersionBlock vbIn = (VersionBlock)Serializer.Deserialize(data, typeof(VersionBlock));
-
- Console.WriteLine("Copydisk client is version {0}, '{1}'", vbIn.Version, vbIn.Herald.ToString());
-
- // Send the response:
- VersionBlock vbOut = new VersionBlock(vbIn.Version, "IFS CopyDisk of 26-Jan-2016");
- channel.Send(Serializer.Serialize(vbOut));
- break;
-
- case CopyDiskBlock.Login:
-
- break;
-
- default:
- Console.WriteLine("Unhandled CopyDisk block {0}", blockType);
- break;
- }
- }
- }
- }
-}
diff --git a/PUP/DirectoryServices.cs b/PUP/DirectoryServices.cs
index 746009b..1049383 100644
--- a/PUP/DirectoryServices.cs
+++ b/PUP/DirectoryServices.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;
+using IFS.Logging;
namespace IFS
{
@@ -41,7 +42,7 @@ namespace IFS
// Load in hosts table from hosts file.
LoadHostTable();
- Logging.Log.Write(Logging.LogLevel.Normal, "Directory services initialized.");
+ Log.Write(LogComponent.DirectoryServices, "Directory services initialized.");
}
public string AddressLookup(HostAddress address)
@@ -142,8 +143,8 @@ namespace IFS
if (tokens.Length < 2)
{
// Log warning and continue.
- Logging.Log.Write(Logging.LogLevel.Warning,
- String.Format("hosts.txt line {0}: Invalid syntax.", lineNumber));
+ Log.Write(LogType.Warning, LogComponent.DirectoryServices,
+ "hosts.txt line {0}: Invalid syntax.", lineNumber);
continue;
}
@@ -152,8 +153,8 @@ namespace IFS
if (!tokens[0].EndsWith("#"))
{
// Log warning and continue.
- Logging.Log.Write(Logging.LogLevel.Warning,
- String.Format("hosts.txt line {0}: Improperly formed inter-network name '{1}'.", lineNumber, tokens[0]));
+ Log.Write(LogType.Warning, LogComponent.DirectoryServices,
+ "hosts.txt line {0}: Improperly formed inter-network name '{1}'.", lineNumber, tokens[0]);
continue;
}
@@ -173,8 +174,8 @@ namespace IFS
catch
{
// Log warning and continue.
- Logging.Log.Write(Logging.LogLevel.Warning,
- String.Format("hosts.txt line {0}: Invalid host number in inter-network address '{1}'.", lineNumber, tokens[0]));
+ Log.Write(LogType.Warning, LogComponent.DirectoryServices,
+ "hosts.txt line {0}: Invalid host number in inter-network address '{1}'.", lineNumber, tokens[0]);
continue;
}
@@ -189,8 +190,8 @@ namespace IFS
catch
{
// Log warning and continue.
- Logging.Log.Write(Logging.LogLevel.Warning,
- String.Format("hosts.txt line {0}: Invalid host or network number in inter-network address '{1}'.", lineNumber, tokens[0]));
+ Log.Write(LogType.Warning, LogComponent.DirectoryServices,
+ "hosts.txt line {0}: Invalid host or network number in inter-network address '{1}'.", lineNumber, tokens[0]);
continue;
}
@@ -198,8 +199,8 @@ namespace IFS
else
{
// Log warning and continue.
- Logging.Log.Write(Logging.LogLevel.Warning,
- String.Format("hosts.txt line {0}: Improperly formed inter-network name '{1}'.", lineNumber, tokens[0]));
+ Log.Write(LogType.Warning, LogComponent.DirectoryServices,
+ "hosts.txt line {0}: Improperly formed inter-network name '{1}'.", lineNumber, tokens[0]);
continue;
}
@@ -213,8 +214,8 @@ namespace IFS
if (_hostNameTable.ContainsKey(hostName))
{
// Duplicate name entry! Skip this line.
- Logging.Log.Write(Logging.LogLevel.Warning,
- String.Format("hosts.txt line {0}: Duplicate hostname '{1}'.", lineNumber, hostName));
+ Log.Write(LogType.Warning, LogComponent.DirectoryServices,
+ "hosts.txt line {0}: Duplicate hostname '{1}'.", lineNumber, hostName);
break;
}
@@ -239,8 +240,8 @@ namespace IFS
if (networkTable.ContainsKey(host.Host))
{
// Duplicate host entry! Skip this line.
- Logging.Log.Write(Logging.LogLevel.Warning,
- String.Format("hosts.txt line {0}: Duplicate host ID '{1}'.", lineNumber, host.Host));
+ Log.Write(LogType.Warning, LogComponent.DirectoryServices,
+ "hosts.txt line {0}: Duplicate host ID '{1}'.", lineNumber, host.Host);
break;
}
diff --git a/PUP/Disks/diag.dsk b/PUP/Disks/diag.dsk
new file mode 100644
index 0000000..0f0cbdb
Binary files /dev/null and b/PUP/Disks/diag.dsk differ
diff --git a/PUP/Disks/tdisk8.dsk b/PUP/Disks/tdisk8.dsk
new file mode 100644
index 0000000..5dbdeaa
Binary files /dev/null and b/PUP/Disks/tdisk8.dsk differ
diff --git a/PUP/Entrypoint.cs b/PUP/Entrypoint.cs
index 0f7199c..7499e46 100644
--- a/PUP/Entrypoint.cs
+++ b/PUP/Entrypoint.cs
@@ -1,4 +1,5 @@
-using IFS.Transport;
+using IFS.CopyDisk;
+using IFS.Transport;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -33,9 +34,7 @@ namespace IFS
foo oldFoo = (foo) Serializer.Deserialize(data, typeof(foo));
-
-
- Logging.Log.Level = Logging.LogLevel.All;
+
List ifaces = EthernetInterface.EnumerateDevices();
diff --git a/PUP/GatewayInformationProtocol.cs b/PUP/GatewayInformationProtocol.cs
index e7996e2..ce528a1 100644
--- a/PUP/GatewayInformationProtocol.cs
+++ b/PUP/GatewayInformationProtocol.cs
@@ -7,6 +7,14 @@ using System.Threading.Tasks;
namespace IFS
{
+ public struct GatewayInformation
+ {
+ public byte TargetNet;
+ public byte GatewayNet;
+ public byte GatewayHost;
+ public byte HopCount;
+ }
+
///
/// Gateway Information Protocol (see http://xeroxalto.computerhistory.org/_cd8_/pup/.gatewayinformation.press!1.pdf)
///
@@ -16,6 +24,7 @@ namespace IFS
{
// TODO:
// load host tables, etc.
+ // spin up thread that spits out a GatewayInformation PUP periodically.
}
///
@@ -26,14 +35,55 @@ namespace IFS
{
switch (p.Type)
{
-
+ case PupType.GatewayInformationRequest:
+ SendGatewayInformationResponse(p);
+ break;
default:
- Log.Write(LogLevel.UnhandledProtocol, String.Format("Unhandled Gateway protocol {0}", p.Type));
+ Log.Write(LogComponent.MiscServices, String.Format("Unhandled Gateway protocol {0}", p.Type));
break;
}
}
+ private void SendGatewayInformationResponse(PUP p)
+ {
+ //
+ // Pup Type: 201 (octal)
+ // Pup ID: same as in Request Pup
+ // Pup Contents: one or more groups of four bytes, each providing routing information for
+ // one network, as follows:
+ //
+ //
+ //
+ // In each group, the first byte specifies the target network number. If the gateway host is
+ // directly connected to that network, then the is zero and the and
+ // describe the gateway’s connection to the network.
+ // If the gateway host is not directly connected to the target network, then the second and
+ // third bytes give the network and host numbers of another gateway through which the
+ // responding gateway routes Pups to that network, and the fourth byte gives the hop count,
+ // i.e., the number of additional gateways (not including itself) through which the responding
+ // gateway believes a Pup must pass to reach the specified network. A hop count greater than
+ // the constant maxHops (presently 15) signifies that the target network is believed to be
+ // inaccessible.
+ //
+
+ // Right now, we know of only one network (our own) and we are directly connected to it.
+ //
+ GatewayInformation info = new GatewayInformation();
+ info.TargetNet = DirectoryServices.Instance.LocalNetwork;
+ info.GatewayNet = DirectoryServices.Instance.LocalNetwork;
+ info.GatewayHost = DirectoryServices.Instance.LocalNetwork;
+ info.HopCount = 0;
+
+ PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
+
+ // Response must contain our network number; this is used to tell clients what network they're on if they don't already know.
+ PUPPort remotePort = new PUPPort(DirectoryServices.Instance.LocalNetwork, p.SourcePort.Host, p.SourcePort.Socket);
+
+ PUP response = new PUP(PupType.GatewayInformationResponse, p.ID, remotePort, localPort, Serializer.Serialize(info));
+
+ PUPProtocolDispatcher.Instance.SendPup(response);
+ }
}
}
diff --git a/PUP/IFS.csproj b/PUP/IFS.csproj
index 5220e86..f9fb74f 100644
--- a/PUP/IFS.csproj
+++ b/PUP/IFS.csproj
@@ -75,7 +75,8 @@
-
+
+
@@ -95,6 +96,15 @@
PreserveNewest
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+