1
0
mirror of https://github.com/livingcomputermuseum/IFS.git synced 2026-01-13 15:27:25 +00:00

Fixed BSP issue on flaky nets, implemented EFTP and Boot services.

This commit is contained in:
Josh Dersch 2016-02-17 13:52:34 -08:00
parent 39a32469d3
commit 1d90b5e8ae
15 changed files with 795 additions and 76 deletions

View File

@ -587,7 +587,7 @@ namespace IFS.BSP
//
// Pull the next PUP off the output queue and send it.
//
PUP nextPup = _outputWindow[_outputWindowIndex++];
PUP nextPup = _outputWindow[_outputWindowIndex++];
//
// If we've sent as many PUPs to the client as it says it can take,
@ -606,13 +606,13 @@ namespace IFS.BSP
// TODO: rewrite the underlying PUP code so we don't have to completely recreate the PUPs like this, it makes me hurt.
//
if (nextPup.Type == PupType.Data || nextPup.Type == PupType.AData)
{
nextPup = new PUP(bAck ? PupType.AData : PupType.Data, _sendPos, nextPup.DestinationPort, nextPup.SourcePort, nextPup.Contents);
{
_outputWindow[_outputWindowIndex - 1] = nextPup = new PUP(bAck ? PupType.AData : PupType.Data, _sendPos, nextPup.DestinationPort, nextPup.SourcePort, nextPup.Contents);
}
else if (nextPup.Type == PupType.Mark || nextPup.Type == PupType.AMark)
{
nextPup = new PUP(bAck ? PupType.AMark : PupType.Mark, _sendPos, nextPup.DestinationPort, nextPup.SourcePort, nextPup.Contents);
}
_outputWindow[_outputWindowIndex - 1] = nextPup = new PUP(bAck ? PupType.AMark : PupType.Mark, _sendPos, nextPup.DestinationPort, nextPup.SourcePort, nextPup.Contents);
}
//
// Send it!
@ -634,8 +634,9 @@ namespace IFS.BSP
{
break;
}
// Nope. Request another ACK.
Log.Write(LogType.Verbose, LogComponent.BSP, "Waiting for client to become ready..");
}
//
@ -649,15 +650,23 @@ namespace IFS.BSP
_lastClientRecvPos,
_sendPos);
Log.Write(LogType.Warning, LogComponent.BSP,
"First position in window is {0}.",
_outputWindow[0].ID);
// Require ACKs for all retried packets.
bAck = true;
//
// Move our window index back to the first PUP we missed and start resending from that position.
//
_outputWindowIndex = 0;
while(_outputWindowIndex < _outputWindow.Count)
{
{
if (_outputWindow[_outputWindowIndex].ID == _lastClientRecvPos)
{
_sendPos = _outputWindow[_outputWindowIndex].ID;
Log.Write(LogType.Verbose, LogComponent.BSP, "Resending from position {0}", _sendPos);
break;
}
_outputWindowIndex++;

View File

@ -1,24 +1,152 @@
using System;
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IFS.Boot
{
public class BootServerProtocol : PUPProtocolBase
public struct BootFileEntry
{
public BootServerProtocol()
{
public ushort BootNumber;
public string Filename;
public DateTime Date;
}
/// <summary>
/// BootServer provides encapsulation of boot file mappings and enumerations.
/// </summary>
public static class BootServer
{
static BootServer()
{
LoadBootFileTables();
}
/// <summary>
/// Called by dispatcher to send incoming data destined for this protocol
/// </summary>
/// <param name="p"></param>
public override void RecvData(PUP p)
private static void LoadBootFileTables()
{
_numberToNameTable = new Dictionary<ushort, string>();
using (StreamReader sr = new StreamReader("Conf\\bootdirectory.txt"))
{
int lineNumber = 0;
while (!sr.EndOfStream)
{
//
// Read in the next line. This is either:
// - A comment to EOL (starting with "#")
// - empty
// - a mapping, consisting of two tokens: a number and a name.
//
lineNumber++;
string line = sr.ReadLine().Trim();
if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
{
continue;
}
string[] tokens = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length != 2)
{
Log.Write(LogType.Warning, LogComponent.BootServer,
"bootdirectory.txt line {0}: Invalid syntax: expected two tokens, got {1}. Ignoring.", lineNumber, tokens.Length);
continue;
}
else
{
ushort bootNumber = 0;
try
{
bootNumber = Convert.ToUInt16(tokens[0], 8);
}
catch
{
Log.Write(LogType.Warning, LogComponent.BootServer,
"bootdirectory.txt line {0}: Invalid syntax: '{1}' is not a valid integer value.", lineNumber, tokens[0]);
continue;
}
//
// Validate that the file exists in the boot subdirectory.
//
if (!File.Exists(Path.Combine(Configuration.BootRoot, tokens[1])))
{
Log.Write(LogType.Warning, LogComponent.BootServer,
"bootdirectory.txt line {0}: Specified boot file '{1}' does not exist.", lineNumber, tokens[1]);
continue;
}
//
// Looks like this entry is OK syntactically, add it if it isn't a duplicate.
//
if (!_numberToNameTable.ContainsKey(bootNumber))
{
_numberToNameTable.Add(bootNumber, tokens[1]);
}
else
{
Log.Write(LogType.Warning, LogComponent.BootServer,
"bootdirectory.txt line {0}: Specified boot file '{1}' has already been specified. Ignoring duplicate.", lineNumber, bootNumber);
continue;
}
}
}
}
}
public static string GetFileNameForNumber(ushort number)
{
if (_numberToNameTable.ContainsKey(number))
{
return _numberToNameTable[number];
}
else
{
return null;
}
}
public static FileStream GetStreamForNumber(ushort number)
{
if (_numberToNameTable.ContainsKey(number))
{
string filePath = Path.Combine(Configuration.BootRoot, _numberToNameTable[number]);
try
{
return new FileStream(filePath, FileMode.Open, FileAccess.Read);
}
catch (Exception e)
{
Log.Write(LogType.Warning, LogComponent.BootServer, "Bootfile {0} could not be opened, error is '{1}'", filePath, e.Message);
return null;
}
}
else
{
return null;
}
}
public static List<BootFileEntry> EnumerateBootFiles()
{
List<BootFileEntry> bootFiles = new List<BootFileEntry>();
foreach(ushort key in _numberToNameTable.Keys)
{
BootFileEntry newEntry = new BootFileEntry();
newEntry.BootNumber = key;
newEntry.Filename = _numberToNameTable[key];
newEntry.Date = new DateTime(1978, 2, 15); // todo: get real date
bootFiles.Add(newEntry);
}
return bootFiles;
}
private static Dictionary<ushort, string> _numberToNameTable;
}
}

View File

@ -0,0 +1,64 @@
# bootdirectory.txt:
#
# This file contains a simple mapping from boot number to boot file, one entry per line
# in the form:
# <number(8)> <name>
#
# Boot files are expected to reside in the "boot" subdirectory of the IFS root store.
#
# The order of the files is based on the order used on authentic Xerox Gateway servers, for example
# the config listed here: http://xeroxalto.computerhistory.org/Io/Murray/.Brio.txt!1.html
#
0 dmt.boot
1 newos.boot
2 ftp.boot
3 scavenger.boot
4 copydisk.boot
5 crttest.boot
6 madtest.boot
7 chat.boot
10 netexec.boot
11 puptest.boot
12 etherwatch.boot
13 keytest.boot
14 calculator.boot
15 diex.boot
16 triex.boot
17 edp.boot
20 bfstest.boot
21 gatecontrol.boot
22 etherload.boot
24 neptune.boot
25 what.boot
40 pupwatch.boot
# past this point are more esoteric things, here because they're cool
100 mesanetexec.boot
200 cedarnetexec.boot
1000 showais.boot
1001 starwars.boot
1002 fly.boot
1003 kal.boot
1004 pinball.boot
1005 pool.boot
1006 mazewar.boot
1007 trek.boot
1010 invaders.boot
1011 astroroids.boot
1012 astroroids.boot
1013 clock.boot
1014 galaxian.boot
1015 ppong.boot
1017 messenger.boot
1020 reversi.boot
1023 missilecommand.boot
40000 boggs.boot
40002 murray.boot
40003 johnsson.boot
43000 alphamesamesanetexec.boot

View File

@ -10,10 +10,10 @@ namespace IFS
/// Encapsulates global server configuration information.
///
/// TODO: read in configuration from a text file.
/// TODO also: make cross-platform compatible (no hardcoding of path delimiters).
/// </summary>
public static class Configuration
{
/// <summary>
/// The root directory for the FTP file store.
/// </summary>
@ -24,5 +24,10 @@ namespace IFS
/// </summary>
public static readonly string CopyDiskRoot = "C:\\ifs\\copydisk";
/// <summary>
/// The root directory for the Boot file store.
/// </summary>
public static readonly string BootRoot = "C:\\ifs\\boot";
}
}

206
PUP/EFTP/EFTPChannel.cs Normal file
View File

@ -0,0 +1,206 @@
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace IFS.EFTP
{
/// <summary>
/// Represents an open EFTP channel and provides methods for sending data to it.
/// (Receiving is not yet implemented, the current implementation exists only to support Boot File requests.)
/// </summary>
public class EFTPChannel
{
public EFTPChannel(PUPPort destination, UInt32 socketID)
{
_clientConnectionPort = destination;
_outputAckEvent = new AutoResetEvent(false);
// We create our connection port using a unique socket address.
_serverConnectionPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, socketID);
_outputQueue = new Queue<byte>(65536);
_sendPos = 0;
}
public PUPPort ServerPort
{
get { return _serverConnectionPort; }
}
public delegate void DestroyDelegate();
public DestroyDelegate OnDestroy;
public void Destroy()
{
if (OnDestroy != null)
{
OnDestroy();
}
}
/// <summary>
/// Sends data to the channel (i.e. to the client). Will block (waiting for an ACK) if an ACK is requested.
/// </summary>
/// <param name="data">The data to be sent</param>
/// <param name="flush">Whether to flush data out immediately or to wait for enough for a full PUP first.</param>
public void Send(byte[] data, int length, bool flush)
{
if (length > data.Length)
{
throw new InvalidOperationException("Length must be less than or equal to the size of data.");
}
// Add output data to output queue.
// Again, this is really inefficient.
for (int i = 0; i < length; i++)
{
_outputQueue.Enqueue(data[i]);
}
if (flush || _outputQueue.Count >= PUP.MAX_PUP_SIZE)
{
// Send data until all is used (for a flush) or until we have less than a full PUP (non-flush).
while (_outputQueue.Count >= (flush ? 1 : PUP.MAX_PUP_SIZE))
{
byte[] chunk = new byte[Math.Min(PUP.MAX_PUP_SIZE, _outputQueue.Count)];
// Ugh.
for (int i = 0; i < chunk.Length; i++)
{
chunk[i] = _outputQueue.Dequeue();
}
while (true)
{
// Send the data.
PUP dataPup = new PUP(PupType.EFTPData, _sendPos, _clientConnectionPort, _serverConnectionPort, chunk);
PUPProtocolDispatcher.Instance.SendPup(dataPup);
// Await an ACK. We will retry several times and resend as necessary.
int retry = 0;
for (retry = 0; retry < EFTPRetryCount; retry++)
{
if (_outputAckEvent.WaitOne(EFTPAckTimeoutPeriod))
{
// done, we got our ACK.
break;
}
else
{
// timeout: resend the PUP and wait for an ACK again.
PUPProtocolDispatcher.Instance.SendPup(dataPup);
}
}
if (retry >= EFTPRetryCount)
{
Log.Write(LogType.Error, LogComponent.EFTP, "Timeout waiting for ACK, aborting connection.");
SendAbort("Client unresponsive.");
EFTPManager.DestroyChannel(this);
}
if (_lastRecvPos == _sendPos)
{
// The client is in sync with us, we are done with this packet.
break;
}
else if (_sendPos - _lastRecvPos > 1)
{
// We lost more than one packet, something is very broken.
Log.Write(LogType.Error, LogComponent.EFTP, "Client lost more than one packet, connection is broken. Aborting.");
SendAbort("Client lost too much data.");
EFTPManager.DestroyChannel(this);
}
else
{
// We lost one packet, move back and send it again.
Log.Write(LogType.Warning, LogComponent.EFTP, "Client lost a packet, resending.");
}
}
// Move to next packet.
_sendPos++;
}
}
}
public void SendEnd()
{
PUP endPup = new PUP(PupType.EFTPEnd, _sendPos, _clientConnectionPort, _serverConnectionPort, new byte[0]);
PUPProtocolDispatcher.Instance.SendPup(endPup);
// Await an ack
_outputAckEvent.WaitOne(EFTPAckTimeoutPeriod);
_sendPos++;
// Send another end to close things off.
endPup = new PUP(PupType.EFTPEnd, _sendPos, _clientConnectionPort, _serverConnectionPort, new byte[0]);
PUPProtocolDispatcher.Instance.SendPup(endPup);
}
public void RecvData(PUP p)
{
// For now, receive is not implemented.
throw new NotImplementedException();
}
public void RecvAck(PUP p)
{
//
// Sanity check that the client's position matches ours.
//
_lastRecvPos = p.ID;
if (_lastRecvPos != _sendPos)
{
Log.Write(LogType.Error, LogComponent.EFTP, "Client position does not match server ({0} != {1}",
_lastRecvPos, _sendPos);
}
//
// Unblock those waiting for an ACK.
//
_outputAckEvent.Set();
}
public void End(PUP p)
{
}
private void SendAbort(string message)
{
/*
PUP abortPup = new PUP(PupType.EFTPAbort, _sendPos, _clientConnectionPort, _serverConnectionPort, Helpers.StringToArray(message));
//
// Send this directly, do not wait for the client to be ready (since it may be wedged, and we don't expect anyone to actually notice
// this anyway).
//
PUPProtocolDispatcher.Instance.SendPup(abortPup);
*/
}
private PUPPort _clientConnectionPort;
private PUPPort _serverConnectionPort;
private uint _sendPos;
private uint _lastRecvPos;
private Queue<byte> _outputQueue;
private AutoResetEvent _outputAckEvent;
// Timeouts and retries
private const int EFTPRetryCount = 5;
private const int EFTPAckTimeoutPeriod = 1000; // 1 second
}
}

