diff --git a/Ethernet/Ethernet/Class1.cs b/BSP/BSP/Class1.cs
similarity index 89%
rename from Ethernet/Ethernet/Class1.cs
rename to BSP/BSP/Class1.cs
index e92684d..1baa8d2 100644
--- a/Ethernet/Ethernet/Class1.cs
+++ b/BSP/BSP/Class1.cs
@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace Ethernet
+namespace BSP
{
public class Class1
{
diff --git a/BSP/BSP/Properties/AssemblyInfo.cs b/BSP/BSP/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..cdf0176
--- /dev/null
+++ b/BSP/BSP/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BSP")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Vulcan Inc.")]
+[assembly: AssemblyProduct("BSP")]
+[assembly: AssemblyCopyright("Copyright © Vulcan Inc. 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("5093c127-6a0e-48ff-8ae3-7db590348b6f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BreathOfLife/BreathOfLife/App.config b/BreathOfLife/BreathOfLife/App.config
new file mode 100644
index 0000000..8e15646
--- /dev/null
+++ b/BreathOfLife/BreathOfLife/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BreathOfLife/BreathOfLife/BreathOfLife.csproj b/BreathOfLife/BreathOfLife/BreathOfLife.csproj
new file mode 100644
index 0000000..40b076b
--- /dev/null
+++ b/BreathOfLife/BreathOfLife/BreathOfLife.csproj
@@ -0,0 +1,58 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {BD5D7CF7-52BA-4960-A60F-75EEECCEF775}
+ Exe
+ Properties
+ BreathOfLife
+ BreathOfLife
+ v4.5
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BreathOfLife/BreathOfLife/Program.cs b/BreathOfLife/BreathOfLife/Program.cs
new file mode 100644
index 0000000..0bed51f
--- /dev/null
+++ b/BreathOfLife/BreathOfLife/Program.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BreathOfLife
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ }
+ }
+}
diff --git a/BreathOfLife/BreathOfLife/Properties/AssemblyInfo.cs b/BreathOfLife/BreathOfLife/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..cc9f7cc
--- /dev/null
+++ b/BreathOfLife/BreathOfLife/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BreathOfLife")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Vulcan Inc.")]
+[assembly: AssemblyProduct("BreathOfLife")]
+[assembly: AssemblyCopyright("Copyright © Vulcan Inc. 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("af633818-d867-41ba-a09c-d26de9bc75b2")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Ethernet/Ethernet/Ethernet.csproj b/Ethernet/Ethernet/Ethernet.csproj
index b7e0fb3..15609e2 100644
--- a/Ethernet/Ethernet/Ethernet.csproj
+++ b/Ethernet/Ethernet/Ethernet.csproj
@@ -20,6 +20,7 @@
DEBUG;TRACE
prompt
4
+ x64
pdbonly
@@ -30,6 +31,18 @@
4
+
+ ..\pcap\PcapDotNet.Base.dll
+
+
+ ..\pcap\PcapDotNet.Core.dll
+
+
+ ..\pcap\PcapDotNet.Core.Extensions.dll
+
+
+ ..\pcap\PcapDotNet.Packets.dll
+
@@ -39,7 +52,7 @@
-
+
diff --git a/PUP/BCPLString.cs b/PUP/BCPLString.cs
new file mode 100644
index 0000000..0554131
--- /dev/null
+++ b/PUP/BCPLString.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+ ///
+ /// A BCPL string is one byte of length N followed by N bytes of ASCII.
+ /// This class is a (very) simple encapsulation of that over a byte array.
+ ///
+ public class BCPLString
+ {
+ public BCPLString(string s)
+ {
+ if (s.Length > 255)
+ {
+ throw new InvalidOperationException("Max length for a BCPL string is 255 characters.");
+ }
+
+ _string = new byte[_string.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< _string.Length; i++)
+ {
+ _string[i] = (byte)s[i];
+ }
+ }
+
+ ///
+ /// Build a new BCPL string from the raw representation
+ ///
+ ///
+ public BCPLString(byte[] rawData)
+ {
+ if (rawData.Length > 256)
+ {
+ throw new InvalidOperationException("Max length for a BCPL string is 255 characters.");
+ }
+
+ // Sanity check that first byte matches length of data sent to us
+ if (rawData.Length < 1 || rawData[0] != rawData.Length - 1)
+ {
+ throw new InvalidOperationException("BCPL length data is invalid.");
+ }
+
+ _string = new byte[rawData.Length - 1];
+ Array.Copy(rawData, 1, _string, 0, rawData.Length - 1);
+ }
+
+ ///
+ /// Returns a native representation of the BCPL string
+ ///
+ ///
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ // See notes in constructor re: unicode.
+ for (int i = 0; i < _string.Length; i++)
+ {
+ sb.Append((char)_string[i]);
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Returns the raw representation of the BCPL string
+ ///
+ ///
+ public byte[] ToArray()
+ {
+ byte[] a = new byte[_string.Length + 1];
+
+ a[0] = (byte)_string.Length;
+ _string.CopyTo(a, 1);
+
+ return a;
+ }
+
+ private byte[] _string;
+ }
+}
diff --git a/PUP/BSPManager.cs b/PUP/BSPManager.cs
new file mode 100644
index 0000000..753bc8a
--- /dev/null
+++ b/PUP/BSPManager.cs
@@ -0,0 +1,301 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+
+
+ public enum BSPState
+ {
+ Unconnected,
+ Connected
+ }
+
+ public class BSPChannel
+ {
+ public BSPChannel(PUP rfcPup)
+ {
+ _inputLock = new ReaderWriterLockSlim();
+ _outputLock = new ReaderWriterLockSlim();
+
+ _inputWriteEvent = new AutoResetEvent(false);
+
+ _inputQueue = new MemoryStream(65536);
+
+ // TODO: init IDs, etc. based on RFC PUP
+ _start_pos = _recv_pos = _send_pos = rfcPup.ID;
+
+ }
+
+ public PUPPort ClientPort
+ {
+ get { return _clientConnectionPort; }
+ }
+
+ public PUPPort ServerPort
+ {
+ get { return _serverConnectionPort; }
+ }
+
+ ///
+ /// Reads data from the channel (i.e. from the client). Will block if not all the requested data is available.
+ ///
+ ///
+ public int Read(ref byte[] data, int count)
+ {
+ // sanity check
+ if (count > data.Length)
+ {
+ throw new InvalidOperationException("count must be less than or equal to the length of the buffer being read into.");
+ }
+
+ int read = 0;
+
+ // Loop until the data we asked for arrives or until we time out waiting.
+ // TODO: handle partial transfers due to aborted BSPs.
+ while (true)
+ {
+ _inputLock.EnterUpgradeableReadLock();
+ if (_inputQueue.Count >= count)
+ {
+ _inputLock.EnterWriteLock();
+ // We have the data right now, read it and return.
+ // TODO: this is a really inefficient thing.
+ for (int i = 0; i < count; i++)
+ {
+ data[i] = _inputQueue.Dequeue();
+ }
+
+ _inputLock.ExitWriteLock();
+ _inputLock.ExitUpgradeableReadLock();
+
+ break;
+ }
+ else
+ {
+ _inputLock.ExitUpgradeableReadLock();
+
+ // Not enough data in the queue.
+ // Wait until we have received more data, then try again.
+ _inputWriteEvent.WaitOne(); // TODO: timeout and fail
+ }
+ }
+
+ return read;
+ }
+
+ ///
+ /// Appends data into the input queue (called from BSPManager to place new PUP data into the BSP stream)
+ ///
+ public void WriteQueue(PUP dataPUP)
+ {
+ // If we are over our high watermark, we will drop the data (and not send an ACK even if requested).
+ // Clients should be honoring the limits we set in the RFC packets.
+ _inputLock.EnterUpgradeableReadLock();
+ if (_inputQueue.Count + dataPUP.Contents.Length > MaxBytes)
+ {
+ _inputLock.ExitUpgradeableReadLock();
+ return;
+ }
+
+ // Sanity check on expected position from sender vs. received data on our end.
+ // If they don't match then we've lost a packet somewhere.
+ if (dataPUP.ID != _recv_pos)
+ {
+ // 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.
+ return;
+ }
+
+
+ // Prepare to add data to the queue
+ // Again, this is really inefficient
+ _inputLock.EnterWriteLock();
+
+ for (int i = 0; i < dataPUP.Contents.Length; i++)
+ {
+ _inputQueue.Enqueue(dataPUP.Contents[i]);
+ }
+
+ _recv_pos += (UInt32)dataPUP.Contents.Length;
+
+ _inputLock.ExitWriteLock();
+
+ _inputLock.ExitUpgradeableReadLock();
+
+ _inputWriteEvent.Set();
+
+ if ((PupType)dataPUP.Type == PupType.AData)
+ {
+ SendAck();
+ }
+
+ }
+
+ ///
+ /// Sends data to the channel (i.e. to the client). Will block if an ACK is requested.
+ ///
+ /// The data to be sent
+ public void Send(byte[] data)
+ {
+ // Write data to the output stream
+
+ // Await an ack for the PUP we just sent
+ _outputAckEvent.WaitOne(); // TODO: timeout and fail
+
+ }
+
+ public void Ack(PUP ackPUP)
+ {
+ // Update receiving end stats (max PUPs, etc.)
+ // Ensure client's position matches ours
+
+ // Let any waiting threads continue
+ _outputAckEvent.Set();
+ }
+
+ public void Mark(byte type);
+ public void Interrupt();
+
+ public void Abort(int code, string message);
+ public void Error(int code, string message);
+
+ public void End();
+
+ // TODO:
+ // Events for:
+ // Abort, End, Mark, Interrupt (from client)
+ // Repositioning (due to lost packets) (perhaps not necessary)
+ //
+
+ private void SendAck()
+ {
+
+ }
+
+
+ private BSPState _state;
+ private UInt32 _recv_pos;
+ private UInt32 _send_pos;
+ private UInt32 _start_pos;
+
+ private PUPPort _clientConnectionPort; // the client port
+ private PUPPort _serverConnectionPort; // the server port we (the server) have established for communication
+
+ private ReaderWriterLockSlim _inputLock;
+ private System.Threading.AutoResetEvent _inputWriteEvent;
+
+ private ReaderWriterLockSlim _outputLock;
+
+ private System.Threading.AutoResetEvent _outputAckEvent;
+
+ // TODO: replace this with a more efficient structure for buffering data
+ private Queue _inputQueue;
+
+ // Constants
+
+ // For now, we work on one PUP at a time.
+ private const int MaxPups = 1;
+ private const int MaxPupSize = 532;
+ private const int MaxBytes = 1 * 532;
+ }
+
+ ///
+ ///
+ ///
+ public static class BSPManager
+ {
+ static BSPManager()
+ {
+
+ }
+
+
+ ///
+ /// Called when BSP-based protocols receive data.
+ ///
+ ///
+ /// null if no new channel is created due to the sent PUP (not an RFC)
+ /// a new BSPChannel if one has been created based on the PUP (new RFC)
+ ///
+ ///
+ public static BSPChannel RecvData(PUP p)
+ {
+ PupType type = (PupType)p.Type;
+
+ switch (type)
+ {
+ case PupType.RFC:
+ {
+ BSPChannel newChannel = new BSPChannel(p);
+ _activeChannels.Add(newChannel.ClientPort.Socket);
+
+ return newChannel;
+ }
+
+ case PupType.Data:
+ case PupType.AData:
+ {
+ BSPChannel channel = FindChannelForPup(p);
+
+ if (channel != null)
+ {
+ channel.WriteQueue(p);
+ }
+ }
+ break;
+
+ case PupType.Ack:
+ BSPChannel channel = FindChannelForPup(p);
+
+ if (channel != null)
+ {
+ channel.Ack(p);
+ }
+ break;
+
+ case PupType.End:
+ {
+ BSPChannel channel = FindChannelForPup(p);
+
+ if (channel != null)
+ {
+ channel.EndReply();
+ }
+ }
+ break;
+
+ case PupType.Abort:
+ {
+ // TODO: tear down the channel
+ }
+ break;
+
+ default:
+ throw new NotImplementedException(String.Format("Unhandled BSP PUP type {0}.", type));
+
+ }
+
+ return null;
+ }
+
+ public static void DestroyChannel(BSPChannel channel)
+ {
+
+ }
+
+ private static BSPChannel FindChannelForPup(PUP p)
+ {
+ return null;
+ }
+
+ private static Dictionary _activeChannels;
+ }
+}
diff --git a/PUP/CopyDiskServer.cs b/PUP/CopyDiskServer.cs
new file mode 100644
index 0000000..0d4c810
--- /dev/null
+++ b/PUP/CopyDiskServer.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+ public class CopyDiskServer : PUPProtocolBase
+ {
+ ///
+ /// Called by dispatcher to send incoming data destined for this protocol.
+ ///
+ ///
+ public override void RecvData(PUP p)
+ {
+ BSPChannel newChannel = BSPManager.RecvData(p);
+
+ if (newChannel != null)
+ {
+ // spwan new worker thread with new BSP channel
+ }
+ }
+ }
+}
diff --git a/PUP/DirectoryServices.cs b/PUP/DirectoryServices.cs
new file mode 100644
index 0000000..16ab812
--- /dev/null
+++ b/PUP/DirectoryServices.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+
+ public class HostAddress
+ {
+ public HostAddress(byte network, byte host)
+ {
+ Network = network;
+ Host = host;
+ }
+
+ public byte Network;
+ public byte Host;
+ }
+
+ ///
+ /// Provides a basic database used by the Misc. Services to do name lookup and whatnot.
+ ///
+ ///
+ public class DirectoryServices
+ {
+ private DirectoryServices()
+ {
+ // Get our host address; for now just hardcode it.
+ // TODO: need to define config files, etc.
+
+ _localHost = new HostAddress(1, 34);
+ }
+
+ public static DirectoryServices Instance
+ {
+ get { return _instance; }
+ }
+
+ public HostAddress LocalHostAddress
+ {
+ get { return _localHost; }
+ }
+
+ private HostAddress _localHost;
+
+ private static DirectoryServices _instance = new DirectoryServices();
+ }
+}
diff --git a/PUP/Dispatcher.cs b/PUP/Dispatcher.cs
new file mode 100644
index 0000000..8065914
--- /dev/null
+++ b/PUP/Dispatcher.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using IFS.Transport;
+using PcapDotNet.Packets;
+
+namespace IFS
+{
+
+
+
+ ///
+ /// Dispatches incoming PUPs to the right protocol handler; sends outgoing PUPs over the network.
+ ///
+ public class Dispatcher
+ {
+ ///
+ /// Private Constructor for this class, enforcing Singleton usage.
+ ///
+ private Dispatcher()
+ {
+ _dispatchMap = new Dictionary();
+
+ _packetInterface = new Ethernet(iface, OnPacketReceived);
+ }
+
+ ///
+ /// Accessor for singleton instance of this class.
+ ///
+ public static Dispatcher Instance
+ {
+ get { return _instance; }
+ }
+
+ ///
+ /// Registers a new protocol with the dispatcher
+ ///
+ ///
+ ///
+ public void RegisterProtocol(PUPProtocolEntry entry)
+ {
+ if (_dispatchMap.ContainsKey(entry.Socket))
+ {
+ throw new InvalidOperationException(
+ String.Format("Socket {0} has already been registered for protocol {1}", impl.Socket, _dispatchMap[impl.Socket].FriendlyName));
+ }
+
+ _dispatchMap[entry.Socket] = entry;
+ }
+
+ public void SendPup(PUP p)
+ {
+ // TODO: Write PUP to ethernet
+
+ }
+
+ private void OnPacketReceived(Packet p)
+ {
+ // filter out PUPs, discard all other packets. Forward PUP on to registered endpoints.
+ //
+ if ((int)p.Ethernet.EtherType == 512) // 512 = pup type
+ {
+ PUP pup = new PUP(p.Ethernet.Payload.ToMemoryStream());
+
+ if (_dispatchMap.ContainsKey(pup.DestinationPort.Socket))
+ {
+ PUPProtocolEntry entry = _dispatchMap[pup.DestinationPort.Socket];
+ entry.ProtocolImplementation.RecvData(pup);
+ }
+ else
+ {
+ // Not a protocol we handle; TODO: log it.
+ }
+ }
+ else
+ {
+ // Not a PUP, Discard the packet.
+ }
+ }
+
+ ///
+ /// Our interface to some kind of network
+ ///
+ private IPacketInterface _packetInterface;
+
+ ///
+ /// Map from socket to protocol implementation
+ ///
+ private Dictionary _dispatchMap;
+
+ private static Dispatcher _instance = new Dispatcher();
+ }
+}
diff --git a/PUP/EchoProtocol.cs b/PUP/EchoProtocol.cs
new file mode 100644
index 0000000..9c5b48d
--- /dev/null
+++ b/PUP/EchoProtocol.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+ ///
+ /// Implements the PUP Echo Protocol.
+ ///
+ public class EchoProtocol : PUPProtocolBase
+ {
+ public EchoProtocol()
+ {
+
+ }
+
+ ///
+ /// Called by dispatcher to send incoming data destined for this protocol
+ ///
+ ///
+ public override void RecvData(PUP p)
+ {
+ // If this is an EchoMe packet, we will send back an "ImAnEcho" packet.
+ if (p.Type == PupType.EchoMe)
+ {
+ // 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);
+ }
+ }
+
+ }
+}
diff --git a/PUP/Entrypoint.cs b/PUP/Entrypoint.cs
new file mode 100644
index 0000000..dea6ab1
--- /dev/null
+++ b/PUP/Entrypoint.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+ public class Entrypoint
+ {
+ static void Main(string[] args)
+ {
+ // 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()));
+
+ // RTP/BSP based:
+ Dispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("CopyDisk", 0x15 /* 25B */, ConnectionType.BSP, new CopyDiskServer()));
+
+ }
+}
diff --git a/PUP/IFS.csproj b/PUP/IFS.csproj
new file mode 100644
index 0000000..8135c78
--- /dev/null
+++ b/PUP/IFS.csproj
@@ -0,0 +1,80 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {5C0BBE4B-76AB-4AC1-8691-F19D8D282DCB}
+ Exe
+ Properties
+ IFS
+ IFS
+ v4.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+ ..\Ethernet\pcap\PcapDotNet.Base.dll
+
+
+ ..\Ethernet\pcap\PcapDotNet.Core.dll
+
+
+ ..\Ethernet\pcap\PcapDotNet.Core.Extensions.dll
+
+
+ ..\Ethernet\pcap\PcapDotNet.Packets.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Ethernet/Ethernet.sln b/PUP/IFS.sln
similarity index 54%
rename from Ethernet/Ethernet.sln
rename to PUP/IFS.sln
index 96797f4..686ba3a 100644
--- a/Ethernet/Ethernet.sln
+++ b/PUP/IFS.sln
@@ -1,9 +1,9 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.31101.0
+# Visual Studio 14
+VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ethernet", "Ethernet\Ethernet.csproj", "{BFFB20D8-4684-4E44-9521-44F3593525FB}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IFS", "IFS.csproj", "{5C0BBE4B-76AB-4AC1-8691-F19D8D282DCB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -11,10 +11,10 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {BFFB20D8-4684-4E44-9521-44F3593525FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BFFB20D8-4684-4E44-9521-44F3593525FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BFFB20D8-4684-4E44-9521-44F3593525FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BFFB20D8-4684-4E44-9521-44F3593525FB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5C0BBE4B-76AB-4AC1-8691-F19D8D282DCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5C0BBE4B-76AB-4AC1-8691-F19D8D282DCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5C0BBE4B-76AB-4AC1-8691-F19D8D282DCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5C0BBE4B-76AB-4AC1-8691-F19D8D282DCB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/PUP/MiscServicesProtocol.cs b/PUP/MiscServicesProtocol.cs
new file mode 100644
index 0000000..b02eb82
--- /dev/null
+++ b/PUP/MiscServicesProtocol.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+ ///
+ /// Implements PUP Miscellaneous Services (see miscSvcsProto.pdf)
+ /// which include:
+ /// - Date and Time services
+ /// - Mail check
+ /// - Network Directory Lookup
+ /// - Alto Boot protocols
+ /// - Authenticate/Validate
+ ///
+ public class MiscServicesProtocol : PUPProtocolBase
+ {
+ public MiscServicesProtocol()
+ {
+ // TODO:
+ // load host tables, etc.
+ }
+
+ ///
+ /// Called by dispatcher to send incoming data destined for this protocol
+ ///
+ ///
+ public override void RecvData(PUP p)
+ {
+ switch (p.Type)
+ {
+ case PupType.StringTimeRequest:
+ SendStringTimeReply(p);
+ break;
+
+ case PupType.AltoTimeRequest:
+ SendAltoTimeReply(p);
+ break;
+
+ case PupType.AddressLookupRequest:
+ SendAddressLookupReply(p);
+ break;
+
+ case PupType.NameLookupRequest:
+ SendNameLookupReply(p);
+ break;
+
+ default:
+ // Unhandled, TODO: log it
+ break;
+ }
+ }
+
+
+ private void SendStringTimeReply(PUP p)
+ {
+ //
+ // From the spec, the response is:
+ // "A string consisting of the current date and time in the form
+ // '11-SEP-75 15:44:25'"
+ //
+ // It makes no mention of timezone or DST, so I am assuming local time here.
+ // Good enough for government work.
+ //
+ DateTime currentTime = DateTime.Now;
+
+ BCPLString bcplDateString = new BCPLString(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());
+
+ Dispatcher.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;
+
+ // 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);
+ 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);
+
+ PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
+ PUP response = new PUP(PupType.AltoTimeResponse, p.ID, p.SourcePort, localPort, data);
+
+ Dispatcher.Instance.SendPup(response);
+ }
+
+ private void SendAddressLookupReply(PUP p)
+ {
+ //
+ // Need to find more... useful documentation, but here's what I have:
+ // For the request PUP:
+ // A port (6 bytes).
+ //
+ // Response:
+ // A string consisting of an inter-network name expression that matches the request Port.
+ //
+
+ //
+ // I am at this time unsure what exactly an "inter-network name expression" consists of.
+ // Empirically, a simple string name seems to make the Alto happy.
+ //
+
+ //
+ // The request PUP contains a port address, we will check the host and network (and ignore the socket).
+ // and see if we have a match.
+ //
+ PUPPort lookupAddress = new PUPPort(p.Contents, 0);
+ string hostName = DirectoryServices.Instance.AddressLookup(lookupAddress);
+
+ if (!String.IsNullOrEmpty(hostName))
+ {
+ // We have a result, pack the name into the response.
+ BCPLString interNetworkName = new BCPLString(hostName);
+ PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
+ PUP lookupReply = new PUP(PupType.AddressLookupResponse, p.ID, p.SourcePort, localPort, interNetworkName.ToArray());
+
+ Dispatcher.Instance.SendPup(lookupReply);
+ }
+ else
+ {
+ // Unknown host, send an error reply
+ BCPLString errorString = new BCPLString("Unknown host for address.");
+ PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
+ PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, errorString.ToArray());
+
+ Dispatcher.Instance.SendPup(errorReply);
+ }
+ }
+
+ private void SendNameLookupReply(PUP p)
+ {
+ //
+ // For the request PUP:
+ // A string consisting of an inter-network name expression
+ //
+ // Response:
+ // One or more 6-byte blocks containing the address(es) corresponding to the
+ // name expression. Each block is a Pup Port structure, with the network and host numbers in
+ // the first two bytes and the socket number in the last four bytes.
+ //
+
+ //
+ // 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.
+ //
+ BCPLString lookupName = new BCPLString(p.Contents);
+
+ HostAddress address = DirectoryServices.Instance.NameLookup(lookupName.ToString());
+
+ if (address != null)
+ {
+ // We found an address, pack the port into the response.
+ PUPPort lookupPort = new PUPPort(address, 0);
+ 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);
+ }
+ else
+ {
+ // Unknown host, send an error reply
+ BCPLString errorString = new BCPLString("Unknown host for name.");
+ PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
+ PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, errorString.ToArray());
+
+ Dispatcher.Instance.SendPup(errorReply);
+ }
+ }
+ }
+}
diff --git a/PUP/PUP.cs b/PUP/PUP.cs
new file mode 100644
index 0000000..aa185c1
--- /dev/null
+++ b/PUP/PUP.cs
@@ -0,0 +1,316 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+ public enum PupType
+ {
+ // Basic types
+ EchoMe = 1,
+ ImAnEcho = 2,
+ ImABadEcho = 3,
+ Error = 4,
+
+ // BSP/RFC types
+ RFC = 8,
+ Abort = 9,
+ End = 10,
+ EndReply = 11,
+ Data = 16,
+ AData = 17,
+ Ack = 18,
+ Mark = 19,
+ Interrupt = 20,
+ InterruptReply = 21,
+ AMark = 22,
+
+ // Misc. Services types
+ StringTimeRequest = 128,
+ StringTimeReply = 129,
+ TenexTimeRequest = 130,
+ TenexTimeReply = 131,
+ AltoTimeRequestOld = 132,
+ AltoTimeResponseOld = 133,
+ AltoTimeRequest = 134,
+ AltoTimeResponse = 135,
+
+ // Network Lookup
+ NameLookupRequest = 144,
+ NameLookupResponse = 145,
+ DirectoryLookupErrorReply = 146,
+ AddressLookupRequest = 147,
+ AddressLookupResponse = 148,
+
+ // Alto Boot
+ SendBootFileRequest = 164,
+ BootDirectoryRequest = 165,
+ BootDirectoryReply = 166,
+ }
+
+ public struct PUPPort
+ {
+ ///
+ /// Builds a new port address given network, host and socket parameters
+ ///
+ ///
+ ///
+ ///
+ public PUPPort(byte network, byte host, UInt32 socket)
+ {
+ Network = network;
+ Host = host;
+ Socket = socket;
+ }
+
+ ///
+ /// Builds a new port address given a HostAddress and a socket.
+ ///
+ ///
+ ///
+ public PUPPort(HostAddress address, UInt32 socket)
+ {
+ Network = address.Network;
+ Host = address.Host;
+ Socket = socket;
+ }
+
+ ///
+ /// Builds a new port address from an array containing a raw port representaton
+ ///
+ ///
+ ///
+ public PUPPort(byte[] rawData, int offset)
+ {
+ Network = rawData[offset];
+ Host = rawData[offset + 1];
+ Socket = Helpers.ReadUInt(rawData, 2);
+ }
+
+ ///
+ /// Writes this address back out to a raw byte array at the specified offset
+ ///
+ ///
+ ///
+ public void WriteToArray(ref byte[] rawData, int offset)
+ {
+ rawData[offset] = Network;
+ rawData[offset + 1] = Host;
+ Helpers.WriteUInt(ref rawData, Socket, offset + 2);
+ }
+
+ ///
+ /// Same as above, but simply returns a new array instead of writing into an existing one.
+ ///
+ ///
+ public byte[] ToArray()
+ {
+ byte[] a = new byte[6];
+ WriteToArray(ref a, 0);
+ return a;
+ }
+
+ public byte Network;
+ public byte Host;
+ public UInt32 Socket;
+ }
+
+ public class PUP
+ {
+ ///
+ /// Construct a new packet from the supplied data.
+ ///
+ ///
+ public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents)
+ {
+ _rawData = null;
+
+ // Ensure content length is <= 532 bytes. (Technically larger PUPs are allowed,
+ // but conventionally they are not used and I want to keep things safe.)
+ if (contents.Length > MAX_PUP_SIZE)
+ {
+ throw new InvalidOperationException("PUP size must not exceed 532 bytes.");
+ }
+
+ TransportControl = 0;
+ Type = type;
+ ID = id;
+ DestinationPort = destination;
+ SourcePort = source;
+ Contents = contents;
+ Length = (ushort)(PUP_HEADER_SIZE + PUP_CHECKSUM_SIZE + contents.Length);
+
+ // Stuff data into raw array
+ _rawData = new byte[Length];
+ Helpers.WriteUShort(ref _rawData, Length, 0);
+ _rawData[2] = TransportControl;
+ _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);
+
+ // Calculate the checksum and stow it
+ Checksum = CalculateChecksum();
+ Helpers.WriteUShort(ref _rawData, Checksum, Length - 2);
+ }
+
+ ///
+ /// Same as above, but with no content (i.e. a zero-byte payload)
+ ///
+ ///
+ ///
+ ///
+ ///
+ public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source) :
+ this(type, id, destination, source, new byte[0])
+ {
+
+ }
+
+ ///
+ /// Load in an existing packet from a stream
+ ///
+ ///
+ public PUP(MemoryStream stream)
+ {
+ _rawData = new byte[stream.Length];
+ stream.Read(_rawData, 0, (int)stream.Length);
+
+ // Read fields in. TODO: investigate more efficient ways to do this.
+ Length = Helpers.ReadUShort(_rawData, 0);
+
+ // Sanity check size:
+ if (Length > stream.Length)
+ {
+ throw new InvalidOperationException("Length field in PUP is invalid.");
+ }
+
+ TransportControl = _rawData[2];
+ 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);
+
+ // Validate checksum
+ ushort cChecksum = CalculateChecksum();
+
+ if (cChecksum != Checksum)
+ {
+ throw new InvalidOperationException(String.Format("PUP checksum is invalid. ({0} vs {1}", Checksum, cChecksum));
+ }
+
+ }
+
+ public byte[] RawData
+ {
+ get { return _rawData; }
+ }
+
+ private ushort CalculateChecksum()
+ {
+
+ 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)
+ {
+ ushort word = Helpers.ReadUShort(_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;
+ }
+
+ if (cksm.scksm.cksm == 0xffff)
+ {
+ cksm.scksm.cksm = 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;
+ }
+
+ public readonly ushort Length;
+ public readonly byte TransportControl;
+ public readonly PupType Type;
+ public readonly UInt32 ID;
+ public readonly PUPPort DestinationPort;
+ public readonly PUPPort SourcePort;
+ public readonly byte[] Contents;
+ public readonly ushort Checksum;
+
+ private byte[] _rawData;
+
+ private const int MAX_PUP_SIZE = 532;
+ private const int PUP_HEADER_SIZE = 20;
+ private const int PUP_CHECKSUM_SIZE = 2;
+
+
+ }
+
+ public static class Helpers
+ {
+ public static ushort ReadUShort(byte[] data, int offset)
+ {
+ return (ushort)((data[offset] << 8) | data[offset + 1]);
+ }
+
+ 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 void WriteUShort(ref byte[] data, ushort s, int offset)
+ {
+ data[offset] = (byte)(s >> 8);
+ data[offset + 1] = (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;
+ }
+ }
+}
diff --git a/PUP/PUPProtocolBase.cs b/PUP/PUPProtocolBase.cs
new file mode 100644
index 0000000..9cf1dfb
--- /dev/null
+++ b/PUP/PUPProtocolBase.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+ public enum ConnectionType
+ {
+ Connectionless, /* echo, name resolution, etc. */
+ BSP, /* FTP, Telnet, CopyDisk, etc. */
+ }
+
+ public struct PUPProtocolEntry
+ {
+ public PUPProtocolEntry(string friendlyName, UInt32 socket, ConnectionType connectionType, PUPProtocolBase implementation)
+ {
+ FriendlyName = friendlyName;
+ Socket = socket;
+ ConnectionType = connectionType;
+ ProtocolImplementation = implementation;
+ }
+
+ ///
+ /// Indicates the 'friendly' name for the protocol.
+ ///
+ public string FriendlyName;
+
+ ///
+ /// Indicates the socket used by the protocol
+ ///
+ public UInt32 Socket;
+
+ ///
+ /// Indicates the type of connection (connectionless or BSP-based)
+ ///
+ public ConnectionType ConnectionType;
+
+ public PUPProtocolBase ProtocolImplementation;
+ }
+
+ ///
+ /// Base class for all PUP-based protocols.
+ ///
+ public abstract class PUPProtocolBase
+ {
+ public PUPProtocolBase()
+ {
+
+ }
+
+ ///
+ /// Called by dispatcher to send incoming data destined for this protocol.
+ ///
+ ///
+ public abstract void RecvData(PUP p);
+
+ }
+}
diff --git a/PUP/Properties/AssemblyInfo.cs b/PUP/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..df0ab97
--- /dev/null
+++ b/PUP/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PUP")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Vulcan Inc.")]
+[assembly: AssemblyProduct("PUP")]
+[assembly: AssemblyCopyright("Copyright © Vulcan Inc. 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("60ac4040-d43f-4d54-ada8-2ba64163973d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/PUP/Transport/Ethernet.cs b/PUP/Transport/Ethernet.cs
new file mode 100644
index 0000000..62d023e
--- /dev/null
+++ b/PUP/Transport/Ethernet.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using PcapDotNet.Core;
+using PcapDotNet.Packets;
+
+namespace IFS.Transport
+{
+ public struct EthernetInterface
+ {
+ public EthernetInterface(string name, string description)
+ {
+ Name = name;
+ Description = description;
+ }
+
+ public string Name;
+ public string Description;
+ }
+
+ ///
+ /// Defines interface "to the metal" (raw ethernet frames) which may wrap the underlying transport (for example, winpcap)
+ ///
+ public class Ethernet : IPacketInterface
+ {
+ public Ethernet(EthernetInterface iface, HandlePacket callback)
+ {
+ AttachInterface(iface);
+ _callback = callback;
+ }
+
+ public static List EnumerateDevices()
+ {
+ List interfaces = new List();
+
+ foreach(LivePacketDevice device in LivePacketDevice.AllLocalMachine)
+ {
+ interfaces.Add(new EthernetInterface(device.Name, device.Description));
+ }
+
+ return interfaces;
+ }
+
+ public void Open(bool promiscuous, int timeout)
+ {
+ _communicator = _interface.Open(0xffff, promiscuous ? PacketDeviceOpenAttributes.Promiscuous : PacketDeviceOpenAttributes.None, timeout);
+ }
+
+ ///
+ /// Begin receiving packets, forever.
+ ///
+ public void BeginReceive()
+ {
+ _communicator.ReceivePackets(-1, ReceiveCallback);
+ }
+
+ private void ReceiveCallback(Packet p)
+ {
+ _callback(p);
+ }
+
+ private void AttachInterface(EthernetInterface iface)
+ {
+ _interface = null;
+
+ // Find the specified device by name
+ foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
+ {
+ if (device.Name == iface.Name)
+ {
+ _interface = device;
+ break;
+ }
+ }
+
+ if (_interface == null)
+ {
+ throw new InvalidOperationException("Requested interface not found.");
+ }
+ }
+
+ private LivePacketDevice _interface;
+ private PacketCommunicator _communicator;
+ private HandlePacket _callback;
+
+ }
+}
diff --git a/PUP/Transport/PacketInterface.cs b/PUP/Transport/PacketInterface.cs
new file mode 100644
index 0000000..f8a3329
--- /dev/null
+++ b/PUP/Transport/PacketInterface.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IFS.Transport
+{
+ ///
+ /// PacketInterface provides an abstraction over a transport (Ethernet, IP, Carrier Pigeon)
+ /// which can provide raw packets.
+ ///
+ interface IPacketInterface
+ {
+ }
+}