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

436 lines
14 KiB
C#

using IFS.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace IFS
{
/// <summary>
/// All of the different types of PUPs.
/// </summary>
public enum PupType
{
// Basic types
EchoMe = 1,
ImAnEcho = 2,
ImABadEcho = 3,
Error = 4,
// BSP/RFC types
RFC = 8,
Abort = 9,
End = 10,
EndReply = 11,
Data = 16,
AData = 17,
Ack = 18,
Mark = 19,
Interrupt = 20,
InterruptReply = 21,
AMark = 22,
// EFTP types
EFTPData = 24,
EFTPAck = 25,
EFTPEnd = 26,
EFTPAbort = 27,
// Misc. Services types
StringTimeRequest = 128,
StringTimeReply = 129,
TenexTimeRequest = 130,
TenexTimeReply = 131,
AltoTimeRequestOld = 132,
AltoTimeResponseOld = 133,
AltoTimeRequest = 134,
AltoTimeResponse = 135,
// Network Lookup
NameLookupRequest = 144,
NameLookupResponse = 145,
DirectoryLookupErrorReply = 146,
AddressLookupRequest = 147,
AddressLookupResponse = 148,
// Where is User
WhereIsUserRequest = 152,
WhereIsUserResponse = 153,
WhereIsUserErrorReply = 154,
// Alto Boot
SendBootFileRequest = 164,
BootDirectoryRequest = 175,
BootDirectoryReply = 176,
// User authentication
AuthenticateRequest = 168,
AuthenticatePositiveResponse = 169,
AuthenticateNegativeResponse = 170,
// Gateway Information Protocol
GatewayInformationRequest = 128,
GatewayInformationResponse = 129
}
public struct PUPPort
{
/// <summary>
/// Builds a new port address given network, host and socket parameters
/// </summary>
/// <param name="network"></param>
/// <param name="host"></param>
/// <param name="socket"></param>
public PUPPort(byte network, byte host, UInt32 socket)
{
Network = network;
Host = host;
Socket = socket;
}
/// <summary>
/// Builds a new port address given a HostAddress and a socket.
/// </summary>
/// <param name="address"></param>
/// <param name="socket"></param>
public PUPPort(HostAddress address, UInt32 socket)
{
Network = address.Network;
Host = address.Host;
Socket = socket;
}
/// <summary>
/// Builds a new port address from an array containing a raw port representaton
/// </summary>
/// <param name="rawData"></param>
/// <param name="offset"></param>
public PUPPort(byte[] rawData, int offset)
{
Network = rawData[offset];
Host = rawData[offset + 1];
Socket = Helpers.ReadUInt(rawData, offset + 2);
}
/// <summary>
/// Writes this address back out to a raw byte array at the specified offset
/// </summary>
/// <param name="rawData"></param>
/// <param name="offset"></param>
public void WriteToArray(ref byte[] rawData, int offset)
{
rawData[offset] = Network;
rawData[offset + 1] = Host;
Helpers.WriteUInt(ref rawData, Socket, offset + 2);
}
/// <summary>
/// Same as above, but simply returns a new array instead of writing into an existing one.
/// </summary>
/// <returns></returns>
public byte[] ToArray()
{
byte[] a = new byte[6];
WriteToArray(ref a, 0);
return a;
}
public override string ToString()
{
return String.Format("Net {0} Host {1} Socket {2}", Network, Host, Socket);
}
public byte Network;
public byte Host;
public UInt32 Socket;
}
public class PUP
{
/// <summary>
/// Construct a new packet from the supplied data.
/// </summary>
/// <param name="contentsContainsGarbageByte">
/// True if the "contents" array contains a garbage (padding) byte that should NOT
/// be factored into the Length field of the PUP. This is necessary so we can properly
/// support the Echo protocol; since some PUP tests that check validation require that the
/// PUP be echoed in its entirety (including the supposedly-ignorable "garbage" byte on
/// odd-length PUPs) we need to be able to craft a PUP with one extra byte of content that's
/// otherwise ignored...
///
/// TODO: Update to use Serialization code rather than packing bytes by hand.
/// </param>
///
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents, bool contentsContainsGarbageByte)
{
_rawData = null;
// Ensure content length is <= 532 bytes. (Technically larger PUPs are allowed,
// but conventionally they are not used and I want to keep things safe.)
if (contents.Length > MAX_PUP_SIZE)
{
throw new InvalidOperationException("PUP size must not exceed 532 bytes.");
}
//
// Sanity check:
// "contentsContainGarbageByte" can ONLY be true if "contents" is of even length
//
if (contentsContainsGarbageByte && (contents.Length % 2) != 0)
{
throw new InvalidOperationException("Odd content length with garbage byte specified.");
}
TransportControl = 0;
Type = type;
ID = id;
DestinationPort = destination;
SourcePort = source;
// Ensure contents are an even number of bytes.
int contentLength = (contents.Length % 2) == 0 ? contents.Length : contents.Length + 1;
Contents = new byte[contents.Length];
contents.CopyTo(Contents, 0);
// Length is always the real length of the data (not padded to an even number)
Length = (ushort)(PUP_HEADER_SIZE + PUP_CHECKSUM_SIZE + contents.Length);
// Stuff data into raw array
_rawData = new byte[PUP_HEADER_SIZE + PUP_CHECKSUM_SIZE + contentLength];
//
// Subtract off one byte from the Length value if the contents contain a garbage byte.
// (See header comments for function)
if (contentsContainsGarbageByte)
{
Length--;
}
Helpers.WriteUShort(ref _rawData, Length, 0);
_rawData[2] = TransportControl;
_rawData[3] = (byte)Type;
Helpers.WriteUInt(ref _rawData, ID, 4);
DestinationPort.WriteToArray(ref _rawData, 8);
SourcePort.WriteToArray(ref _rawData, 14);
Array.Copy(Contents, 0, _rawData, 20, Contents.Length);
// Calculate the checksum and stow it
Checksum = CalculateChecksum();
Helpers.WriteUShort(ref _rawData, Checksum, _rawData.Length - 2);
}
/// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <param name="id"></param>
/// <param name="destination"></param>
/// <param name="source"></param>
/// <param name="contents"></param>
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents) :
this(type, id, destination, source, contents, false)
{
}
/// <summary>
/// Same as above, but with no content (i.e. a zero-byte payload)
/// </summary>
/// <param name="type"></param>
/// <param name="id"></param>
/// <param name="destination"></param>
/// <param name="source"></param>
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source) :
this(type, id, destination, source, new byte[0])
{
}
/// <summary>
/// Load in an existing packet from a stream
/// </summary>
/// <param name="stream"></param>
public PUP(MemoryStream stream, int length)
{
_rawData = new byte[length];
stream.Read(_rawData, 0, length);
// Read fields in. TODO: investigate more efficient ways to do this.
Length = Helpers.ReadUShort(_rawData, 0);
// Sanity check size:
if (Length > length)
{
throw new InvalidOperationException("Length field in PUP is invalid.");
}
TransportControl = _rawData[2];
Type = (PupType)_rawData[3];
ID = Helpers.ReadUInt(_rawData, 4);
DestinationPort = new PUPPort(_rawData, 8);
SourcePort = new PUPPort(_rawData, 14);
int contentLength = Length - PUP_HEADER_SIZE - PUP_CHECKSUM_SIZE;
Contents = new byte[contentLength];
Array.Copy(_rawData, 20, Contents, 0, contentLength);
// Length is the number of valid bytes in the PUP, which may be an odd number.
// There are always an even number of bytes in the PUP, and the checksum
// therefore begins on an even byte boundary. Calculate the checksum offset
// appropriately. (Empirically, we could also just use the last word of the packet,
// as the Alto PUP implementation never appears to pad any extra data after the PUP, but
// this doesn't appear to be a requirement and I don't want to rely on it.)
int checksumOffset = (Length % 2) == 0 ? Length - PUP_CHECKSUM_SIZE : Length - PUP_CHECKSUM_SIZE + 1;
Checksum = Helpers.ReadUShort(_rawData, checksumOffset);
// Validate checksum
ushort cChecksum = CalculateChecksum();
if (Checksum != 0xffff && cChecksum != Checksum)
{
// TODO: determine what to do with packets that are corrupted.
Log.Write(LogType.Warning, LogComponent.PUP, "PUP checksum is invalid. (got {0:x}, expected {1:x})", Checksum, cChecksum);
}
}
public byte[] RawData
{
get { return _rawData; }
}
private ushort CalculateChecksum()
{
uint sum = 0;
// Sum over everything except the checksum word
for (int i=0; i< _rawData.Length - PUP_CHECKSUM_SIZE; i+=2)
{
ushort nextWord = Helpers.ReadUShort(_rawData, i);
//ushort nextWord = (ushort)((_rawData[i + 1] << 8) | _rawData[i]);
// 2's complement add with "end-around" carry results in
// 1's complement add
sum += nextWord;
uint carry = (sum & 0x10000) >> 16;
sum = (ushort)(sum + carry);
// Rotate left
sum = sum << 1;
carry = (sum & 0x10000) >> 16;
sum = (ushort)(sum + carry);
}
// Negative 0? convert to positive 0.
if (sum == 0xffff)
{
sum = 0;
}
return (ushort)sum;
}
public readonly ushort Length;
public readonly byte TransportControl;
public readonly PupType Type;
public readonly UInt32 ID;
public readonly PUPPort DestinationPort;
public readonly PUPPort SourcePort;
public readonly byte[] Contents;
public readonly ushort Checksum;
private byte[] _rawData;
public readonly static int MAX_PUP_SIZE = 532;
public readonly static int PUP_HEADER_SIZE = 20;
public readonly static int PUP_CHECKSUM_SIZE = 2;
}
public static class Helpers
{
public static ushort ReadUShort(byte[] data, int offset)
{
return (ushort)((data[offset] << 8) | data[offset + 1]);
}
public static ushort ReadUShort(Stream s)
{
return (ushort)((s.ReadByte() << 8) | s.ReadByte());
}
public static UInt32 ReadUInt(byte[] data, int offset)
{
return (UInt32)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]);
}
public static UInt32 ReadUInt(Stream s)
{
return (UInt32)((s.ReadByte() << 24) | (s.ReadByte() << 16) | (s.ReadByte() << 8) | s.ReadByte());
}
public static void WriteUShort(ref byte[] data, ushort s, int offset)
{
data[offset] = (byte)(s >> 8);
data[offset + 1] = (byte)s;
}
public static void WriteUShort(Stream st, ushort s)
{
st.WriteByte((byte)(s >> 8));
st.WriteByte((byte)s);
}
public static void WriteUInt(ref byte[] data, UInt32 s, int offset)
{
data[offset] = (byte)(s >> 24);
data[offset + 1] = (byte)(s >> 16);
data[offset + 2] = (byte)(s >> 8);
data[offset + 3] = (byte)s;
}
public static void WriteUInt(Stream st, UInt32 s)
{
st.WriteByte((byte)(s >> 24));
st.WriteByte((byte)(s >> 16));
st.WriteByte((byte)(s >> 8));
st.WriteByte((byte)s);
}
public static byte[] StringToArray(string s)
{
byte[] stringArray = new byte[s.Length];
// We simply take the low 8-bits of each Unicode character and stuff it into the
// byte array. This works fine for the ASCII subset of Unicode but obviously
// is bad for everything else. This is unlikely to be an issue given the lack of
// any real internationalization support on the IFS end of things, but might be
// something to look at.
for (int i = 0; i < stringArray.Length; i++)
{
stringArray[i] = (byte)s[i];
}
return stringArray;
}
public static string ArrayToString(byte[] a)
{
StringBuilder sb = new StringBuilder(a.Length);
for (int i = 0; i < a.Length; i++)
{
sb.Append((char)(a[i]));
}
return sb.ToString();
}
}
}