164
PUP/EFTP/EFTPManager.cs Normal file
View File

@ -0,0 +1,164 @@
using IFS.BSP;
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
namespace IFS.EFTP
{
/// <summary>
/// EFTP: It's like a really limited version of BSP.
/// This is not a standalone server like FTP but provides routines for sending / receiving data
/// via EFTP, so that actual servers (Boot, Printing, etc.) can serve their clients.
/// </summary>
public static class EFTPManager
{
static EFTPManager()
{
//
// Initialize the socket ID counter; we start with a
// number beyond the range of well-defined sockets.
// For each new EFTP channel that gets opened, we will
// increment this counter to ensure that each channel gets
// a unique ID. (Well, until we wrap around...)
//
_nextSocketID = _startingSocketID;
_activeChannels = new Dictionary<uint, EFTPChannel>();
}
public static void SendFile(PUPPort destination, Stream data)
{
UInt32 socketID = GetNextSocketID();
EFTPChannel newChannel = new EFTPChannel(destination, socketID);
_activeChannels.Add(socketID, newChannel);
EFTPSend.StartSend(newChannel, data);
}
public static void RecvData(PUP p)
{
EFTPChannel channel = FindChannelForPup(p);
if (channel == null)
{
Log.Write(LogType.Error, LogComponent.EFTP, "Received EFTP PUP on an unconnected socket, ignoring.");
return;
}
switch (p.Type)
{
case PupType.EFTPData:
{
channel.RecvData(p);
}
break;
case PupType.EFTPAck:
{
channel.RecvAck(p);
}
break;
case PupType.EFTPEnd:
{
channel.End(p);
}
break;
case PupType.EFTPAbort:
{
string abortMessage = Helpers.ArrayToString(p.Contents);
Log.Write(LogType.Warning, LogComponent.RTP, String.Format("EFTP aborted, message: '{0}'", abortMessage));
DestroyChannel(channel);
}
break;
default:
throw new NotImplementedException(String.Format("Unhandled EFTP PUP type {0}.", p.Type));
}
}
/// <summary>
/// Indicates whether a channel exists for the socket specified by the given PUP.
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
public static bool ChannelExistsForSocket(PUP p)
{
return FindChannelForPup(p) != null;
}
/// <summary>
/// Destroys and unregisters the specified channel.
/// </summary>
/// <param name="channel"></param>
public static void DestroyChannel(EFTPChannel channel)
{
channel.Destroy();
_activeChannels.Remove(channel.ServerPort.Socket);
}
/// <summary>
/// Finds the appropriate channel for the given PUP.
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
private static EFTPChannel FindChannelForPup(PUP p)
{
if (_activeChannels.ContainsKey(p.DestinationPort.Socket))
{
return _activeChannels[p.DestinationPort.Socket];
}
else
{
return null;
}
}
/// <summary>
/// Generates a unique Socket ID. We count downward from the max value
/// (whereas BSP counts upwards) so as to avoid any duplicates.
/// TODO: this could be done much more sanely via a centralized ID factory...
/// </summary>
/// <returns></returns>
private static UInt32 GetNextSocketID()
{
UInt32 next = _nextSocketID;
_nextSocketID--;
//
// Handle the wrap around case (which we're very unlikely to
// ever hit, but why not do the right thing).
// Start over at the initial ID. This is very unlikely to
// collide with any pending channels.
//
if (_nextSocketID < 0x1000)
{
_nextSocketID = _startingSocketID;
}
return next;
}
/// <summary>
/// Map from socket address to BSP channel
/// </summary>
private static Dictionary<UInt32, EFTPChannel> _activeChannels;
private static UInt32 _nextSocketID;
private static readonly UInt32 _startingSocketID = UInt32.MaxValue;
}
}

