mirror of
https://github.com/livingcomputermuseum/IFS.git
synced 2026-03-09 20:38:17 +00:00
Adding built-in support for Ken Shirriff's BeagleBone-based Alto Ethernet Interface.
Adding revised version of MicrocodeBootRequest, to support booting Dolphin and Dorado hardware.
This commit is contained in:
@@ -10,11 +10,9 @@ CopyDiskRoot = c:\ifs\copydisk
|
||||
BootRoot = c:\ifs\boot
|
||||
MailRoot = c:\ifs\mail
|
||||
|
||||
# InterfaceType defines the type of interface for local networking,
|
||||
# (either RAW or UDP)
|
||||
# and must be the same as other devices or software on the network that
|
||||
# wishes to talk to this server.
|
||||
InterfaceType = raw
|
||||
# InterfaceType defines the type of interface(s) to use for local networking,
|
||||
# (one or more of RAW, UDP, or 3MBIT)
|
||||
InterfaceTypes = raw
|
||||
|
||||
# The name of the network interface to use for local networking.
|
||||
# This is the name reported by "ipconfig"
|
||||
@@ -25,6 +23,9 @@ InterfaceName = Ethernet
|
||||
# (gateway ports are specified in networks.txt)
|
||||
UDPPort = 42424
|
||||
|
||||
# Whether to run IFS services or just bridge interfaces
|
||||
RunIFSServices = true
|
||||
|
||||
# Defines the address for this IFS server.
|
||||
# An entry for this IFS server's network must be
|
||||
# present in networks.txt
|
||||
|
||||
@@ -83,9 +83,9 @@ namespace IFS
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of interface (UDP or RAW) to communicate over
|
||||
/// The type of interface(s) (UDP, RAW, or 3mbit) to communicate over
|
||||
/// </summary>
|
||||
public static readonly string InterfaceType;
|
||||
public static readonly string InterfaceTypes;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the network interface to use
|
||||
@@ -97,6 +97,11 @@ namespace IFS
|
||||
/// </summary>
|
||||
public static readonly int UDPPort;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to run IFS Services or just bridge interfaces.
|
||||
/// </summary>
|
||||
public static readonly bool RunIFSServices;
|
||||
|
||||
/// <summary>
|
||||
/// The network that this server lives on
|
||||
/// </summary>
|
||||
@@ -208,6 +213,13 @@ namespace IFS
|
||||
{
|
||||
switch (field.FieldType.Name)
|
||||
{
|
||||
case "Boolean":
|
||||
{
|
||||
bool b = bool.Parse(value);
|
||||
field.SetValue(null, b);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Int32":
|
||||
{
|
||||
int v = int.Parse(value);
|
||||
@@ -248,7 +260,7 @@ namespace IFS
|
||||
"ifs.cfg line {0}: Unknown configuration parameter '{1}'.", lineNumber, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,20 +15,12 @@
|
||||
along with IFS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using IFS.Boot;
|
||||
using IFS.CopyDisk;
|
||||
using IFS.FTP;
|
||||
using IFS.Gateway;
|
||||
using IFS.IfsConsole;
|
||||
using IFS.Transport;
|
||||
using PcapDotNet.Core;
|
||||
using PcapDotNet.Core.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
@@ -36,9 +28,9 @@ namespace IFS
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
PrintHerald();
|
||||
PrintHerald();
|
||||
|
||||
RegisterInterface();
|
||||
RegisterInterfaces();
|
||||
|
||||
// This runs forever, or until the user tells us to exit.
|
||||
RunCommandPrompt();
|
||||
@@ -51,61 +43,69 @@ namespace IFS
|
||||
|
||||
private static void PrintHerald()
|
||||
{
|
||||
Console.WriteLine("LCM+L IFS v0.3, 11/17/2020.");
|
||||
Console.WriteLine("(c) 2015-2020 Living Computers: Museum+Labs.");
|
||||
Console.WriteLine("Bug reports to joshd@livingcomputers.org");
|
||||
Console.WriteLine($"LCM+L IFS {typeof(Entrypoint).Assembly.GetName().Version}, 9/30/2023");
|
||||
Console.WriteLine("(c) 2015-2020 Living Computers: Museum+Labs, 2020-2023 Josh Dersch");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private static void RegisterInterface()
|
||||
private static void RegisterInterfaces()
|
||||
{
|
||||
bool bFound = false;
|
||||
|
||||
switch (Configuration.InterfaceType.ToLowerInvariant())
|
||||
string[] selectedInterfaces = Configuration.InterfaceTypes.ToLowerInvariant().Split(new char[] { ' ', '\t', ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (string ifaceName in selectedInterfaces)
|
||||
{
|
||||
case "udp":
|
||||
// Find matching network interface
|
||||
{
|
||||
NetworkInterface[] ifaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
foreach(NetworkInterface iface in ifaces)
|
||||
switch (ifaceName)
|
||||
{
|
||||
case "udp":
|
||||
// Find matching network interface
|
||||
{
|
||||
if (iface.Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
|
||||
NetworkInterface[] ifaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
foreach (NetworkInterface iface in ifaces)
|
||||
{
|
||||
Router.Instance.RegisterUDPInterface(iface);
|
||||
bFound = true;
|
||||
break;
|
||||
if (iface.Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
|
||||
{
|
||||
Router.Instance.RegisterUDPInterface(iface);
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case "raw":
|
||||
// Find matching RAW interface
|
||||
{
|
||||
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
|
||||
case "raw":
|
||||
// Find matching RAW interface
|
||||
{
|
||||
if (device.GetNetworkInterface() != null &&
|
||||
device.GetNetworkInterface().Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
|
||||
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
|
||||
{
|
||||
Router.Instance.RegisterRAWInterface(device);
|
||||
bFound = true;
|
||||
break;
|
||||
if (device.GetNetworkInterface() != null &&
|
||||
device.GetNetworkInterface().Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
|
||||
{
|
||||
Router.Instance.RegisterRAWInterface(device);
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
case "3mbit":
|
||||
Router.Instance.RegisterBeagleBoneInterface();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidConfigurationException(
|
||||
String.Format("The specified interface type ({0}) is invalid.", Configuration.InterfaceTypes));
|
||||
}
|
||||
|
||||
// Not found.
|
||||
if (!bFound)
|
||||
{
|
||||
throw new InvalidConfigurationException(
|
||||
String.Format("The specified interface type ({0}) is invalid.", Configuration.InterfaceType));
|
||||
}
|
||||
|
||||
// Not found.
|
||||
if (!bFound)
|
||||
{
|
||||
throw new InvalidConfigurationException(
|
||||
String.Format("The specified network interface ({0}) is invalid or unusable by IFS.", Configuration.InterfaceName));
|
||||
String.Format("The specified network interface ({0}) is invalid or unusable by IFS.", Configuration.InterfaceName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ using System.Threading;
|
||||
|
||||
namespace IFS.Gateway
|
||||
{
|
||||
public delegate void RoutePupCallback(PUP pup, bool route);
|
||||
public delegate void ReceivedPacketCallback(MemoryStream packetStream, IPacketInterface receivingInterface);
|
||||
|
||||
/// <summary>
|
||||
/// Implements gateway services, routing PUPs intended for other networks to
|
||||
@@ -50,6 +50,7 @@ namespace IFS.Gateway
|
||||
{
|
||||
_localProtocolDispatcher = new PUPProtocolDispatcher();
|
||||
_routingTable = new RoutingTable();
|
||||
_packetInterfaces = new List<IPacketInterface>();
|
||||
|
||||
//
|
||||
// Look up our own network in the table and get our port.
|
||||
@@ -96,7 +97,11 @@ namespace IFS.Gateway
|
||||
public void Shutdown()
|
||||
{
|
||||
_localProtocolDispatcher.Shutdown();
|
||||
_pupPacketInterface.Shutdown();
|
||||
|
||||
foreach (IPacketInterface iface in _packetInterfaces)
|
||||
{
|
||||
iface.Shutdown();
|
||||
}
|
||||
|
||||
if (_gatewayUdpClient != null)
|
||||
{
|
||||
@@ -109,19 +114,22 @@ namespace IFS.Gateway
|
||||
public void RegisterRAWInterface(LivePacketDevice iface)
|
||||
{
|
||||
Ethernet enet = new Ethernet(iface);
|
||||
|
||||
_pupPacketInterface = enet;
|
||||
_rawPacketInterface = enet;
|
||||
_pupPacketInterface.RegisterRouterCallback(RouteIncomingLocalPacket);
|
||||
_packetInterfaces.Add(enet);
|
||||
enet.RegisterRouterCallback(HandleIncomingPacket);
|
||||
}
|
||||
|
||||
public void RegisterUDPInterface(NetworkInterface iface)
|
||||
{
|
||||
UDPEncapsulation udp = new UDPEncapsulation(iface);
|
||||
_packetInterfaces.Add(udp);
|
||||
udp.RegisterRouterCallback(HandleIncomingPacket);
|
||||
}
|
||||
|
||||
_pupPacketInterface = udp;
|
||||
_rawPacketInterface = udp;
|
||||
_pupPacketInterface.RegisterRouterCallback(RouteIncomingLocalPacket);
|
||||
public void RegisterBeagleBoneInterface()
|
||||
{
|
||||
Ether3MbitInterface bbInterface = new Ether3MbitInterface();
|
||||
_packetInterfaces.Add(bbInterface);
|
||||
bbInterface.RegisterRouterCallback(HandleIncomingPacket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -142,9 +150,8 @@ namespace IFS.Gateway
|
||||
/// <param name="frameType"></param>
|
||||
public void Send(byte[] data, byte source, byte destination, ushort frameType)
|
||||
{
|
||||
if (_rawPacketInterface != null)
|
||||
{
|
||||
_rawPacketInterface.Send(data, source, destination, frameType);
|
||||
foreach(IPacketInterface iface in _packetInterfaces) {
|
||||
iface.Send(data, source, destination, frameType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +170,10 @@ namespace IFS.Gateway
|
||||
if (p.DestinationPort.Network == 0 ||
|
||||
p.DestinationPort.Network == DirectoryServices.Instance.LocalNetwork)
|
||||
{
|
||||
_pupPacketInterface.Send(p);
|
||||
foreach (IPacketInterface iface in _packetInterfaces)
|
||||
{
|
||||
iface.Send(p);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -204,6 +214,57 @@ namespace IFS.Gateway
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles an encapsulated 3mbit frame incoming from the receiver.
|
||||
/// </summary>
|
||||
/// <param name="packetStream"></param>
|
||||
private void HandleIncomingPacket(MemoryStream packetStream, IPacketInterface receivingInterface)
|
||||
{
|
||||
// Read the length prefix (in words), convert to bytes.
|
||||
// Subtract off 2 words for the ethernet header
|
||||
int length = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())) * 2 - 4;
|
||||
|
||||
// Read the address (1st word of 3mbit packet)
|
||||
byte destination = (byte)packetStream.ReadByte();
|
||||
byte source = (byte)packetStream.ReadByte();
|
||||
|
||||
// Read the type and switch on it
|
||||
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
|
||||
|
||||
//
|
||||
// Ensure this is a packet we're interested in.
|
||||
//
|
||||
if (Configuration.RunIFSServices && // We're servicing packets
|
||||
etherType3mbit == PupPacketBuilder.PupFrameType && // it's a PUP
|
||||
(destination == DirectoryServices.Instance.LocalHost || // for us, or...
|
||||
destination == 0)) // broadcast
|
||||
{
|
||||
try
|
||||
{
|
||||
PUP pup = new PUP(packetStream, length);
|
||||
RouteIncomingLocalPacket(pup, destination != 0);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// An error occurred, log it.
|
||||
Log.Write(LogType.Error, LogComponent.PUP, "Error handling PUP: {0}", e.Message);
|
||||
}
|
||||
}
|
||||
else if (!Configuration.RunIFSServices)
|
||||
{
|
||||
// Bridge the packet through all registered interfaces other than the one it came in on
|
||||
foreach (IPacketInterface iface in _packetInterfaces)
|
||||
{
|
||||
if (iface != receivingInterface)
|
||||
{
|
||||
packetStream.Seek(0, SeekOrigin.Begin);
|
||||
Console.WriteLine("Sending to {0}", iface);
|
||||
iface.Send(packetStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RoutePacketExternally(PUP p)
|
||||
{
|
||||
RoutingTableEntry destinationNetworkEntry = _routingTable.GetAddressForNetworkNumber(p.DestinationPort.Network);
|
||||
@@ -270,7 +331,10 @@ namespace IFS.Gateway
|
||||
if (p.DestinationPort.Host == DirectoryServices.Instance.LocalHostAddress.Host || // us specifically
|
||||
p.DestinationPort.Host == 0) // broadcast
|
||||
{
|
||||
_localProtocolDispatcher.ReceivePUP(p);
|
||||
if (Configuration.RunIFSServices)
|
||||
{
|
||||
_localProtocolDispatcher.ReceivePUP(p);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -279,7 +343,10 @@ namespace IFS.Gateway
|
||||
if (p.DestinationPort.Host != DirectoryServices.Instance.LocalHostAddress.Host || // not us
|
||||
p.DestinationPort.Host == 0) // broadcast
|
||||
{
|
||||
_pupPacketInterface.Send(p);
|
||||
foreach (IPacketInterface iface in _packetInterfaces)
|
||||
{
|
||||
iface.Send(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -395,14 +462,9 @@ namespace IFS.Gateway
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Our interface to a facility that can transmit/receive PUPs
|
||||
/// The various interfaces we use to send and receive 3mbit ethernet packets, encapsulated or otherwise.
|
||||
/// </summary>
|
||||
private IPupPacketInterface _pupPacketInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Our interface to a facility that can transmit raw Ethernet frames
|
||||
/// </summary>
|
||||
private IRawPacketInterface _rawPacketInterface;
|
||||
private List<IPacketInterface> _packetInterfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Our UdpClient for sending PUPs to external networks.
|
||||
|
||||
@@ -124,11 +124,13 @@
|
||||
<Compile Include="MiscServicesProtocol.cs" />
|
||||
<Compile Include="Serializer.cs" />
|
||||
<Compile Include="SocketIDGenerator.cs" />
|
||||
<Compile Include="Transport\3MbitAdapter.cs" />
|
||||
<Compile Include="Transport\Ethernet.cs" />
|
||||
<Compile Include="PUPProtocolBase.cs" />
|
||||
<Compile Include="PUP.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="PUPProtocolDispatcher.cs" />
|
||||
<Compile Include="Transport\PacketBuilder.cs" />
|
||||
<Compile Include="Transport\PacketInterface.cs" />
|
||||
<Compile Include="Transport\UDP.cs" />
|
||||
</ItemGroup>
|
||||
@@ -151,6 +153,12 @@
|
||||
<Content Include="Conf\hosts.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="etherdata.bin">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="ethertext.bin">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
@@ -43,8 +43,9 @@ namespace IFS.Logging
|
||||
UDP = 0x800,
|
||||
Mail = 0x1000,
|
||||
Routing = 0x2000,
|
||||
E3Mbit = 0x4000,
|
||||
|
||||
Configuration = 0x4000,
|
||||
Configuration = 0x8000,
|
||||
All = 0x7fffffff
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace IFS
|
||||
{
|
||||
@@ -71,7 +70,7 @@ namespace IFS
|
||||
{
|
||||
public MiscServicesProtocol()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -139,8 +138,8 @@ namespace IFS
|
||||
//
|
||||
DateTime currentTime = DateTime.Now;
|
||||
|
||||
byte[] timeString = Helpers.StringToArray(currentTime.ToString("dd-MMM-yy HH:mm:ss"));
|
||||
|
||||
byte[] timeString = Helpers.StringToArray(currentTime.ToString("dd-MMM-yy HH:mm:ss"));
|
||||
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
|
||||
PUP response = new PUP(PupType.StringTimeReply, p.ID, p.SourcePort, localPort, timeString);
|
||||
|
||||
@@ -148,7 +147,7 @@ namespace IFS
|
||||
}
|
||||
|
||||
private void SendAltoTimeReply(PUP p)
|
||||
{
|
||||
{
|
||||
// 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.
|
||||
//
|
||||
@@ -167,8 +166,8 @@ namespace IFS
|
||||
|
||||
// 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);
|
||||
|
||||
DateTime altoEpoch = new DateTime(1901, 1, 1);
|
||||
|
||||
TimeSpan timeSinceAltoEpoch = new TimeSpan(currentTime.Ticks - altoEpoch.Ticks);
|
||||
|
||||
UInt32 altoTime = (UInt32)timeSinceAltoEpoch.TotalSeconds;
|
||||
@@ -178,9 +177,9 @@ namespace IFS
|
||||
time.DateTime = altoTime;
|
||||
time.TimeZone = 0; // Hardcoded to GMT
|
||||
time.DSTStart = 366; // DST not specified yet
|
||||
time.DSTEnd = 366;
|
||||
time.DSTEnd = 366;
|
||||
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
|
||||
|
||||
// Response must contain our network number; this is used to tell clients what network they're on if they don't already know.
|
||||
PUPPort remotePort = new PUPPort(DirectoryServices.Instance.LocalNetwork, p.SourcePort.Host, p.SourcePort.Socket);
|
||||
@@ -218,7 +217,7 @@ namespace IFS
|
||||
// We have a result, pack the name into the response.
|
||||
// NOTE: This is *not* a BCPL string, just the raw characters.
|
||||
byte[] interNetworkName = Helpers.StringToArray(hostName);
|
||||
|
||||
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
|
||||
PUP lookupReply = new PUP(PupType.AddressLookupResponse, p.ID, p.SourcePort, localPort, interNetworkName);
|
||||
|
||||
@@ -239,8 +238,8 @@ namespace IFS
|
||||
{
|
||||
//
|
||||
// For the request PUP:
|
||||
// A string consisting of an inter-network name expression.
|
||||
// NOTE: This is *not* a BCPL string, just the raw characters.
|
||||
// A string consisting of an inter-network name expression.
|
||||
// NOTE: This is *not* a BCPL string, just the raw characters.
|
||||
//
|
||||
// Response:
|
||||
// One or more 6-byte blocks containing the address(es) corresponding to the
|
||||
@@ -315,7 +314,7 @@ namespace IFS
|
||||
|
||||
List<BootFileEntry> bootFiles = BootServer.EnumerateBootFiles();
|
||||
|
||||
foreach(BootFileEntry entry in bootFiles)
|
||||
foreach (BootFileEntry entry in bootFiles)
|
||||
{
|
||||
BootDirectoryBlock block;
|
||||
block.FileNumber = entry.BootNumber;
|
||||
@@ -323,7 +322,7 @@ namespace IFS
|
||||
block.FileName = new BCPLString(entry.Filename);
|
||||
|
||||
byte[] serialized = Serializer.Serialize(block);
|
||||
|
||||
|
||||
//
|
||||
// If this block fits into the current PUP, add it to the stream, otherwise send off the current PUP
|
||||
// and start a new one.
|
||||
@@ -375,7 +374,7 @@ namespace IFS
|
||||
|
||||
int passwordOffset = (userName.Length % 2) == 0 ? userName.Length : userName.Length + 1;
|
||||
string password = Helpers.MesaArrayToString(p.Contents, passwordOffset + 4);
|
||||
|
||||
|
||||
UserToken token = Authentication.Authenticate(userName, password);
|
||||
|
||||
if (token == null)
|
||||
@@ -411,7 +410,7 @@ namespace IFS
|
||||
// If mailbox name has a host/registry appended, we will strip it off.
|
||||
// TODO: probably should validate host...
|
||||
//
|
||||
mailboxName = Authentication.GetUserNameFromFullName(mailboxName);
|
||||
mailboxName = Authentication.GetUserNameFromFullName(mailboxName);
|
||||
|
||||
IEnumerable<string> mailList = MailManager.EnumerateMail(mailboxName);
|
||||
|
||||
@@ -433,31 +432,39 @@ namespace IFS
|
||||
|
||||
private void SendMicrocodeResponse(PUP p)
|
||||
{
|
||||
//
|
||||
// TODO; validate that this is a request for V1 of the protocol (I don't think there was ever another version...)
|
||||
//
|
||||
|
||||
//
|
||||
// The request PUP contains the file number in the lower-order 16-bits of the pup ID.
|
||||
// Assuming the number is a valid bootfile, we start sending it to the client's port via EFTP.
|
||||
//
|
||||
ushort fileNumber = (ushort)p.ID;
|
||||
ushort version = (ushort)(p.ID >> 16);
|
||||
|
||||
Log.Write(LogType.Verbose, LogComponent.MiscServices, "Microcode request is for file {0}.", fileNumber);
|
||||
Log.Write(LogType.Verbose, LogComponent.MiscServices, "Microcode request (version {0}) is for file {1}.", version, Helpers.ToOctal(fileNumber));
|
||||
|
||||
FileStream microcodeFile = BootServer.GetStreamForNumber(fileNumber);
|
||||
|
||||
if (microcodeFile == null)
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file {0} does not exist or could not be opened.", fileNumber);
|
||||
Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file {0} does not exist or could not be opened.", Helpers.ToOctal(fileNumber));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send the file. The MicrocodeReply protocol is extremely simple:
|
||||
// Send the file asynchronously. The MicrocodeReply protocol is extremely simple:
|
||||
// Just send a sequence of MicrocodeReply PUPs containing the microcode data,
|
||||
// there are no acks or flow control of any kind.
|
||||
Log.Write(LogType.Warning, LogComponent.MiscServices, "Sending microcode file {0}.", fileNumber);
|
||||
SendMicrocodeFile(p.SourcePort, microcodeFile);
|
||||
ThreadPool.QueueUserWorkItem((ctx) =>
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.MiscServices, "Sending microcode file {0} ('{1}').", Helpers.ToOctal(fileNumber), microcodeFile.Name);
|
||||
SendMicrocodeFile(p.SourcePort, microcodeFile, fileNumber == 0x100 /* test for Initial.eb */);
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendMicrocodeFile(PUPPort sourcePort, Stream microcodeFile)
|
||||
private void SendMicrocodeFile(PUPPort sourcePort, Stream microcodeFile, bool sendEmptyPacket)
|
||||
{
|
||||
//
|
||||
// "For version 1 of the protocol, a server willing to supply the data simply sends a sequence of packets
|
||||
@@ -467,25 +474,48 @@ namespace IFS
|
||||
// acknowledgments. This protocol is used by Dolphins and Dorados.
|
||||
// Currently, the version 1 servers send packets containing 3 * n words of data. This constraint is imposed by the
|
||||
// Rev L Dolphin EPROM microcode. I’d like to remove this restriction if I get a chance, so please don’t take
|
||||
// advantage of it unless you need to.The Rev L Dolphin EPROM also requires the second word of the source
|
||||
// advantage of it unless you need to. The Rev L Dolphin EPROM also requires the second word of the source
|
||||
// socket to be 4. / HGM May - 80."
|
||||
//
|
||||
|
||||
// TODO: this should happen in a worker thread.
|
||||
//
|
||||
// Skip the first 256 header words in the microcode file.
|
||||
microcodeFile.Seek(512, SeekOrigin.Begin);
|
||||
|
||||
//
|
||||
// We send 192 words of data per PUP (3 * 64) in an attempt to make the Dolphin happy.
|
||||
// We send 258 words of data per PUP (3 * 86) in an attempt to make the Dolphin happy.
|
||||
// This is what the original Xerox IFS code did.
|
||||
// We space these out a bit to give the D-machine time to keep up, we're much much faster than they are.
|
||||
//
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, 4);
|
||||
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, SocketIDGenerator.GetNextSocketID() << 16 | 0x4);
|
||||
|
||||
byte[] buffer = new byte[384];
|
||||
bool done = false;
|
||||
uint id = 0;
|
||||
|
||||
//
|
||||
// Send an empty packet to start the transfer. The prom boot microcode will explicitly ignore this.
|
||||
// Note that this is not documented in the (meager) protocol docs, nor does the BCPL IFS code
|
||||
// appear to actually send such a packet, at least not explicitly.
|
||||
//
|
||||
// Further:
|
||||
// D0 Initial's E3Boot doesn't like the empty packet, and assumes it means the end of the microcode reply; it then
|
||||
// tries to load a 0-length microcode file into CS and falls over.
|
||||
// The below hacks around it (it only sends the empty packet when the Initial microcode file is requested).
|
||||
// I'm unsure if there's a subtle bug in our IFS code here or elsewhere or a subtle bug in PARC's IFS code; it does kind of seem
|
||||
// like the microcode is working around a weird issue but those folks were a lot smarter than I.
|
||||
// Addendum 7/28/23:
|
||||
// After reset, The real D0 seems to occasionally complete the first-stage (Initial) boot without the extra empty packet being sent.
|
||||
// I wonder if there's a hardware glitch the boot microcode is working around.
|
||||
// Additionally: the Dorado boot ucode source makes no mention of ignoring an empty packet, nor does the code implement such behavior.
|
||||
//
|
||||
if (sendEmptyPacket)
|
||||
{
|
||||
Router.Instance.SendPup(new PUP(PupType.MicrocodeReply, 0x10000, sourcePort, localPort, new byte[] { }));
|
||||
}
|
||||
|
||||
uint checksum = 0;
|
||||
while (!done)
|
||||
{
|
||||
byte[] buffer = new byte[258 * 2]; // 258 words, as the original IFS did
|
||||
int read = microcodeFile.Read(buffer, 0, buffer.Length);
|
||||
|
||||
if (read < buffer.Length)
|
||||
@@ -495,12 +525,23 @@ namespace IFS
|
||||
|
||||
if (read > 0)
|
||||
{
|
||||
PUP microcodeReply = new PUP(PupType.MicrocodeReply, (id | 0x00010000), sourcePort, localPort, buffer);
|
||||
// Send ONLY the bytes we read.
|
||||
byte[] packetBuffer = new byte[read];
|
||||
Array.Copy(buffer, packetBuffer, read);
|
||||
PUP microcodeReply = new PUP(PupType.MicrocodeReply, (id | 0x10000), sourcePort, localPort, buffer);
|
||||
Router.Instance.SendPup(microcodeReply);
|
||||
|
||||
Log.Write(LogType.Warning, LogComponent.MiscServices, "Sequence {0} Sent {1} bytes of microcode file", id, read);
|
||||
|
||||
for (int i = 0; i < read; i += 2)
|
||||
{
|
||||
checksum += (uint)(packetBuffer[i + 1] | (packetBuffer[i] << 8));
|
||||
}
|
||||
}
|
||||
|
||||
// Pause a bit to give the D0 time to breathe.
|
||||
System.Threading.Thread.Sleep(5);
|
||||
// TODO: make this configurable?
|
||||
System.Threading.Thread.Sleep(10);
|
||||
|
||||
id++;
|
||||
}
|
||||
@@ -508,10 +549,9 @@ namespace IFS
|
||||
//
|
||||
// Send an empty packet to conclude the transfer.
|
||||
//
|
||||
PUP endReply = new PUP(PupType.MicrocodeReply, (id | 0x00010000), sourcePort, localPort, new byte[] { });
|
||||
Router.Instance.SendPup(endReply);
|
||||
Router.Instance.SendPup(new PUP(PupType.MicrocodeReply, (id | 0x10000), sourcePort, localPort, new byte[] { }));
|
||||
|
||||
Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file sent.");
|
||||
Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file sent. Checksum {0:x4}", (checksum & 0xffff));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
|
||||
[assembly: AssemblyProduct("IFS")]
|
||||
[assembly: AssemblyCopyright("Copyright © LCM+L 2015-2020")]
|
||||
[assembly: AssemblyCopyright("Copyright © LCM+L 2015-2020, Josh Dersch 2020-2023")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Runtime.InteropServices;
|
||||
// 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.3.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.3.0.0")]
|
||||
[assembly: AssemblyVersion("1.4.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.4.0.0")]
|
||||
|
||||
655
PUP/Transport/3MbitAdapter.cs
Normal file
655
PUP/Transport/3MbitAdapter.cs
Normal file
@@ -0,0 +1,655 @@
|
||||
/*
|
||||
This file is part of IFS.
|
||||
|
||||
IFS is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
IFS is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with IFS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
using IFS.Gateway;
|
||||
using IFS.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace IFS.Transport
|
||||
{
|
||||
/// <summary>
|
||||
/// This provides a packet interface implementation that talks to Ken Shirriff's 3mbit interface on the BeagleBone.
|
||||
/// See https://github.com/shirriff/alto-ethernet-interface for the original code. This class effectively replaces
|
||||
/// the "gateway" C program and talks directly to the PRUs on the beaglebone to exchange packets with the hardware.
|
||||
/// The PRU data and code files etherdata.bin and ethertext.bin are used to load the PRU with the appropriate
|
||||
/// 3mbit driver code; these are included with this project and must be placed alongside IFS.exe in order to be
|
||||
/// found and loaded.
|
||||
///
|
||||
/// This code is more or less a direct port of Ken's code over to C#, with a bit of cleanup to make it more palatable
|
||||
/// for C# coding styles. Though it's still pretty rough.
|
||||
/// </summary>
|
||||
public class Ether3MbitInterface : IPacketInterface
|
||||
{
|
||||
public Ether3MbitInterface()
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializePRU();
|
||||
StartReceiver();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write(LogType.Error, LogComponent.E3Mbit, "Failed to initialize the BeagleBone 3Mbit Interface. Error: {0}", e.Message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterRouterCallback(ReceivedPacketCallback callback)
|
||||
{
|
||||
_routerCallback = callback;
|
||||
}
|
||||
|
||||
public void Send(PUP p)
|
||||
{
|
||||
byte[] frameData = PupPacketBuilder.BuildEthernetFrameFromPup(p);
|
||||
SendToNetworkInterface(frameData);
|
||||
}
|
||||
|
||||
public void Send(byte[] data, byte source, byte destination, ushort frameType)
|
||||
{
|
||||
byte[] frameData = PupPacketBuilder.BuildEthernetFrameFromRawData(data, source, destination, frameType);
|
||||
SendToNetworkInterface(frameData);
|
||||
}
|
||||
|
||||
public void Send(MemoryStream encapsulatedPacketStream)
|
||||
{
|
||||
byte[] encapsulatedFrameData = encapsulatedPacketStream.ToArray();
|
||||
// Skip the first two bytes (encapsulated length info). This is annoying.
|
||||
byte[] frameData = new byte[encapsulatedFrameData.Length - 2];
|
||||
Array.Copy(encapsulatedFrameData, 2, frameData, 0, frameData.Length);
|
||||
|
||||
SendToNetworkInterface(frameData);
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void InitializePRU()
|
||||
{
|
||||
Log.Write(LogType.Normal, LogComponent.E3Mbit, "PRU Initialization started.");
|
||||
|
||||
PRU.prussdrv_init();
|
||||
|
||||
if (PRU.prussdrv_open(PRU.PRU_EVTOUT_0) == -1)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to open PRU.");
|
||||
}
|
||||
|
||||
PRU.tpruss_intc_initdata initData;
|
||||
initData.sysevts_enabled = new byte[]{ PRU.PRU0_PRU1_INTERRUPT, PRU.PRU1_PRU0_INTERRUPT, PRU.PRU0_ARM_INTERRUPT, PRU.PRU1_ARM_INTERRUPT, PRU.ARM_PRU0_INTERRUPT, PRU.ARM_PRU1_INTERRUPT, 15, 0xff };
|
||||
initData.sysevt_to_channel_map = new PRU.tsysevt_to_channel_map[]
|
||||
{
|
||||
new PRU.tsysevt_to_channel_map(PRU.PRU0_PRU1_INTERRUPT, PRU.CHANNEL1),
|
||||
new PRU.tsysevt_to_channel_map(PRU.PRU1_PRU0_INTERRUPT, PRU.CHANNEL0),
|
||||
new PRU.tsysevt_to_channel_map(PRU.PRU0_ARM_INTERRUPT, PRU.CHANNEL2),
|
||||
new PRU.tsysevt_to_channel_map(PRU.PRU1_ARM_INTERRUPT, PRU.CHANNEL3),
|
||||
new PRU.tsysevt_to_channel_map(PRU.ARM_PRU0_INTERRUPT, PRU.CHANNEL0),
|
||||
new PRU.tsysevt_to_channel_map(PRU.ARM_PRU1_INTERRUPT, PRU.CHANNEL1),
|
||||
new PRU.tsysevt_to_channel_map(15, PRU.CHANNEL0),
|
||||
new PRU.tsysevt_to_channel_map(-1, -1),
|
||||
};
|
||||
|
||||
initData.channel_to_host_map = new PRU.tchannel_to_host_map[]
|
||||
{
|
||||
new PRU.tchannel_to_host_map(PRU.CHANNEL0, PRU.PRU0),
|
||||
new PRU.tchannel_to_host_map(PRU.CHANNEL1, PRU.PRU1),
|
||||
new PRU.tchannel_to_host_map(PRU.CHANNEL2, PRU.PRU_EVTOUT0),
|
||||
new PRU.tchannel_to_host_map(PRU.CHANNEL3, PRU.PRU_EVTOUT1),
|
||||
new PRU.tchannel_to_host_map(-1, -1),
|
||||
};
|
||||
|
||||
initData.host_enable_bitmask = PRU.PRU0_HOSTEN_MASK | PRU.PRU1_HOSTEN_MASK | PRU.PRU_EVTOUT0_HOSTEN_MASK | PRU.PRU_EVTOUT1_HOSTEN_MASK;
|
||||
|
||||
PRU.prussdrv_pruintc_init(ref initData);
|
||||
|
||||
if (PRU.prussdrv_load_datafile(0, "etherdata.bin") < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load PRU data file 'etherdata.bin'.");
|
||||
}
|
||||
|
||||
if (PRU.prussdrv_exec_program(0, "ethertext.bin") < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load and exec PRU program file 'ethertext.bin'.");
|
||||
}
|
||||
|
||||
if (PRU.prussdrv_map_prumem(PRU.PRUSS0_PRU0_DATARAM, out _sharedPruMemory) < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to map PRU shared memory.");
|
||||
}
|
||||
|
||||
Log.Write(LogType.Verbose, LogComponent.E3Mbit, "Shared PRU memory at 0x{0:x}", _sharedPruMemory.ToInt64());
|
||||
|
||||
// Initialize PRU control block:
|
||||
PruInterfaceControlBlock cb;
|
||||
cb.r_owner = OWNER_PRU;
|
||||
cb.r_buf = R_PTR_OFFSET;
|
||||
cb.r_max_length = MAX_SIZE;
|
||||
cb.r_received_length = 0;
|
||||
cb.r_status = 0;
|
||||
cb.r_truncated = 0;
|
||||
cb.w_owner = OWNER_ARM;
|
||||
cb.w_buf = W_PTR_OFFSET;
|
||||
cb.w_length = 0;
|
||||
cb.w_status = 0;
|
||||
|
||||
SetInterfaceControlBlock(cb);
|
||||
|
||||
Log.Write(LogType.Normal, LogComponent.E3Mbit, "PRU Initialization completed.");
|
||||
}
|
||||
|
||||
private void StartReceiver()
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem((ctx) =>
|
||||
{
|
||||
Log.Write(LogType.Normal, LogComponent.E3Mbit, "Starting receiver thread.");
|
||||
ReceiveWorker();
|
||||
}, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker thread function. Waits for incoming packets on the 3mbit network and handles them
|
||||
/// when they arrive.
|
||||
/// </summary>
|
||||
private void ReceiveWorker()
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
// Wait for the next wakeup from the PRU
|
||||
PRU.prussdrv_pru_wait_event(PRU.PRU_EVTOUT_0);
|
||||
|
||||
// Clear it
|
||||
PRU.prussdrv_pru_clear_event(PRU.PRU_EVTOUT_0, PRU.PRU0_ARM_INTERRUPT);
|
||||
|
||||
if (HostOwnsReadBuffer())
|
||||
{
|
||||
// PRU gave us a read packet from the 3mbit Ether, handle it.
|
||||
ReceiveFromNetworkInterface();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The following functions read and write the control block located in Host/PRU shared memory.
|
||||
//
|
||||
|
||||
private void SetInterfaceControlBlock(PruInterfaceControlBlock controlBlock)
|
||||
{
|
||||
Marshal.StructureToPtr(controlBlock, _sharedPruMemory, false);
|
||||
}
|
||||
|
||||
private PruInterfaceControlBlock GetInterfaceControlBlock()
|
||||
{
|
||||
return (PruInterfaceControlBlock)Marshal.PtrToStructure(_sharedPruMemory, typeof(PruInterfaceControlBlock));
|
||||
}
|
||||
|
||||
private bool HostOwnsReadBuffer()
|
||||
{
|
||||
// r_owner is at offset + 0
|
||||
return Marshal.ReadInt32(_sharedPruMemory) == OWNER_ARM;
|
||||
}
|
||||
|
||||
private bool HostOwnsWriteBuffer()
|
||||
{
|
||||
// w_owner is at offset + 24
|
||||
return Marshal.ReadInt32(new IntPtr(_sharedPruMemory.ToInt64() + 24)) == OWNER_ARM;
|
||||
}
|
||||
|
||||
private void SetReadBufferOwner(UInt32 owner)
|
||||
{
|
||||
// r_owner is at offset + 0
|
||||
Marshal.WriteInt32(_sharedPruMemory, (int)owner);
|
||||
}
|
||||
|
||||
private void SetWriteBufferOwner(UInt32 owner)
|
||||
{
|
||||
// w_owner is at offset + 24
|
||||
Marshal.WriteInt32(new IntPtr(_sharedPruMemory.ToInt64() + 24), (int)owner);
|
||||
}
|
||||
|
||||
private void SetWriteBufferLength(UInt32 length)
|
||||
{
|
||||
// w_length is at offset + 28
|
||||
Marshal.WriteInt32(new IntPtr(_sharedPruMemory.ToInt64() + 28), (int)length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pulls data received from the 3mbit interface and passes it to the router.
|
||||
/// </summary>
|
||||
private void ReceiveFromNetworkInterface()
|
||||
{
|
||||
PruInterfaceControlBlock cb = GetInterfaceControlBlock();
|
||||
|
||||
if (cb.r_truncated != 0)
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Truncated packet recieved.");
|
||||
cb.r_truncated = 0;
|
||||
SetInterfaceControlBlock(cb);
|
||||
SetReadBufferOwner(OWNER_PRU);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cb.r_status != STATUS_INPUT_COMPLETE)
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Bad PRU status 0x{0:x}", cb.r_status);
|
||||
SetReadBufferOwner(OWNER_PRU);
|
||||
return;
|
||||
}
|
||||
|
||||
int receivedDataLength = (int)cb.r_received_length;
|
||||
if (receivedDataLength > MAX_SIZE)
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Received data too long (0x{0:x} bytes)", receivedDataLength);
|
||||
SetReadBufferOwner(OWNER_PRU);
|
||||
return;
|
||||
}
|
||||
|
||||
if (receivedDataLength == 0)
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Received 0 bytes of duration data. Ignoring packet.");
|
||||
SetReadBufferOwner(OWNER_PRU);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the received data from the shared PRU memory:
|
||||
byte[] durationBuffer = new byte[receivedDataLength];
|
||||
Marshal.Copy(new IntPtr(_sharedPruMemory.ToInt64() + R_PTR_OFFSET), durationBuffer, 0, receivedDataLength);
|
||||
|
||||
// Ready for next packet
|
||||
SetReadBufferOwner(OWNER_PRU);
|
||||
|
||||
byte[] decodedPacket = DecodeDurationBuffer(durationBuffer);
|
||||
if (decodedPacket == null)
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Received bad packet.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepend packet length for our internal encapsulation (annoying since we're just going to strip it off again...)
|
||||
byte[] encapsulatedPacket = new byte[decodedPacket.Length + 2];
|
||||
Array.Copy(decodedPacket, 0, encapsulatedPacket, 2, decodedPacket.Length);
|
||||
|
||||
int encapsulatedLength = decodedPacket.Length / 2 + 2;
|
||||
encapsulatedPacket[0] = (byte)(encapsulatedLength >> 8);
|
||||
encapsulatedPacket[1] = (byte)encapsulatedLength;
|
||||
|
||||
MemoryStream packetStream = new MemoryStream(encapsulatedPacket);
|
||||
_routerCallback(packetStream, this);
|
||||
|
||||
Log.Write(LogType.Verbose, LogComponent.E3Mbit, "Received packet (0x{0:x} bytes), sent to router.", receivedDataLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends data to the 3mbit interface.
|
||||
/// </summary>
|
||||
private void SendToNetworkInterface(byte[] data)
|
||||
{
|
||||
if (!HostOwnsWriteBuffer())
|
||||
{
|
||||
// Shouldn't happen
|
||||
Log.Write(LogType.Error, LogComponent.E3Mbit, "SendToNetworkInterface called when PRU is not ready.");
|
||||
return;
|
||||
}
|
||||
|
||||
ushort crcVal = CalculateCRC(data, data.Length);
|
||||
|
||||
// Construct a new buffer with space for the CRC
|
||||
byte[] fullPacket = new byte[data.Length + 2];
|
||||
Array.Copy(data, fullPacket, data.Length);
|
||||
|
||||
fullPacket[fullPacket.Length - 2] = (byte)(crcVal >> 8);
|
||||
fullPacket[fullPacket.Length - 1] = (byte)(crcVal);
|
||||
|
||||
// Copy the buffer to the shared PRU memory.
|
||||
Marshal.Copy(fullPacket, 0, new IntPtr(_sharedPruMemory.ToInt64() + W_PTR_OFFSET), fullPacket.Length);
|
||||
SetWriteBufferLength((uint)fullPacket.Length);
|
||||
|
||||
// Signal PRU to send the data in the write buffer.
|
||||
SetWriteBufferOwner(OWNER_PRU);
|
||||
|
||||
Log.Write(LogType.Verbose, LogComponent.E3Mbit, "Packet sent to 3mbit interface.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes bit timings into packet data. Returns null if issues were found with the data.
|
||||
/// </summary>
|
||||
/// <param name="durationBuf"></param>
|
||||
/// <returns></returns>
|
||||
byte[] DecodeDurationBuffer(byte[] durationBuf) {
|
||||
|
||||
Log.Write(LogType.Verbose, LogComponent.E3Mbit, $"Decoding duration buffer of length {durationBuf.Length}.");
|
||||
|
||||
List<byte> byteBuffer = new List<byte>();
|
||||
bool[] bitBuf = new bool[8 * PUP.MAX_PUP_SIZE];
|
||||
const int RECV_WIDTH = 2; // Recv values are in units of 2 ns (to fit in byte)
|
||||
|
||||
// Convert timings in durationBuf into high/low vector in bitBuf
|
||||
// bitBuf holds values like 1, 0, 0, 1, 0, 1, indicating if the input
|
||||
// was high or low during that time interval.
|
||||
// A Manchester-encoded data bit consists of two values in bitBuf.
|
||||
int offset1; // Offset into timing vector
|
||||
int offset2 = 0; // Offset into bit vector
|
||||
bool value = true; // Current high/low value
|
||||
for (offset1 = 0; offset1 < durationBuf.Length; offset1++)
|
||||
{
|
||||
int width = durationBuf[offset1] * RECV_WIDTH;
|
||||
if (width < 120)
|
||||
{
|
||||
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad width {width} at {offset1} of {durationBuf.Length}");
|
||||
return null;
|
||||
}
|
||||
else if (width < 230)
|
||||
{
|
||||
value = !value;
|
||||
bitBuf[offset2++] = value;
|
||||
}
|
||||
else if (width < 280)
|
||||
{
|
||||
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad width {width} at {offset1} of {durationBuf.Length}");
|
||||
return null;
|
||||
}
|
||||
else if (width < 400)
|
||||
{
|
||||
value = !value;
|
||||
bitBuf[offset2++] = value;
|
||||
bitBuf[offset2++] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad width {width} at {offset1} of {durationBuf.Length}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert bit pairs in bitBuf to bytes in byteBuffer
|
||||
byte b = 0;
|
||||
int i;
|
||||
if ((offset2 % 2) == 0)
|
||||
{
|
||||
// For a 0 bit, the last 1 signal gets combined with the no-signal state and lost.
|
||||
// So add it back.
|
||||
bitBuf[offset2] = true;
|
||||
offset2 += 1;
|
||||
}
|
||||
|
||||
Log.Write(LogType.Verbose, LogComponent.E3Mbit, $"Offset2 is {offset2}.");
|
||||
// Start at 1 to skip sync
|
||||
for (i = 1; i < offset2; i += 2)
|
||||
{
|
||||
if (bitBuf[i] == bitBuf[i + 1])
|
||||
{
|
||||
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad bit sequence at {i} of {offset2}: {bitBuf[i]}, {bitBuf[i+1]}");
|
||||
b = (byte)(b << 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
b = (byte)((b << 1) | (bitBuf[i] ? 1 : 0));
|
||||
}
|
||||
if ((i % 16) == 15)
|
||||
{
|
||||
byteBuffer.Add(b);
|
||||
b = 0;
|
||||
}
|
||||
}
|
||||
if ((offset2 % 16) != 1)
|
||||
{
|
||||
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad offset2: {offset2}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check the Ethernet CRC
|
||||
byte[] byteArray = byteBuffer.ToArray();
|
||||
ushort crcVal = CalculateCRC(byteArray, byteArray.Length - 2);
|
||||
ushort readCrcVal = (ushort)((byteBuffer[byteBuffer.Count - 2] << 8) | byteBuffer[byteBuffer.Count - 1]);
|
||||
if (crcVal != readCrcVal)
|
||||
{
|
||||
Log.Write(LogType.Error, LogComponent.E3Mbit, "Bad CRC, {0:x} vs {1:x}", crcVal, readCrcVal);
|
||||
return null;
|
||||
}
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
// Generate CRC-16 for 3Mbit Ethernet
|
||||
// buf is sequence of words stored big-endian.
|
||||
ushort CalculateCRC(byte[] buf, int lengthInBytes)
|
||||
{
|
||||
ushort crc = 0x8005; // Due to the sync bit
|
||||
for (int index = 0; index < lengthInBytes; index++)
|
||||
{
|
||||
ushort data = (ushort)(buf[index] << 8);
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
ushort xorFeedback = (ushort)((crc ^ data) & 0x8000); // Test upper bit
|
||||
crc = (ushort)(crc << 1);
|
||||
data = (ushort)(data << 1);
|
||||
if (xorFeedback != 0)
|
||||
{
|
||||
crc ^= 0x8005; // CRC-16 polynomial constant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
IntPtr _sharedPruMemory;
|
||||
|
||||
private ReceivedPacketCallback _routerCallback;
|
||||
|
||||
// Interface between host and PRU
|
||||
// The idea is there are two buffers: r_ and w_.
|
||||
// Ownership is passed back and forth between the PRU and the ARM processor.
|
||||
// The PRU sends a signal whenever it gives a buffer back to the ARM.
|
||||
// "in" and "out" below are from the perspective of the PRU.
|
||||
//
|
||||
// This struct is here more for convenience of debugging than actual use in C#
|
||||
// since it's not really possible to map a C# object directly to volatile memory
|
||||
// in a way that I feel good about using.
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
struct PruInterfaceControlBlock
|
||||
{
|
||||
public UInt32 r_owner; // in
|
||||
public UInt32 r_max_length; // in, bytes
|
||||
public UInt32 r_received_length; // out, bytes
|
||||
public UInt32 r_buf; // in (pointer offset)
|
||||
public UInt32 r_truncated; // out, boolean
|
||||
public UInt32 r_status; // out
|
||||
public UInt32 w_owner; // in
|
||||
public UInt32 w_length; // bytes, in (buffer length)
|
||||
public UInt32 w_buf; // in (pointer offset)
|
||||
public UInt32 w_status; // out
|
||||
};
|
||||
|
||||
const uint STATUS_INPUT_COMPLETE = (0 << 8);
|
||||
const uint STATUS_OUTPUT_COMPLETE = (1 << 8);
|
||||
const uint STATUS_INPUT_OVERRUN = (2 << 8);
|
||||
const uint STATUS_SOFTWARE_RESET = (5 << 8); // Internal only
|
||||
|
||||
const uint STATUS_TRUNCATED = 36; // Not part of real interface
|
||||
const uint STATUS_TIMING_ERROR = 32; // Not part of real interface
|
||||
const uint STATUS_BIT_COLLISION = 16;
|
||||
const uint STATUS_BIT_CRC_BAD = 8; // unused
|
||||
const uint STATUS_BIT_ICMD = 4; // unused
|
||||
const uint STATUS_BIT_OCMD = 2; // unused
|
||||
const uint STATUS_BIT_INCOMPLETE = 1; // Not byte boundary
|
||||
|
||||
const uint COMMAND_NONE = 0;
|
||||
const uint COMMAND_SEND = 1;
|
||||
const uint COMMAND_RECV = 2;
|
||||
const uint COMMAND_HALT = 3;
|
||||
|
||||
const uint OWNER_ARM = 1;
|
||||
const uint OWNER_PRU = 2;
|
||||
|
||||
const uint W_PTR_OFFSET = 0x400;
|
||||
const uint R_PTR_OFFSET = 0x10000;
|
||||
const uint MAX_SIZE = 12 * 1024;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides constants, structs, and functions needed to P/Invoke into prussdrv lib calls.
|
||||
/// </summary>
|
||||
public static class PRU
|
||||
{
|
||||
public const int NUM_PRU_HOSTIRQS = 8;
|
||||
public const int NUM_PRU_HOSTS = 10;
|
||||
public const int NUM_PRU_CHANNELS = 10;
|
||||
public const int NUM_PRU_SYS_EVTS = 64;
|
||||
|
||||
public const uint PRUSS0_PRU0_DATARAM = 0;
|
||||
public const uint PRUSS0_PRU1_DATARAM = 1;
|
||||
public const uint PRUSS0_PRU0_IRAM = 2;
|
||||
public const uint PRUSS0_PRU1_IRAM = 3;
|
||||
|
||||
public const uint PRUSS_V1 = 1; // AM18XX
|
||||
public const uint PRUSS_V2 = 2; // AM33XX
|
||||
|
||||
//Available in AM33xx series - begin
|
||||
public const uint PRUSS0_SHARED_DATARAM = 4;
|
||||
public const uint PRUSS0_CFG = 5;
|
||||
public const uint PRUSS0_UART = 6;
|
||||
public const uint PRUSS0_IEP = 7;
|
||||
public const uint PRUSS0_ECAP = 8;
|
||||
public const uint PRUSS0_MII_RT = 9;
|
||||
public const uint PRUSS0_MDIO = 10;
|
||||
//Available in AM33xx series - end
|
||||
|
||||
public const uint PRU_EVTOUT_0 = 0;
|
||||
public const uint PRU_EVTOUT_1 = 1;
|
||||
public const uint PRU_EVTOUT_2 = 2;
|
||||
public const uint PRU_EVTOUT_3 = 3;
|
||||
public const uint PRU_EVTOUT_4 = 4;
|
||||
public const uint PRU_EVTOUT_5 = 5;
|
||||
public const uint PRU_EVTOUT_6 = 6;
|
||||
public const uint PRU_EVTOUT_7 = 7;
|
||||
|
||||
public const byte PRU0_PRU1_INTERRUPT = 17;
|
||||
public const byte PRU1_PRU0_INTERRUPT = 18;
|
||||
public const byte PRU0_ARM_INTERRUPT = 19;
|
||||
public const byte PRU1_ARM_INTERRUPT = 20;
|
||||
public const byte ARM_PRU0_INTERRUPT = 21;
|
||||
public const byte ARM_PRU1_INTERRUPT = 22;
|
||||
|
||||
public const byte CHANNEL0 = 0;
|
||||
public const byte CHANNEL1 = 1;
|
||||
public const byte CHANNEL2 = 2;
|
||||
public const byte CHANNEL3 = 3;
|
||||
public const byte CHANNEL4 = 4;
|
||||
public const byte CHANNEL5 = 5;
|
||||
public const byte CHANNEL6 = 6;
|
||||
public const byte CHANNEL7 = 7;
|
||||
public const byte CHANNEL8 = 8;
|
||||
public const byte CHANNEL9 = 9;
|
||||
|
||||
public const byte PRU0 = 0;
|
||||
public const byte PRU1 = 1;
|
||||
public const byte PRU_EVTOUT0 = 2;
|
||||
public const byte PRU_EVTOUT1 = 3;
|
||||
public const byte PRU_EVTOUT2 = 4;
|
||||
public const byte PRU_EVTOUT3 = 5;
|
||||
public const byte PRU_EVTOUT4 = 6;
|
||||
public const byte PRU_EVTOUT5 = 7;
|
||||
public const byte PRU_EVTOUT6 = 8;
|
||||
public const byte PRU_EVTOUT7 = 9;
|
||||
|
||||
public const uint PRU0_HOSTEN_MASK = 0x0001;
|
||||
public const uint PRU1_HOSTEN_MASK = 0x0002;
|
||||
public const uint PRU_EVTOUT0_HOSTEN_MASK = 0x0004;
|
||||
public const uint PRU_EVTOUT1_HOSTEN_MASK = 0x0008;
|
||||
public const uint PRU_EVTOUT2_HOSTEN_MASK = 0x0010;
|
||||
public const uint PRU_EVTOUT3_HOSTEN_MASK = 0x0020;
|
||||
public const uint PRU_EVTOUT4_HOSTEN_MASK = 0x0040;
|
||||
public const uint PRU_EVTOUT5_HOSTEN_MASK = 0x0080;
|
||||
public const uint PRU_EVTOUT6_HOSTEN_MASK = 0x0100;
|
||||
public const uint PRU_EVTOUT7_HOSTEN_MASK = 0x0200;
|
||||
|
||||
public struct tsysevt_to_channel_map
|
||||
{
|
||||
public tsysevt_to_channel_map(short s, short c)
|
||||
{
|
||||
sysevt = s;
|
||||
channel = c;
|
||||
}
|
||||
public short sysevt;
|
||||
public short channel;
|
||||
}
|
||||
|
||||
public struct tchannel_to_host_map
|
||||
{
|
||||
public tchannel_to_host_map(short c, short h)
|
||||
{
|
||||
channel = c;
|
||||
host = h;
|
||||
}
|
||||
public short channel;
|
||||
public short host;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct tpruss_intc_initdata
|
||||
{
|
||||
//Enabled SYSEVTs - Range:0..63
|
||||
//{-1} indicates end of list
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_PRU_SYS_EVTS)]
|
||||
public byte[] sysevts_enabled;
|
||||
|
||||
//SysEvt to Channel map. SYSEVTs - Range:0..63 Channels -Range: 0..9
|
||||
//{-1, -1} indicates end of list
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_PRU_SYS_EVTS)]
|
||||
public tsysevt_to_channel_map[] sysevt_to_channel_map;
|
||||
|
||||
//Channel to Host map.Channels -Range: 0..9 HOSTs - Range:0..9
|
||||
//{-1, -1} indicates end of list
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_PRU_CHANNELS)]
|
||||
public tchannel_to_host_map[] channel_to_host_map;
|
||||
|
||||
//10-bit mask - Enable Host0-Host9 {Host0/1:PRU0/1, Host2..9 : PRUEVT_OUT0..7}
|
||||
public UInt32 host_enable_bitmask;
|
||||
}
|
||||
|
||||
[DllImport("prussdrv")]
|
||||
public static extern int prussdrv_init();
|
||||
|
||||
[DllImport("prussdrv")]
|
||||
public static extern int prussdrv_open(UInt32 host_interrupt);
|
||||
|
||||
[DllImport("prussdrv")]
|
||||
public static extern int prussdrv_pruintc_init(ref tpruss_intc_initdata prussintc_init_data);
|
||||
|
||||
[DllImport("prussdrv")]
|
||||
public static extern int prussdrv_load_datafile(int prunum, [MarshalAs(UnmanagedType.LPStr)] string filename);
|
||||
|
||||
[DllImport("prussdrv")]
|
||||
public static extern int prussdrv_exec_program(int prunum, [MarshalAs(UnmanagedType.LPStr)] string filename);
|
||||
|
||||
[DllImport("prussdrv")]
|
||||
public static extern int prussdrv_map_prumem(UInt32 pru_ram_id, out IntPtr address);
|
||||
|
||||
[DllImport("prussdrv")]
|
||||
public static extern int prussdrv_pru_wait_event(UInt32 host_interrupt);
|
||||
|
||||
[DllImport("prussdrv")]
|
||||
public static extern int prussdrv_pru_clear_event(UInt32 host_interrupt, UInt32 sysevent);
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,6 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using PcapDotNet.Base;
|
||||
using PcapDotNet.Core;
|
||||
using PcapDotNet.Core.Extensions;
|
||||
@@ -28,7 +23,6 @@ using PcapDotNet.Packets;
|
||||
using PcapDotNet.Packets.Ethernet;
|
||||
using IFS.Logging;
|
||||
using System.IO;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading;
|
||||
using IFS.Gateway;
|
||||
|
||||
@@ -41,14 +35,14 @@ namespace IFS.Transport
|
||||
/// Ethernet packets are broadcast. See comments in UDP.cs for the reasoning behind this.
|
||||
///
|
||||
/// </summary>
|
||||
public class Ethernet : IPupPacketInterface, IRawPacketInterface
|
||||
public class Ethernet : IPacketInterface
|
||||
{
|
||||
public Ethernet(LivePacketDevice iface)
|
||||
{
|
||||
_interface = iface;
|
||||
_interface = iface;
|
||||
}
|
||||
|
||||
public void RegisterRouterCallback(RoutePupCallback callback)
|
||||
public void RegisterRouterCallback(ReceivedPacketCallback callback)
|
||||
{
|
||||
_routerCallback = callback;
|
||||
|
||||
@@ -63,7 +57,7 @@ namespace IFS.Transport
|
||||
public void Shutdown()
|
||||
{
|
||||
_routerCallback = null;
|
||||
_communicator.Break();
|
||||
_communicator.Break();
|
||||
}
|
||||
|
||||
public void Send(PUP p)
|
||||
@@ -71,75 +65,26 @@ namespace IFS.Transport
|
||||
//
|
||||
// Write PUP to ethernet:
|
||||
//
|
||||
|
||||
// Build the outgoing data; this is:
|
||||
// 1st word: length of data following
|
||||
// 2nd word: 3mbit destination / source bytes
|
||||
// 3rd word: frame type (PUP)
|
||||
byte[] encapsulatedFrame = new byte[6 + p.RawData.Length];
|
||||
|
||||
// 3mbit Packet length
|
||||
encapsulatedFrame[0] = (byte)((p.RawData.Length / 2 + 2) >> 8);
|
||||
encapsulatedFrame[1] = (byte)(p.RawData.Length / 2 + 2);
|
||||
|
||||
// addressing
|
||||
encapsulatedFrame[2] = p.DestinationPort.Host;
|
||||
encapsulatedFrame[3] = p.SourcePort.Host;
|
||||
|
||||
// frame type
|
||||
encapsulatedFrame[4] = (byte)(_pupFrameType >> 8);
|
||||
encapsulatedFrame[5] = (byte)_pupFrameType;
|
||||
|
||||
// Actual data
|
||||
p.RawData.CopyTo(encapsulatedFrame, 6);
|
||||
|
||||
MacAddress destinationMac = new MacAddress(_10mbitBroadcast);
|
||||
|
||||
// Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
|
||||
EthernetLayer ethernetLayer = new EthernetLayer
|
||||
{
|
||||
Source = _interface.GetMacAddress(),
|
||||
Destination = destinationMac,
|
||||
EtherType = (EthernetType)_3mbitFrameType,
|
||||
};
|
||||
|
||||
PayloadLayer payloadLayer = new PayloadLayer
|
||||
{
|
||||
Data = new Datagram(encapsulatedFrame),
|
||||
};
|
||||
|
||||
PacketBuilder builder = new PacketBuilder(ethernetLayer, payloadLayer);
|
||||
|
||||
// Send it over the 'net!
|
||||
_communicator.SendPacket(builder.Build(DateTime.Now));
|
||||
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromPup(p);
|
||||
SendFrame(encapsulatedFrame);
|
||||
}
|
||||
|
||||
public void Send(byte[] data, byte source, byte destination, ushort frameType)
|
||||
{
|
||||
// Build the outgoing data; this is:
|
||||
// 1st word: length of data following
|
||||
// 2nd word: 3mbit destination / source bytes
|
||||
// 3rd word: frame type (PUP)
|
||||
byte[] encapsulatedFrame = new byte[6 + data.Length];
|
||||
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromRawData(data, source, destination, frameType);
|
||||
SendFrame(encapsulatedFrame);
|
||||
}
|
||||
|
||||
// 3mbit Packet length
|
||||
encapsulatedFrame[0] = (byte)((data.Length / 2 + 2) >> 8);
|
||||
encapsulatedFrame[1] = (byte)(data.Length / 2 + 2);
|
||||
public void Send(MemoryStream encapsulatedFrameStream)
|
||||
{
|
||||
SendFrame(encapsulatedFrameStream.ToArray());
|
||||
}
|
||||
|
||||
// addressing
|
||||
encapsulatedFrame[2] = destination;
|
||||
encapsulatedFrame[3] = source;
|
||||
private void SendFrame(byte[] encapsulatedFrame)
|
||||
{
|
||||
MacAddress destinationMac = new MacAddress(_10mbitBroadcast);
|
||||
|
||||
// frame type
|
||||
encapsulatedFrame[4] = (byte)(frameType >> 8);
|
||||
encapsulatedFrame[5] = (byte)frameType;
|
||||
|
||||
// Actual data
|
||||
data.CopyTo(encapsulatedFrame, 6);
|
||||
|
||||
MacAddress destinationMac = new MacAddress(_10mbitBroadcast);
|
||||
|
||||
// Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
|
||||
// Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
|
||||
EthernetLayer ethernetLayer = new EthernetLayer
|
||||
{
|
||||
Source = _interface.GetMacAddress(),
|
||||
@@ -160,56 +105,19 @@ namespace IFS.Transport
|
||||
|
||||
private void ReceiveCallback(Packet p)
|
||||
{
|
||||
//
|
||||
// Filter out encapsulated 3mbit frames and look for PUPs, forward them on.
|
||||
//
|
||||
if ((int)p.Ethernet.EtherType == _3mbitFrameType)
|
||||
{
|
||||
Log.Write(LogType.Verbose, LogComponent.Ethernet, "3mbit pup received.");
|
||||
|
||||
MemoryStream packetStream = p.Ethernet.Payload.ToMemoryStream();
|
||||
|
||||
// Read the length prefix (in words), convert to bytes.
|
||||
// Subtract off 2 words for the ethernet header
|
||||
int length = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())) * 2 - 4;
|
||||
|
||||
// Read the address (1st word of 3mbit packet)
|
||||
byte destination = (byte)packetStream.ReadByte();
|
||||
byte source = (byte)packetStream.ReadByte();
|
||||
|
||||
// Read the type and switch on it
|
||||
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
|
||||
|
||||
//
|
||||
// Ensure this is a packet we're interested in.
|
||||
//
|
||||
if (etherType3mbit == _pupFrameType && // it's a PUP
|
||||
(destination == DirectoryServices.Instance.LocalHost || // for us, or...
|
||||
destination == 0)) // broadcast
|
||||
{
|
||||
try
|
||||
{
|
||||
PUP pup = new PUP(packetStream, length);
|
||||
_routerCallback(pup, destination != 0);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// An error occurred, log it.
|
||||
Log.Write(LogType.Error, LogComponent.PUP, "Error handling PUP: {0}", e.Message);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.Ethernet, "3mbit packet is not a PUP, dropping");
|
||||
}
|
||||
_routerCallback(packetStream, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a PUP, Discard the packet. We will not log this, so as to keep noise down.
|
||||
// Not an encapsulated 3mbit frame, Discard the packet. We will not log this, so as to keep noise down.
|
||||
// Log.Write(LogType.Verbose, LogComponent.Ethernet, "Not a PUP (type 0x{0:x}. Dropping.", p.Ethernet.EtherType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Open(bool promiscuous, int timeout)
|
||||
{
|
||||
@@ -231,13 +139,10 @@ namespace IFS.Transport
|
||||
|
||||
private LivePacketDevice _interface;
|
||||
private PacketCommunicator _communicator;
|
||||
private RoutePupCallback _routerCallback;
|
||||
private ReceivedPacketCallback _routerCallback;
|
||||
|
||||
// Constants
|
||||
|
||||
// The ethertype used in the encapsulated 3mbit frame
|
||||
private readonly ushort _pupFrameType = 512;
|
||||
|
||||
// The type used for 3mbit frames encapsulated in 10mb frames
|
||||
private readonly int _3mbitFrameType = 0xbeef; // easy to identify, ostensibly unused by anything of any import
|
||||
|
||||
|
||||
74
PUP/Transport/PacketBuilder.cs
Normal file
74
PUP/Transport/PacketBuilder.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS.Transport
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper functions for building Ethernet frames in a variety of ways.
|
||||
/// </summary>
|
||||
public static class PupPacketBuilder
|
||||
{
|
||||
// The ethertype used in the encapsulated 3mbit frame
|
||||
public static readonly ushort PupFrameType = 512;
|
||||
|
||||
public static byte[] BuildEncapsulatedEthernetFrameFromPup(PUP p)
|
||||
{
|
||||
return BuildEncapsulatedEthernetFrameFromRawData(p.RawData, p.SourcePort.Host, p.DestinationPort.Host, PupFrameType);
|
||||
}
|
||||
|
||||
public static byte[] BuildEncapsulatedEthernetFrameFromRawData(byte[] data, byte source, byte destination, ushort frameType)
|
||||
{
|
||||
// Build the outgoing data; this is:
|
||||
// 1st word: length of data following
|
||||
// 2nd word: 3mbit destination / source bytes
|
||||
// 3rd word: frame type
|
||||
byte[] newFrame = new byte[6 + data.Length];
|
||||
|
||||
// 3mbit Packet length
|
||||
newFrame[0] = (byte)((data.Length / 2 + 2) >> 8);
|
||||
newFrame[1] = (byte)(data.Length / 2 + 2);
|
||||
|
||||
// addressing
|
||||
newFrame[2] = destination;
|
||||
newFrame[3] = source;
|
||||
|
||||
// frame type
|
||||
newFrame[4] = (byte)(frameType >> 8);
|
||||
newFrame[5] = (byte)frameType;
|
||||
|
||||
// Actual data
|
||||
data.CopyTo(newFrame, 6);
|
||||
|
||||
return newFrame;
|
||||
}
|
||||
|
||||
public static byte[] BuildEthernetFrameFromPup(PUP p)
|
||||
{
|
||||
return BuildEthernetFrameFromRawData(p.RawData, p.SourcePort.Host, p.DestinationPort.Host, PupFrameType);
|
||||
}
|
||||
|
||||
public static byte[] BuildEthernetFrameFromRawData(byte[] data, byte source, byte destination, ushort frameType)
|
||||
{
|
||||
// Build the full raw frame data; this is:
|
||||
// 2nd word: 3mbit destination / source bytes
|
||||
// 3rd word: frame type
|
||||
byte[] newFrame = new byte[4 + data.Length];
|
||||
|
||||
// addressing
|
||||
newFrame[0] = destination;
|
||||
newFrame[1] = source;
|
||||
|
||||
// frame type
|
||||
newFrame[2] = (byte)(frameType >> 8);
|
||||
newFrame[3] = (byte)frameType;
|
||||
|
||||
// Actual data
|
||||
data.CopyTo(newFrame, 4);
|
||||
|
||||
return newFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,7 @@
|
||||
*/
|
||||
|
||||
using IFS.Gateway;
|
||||
using PcapDotNet.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
namespace IFS.Transport
|
||||
{
|
||||
@@ -41,7 +36,7 @@ namespace IFS.Transport
|
||||
/// Registers a callback (into the router) to be invoked on receipt of a PUP.
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
void RegisterRouterCallback(RoutePupCallback callback);
|
||||
void RegisterRouterCallback(ReceivedPacketCallback callback);
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down the interface.
|
||||
@@ -69,5 +64,18 @@ namespace IFS.Transport
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="frameType"></param>
|
||||
void Send(byte[] data, byte source, byte destination, ushort frameType);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the specified data over the transport.
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
void Send(MemoryStream encapsulatedFrameStream);
|
||||
}
|
||||
|
||||
public interface IPacketInterface : IPupPacketInterface, IRawPacketInterface
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace IFS.Transport
|
||||
/// and go with the broadcast implementation.
|
||||
///
|
||||
/// </summary>
|
||||
public class UDPEncapsulation : IPupPacketInterface, IRawPacketInterface
|
||||
public class UDPEncapsulation : IPacketInterface
|
||||
{
|
||||
public UDPEncapsulation(NetworkInterface iface)
|
||||
{
|
||||
@@ -78,7 +78,7 @@ namespace IFS.Transport
|
||||
// Grab the broadcast address for the interface so that we know what broadcast address to use
|
||||
// for our UDP datagrams.
|
||||
//
|
||||
IPInterfaceProperties props = iface.GetIPProperties();
|
||||
IPInterfaceProperties props = iface.GetIPProperties();
|
||||
|
||||
foreach (UnicastIPAddressInformation unicast in props.UnicastAddresses)
|
||||
{
|
||||
@@ -117,11 +117,11 @@ namespace IFS.Transport
|
||||
/// Registers a gateway to handle incoming PUPs.
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
public void RegisterRouterCallback(RoutePupCallback callback)
|
||||
public void RegisterRouterCallback(ReceivedPacketCallback callback)
|
||||
{
|
||||
_routerCallback = callback;
|
||||
|
||||
// Now that we have a callback we can start receiving stuff.
|
||||
// Now that we have a callback we can start receiving stuff.
|
||||
BeginReceive();
|
||||
}
|
||||
|
||||
@@ -136,37 +136,16 @@ namespace IFS.Transport
|
||||
//
|
||||
// Write PUP to UDP:
|
||||
//
|
||||
// For now, no actual routing (Gateway not implemented yet), everything is on the same 'net.
|
||||
// Just send a broadcast UDP with the encapsulated frame inside of it.
|
||||
//
|
||||
//
|
||||
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromPup(p);
|
||||
|
||||
// Build the outgoing data; this is:
|
||||
// 1st word: length of data following
|
||||
// 2nd word: 3mbit destination / source bytes
|
||||
// 3rd word: frame type (PUP)
|
||||
byte[] encapsulatedFrame = new byte[6 + p.RawData.Length];
|
||||
|
||||
// 3mbit Packet length
|
||||
encapsulatedFrame[0] = (byte)((p.RawData.Length / 2 + 2) >> 8);
|
||||
encapsulatedFrame[1] = (byte)(p.RawData.Length / 2 + 2);
|
||||
|
||||
// addressing
|
||||
encapsulatedFrame[2] = p.DestinationPort.Host;
|
||||
encapsulatedFrame[3] = p.SourcePort.Host;
|
||||
|
||||
// frame type
|
||||
encapsulatedFrame[4] = (byte)(_pupFrameType >> 8);
|
||||
encapsulatedFrame[5] = (byte)_pupFrameType;
|
||||
|
||||
// Actual data
|
||||
p.RawData.CopyTo(encapsulatedFrame, 6);
|
||||
|
||||
// Send as UDP broadcast.
|
||||
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
|
||||
// Send as UDP broadcast.
|
||||
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet.
|
||||
/// Sends an array of bytes over the network as a 3mbit packet encapsulated in a UDP datagram.
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
/// <param name="hostId"></param>
|
||||
@@ -176,66 +155,26 @@ namespace IFS.Transport
|
||||
// 1st word: length of data following
|
||||
// 2nd word: 3mbit destination / source bytes
|
||||
// 3rd word: frame type (PUP)
|
||||
byte[] encapsulatedFrame = new byte[6 + data.Length];
|
||||
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromRawData(data, source, destination, frameType);
|
||||
|
||||
// 3mbit Packet length
|
||||
encapsulatedFrame[0] = (byte)((data.Length / 2 + 2) >> 8);
|
||||
encapsulatedFrame[1] = (byte)(data.Length / 2 + 2);
|
||||
// Send as UDP broadcast.
|
||||
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
|
||||
}
|
||||
|
||||
// addressing
|
||||
encapsulatedFrame[2] = destination;
|
||||
encapsulatedFrame[3] = source;
|
||||
|
||||
// frame type
|
||||
encapsulatedFrame[4] = (byte)(frameType >> 8);
|
||||
encapsulatedFrame[5] = (byte)frameType;
|
||||
|
||||
// Actual data
|
||||
data.CopyTo(encapsulatedFrame, 6);
|
||||
|
||||
// Send as UDP broadcast.
|
||||
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
|
||||
/// <summary>
|
||||
/// Sends a stream of bytes over the network as a 3mbit packet encapsulated in a UDP datagram.
|
||||
/// </summary>
|
||||
/// <param name="encapsulatedPacketStream"></param>
|
||||
public void Send(MemoryStream encapsulatedPacketStream)
|
||||
{
|
||||
// Send as UDP broadcast.
|
||||
byte[] buf = encapsulatedPacketStream.ToArray();
|
||||
_udpClient.Send(buf, buf.Length, _broadcastEndpoint);
|
||||
}
|
||||
|
||||
private void Receive(MemoryStream packetStream)
|
||||
{
|
||||
//
|
||||
// Look for PUPs, forward them on.
|
||||
//
|
||||
|
||||
// Read the length prefix (in words), convert to bytes.
|
||||
// Subtract off 2 words for the ethernet header
|
||||
int length = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())) * 2 - 4;
|
||||
|
||||
// Read the address (1st word of 3mbit packet)
|
||||
byte destination = (byte)packetStream.ReadByte();
|
||||
byte source = (byte)packetStream.ReadByte();
|
||||
|
||||
// Read the type and switch on it
|
||||
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
|
||||
|
||||
//
|
||||
// Ensure this is a packet we're interested in.
|
||||
//
|
||||
if (etherType3mbit == _pupFrameType && // it's a PUP
|
||||
(destination == DirectoryServices.Instance.LocalHost || // for us, or...
|
||||
destination == 0)) // broadcast
|
||||
{
|
||||
try
|
||||
{
|
||||
PUP pup = new PUP(packetStream, length);
|
||||
_routerCallback(pup, destination != 0);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// An error occurred, log it.
|
||||
Log.Write(LogType.Error, LogComponent.PUP, "Error handling PUP: {0}", e.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Write(LogType.Warning, LogComponent.Ethernet, "UDP packet is not a PUP, dropping");
|
||||
}
|
||||
_routerCallback(packetStream, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -258,7 +197,7 @@ namespace IFS.Transport
|
||||
// properly.)
|
||||
Log.Write(LogComponent.UDP, "UDP Receiver thread started.");
|
||||
|
||||
IPEndPoint groupEndPoint = new IPEndPoint(IPAddress.Any, Configuration.UDPPort);
|
||||
IPEndPoint groupEndPoint = new IPEndPoint(IPAddress.Any, Configuration.UDPPort);
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -285,12 +224,9 @@ namespace IFS.Transport
|
||||
}
|
||||
|
||||
return new IPAddress(broadcastAddress);
|
||||
}
|
||||
|
||||
// The ethertype used in the encapsulated 3mbit frame
|
||||
private readonly ushort _pupFrameType = 512;
|
||||
}
|
||||
|
||||
private RoutePupCallback _routerCallback;
|
||||
private ReceivedPacketCallback _routerCallback;
|
||||
|
||||
// Thread used for receive
|
||||
private Thread _receiveThread;
|
||||
|
||||
BIN
PUP/etherdata.bin
Normal file
BIN
PUP/etherdata.bin
Normal file
Binary file not shown.
BIN
PUP/ethertext.bin
Normal file
BIN
PUP/ethertext.bin
Normal file
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
Readme.txt for IFS v1.2:
|
||||
Readme.txt for IFS v1.4:
|
||||
|
||||
1. Introduction and Overview
|
||||
============================
|
||||
@@ -11,8 +11,10 @@ into being during the Alto's lifetime, so the IFS was a permanent fixture of
|
||||
the network environment at PARC during the heyday of the Alto.
|
||||
|
||||
The LCM+L's IFS implementation is an implementation of this protocol suite
|
||||
that runs on a modern PC, and is designed to work with the ContrAlto Alto
|
||||
emulator over either Raw Ethernet packets or UDP broadcasts.
|
||||
that runs on a modern PC. It is designed to work with the ContrAlto Alto
|
||||
emulator over either Raw Ethernet packets or UDP broadcasts, as well as with
|
||||
real hardware when using the BeagleBone-based Alto Ethernet Interface available at
|
||||
https://github.com/shirriff/alto-ethernet-interface.
|
||||
|
||||
It provides the following IFS services:
|
||||
|
||||
@@ -84,8 +86,12 @@ Directory configuration:
|
||||
(User mail folders are placed in this directory.)
|
||||
|
||||
Interface configuration:
|
||||
- InterfaceType: "RAW" or "UDP". Specifies the transport to use for
|
||||
communication.
|
||||
- InterfaceTypes: Any combination of "RAW" "UDP" or "3MBIT". Specifies
|
||||
the transports to use for communication:
|
||||
- RAW: Raw Ethernet frames
|
||||
- UDP: UDP Datagram
|
||||
- 3MBIT: The Beaglebone-based Alto Ethernet Interface
|
||||
|
||||
- InterfaceName: The name of the host network adapter to use for
|
||||
communication.
|
||||
|
||||
@@ -104,6 +110,12 @@ Debugging configuration:
|
||||
DirectoryServices, PUP, FTP, BreathOfLife, EFTP,
|
||||
BootServer, UDP, Mail, Configuration, or All
|
||||
|
||||
Misc:
|
||||
- RunIFSServices: Whether to run the full suite of IFS services or just
|
||||
perform basic bridging (this is mostly useful when
|
||||
working with real hardware via the Alto Interface Hardware
|
||||
at https://github.com/shirriff/alto-ethernet-interface.)
|
||||
|
||||
2.2 hosts.txt:
|
||||
--------------
|
||||
|
||||
@@ -398,9 +410,9 @@ The following documents may be useful in actually using the Alto-land client
|
||||
tools (FTP, CopyDisk, mail, etc) to communicate with the IFS server:
|
||||
|
||||
The Alto User's Handbook:
|
||||
http://bitsavers.org/pdf/xerox/alto/Alto_Users_Handbook_Sep79.pdf
|
||||
http://bitsavers.org/pdf/xerox/alto/Alto_Users_Handbook_Sep79.pdf
|
||||
Alto Subsystems:
|
||||
http://bitsavers.org/pdf/xerox/alto/memos_1981/Alto_Subsystems_May81.pdf
|
||||
http://bitsavers.org/pdf/xerox/alto/memos_1981/Alto_Subsystems_May81.pdf
|
||||
|
||||
|
||||
The following specifications were used to implement the IFS protocol suite:
|
||||
@@ -414,16 +426,48 @@ Misc Services:
|
||||
http://xeroxalto.computerhistory.org/_cd8_/pup/.miscservices.press!1.pdf
|
||||
|
||||
|
||||
8.0 Packet-Level Protocol
|
||||
8.0 Using With Real Hardware
|
||||
============================
|
||||
|
||||
IFS can be used to talk with real hardware that has a 3mbit Ethernet interface
|
||||
(Altos, Dolphins, Dorados, PERQs, and early Sun hardware).
|
||||
This is accomplished by using the BeagleBone Alto Ethernet Interface developed by
|
||||
Ken Sherriff and documented here: https://github.com/shirriff/alto-ethernet-interface.
|
||||
|
||||
Ken provides his own modification of this IFS code to use with his hardware,
|
||||
but as of V1.4, this IFS implementation provides all the functionality of his
|
||||
software, making his fork unnecessary. All that is necessary is to place the
|
||||
release binaries on the BeagleBone and ensure that IFS.exe is running (you must
|
||||
disable any earlier versions of IFS.exe and the 'gateway' program before doing so.)
|
||||
|
||||
Use of the BeagleBone Alto Ethernet Interface is enabled by specifying "3MBIT" as one
|
||||
of the InterfaceTypes options in Ifs.cfg (see section 2.1).
|
||||
|
||||
When running in this configuration, by default IFS will run just as it does on desktop PCs and
|
||||
will provide network services directly to the system it is connected to.
|
||||
|
||||
8.1 Bridging Mode
|
||||
-----------------
|
||||
Additionally, by setting the "RunIFSServices" parameter to false, IFS will run in
|
||||
a mode where it does not provide any IFS services, but instead bridges interfaces together:
|
||||
Packets received from a real Alto will be sent out over UDP and RAW interfaces (if configured)
|
||||
and similarly, packets coming in over UDP and RAW interfaces will be sent to the real Alto.
|
||||
This allows the IFS software to let real hardware directly communicate with other 3mbit devices on
|
||||
your local network (real or emulated) with a minimum of effort. The expectation in this
|
||||
mode is that some other system on the network (a PC or a server somewhere) will be providing
|
||||
IFS services, not the IFS running on the BeagleBone.
|
||||
|
||||
|
||||
9.0 Packet-Level Protocol
|
||||
=========================
|
||||
|
||||
IFS (and ContrAlto) use a very simple encapsulation for transmitting 3mbit
|
||||
Ethernet packets over modern transports. An encapsulated packet consists of
|
||||
two fields:
|
||||
- Packet Length (2 bytes): Length (in 16-bit words) of the 3mbit Packet
|
||||
Data field (see below)
|
||||
- Packet Data (N bytes): The 3mbit packet, including 3mbit Ethernet header
|
||||
but excluding the checksum word.
|
||||
- Packet Length (2 bytes): Length (in 16-bit words) of the 3mbit Packet
|
||||
Data field (see below)
|
||||
- Packet Data (N bytes): The 3mbit packet, including 3mbit Ethernet header
|
||||
but excluding the checksum word.
|
||||
|
||||
All words are stored in big-endian format.
|
||||
|
||||
@@ -437,7 +481,7 @@ As discussed in Section 5.0, all packets are broadcast. The technical reasons
|
||||
for this are documented in the source code; see Transport\UDP.cs for details.
|
||||
|
||||
|
||||
9.0 Thanks
|
||||
10.0 Thanks
|
||||
==========
|
||||
|
||||
This project would not have been possible without the conservation efforts of
|
||||
|
||||
Reference in New Issue
Block a user