/**************************************************************************************************** RepRapFirmware - Webserver This class serves a single-page web applications to the attached network. This page forms the user's interface with the RepRap machine. This software interprests returned values from the page and uses it to Generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like temperature and uses those to construct the web page. The page itself - reprap.htm - uses Knockout.js and Jquery.js. See: http://knockoutjs.com/ http://jquery.com/ ----------------------------------------------------------------------------------------------------- Version 0.2 10 May 2013 Adrian Bowyer RepRap Professional Ltd http://reprappro.com Licence: GPL ****************************************************************************************************/ #ifndef WEBSERVER_H #define WEBSERVER_H /* 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 { HTTP, Telnet }; // 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: 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: friend class Platform; friend class ProtocolInterpreter; Webserver(Platform* p, Network *n); void Init(); void Spin(); void Exit(); void Diagnostics(MessageType mtype); bool GCodeAvailable(const WebSource source) const; char ReadGCode(const WebSource source); void HandleGCodeReply(const WebSource source, OutputBuffer *reply); void HandleGCodeReply(const WebSource source, const char *reply); 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; 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(); // 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