65
PUP/EFTP/EFTPSend.cs Normal file
View File

@ -0,0 +1,65 @@
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading;
namespace IFS.EFTP
{
public class EFTPSend
{
public static void StartSend(EFTPChannel channel, Stream data)
{
EFTPSend newSender = new EFTPSend(channel, data);
}
private EFTPSend(EFTPChannel channel, Stream data)
{
_data = data;
_channel = channel;
_workerThread = new Thread(SendWorker);
_workerThread.Start();
channel.OnDestroy += OnChannelDestroyed;
}
private void SendWorker()
{
byte[] block = new byte[512];
while(true)
{
//
// Send the file 512 bytes at a time to the channel, and then finish.
//
int read = _data.Read(block, 0, block.Length);
_channel.Send(block, read, true);
Log.Write(LogType.Verbose, LogComponent.EFTP, "Sent data, position is now {0}", _data.Position);
if (read != block.Length)
{
_channel.SendEnd();
break;
}
}
_data.Close();
}
private void OnChannelDestroyed()
{
_workerThread.Abort();
_data.Close();
}
private Thread _workerThread;
private EFTPChannel _channel;
private Stream _data;
}
}

View File

@ -1,37 +0,0 @@
using IFS.BSP;
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
namespace IFS.EFTP
{
/// <summary>
/// EFTP: It's like a really limited version of BSP.
/// This is not a standalone server like FTP but provides routines for sending / receiving data
/// via FTP, so that actual servers (Boot, Printing, etc.) can serve their clients.
/// </summary>
public class EFTPServer : PUPProtocolBase
{
/// <summary>
/// Called by dispatcher to send incoming data destined for this protocol.
/// </summary>
/// <param name="p"></param>
public override void RecvData(PUP p)
{
}
}
public class EFTPWorker
{
public EFTPWorker(BSPChannel channel)
{
}
}
}

