mirror of
https://github.com/livingcomputermuseum/IFS.git
synced 2026-03-08 03:49:22 +00:00
Fleshed out implementation.
This commit is contained in:
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ethernet
|
||||
namespace BSP
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
36
BSP/BSP/Properties/AssemblyInfo.cs
Normal file
36
BSP/BSP/Properties/AssemblyInfo.cs
Normal file
@@ -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")]
|
||||
6
BreathOfLife/BreathOfLife/App.config
Normal file
6
BreathOfLife/BreathOfLife/App.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
||||
</startup>
|
||||
</configuration>
|
||||
58
BreathOfLife/BreathOfLife/BreathOfLife.csproj
Normal file
58
BreathOfLife/BreathOfLife/BreathOfLife.csproj
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{BD5D7CF7-52BA-4960-A60F-75EEECCEF775}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>BreathOfLife</RootNamespace>
|
||||
<AssemblyName>BreathOfLife</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</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.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
15
BreathOfLife/BreathOfLife/Program.cs
Normal file
15
BreathOfLife/BreathOfLife/Program.cs
Normal file
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
36
BreathOfLife/BreathOfLife/Properties/AssemblyInfo.cs
Normal file
36
BreathOfLife/BreathOfLife/Properties/AssemblyInfo.cs
Normal file
@@ -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")]
|
||||
@@ -20,6 +20,7 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
@@ -30,6 +31,18 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="PcapDotNet.Base">
|
||||
<HintPath>..\pcap\PcapDotNet.Base.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PcapDotNet.Core">
|
||||
<HintPath>..\pcap\PcapDotNet.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PcapDotNet.Core.Extensions">
|
||||
<HintPath>..\pcap\PcapDotNet.Core.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PcapDotNet.Packets">
|
||||
<HintPath>..\pcap\PcapDotNet.Packets.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
@@ -39,7 +52,7 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Class1.cs" />
|
||||
<Compile Include="Ethernet.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
89
PUP/BCPLString.cs
Normal file
89
PUP/BCPLString.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a new BCPL string from the raw representation
|
||||
/// </summary>
|
||||
/// <param name="rawData"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a native representation of the BCPL string
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the raw representation of the BCPL string
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public byte[] ToArray()
|
||||
{
|
||||
byte[] a = new byte[_string.Length + 1];
|
||||
|
||||
a[0] = (byte)_string.Length;
|
||||
_string.CopyTo(a, 1);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
private byte[] _string;
|
||||
}
|
||||
}
|
||||
301
PUP/BSPManager.cs
Normal file
301
PUP/BSPManager.cs
Normal file
@@ -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; }
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends data into the input queue (called from BSPManager to place new PUP data into the BSP stream)
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends data to the channel (i.e. to the client). Will block 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
|
||||
|
||||
// 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<byte> _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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class BSPManager
|
||||
{
|
||||
static BSPManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when BSP-based protocols receive data.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// null if no new channel is created due to the sent PUP (not an RFC)
|
||||
/// a new BSPChannel if one has been created based on the PUP (new RFC)
|
||||
/// </returns>
|
||||
/// <param name="p"></param>
|
||||
public static 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<UInt32, BSPChannel> _activeChannels;
|
||||
}
|
||||
}
|
||||
25
PUP/CopyDiskServer.cs
Normal file
25
PUP/CopyDiskServer.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Called by dispatcher to send incoming data destined for this protocol.
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
public override void RecvData(PUP p)
|
||||
{
|
||||
BSPChannel newChannel = BSPManager.RecvData(p);
|
||||
|
||||
if (newChannel != null)
|
||||
{
|
||||
// spwan new worker thread with new BSP channel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
PUP/DirectoryServices.cs
Normal file
50
PUP/DirectoryServices.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a basic database used by the Misc. Services to do name lookup and whatnot.
|
||||
///
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
96
PUP/Dispatcher.cs
Normal file
96
PUP/Dispatcher.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches incoming PUPs to the right protocol handler; sends outgoing PUPs over the network.
|
||||
/// </summary>
|
||||
public class Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Private Constructor for this class, enforcing Singleton usage.
|
||||
/// </summary>
|
||||
private Dispatcher()
|
||||
{
|
||||
_dispatchMap = new Dictionary<uint, PUPProtocolEntry>();
|
||||
|
||||
_packetInterface = new Ethernet(iface, OnPacketReceived);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for singleton instance of this class.
|
||||
/// </summary>
|
||||
public static Dispatcher Instance
|
||||
{
|
||||
get { return _instance; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new protocol with the dispatcher
|
||||
/// </summary>
|
||||
/// <param name="reg"></param>
|
||||
/// <param name="impl"></param>
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Our interface to some kind of network
|
||||
/// </summary>
|
||||
private IPacketInterface _packetInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Map from socket to protocol implementation
|
||||
/// </summary>
|
||||
private Dictionary<UInt32, PUPProtocolEntry> _dispatchMap;
|
||||
|
||||
private static Dispatcher _instance = new Dispatcher();
|
||||
}
|
||||
}
|
||||
37
PUP/EchoProtocol.cs
Normal file
37
PUP/EchoProtocol.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the PUP Echo Protocol.
|
||||
/// </summary>
|
||||
public class EchoProtocol : PUPProtocolBase
|
||||
{
|
||||
public EchoProtocol()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by dispatcher to send incoming data destined for this protocol
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
23
PUP/Entrypoint.cs
Normal file
23
PUP/Entrypoint.cs
Normal file
@@ -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()));
|
||||
|
||||
}
|
||||
}
|
||||
80
PUP/IFS.csproj
Normal file
80
PUP/IFS.csproj
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{5C0BBE4B-76AB-4AC1-8691-F19D8D282DCB}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>IFS</RootNamespace>
|
||||
<AssemblyName>IFS</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="PcapDotNet.Base">
|
||||
<HintPath>..\Ethernet\pcap\PcapDotNet.Base.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PcapDotNet.Core">
|
||||
<HintPath>..\Ethernet\pcap\PcapDotNet.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PcapDotNet.Core.Extensions">
|
||||
<HintPath>..\Ethernet\pcap\PcapDotNet.Core.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PcapDotNet.Packets">
|
||||
<HintPath>..\Ethernet\pcap\PcapDotNet.Packets.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BCPLString.cs" />
|
||||
<Compile Include="BSPManager.cs" />
|
||||
<Compile Include="CopyDiskServer.cs" />
|
||||
<Compile Include="DirectoryServices.cs" />
|
||||
<Compile Include="EchoProtocol.cs" />
|
||||
<Compile Include="Entrypoint.cs" />
|
||||
<Compile Include="MiscServicesProtocol.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="Transport\PacketInterface.cs" />
|
||||
</ItemGroup>
|
||||
<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.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -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
|
||||
210
PUP/MiscServicesProtocol.cs
Normal file
210
PUP/MiscServicesProtocol.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements PUP Miscellaneous Services (see miscSvcsProto.pdf)
|
||||
/// which include:
|
||||
/// - Date and Time services
|
||||
/// - Mail check
|
||||
/// - Network Directory Lookup
|
||||
/// - Alto Boot protocols
|
||||
/// - Authenticate/Validate
|
||||
/// </summary>
|
||||
public class MiscServicesProtocol : PUPProtocolBase
|
||||
{
|
||||
public MiscServicesProtocol()
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
316
PUP/PUP.cs
Normal file
316
PUP/PUP.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a new port address given network, host and socket parameters
|
||||
/// </summary>
|
||||
/// <param name="network"></param>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="socket"></param>
|
||||
public PUPPort(byte network, byte host, UInt32 socket)
|
||||
{
|
||||
Network = network;
|
||||
Host = host;
|
||||
Socket = socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a new port address given a HostAddress and a socket.
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="socket"></param>
|
||||
public PUPPort(HostAddress address, UInt32 socket)
|
||||
{
|
||||
Network = address.Network;
|
||||
Host = address.Host;
|
||||
Socket = socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a new port address from an array containing a raw port representaton
|
||||
/// </summary>
|
||||
/// <param name="rawData"></param>
|
||||
/// <param name="offset"></param>
|
||||
public PUPPort(byte[] rawData, int offset)
|
||||
{
|
||||
Network = rawData[offset];
|
||||
Host = rawData[offset + 1];
|
||||
Socket = Helpers.ReadUInt(rawData, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes this address back out to a raw byte array at the specified offset
|
||||
/// </summary>
|
||||
/// <param name="rawData"></param>
|
||||
/// <param name="offset"></param>
|
||||
public void WriteToArray(ref byte[] rawData, int offset)
|
||||
{
|
||||
rawData[offset] = Network;
|
||||
rawData[offset + 1] = Host;
|
||||
Helpers.WriteUInt(ref rawData, Socket, offset + 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as above, but simply returns a new array instead of writing into an existing one.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as above, but with no content (i.e. a zero-byte payload)
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="source"></param>
|
||||
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source) :
|
||||
this(type, id, destination, source, new byte[0])
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load in an existing packet from a stream
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
PUP/PUPProtocolBase.cs
Normal file
60
PUP/PUPProtocolBase.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the 'friendly' name for the protocol.
|
||||
/// </summary>
|
||||
public string FriendlyName;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the socket used by the protocol
|
||||
/// </summary>
|
||||
public UInt32 Socket;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the type of connection (connectionless or BSP-based)
|
||||
/// </summary>
|
||||
public ConnectionType ConnectionType;
|
||||
|
||||
public PUPProtocolBase ProtocolImplementation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all PUP-based protocols.
|
||||
/// </summary>
|
||||
public abstract class PUPProtocolBase
|
||||
{
|
||||
public PUPProtocolBase()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by dispatcher to send incoming data destined for this protocol.
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
public abstract void RecvData(PUP p);
|
||||
|
||||
}
|
||||
}
|
||||
36
PUP/Properties/AssemblyInfo.cs
Normal file
36
PUP/Properties/AssemblyInfo.cs
Normal file
@@ -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")]
|
||||
90
PUP/Transport/Ethernet.cs
Normal file
90
PUP/Transport/Ethernet.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines interface "to the metal" (raw ethernet frames) which may wrap the underlying transport (for example, winpcap)
|
||||
/// </summary>
|
||||
public class Ethernet : IPacketInterface
|
||||
{
|
||||
public Ethernet(EthernetInterface iface, HandlePacket callback)
|
||||
{
|
||||
AttachInterface(iface);
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
public static List<EthernetInterface> EnumerateDevices()
|
||||
{
|
||||
List<EthernetInterface> interfaces = new List<EthernetInterface>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin receiving packets, forever.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
16
PUP/Transport/PacketInterface.cs
Normal file
16
PUP/Transport/PacketInterface.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS.Transport
|
||||
{
|
||||
/// <summary>
|
||||
/// PacketInterface provides an abstraction over a transport (Ethernet, IP, Carrier Pigeon)
|
||||
/// which can provide raw packets.
|
||||
/// </summary>
|
||||
interface IPacketInterface
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user