diff options
Diffstat (limited to 'src/DuetNG/DuetEthernet/Webserver.h')
-rw-r--r-- | src/DuetNG/DuetEthernet/Webserver.h | 336 |
1 files changed, 322 insertions, 14 deletions
diff --git a/src/DuetNG/DuetEthernet/Webserver.h b/src/DuetNG/DuetEthernet/Webserver.h index c03388c2..1b4975bc 100644 --- a/src/DuetNG/DuetEthernet/Webserver.h +++ b/src/DuetNG/DuetEthernet/Webserver.h @@ -30,8 +30,40 @@ Licence: GPL #ifndef WEBSERVER_H #define WEBSERVER_H -#include <cstdint> -#include <cstddef> +/* Generic values */ + +const size_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2 +const unsigned int TCP_MSS = 1460; + +class ConnectionState; +class NetworkTransaction; + +/* HTTP */ + +#define KO_START "rr_" +#define KO_FIRST 3 + +const uint16_t webMessageLength = TCP_MSS; // maximum length of the web message we accept after decoding +const size_t minHttpResponseSize = 768; // minimum number of bytes required for an HTTP response + +const size_t maxCommandWords = 4; // max number of space-separated words in the command +const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier +const size_t maxHeaders = 16; // max number of key/value pairs in the headers + +const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions +const uint32_t httpSessionTimeout = 8000; // HTTP session timeout in milliseconds + +/* FTP */ + +const uint16_t ftpMessageLength = 128; // maximum line length for incoming FTP commands +const uint32_t ftpPasvPortTimeout = 10000; // maximum time to wait for an FTP data connection in milliseconds + +/* Telnet */ + +const uint32_t telnetSetupDuration = 4000; // ignore the first Telnet request within this duration (in ms) + + +class Webserver; // List of protocols that can execute G-Codes enum class WebSource @@ -40,20 +72,56 @@ enum class WebSource Telnet }; -const uint16_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2 -const uint16_t webMessageLength = 2000; // maximum length of the web message we accept after decoding -const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier -const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions -const uint32_t httpSessionTimeout = 20000; // HTTP session timeout in milliseconds +// This is the abstract class for all supported protocols +// Any inherited class should implement a state machine to increase performance and reduce memory usage. +class ProtocolInterpreter +{ + public: + + ProtocolInterpreter(Platform *p, Webserver *ws, Network *n); + virtual ~ProtocolInterpreter() { } // to keep Eclipse happy + virtual void Diagnostics(MessageType mtype) = 0; + virtual void Spin(); + + virtual void ConnectionEstablished(); + virtual void ConnectionLost(const ConnectionState *cs) { } + virtual bool CanParseData(); + virtual bool CharFromClient(const char c) = 0; + virtual void NoMoreDataAvailable(); + + virtual bool DoingFastUpload() const; + virtual void DoFastUpload(); + void CancelUpload(); // may be called from ISR! + + protected: -class Platform; -class Network; + Platform *platform; + Webserver *webserver; + Network *network; + + // Information for file uploading + enum UploadState + { + notUploading, // no upload in progress + uploadOK, // upload in progress, no error so far + uploadError // upload in progress but had error + }; + + UploadState uploadState; + FileData fileBeingUploaded; + char filenameBeingUploaded[FILENAME_LENGTH]; + + bool StartUpload(FileStore *file, const char *fileName); + bool IsUploading() const; + bool FinishUpload(uint32_t fileLength); +}; class Webserver { -public: + public: friend class Platform; + friend class ProtocolInterpreter; Webserver(Platform* p, Network *n); void Init(); @@ -65,12 +133,252 @@ public: char ReadGCode(const WebSource source); void HandleGCodeReply(const WebSource source, OutputBuffer *reply); void HandleGCodeReply(const WebSource source, const char *reply); - uint32_t GetReplySeq() const { return seq; } + uint32_t GetReplySeq() const; + // Returns the available G-Code buffer space of the HTTP interpreter (may be dropped in a future version) - uint16_t GetGCodeBufferSpace(const WebSource source) const { return 0; } + uint16_t GetGCodeBufferSpace(const WebSource source) const; + + void ConnectionLost(const ConnectionState *cs); + void ConnectionError(); + + protected: + + class HttpInterpreter : public ProtocolInterpreter + { + public: + + HttpInterpreter(Platform *p, Webserver *ws, Network *n); + void Spin(); + void Diagnostics(MessageType mtype) override; + void ConnectionLost(const ConnectionState *cs); + bool CanParseData() override; + bool CharFromClient(const char c) override; + void NoMoreDataAvailable() override; + void ResetState(); + void ResetSessions(); + + bool DoingFastUpload() const override; + void DoFastUpload(); + + bool GCodeAvailable() const; + char ReadGCode(); + void HandleGCodeReply(OutputBuffer *reply); + void HandleGCodeReply(const char *reply); + uint16_t GetGCodeBufferSpace() const; + uint32_t GetReplySeq() const; + + private: + + // HTTP server state enumeration. The order is important, in particular xxxEsc1 must follow xxx, and xxxEsc2 must follow xxxEsc1. + // We assume that qualifier keys do not contain escapes, because none of ours needs to be encoded. If we are sent escapes in the key, + // it won't do any harm, but the key won't be recognised even if it would be valid were it decoded. + enum HttpState + { + doingCommandWord, // receiving a word in the first line of the HTTP request + doingFilename, // receiving the filename (second word in the command line) + doingFilenameEsc1, // received '%' in the filename (e.g. we are being asked for a filename with spaces in it) + doingFilenameEsc2, // received '%' and one hex digit in the filename + doingQualifierKey, // receiving a key name in the HTTP request + doingQualifierValue, // receiving a key value in the HTTP request + doingQualifierValueEsc1, // received '%' in the qualifier + doingQualifierValueEsc2, // received '%' and one hex digit in the qualifier + doingHeaderKey, // receiving a header key + expectingHeaderValue, // expecting a header value + doingHeaderValue, // receiving a header value + doingHeaderContinuation // received a newline after a header value + }; + HttpState state; + + struct KeyValueIndices + { + const char* key; + const char* value; + }; + + void SendFile(const char* nameOfFileToSend, bool isWebFile); + void SendGCodeReply(); + void SendJsonResponse(const char* command); + void GetJsonResponse(const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen); + bool ProcessMessage(); + bool RejectMessage(const char* s, unsigned int code = 500); + + // Buffers for processing HTTP input + char clientMessage[webMessageLength + 3]; // holds the command, qualifier, and headers + size_t clientPointer; // current index into clientMessage + char decodeChar; + + const char* commandWords[maxCommandWords]; + KeyValueIndices qualifiers[maxQualKeys + 1]; // offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls + KeyValueIndices headers[maxHeaders]; // offsets into clientHeader of the key/value pairs + size_t numCommandWords; + size_t numQualKeys; // number of qualifier keys we have found, <= maxQualKeys + size_t numHeaderKeys; // number of keys we have found, <= maxHeaders + + // HTTP sessions + struct HttpSession + { + uint32_t ip; + uint32_t lastQueryTime; + bool isPostUploading; + uint16_t postPort; + }; + + HttpSession sessions[maxHttpSessions]; + uint8_t numSessions; + uint8_t clientsServed; + + bool Authenticate(); + bool IsAuthenticated() const; + void UpdateAuthentication(); + bool RemoveAuthentication(); -private: - uint32_t seq; + // Deal with incoming G-Codes + + char gcodeBuffer[gcodeBufferLength]; + uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer + + void LoadGcodeBuffer(const char* gc); + void ProcessGcode(const char* gc); + void StoreGcodeData(const char* data, uint16_t len); + + // Responses from GCodes class + + uint32_t seq; // Sequence number for G-Code replies + OutputStack *gcodeReply; + + // File uploads + uint32_t postFileLength, uploadedBytes; // How many POST bytes do we expect and how many have already been written? + time_t fileLastModified; + + // Deferred requests (rr_fileinfo) + ConnectionState * volatile deferredRequestConnection; // Which connection expects a response for a deferred request? + char filenameBeingProcessed[FILENAME_LENGTH]; // The filename being processed (for rr_fileinfo) + + void ProcessDeferredRequest(); + }; + HttpInterpreter *httpInterpreter; + + class FtpInterpreter : public ProtocolInterpreter + { + public: + + FtpInterpreter(Platform *p, Webserver *ws, Network *n); + void Diagnostics(MessageType mtype) override; + + void ConnectionEstablished() override; + void ConnectionLost(const ConnectionState *cs) override; + bool CharFromClient(const char c) override; + void ResetState(); + + bool DoingFastUpload() const override; + + private: + + enum FtpState + { + idle, // no client connected + authenticating, // not logged in + authenticated, // logged in + waitingForPasvPort, // waiting for connection to be established on PASV port + pasvPortConnected, // client connected to PASV port, ready to send data + doingPasvIO // client is connected and data is being transferred + }; + FtpState state; + uint8_t connectedClients; + + char clientMessage[ftpMessageLength]; + size_t clientPointer; + + char filename[FILENAME_LENGTH]; + char currentDir[FILENAME_LENGTH]; + + uint32_t portOpenTime; + + void ProcessLine(); + void SendReply(int code, const char *message, bool keepConnection = true); + void SendFeatures(); + + void ReadFilename(uint16_t start); + void ChangeDirectory(const char *newDirectory); + }; + FtpInterpreter *ftpInterpreter; + + class TelnetInterpreter : public ProtocolInterpreter + { + public: + + TelnetInterpreter(Platform *p, Webserver *ws, Network *n); + void Diagnostics(MessageType mtype) override; + + void ConnectionEstablished() override; + void ConnectionLost(const ConnectionState *cs) override; + bool CanParseData() override; + bool CharFromClient(const char c) override; + void ResetState(); + + bool GCodeAvailable() const; + char ReadGCode(); + void HandleGCodeReply(OutputBuffer *reply); + void HandleGCodeReply(const char *reply); + uint16_t GetGCodeBufferSpace() const; + + void SendGCodeReply(); + + private: + + enum TelnetState + { + idle, // not connected + justConnected, // not logged in, but the client has just connected + authenticating, // not logged in + authenticated // logged in + }; + TelnetState state; + uint8_t connectedClients; + uint32_t connectTime; + + bool processNextLine; + char clientMessage[GCODE_LENGTH]; + size_t clientPointer; + + bool ProcessLine(); + + // Deal with incoming G-Codes + + char gcodeBuffer[gcodeBufferLength]; + uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer + + void ProcessGcode(const char* gc); + void StoreGcodeData(const char* data, uint16_t len); + + // Converted response from GCodes class (NL -> CRNL) + + OutputBuffer * volatile gcodeReply; + }; + TelnetInterpreter *telnetInterpreter; + + private: + + Platform* platform; + Network* network; + bool webserverActive; + NetworkTransaction *currentTransaction; + ConnectionState * volatile readingConnection; + + float longWait; }; +inline bool ProtocolInterpreter::CanParseData() { return true; } +inline bool ProtocolInterpreter::DoingFastUpload() const { return false; } +inline bool ProtocolInterpreter::IsUploading() const { return uploadState != notUploading; } + +inline uint32_t Webserver::GetReplySeq() const { return httpInterpreter->GetReplySeq(); } + +inline uint16_t Webserver::HttpInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; } +inline bool Webserver::HttpInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; } +inline uint32_t Webserver::HttpInterpreter::GetReplySeq() const { return seq; } + +inline uint16_t Webserver::TelnetInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; } +inline bool Webserver::TelnetInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; } + #endif |