View File

@ -27,8 +27,7 @@ namespace IFS
// Connectionless
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Gateway Information", 2, ConnectionType.Connectionless, new GatewayInformationProtocol()));
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Misc Services", 0x4, ConnectionType.Connectionless, new MiscServicesProtocol()));
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Echo", 0x5, ConnectionType.Connectionless, new EchoProtocol()));
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Boot", 0x10, ConnectionType.Connectionless, new BootServerProtocol()));
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Echo", 0x5, ConnectionType.Connectionless, new EchoProtocol()));
// RTP/BSP based:
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("CopyDisk", 0x15 /* 25B */, ConnectionType.BSP, new CopyDiskServer()));

View File

@ -172,6 +172,7 @@ namespace IFS.FTP
break;
case FTPCommand.Enumerate:
case FTPCommand.NewEnumerate:
{
// Argument to Enumerate is a property list (string).
//
@ -180,7 +181,7 @@ namespace IFS.FTP
PropertyList pl = new PropertyList(fileSpec);
EnumerateFiles(pl);
EnumerateFiles(pl, command == FTPCommand.NewEnumerate);
}
break;
@ -318,7 +319,7 @@ namespace IFS.FTP
/// Enumerates the files matching the requested file specification.
/// </summary>
/// <param name="fileSpec"></param>
private void EnumerateFiles(PropertyList fileSpec)
private void EnumerateFiles(PropertyList fileSpec, bool newEnumerate)
{
string fullPath = BuildAndValidateFilePath(fileSpec);
@ -329,11 +330,29 @@ namespace IFS.FTP
List<PropertyList> files = EnumerateFiles(fullPath);
// Send each property list to the user
foreach(PropertyList matchingFile in files)
{
if (newEnumerate)
{
// "New" enumerate: send all property lists concatenated together in a single Here-Is-Property-List
// command.
StringBuilder sb = new StringBuilder();
foreach (PropertyList matchingFile in files)
{
sb.Append(matchingFile.ToString());
}
_channel.SendMark((byte)FTPCommand.HereIsPropertyList, false);
_channel.Send(Helpers.StringToArray(matchingFile.ToString()));
_channel.Send(Helpers.StringToArray(sb.ToString()));
}
else
{
// "Old" enumrate: send each property list to the user in separate Here-Is-Property-List
// command.
foreach (PropertyList matchingFile in files)
{
_channel.SendMark((byte)FTPCommand.HereIsPropertyList, false);
_channel.Send(Helpers.StringToArray(matchingFile.ToString()));
}
}
// End the enumeration.

View File

@ -82,7 +82,9 @@
<Compile Include="CopyDisk\DiabloPack.cs" />
<Compile Include="DirectoryServices.cs" />
<Compile Include="EchoProtocol.cs" />
<Compile Include="EFTP\EFTPServer.cs" />
<Compile Include="EFTP\EFTPChannel.cs" />
<Compile Include="EFTP\EFTPManager.cs" />
<Compile Include="EFTP\EFTPSend.cs" />
<Compile Include="Entrypoint.cs" />
<Compile Include="FTP\FTPServer.cs" />
<Compile Include="FTP\PropertyList.cs" />
@ -98,6 +100,9 @@
<Compile Include="Transport\PacketInterface.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Conf\bootdirectory.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Conf\hosts.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@ -21,6 +21,8 @@ namespace IFS.Logging
PUP = 0x40,
FTP = 0x80,
BreathOfLife = 0x100,
EFTP = 0x200,
BootServer = 0x400,
All = 0x7fffffff
}

View File

@ -1,6 +1,10 @@
using IFS.Logging;
using IFS.Boot;
using IFS.EFTP;
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -80,7 +84,11 @@ namespace IFS
case PupType.SendBootFileRequest:
SendBootFile(p);
break;
break;
case PupType.BootDirectoryRequest:
SendBootDirectory(p);
break;
default:
Log.Write(LogComponent.MiscServices, String.Format("Unhandled misc. protocol {0}", p.Type));
@ -182,7 +190,7 @@ namespace IFS
// NOTE: This is *not* a BCPL string, just the raw characters.
byte[] interNetworkName = Helpers.StringToArray(hostName);
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP lookupReply = new PUP(PupType.AddressLookupResponse, p.ID, p.SourcePort, localPort, interNetworkName);
PUPProtocolDispatcher.Instance.SendPup(lookupReply);
@ -191,7 +199,7 @@ namespace IFS
{
// Unknown host, send an error reply
string errorString = "Unknown host.";
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString));
PUPProtocolDispatcher.Instance.SendPup(errorReply);
@ -222,7 +230,7 @@ namespace IFS
{
// 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);
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP lookupReply = new PUP(PupType.NameLookupResponse, p.ID, p.SourcePort, localPort, lookupPort.ToArray());
PUPProtocolDispatcher.Instance.SendPup(lookupReply);
@ -231,7 +239,7 @@ namespace IFS
{
// Unknown host, send an error reply
string errorString = "Unknown host.";
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString));
PUPProtocolDispatcher.Instance.SendPup(errorReply);
@ -244,11 +252,79 @@ namespace IFS
// 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.
//
uint fileNumber = p.ID & 0xffff;
ushort fileNumber = (ushort)p.ID;
Log.Write(LogType.Verbose, LogComponent.MiscServices, "Boot file request is for file {0}.", fileNumber);
FileStream bootFile = BootServer.GetStreamForNumber(fileNumber);
if (bootFile == null)
{
Log.Write(LogType.Warning, LogComponent.MiscServices, "Boot file {0} does not exist or could not be opened.", fileNumber);
}
else
{
// Send the file.
EFTPManager.SendFile(p.SourcePort, bootFile);
}
}
private void SendBootDirectory(PUP p)
{
//
// From etherboot.bravo
// "Pup ID: if it is in reply to a BootDirRequest, the ID should match the request.
// Pup Contents: 1 or more blocks of the following format: A boot file number (the number that goes in the low 16 bits of a
// BootFileRequest Pup), an Alto format date (2 words), a boot file name in BCPL string format."
//
MemoryStream ms = new MemoryStream(PUP.MAX_PUP_SIZE);
List<BootFileEntry> bootFiles = BootServer.EnumerateBootFiles();
foreach(BootFileEntry entry in bootFiles)
{
BootDirectoryBlock block;
block.FileNumber = entry.BootNumber;
block.FileDate = 0;
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.
//
if (serialized.Length + ms.Length <= PUP.MAX_PUP_SIZE)
{
ms.Write(serialized, 0, serialized.Length);
}
else
{
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP bootDirReply = new PUP(PupType.BootDirectoryReply, p.ID, p.SourcePort, localPort, ms.ToArray());
PUPProtocolDispatcher.Instance.SendPup(bootDirReply);
ms.Seek(0, SeekOrigin.Begin);
ms.SetLength(0);
}
}
// Shuffle out any remaining data.
if (ms.Length > 0)
{
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP bootDirReply = new PUP(PupType.BootDirectoryReply, p.ID, p.SourcePort, localPort, ms.ToArray());
PUPProtocolDispatcher.Instance.SendPup(bootDirReply);
}
}
private struct BootDirectoryBlock
{
public ushort FileNumber;
public UInt32 FileDate;
public BCPLString FileName;
}
}
}

