private void SendMailCheckResponse(PUP p) { // // "Pup Contents: A string specifying the mailbox name." // // // See if there is any mail for the specified mailbox. // string mailboxName = Helpers.ArrayToString(p.Contents); // // If mailbox name has a host/registry appended, we will strip it off. // TODO: probably should validate host... // mailboxName = Authentication.GetUserNameFromFullName(mailboxName); IEnumerable <string> mailList = MailManager.EnumerateMail(mailboxName); if (mailList == null || mailList.Count() == 0) { PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket); PUP noMailReply = new PUP(PupType.NoNewMailExistsReply, p.ID, p.SourcePort, localPort, new byte[] { }); Router.Instance.SendPup(noMailReply); } else { PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket); PUP mailReply = new PUP(PupType.NewMailExistsReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray("You've got mail!")); Router.Instance.SendPup(mailReply); } }
/// <summary> /// Called by dispatcher to send incoming data destined for this protocol /// </summary> /// <param name="p"></param> public override void RecvData(PUP p) { // If this is an EchoMe packet, we will send back an "ImAnEcho" packet. if (p.Type == PupType.EchoMe) { // Just send it back with the source/destination swapped. PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket); // // An annoyance: The Alto "puptest" diagnostic actually expects us to echo *everything* back including // the garbage byte on odd-length PUPs. (Even though the garbage byte is meant to be ignored.) // So in these cases we need to do extra work and copy in the garbage byte. Grr. // byte[] contents; bool garbageByte = (p.Contents.Length % 2) != 0; if (!garbageByte) { // Even, no work needed contents = p.Contents; } else { // No such luck, copy in the extra garbage byte to make diagnostics happy. contents = new byte[p.Contents.Length + 1]; p.Contents.CopyTo(contents, 0); contents[contents.Length - 1] = p.RawData[p.RawData.Length - 3]; } PUP echoPup = new PUP(PupType.ImAnEcho, p.ID, p.SourcePort, localPort, contents, garbageByte); Router.Instance.SendPup(echoPup); } }
/// <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, byte transportControl, 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 = transportControl; 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); }
private void SendMicrocodeFile(PUPPort sourcePort, Stream microcodeFile) { // // "For version 1 of the protocol, a server willing to supply the data simply sends a sequence of packets // of type MicrocodeReply as fast as it can. The high half of its pupID contains the version number(1) // and the low half of the pupID contains the packet sequence number. After all the data packets // have been sent, the server sends an empty (0 data bytes) packet for an end marker. There are no // 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 // socket to be 4. / HGM May - 80." // // TODO: this should happen in a worker thread. // // // We send 192 words of data per PUP (3 * 64) in an attempt to make the Dolphin happy. // 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); byte[] buffer = new byte[384]; bool done = false; uint id = 0; while (!done) { int read = microcodeFile.Read(buffer, 0, buffer.Length); if (read < buffer.Length) { done = true; } if (read > 0) { PUP microcodeReply = new PUP(PupType.MicrocodeReply, (id | 0x00010000), sourcePort, localPort, buffer); Router.Instance.SendPup(microcodeReply); } // Pause a bit to give the D0 time to breathe. System.Threading.Thread.Sleep(5); id++; } // // Send an empty packet to conclude the transfer. // PUP endReply = new PUP(PupType.MicrocodeReply, (id | 0x00010000), sourcePort, localPort, new byte[] { }); Router.Instance.SendPup(endReply); Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file sent."); }
//string request = "GET /\r\n"; //Byte[] bytesSent = Encoding.ASCII.GetBytes(request); //Byte[] bytesReceived = new Byte[1000]; //s.Send(bytesSent, bytesSent.Length, 0); //int bytes = 0; //bytes = s.Receive(bytesReceived, bytesReceived.Length, 0); //string data = Encoding.ASCII.GetString(bytesReceived, 0, bytes); //Log.Write(LogType.Verbose, LogComponent.Exp, "Received {0}", data); private void SendNameLookupReply(PUP p) { // // For the request PUP: // 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 // name expression. Each block is a Pup Port structure, with the network and host numbers in // the first two bytes and the socket number in the last four bytes. // // // For now, the assumption is that each name maps to at most one address. // string lookupName = Helpers.ArrayToString(p.Contents); Log.Write(LogType.Verbose, LogComponent.MiscServices, "Name lookup is for '{0}'", lookupName); HostAddress address = DirectoryServices.Instance.NameLookup(lookupName); if (address == null) { address = ExternalHost.LookupExternalHost(lookupName); } if (address != null) { // We found an address, pack the port into the response. PUPPort lookupPort = new PUPPort(address, 0); PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket); PUP lookupReply = new PUP(PupType.NameLookupResponse, p.ID, p.SourcePort, localPort, lookupPort.ToArray()); Router.Instance.SendPup(lookupReply); Log.Write(LogType.Verbose, LogComponent.MiscServices, "Address is '{0}'", address); } else { // Unknown host, send an error reply string errorString = "Unknown host."; PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket); PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString)); Router.Instance.SendPup(errorReply); Log.Write(LogType.Verbose, LogComponent.MiscServices, "Host is unknown."); } }
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()); Router.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()); Router.Instance.SendPup(bootDirReply); } }
private void SendAddressLookupReply(PUP p) { // // Need to find more... useful documentation, but here's what I have: // For the request PUP: // A port (6 bytes). // // Response: // A string consisting of an inter-network name expression that matches the request Port. // // // I am at this time unsure what exactly an "inter-network name expression" consists of. // Empirically, a simple string name seems to make the Alto happy. // // // The request PUP contains a port address, we will check the host and network (and ignore the socket). // and see if we have a match. // PUPPort lookupAddress = new PUPPort(p.Contents, 0); string hostName = DirectoryServices.Instance.AddressLookup(new HostAddress(lookupAddress.Network, lookupAddress.Host)); if (!String.IsNullOrEmpty(hostName)) { // 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); Router.Instance.SendPup(lookupReply); } else { // Unknown host, send an error reply string errorString = "Unknown host."; PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket); PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString)); Router.Instance.SendPup(errorReply); } }
/// <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. 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 -- drop, or continue anyway? Log.Write(LogType.Warning, LogComponent.PUP, "PUP checksum is invalid. (got {0:x}, expected {1:x})", Checksum, cChecksum); } }
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. // // Additionally: While certain routines seem to be Y2K compliant (the time requests made from // the Alto's "puptest" diagnostic, for example), the Executive is not. To keep things happy, // we move things back 28 years so that the calendar at least matches up. // DateTime currentTime = new DateTime( DateTime.Now.Year - 28, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second); // The epoch for .NET is 1/1/0001 at 12 midnight and is counted in 100-ns intervals. // Some conversion is needed, is what I'm saying. DateTime altoEpoch = new DateTime(1901, 1, 1); TimeSpan timeSinceAltoEpoch = new TimeSpan(currentTime.Ticks - altoEpoch.Ticks); UInt32 altoTime = (UInt32)timeSinceAltoEpoch.TotalSeconds; // Build the response data AltoTime time = new AltoTime(); time.DateTime = altoTime; time.TimeZone = 0; // Hardcoded to GMT time.DSTStart = 366; // DST not specified yet time.DSTEnd = 366; 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); PUP response = new PUP(PupType.AltoTimeResponse, p.ID, remotePort, localPort, Serializer.Serialize(time)); Router.Instance.SendPup(response); }
private void SendAuthenticationResponse(PUP p) { // // "Pup Contents: Two Mesa strings (more precisely StringBodys), packed in such a way that // the maxLength of the first string may be used to locate the second string. The first // string is a user name and the second a password." // // I have chosen not to write a helper class encapsulating Mesa strings since this is the // first (and so far *only*) instance in which Mesa strings are used in IFS communications. // // Empirical analysis shows the format of a Mesa string to be: // Word 1: Length (bytes) // Word 2: MaxLength (bytes) // Byte 4 thru 4 + MaxLength: string data // data is padded to a word length. // string userName = Helpers.MesaArrayToString(p.Contents, 0); 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) { string errorString = "Invalid username or password."; PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket); PUP errorReply = new PUP(PupType.AuthenticateNegativeResponse, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString)); Router.Instance.SendPup(errorReply); } else { // S'ok! PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket); PUP okReply = new PUP(PupType.AuthenticatePositiveResponse, p.ID, p.SourcePort, localPort, new byte[] { }); Router.Instance.SendPup(okReply); } }
private void SendStringTimeReply(PUP p) { // // From the spec, the response is: // "A string consisting of the current date and time in the form // '11-SEP-75 15:44:25'" // NOTE: This is *not* a BCPL string, just the raw characters. // // It makes no mention of timezone or DST, so I am assuming local time here. // Good enough for government work. // DateTime currentTime = DateTime.Now; 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); Router.Instance.SendPup(response); }
/// <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> /// Same as above, no garbage byte. /// </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> /// Constructor that assumes a transport control of 0 /// </summary> /// <param name="type"></param> /// <param name="id"></param> /// <param name="destination"></param> /// <param name="source"></param> /// <param name="contents"></param> /// <param name="contentsContainsGarbageByte"></param> public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents, bool contentsContainsGarbageByte) : this(type, 0, id, destination, source, contents, contentsContainsGarbageByte) { }
public HostAddress(PUPPort port) { Network = port.Network; Host = port.Host; }