mirror of
https://github.com/livingcomputermuseum/IFS.git
synced 2026-02-04 23:45:17 +00:00
Progress made; updated to work with Contralto, initial stubs for CopyDisk. Misc services work.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -19,7 +20,7 @@ namespace IFS
|
||||
throw new InvalidOperationException("Max length for a BCPL string is 255 characters.");
|
||||
}
|
||||
|
||||
_string = new byte[_string.Length];
|
||||
_string = new byte[s.Length];
|
||||
|
||||
// We simply take the low 8-bits of each Unicode character and stuff it into the
|
||||
// byte array. This works fine for the ASCII subset of Unicode but obviously
|
||||
@@ -53,6 +54,45 @@ namespace IFS
|
||||
Array.Copy(rawData, 1, _string, 0, rawData.Length - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a new BCPL string from the raw representation at the given position in the array
|
||||
/// </summary>
|
||||
/// <param name="rawData"></param>
|
||||
public BCPLString(byte[] rawData, int offset)
|
||||
{
|
||||
int length = rawData[offset];
|
||||
|
||||
// Sanity check that BCPL length fits within specified array
|
||||
if (length > rawData.Length - offset)
|
||||
{
|
||||
throw new InvalidOperationException("BCPL length data is invalid.");
|
||||
}
|
||||
|
||||
_string = new byte[length];
|
||||
Array.Copy(rawData, offset + 1, _string, 0, length);
|
||||
}
|
||||
|
||||
public BCPLString(BSPChannel channel)
|
||||
{
|
||||
byte length = channel.ReadByte();
|
||||
_string = new byte[length];
|
||||
|
||||
channel.Read(ref _string, length);
|
||||
}
|
||||
|
||||
public BCPLString(Stream s)
|
||||
{
|
||||
byte length = (byte)s.ReadByte();
|
||||
_string = new byte[length];
|
||||
|
||||
s.Read(_string, 0, length);
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return _string.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a native representation of the BCPL string
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using IFS.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -9,6 +10,10 @@ using System.Threading.Tasks;
|
||||
namespace IFS
|
||||
{
|
||||
|
||||
public abstract class BSPProtocol : PUPProtocolBase
|
||||
{
|
||||
public abstract void InitializeServerForChannel(BSPChannel channel);
|
||||
}
|
||||
|
||||
public enum BSPState
|
||||
{
|
||||
@@ -18,7 +23,7 @@ namespace IFS
|
||||
|
||||
public class BSPChannel
|
||||
{
|
||||
public BSPChannel(PUP rfcPup, UInt32 socketID)
|
||||
public BSPChannel(PUP rfcPup, UInt32 socketID, BSPProtocol protocolHandler)
|
||||
{
|
||||
_inputLock = new ReaderWriterLockSlim();
|
||||
_outputLock = new ReaderWriterLockSlim();
|
||||
@@ -27,6 +32,10 @@ namespace IFS
|
||||
|
||||
_inputQueue = new Queue<byte>(65536);
|
||||
|
||||
_outputAckEvent = new AutoResetEvent(false);
|
||||
|
||||
_protocolHandler = protocolHandler;
|
||||
|
||||
// TODO: init IDs, etc. based on RFC PUP
|
||||
_start_pos = _recv_pos = _send_pos = rfcPup.ID;
|
||||
|
||||
@@ -35,10 +44,16 @@ namespace IFS
|
||||
// in the RFC pup.
|
||||
_clientConnectionPort = new PUPPort(rfcPup.Contents, 0);
|
||||
|
||||
//
|
||||
if (_clientConnectionPort.Network == 0)
|
||||
{
|
||||
_clientConnectionPort.Network = DirectoryServices.Instance.LocalNetwork;
|
||||
}
|
||||
|
||||
// We create our connection port using a unique socket address.
|
||||
_serverConnectionPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, socketID);
|
||||
}
|
||||
|
||||
|
||||
public PUPPort ClientPort
|
||||
{
|
||||
get { return _clientConnectionPort; }
|
||||
@@ -49,11 +64,25 @@ namespace IFS
|
||||
get { return _serverConnectionPort; }
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the channel (i.e. from the client). Will block if not all the requested data is available.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int Read(ref byte[] data, int count)
|
||||
{
|
||||
return Read(ref data, count, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the channel (i.e. from the client). Will block if not all the requested data is available.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int Read(ref byte[] data, int count, int offset)
|
||||
{
|
||||
// sanity check
|
||||
if (count > data.Length)
|
||||
@@ -75,7 +104,7 @@ namespace IFS
|
||||
// TODO: this is a really inefficient thing.
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
data[i] = _inputQueue.Dequeue();
|
||||
data[i + offset] = _inputQueue.Dequeue();
|
||||
}
|
||||
|
||||
_inputLock.ExitWriteLock();
|
||||
@@ -96,8 +125,33 @@ namespace IFS
|
||||
return read;
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
// TODO: optimize this
|
||||
byte[] data = new byte[1];
|
||||
|
||||
Read(ref data, 1);
|
||||
|
||||
return data[0];
|
||||
}
|
||||
|
||||
public ushort ReadUShort()
|
||||
{
|
||||
// TODO: optimize this
|
||||
byte[] data = new byte[2];
|
||||
|
||||
Read(ref data, 2);
|
||||
|
||||
return Helpers.ReadUShort(data, 0);
|
||||
}
|
||||
|
||||
public BCPLString ReadBCPLString()
|
||||
{
|
||||
return new BCPLString(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends data into the input queue (called from BSPManager to place new PUP data into the BSP stream)
|
||||
/// 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)
|
||||
{
|
||||
@@ -117,7 +171,8 @@ namespace IFS
|
||||
// Current behavior is to simply drop all incoming PUPs (and not ACK them) until they are re-sent to us
|
||||
// (in which case the above sanity check will pass). According to spec, AData requests that are not ACKed
|
||||
// must eventually be resent. This is far simpler than accepting out-of-order data and keeping track
|
||||
// of where it goes in the queue.
|
||||
// of where it goes in the queue, though less efficient.
|
||||
_inputLock.ExitUpgradeableReadLock();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,6 +184,8 @@ namespace IFS
|
||||
for (int i = 0; i < dataPUP.Contents.Length; i++)
|
||||
{
|
||||
_inputQueue.Enqueue(dataPUP.Contents[i]);
|
||||
|
||||
//Console.Write("{0:x} ({1}), ", dataPUP.Contents[i], (char)dataPUP.Contents[i]);
|
||||
}
|
||||
|
||||
_recv_pos += (UInt32)dataPUP.Contents.Length;
|
||||
@@ -139,6 +196,7 @@ namespace IFS
|
||||
|
||||
_inputWriteEvent.Set();
|
||||
|
||||
// If the client wants an ACK, send it now.
|
||||
if ((PupType)dataPUP.Type == PupType.AData)
|
||||
{
|
||||
SendAck();
|
||||
@@ -147,16 +205,23 @@ namespace IFS
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends data to the channel (i.e. to the client). Will block if an ACK is requested.
|
||||
/// 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)
|
||||
{
|
||||
// Write data to the output stream
|
||||
// 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);
|
||||
|
||||
PUPProtocolDispatcher.Instance.SendPup(dataPup);
|
||||
|
||||
_send_pos += (uint)data.Length;
|
||||
|
||||
// Await an ack for the PUP we just sent
|
||||
_outputAckEvent.WaitOne(); // TODO: timeout and fail
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,6 +232,15 @@ namespace IFS
|
||||
{
|
||||
// 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));
|
||||
}
|
||||
|
||||
|
||||
// Let any waiting threads continue
|
||||
_outputAckEvent.Set();
|
||||
@@ -186,15 +260,18 @@ namespace IFS
|
||||
// Events for:
|
||||
// Abort, End, Mark, Interrupt (from client)
|
||||
// Repositioning (due to lost packets) (perhaps not necessary)
|
||||
// to allow protocols consuming BSP streams to be alerted when things happen.
|
||||
//
|
||||
|
||||
private void SendAck()
|
||||
{
|
||||
PUP ackPup = new PUP(PupType.Ack, _recv_pos, _clientConnectionPort, _serverConnectionPort);
|
||||
|
||||
PUPProtocolDispatcher.Instance.SendPup(ackPup);
|
||||
}
|
||||
|
||||
private BSPProtocol _protocolHandler;
|
||||
|
||||
private BSPState _state;
|
||||
private UInt32 _recv_pos;
|
||||
private UInt32 _send_pos;
|
||||
private UInt32 _start_pos;
|
||||
@@ -235,8 +312,39 @@ namespace IFS
|
||||
// a unique ID. (Well, until we wrap around...)
|
||||
//
|
||||
_nextSocketID = _startingSocketID;
|
||||
|
||||
_activeChannels = new Dictionary<uint, BSPChannel>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a PUP comes in on a known BSP socket
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
public static void EstablishRendezvous(PUP p, BSPProtocol protocolHandler)
|
||||
{
|
||||
if (p.Type != PupType.RFC)
|
||||
{
|
||||
Log.Write(LogLevel.Error, String.Format("Expected RFC pup, got {0}", p.Type));
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 socketID = GetNextSocketID();
|
||||
BSPChannel newChannel = new BSPChannel(p, socketID, protocolHandler);
|
||||
_activeChannels.Add(socketID, newChannel);
|
||||
|
||||
//
|
||||
// Initialize the server for this protocol.
|
||||
protocolHandler.InitializeServerForChannel(newChannel);
|
||||
|
||||
// Send RFC response to complete the rendezvous.
|
||||
|
||||
// Modify the destination port to specify our network
|
||||
PUPPort sourcePort = p.DestinationPort;
|
||||
sourcePort.Network = DirectoryServices.Instance.LocalNetwork;
|
||||
PUP rfcResponse = new PUP(PupType.RFC, p.ID, newChannel.ClientPort, sourcePort, newChannel.ServerPort.ToArray());
|
||||
|
||||
PUPProtocolDispatcher.Instance.SendPup(rfcResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when BSP-based protocols receive data.
|
||||
@@ -246,82 +354,80 @@ namespace IFS
|
||||
/// a new BSPChannel if one has been created based on the PUP (new RFC)
|
||||
/// </returns>
|
||||
/// <param name="p"></param>
|
||||
public static BSPChannel RecvData(PUP p)
|
||||
{
|
||||
PupType type = (PupType)p.Type;
|
||||
public static void RecvData(PUP p)
|
||||
{
|
||||
BSPChannel channel = FindChannelForPup(p);
|
||||
|
||||
switch (type)
|
||||
if (channel == null)
|
||||
{
|
||||
Log.Write(LogLevel.Error, "Received BSP PUP on an unconnected socket, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (p.Type)
|
||||
{
|
||||
case PupType.RFC:
|
||||
{
|
||||
BSPChannel newChannel = new BSPChannel(p, GetNextSocketID());
|
||||
_activeChannels.Add(newChannel.ServerPort.Socket, newChannel);
|
||||
|
||||
// Send RFC response to complete the rendezvous.
|
||||
PUP rfcResponse = new PUP(PupType.RFC, p.ID, newChannel.ClientPort, newChannel.ServerPort, newChannel.ServerPort.ToArray());
|
||||
|
||||
Dispatcher.Instance.SendPup(rfcResponse);
|
||||
|
||||
return newChannel;
|
||||
}
|
||||
Log.Write(LogLevel.Error, "Received RFC on established channel, ignoring.");
|
||||
break;
|
||||
|
||||
case PupType.Data:
|
||||
case PupType.AData:
|
||||
{
|
||||
BSPChannel channel = FindChannelForPup(p);
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
channel.WriteQueue(p);
|
||||
}
|
||||
{
|
||||
channel.WriteQueue(p);
|
||||
}
|
||||
break;
|
||||
|
||||
case PupType.Ack:
|
||||
{
|
||||
BSPChannel channel = FindChannelForPup(p);
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
channel.Ack(p);
|
||||
}
|
||||
{
|
||||
channel.Ack(p);
|
||||
}
|
||||
break;
|
||||
|
||||
case PupType.End:
|
||||
{
|
||||
BSPChannel channel = FindChannelForPup(p);
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
//channel.EndReply();
|
||||
}
|
||||
{
|
||||
//channel.EndReply();
|
||||
}
|
||||
break;
|
||||
|
||||
case PupType.Abort:
|
||||
{
|
||||
// TODO: tear down the channel
|
||||
DestroyChannel(channel);
|
||||
|
||||
string abortMessage = Helpers.ArrayToString(p.Contents);
|
||||
|
||||
Log.Write(LogLevel.Warning, String.Format("BSP aborted, message: '{0}'", abortMessage));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException(String.Format("Unhandled BSP PUP type {0}.", type));
|
||||
throw new NotImplementedException(String.Format("Unhandled BSP PUP type {0}.", p.Type));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
public static bool ChannelExistsForSocket(PUP p)
|
||||
{
|
||||
return FindChannelForPup(p) != null;
|
||||
}
|
||||
|
||||
public static void DestroyChannel(BSPChannel channel)
|
||||
{
|
||||
|
||||
channel.Destroy();
|
||||
|
||||
_activeChannels.Remove(channel.ServerPort.Socket);
|
||||
}
|
||||
|
||||
private static BSPChannel FindChannelForPup(PUP p)
|
||||
{
|
||||
return null;
|
||||
if (_activeChannels.ContainsKey(p.DestinationPort.Socket))
|
||||
{
|
||||
return _activeChannels[p.DestinationPort.Socket];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static UInt32 GetNextSocketID()
|
||||
|
||||
23
PUP/Conf/hosts.txt
Normal file
23
PUP/Conf/hosts.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
# hosts.txt:
|
||||
#
|
||||
# This is similar in nearly every way to a typical UNIX 'hosts' file except that
|
||||
# Xerox Inter-Network Name Expressions (i.e. network#host#) are used instead of IP addresses.
|
||||
#
|
||||
# Format for Inter-Network name expressions is either:
|
||||
# network#host# (to specify hosts on another network)
|
||||
# or
|
||||
# host# (to specify hosts on the same network as this IFS server)
|
||||
#
|
||||
# all numbers are in octal.
|
||||
#
|
||||
|
||||
1# ifs
|
||||
42# Muffin
|
||||
43# Pumpkin
|
||||
|
||||
43# Duplicate
|
||||
45# Pumpkin
|
||||
|
||||
# to test
|
||||
5#177# NotHere
|
||||
|
||||
@@ -2,11 +2,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
public class CopyDiskServer : PUPProtocolBase
|
||||
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.
|
||||
@@ -14,11 +49,60 @@ namespace IFS
|
||||
/// <param name="p"></param>
|
||||
public override void RecvData(PUP p)
|
||||
{
|
||||
BSPChannel newChannel = BSPManager.RecvData(p);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
if (newChannel != null)
|
||||
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)
|
||||
{
|
||||
// spwan new worker thread with new BSP channel
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -17,6 +18,11 @@ namespace IFS
|
||||
|
||||
public byte Network;
|
||||
public byte Host;
|
||||
|
||||
/// <summary>
|
||||
/// Non-existent address
|
||||
/// </summary>
|
||||
public static HostAddress Empty = new HostAddress(0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,19 +36,50 @@ namespace IFS
|
||||
// Get our host address; for now just hardcode it.
|
||||
// TODO: need to define config files, etc.
|
||||
|
||||
_localHost = new HostAddress(1, 34);
|
||||
_localHost = new HostAddress(1, 1);
|
||||
|
||||
// Load in hosts table from hosts file.
|
||||
LoadHostTable();
|
||||
|
||||
Logging.Log.Write(Logging.LogLevel.Normal, "Directory services initialized.");
|
||||
}
|
||||
|
||||
public string AddressLookup(HostAddress address)
|
||||
{
|
||||
// TODO: actually look things up
|
||||
return "Alto";
|
||||
//
|
||||
// First look up the network. If specified network is Zero,
|
||||
// we will assume our network number.
|
||||
//
|
||||
byte network = address.Network;
|
||||
|
||||
if (network == 0)
|
||||
{
|
||||
network = _localHost.Network;
|
||||
}
|
||||
|
||||
if (_hostAddressTable.ContainsKey(network))
|
||||
{
|
||||
//
|
||||
// We have entries for this network, see if the host is specified.
|
||||
//
|
||||
if (_hostAddressTable[network].ContainsKey(address.Host))
|
||||
{
|
||||
return _hostAddressTable[network][address.Host];
|
||||
}
|
||||
}
|
||||
|
||||
// Not found.
|
||||
return null;
|
||||
}
|
||||
|
||||
public HostAddress NameLookup(string hostName)
|
||||
{
|
||||
// TODO: actually look things up
|
||||
return new HostAddress(1, 0x80);
|
||||
{
|
||||
if (_hostNameTable.ContainsKey(hostName.ToLowerInvariant()))
|
||||
{
|
||||
return _hostNameTable[hostName.ToLowerInvariant()];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DirectoryServices Instance
|
||||
@@ -55,8 +92,183 @@ namespace IFS
|
||||
get { return _localHost; }
|
||||
}
|
||||
|
||||
public byte LocalNetwork
|
||||
{
|
||||
get { return _localHost.Network; }
|
||||
}
|
||||
|
||||
public byte LocalHost
|
||||
{
|
||||
get { return _localHost.Host; }
|
||||
}
|
||||
|
||||
private void LoadHostTable()
|
||||
{
|
||||
_hostAddressTable = new Dictionary<byte, Dictionary<byte, string>>();
|
||||
_hostNameTable = new Dictionary<string, HostAddress>();
|
||||
|
||||
// TODO: do not hardcode path like this.
|
||||
using (StreamReader sr = new StreamReader("Conf\\hosts.txt"))
|
||||
{
|
||||
int lineNumber = 0;
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
lineNumber++;
|
||||
|
||||
//
|
||||
// A line is either:
|
||||
// '#' followed by comment to EOL
|
||||
// <inter-network name> <hostname>
|
||||
// Any whitespace is ignored
|
||||
//
|
||||
// Format for Inter-Network name expressions is either:
|
||||
// network#host# (to specify hosts on another network)
|
||||
// or
|
||||
// host# (to specify hosts on our network)
|
||||
//
|
||||
|
||||
string line = sr.ReadLine().Trim().ToLowerInvariant();
|
||||
|
||||
if (line.StartsWith("#") || String.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
// Comment or empty, just ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tokenize on whitespace
|
||||
string[] tokens = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// We need at least two tokens (inter-network name and one hostname)
|
||||
if (tokens.Length < 2)
|
||||
{
|
||||
// Log warning and continue.
|
||||
Logging.Log.Write(Logging.LogLevel.Warning,
|
||||
String.Format("hosts.txt line {0}: Invalid syntax.", lineNumber));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// First token should be an inter-network name, which should end with '#'.
|
||||
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]));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// tokenize on '#'
|
||||
string[] networkTokens = tokens[0].Split(new char[] { '#' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
HostAddress host = new HostAddress(0, 0);
|
||||
// 1 token means a local name, 2 means on other network, anything else is illegal
|
||||
if (networkTokens.Length == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
host.Host = Convert.ToByte(networkTokens[0], 8);
|
||||
host.Network = _localHost.Network;
|
||||
}
|
||||
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]));
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (networkTokens.Length == 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
host.Network = Convert.ToByte(networkTokens[0], 8);
|
||||
host.Host = Convert.ToByte(networkTokens[1], 8);
|
||||
}
|
||||
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]));
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
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]));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Hash the host by one or more names
|
||||
for (int i=1;i<tokens.Length;i++)
|
||||
{
|
||||
string hostName = tokens[i];
|
||||
|
||||
// Add to name table
|
||||
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));
|
||||
break;
|
||||
}
|
||||
|
||||
_hostNameTable.Add(hostName, host);
|
||||
|
||||
// Add to address table:
|
||||
// - See if network has entry
|
||||
Dictionary<byte, string> networkTable = null;
|
||||
|
||||
if (_hostAddressTable.ContainsKey(host.Network))
|
||||
{
|
||||
networkTable = _hostAddressTable[host.Network];
|
||||
}
|
||||
else
|
||||
{
|
||||
// No entry for this network yet, add it now
|
||||
networkTable = new Dictionary<byte, string>();
|
||||
_hostAddressTable.Add(host.Network, networkTable);
|
||||
}
|
||||
|
||||
// Add to network table
|
||||
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));
|
||||
break;
|
||||
}
|
||||
|
||||
networkTable.Add(host.Host, hostName);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Points to us.
|
||||
/// </summary>
|
||||
private HostAddress _localHost;
|
||||
|
||||
/// <summary>
|
||||
/// Hash table for address resolution; outer hash finds the dictionary
|
||||
/// for a given network, inner hash finds names for hosts.
|
||||
/// </summary>
|
||||
private Dictionary<byte, Dictionary<byte, string>> _hostAddressTable;
|
||||
|
||||
/// <summary>
|
||||
/// Hash table for name resolution.
|
||||
/// </summary>
|
||||
private Dictionary<string, HostAddress> _hostNameTable;
|
||||
|
||||
private static DirectoryServices _instance = new DirectoryServices();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,29 @@ namespace IFS
|
||||
// Just send it back with the source/destination swapped.
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
|
||||
|
||||
PUP echoPup = new PUP(PupType.ImAnEcho, p.ID, p.SourcePort, localPort);
|
||||
Dispatcher.Instance.SendPup(p);
|
||||
//
|
||||
// An annoyance: The Alto "puptest" diagnostic actually expects us to echo *everything* back including
|
||||
// the garbage byte on odd-length PUPs. (Even though the garbage byte is meant to be ignored.)
|
||||
// So in these cases we need to do extra work and copy in the garbage byte. Grr.
|
||||
//
|
||||
byte[] contents;
|
||||
bool garbageByte = (p.Contents.Length % 2) != 0;
|
||||
|
||||
if (!garbageByte)
|
||||
{
|
||||
// Even, no work needed
|
||||
contents = p.Contents;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No such luck, copy in the extra garbage byte to make diagnostics happy.
|
||||
contents = new byte[p.Contents.Length + 1];
|
||||
p.Contents.CopyTo(contents, 0);
|
||||
contents[contents.Length - 1] = p.RawData[p.RawData.Length - 3];
|
||||
}
|
||||
|
||||
PUP echoPup = new PUP(PupType.ImAnEcho, p.ID, p.SourcePort, localPort, contents, garbageByte);
|
||||
PUPProtocolDispatcher.Instance.SendPup(echoPup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,31 @@ namespace IFS
|
||||
{
|
||||
public class Entrypoint
|
||||
{
|
||||
struct foo
|
||||
{
|
||||
public ushort Bar;
|
||||
public short Baz;
|
||||
public byte Thing;
|
||||
public int Inty;
|
||||
public uint Uinty;
|
||||
public BCPLString Quux;
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
foo newFoo = new foo();
|
||||
newFoo.Bar = 0x1234;
|
||||
newFoo.Baz = 0x5678;
|
||||
newFoo.Thing = 0xcc;
|
||||
newFoo.Inty = 0x01020304;
|
||||
newFoo.Uinty = 0x05060708;
|
||||
newFoo.Quux = new BCPLString("The quick brown fox jumped over the lazy dog's tail.");
|
||||
|
||||
byte[] data = Serializer.Serialize(newFoo);
|
||||
|
||||
|
||||
foo oldFoo = (foo) Serializer.Deserialize(data, typeof(foo));
|
||||
|
||||
|
||||
Logging.Log.Level = Logging.LogLevel.All;
|
||||
|
||||
@@ -19,20 +42,21 @@ namespace IFS
|
||||
Console.WriteLine("available interfaces are:");
|
||||
foreach(EthernetInterface i in ifaces)
|
||||
{
|
||||
Console.WriteLine(String.Format("{0} - address {1}", i.Name, i.MacAddress));
|
||||
}
|
||||
|
||||
// TODO: MAKE THIS CONFIGURABLE.
|
||||
Dispatcher.Instance.RegisterInterface(ifaces[1]);
|
||||
Console.WriteLine(String.Format("{0} - address {1}, desc {2} ", i.Name, i.MacAddress, i.Description));
|
||||
}
|
||||
|
||||
// Set up protocols:
|
||||
|
||||
// Connectionless
|
||||
Dispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Misc Services", 0x4, ConnectionType.Connectionless, new MiscServicesProtocol()));
|
||||
Dispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Echo", 0x5, ConnectionType.Connectionless, new EchoProtocol()));
|
||||
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Gateway Information", 2, ConnectionType.Connectionless, new GatewayInformationProtocol()));
|
||||
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Misc Services", 0x4, ConnectionType.Connectionless, new MiscServicesProtocol()));
|
||||
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Echo", 0x5, ConnectionType.Connectionless, new EchoProtocol()));
|
||||
|
||||
// RTP/BSP based:
|
||||
Dispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("CopyDisk", 0x15 /* 25B */, ConnectionType.BSP, new CopyDiskServer()));
|
||||
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("CopyDisk", 0x15 /* 25B */, ConnectionType.BSP, new CopyDiskServer()));
|
||||
|
||||
// TODO: MAKE THIS CONFIGURABLE.
|
||||
PUPProtocolDispatcher.Instance.RegisterInterface(ifaces[2]);
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
||||
39
PUP/GatewayInformationProtocol.cs
Normal file
39
PUP/GatewayInformationProtocol.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using IFS.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Gateway Information Protocol (see http://xeroxalto.computerhistory.org/_cd8_/pup/.gatewayinformation.press!1.pdf)
|
||||
/// </summary>
|
||||
public class GatewayInformationProtocol : PUPProtocolBase
|
||||
{
|
||||
public GatewayInformationProtocol()
|
||||
{
|
||||
// TODO:
|
||||
// load host tables, etc.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by dispatcher to send incoming data destined for this protocol
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
public override void RecvData(PUP p)
|
||||
{
|
||||
switch (p.Type)
|
||||
{
|
||||
|
||||
|
||||
default:
|
||||
Log.Write(LogLevel.UnhandledProtocol, String.Format("Unhandled Gateway protocol {0}", p.Type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -80,15 +80,21 @@
|
||||
<Compile Include="EchoProtocol.cs" />
|
||||
<Compile Include="Entrypoint.cs" />
|
||||
<Compile Include="Logging\Log.cs" />
|
||||
<Compile Include="GatewayInformationProtocol.cs" />
|
||||
<Compile Include="MiscServicesProtocol.cs" />
|
||||
<Compile Include="Serializer.cs" />
|
||||
<Compile Include="Transport\Ethernet.cs" />
|
||||
<Compile Include="PUPProtocolBase.cs" />
|
||||
<Compile Include="PUP.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Dispatcher.cs" />
|
||||
<Compile Include="PUPProtocolDispatcher.cs" />
|
||||
<Compile Include="Transport\PacketInterface.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<Content Include="Conf\hosts.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</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.
|
||||
|
||||
@@ -16,7 +16,9 @@ namespace IFS.Logging
|
||||
DroppedPacket = 8,
|
||||
InvalidPacket = 0x10,
|
||||
UnhandledProtocol = 0x20,
|
||||
DuplicateHostNumber = 0x40,
|
||||
HandledProtocol = 0x40,
|
||||
DuplicateHostNumber = 0x80,
|
||||
BSPLostPacket = 0x100,
|
||||
|
||||
All = 0x7fffffff,
|
||||
}
|
||||
@@ -25,7 +27,7 @@ namespace IFS.Logging
|
||||
{
|
||||
static Log()
|
||||
{
|
||||
_level = LogLevel.None;
|
||||
_level = LogLevel.All;
|
||||
}
|
||||
|
||||
public static LogLevel Level
|
||||
|
||||
@@ -7,6 +7,35 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
//
|
||||
// From the spec, the AltoTime response is:
|
||||
// "10 bytes in all, organized as 5 16-bit words:
|
||||
// words 0, 1 Present date and time: a 32-bit integer representing number of
|
||||
// seconds since midnight, January 1, 1901, Greenwich Mean Time (GMT).
|
||||
//
|
||||
// word 2 Local time zone information. Bit 0 is zero if west of Greenwich
|
||||
// and one if east. Bits 1-7 are the number of hours east or west of
|
||||
// Greenwich. Bits 8-15 are an additional number of minutes.
|
||||
//
|
||||
// word 3 Day of the year on or before which Daylight Savings Time takes
|
||||
// effect locally, where 1 = January 1 and 366 = Dcember 31. (The
|
||||
// actual day is the next preceding Sunday.)
|
||||
//
|
||||
// word 4 Day of the year on or before which Daylight Savings Time ends. If
|
||||
// Daylight Savings Time is not observed locally, both the start and
|
||||
// end dates should be 366.
|
||||
//
|
||||
// The local time parameters in words 2 and 4 are those in effect at the server's
|
||||
// location.
|
||||
//
|
||||
struct AltoTime
|
||||
{
|
||||
public uint DateTime;
|
||||
public ushort TimeZone;
|
||||
public ushort DSTStart;
|
||||
public ushort DSTEnd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements PUP Miscellaneous Services (see miscSvcsProto.pdf)
|
||||
/// which include:
|
||||
@@ -30,6 +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));
|
||||
switch (p.Type)
|
||||
{
|
||||
case PupType.StringTimeRequest:
|
||||
@@ -46,7 +76,7 @@ namespace IFS
|
||||
|
||||
case PupType.NameLookupRequest:
|
||||
SendNameLookupReply(p);
|
||||
break;
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.Write(LogLevel.UnhandledProtocol, String.Format("Unhandled misc. protocol {0}", p.Type));
|
||||
@@ -61,67 +91,66 @@ namespace IFS
|
||||
// From the spec, the response is:
|
||||
// "A string consisting of the current date and time in the form
|
||||
// '11-SEP-75 15:44:25'"
|
||||
// NOTE: This is *not* a BCPL string, just the raw characters.
|
||||
//
|
||||
// It makes no mention of timezone or DST, so I am assuming local time here.
|
||||
// Good enough for government work.
|
||||
//
|
||||
DateTime currentTime = DateTime.Now;
|
||||
DateTime currentTime = DateTime.Now;
|
||||
|
||||
BCPLString bcplDateString = new BCPLString(currentTime.ToString("dd-MMM-yy HH:mm:ss"));
|
||||
byte[] timeString = Helpers.StringToArray(currentTime.ToString("dd-MMM-yy HH:mm:ss"));
|
||||
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
|
||||
PUP response = new PUP(PupType.StringTimeReply, p.ID, p.SourcePort, localPort, bcplDateString.ToArray());
|
||||
PUP response = new PUP(PupType.StringTimeReply, p.ID, p.SourcePort, localPort, timeString);
|
||||
|
||||
Dispatcher.Instance.SendPup(response);
|
||||
PUPProtocolDispatcher.Instance.SendPup(response);
|
||||
}
|
||||
|
||||
private void SendAltoTimeReply(PUP p)
|
||||
{
|
||||
//
|
||||
// From the spec, the response is:
|
||||
// "10 bytes in all, organized as 5 16-bit words:
|
||||
// words 0, 1 Present date and time: a 32-bit integer representing number of
|
||||
// seconds since midnight, January 1, 1901, Greenwich Mean Time (GMT).
|
||||
//
|
||||
// word 2 Local time zone information. Bit 0 is zero if west of Greenwich
|
||||
// and one if east. Bits 1-7 are the number of hours east or west of
|
||||
// Greenwich. Bits 8-15 are an additional number of minutes.
|
||||
//
|
||||
// word 3 Day of the year on or before which Daylight Savings Time takes
|
||||
// effect locally, where 1 = January 1 and 366 = Dcember 31. (The
|
||||
// actual day is the next preceding Sunday.)
|
||||
//
|
||||
// word 4 Day of the year on or before which Daylight Savings Time ends. If
|
||||
// Daylight Savings Time is not observed locally, both the start and
|
||||
// end dates should be 366.
|
||||
//
|
||||
// The local time parameters in words 2 and 4 are those in effect at the server's
|
||||
// location.
|
||||
//
|
||||
|
||||
|
||||
// So the Alto epoch is 1/1/1901. For the time being to keep things simple we're assuming
|
||||
// GMT and no DST at all. TODO: make this take into account our TZ, etc.
|
||||
//
|
||||
DateTime currentTime = DateTime.Now;
|
||||
// Additionally: While certain routines seem to be Y2K compliant (the time requests made from
|
||||
// the Alto's "puptest" diagnostic, for example), the Executive is not. To keep things happy,
|
||||
// we move things back 28 years so that the calendar at least matches up.
|
||||
//
|
||||
DateTime currentTime =
|
||||
new DateTime(
|
||||
DateTime.Now.Year - 28,
|
||||
DateTime.Now.Month,
|
||||
DateTime.Now.Day,
|
||||
DateTime.Now.Hour,
|
||||
DateTime.Now.Minute,
|
||||
DateTime.Now.Second);
|
||||
|
||||
// The epoch for .NET is 1/1/0001 at 12 midnight and is counted in 100-ns intervals.
|
||||
// Some conversion is needed, is what I'm saying.
|
||||
DateTime altoEpoch = new DateTime(1901, 1, 1);
|
||||
// Some conversion is needed, is what I'm saying.
|
||||
DateTime altoEpoch = new DateTime(1901, 1, 1);
|
||||
|
||||
TimeSpan timeSinceAltoEpoch = new TimeSpan(currentTime.Ticks - altoEpoch.Ticks);
|
||||
|
||||
UInt32 altoTime = (UInt32)timeSinceAltoEpoch.TotalSeconds;
|
||||
|
||||
|
||||
|
||||
// Build the response data
|
||||
byte[] data = new byte[10];
|
||||
Helpers.WriteUInt(ref data, altoTime, 0);
|
||||
Helpers.WriteUShort(ref data, 0, 4); // Timezone, hardcoded to GMT
|
||||
Helpers.WriteUShort(ref data, 366, 6); // DST info, not used right now.
|
||||
Helpers.WriteUShort(ref data, 366, 8);
|
||||
AltoTime time = new AltoTime();
|
||||
time.DateTime = altoTime;
|
||||
time.TimeZone = 0; // Hardcoded to GMT
|
||||
time.DSTStart = 366; // DST not specified yet
|
||||
time.DSTEnd = 366;
|
||||
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
|
||||
PUP response = new PUP(PupType.AltoTimeResponse, p.ID, p.SourcePort, localPort, data);
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
|
||||
|
||||
Dispatcher.Instance.SendPup(response);
|
||||
// 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.AltoTimeResponse, p.ID, remotePort, localPort, Serializer.Serialize(time));
|
||||
|
||||
PUPProtocolDispatcher.Instance.SendPup(response);
|
||||
}
|
||||
|
||||
private void SendAddressLookupReply(PUP p)
|
||||
@@ -150,20 +179,22 @@ namespace IFS
|
||||
if (!String.IsNullOrEmpty(hostName))
|
||||
{
|
||||
// We have a result, pack the name into the response.
|
||||
BCPLString interNetworkName = new BCPLString(hostName);
|
||||
// NOTE: This is *not* a BCPL string, just the raw characters.
|
||||
byte[] interNetworkName = Helpers.StringToArray(hostName);
|
||||
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
|
||||
PUP lookupReply = new PUP(PupType.AddressLookupResponse, p.ID, p.SourcePort, localPort, interNetworkName.ToArray());
|
||||
PUP lookupReply = new PUP(PupType.AddressLookupResponse, p.ID, p.SourcePort, localPort, interNetworkName);
|
||||
|
||||
Dispatcher.Instance.SendPup(lookupReply);
|
||||
PUPProtocolDispatcher.Instance.SendPup(lookupReply);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown host, send an error reply
|
||||
BCPLString errorString = new BCPLString("Unknown host for address.");
|
||||
string errorString = "Unknown host.";
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
|
||||
PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, errorString.ToArray());
|
||||
PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString));
|
||||
|
||||
Dispatcher.Instance.SendPup(errorReply);
|
||||
PUPProtocolDispatcher.Instance.SendPup(errorReply);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +202,8 @@ namespace IFS
|
||||
{
|
||||
//
|
||||
// For the request PUP:
|
||||
// A string consisting of an inter-network name expression
|
||||
// A string consisting of an inter-network name expression.
|
||||
// NOTE: This is *not* a BCPL string, just the raw characters.
|
||||
//
|
||||
// Response:
|
||||
// One or more 6-byte blocks containing the address(es) corresponding to the
|
||||
@@ -180,13 +212,11 @@ namespace IFS
|
||||
//
|
||||
|
||||
//
|
||||
// I'm not sure what would cause a name to resolve to multiple addresses at this time.
|
||||
// Also still not sure what an 'inter-network name expression' is.
|
||||
// As above, assuming this is a simple hostname.
|
||||
// For now, the assumption is that each name maps to at most one address.
|
||||
//
|
||||
BCPLString lookupName = new BCPLString(p.Contents);
|
||||
string lookupName = Helpers.ArrayToString(p.Contents);
|
||||
|
||||
HostAddress address = DirectoryServices.Instance.NameLookup(lookupName.ToString());
|
||||
HostAddress address = DirectoryServices.Instance.NameLookup(lookupName);
|
||||
|
||||
if (address != null)
|
||||
{
|
||||
@@ -195,16 +225,16 @@ namespace IFS
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
|
||||
PUP lookupReply = new PUP(PupType.NameLookupResponse, p.ID, p.SourcePort, localPort, lookupPort.ToArray());
|
||||
|
||||
Dispatcher.Instance.SendPup(lookupReply);
|
||||
PUPProtocolDispatcher.Instance.SendPup(lookupReply);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown host, send an error reply
|
||||
BCPLString errorString = new BCPLString("Unknown host for name.");
|
||||
string errorString = "Unknown host.";
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
|
||||
PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, errorString.ToArray());
|
||||
PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString));
|
||||
|
||||
Dispatcher.Instance.SendPup(errorReply);
|
||||
PUPProtocolDispatcher.Instance.SendPup(errorReply);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
235
PUP/PUP.cs
235
PUP/PUP.cs
@@ -45,11 +45,25 @@ namespace IFS
|
||||
DirectoryLookupErrorReply = 146,
|
||||
AddressLookupRequest = 147,
|
||||
AddressLookupResponse = 148,
|
||||
|
||||
// Where is User
|
||||
WhereIsUserRequest = 152,
|
||||
WhereIsUserResponse = 153,
|
||||
WhereIsUserErrorReply = 154,
|
||||
|
||||
// Alto Boot
|
||||
SendBootFileRequest = 164,
|
||||
BootDirectoryRequest = 165,
|
||||
BootDirectoryReply = 166,
|
||||
|
||||
// User authentication
|
||||
AuthenticateRequest = 168,
|
||||
AuthenticatePositiveResponse = 169,
|
||||
AuthenticateNegativeResponse = 170,
|
||||
|
||||
// Gateway Information Protocol
|
||||
GatewayInformationRequest = 128,
|
||||
GatewayInformationResponse = 129
|
||||
}
|
||||
|
||||
public struct PUPPort
|
||||
@@ -87,8 +101,8 @@ namespace IFS
|
||||
public PUPPort(byte[] rawData, int offset)
|
||||
{
|
||||
Network = rawData[offset];
|
||||
Host = rawData[offset + 1];
|
||||
Socket = Helpers.ReadUInt(rawData, 2);
|
||||
Host = rawData[offset + 1];
|
||||
Socket = Helpers.ReadUInt(rawData, offset + 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -99,7 +113,7 @@ namespace IFS
|
||||
public void WriteToArray(ref byte[] rawData, int offset)
|
||||
{
|
||||
rawData[offset] = Network;
|
||||
rawData[offset + 1] = Host;
|
||||
rawData[offset + 1] = Host;
|
||||
Helpers.WriteUInt(ref rawData, Socket, offset + 2);
|
||||
}
|
||||
|
||||
@@ -124,8 +138,18 @@ namespace IFS
|
||||
/// <summary>
|
||||
/// Construct a new packet from the supplied data.
|
||||
/// </summary>
|
||||
/// <param name="rawPacket"></param>
|
||||
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents)
|
||||
/// <param name="contentsContainsGarbageByte">
|
||||
/// True if the "contents" array contains a garbage (padding) byte that should NOT
|
||||
/// be factored into the Length field of the PUP. This is necessary so we can properly
|
||||
/// support the Echo protocol; since some PUP tests that check validation require that the
|
||||
/// PUP be echoed in its entirety (including the supposedly-ignorable "garbage" byte on
|
||||
/// odd-length PUPs) we need to be able to craft a PUP with one extra byte of content that's
|
||||
/// otherwise ignored...
|
||||
///
|
||||
/// TODO: Update to use Serialization code rather than packing bytes by hand.
|
||||
/// </param>
|
||||
///
|
||||
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents, bool contentsContainsGarbageByte)
|
||||
{
|
||||
_rawData = null;
|
||||
|
||||
@@ -136,29 +160,67 @@ namespace IFS
|
||||
throw new InvalidOperationException("PUP size must not exceed 532 bytes.");
|
||||
}
|
||||
|
||||
//
|
||||
// Sanity check:
|
||||
// "contentsContainGarbageByte" can ONLY be true if "contents" is of even length
|
||||
//
|
||||
if (contentsContainsGarbageByte && (contents.Length % 2) != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Odd content length with garbage byte specified.");
|
||||
}
|
||||
|
||||
TransportControl = 0;
|
||||
Type = type;
|
||||
ID = id;
|
||||
DestinationPort = destination;
|
||||
SourcePort = source;
|
||||
Contents = contents;
|
||||
Length = (ushort)(PUP_HEADER_SIZE + PUP_CHECKSUM_SIZE + contents.Length);
|
||||
|
||||
// Ensure contents are an even number of bytes.
|
||||
int contentLength = (contents.Length % 2) == 0 ? contents.Length : contents.Length + 1;
|
||||
Contents = new byte[contentLength];
|
||||
contents.CopyTo(Contents, 0);
|
||||
|
||||
Length = (ushort)(PUP_HEADER_SIZE + PUP_CHECKSUM_SIZE + contentLength);
|
||||
|
||||
// Stuff data into raw array
|
||||
_rawData = new byte[Length];
|
||||
|
||||
//
|
||||
// Subtract off one byte from the Length value if the contents contain a garbage byte.
|
||||
// (See header comments for function)
|
||||
if (contentsContainsGarbageByte)
|
||||
{
|
||||
Length--;
|
||||
}
|
||||
|
||||
Helpers.WriteUShort(ref _rawData, Length, 0);
|
||||
_rawData[2] = TransportControl;
|
||||
_rawData[3] = (byte)Type;
|
||||
_rawData[3] = (byte)Type;
|
||||
Helpers.WriteUInt(ref _rawData, ID, 4);
|
||||
DestinationPort.WriteToArray(ref _rawData, 8);
|
||||
SourcePort.WriteToArray(ref _rawData, 14);
|
||||
Array.Copy(Contents, 0, _rawData, 20, Contents.Length);
|
||||
Array.Copy(Contents, 0, _rawData, 20, contentLength);
|
||||
|
||||
// Calculate the checksum and stow it
|
||||
Checksum = CalculateChecksum();
|
||||
Helpers.WriteUShort(ref _rawData, Checksum, Length - 2);
|
||||
Helpers.WriteUShort(ref _rawData, Checksum, _rawData.Length - 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="contents"></param>
|
||||
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents) :
|
||||
this(type, id, destination, source, contents, false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Same as above, but with no content (i.e. a zero-byte payload)
|
||||
/// </summary>
|
||||
@@ -176,34 +238,47 @@ namespace IFS
|
||||
/// Load in an existing packet from a stream
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
public PUP(MemoryStream stream)
|
||||
public PUP(MemoryStream stream, int length)
|
||||
{
|
||||
_rawData = new byte[stream.Length];
|
||||
stream.Read(_rawData, 0, (int)stream.Length);
|
||||
_rawData = new byte[length];
|
||||
stream.Read(_rawData, 0, length);
|
||||
|
||||
// Read fields in. TODO: investigate more efficient ways to do this.
|
||||
Length = Helpers.ReadUShort(_rawData, 0);
|
||||
|
||||
// Sanity check size:
|
||||
if (Length > stream.Length)
|
||||
if (Length > length)
|
||||
{
|
||||
throw new InvalidOperationException("Length field in PUP is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
TransportControl = _rawData[2];
|
||||
Type = (PupType)_rawData[3];
|
||||
Type = (PupType)_rawData[3];
|
||||
ID = Helpers.ReadUInt(_rawData, 4);
|
||||
DestinationPort = new PUPPort(_rawData, 8);
|
||||
SourcePort = new PUPPort(_rawData, 14);
|
||||
Array.Copy(_rawData, 20, Contents, 0, Length - PUP_HEADER_SIZE - PUP_CHECKSUM_SIZE);
|
||||
Checksum = Helpers.ReadUShort(_rawData, Length - 2);
|
||||
|
||||
int contentLength = Length - PUP_HEADER_SIZE - PUP_CHECKSUM_SIZE;
|
||||
Contents = new byte[contentLength];
|
||||
Array.Copy(_rawData, 20, Contents, 0, contentLength);
|
||||
|
||||
// Length is the number of valid bytes in the PUP, which may be an odd number.
|
||||
// There are always an even number of bytes in the PUP, and the checksum
|
||||
// therefore begins on an even byte boundary. Calculate the checksum offset
|
||||
// appropriately. (Empirically, we could also just use the last word of the packet,
|
||||
// as the Alto PUP implementation never appears to pad any extra data after the PUP, but
|
||||
// this doesn't appear to be a requirement and I don't want to rely on it.)
|
||||
int checksumOffset = (Length % 2) == 0 ? Length - PUP_CHECKSUM_SIZE : Length - PUP_CHECKSUM_SIZE + 1;
|
||||
|
||||
Checksum = Helpers.ReadUShort(_rawData, checksumOffset);
|
||||
|
||||
// Validate checksum
|
||||
ushort cChecksum = CalculateChecksum();
|
||||
|
||||
if (cChecksum != Checksum)
|
||||
if (Checksum != 0xffff && cChecksum != Checksum)
|
||||
{
|
||||
throw new InvalidOperationException(String.Format("PUP checksum is invalid. ({0} vs {1}", Checksum, cChecksum));
|
||||
// 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -215,59 +290,36 @@ namespace IFS
|
||||
|
||||
private ushort CalculateChecksum()
|
||||
{
|
||||
uint sum = 0;
|
||||
|
||||
int i;
|
||||
|
||||
//
|
||||
// This code "borrowed" from the Stanford PUP code
|
||||
// and translated roughly to C#
|
||||
//
|
||||
Cksum cksm;
|
||||
|
||||
cksm.lcksm = 0;
|
||||
cksm.scksm.ccksm = 0; // to make the C# compiler happy since it knows not of unions
|
||||
cksm.scksm.cksm = 0;
|
||||
|
||||
for (i = 0; i < _rawData.Length - PUP_CHECKSUM_SIZE; i += 2)
|
||||
// Sum over everything except the checksum word
|
||||
for (int i=0; i< _rawData.Length - PUP_CHECKSUM_SIZE; i+=2)
|
||||
{
|
||||
ushort word = Helpers.ReadUShort(_rawData, i);
|
||||
ushort nextWord = Helpers.ReadUShort(_rawData, i);
|
||||
//ushort nextWord = (ushort)((_rawData[i + 1] << 8) | _rawData[i]);
|
||||
|
||||
cksm.lcksm += word;
|
||||
cksm.scksm.cksm += cksm.scksm.ccksm;
|
||||
cksm.scksm.ccksm = 0;
|
||||
cksm.lcksm <<= 1;
|
||||
cksm.scksm.cksm += cksm.scksm.ccksm;
|
||||
cksm.scksm.ccksm = 0;
|
||||
// 2's complement add with "end-around" carry results in
|
||||
// 1's complement add
|
||||
sum += nextWord;
|
||||
|
||||
uint carry = (sum & 0x10000) >> 16;
|
||||
sum = (ushort)(sum + carry);
|
||||
|
||||
// Rotate left
|
||||
sum = sum << 1;
|
||||
carry = (sum & 0x10000) >> 16;
|
||||
sum = (ushort)(sum + carry);
|
||||
}
|
||||
|
||||
if (cksm.scksm.cksm == 0xffff)
|
||||
// Negative 0? convert to positive 0.
|
||||
if (sum == 0xffff)
|
||||
{
|
||||
cksm.scksm.cksm = 0;
|
||||
sum = 0;
|
||||
}
|
||||
|
||||
return cksm.scksm.cksm;
|
||||
}
|
||||
|
||||
// Structs used by CalculateChecksum to simulate
|
||||
// a C union in C#
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
struct Scksum
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public ushort ccksm;
|
||||
[FieldOffset(2)]
|
||||
public ushort cksm;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
struct Cksum
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public ulong lcksm;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public Scksum scksm;
|
||||
}
|
||||
return (ushort)sum;
|
||||
}
|
||||
|
||||
public readonly ushort Length;
|
||||
public readonly byte TransportControl;
|
||||
@@ -294,23 +346,76 @@ namespace IFS
|
||||
return (ushort)((data[offset] << 8) | data[offset + 1]);
|
||||
}
|
||||
|
||||
public static ushort ReadUShort(Stream s)
|
||||
{
|
||||
return (ushort)((s.ReadByte() << 8) | s.ReadByte());
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt(byte[] data, int offset)
|
||||
{
|
||||
return (UInt32)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]);
|
||||
}
|
||||
|
||||
public static UInt32 ReadUInt(Stream s)
|
||||
{
|
||||
return (UInt32)((s.ReadByte() << 24) | (s.ReadByte() << 16) | (s.ReadByte() << 8) | s.ReadByte());
|
||||
}
|
||||
|
||||
public static void WriteUShort(ref byte[] data, ushort s, int offset)
|
||||
{
|
||||
data[offset] = (byte)(s >> 8);
|
||||
data[offset + 1] = (byte)s;
|
||||
}
|
||||
|
||||
public static void WriteUShort(Stream st, ushort s)
|
||||
{
|
||||
st.WriteByte((byte)(s >> 8));
|
||||
st.WriteByte((byte)s);
|
||||
}
|
||||
|
||||
public static void WriteUInt(ref byte[] data, UInt32 s, int offset)
|
||||
{
|
||||
data[offset] = (byte)(s >> 24);
|
||||
data[offset + 1] = (byte)(s >> 16);
|
||||
data[offset + 2] = (byte)(s >> 8);
|
||||
data[offset + 3] = (byte)s;
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteUInt(Stream st, UInt32 s)
|
||||
{
|
||||
st.WriteByte((byte)(s >> 24));
|
||||
st.WriteByte((byte)(s >> 16));
|
||||
st.WriteByte((byte)(s >> 8));
|
||||
st.WriteByte((byte)s);
|
||||
}
|
||||
|
||||
public static byte[] StringToArray(string s)
|
||||
{
|
||||
byte[] stringArray = new byte[s.Length];
|
||||
|
||||
// We simply take the low 8-bits of each Unicode character and stuff it into the
|
||||
// byte array. This works fine for the ASCII subset of Unicode but obviously
|
||||
// is bad for everything else. This is unlikely to be an issue given the lack of
|
||||
// any real internationalization support on the IFS end of things, but might be
|
||||
// something to look at.
|
||||
for (int i = 0; i < stringArray.Length; i++)
|
||||
{
|
||||
stringArray[i] = (byte)s[i];
|
||||
}
|
||||
|
||||
return stringArray;
|
||||
}
|
||||
|
||||
public static string ArrayToString(byte[] a)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(a.Length);
|
||||
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
{
|
||||
sb.Append((char)(a[i]));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace IFS
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
public abstract void RecvData(PUP p);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ namespace IFS
|
||||
/// <summary>
|
||||
/// Dispatches incoming PUPs to the right protocol handler; sends outgoing PUPs over the network.
|
||||
/// </summary>
|
||||
public class Dispatcher
|
||||
public class PUPProtocolDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Private Constructor for this class, enforcing Singleton usage.
|
||||
/// </summary>
|
||||
private Dispatcher()
|
||||
private PUPProtocolDispatcher()
|
||||
{
|
||||
_dispatchMap = new Dictionary<uint, PUPProtocolEntry>();
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace IFS
|
||||
/// <summary>
|
||||
/// Accessor for singleton instance of this class.
|
||||
/// </summary>
|
||||
public static Dispatcher Instance
|
||||
public static PUPProtocolDispatcher Instance
|
||||
{
|
||||
get { return _instance; }
|
||||
}
|
||||
@@ -62,14 +62,33 @@ namespace IFS
|
||||
}
|
||||
|
||||
private void OnPupReceived(PUP pup)
|
||||
{
|
||||
{
|
||||
//
|
||||
// Forward PUP on to registered endpoints.
|
||||
//
|
||||
if (_dispatchMap.ContainsKey(pup.DestinationPort.Socket))
|
||||
{
|
||||
PUPProtocolEntry entry = _dispatchMap[pup.DestinationPort.Socket];
|
||||
entry.ProtocolImplementation.RecvData(pup);
|
||||
|
||||
if (entry.ConnectionType == ConnectionType.Connectionless)
|
||||
{
|
||||
Log.Write(LogLevel.HandledProtocol, String.Format("Dispatching PUP to {0} handler.", 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));
|
||||
//entry.ProtocolImplementation.RecvData(pup);
|
||||
|
||||
BSPManager.EstablishRendezvous(pup, (BSPProtocol)entry.ProtocolImplementation);
|
||||
}
|
||||
}
|
||||
else if (BSPManager.ChannelExistsForSocket(pup))
|
||||
{
|
||||
// An established BSP channel, send data to it.
|
||||
BSPManager.RecvData(pup);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -88,6 +107,6 @@ namespace IFS
|
||||
/// </summary>
|
||||
private Dictionary<UInt32, PUPProtocolEntry> _dispatchMap;
|
||||
|
||||
private static Dispatcher _instance = new Dispatcher();
|
||||
private static PUPProtocolDispatcher _instance = new PUPProtocolDispatcher();
|
||||
}
|
||||
}
|
||||
160
PUP/Serializer.cs
Normal file
160
PUP/Serializer.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
public static class Serializer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified byte array into the specified object.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="t"></param>
|
||||
/// <returns></returns>
|
||||
public static object Deserialize(byte[] data, Type t)
|
||||
{
|
||||
//
|
||||
// We support serialization of structs containing only:
|
||||
// - byte
|
||||
// - ushort
|
||||
// - short
|
||||
// - int
|
||||
// - uint
|
||||
// - BCPLString
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
MemoryStream ms = new MemoryStream(data);
|
||||
System.Reflection.FieldInfo[] info = t.GetFields(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
object o = Activator.CreateInstance(t);
|
||||
|
||||
for (int i = 0; i < info.Length; i++)
|
||||
{
|
||||
switch (info[i].FieldType.Name)
|
||||
{
|
||||
case "Byte":
|
||||
info[i].SetValue(o, (byte)ms.ReadByte());
|
||||
break;
|
||||
|
||||
case "UInt16":
|
||||
{
|
||||
info[i].SetValue(o, Helpers.ReadUShort(ms));
|
||||
}
|
||||
break;
|
||||
|
||||
case "Int16":
|
||||
{
|
||||
info[i].SetValue(o, (short)Helpers.ReadUShort(ms));
|
||||
}
|
||||
break;
|
||||
|
||||
case "UInt32":
|
||||
{
|
||||
info[i].SetValue(o, Helpers.ReadUInt(ms));
|
||||
}
|
||||
break;
|
||||
|
||||
case "Int32":
|
||||
{
|
||||
info[i].SetValue(o, (int)Helpers.ReadUInt(ms));
|
||||
}
|
||||
break;
|
||||
|
||||
case "BCPLString":
|
||||
{
|
||||
info[i].SetValue(o, new BCPLString(ms));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format("Type {0} is unsupported for deserialization.", info[i].FieldType.Name));
|
||||
}
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize the object (if supported) to an array.
|
||||
/// </summary>
|
||||
/// <param name="o"></param>
|
||||
/// <param name="channel"></param>
|
||||
public static byte[] Serialize(object o)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
|
||||
//
|
||||
// We support serialization of structs containing only:
|
||||
// - byte
|
||||
// - ushort
|
||||
// - short
|
||||
// - int
|
||||
// - uint
|
||||
// - BCPLString
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
System.Reflection.FieldInfo[] info = o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
for(int i=0;i<info.Length;i++)
|
||||
{
|
||||
switch(info[i].FieldType.Name)
|
||||
{
|
||||
case "Byte":
|
||||
ms.WriteByte((byte)info[i].GetValue(o));
|
||||
break;
|
||||
|
||||
case "UInt16":
|
||||
{
|
||||
ushort value = (ushort)(info[i].GetValue(o));
|
||||
Helpers.WriteUShort(ms, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Int16":
|
||||
{
|
||||
short value = (short)(info[i].GetValue(o));
|
||||
Helpers.WriteUShort(ms, (ushort)value);
|
||||
}
|
||||
break;
|
||||
|
||||
case "UInt32":
|
||||
{
|
||||
uint value = (uint)(info[i].GetValue(o));
|
||||
Helpers.WriteUInt(ms, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Int32":
|
||||
{
|
||||
int value = (int)(info[i].GetValue(o));
|
||||
Helpers.WriteUInt(ms, (uint)value);
|
||||
}
|
||||
break;
|
||||
|
||||
case "BCPLString":
|
||||
{
|
||||
BCPLString value = (BCPLString)(info[i].GetValue(o));
|
||||
byte[] bcplArray = value.ToArray();
|
||||
ms.Write(bcplArray, 0, bcplArray.Length);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format("Type {0} is unsupported for serialization.", info[i].FieldType.Name));
|
||||
}
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using PcapDotNet.Core.Extensions;
|
||||
using PcapDotNet.Packets;
|
||||
using PcapDotNet.Packets.Ethernet;
|
||||
using IFS.Logging;
|
||||
using System.IO;
|
||||
|
||||
namespace IFS.Transport
|
||||
{
|
||||
@@ -73,19 +74,43 @@ namespace IFS.Transport
|
||||
//
|
||||
if (_pupToEthernetMap.ContainsKey(p.DestinationPort.Host))
|
||||
{
|
||||
MacAddress destinationMac = _pupToEthernetMap[p.SourcePort.Host];
|
||||
// Build the outgoing data; this is:
|
||||
// 1st word: length of data following
|
||||
// 2nd word: 3mbit destination / source bytes
|
||||
// 3rd word: frame type (PUP)
|
||||
byte[] encapsulatedFrame = new byte[6 + p.RawData.Length];
|
||||
|
||||
// 3mbit Packet length
|
||||
encapsulatedFrame[0] = (byte)((p.RawData.Length / 2 + 2) >> 8);
|
||||
encapsulatedFrame[1] = (byte)(p.RawData.Length / 2 + 2);
|
||||
|
||||
// addressing
|
||||
encapsulatedFrame[2] = p.DestinationPort.Host;
|
||||
encapsulatedFrame[3] = p.SourcePort.Host;
|
||||
|
||||
// frame type
|
||||
encapsulatedFrame[4] = (byte)(_pupFrameType >> 8);
|
||||
encapsulatedFrame[5] = (byte)_pupFrameType;
|
||||
|
||||
// Actual data
|
||||
p.RawData.CopyTo(encapsulatedFrame, 6);
|
||||
|
||||
// Byte swap
|
||||
encapsulatedFrame = ByteSwap(encapsulatedFrame);
|
||||
|
||||
MacAddress destinationMac = _pupToEthernetMap[p.DestinationPort.Host];
|
||||
|
||||
// Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
|
||||
EthernetLayer ethernetLayer = new EthernetLayer
|
||||
{
|
||||
Source = _interface.GetMacAddress(),
|
||||
Destination = destinationMac,
|
||||
EtherType = (EthernetType)_pupFrameType,
|
||||
};
|
||||
EtherType = (EthernetType)_3mbitFrameType,
|
||||
};
|
||||
|
||||
PayloadLayer payloadLayer = new PayloadLayer
|
||||
{
|
||||
Data = new Datagram(p.RawData),
|
||||
Data = new Datagram(encapsulatedFrame),
|
||||
};
|
||||
|
||||
PacketBuilder builder = new PacketBuilder(ethernetLayer, payloadLayer);
|
||||
@@ -103,27 +128,47 @@ namespace IFS.Transport
|
||||
private void ReceiveCallback(Packet p)
|
||||
{
|
||||
//
|
||||
// Filter out PUPs, forward them on.
|
||||
// Filter out encapsulated 3mbit frames and look for PUPs, forward them on.
|
||||
//
|
||||
if ((int)p.Ethernet.EtherType == _pupFrameType)
|
||||
if ((int)p.Ethernet.EtherType == _3mbitFrameType)
|
||||
{
|
||||
PUP pup = new PUP(p.Ethernet.Payload.ToMemoryStream());
|
||||
MemoryStream packetStream = ByteSwap(p.Ethernet.Payload.ToMemoryStream());
|
||||
|
||||
//
|
||||
// Check the network -- if this is not network zero (coming from a host that doesn't yet know what
|
||||
// network it's on) or the network we're on, we will ignore it (for now). Once we implement
|
||||
// Gateway services we will handle these appropriately (at a higher, as-yet-unimplemented level between this
|
||||
// and the Dispatcher).
|
||||
//
|
||||
if (pup.SourcePort.Network == 0 || pup.SourcePort.Network == DirectoryServices.Instance.LocalHostAddress.Network)
|
||||
// Read the length prefix (in words), convert to bytes.
|
||||
// Subtract off 2 words for the ethernet header
|
||||
int length = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())) * 2 - 4;
|
||||
|
||||
// Read the address (1st word of 3mbit packet)
|
||||
byte destination = (byte)packetStream.ReadByte();
|
||||
byte source = (byte)packetStream.ReadByte();
|
||||
|
||||
// Read the type and switch on it
|
||||
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
|
||||
|
||||
if (etherType3mbit == _pupFrameType)
|
||||
{
|
||||
UpdateMACTable(pup, p);
|
||||
_callback(pup);
|
||||
PUP pup = new PUP(packetStream, length);
|
||||
|
||||
//
|
||||
// Check the network -- if this is not network zero (coming from a host that doesn't yet know what
|
||||
// network it's on, or specifying the current network) or the network we're on, we will ignore it (for now). Once we implement
|
||||
// Gateway services we will handle these appropriately (at a higher, as-yet-unimplemented layer between this
|
||||
// and the Dispatcher).
|
||||
//
|
||||
if (pup.DestinationPort.Network == 0 || pup.DestinationPort.Network == DirectoryServices.Instance.LocalHostAddress.Network)
|
||||
{
|
||||
UpdateMACTable(pup, p);
|
||||
_callback(pup);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not for our network.
|
||||
Log.Write(LogLevel.DroppedPacket, String.Format("PUP is for network {0}, dropping.", pup.DestinationPort.Network));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not for our network.
|
||||
Log.Write(LogLevel.DroppedPacket, String.Format("PUP is for network {0}, dropping.", pup.SourcePort.Network));
|
||||
Log.Write(LogLevel.DroppedPacket, String.Format("3mbit packet is not a PUP, dropping"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -155,7 +200,12 @@ namespace IFS.Transport
|
||||
|
||||
private void Open(bool promiscuous, int timeout)
|
||||
{
|
||||
_communicator = _interface.Open(0xffff, promiscuous ? PacketDeviceOpenAttributes.Promiscuous : PacketDeviceOpenAttributes.None, timeout);
|
||||
_communicator = _interface.Open(
|
||||
0xffff,
|
||||
promiscuous ? PacketDeviceOpenAttributes.Promiscuous | PacketDeviceOpenAttributes.NoCaptureLocal: PacketDeviceOpenAttributes.NoCaptureLocal,
|
||||
timeout);
|
||||
|
||||
_communicator.SetKernelMinimumBytesToCopy(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -194,6 +244,36 @@ namespace IFS.Transport
|
||||
}
|
||||
}
|
||||
|
||||
private MemoryStream ByteSwap(MemoryStream input)
|
||||
{
|
||||
byte[] buffer = new byte[input.Length];
|
||||
|
||||
input.Read(buffer, 0, buffer.Length);
|
||||
|
||||
for(int i=0;i<buffer.Length;i+=2)
|
||||
{
|
||||
byte temp = buffer[i];
|
||||
buffer[i] = buffer[i + 1];
|
||||
buffer[i + 1] = temp;
|
||||
}
|
||||
|
||||
input.Position = 0;
|
||||
|
||||
return new MemoryStream(buffer);
|
||||
}
|
||||
|
||||
private byte[] ByteSwap(byte[] input)
|
||||
{
|
||||
for (int i = 0; i < input.Length; i += 2)
|
||||
{
|
||||
byte temp = input[i];
|
||||
input[i] = input[i + 1];
|
||||
input[i + 1] = temp;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PUP<->Ethernet address map
|
||||
/// </summary>
|
||||
@@ -205,7 +285,12 @@ namespace IFS.Transport
|
||||
private HandlePup _callback;
|
||||
|
||||
// Constants
|
||||
private const ushort _pupFrameType = 512;
|
||||
|
||||
// The ethertype used in the encapsulated 3mbit frame
|
||||
private readonly ushort _pupFrameType = 512;
|
||||
|
||||
// The type used for 3mbit frames encapsulated in 10mb frames
|
||||
private readonly int _3mbitFrameType = 0xbeef; // easy to identify, ostensibly unused by anything of any import
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user