View File

@ -63,8 +63,8 @@ namespace IFS
// Alto Boot
SendBootFileRequest = 164,
BootDirectoryRequest = 165,
BootDirectoryReply = 166,
BootDirectoryRequest = 175,
BootDirectoryReply = 176,
// User authentication
AuthenticateRequest = 168,

View File

@ -1,4 +1,5 @@
using IFS.BSP;
using IFS.EFTP;
using IFS.Logging;
using IFS.Transport;
@ -61,8 +62,15 @@ namespace IFS
}
public void SendPup(PUP p)
{
_pupPacketInterface.Send(p);
{
// drop every 10th packet for testing
_packet++;
// if ((_packet % 10) != 5)
{
_pupPacketInterface.Send(p);
}
}
public void Send(byte[] data, byte source, byte destination, ushort frameType)
@ -71,7 +79,7 @@ namespace IFS
{
_rawPacketInterface.Send(data, source, destination, frameType);
}
}
}
private void OnPupReceived(PUP pup)
{
@ -115,10 +123,14 @@ namespace IFS
// An established BSP channel, send data to it.
BSPManager.RecvData(pup);
}
else if (EFTPManager.ChannelExistsForSocket(pup))
{
EFTPManager.RecvData(pup);
}
else
{
// Not a protocol we handle; log it.
Log.Write(LogType.Normal, LogComponent.PUP, "Unhandled PUP protocol, socket {0}, dropped packet.", pup.DestinationPort.Socket);
Log.Write(LogType.Normal, LogComponent.PUP, "Unhandled PUP protocol, source socket {0}, destination socket {1}, type {2}, dropped packet.", pup.SourcePort.Socket, pup.DestinationPort.Socket, pup.Type);
}
}
@ -137,6 +149,8 @@ namespace IFS
/// </summary>
private Dictionary<UInt32, PUPProtocolEntry> _dispatchMap;
private int _packet;
private static PUPProtocolDispatcher _instance = new PUPProtocolDispatcher();
}
}