mirror of
https://github.com/livingcomputermuseum/IFS.git
synced 2026-03-02 18:04:24 +00:00
Implemented CopyDisk, some cleanup, improved logging.
This commit is contained in:
@@ -111,12 +111,14 @@ namespace IFS
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
|
||||
@@ -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<byte>(65536);
|
||||
|
||||
_outputAckEvent = new AutoResetEvent(false);
|
||||
_outputQueue = new Queue<byte>(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...
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Appends incoming client data into the input queue (called from BSPManager to place new PUP data into the BSP stream)
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends data, with immediate flush.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void Send(byte[] data)
|
||||
{
|
||||
Send(data, true /* flush */);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public void Send(byte[] data)
|
||||
/// <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, 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the client sends an ACK
|
||||
/// </summary>
|
||||
/// <param name="ackPUP"></param>
|
||||
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<byte> _inputQueue;
|
||||
private Queue<byte> _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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when BSP-based protocols receive data.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// 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)
|
||||
/// </returns>
|
||||
/// <param name="p"></param>
|
||||
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;
|
||||
|
||||
|
||||
573
PUP/CopyDisk/CopyDiskServer.cs
Normal file
573
PUP/CopyDisk/CopyDiskServer.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Called by dispatcher to send incoming data destined for this protocol.
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a relative path to the directory that holds the disk images.
|
||||
/// </summary>
|
||||
/// <param name="packName"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
242
PUP/CopyDisk/DiabloPack.cs
Normal file
242
PUP/CopyDisk/DiabloPack.cs
Normal file
@@ -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]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
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<data.Length;i+=2)
|
||||
{
|
||||
swapped[i] = data[i + 1];
|
||||
swapped[i + 1] = data[i];
|
||||
}
|
||||
|
||||
return swapped;
|
||||
}
|
||||
|
||||
private string _packName; // The file from whence the data came originally
|
||||
private DiabloDiskSector[,,] _sectors;
|
||||
private DiabloDiskType _diskType;
|
||||
private DiskGeometry _geometry;
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
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,
|
||||
}
|
||||
|
||||
struct VersionBlock
|
||||
{
|
||||
public VersionBlock(ushort version, string herald)
|
||||
{
|
||||
Version = version;
|
||||
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)CopyDiskBlock.Version;
|
||||
}
|
||||
|
||||
public ushort Length;
|
||||
public ushort Command;
|
||||
public ushort Version;
|
||||
public BCPLString Herald;
|
||||
}
|
||||
|
||||
public class CopyDiskServer : BSPProtocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Called by dispatcher to send incoming data destined for this protocol.
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
BIN
PUP/Disks/diag.dsk
Normal file
BIN
PUP/Disks/diag.dsk
Normal file
Binary file not shown.
BIN
PUP/Disks/tdisk8.dsk
Normal file
BIN
PUP/Disks/tdisk8.dsk
Normal file
Binary file not shown.
@@ -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<EthernetInterface> ifaces = EthernetInterface.EnumerateDevices();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gateway Information Protocol (see http://xeroxalto.computerhistory.org/_cd8_/pup/.gatewayinformation.press!1.pdf)
|
||||
/// </summary>
|
||||
@@ -16,6 +24,7 @@ namespace IFS
|
||||
{
|
||||
// TODO:
|
||||
// load host tables, etc.
|
||||
// spin up thread that spits out a GatewayInformation PUP periodically.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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:
|
||||
//
|
||||
// <target-net> <gateway-net> <gateway-host> <hop-count>
|
||||
//
|
||||
// In each group, the first byte specifies the target network number. If the gateway host is
|
||||
// directly connected to that network, then the <hop-count> is zero and the <gateway-net> and
|
||||
// <gateway-host> 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@
|
||||
<Compile Include="BCPLString.cs" />
|
||||
<Compile Include="BreathOfLife.cs" />
|
||||
<Compile Include="BSPManager.cs" />
|
||||
<Compile Include="CopyDiskServer.cs" />
|
||||
<Compile Include="CopyDisk\CopyDiskServer.cs" />
|
||||
<Compile Include="CopyDisk\DiabloPack.cs" />
|
||||
<Compile Include="DirectoryServices.cs" />
|
||||
<Compile Include="EchoProtocol.cs" />
|
||||
<Compile Include="Entrypoint.cs" />
|
||||
@@ -95,6 +96,15 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<None Include="Disks\diag.dsk">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Disks\tdisk8.dsk">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
@@ -1,50 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
#define LOGGING_ENABLED
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace IFS.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies a component to specify logging for
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LogLevel
|
||||
public enum LogComponent
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
Warning = 2,
|
||||
Error = 4,
|
||||
DroppedPacket = 8,
|
||||
InvalidPacket = 0x10,
|
||||
UnhandledProtocol = 0x20,
|
||||
HandledProtocol = 0x40,
|
||||
DuplicateHostNumber = 0x80,
|
||||
BSPLostPacket = 0x100,
|
||||
|
||||
All = 0x7fffffff,
|
||||
Ethernet = 0x1,
|
||||
RTP = 0x2,
|
||||
BSP = 0x4,
|
||||
MiscServices = 0x8,
|
||||
CopyDisk = 0x10,
|
||||
DirectoryServices = 0x20,
|
||||
PUP = 0x40,
|
||||
|
||||
All = 0x7fffffff
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type (or severity) of a given log message
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LogType
|
||||
{
|
||||
None = 0,
|
||||
Normal = 0x1,
|
||||
Warning = 0x2,
|
||||
Error = 0x4,
|
||||
Verbose = 0x8,
|
||||
All = 0x7fffffff
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides basic functionality for logging messages of all types.
|
||||
/// </summary>
|
||||
public static class Log
|
||||
{
|
||||
static Log()
|
||||
{
|
||||
_level = LogLevel.All;
|
||||
// TODO: make configurable
|
||||
_components = LogComponent.All;
|
||||
_type = LogType.Normal;
|
||||
|
||||
//_logStream = new StreamWriter("log.txt");
|
||||
}
|
||||
|
||||
public static LogLevel Level
|
||||
public static LogComponent LogComponents
|
||||
{
|
||||
get { return _level; }
|
||||
set { _level = value; }
|
||||
get { return _components; }
|
||||
set { _components = value; }
|
||||
}
|
||||
|
||||
public static void Write(LogLevel level, string message)
|
||||
#if LOGGING_ENABLED
|
||||
/// <summary>
|
||||
/// Logs a message without specifying type/severity for terseness;
|
||||
/// will not log if Type has been set to None.
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void Write(LogComponent component, string message, params object[] args)
|
||||
{
|
||||
if ((level & _level) != 0)
|
||||
Write(LogType.Normal, component, message, args);
|
||||
}
|
||||
|
||||
public static void Write(LogType type, LogComponent component, string message, params object[] args)
|
||||
{
|
||||
if ((_type & type) != 0 &&
|
||||
(_components & component) != 0)
|
||||
{
|
||||
//
|
||||
// My log has something to tell you...
|
||||
Console.WriteLine("{0}: {1} - {2}", DateTime.Now, level, message);
|
||||
// TODO: color based on type, etc.
|
||||
Console.WriteLine(component.ToString() + ": " + message, args);
|
||||
|
||||
if (_logStream != null)
|
||||
{
|
||||
_logStream.WriteLine(component.ToString() + ": " + message, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
public static void Write(LogComponent component, string message, params object[] args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static LogLevel _level;
|
||||
public static void Write(LogType type, LogComponent component, string message, params object[] args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private static LogComponent _components;
|
||||
private static LogType _type;
|
||||
private static StreamWriter _logStream;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace IFS
|
||||
/// <param name="p"></param>
|
||||
public override void RecvData(PUP p)
|
||||
{
|
||||
Log.Write(LogLevel.HandledProtocol, String.Format("Misc. protocol request is for {0}.", p.Type));
|
||||
Log.Write(LogType.Verbose, LogComponent.MiscServices, String.Format("Misc. protocol request is for {0}.", p.Type));
|
||||
switch (p.Type)
|
||||
{
|
||||
case PupType.StringTimeRequest:
|
||||
@@ -79,7 +79,7 @@ namespace IFS
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.Write(LogLevel.UnhandledProtocol, String.Format("Unhandled misc. protocol {0}", p.Type));
|
||||
Log.Write(LogComponent.MiscServices, String.Format("Unhandled misc. protocol {0}", p.Type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
21
PUP/PUP.cs
21
PUP/PUP.cs
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using IFS.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -8,6 +9,9 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
/// <summary>
|
||||
/// All of the different types of PUPs.
|
||||
/// </summary>
|
||||
public enum PupType
|
||||
{
|
||||
// Basic types
|
||||
@@ -128,6 +132,11 @@ namespace IFS
|
||||
return a;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("Net {0} Host {1} Socket {2}", Network, Host, Socket);
|
||||
}
|
||||
|
||||
public byte Network;
|
||||
public byte Host;
|
||||
public UInt32 Socket;
|
||||
@@ -278,7 +287,7 @@ namespace IFS
|
||||
if (Checksum != 0xffff && cChecksum != Checksum)
|
||||
{
|
||||
// TODO: determine what to do with packets that are corrupted.
|
||||
Logging.Log.Write(Logging.LogLevel.Warning, String.Format("PUP checksum is invalid. (got {0:x}, expected {1:x})", Checksum, cChecksum));
|
||||
Log.Write(LogType.Warning, LogComponent.PUP, "PUP checksum is invalid. (got {0:x}, expected {1:x})", Checksum, cChecksum);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -332,11 +341,9 @@ namespace IFS
|
||||
|
||||
private byte[] _rawData;
|
||||
|
||||
private const int MAX_PUP_SIZE = 532;
|
||||
private const int PUP_HEADER_SIZE = 20;
|
||||
private const int PUP_CHECKSUM_SIZE = 2;
|
||||
|
||||
|
||||
public readonly static int MAX_PUP_SIZE = 532;
|
||||
public readonly static int PUP_HEADER_SIZE = 20;
|
||||
public readonly static int PUP_CHECKSUM_SIZE = 2;
|
||||
}
|
||||
|
||||
public static class Helpers
|
||||
|
||||
@@ -57,12 +57,25 @@ namespace IFS
|
||||
}
|
||||
|
||||
public void SendPup(PUP p)
|
||||
{
|
||||
{
|
||||
_pupPacketInterface.Send(p);
|
||||
}
|
||||
|
||||
private void OnPupReceived(PUP pup)
|
||||
{
|
||||
//
|
||||
// Filter out packets not destined for us.
|
||||
// Even though we use pcap in non-promiscuous mode, if
|
||||
// something else has set the interface to promiscuous mode, that
|
||||
// setting may be overridden.
|
||||
//
|
||||
if (pup.DestinationPort.Host != 0 && // Not broadcast.
|
||||
pup.DestinationPort.Host != DirectoryServices.Instance.LocalHost) // Not our address.
|
||||
{
|
||||
// Do nothing with this PUP.
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Forward PUP on to registered endpoints.
|
||||
//
|
||||
@@ -72,14 +85,14 @@ namespace IFS
|
||||
|
||||
if (entry.ConnectionType == ConnectionType.Connectionless)
|
||||
{
|
||||
Log.Write(LogLevel.HandledProtocol, String.Format("Dispatching PUP to {0} handler.", entry.FriendlyName));
|
||||
Log.Write(LogType.Verbose, LogComponent.PUP, "Dispatching PUP (source {0}, dest {1}) to {2} handler.", pup.SourcePort, pup.DestinationPort, entry.FriendlyName);
|
||||
// Connectionless; just pass the PUP directly to the protocol
|
||||
entry.ProtocolImplementation.RecvData(pup);
|
||||
}
|
||||
else
|
||||
{
|
||||
// RTP / BSP protocol. Pass this to the BSP handler to set up a channel.
|
||||
Log.Write(LogLevel.HandledProtocol, String.Format("Dispatching PUP to BSP protocol for {0}.", entry.FriendlyName));
|
||||
Log.Write(LogType.Verbose, LogComponent.PUP, "Dispatching PUP (source {0}, dest {1}) to BSP protocol for {0}.", pup.SourcePort, pup.DestinationPort, entry.FriendlyName);
|
||||
//entry.ProtocolImplementation.RecvData(pup);
|
||||
|
||||
BSPManager.EstablishRendezvous(pup, (BSPProtocol)entry.ProtocolImplementation);
|
||||
@@ -93,7 +106,7 @@ namespace IFS
|
||||
else
|
||||
{
|
||||
// Not a protocol we handle; log it.
|
||||
Log.Write(LogLevel.UnhandledProtocol | LogLevel.DroppedPacket, String.Format("Unhandled PUP protocol, socket {0}, dropped packet.", pup.DestinationPort.Socket));
|
||||
Log.Write(LogType.Normal, LogComponent.PUP, "Unhandled PUP protocol, socket {0}, dropped packet.", pup.DestinationPort.Socket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,34 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Custom attribute allowing specification of Word (16-bit) alignment
|
||||
/// of a given field. (Alignment is byte-oriented by default).
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Field)]
|
||||
public class WordAligned : System.Attribute
|
||||
{
|
||||
public WordAligned()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom attribute allowing static specification of size (in elements) of array
|
||||
/// fields. Used during deserialization.
|
||||
/// </summary>
|
||||
public class ArrayLength : System.Attribute
|
||||
{
|
||||
public ArrayLength(int i)
|
||||
{
|
||||
Length = i;
|
||||
}
|
||||
|
||||
public int Length;
|
||||
}
|
||||
|
||||
public static class Serializer
|
||||
{
|
||||
|
||||
@@ -27,6 +55,7 @@ namespace IFS
|
||||
// - int
|
||||
// - uint
|
||||
// - BCPLString
|
||||
// - byte[]
|
||||
//
|
||||
// Struct fields are serialized in the order they are defined in the struct. Only Public instance fields are considered.
|
||||
// If any unsupported fields are present in the considered field types, an exception will be thrown.
|
||||
@@ -38,6 +67,17 @@ namespace IFS
|
||||
|
||||
for (int i = 0; i < info.Length; i++)
|
||||
{
|
||||
// Check alignment of the field; if word aligned we need to ensure proper positioning of the stream.
|
||||
if (IsWordAligned(info[i]))
|
||||
{
|
||||
if ((ms.Position % 2) != 0)
|
||||
{
|
||||
// Eat up a padding byte
|
||||
ms.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
// Now read in the appropriate type.
|
||||
switch (info[i].FieldType.Name)
|
||||
{
|
||||
case "Byte":
|
||||
@@ -74,6 +114,23 @@ namespace IFS
|
||||
}
|
||||
break;
|
||||
|
||||
case "Byte[]":
|
||||
{
|
||||
// The field MUST be annotated with a length value.
|
||||
int length = GetArrayLength(info[i]);
|
||||
|
||||
if (length == -1)
|
||||
{
|
||||
throw new InvalidOperationException("Byte arrays must be annotated with an ArrayLength attribute to be deserialized into.");
|
||||
}
|
||||
|
||||
byte[] value = new byte[length];
|
||||
ms.Read(value, 0, value.Length);
|
||||
|
||||
info[i].SetValue(o, value);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format("Type {0} is unsupported for deserialization.", info[i].FieldType.Name));
|
||||
}
|
||||
@@ -107,7 +164,17 @@ namespace IFS
|
||||
|
||||
for(int i=0;i<info.Length;i++)
|
||||
{
|
||||
switch(info[i].FieldType.Name)
|
||||
// Check alignment of the field; if word aligned we need to pad as necessary to align the next field.
|
||||
if (IsWordAligned(info[i]))
|
||||
{
|
||||
if ((ms.Position % 2) != 0)
|
||||
{
|
||||
// Write a padding byte
|
||||
ms.WriteByte(0);
|
||||
}
|
||||
}
|
||||
|
||||
switch (info[i].FieldType.Name)
|
||||
{
|
||||
case "Byte":
|
||||
ms.WriteByte((byte)info[i].GetValue(o));
|
||||
@@ -149,6 +216,24 @@ namespace IFS
|
||||
}
|
||||
break;
|
||||
|
||||
case "Byte[]":
|
||||
{
|
||||
byte[] value = (byte[])(info[i].GetValue(o));
|
||||
|
||||
// Sanity check length of array vs. the specified annotation (if any -- not required
|
||||
// for serialization)
|
||||
int length = GetArrayLength(info[i]);
|
||||
|
||||
if (length > 0 && length != value.Length)
|
||||
{
|
||||
throw new InvalidOperationException("Array size does not match the size required by the ArraySize annotation.");
|
||||
}
|
||||
|
||||
ms.Write(value, 0, value.Length);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format("Type {0} is unsupported for serialization.", info[i].FieldType.Name));
|
||||
}
|
||||
@@ -156,5 +241,42 @@ namespace IFS
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private static void SwapBytes(byte[] data)
|
||||
{
|
||||
for (int i = 0; i < data.Length; i += 2)
|
||||
{
|
||||
byte t = data[i];
|
||||
data[i] = data[i + 1];
|
||||
data[i + 1] = t;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool IsWordAligned(FieldInfo field)
|
||||
{
|
||||
foreach(CustomAttributeData attribute in field.CustomAttributes)
|
||||
{
|
||||
if (attribute.AttributeType == typeof(WordAligned))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int GetArrayLength(FieldInfo field)
|
||||
{
|
||||
foreach (Attribute attribute in System.Attribute.GetCustomAttributes(field))
|
||||
{
|
||||
if (attribute is ArrayLength)
|
||||
{
|
||||
return ((ArrayLength)attribute).Length;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace IFS.Transport
|
||||
else
|
||||
{
|
||||
// Log error, this should not happen.
|
||||
Log.Write(LogLevel.Error, String.Format("PUP destination address {0} is unknown.", p.DestinationPort.Host));
|
||||
Log.Write(LogType.Error, LogComponent.Ethernet, String.Format("PUP destination address {0} is unknown.", p.DestinationPort.Host));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,12 +163,12 @@ namespace IFS.Transport
|
||||
else
|
||||
{
|
||||
// Not for our network.
|
||||
Log.Write(LogLevel.DroppedPacket, String.Format("PUP is for network {0}, dropping.", pup.DestinationPort.Network));
|
||||
Log.Write(LogType.Verbose, LogComponent.Ethernet, "PUP is for network {0}, dropping.", pup.DestinationPort.Network);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Write(LogLevel.DroppedPacket, String.Format("3mbit packet is not a PUP, dropping"));
|
||||
Log.Write(LogType.Warning, LogComponent.Ethernet, "3mbit packet is not a PUP, dropping");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -229,11 +229,11 @@ namespace IFS.Transport
|
||||
//
|
||||
if (_pupToEthernetMap[p.SourcePort.Host] != e.Ethernet.Source)
|
||||
{
|
||||
Log.Write(LogLevel.DuplicateHostNumber,
|
||||
String.Format("Duplicate host ID {0} for MAC {1} (currently mapped to MAC {2})",
|
||||
p.SourcePort.Host,
|
||||
e.Ethernet.Source,
|
||||
_pupToEthernetMap[p.SourcePort.Host]));
|
||||
Log.Write(LogType.Error, LogComponent.Ethernet,
|
||||
"Duplicate host ID {0} for MAC {1} (currently mapped to MAC {2})",
|
||||
p.SourcePort.Host,
|
||||
e.Ethernet.Source,
|
||||
_pupToEthernetMap[p.SourcePort.Host]);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user