1
0
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:
Josh Dersch
2016-01-27 17:24:51 -08:00
parent 2badecda6d
commit 32beacdca8
16 changed files with 1179 additions and 222 deletions

View File

@@ -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>

View File

@@ -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
View 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

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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)
{

View 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;
}
}
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -55,6 +55,7 @@ namespace IFS
/// </summary>
/// <param name="p"></param>
public abstract void RecvData(PUP p);
}
}

View File

@@ -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
View 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();
}
}
}

View File

@@ -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
}
}