diff options
86 files changed, 2131 insertions, 4883 deletions
@@ -103,6 +103,7 @@ <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/Duet}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/Duet/Lwip}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/Duet/EMAC}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/Webserver}""/> </option> <option id="gnu.cpp.compiler.option.preprocessor.def.1548770846" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols"> <listOptionValue builtIn="false" value="__SAM3X8E__"/> @@ -235,7 +236,7 @@ </toolChain> </folderInfo> <sourceEntries> - <entry excluding="src/RADDS|src/DuetNG/DuetEthernet|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> + <entry excluding="src/Webserver|src/RADDS|src/DuetNG/DuetEthernet|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> </sourceEntries> </configuration> </storageModule> @@ -350,7 +351,7 @@ </toolChain> </folderInfo> <sourceEntries> - <entry excluding="src/Duet/Lwip/lwip/src/core/ipv6|src/Duet/Lwip/lwip/test|src/DuetNG|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> + <entry excluding="src/Webserver|src/Duet/Lwip/lwip/src/core/ipv6|src/Duet/Lwip/lwip/test|src/DuetNG|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> </sourceEntries> </configuration> </storageModule> @@ -462,6 +463,7 @@ <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/DuetNG}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/DuetNG/DuetEthernet}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/DuetNG/DuetEthernet/Wiznet/Ethernet}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/Webserver}""/> </option> <option id="gnu.cpp.compiler.option.preprocessor.def.1120095540" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols"> <listOptionValue builtIn="false" value="__SAM4E8E__"/> @@ -478,12 +480,19 @@ <toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1089268166" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release" unusedChildren=""> <option id="cdt.managedbuild.option.gnu.cross.path.1965306885.313001552" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path.1965306885"/> <option id="cdt.managedbuild.option.gnu.cross.prefix.444696517.1953934546" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix.444696517"/> - <tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.200282872" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler.1431507147"/> - <tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1364761638" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1806370384"/> + <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/> + <tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.200282872" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler.1431507147"> + <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1696293223" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/> + </tool> + <tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1364761638" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1806370384"> + <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.376414925" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/> + </tool> <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.162777510" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker.212863977"/> <tool id="cdt.managedbuild.tool.gnu.cross.cpp.linker.57723374" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker.394147701"/> <tool id="cdt.managedbuild.tool.gnu.cross.archiver.863372043" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver.136873036"/> - <tool id="cdt.managedbuild.tool.gnu.cross.assembler.555179118" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler.2020291169"/> + <tool id="cdt.managedbuild.tool.gnu.cross.assembler.555179118" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler.2020291169"> + <inputType id="cdt.managedbuild.tool.gnu.assembler.input.403264608" superClass="cdt.managedbuild.tool.gnu.assembler.input"/> + </tool> </toolChain> </folderInfo> <folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.1275216290.918081093" name="/" resourcePath="src/DuetNG/DuetEthernet/Ethernet3/examples"> diff --git a/src/Configuration.h b/src/Configuration.h index ff706cbe..a2475a36 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -25,20 +25,6 @@ Licence: GPL #include <cstddef> // for size_t -// Firmware name is now defined in the Pins file - -#ifndef VERSION -# define VERSION "1.17" -#endif - -#ifndef DATE -# define DATE "2016-12-24" -#endif - -#define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman" - -#define FLASH_SAVE_ENABLED (1) - // Other firmware that we might switch to be compatible with. enum Compatibility @@ -95,6 +81,10 @@ const float DefaultHotEndHeaterGain = 340.0; const float DefaultHotEndHeaterTimeConstant = 140.0; const float DefaultHotEndHeaterDeadTime = 5.5; +const int8_t DefaultBedHeater = 0; +const int8_t DefaultChamberHeater = -1; +const int8_t DefaultE0Heater = 1; // Index of the default first extruder heater + // These parameters are about right for a typical PCB bed heater that maxes out at 110C const float DefaultBedHeaterGain = 90.0; const float DefaultBedHeaterTimeConstant = 700.0; diff --git a/src/Duet/ConnectionState.cpp b/src/Duet/ConnectionState.cpp new file mode 100644 index 00000000..181682c3 --- /dev/null +++ b/src/Duet/ConnectionState.cpp @@ -0,0 +1,37 @@ +/* + * ConnectionState.cpp + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#include "ConnectionState.h" + +extern "C" { + #include "lwip/src/include/lwip/tcp.h" +} + +//*************************************************************************************************** +// ConnectionState class + +void ConnectionState::Init(tcp_pcb *p) +{ + pcb = p; + localPort = p->local_port; + remoteIPAddress = p->remote_ip.addr; + remotePort = p->remote_port; + next = nullptr; + sendingTransaction = nullptr; + persistConnection = true; + isTerminated = false; +} + +void ConnectionState::Terminate() +{ + if (pcb != nullptr) + { + tcp_abort(pcb); + } +} + +// End diff --git a/src/Duet/ConnectionState.h b/src/Duet/ConnectionState.h new file mode 100644 index 00000000..e3b448d0 --- /dev/null +++ b/src/Duet/ConnectionState.h @@ -0,0 +1,36 @@ +/* + * ConnectionState.h + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#ifndef SRC_DUET_CONNECTIONSTATE_H_ +#define SRC_DUET_CONNECTIONSTATE_H_ + +#include <NetworkDefs.h> +#include "RepRapFirmware.h" + +class tcp_pcb; + +// ConnectionState structure that we use to track TCP connections. It is usually combined with NetworkTransactions. +struct ConnectionState +{ + tcp_pcb *volatile pcb; // Connection PCB + uint16_t localPort, remotePort; // Copy of the local and remote ports, because the PCB may be unavailable + uint32_t remoteIPAddress; // Same for the remote IP address + NetworkTransaction * volatile sendingTransaction; // NetworkTransaction that is currently sending via this connection + ConnectionState * volatile next; // Next ConnectionState in this list + bool persistConnection; // Do we expect this connection to stay alive? + volatile bool isTerminated; // Will be true if the connection has gone down unexpectedly (TCP RST) + + void Init(tcp_pcb *p); + uint16_t GetLocalPort() const { return localPort; } + uint32_t GetRemoteIP() const { return remoteIPAddress; } + uint16_t GetRemotePort() const { return remotePort; } + bool IsConnected() const { return pcb != nullptr; } + bool IsTerminated() const { return isTerminated; } + void Terminate(); +}; + +#endif /* SRC_DUET_CONNECTIONSTATE_H_ */ diff --git a/src/Duet/Network.cpp b/src/Duet/Network.cpp index ad2fdb8b..90cbeb35 100644 --- a/src/Duet/Network.cpp +++ b/src/Duet/Network.cpp @@ -38,7 +38,14 @@ ****************************************************************************************************/ -#include "RepRapFirmware.h" +#include "Network.h" + +#include "ConnectionState.h" +#include "NetworkTransaction.h" +#include "Platform.h" +#include "RepRap.h" +#include "Webserver.h" +#include "Version.h" extern "C" { @@ -94,13 +101,13 @@ static const char *mdns_txt_records[] = { static bool closingDataPort = false; -static ConnectionState *sendingConnection = nullptr; +ConnectionState *sendingConnection = nullptr; static uint32_t sendingWindow32[(TCP_WND + 3)/4]; // should be 32-bit aligned for efficiency -static char * const sendingWindow = reinterpret_cast<char *>(sendingWindow32); -static uint16_t sendingWindowSize, sentDataOutstanding; -static uint8_t sendingRetries; -static err_t writeResult, outputResult; +char * const sendingWindow = reinterpret_cast<char *>(sendingWindow32); +uint16_t sendingWindowSize, sentDataOutstanding; +uint8_t sendingRetries; +err_t writeResult, outputResult; static uint16_t httpPort = DefaultHttpPort; @@ -176,83 +183,6 @@ static void conn_err(void *arg, err_t err) } } -static err_t conn_poll(void *arg, tcp_pcb *pcb) -{ - ConnectionState *cs = (ConnectionState*)arg; - if (cs == sendingConnection) - { - // Data could not be sent last time, check if the connection has to be timed out - sendingRetries++; - if (sendingRetries == TCP_MAX_SEND_RETRIES) - { - reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Could not transmit data after %.1f seconds\n", (float)TCP_WRITE_TIMEOUT / 1000.0); - tcp_abort(pcb); - return ERR_ABRT; - } - - // Try to write the remaining data once again (if required) - if (writeResult != ERR_OK) - { - writeResult = tcp_write(pcb, sendingWindow + (sendingWindowSize - sentDataOutstanding), sentDataOutstanding, 0); - if (ERR_IS_FATAL(writeResult)) - { - reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to write data in conn_poll (code %d)\n", writeResult); - tcp_abort(pcb); - return ERR_ABRT; - } - - if (writeResult != ERR_OK && reprap.Debug(moduleNetwork)) - { - reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_write resulted in error code %d\n", writeResult); - } - } - - // If that worked, try to output the remaining data (if required) - if (outputResult != ERR_OK) - { - outputResult = tcp_output(pcb); - if (ERR_IS_FATAL(outputResult)) - { - reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to output data in conn_poll (code %d)\n", outputResult); - tcp_abort(pcb); - return ERR_ABRT; - } - - if (outputResult != ERR_OK && reprap.Debug(moduleNetwork)) - { - reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_output resulted in error code %d\n", outputResult); - } - } - } - else - { - reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Mismatched pcb in conn_poll!\n"); - } - return ERR_OK; -} - -static err_t conn_sent(void *arg, tcp_pcb *pcb, u16_t len) -{ - ConnectionState *cs = (ConnectionState*)arg; - if (cs == sendingConnection) - { - if (sentDataOutstanding > len) - { - sentDataOutstanding -= len; - } - else - { - tcp_poll(pcb, nullptr, TCP_WRITE_TIMEOUT / TCP_SLOW_INTERVAL / TCP_MAX_SEND_RETRIES); - sendingConnection = nullptr; - } - } - else - { - reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Mismatched pcb in conn_sent!\n"); - } - return ERR_OK; -} - static err_t conn_recv(void *arg, tcp_pcb *pcb, pbuf *p, err_t err) { ConnectionState *cs = (ConnectionState*)arg; @@ -1002,566 +932,11 @@ bool Network::AcquireTransaction(ConnectionState *cs) return true; } -//*************************************************************************************************** - -// ConnectionState class - -void ConnectionState::Init(tcp_pcb *p) -{ - pcb = p; - localPort = p->local_port; - remoteIPAddress = p->remote_ip.addr; - remotePort = p->remote_port; - next = nullptr; - sendingTransaction = nullptr; - persistConnection = true; - isTerminated = false; -} - -void ConnectionState::Terminate() -{ - if (pcb != nullptr) - { - tcp_abort(pcb); - } -} - -//*************************************************************************************************** -// NetworkTransaction class - -NetworkTransaction::NetworkTransaction(NetworkTransaction *n) : next(n), status(released) -{ - sendStack = new OutputStack(); -} - -void NetworkTransaction::Set(pbuf *p, ConnectionState *c, TransactionStatus s) -{ - cs = c; - pb = readingPb = p; - status = s; - inputPointer = 0; - sendBuffer = nullptr; - fileBeingSent = nullptr; - closeRequested = false; - nextWrite = nullptr; - dataAcknowledged = false; -} - -// Read one char from the NetworkTransaction -bool NetworkTransaction::Read(char& b) -{ - if (readingPb == nullptr) - { - b = 0; - return false; - } - - b = ((const char*)readingPb->payload)[inputPointer++]; - if (inputPointer == readingPb->len) - { - readingPb = readingPb->next; - inputPointer = 0; - } - return true; -} - -// Read data from the NetworkTransaction and return true on success -bool NetworkTransaction::ReadBuffer(const char *&buffer, size_t &len) -{ - if (readingPb == nullptr) - { - return false; - } - - if (inputPointer >= readingPb->len) - { - readingPb = readingPb->next; - inputPointer = 0; - if (readingPb == nullptr) - { - return false; - } - } - - buffer = (const char*)readingPb->payload + inputPointer; - len = readingPb->len - inputPointer; - readingPb = readingPb->next; - inputPointer = 0; - return true; -} - -void NetworkTransaction::Write(char b) -{ - if (CanWrite()) - { - if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer)) - { - // Should never get here - return; - } - sendBuffer->cat(b); - } -} - -void NetworkTransaction::Write(const char* s) -{ - if (CanWrite()) - { - if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer)) - { - // Should never get here - return; - } - sendBuffer->cat(s); - } -} - -void NetworkTransaction::Write(StringRef ref) -{ - Write(ref.Pointer(), ref.strlen()); -} - -void NetworkTransaction::Write(const char* s, size_t len) -{ - if (CanWrite()) - { - if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer)) - { - // Should never get here - return; - } - sendBuffer->cat(s, len); - } -} - -void NetworkTransaction::Write(OutputBuffer *buffer) -{ - if (CanWrite()) - { - // Note we use an individual stack here, because we don't want to link different - // OutputBuffers for different destinations together... - sendStack->Push(buffer); - } - else - { - // Don't keep buffers we can't send... - OutputBuffer::ReleaseAll(buffer); - } -} - -void NetworkTransaction::Write(OutputStack *stack) -{ - if (stack != nullptr) - { - if (CanWrite()) - { - sendStack->Append(stack); - } - else - { - stack->ReleaseAll(); - } - } -} - -void NetworkTransaction::Printf(const char* fmt, ...) -{ - if (CanWrite() && (sendBuffer != nullptr || OutputBuffer::Allocate(sendBuffer))) - { - va_list p; - va_start(p, fmt); - sendBuffer->vprintf(fmt, p); - va_end(p); - } -} - -void NetworkTransaction::SetFileToWrite(FileStore *file) -{ - if (CanWrite()) - { - fileBeingSent = file; - } - else if (file != nullptr) - { - file->Close(); - } -} - -// Send exactly one TCP window of data and return true when this transaction can be released -bool NetworkTransaction::Send() -{ - // Free up this transaction if the connection is supposed to be closed - if (closeRequested) - { - reprap.GetNetwork()->ConnectionClosed(cs, true); // This will release the transaction too - return false; - } - - // Fill up the TCP window with some data chunks from our OutputBuffer instances - size_t bytesBeingSent = 0, bytesLeftToSend = TCP_WND; - while (sendBuffer != nullptr && bytesLeftToSend > 0) - { - size_t copyLength = min<size_t>(bytesLeftToSend, sendBuffer->BytesLeft()); - memcpy(sendingWindow + bytesBeingSent, sendBuffer->Read(copyLength), copyLength); - bytesBeingSent += copyLength; - bytesLeftToSend -= copyLength; - - if (sendBuffer->BytesLeft() == 0) - { - sendBuffer = OutputBuffer::Release(sendBuffer); - if (sendBuffer == nullptr) - { - sendBuffer = sendStack->Pop(); - } - } - } - - // We also intend to send a file, so check if we can fill up the TCP window - if (sendBuffer == nullptr && bytesLeftToSend != 0 && fileBeingSent != nullptr) - { - // For HSMCI efficiency, read from the file in multiples of 4 bytes except at the end. - // This ensures that the second and subsequent chunks can be DMA'd directly into sendingWindow. - size_t bytesToRead = bytesLeftToSend & (~3); - if (bytesToRead != 0) - { - int bytesRead = fileBeingSent->Read(sendingWindow + bytesBeingSent, bytesToRead); - if (bytesRead > 0) - { - bytesBeingSent += bytesRead; - } - - if (bytesRead != (int)bytesToRead) - { - fileBeingSent->Close(); - fileBeingSent = nullptr; - } - } - } - - if (bytesBeingSent == 0) - { - // If we have no data to send, this connection can be closed next time - if (!cs->persistConnection && nextWrite == nullptr) - { - Close(); - return false; - } - - // We want to send data from another transaction as well, so only free up this one - cs->sendingTransaction = nextWrite; - return true; - } - - // The TCP window has been filled up as much as possible, so send it now. There is no need to check - // the available space in the SNDBUF queue, because we really write only one TCP window at once. - writeResult = tcp_write(cs->pcb, sendingWindow, bytesBeingSent, 0); - if (ERR_IS_FATAL(writeResult)) - { - reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to write data in Send (code %d)\n", writeResult); - tcp_abort(cs->pcb); - return false; - } - - outputResult = tcp_output(cs->pcb); - if (ERR_IS_FATAL(outputResult)) - { - reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to output data in Send (code %d)\n", outputResult); - tcp_abort(cs->pcb); - return false; - } - - if (outputResult != ERR_OK && reprap.Debug(moduleNetwork)) - { - reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_output resulted in error code %d\n", outputResult); - } - - // Set LwIP callbacks for ACK and retransmission handling - tcp_poll(cs->pcb, conn_poll, TCP_WRITE_TIMEOUT / TCP_SLOW_INTERVAL / TCP_MAX_SEND_RETRIES); - tcp_sent(cs->pcb, conn_sent); - - // Set all values for the send process - sendingConnection = cs; - sendingRetries = 0; - sendingWindowSize = sentDataOutstanding = bytesBeingSent; - return false; -} - -// This is called by the Webserver to send output data to a client. If keepConnectionAlive is set to false, -// the current connection will be terminated once everything has been sent. -void NetworkTransaction::Commit(bool keepConnectionAlive) -{ - // If the connection has been terminated (e.g. RST received while writing upload data), discard this transaction - if (!IsConnected() || status == released) - { - Discard(); - return; - } - - // Free buffer holding the incoming data and prepare some values for the sending process - FreePbuf(); - cs->persistConnection = keepConnectionAlive; - if (sendBuffer == nullptr) - { - sendBuffer = sendStack->Pop(); - } - status = sending; - - // Unlink the item(s) from the list of ready transactions - if (keepConnectionAlive) - { - // Our connection is still of interest, remove only this transaction from the list - NetworkTransaction *previous = nullptr; - for(NetworkTransaction *item = reprap.GetNetwork()->readyTransactions; item != nullptr; item = item->next) - { - if (item == this) - { - if (previous == nullptr) - { - reprap.GetNetwork()->readyTransactions = next; - } - else - { - previous->next = next; - } - break; - } - previous = item; - } - } - else - { - // We will close this connection soon, stop receiving data from this PCB - tcp_recv(cs->pcb, nullptr); - - // Also remove all ready transactions pointing to our ConnectionState - NetworkTransaction *previous = nullptr, *item = reprap.GetNetwork()->readyTransactions; - while (item != nullptr) - { - if (item->cs == cs) - { - if (item == this) - { - // Only unlink this item - if (previous == nullptr) - { - reprap.GetNetwork()->readyTransactions = next; - } - else - { - previous->next = next; - } - item = next; - } - else - { - // Remove all others - item->Discard(); - item = (previous == nullptr) ? reprap.GetNetwork()->readyTransactions : previous->next; - } - } - else - { - previous = item; - item = item->next; - } - } - } - - // Enqueue this transaction, so it's sent in the right order - NetworkTransaction *mySendingTransaction = cs->sendingTransaction; - if (mySendingTransaction == nullptr) - { - cs->sendingTransaction = this; - reprap.GetNetwork()->AppendTransaction(&reprap.GetNetwork()->writingTransactions, this); - } - else - { - while (mySendingTransaction->nextWrite != nullptr) - { - mySendingTransaction = mySendingTransaction->nextWrite; - } - mySendingTransaction->nextWrite = this; - } -} - -// Call this to perform some networking tasks while processing deferred requests, -// and to move this transaction and all transactions that are associated with its -// connection to the end of readyTransactions. There are three ways to do this: -// -// 1) DeferOnly: Do not modify any of the processed data and don't send an ACK. -// This will ensure that zero-window packets are sent back to the client -// 2) ResetData: Reset the read pointers and acknowledge that the data has been processed -// 3) DiscardData: Free the processed data, acknowledge it and append this transaction as -// an empty item again without payload (i.e. without pbufs) -// -void NetworkTransaction::Defer(DeferralMode mode) -{ - if (mode == DeferralMode::ResetData) - { - // Reset the reading pointers and send an ACK - inputPointer = 0; - readingPb = pb; - if (IsConnected() && pb != nullptr && !dataAcknowledged) - { - tcp_recved(cs->pcb, pb->tot_len); - dataAcknowledged = true; - } - } - else if (mode == DeferralMode::DiscardData) - { - // Discard the incoming data, because we don't need to process it any more - FreePbuf(); - } - - status = deferred; - - // Unlink this transaction from the list of ready transactions and append it again - Network *network = reprap.GetNetwork(); - NetworkTransaction *item, *previous = nullptr; - for(item = network->readyTransactions; item != nullptr; item = item->next) - { - if (item == this) - { - if (previous == nullptr) - { - network->readyTransactions = next; - } - else - { - previous->next = next; - } - break; - } - previous = item; - } - network->AppendTransaction(&network->readyTransactions, this); - - // Append all other transactions that are associated to this connection, so that the - // Webserver gets a chance to deal with all connected clients even while multiple - // deferred requests are present in the list. - item = network->readyTransactions; - previous = nullptr; - while (item != this) - { - if (item->cs == cs) - { - NetworkTransaction *nextItem = item->next; - if (previous == nullptr) - { - network->readyTransactions = item->next; - network->AppendTransaction(&network->readyTransactions, item); - } - else - { - previous->next = item->next; - network->AppendTransaction(&network->readyTransactions, item); - } - item = nextItem; - } - else - { - previous = item; - item = item->next; - } - } -} - - -// This method should be called if we don't want to send data to the client and if we -// don't want to interfere with the connection state. May also be called from ISR! -void NetworkTransaction::Discard() -{ - // Can we do anything? - if (status == released) - { - // No - don't free up released items multiple times - return; - } - - // Free up some resources - FreePbuf(); - - if (fileBeingSent != nullptr) - { - fileBeingSent->Close(); - fileBeingSent = nullptr; - } - - OutputBuffer::ReleaseAll(sendBuffer); - sendStack->ReleaseAll(); - - // Unlink this transactions from the list of ready transactions and free it. It is then appended to the list of - // free transactions because we don't want to risk reusing it when the ethernet ISR processes incoming data - NetworkTransaction *previous = nullptr; - for(NetworkTransaction *item = reprap.GetNetwork()->readyTransactions; item != nullptr; item = item->next) - { - if (item == this) - { - if (previous == nullptr) - { - reprap.GetNetwork()->readyTransactions = next; - } - else - { - previous->next = next; - } - break; - } - previous = item; - } - reprap.GetNetwork()->AppendTransaction(&reprap.GetNetwork()->freeTransactions, this); - bool callDisconnectHandler = (cs != nullptr && status == disconnected); - status = released; - - // Call disconnect event if this transaction indicates a graceful disconnect and if the connection - // still persists (may not be the case if a RST packet was received before) - if (callDisconnectHandler) - { - if (reprap.Debug(moduleNetwork)) - { - reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Discard() is handling a graceful disconnect\n"); - } - reprap.GetNetwork()->ConnectionClosed(cs, false); - } -} - -uint32_t NetworkTransaction::GetRemoteIP() const -{ - return (cs != nullptr) ? cs->GetRemoteIP() : 0; -} - -uint16_t NetworkTransaction::GetRemotePort() const -{ - return (cs != nullptr) ? cs->GetRemotePort() : 0; -} - -uint16_t NetworkTransaction::GetLocalPort() const -{ - return (cs != nullptr) ? cs->GetLocalPort() : 0; -} - -void NetworkTransaction::Close() -{ - tcp_pcb *pcb = cs->pcb; - tcp_recv(pcb, nullptr); - closeRequested = true; -} - -void NetworkTransaction::FreePbuf() -{ - // See if we have to send an ACK to the client - if (IsConnected() && pb != nullptr && !dataAcknowledged) - { - tcp_recved(cs->pcb, pb->tot_len); - dataAcknowledged = true; - } - - // Free all pbufs (pbufs are thread-safe) - if (pb != nullptr) - { - pbuf_free(pb); - pb = readingPb = nullptr; - } -} +/*static*/ uint16_t Network::GetLocalPort(Connection conn) { return conn->GetLocalPort(); } +/*static*/ uint16_t Network::GetRemotePort(Connection conn) { return conn->GetRemotePort(); } +/*static*/ uint32_t Network::GetRemoteIP(Connection conn) { return conn->GetRemoteIP(); } +/*static*/ bool Network::IsConnected(Connection conn) { return conn->IsConnected(); } +/*static*/ bool Network::IsTerminated(Connection conn) { return conn->IsTerminated(); } +/*static*/ void Network::Terminate(Connection conn) { conn->Terminate(); } // End diff --git a/src/Duet/Network.h b/src/Duet/Network.h index d3d9904b..f31ccdd2 100644 --- a/src/Duet/Network.h +++ b/src/Duet/Network.h @@ -9,14 +9,17 @@ Separated out from Platform.h by dc42 and extended by zpl #ifndef NETWORK_H #define NETWORK_H -#include <cctype> -#include <cstring> -#include <cstdlib> -#include <climits> +#include <NetworkDefs.h> +#include "RepRapFirmware.h" +#include "MessageType.h" -#include "lwipopts.h" +#include "Lwip/lwip/src/include/lwip/err.h" // for err_t -#include "OutputMemory.h" +extern ConnectionState *sendingConnection; +extern char * const sendingWindow; +extern uint16_t sendingWindowSize, sentDataOutstanding; +extern uint8_t sendingRetries; +extern err_t writeResult, outputResult; // This class handles the network - typically an Ethernet. @@ -33,203 +36,96 @@ const size_t NETWORK_TRANSACTION_COUNT = 24; // Number of NetworkTransacti const uint32_t TCP_WRITE_TIMEOUT = 4000; // Miliseconds to wait for data we have written to be acknowledged const uint32_t TCP_MAX_SEND_RETRIES = 8; // How many times can we attempt to write data -const uint8_t DefaultMacAddress[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED }; // Need some sort of default... -const uint8_t DefaultIpAddress[4] = { 192, 168, 1, 10 }; -const uint8_t DefaultNetMask[4] = { 255, 255, 255, 0 }; -const uint8_t DefaultGateway[4] = { 192, 168, 1, 1 }; - -const uint16_t DefaultHttpPort = 80; -const uint16_t FTP_PORT = 21; -const uint16_t TELNET_PORT = 23; - /****************************************************************************************************/ struct tcp_pcb; struct pbuf; -class NetworkTransaction; - -// ConnectionState structure that we use to track TCP connections. It is usually combined with NetworkTransactions. -struct ConnectionState -{ - tcp_pcb *volatile pcb; // Connection PCB - uint16_t localPort, remotePort; // Copy of the local and remote ports, because the PCB may be unavailable - uint32_t remoteIPAddress; // Same for the remote IP address - NetworkTransaction * volatile sendingTransaction; // NetworkTransaction that is currently sending via this connection - ConnectionState * volatile next; // Next ConnectionState in this list - bool persistConnection; // Do we expect this connection to stay alive? - volatile bool isTerminated; // Will be true if the connection has gone down unexpectedly (TCP RST) - - void Init(tcp_pcb *p); - uint16_t GetLocalPort() const { return localPort; } - uint32_t GetRemoteIP() const { return remoteIPAddress; } - uint16_t GetRemotePort() const { return remotePort; } - bool IsConnected() const { return pcb != nullptr; } - bool IsTerminated() const { return isTerminated; } - void Terminate(); -}; - -// Assign a status to each NetworkTransaction -enum TransactionStatus -{ - released, - connected, - receiving, - sending, - disconnected, - deferred, - acquired -}; - -// How is a deferred request supposed to be handled? -enum class DeferralMode -{ - DeferOnly, // don't change anything, because we want to read more of it next time - ResetData, // keep the data and reset all reading pointers allowing us to process it again - DiscardData // discard all incoming data and re-enqueue the empty transaction -}; - -// Start with a class to hold input and output from the network that needs to be responded to. -// This includes changes in the connection state, e.g. connects and disconnects. -class NetworkTransaction -{ - public: - friend class Network; - - NetworkTransaction(NetworkTransaction* n); - void Set(pbuf *p, ConnectionState* c, TransactionStatus s); - TransactionStatus GetStatus() const { return status; } - bool IsConnected() const; - - bool HasMoreDataToRead() const { return readingPb != nullptr; } - bool Read(char& b); - bool ReadBuffer(const char *&buffer, size_t &len); - void Write(char b); - void Write(const char* s); - void Write(StringRef ref); - void Write(const char* s, size_t len); - void Write(OutputBuffer *buffer); - void Write(OutputStack *stack); - void Printf(const char *fmt, ...); - void SetFileToWrite(FileStore *file); - - ConnectionState *GetConnection() const { return cs; } - uint16_t GetLocalPort() const; - uint32_t GetRemoteIP() const; - uint16_t GetRemotePort() const; - - void Commit(bool keepConnectionAlive); - void Defer(DeferralMode mode); - void Discard(); - - private: - bool CanWrite() const; - bool Send(); - void Close(); - void FreePbuf(); - - ConnectionState* cs; - NetworkTransaction* volatile next; // next NetworkTransaction in the list we are in - NetworkTransaction* volatile nextWrite; // next NetworkTransaction queued to write to assigned connection - pbuf *pb, *readingPb; // received packet queue and a pointer to the pbuf being read from - size_t inputPointer; // amount of data already taken from the first packet buffer - - OutputBuffer *sendBuffer; - OutputStack *sendStack; - FileStore * volatile fileBeingSent; - - volatile TransactionStatus status; - volatile bool closeRequested, dataAcknowledged; -}; - // The main network class that drives the network. class Network { - public: - friend class NetworkTransaction; +public: + friend class NetworkTransaction; - Network(Platform* p); - void Init(); - void Exit() {} - void Spin(); - void Interrupt(); - void Diagnostics(MessageType mtype); + Network(Platform* p); + void Init(); + void Exit() {} + void Spin(); + void Interrupt(); + void Diagnostics(MessageType mtype); - // Deal with LwIP + // Deal with LwIP - void ResetCallback(); - bool ReceiveInput(pbuf *pb, ConnectionState *cs); - ConnectionState *ConnectionAccepted(tcp_pcb *pcb); - void ConnectionClosed(ConnectionState* cs, bool closeConnection); - bool ConnectionClosedGracefully(ConnectionState *cs); + void ResetCallback(); + bool ReceiveInput(pbuf *pb, ConnectionState *cs); + ConnectionState *ConnectionAccepted(tcp_pcb *pcb); + void ConnectionClosed(ConnectionState* cs, bool closeConnection); + bool ConnectionClosedGracefully(ConnectionState *cs); - bool Lock(); - void Unlock(); - bool InLwip() const; + bool Lock(); + void Unlock(); + bool InLwip() const; - // Global settings + // Global settings - const uint8_t *GetIPAddress() const; - void SetIPAddress(const uint8_t ipAddress[], const uint8_t netmask[], const uint8_t gateway[]); - void SetHostname(const char *name); + const uint8_t *GetIPAddress() const; + void SetIPAddress(const uint8_t ipAddress[], const uint8_t netmask[], const uint8_t gateway[]); + void SetHostname(const char *name); - void Enable(); - void Disable(); - bool IsEnabled() const { return isEnabled; } + void Enable(); + void Disable(); + bool IsEnabled() const { return isEnabled; } - // Interfaces for the Webserver + // Interfaces for the Webserver - NetworkTransaction *GetTransaction(const ConnectionState *cs = nullptr); + NetworkTransaction *GetTransaction(const ConnectionState *cs = nullptr); - void OpenDataPort(uint16_t port); - uint16_t GetDataPort() const; - void CloseDataPort(); + void OpenDataPort(uint16_t port); + uint16_t GetDataPort() const; + void CloseDataPort(); - void SetHttpPort(uint16_t port); - uint16_t GetHttpPort() const; + void SetHttpPort(uint16_t port); + uint16_t GetHttpPort() const; - void SaveDataConnection(); - void SaveFTPConnection(); - void SaveTelnetConnection(); + void SaveDataConnection(); + void SaveFTPConnection(); + void SaveTelnetConnection(); - bool AcquireFTPTransaction(); - bool AcquireDataTransaction(); - bool AcquireTelnetTransaction(); + bool AcquireFTPTransaction(); + bool AcquireDataTransaction(); + bool AcquireTelnetTransaction(); - private: + static uint16_t GetLocalPort(Connection conn); + static uint16_t GetRemotePort(Connection conn); + static uint32_t GetRemoteIP(Connection conn); + static bool IsConnected(Connection conn); + static bool IsTerminated(Connection conn); + static void Terminate(Connection conn); - Platform* platform; - float longWait; +private: - void AppendTransaction(NetworkTransaction* volatile * list, NetworkTransaction *r); - void PrependTransaction(NetworkTransaction* volatile * list, NetworkTransaction *r); - bool AcquireTransaction(ConnectionState *cs); + Platform* platform; + float longWait; - NetworkTransaction * volatile freeTransactions; - NetworkTransaction * volatile readyTransactions; - NetworkTransaction * volatile writingTransactions; + void AppendTransaction(NetworkTransaction* volatile * list, NetworkTransaction *r); + void PrependTransaction(NetworkTransaction* volatile * list, NetworkTransaction *r); + bool AcquireTransaction(ConnectionState *cs); - enum { NetworkInactive, NetworkEstablishingLink, NetworkObtainingIP, NetworkActive } state; - bool isEnabled; - volatile bool resetCallback; - char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0 + NetworkTransaction * volatile freeTransactions; + NetworkTransaction * volatile readyTransactions; + NetworkTransaction * volatile writingTransactions; - ConnectionState * volatile dataCs; - ConnectionState * volatile ftpCs; - ConnectionState * volatile telnetCs; + enum { NetworkInactive, NetworkEstablishingLink, NetworkObtainingIP, NetworkActive } state; + bool isEnabled; + volatile bool resetCallback; + char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0 - ConnectionState * volatile freeConnections; -}; - -inline bool NetworkTransaction::IsConnected() const -{ - return (cs != nullptr && cs->IsConnected()); -} + ConnectionState * volatile dataCs; + ConnectionState * volatile ftpCs; + ConnectionState * volatile telnetCs; -inline bool NetworkTransaction::CanWrite() const -{ - return (IsConnected() && status != released); -} + ConnectionState * volatile freeConnections; +}; #endif diff --git a/src/Duet/NetworkDefs.h b/src/Duet/NetworkDefs.h new file mode 100644 index 00000000..81f2d593 --- /dev/null +++ b/src/Duet/NetworkDefs.h @@ -0,0 +1,38 @@ +/* + * NetworkCommon.h + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_DUETETHERNET_NETWORKCOMMON_H_ +#define SRC_DUETNG_DUETETHERNET_NETWORKCOMMON_H_ + +#include <cstdint> + +class NetworkTransaction; +class ConnectionState; +class NetworkBuffer; + +// Definition of how a Connection is represented, for Webserver module +typedef ConnectionState *Connection; +const Connection NoConnection = nullptr; + +typedef uint8_t SocketNumber; +const SocketNumber NoSocket = 255; + +typedef uint16_t Port; + +const uint8_t DefaultMacAddress[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED }; // Need some sort of default... +const uint8_t DefaultIpAddress[4] = { 0, 0, 0, 0 }; // Need some sort of default... +const uint8_t DefaultNetMask[4] = { 255, 255, 255, 0 }; +const uint8_t DefaultGateway[4] = { 0, 0, 0, 0 }; + +const Port DefaultHttpPort = 80; +const Port FTP_PORT = 21; +const Port TELNET_PORT = 23; + +// MSS is defined in lwip +#include "Lwip/lwipopts.h" + +#endif /* SRC_DUETNG_DUETETHERNET_NETWORKCOMMON_H_ */ diff --git a/src/Duet/NetworkTransaction.cpp b/src/Duet/NetworkTransaction.cpp new file mode 100644 index 00000000..5c870f8a --- /dev/null +++ b/src/Duet/NetworkTransaction.cpp @@ -0,0 +1,645 @@ +/* + * NetworkTransaction.cpp + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#include "NetworkTransaction.h" + +#include "ConnectionState.h" +#include "Network.h" +#include "OutputMemory.h" +#include "Platform.h" +#include "RepRap.h" +#include "Storage/FileStore.h" + +#include "lwip/src/include/lwip/tcp.h" +#include "lwip/src/include/lwip/tcp_impl.h" + +extern "C" { + +static err_t conn_poll(void *arg, tcp_pcb *pcb) +{ + ConnectionState *cs = (ConnectionState*)arg; + if (cs == sendingConnection) + { + // Data could not be sent last time, check if the connection has to be timed out + sendingRetries++; + if (sendingRetries == TCP_MAX_SEND_RETRIES) + { + reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Could not transmit data after %.1f seconds\n", (float)TCP_WRITE_TIMEOUT / 1000.0); + tcp_abort(pcb); + return ERR_ABRT; + } + + // Try to write the remaining data once again (if required) + if (writeResult != ERR_OK) + { + writeResult = tcp_write(pcb, sendingWindow + (sendingWindowSize - sentDataOutstanding), sentDataOutstanding, 0); + if (ERR_IS_FATAL(writeResult)) + { + reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to write data in conn_poll (code %d)\n", writeResult); + tcp_abort(pcb); + return ERR_ABRT; + } + + if (writeResult != ERR_OK && reprap.Debug(moduleNetwork)) + { + reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_write resulted in error code %d\n", writeResult); + } + } + + // If that worked, try to output the remaining data (if required) + if (outputResult != ERR_OK) + { + outputResult = tcp_output(pcb); + if (ERR_IS_FATAL(outputResult)) + { + reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to output data in conn_poll (code %d)\n", outputResult); + tcp_abort(pcb); + return ERR_ABRT; + } + + if (outputResult != ERR_OK && reprap.Debug(moduleNetwork)) + { + reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_output resulted in error code %d\n", outputResult); + } + } + } + else + { + reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Mismatched pcb in conn_poll!\n"); + } + return ERR_OK; +} + +static err_t conn_sent(void *arg, tcp_pcb *pcb, u16_t len) +{ + ConnectionState *cs = (ConnectionState*)arg; + if (cs == sendingConnection) + { + if (sentDataOutstanding > len) + { + sentDataOutstanding -= len; + } + else + { + tcp_poll(pcb, nullptr, TCP_WRITE_TIMEOUT / TCP_SLOW_INTERVAL / TCP_MAX_SEND_RETRIES); + sendingConnection = nullptr; + } + } + else + { + reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Mismatched pcb in conn_sent!\n"); + } + return ERR_OK; +} + + +} + +//*************************************************************************************************** +// NetworkTransaction class + +NetworkTransaction::NetworkTransaction(NetworkTransaction *n) : next(n), status(released) +{ + sendStack = new OutputStack(); +} + +void NetworkTransaction::Set(pbuf *p, ConnectionState *c, TransactionStatus s) +{ + cs = c; + pb = readingPb = p; + status = s; + inputPointer = 0; + sendBuffer = nullptr; + fileBeingSent = nullptr; + closeRequested = false; + nextWrite = nullptr; + dataAcknowledged = false; +} + +// Read one char from the NetworkTransaction +bool NetworkTransaction::Read(char& b) +{ + if (readingPb == nullptr) + { + b = 0; + return false; + } + + b = ((const char*)readingPb->payload)[inputPointer++]; + if (inputPointer == readingPb->len) + { + readingPb = readingPb->next; + inputPointer = 0; + } + return true; +} + +// Read data from the NetworkTransaction and return true on success +bool NetworkTransaction::ReadBuffer(const char *&buffer, size_t &len) +{ + if (readingPb == nullptr) + { + return false; + } + + if (inputPointer >= readingPb->len) + { + readingPb = readingPb->next; + inputPointer = 0; + if (readingPb == nullptr) + { + return false; + } + } + + buffer = (const char*)readingPb->payload + inputPointer; + len = readingPb->len - inputPointer; + readingPb = readingPb->next; + inputPointer = 0; + return true; +} + +void NetworkTransaction::Write(char b) +{ + if (CanWrite()) + { + if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer)) + { + // Should never get here + return; + } + sendBuffer->cat(b); + } +} + +void NetworkTransaction::Write(const char* s) +{ + if (CanWrite()) + { + if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer)) + { + // Should never get here + return; + } + sendBuffer->cat(s); + } +} + +void NetworkTransaction::Write(StringRef ref) +{ + Write(ref.Pointer(), ref.strlen()); +} + +void NetworkTransaction::Write(const char* s, size_t len) +{ + if (CanWrite()) + { + if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer)) + { + // Should never get here + return; + } + sendBuffer->cat(s, len); + } +} + +void NetworkTransaction::Write(OutputBuffer *buffer) +{ + if (CanWrite()) + { + // Note we use an individual stack here, because we don't want to link different + // OutputBuffers for different destinations together... + sendStack->Push(buffer); + } + else + { + // Don't keep buffers we can't send... + OutputBuffer::ReleaseAll(buffer); + } +} + +void NetworkTransaction::Write(OutputStack *stack) +{ + if (stack != nullptr) + { + if (CanWrite()) + { + sendStack->Append(stack); + } + else + { + stack->ReleaseAll(); + } + } +} + +void NetworkTransaction::Printf(const char* fmt, ...) +{ + if (CanWrite() && (sendBuffer != nullptr || OutputBuffer::Allocate(sendBuffer))) + { + va_list p; + va_start(p, fmt); + sendBuffer->vprintf(fmt, p); + va_end(p); + } +} + +void NetworkTransaction::SetFileToWrite(FileStore *file) +{ + if (CanWrite()) + { + fileBeingSent = file; + } + else if (file != nullptr) + { + file->Close(); + } +} + +// Send exactly one TCP window of data and return true when this transaction can be released +bool NetworkTransaction::Send() +{ + // Free up this transaction if the connection is supposed to be closed + if (closeRequested) + { + reprap.GetNetwork()->ConnectionClosed(cs, true); // This will release the transaction too + return false; + } + + // Fill up the TCP window with some data chunks from our OutputBuffer instances + size_t bytesBeingSent = 0, bytesLeftToSend = TCP_WND; + while (sendBuffer != nullptr && bytesLeftToSend > 0) + { + size_t copyLength = min<size_t>(bytesLeftToSend, sendBuffer->BytesLeft()); + memcpy(sendingWindow + bytesBeingSent, sendBuffer->Read(copyLength), copyLength); + bytesBeingSent += copyLength; + bytesLeftToSend -= copyLength; + + if (sendBuffer->BytesLeft() == 0) + { + sendBuffer = OutputBuffer::Release(sendBuffer); + if (sendBuffer == nullptr) + { + sendBuffer = sendStack->Pop(); + } + } + } + + // We also intend to send a file, so check if we can fill up the TCP window + if (sendBuffer == nullptr && bytesLeftToSend != 0 && fileBeingSent != nullptr) + { + // For HSMCI efficiency, read from the file in multiples of 4 bytes except at the end. + // This ensures that the second and subsequent chunks can be DMA'd directly into sendingWindow. + size_t bytesToRead = bytesLeftToSend & (~3); + if (bytesToRead != 0) + { + int bytesRead = fileBeingSent->Read(sendingWindow + bytesBeingSent, bytesToRead); + if (bytesRead > 0) + { + bytesBeingSent += bytesRead; + } + + if (bytesRead != (int)bytesToRead) + { + fileBeingSent->Close(); + fileBeingSent = nullptr; + } + } + } + + if (bytesBeingSent == 0) + { + // If we have no data to send, this connection can be closed next time + if (!cs->persistConnection && nextWrite == nullptr) + { + Close(); + return false; + } + + // We want to send data from another transaction as well, so only free up this one + cs->sendingTransaction = nextWrite; + return true; + } + + // The TCP window has been filled up as much as possible, so send it now. There is no need to check + // the available space in the SNDBUF queue, because we really write only one TCP window at once. + writeResult = tcp_write(cs->pcb, sendingWindow, bytesBeingSent, 0); + if (ERR_IS_FATAL(writeResult)) + { + reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to write data in Send (code %d)\n", writeResult); + tcp_abort(cs->pcb); + return false; + } + + outputResult = tcp_output(cs->pcb); + if (ERR_IS_FATAL(outputResult)) + { + reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to output data in Send (code %d)\n", outputResult); + tcp_abort(cs->pcb); + return false; + } + + if (outputResult != ERR_OK && reprap.Debug(moduleNetwork)) + { + reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_output resulted in error code %d\n", outputResult); + } + + // Set LwIP callbacks for ACK and retransmission handling + tcp_poll(cs->pcb, conn_poll, TCP_WRITE_TIMEOUT / TCP_SLOW_INTERVAL / TCP_MAX_SEND_RETRIES); + tcp_sent(cs->pcb, conn_sent); + + // Set all values for the send process + sendingConnection = cs; + sendingRetries = 0; + sendingWindowSize = sentDataOutstanding = bytesBeingSent; + return false; +} + +// This is called by the Webserver to send output data to a client. If keepConnectionAlive is set to false, +// the current connection will be terminated once everything has been sent. +void NetworkTransaction::Commit(bool keepConnectionAlive) +{ + // If the connection has been terminated (e.g. RST received while writing upload data), discard this transaction + if (!IsConnected() || status == released) + { + Discard(); + return; + } + + // Free buffer holding the incoming data and prepare some values for the sending process + FreePbuf(); + cs->persistConnection = keepConnectionAlive; + if (sendBuffer == nullptr) + { + sendBuffer = sendStack->Pop(); + } + status = sending; + + // Unlink the item(s) from the list of ready transactions + if (keepConnectionAlive) + { + // Our connection is still of interest, remove only this transaction from the list + NetworkTransaction *previous = nullptr; + for(NetworkTransaction *item = reprap.GetNetwork()->readyTransactions; item != nullptr; item = item->next) + { + if (item == this) + { + if (previous == nullptr) + { + reprap.GetNetwork()->readyTransactions = next; + } + else + { + previous->next = next; + } + break; + } + previous = item; + } + } + else + { + // We will close this connection soon, stop receiving data from this PCB + tcp_recv(cs->pcb, nullptr); + + // Also remove all ready transactions pointing to our ConnectionState + NetworkTransaction *previous = nullptr, *item = reprap.GetNetwork()->readyTransactions; + while (item != nullptr) + { + if (item->cs == cs) + { + if (item == this) + { + // Only unlink this item + if (previous == nullptr) + { + reprap.GetNetwork()->readyTransactions = next; + } + else + { + previous->next = next; + } + item = next; + } + else + { + // Remove all others + item->Discard(); + item = (previous == nullptr) ? reprap.GetNetwork()->readyTransactions : previous->next; + } + } + else + { + previous = item; + item = item->next; + } + } + } + + // Enqueue this transaction, so it's sent in the right order + NetworkTransaction *mySendingTransaction = cs->sendingTransaction; + if (mySendingTransaction == nullptr) + { + cs->sendingTransaction = this; + reprap.GetNetwork()->AppendTransaction(&reprap.GetNetwork()->writingTransactions, this); + } + else + { + while (mySendingTransaction->nextWrite != nullptr) + { + mySendingTransaction = mySendingTransaction->nextWrite; + } + mySendingTransaction->nextWrite = this; + } +} + +// Call this to perform some networking tasks while processing deferred requests, +// and to move this transaction and all transactions that are associated with its +// connection to the end of readyTransactions. There are three ways to do this: +// +// 1) DeferOnly: Do not modify any of the processed data and don't send an ACK. +// This will ensure that zero-window packets are sent back to the client +// 2) ResetData: Reset the read pointers and acknowledge that the data has been processed +// 3) DiscardData: Free the processed data, acknowledge it and append this transaction as +// an empty item again without payload (i.e. without pbufs) +// +void NetworkTransaction::Defer(DeferralMode mode) +{ + if (mode == DeferralMode::ResetData) + { + // Reset the reading pointers and send an ACK + inputPointer = 0; + readingPb = pb; + if (IsConnected() && pb != nullptr && !dataAcknowledged) + { + tcp_recved(cs->pcb, pb->tot_len); + dataAcknowledged = true; + } + } + else if (mode == DeferralMode::DiscardData) + { + // Discard the incoming data, because we don't need to process it any more + FreePbuf(); + } + + status = deferred; + + // Unlink this transaction from the list of ready transactions and append it again + Network *network = reprap.GetNetwork(); + NetworkTransaction *item, *previous = nullptr; + for(item = network->readyTransactions; item != nullptr; item = item->next) + { + if (item == this) + { + if (previous == nullptr) + { + network->readyTransactions = next; + } + else + { + previous->next = next; + } + break; + } + previous = item; + } + network->AppendTransaction(&network->readyTransactions, this); + + // Append all other transactions that are associated to this connection, so that the + // Webserver gets a chance to deal with all connected clients even while multiple + // deferred requests are present in the list. + item = network->readyTransactions; + previous = nullptr; + while (item != this) + { + if (item->cs == cs) + { + NetworkTransaction *nextItem = item->next; + if (previous == nullptr) + { + network->readyTransactions = item->next; + network->AppendTransaction(&network->readyTransactions, item); + } + else + { + previous->next = item->next; + network->AppendTransaction(&network->readyTransactions, item); + } + item = nextItem; + } + else + { + previous = item; + item = item->next; + } + } +} + + +// This method should be called if we don't want to send data to the client and if we +// don't want to interfere with the connection state. May also be called from ISR! +void NetworkTransaction::Discard() +{ + // Can we do anything? + if (status == released) + { + // No - don't free up released items multiple times + return; + } + + // Free up some resources + FreePbuf(); + + if (fileBeingSent != nullptr) + { + fileBeingSent->Close(); + fileBeingSent = nullptr; + } + + OutputBuffer::ReleaseAll(sendBuffer); + sendStack->ReleaseAll(); + + // Unlink this transactions from the list of ready transactions and free it. It is then appended to the list of + // free transactions because we don't want to risk reusing it when the ethernet ISR processes incoming data + NetworkTransaction *previous = nullptr; + for(NetworkTransaction *item = reprap.GetNetwork()->readyTransactions; item != nullptr; item = item->next) + { + if (item == this) + { + if (previous == nullptr) + { + reprap.GetNetwork()->readyTransactions = next; + } + else + { + previous->next = next; + } + break; + } + previous = item; + } + reprap.GetNetwork()->AppendTransaction(&reprap.GetNetwork()->freeTransactions, this); + bool callDisconnectHandler = (cs != nullptr && status == disconnected); + status = released; + + // Call disconnect event if this transaction indicates a graceful disconnect and if the connection + // still persists (may not be the case if a RST packet was received before) + if (callDisconnectHandler) + { + if (reprap.Debug(moduleNetwork)) + { + reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Discard() is handling a graceful disconnect\n"); + } + reprap.GetNetwork()->ConnectionClosed(cs, false); + } +} + +uint32_t NetworkTransaction::GetRemoteIP() const +{ + return (cs != nullptr) ? cs->GetRemoteIP() : 0; +} + +uint16_t NetworkTransaction::GetRemotePort() const +{ + return (cs != nullptr) ? cs->GetRemotePort() : 0; +} + +uint16_t NetworkTransaction::GetLocalPort() const +{ + return (cs != nullptr) ? cs->GetLocalPort() : 0; +} + +void NetworkTransaction::Close() +{ + tcp_pcb *pcb = cs->pcb; + tcp_recv(pcb, nullptr); + closeRequested = true; +} + +void NetworkTransaction::FreePbuf() +{ + // See if we have to send an ACK to the client + if (IsConnected() && pb != nullptr && !dataAcknowledged) + { + tcp_recved(cs->pcb, pb->tot_len); + dataAcknowledged = true; + } + + // Free all pbufs (pbufs are thread-safe) + if (pb != nullptr) + { + pbuf_free(pb); + pb = readingPb = nullptr; + } +} + +bool NetworkTransaction::IsConnected() const +{ + return (cs != nullptr && cs->IsConnected()); +} + +// End diff --git a/src/Duet/NetworkTransaction.h b/src/Duet/NetworkTransaction.h new file mode 100644 index 00000000..e16567e4 --- /dev/null +++ b/src/Duet/NetworkTransaction.h @@ -0,0 +1,94 @@ +/* + * NetworkTransaction.h + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#ifndef SRC_DUET_NETWORKTRANSACTION_H_ +#define SRC_DUET_NETWORKTRANSACTION_H_ + +#include <NetworkDefs.h> +#include "RepRapFirmware.h" + +class pbuf; + +// Assign a status to each NetworkTransaction +enum TransactionStatus +{ + released, + connected, + receiving, + sending, + disconnected, + deferred, + acquired +}; + +// How is a deferred request supposed to be handled? +enum class DeferralMode +{ + DeferOnly, // don't change anything, because we want to read more of it next time + ResetData, // keep the data and reset all reading pointers allowing us to process it again + DiscardData // discard all incoming data and re-enqueue the empty transaction +}; + +// Start with a class to hold input and output from the network that needs to be responded to. +// This includes changes in the connection state, e.g. connects and disconnects. +class NetworkTransaction +{ +public: + friend class Network; + + NetworkTransaction(NetworkTransaction* n); + void Set(pbuf *p, ConnectionState* c, TransactionStatus s); + TransactionStatus GetStatus() const { return status; } + bool IsConnected() const; + + bool HasMoreDataToRead() const { return readingPb != nullptr; } + bool Read(char& b); + bool ReadBuffer(const char *&buffer, size_t &len); + void Write(char b); + void Write(const char* s); + void Write(StringRef ref); + void Write(const char* s, size_t len); + void Write(OutputBuffer *buffer); + void Write(OutputStack *stack); + void Printf(const char *fmt, ...); + void SetFileToWrite(FileStore *file); + + ConnectionState *GetConnection() const { return cs; } + uint16_t GetLocalPort() const; + uint32_t GetRemoteIP() const; + uint16_t GetRemotePort() const; + + void Commit(bool keepConnectionAlive); + void Defer(DeferralMode mode); + void Discard(); + +private: + bool CanWrite() const; + bool Send(); + void Close(); + void FreePbuf(); + + ConnectionState* cs; + NetworkTransaction* volatile next; // next NetworkTransaction in the list we are in + NetworkTransaction* volatile nextWrite; // next NetworkTransaction queued to write to assigned connection + pbuf *pb, *readingPb; // received packet queue and a pointer to the pbuf being read from + size_t inputPointer; // amount of data already taken from the first packet buffer + + OutputBuffer *sendBuffer; + OutputStack *sendStack; + FileStore * volatile fileBeingSent; + + volatile TransactionStatus status; + volatile bool closeRequested, dataAcknowledged; +}; + +inline bool NetworkTransaction::CanWrite() const +{ + return (IsConnected() && status != released); +} + +#endif /* SRC_DUET_NETWORKTRANSACTION_H_ */ diff --git a/src/Duet/Pins_Duet.h b/src/Duet/Pins_Duet.h index d5af7f65..4234a869 100644 --- a/src/Duet/Pins_Duet.h +++ b/src/Duet/Pins_Duet.h @@ -24,8 +24,6 @@ const int8_t HEATERS = 7; // The number of heaters in the machine; 0 is the const size_t MAX_AXES = 6; // The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES const size_t MIN_AXES = 3; // The minimum and default number of axes -const size_t DELTA_AXES = 3; // The number of axis involved in delta movement -const size_t CART_AXES = 3; // The number of Cartesian axes const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders const size_t NUM_SERIAL_CHANNELS = 3; // The number of serial IO channels (USB and two auxiliary UARTs) diff --git a/src/Duet/Webserver.h b/src/Duet/Webserver.h deleted file mode 100644 index d2bab356..00000000 --- a/src/Duet/Webserver.h +++ /dev/null @@ -1,382 +0,0 @@ -/**************************************************************************************************** - -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 - -#include "lwipopts.h" - -/* Generic values */ - -const size_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2 - -/* 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 diff --git a/src/DuetNG/DuetEthernet/Network.cpp b/src/DuetNG/DuetEthernet/Network.cpp index e2c17ddf..4f742cb8 100644 --- a/src/DuetNG/DuetEthernet/Network.cpp +++ b/src/DuetNG/DuetEthernet/Network.cpp @@ -5,10 +5,9 @@ * Author: David */ -#include "RepRapFirmware.h" -#include "compiler.h" -#include "Pins.h" -#include "IPAddress.h" +#include "Network.h" +#include "NetworkTransaction.h" +#include "Platform.h" #include "wizchip_conf.h" #include "Wiznet/Internet/DHCP/dhcp.h" @@ -35,6 +34,7 @@ void Network::Init() state = NetworkState::disabled; longWait = platform->Time(); lastTickMillis = millis(); + InitSockets(); } // This is called at the end of config.g processing. @@ -78,6 +78,7 @@ void Network::Spin() else { debugPrintf("Link established, network running\n"); + InitSockets(); state = NetworkState::active; } } @@ -100,12 +101,14 @@ void Network::Spin() // Send mDNS announcement so that some routers can perform hostname mapping // if this board is connected via a non-IGMP capable WiFi bridge (like the TP-Link WR701N) //mdns_announce(); + InitSockets(); state = NetworkState::active; } } else { DHCP_stop(); + TerminateSockets(); state = NetworkState::establishingLink; } break; @@ -128,9 +131,13 @@ void Network::Spin() } } - // See if we can read any packets -// ethernet_task(); - + sockets[nextSocketToPoll].Poll(); + ++nextSocketToPoll; + if (nextSocketToPoll == NumTcpSockets) + { + nextSocketToPoll = 0; + } +#if 0 // See if we can send anything NetworkTransaction *transaction = writingTransactions; if (transaction != nullptr /*&& sendingConnection == nullptr*/ ) @@ -159,10 +166,12 @@ void Network::Spin() } } } +#endif } else { DHCP_stop(); + TerminateSockets(); state = NetworkState::establishingLink; } break; @@ -303,7 +312,7 @@ bool Network::InLwip() const // This method also ensures that the retrieved transaction is moved to the first item of // readyTransactions, so that a subsequent call with a NULL cs parameter will return exactly // the same instance. -NetworkTransaction *Network::GetTransaction(const ConnectionState *cs) +NetworkTransaction *Network::GetTransaction(Connection conn) { #if 1 return nullptr; @@ -416,26 +425,17 @@ void Network::CloseDataPort() // These methods keep track of our connections in case we need to send to one of them void Network::SaveDataConnection() { - //TODO -#if 0 - dataCs = readyTransactions->cs; -#endif +// dataCs = readyTransactions->cs->GetNumber(); } void Network::SaveFTPConnection() { - //TODO -#if 0 - ftpCs = readyTransactions->cs; -#endif +// ftpCs = readyTransactions->cs->GetNumber(); } void Network::SaveTelnetConnection() { - //TODO -#if 0 - telnetCs = readyTransactions->cs; -#endif +// telnetCs = readyTransactions->cs->GetNumber(); } bool Network::AcquireFTPTransaction() @@ -465,4 +465,32 @@ bool Network::AcquireTelnetTransaction() #endif } +void Network::InitSockets() +{ + for (SocketNumber skt = 0; skt < NumHttpSockets; ++skt) + { + sockets[skt].Init(skt, httpPort); + } + sockets[FtpSocketNumber].Init(FtpSocketNumber, FTP_PORT); + sockets[FtpDataSocketNumber].Init(FtpDataSocketNumber, 0); // FTP data port is allocated dynamically + sockets[TelnetSocketNumber].Init(TelnetSocketNumber, TELNET_PORT); + nextSocketToProcess = nextSocketToPoll = 0; +} + +void Network::TerminateSockets() +{ + for (SocketNumber skt = 0; skt < NumTcpSockets; ++skt) + { + sockets[skt].Terminate(); + } + +} + +/*static*/ uint16_t Network::GetLocalPort(Connection conn) { return conn->GetLocalPort(); } +/*static*/ uint16_t Network::GetRemotePort(Connection conn) { return conn->GetRemotePort(); } +/*static*/ uint32_t Network::GetRemoteIP(Connection conn) { return conn->GetRemoteIP(); } +/*static*/ bool Network::IsConnected(Connection conn) { return conn->IsConnected(); } +/*static*/ bool Network::IsTerminated(Connection conn) { return conn->IsTerminated(); } +/*static*/ void Network::Terminate(Connection conn) { conn->Terminate(); } + // End diff --git a/src/DuetNG/DuetEthernet/Network.h b/src/DuetNG/DuetEthernet/Network.h index 36501dcc..d710205a 100644 --- a/src/DuetNG/DuetEthernet/Network.h +++ b/src/DuetNG/DuetEthernet/Network.h @@ -9,26 +9,18 @@ Separated out from Platform.h by dc42 and extended by zpl #ifndef NETWORK_H #define NETWORK_H -#include <cstdint> -#include <cctype> -#include <cstring> -#include <cstdlib> - +#include "NetworkDefs.h" +#include "RepRapFirmware.h" #include "MessageType.h" -#include "NetworkTransaction.h" - -const uint8_t DefaultMacAddress[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED }; // Need some sort of default... -const uint8_t DefaultIpAddress[4] = { 0, 0, 0, 0 }; // Need some sort of default... -const uint8_t DefaultNetMask[4] = { 255, 255, 255, 0 }; -const uint8_t DefaultGateway[4] = { 0, 0, 0, 0 }; - -const uint16_t DefaultHttpPort = 80; -const uint16_t FTP_PORT = 21; -const uint16_t TELNET_PORT = 23; +#include "Socket.h" -// We have 8 sockets available on the W5500. We reserve one for DHCP, leaving 7 for TCP/IP transactions i.e. HTTP, FTP and Telnet. +// We have 8 sockets available on the W5500. +const size_t NumHttpSockets = 4; // sockets 0-3 are for HTTP +const SocketNumber FtpSocketNumber = 4; +const SocketNumber FtpDataSocketNumber = 5; // TODO can we allocate this dynamically when required, to allow more http sockets most of the time? +const SocketNumber TelnetSocketNumber = 6; const size_t NumTcpSockets = 7; -const uint8_t DhcpSocketNumber = 7; +const SocketNumber DhcpSocketNumber = 7; // TODO can we allocate this dynamically when required, to allow more http sockets most of the time? class Platform; @@ -63,7 +55,7 @@ public: // Interfaces for the Webserver - NetworkTransaction *GetTransaction(const ConnectionState *cs = nullptr); + NetworkTransaction *GetTransaction(Connection conn = NoConnection); void OpenDataPort(uint16_t port); uint16_t GetDataPort() const; @@ -77,6 +69,13 @@ public: bool AcquireDataTransaction(); bool AcquireTelnetTransaction(); + static uint16_t GetLocalPort(Connection conn); + static uint16_t GetRemotePort(Connection conn); + static uint32_t GetRemoteIP(Connection conn); + static bool IsConnected(Connection conn); + static bool IsTerminated(Connection conn); + static void Terminate(Connection conn); + private: enum class NetworkState { @@ -87,13 +86,20 @@ private: active }; + void AppendTransaction(NetworkTransaction* * list, NetworkTransaction *r); + void PrependTransaction(NetworkTransaction* * list, NetworkTransaction *r); + bool AcquireTransaction(Socket *cs); + + void InitSockets(); + void TerminateSockets(); + Platform *platform; float longWait; uint32_t lastTickMillis; - void AppendTransaction(NetworkTransaction* * list, NetworkTransaction *r); - void PrependTransaction(NetworkTransaction* * list, NetworkTransaction *r); - bool AcquireTransaction(ConnectionState *cs); + Socket sockets[NumTcpSockets]; + size_t nextSocketToPoll; // next TCP socket number to poll for read/write operations + size_t nextSocketToProcess; // next TCP socket number to process an incoming request from NetworkTransaction * freeTransactions; NetworkTransaction * readyTransactions; @@ -105,7 +111,7 @@ private: uint8_t gateway[4]; char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0 - NetworkState state; + NetworkState state; bool activated; bool usingDhcp; }; diff --git a/src/DuetNG/DuetEthernet/NetworkBuffer.cpp b/src/DuetNG/DuetEthernet/NetworkBuffer.cpp new file mode 100644 index 00000000..8fad5241 --- /dev/null +++ b/src/DuetNG/DuetEthernet/NetworkBuffer.cpp @@ -0,0 +1,83 @@ +/* + * NetworkBuffer.cpp + * + * Created on: 24 Dec 2016 + * Author: David + */ + +#include "NetworkBuffer.h" +#include <cstring> + +NetworkBuffer *NetworkBuffer::freelist = nullptr; + +NetworkBuffer::NetworkBuffer(NetworkBuffer *n) : next(n), dataLength(0), readPointer(0) +{ +} + +// Release this buffer and return the next one in the chain +NetworkBuffer *NetworkBuffer::Release() +{ + NetworkBuffer *ret = next; + next = freelist; + freelist = this; + return ret; +} + +// Read 1 character, returning true if successful, false if no data left +bool NetworkBuffer::ReadChar(char& b) +{ + if (readPointer < dataLength) + { + b = data[readPointer++]; + return true; + } + + b = 0; + return false; +} + +// Read some data, but not more than the amount in the first buffer +size_t NetworkBuffer::ReadBuffer(uint8_t *buffer, size_t maxLen) +{ + if (maxLen > Remaining()) + { + maxLen = Remaining(); + } + memcpy((void*)buffer, data + readPointer, maxLen); + readPointer += maxLen; + return maxLen; +} + +// Return the amount of data available, including continuation buffers +size_t NetworkBuffer::TotalRemaining() const +{ + const NetworkBuffer *b = this; + size_t ret = 0; + while (b != nullptr) + { + ret += b->Remaining(); + } + return ret; +} + +/*static*/ NetworkBuffer *NetworkBuffer::Allocate() +{ + NetworkBuffer *ret = freelist; + if (ret != nullptr) + { + freelist = ret->next; + ret->next = nullptr; + } + return ret; +} + +/*static*/ void NetworkBuffer::AllocateBuffers(unsigned int number) +{ + while (number != 0) + { + freelist = new NetworkBuffer(freelist); + --number; + } +} + +// End diff --git a/src/DuetNG/DuetEthernet/NetworkBuffer.h b/src/DuetNG/DuetEthernet/NetworkBuffer.h new file mode 100644 index 00000000..9e20c4bd --- /dev/null +++ b/src/DuetNG/DuetEthernet/NetworkBuffer.h @@ -0,0 +1,54 @@ +/* + * NetworkBuffer.h + * + * Created on: 24 Dec 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_DUETETHERNET_NETWORKBUFFER_H_ +#define SRC_DUETNG_DUETETHERNET_NETWORKBUFFER_H_ + +#include <cstdint> +#include <cstddef> + +// Network buffer class. These buffers are 2K long so that they can accept as much data as the W5500 can provide in one go. +class NetworkBuffer +{ +public: + friend class Socket; + + // Release this buffer and return the next one in the chain + NetworkBuffer *Release(); + + // Read 1 character, returning true of successful, false if no data left + bool ReadChar(char& b); + + // Read some data + size_t ReadBuffer(uint8_t *buffer, size_t maxLen); + + // Return the amount of data available, not including continuation buffers + size_t Remaining() const { return dataLength - readPointer; } + + // Return the amount of data available, including continuation buffers + size_t TotalRemaining() const; + + bool IsEmpty() const { return readPointer == dataLength; } + + static NetworkBuffer *Allocate(); + + static void AllocateBuffers(unsigned int number); + + static const size_t bufferSize = 2048; + +private: + NetworkBuffer(NetworkBuffer *n); + + NetworkBuffer *next; + size_t dataLength; + size_t readPointer; + uint8_t data[bufferSize]; + + static NetworkBuffer *freelist; +}; + +#endif /* SRC_DUETNG_DUETETHERNET_NETWORKBUFFER_H_ */ diff --git a/src/DuetNG/DuetEthernet/NetworkDefs.h b/src/DuetNG/DuetEthernet/NetworkDefs.h new file mode 100644 index 00000000..a1225e63 --- /dev/null +++ b/src/DuetNG/DuetEthernet/NetworkDefs.h @@ -0,0 +1,35 @@ +/* + * NetworkCommon.h + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_DUETETHERNET_NETWORKDEFS_H_ +#define SRC_DUETNG_DUETETHERNET_NETWORKDEFS_H_ + +class NetworkTransaction; +class Socket; +class NetworkBuffer; + +// Definition of how a Connection is represented, for Webserver module +typedef Socket *Connection; +const Connection NoConnection = nullptr; + +typedef uint8_t SocketNumber; +const SocketNumber NoSocket = 255; + +typedef uint16_t Port; + +const uint8_t DefaultMacAddress[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED }; // Need some sort of default... +const uint8_t DefaultIpAddress[4] = { 0, 0, 0, 0 }; // Need some sort of default... +const uint8_t DefaultNetMask[4] = { 255, 255, 255, 0 }; +const uint8_t DefaultGateway[4] = { 0, 0, 0, 0 }; + +const Port DefaultHttpPort = 80; +const Port FTP_PORT = 21; +const Port TELNET_PORT = 23; + +const unsigned int TCP_MSS = 1460; + +#endif /* SRC_DUETNG_DUETETHERNET_NETWORKDEFS_H_ */ diff --git a/src/DuetNG/DuetEthernet/NetworkTransacrion.cpp b/src/DuetNG/DuetEthernet/NetworkTransaction.cpp index e954723d..855c4c85 100644 --- a/src/DuetNG/DuetEthernet/NetworkTransacrion.cpp +++ b/src/DuetNG/DuetEthernet/NetworkTransaction.cpp @@ -6,44 +6,10 @@ */ #include "NetworkTransaction.h" +#include "Socket.h" #include <cstdarg> //*************************************************************************************************** - -// ConnectionState class - -#if 0 -void ConnectionState::Init(tcp_pcb *p) -{ - pcb = p; - localPort = p->local_port; - remoteIPAddress = p->remote_ip.addr; - remotePort = p->remote_port; - next = nullptr; - sendingTransaction = nullptr; - persistConnection = true; - isTerminated = false; -} -#endif - -void ConnectionState::Terminate() -{ - //TODO -#if 0 - if (pcb != nullptr) - { - tcp_abort(pcb); - } -#endif -} - -bool ConnectionState::IsConnected() const -{ - //TODO - return false; -} - -//*************************************************************************************************** // NetworkTransaction class NetworkTransaction::NetworkTransaction(NetworkTransaction *n) : next(n), status(released) @@ -72,26 +38,26 @@ bool NetworkTransaction::HasMoreDataToRead() const return false; } +bool NetworkTransaction::IsConnected() const +{ + return (cs != nullptr && cs->IsConnected()); +} + +bool NetworkTransaction::CanWrite() const +{ + return (IsConnected() && status != released); +} + // Read one char from the NetworkTransaction bool NetworkTransaction::Read(char& b) { -#if 1 - return false; -#else - if (readingPb == nullptr) + if (cs == nullptr) { b = 0; return false; } - b = ((const char*)readingPb->payload)[inputPointer++]; - if (inputPointer == readingPb->len) - { - readingPb = readingPb->next; - inputPointer = 0; - } - return true; -#endif + return cs->ReadChar(b); } // Read data from the NetworkTransaction and return true on success diff --git a/src/DuetNG/DuetEthernet/NetworkTransaction.h b/src/DuetNG/DuetEthernet/NetworkTransaction.h index 91a9eedb..93ddc725 100644 --- a/src/DuetNG/DuetEthernet/NetworkTransaction.h +++ b/src/DuetNG/DuetEthernet/NetworkTransaction.h @@ -8,69 +8,13 @@ #ifndef SRC_DUETNG_DUETETHERNET_NETWORKTRANSACTION_H_ #define SRC_DUETNG_DUETETHERNET_NETWORKTRANSACTION_H_ +#include <NetworkDefs.h> #include <cstdint> #include <cstddef> #include "Libraries/General/StringRef.h" #include "OutputMemory.h" #include "Storage/FileStore.h" - -typedef uint8_t SocketNumber; - -const SocketNumber NoSocket = 255; - -// Network buffer class. These buffers are 2K long so that they can accept as much data as the W5500 can provide in one go. -class NetworkBuffer -{ -public: - friend class NetworkTransaction; - - NetworkBuffer(); - - // Release this buffer and return the next one in the chain - NetworkBuffer *Release(); - - // Read 1 character, returning true of successful, false if no data left - bool Read(char& b); - - // Read some data - bool ReadBuffer(const char *&buffer, size_t &len); - - // Return the amount of data available, not including continuation buffers - size_t Remaining() const; - - // Return the amount of data available, including continuation buffers - size_t TotalRemaining() const; - -private: - NetworkBuffer *next; - size_t dataLength; - size_t readPointer; - uint8_t data[2048]; -}; - -class NetworkTransaction; - -// ConnectionState structure that we use to track TCP connections. It is usually combined with NetworkTransactions. -class ConnectionState -{ -public: - void Init(SocketNumber s); - uint16_t GetLocalPort() const { return localPort; } - uint32_t GetRemoteIP() const { return remoteIPAddress; } - uint16_t GetRemotePort() const { return remotePort; } - bool IsConnected() const; // { return pcb != nullptr; } - bool IsTerminated() const { return isTerminated; } - void Terminate(); - -private: - uint16_t localPort, remotePort; // Copy of the local and remote ports, because the PCB may be unavailable - uint32_t remoteIPAddress; // Same for the remote IP address - NetworkTransaction * volatile sendingTransaction; // NetworkTransaction that is currently sending via this connection - ConnectionState * volatile next; // Next ConnectionState in this list - bool persistConnection; // Do we expect this connection to stay alive? - bool isTerminated; // Will be true if the connection has gone down unexpectedly (TCP RST) - SocketNumber socketNum; // The W5500 socket number we are using -}; +#include "NetworkBuffer.h" // Assign a status to each NetworkTransaction enum TransactionStatus @@ -116,10 +60,10 @@ public: void Printf(const char *fmt, ...); void SetFileToWrite(FileStore *file); - ConnectionState *GetConnection() const { return cs; } - uint16_t GetLocalPort() const; + Connection GetConnection() const { return cs; } + Port GetLocalPort() const; uint32_t GetRemoteIP() const; - uint16_t GetRemotePort() const; + Port GetRemotePort() const; bool Send(); void Commit(bool keepConnectionAlive); @@ -133,28 +77,18 @@ private: bool CanWrite() const; void Close(); - ConnectionState* cs; + Socket* cs; // the network socket that this transaction cam from NetworkTransaction* next; // next NetworkTransaction in the list we are in NetworkTransaction* nextWrite; // next NetworkTransaction queued to write to assigned connection - NetworkBuffer *pb, *readingPb; // received packet queue and a pointer to the pbuf being read from +// NetworkBuffer *pb, *readingPb; // received packet queue and a pointer to the pbuf being read from // size_t inputPointer; // amount of data already taken from the first packet buffer OutputBuffer *sendBuffer; OutputStack *sendStack; FileStore * volatile fileBeingSent; - volatile TransactionStatus status; - volatile bool closeRequested, dataAcknowledged; + /*volatile*/ TransactionStatus status; + /*volatile*/ bool closeRequested, dataAcknowledged; }; -inline bool NetworkTransaction::IsConnected() const -{ - return (cs != nullptr && cs->IsConnected()); -} - -inline bool NetworkTransaction::CanWrite() const -{ - return (IsConnected() && status != released); -} - #endif /* SRC_DUETNG_DUETETHERNET_NETWORKTRANSACTION_H_ */ diff --git a/src/DuetNG/DuetEthernet/Socket.cpp b/src/DuetNG/DuetEthernet/Socket.cpp new file mode 100644 index 00000000..7bfff537 --- /dev/null +++ b/src/DuetNG/DuetEthernet/Socket.cpp @@ -0,0 +1,166 @@ +/* + * Socket.cpp + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#include "Socket.h" +#include "NetworkTransaction.h" +#include "NetworkBuffer.h" +#include <socketlib.h> + +//*************************************************************************************************** +// Socket class + +Socket::Socket() : receivedData(nullptr), sendingTransaction(nullptr) +{ +} + +// Initialise a TCP socket +void Socket::Init(SocketNumber skt, Port serverPort) +{ + socketNum = skt; + localPort = serverPort; + ReInit(); +} + +void Socket::ReInit() +{ + // Discard any transactions that were queued to send + NetworkTransaction *st; + while ((st = sendingTransaction) != nullptr) + { + sendingTransaction = st->GetNext(); + st->Discard(); + } + + // Discard any received data + while (receivedData != nullptr) + { + receivedData = receivedData->Release(); + } + + persistConnection = true; + isTerminated = false; + state = SocketState::inactive; + + // Re-initialise the socket on the W5500 + socket(socketNum, Sn_MR_TCP, localPort, 0x00); +} + +// Terminate a connection +void Socket::Terminate() +{ + disconnectNoWait(socketNum); + isTerminated = true; + state = SocketState::inactive; + while (receivedData != nullptr) + { + receivedData = receivedData->Release(); + } +} + +// Test whether we have a connection on this socket +bool Socket::IsConnected() const +{ + return getSn_SR(socketNum) == SOCK_ESTABLISHED; //TODO is this right? +} + +// Read 1 character from the receive buffers, returning true if successful +bool Socket::ReadChar(char& c) +{ + if (receivedData == nullptr) + { + c = 0; + return false; + } + + bool ret = receivedData->ReadChar(c); + if (receivedData->IsEmpty()) + { + receivedData = receivedData->Release(); + } + return ret; +} + +// Poll a socket to see if it needs to be serviced +void Socket::Poll() +{ + switch(getSn_SR(socketNum)) + { + case SOCK_INIT: + // Socket has been initialised but is not listening yet + if (localPort != 0) // localPort for the FTP data socket is 0 until we have decided what port number to use + { + listen(socketNum); + } + break; + + case SOCK_LISTEN: // Socket is listening but no client has connected to it yet + break; + + case SOCK_ESTABLISHED: // A client is connected to this socket + if (getSn_IR(socketNum) & Sn_IR_CON) + { + // New connection, so retrieve the sending IP address and port, and clear the interrupt + getSn_DIPR(socketNum, reinterpret_cast<uint8_t*>(&remoteIPAddress)); + remotePort = getSn_DPORT(socketNum); + setSn_IR(socketNum, Sn_IR_CON); + } + + // See if the socket has received any data + { + const uint16_t len = getSn_RX_RSR(socketNum); + if (len != 0) + { + // There is data available, so allocate a buffer + NetworkBuffer * const buf = NetworkBuffer::Allocate(); + if (buf != nullptr) + { + buf->dataLength = recv(socketNum, buf->data, min<size_t>(len, NetworkBuffer::bufferSize)); + buf->readPointer = 0; + // Append the buffer to the list of receive buffers + NetworkBuffer** bufp = &receivedData; + while (*bufp != nullptr) + { + bufp = &((*bufp)->next); + } + *bufp = buf; + } + } + } + + // See if we can send any data + //TODO + break; + + case SOCK_CLOSE_WAIT: // A client has asked to disconnect +#ifdef _HTTPSERVER_DEBUG_ + printf("> HTTPSocket[%d] : ClOSE_WAIT\r\n", socketNum); +#endif + disconnect(socketNum); + break; + + case SOCK_CLOSED: +#ifdef _HTTPSERVER_DEBUG_ + printf("> HTTPSocket[%d] : CLOSED\r\n", s); +#endif + if (socket(socketNum, Sn_MR_TCP, localPort, 0x00) == socketNum) // Reinitialize the socket + { +#ifdef _HTTPSERVER_DEBUG_ + printf("> HTTPSocket[%d] : OPEN\r\n", socketNum); +#endif + } + break; + + default: + break; + } // end of switch + +#ifdef _USE_WATCHDOG_ + HTTPServer_WDT_Reset(); +#endif +} + +// End diff --git a/src/DuetNG/DuetEthernet/Socket.h b/src/DuetNG/DuetEthernet/Socket.h new file mode 100644 index 00000000..59f04906 --- /dev/null +++ b/src/DuetNG/DuetEthernet/Socket.h @@ -0,0 +1,54 @@ +/* + * Socket.h + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_DUETETHERNET_SOCKET_H_ +#define SRC_DUETNG_DUETETHERNET_SOCKET_H_ + +#include <NetworkDefs.h> +#include <cstdint> +#include <cstddef> + +// Socket structure that we use to track TCP connections +class Socket +{ +public: + Socket(); + void Init(SocketNumber s, Port serverPort); + void Poll(); + Port GetLocalPort() const { return localPort; } + uint32_t GetRemoteIP() const { return remoteIPAddress; } + Port GetRemotePort() const { return remotePort; } + bool IsConnected() const; + bool IsTerminated() const { return isTerminated; } + void Terminate(); + SocketNumber GetNumber() const { return socketNum; } + bool ReadChar(char& c); + +private: + enum class SocketState : uint8_t + { + inactive, + idle, + requestInProgress, + requestDone, + responseInProgress, + responseDone + }; + + void ReInit(); + + Port localPort, remotePort; // The local and remote ports + uint32_t remoteIPAddress; // The remote IP address + NetworkBuffer *receivedData; // Chain of buffers holding received data + NetworkTransaction * /*volatile*/ sendingTransaction; // NetworkTransaction that is currently sending via this connection + bool persistConnection; // Do we expect this connection to stay alive? + bool isTerminated; // Will be true if the connection has gone down unexpectedly (TCP RST) + SocketNumber socketNum; // The W5500 socket number we are using + SocketState state; +}; + +#endif /* SRC_DUETNG_DUETETHERNET_SOCKET_H_ */ diff --git a/src/DuetNG/DuetEthernet/Webserver.cpp b/src/DuetNG/DuetEthernet/Webserver.cpp deleted file mode 100644 index af1bdcbe..00000000 --- a/src/DuetNG/DuetEthernet/Webserver.cpp +++ /dev/null @@ -1,2942 +0,0 @@ -/**************************************************************************************************** - - 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 Jquery.js to perform AJAX. See: - - http://jquery.com/ - - ----------------------------------------------------------------------------------------------------- - - Version 0.2 - - 10 May 2013 - - Adrian Bowyer - RepRap Professional Ltd - http://reprappro.com - - Licence: GPL - - ----------------------------------------------------------------------------------------------------- - - The supported requests are GET requests for files (for which the root is the www directory on the - SD card), and the following. These all start with "/rr_". Ordinary files used for the web interface - must not have names starting "/rr_" or they will not be found. Times should be generally specified - in the format YYYY-MM-DDTHH:MM:SS so the firmware can parse them. - - rr_connect?password=xxx&time=yyy - Sent by the web interface software to establish an initial connection, indicating that - any state variables relating to the web interface (e.g. file upload in progress) should - be reset. This only happens if the password could be verified. - - rr_fileinfo Returns file information about the file being printed. - - rr_fileinfo?name=xxx - Returns file information about a file on the SD card or a JSON-encapsulated response - with err = 1 if the passed filename was invalid. - - rr_status New-style status response, in which temperatures, axis positions and extruder positions - are returned in separate variables. Another difference is that extruder positions are - returned as absolute positions instead of relative to the previous gcode. A client - may also request different status responses by specifying the "type" keyword, followed - by a custom status response type. Also see "M105 S1". - - rr_filelist?dir=xxx - Returns a JSON-formatted list of all the files in xxx including the type and size in the - following format: "files":[{"type":'f/d',"name":"xxx",size:yyy},...] - - rr_files?dir=xxx&flagDirs={1/0} [DEPRECATED] - Returns a listing of the filenames in the /gcode directory of the SD card. 'dir' is a - directory path relative to the root of the SD card. If the 'dir' variable is not present, - it defaults to the /gcode directory. If flagDirs is set to 1, all directories will be - prefixed by an asterisk. - - rr_reply Returns the last-known G-code reply as plain text (not encapsulated as JSON). - - rr_configfile [DEPRECATED] - Sends the config file as plain text (not encapsulated as JSON either). - - rr_download?name=xxx - Download a specified file from the SD card - - rr_upload?name=xxx&time=yyy - Upload a specified file using a POST request. The payload of this request has to be - the file content. Only one file may be uploaded at once. When the upload has finished, - a JSON response with the variable "err" will be returned, which will be 0 if the job - has finished without problems, it will be set to 1 otherwise. - - rr_delete?name=xxx - Delete file xxx. Returns err (zero if successful). - - rr_mkdir?dir=xxx - Create a new directory xxx. Return err (zero if successful). - - rr_move?old=xxx&new=yyy - Rename an old file xxx to yyy. May also be used to move a file to another directory. - - ****************************************************************************************************/ - -#include "RepRapFirmware.h" - -//*************************************************************************************************** - -const char* overflowResponse = "overflow"; -const char* badEscapeResponse = "bad escape"; - - -//******************************************************************************************** -// -//**************************** Generic Webserver implementation ****************************** -// -//******************************************************************************************** - - -// Constructor and initialisation -Webserver::Webserver(Platform* p, Network *n) : platform(p), network(n), webserverActive(false) -{ - httpInterpreter = new HttpInterpreter(p, this, n); - ftpInterpreter = new FtpInterpreter(p, this, n); - telnetInterpreter = new TelnetInterpreter(p, this, n); -} - -void Webserver::Init() -{ - // initialise the webserver class - longWait = platform->Time(); - webserverActive = true; - readingConnection = nullptr; - - // initialise all protocol handlers - httpInterpreter->ResetState(); - ftpInterpreter->ResetState(); - telnetInterpreter->ResetState(); -} - -// Deal with input/output from/to the client (if any) -void Webserver::Spin() -{ - // Check if we are enabled and we can actually send something back to the client - if (webserverActive && OutputBuffer::GetBytesLeft(nullptr) != 0) - { - // We must ensure that we have exclusive access to LWIP - if (!network->Lock()) - { - // Allow each ProtocolInterpreter to do something - httpInterpreter->Spin(); - ftpInterpreter->Spin(); - telnetInterpreter->Spin(); - - // See if we have new data to process - currentTransaction = network->GetTransaction(readingConnection); - if (currentTransaction != nullptr) - { - // Take care of different protocol types here - ProtocolInterpreter *interpreter; - const uint16_t localPort = currentTransaction->GetLocalPort(); - switch (localPort) - { - case FTP_PORT: /* FTP */ - interpreter = ftpInterpreter; - break; - - case TELNET_PORT: /* Telnet */ - interpreter = telnetInterpreter; - break; - - default: /* HTTP and FTP data */ - if (localPort == network->GetHttpPort()) - { - interpreter = httpInterpreter; - } - else - { - interpreter = ftpInterpreter; - } - break; - } - - // See if we have to print some debug info - if (reprap.Debug(moduleWebserver)) - { - const char *type; - switch (currentTransaction->GetStatus()) - { - case released: type = "released"; break; - case connected: type = "connected"; break; - case receiving: type = "receiving"; break; - case sending: type = "sending"; break; - case disconnected: type = "disconnected"; break; - case deferred: type = "deferred"; break; - case acquired: type = "acquired"; break; - default: type = "unknown"; break; - } - platform->MessageF(HOST_MESSAGE, "Incoming transaction: Type %s at local port %d (remote port %d)\n", - type, localPort, currentTransaction->GetRemotePort()); - } - - // For protocols other than HTTP it is important to send a HELO message - TransactionStatus status = currentTransaction->GetStatus(); - if (status == connected) - { - interpreter->ConnectionEstablished(); - } - // Graceful disconnects are handled here, because prior NetworkTransactions might still contain valid - // data. That's why it's a bad idea to close these connections immediately in the Network class. - else if (status == disconnected) - { - // This will call the disconnect events and effectively close the connection - currentTransaction->Discard(); - } - // Check for fast uploads via this connection - else if (interpreter->DoingFastUpload()) - { - interpreter->DoFastUpload(); - } - // Process other messages (if we can) - else if (interpreter->CanParseData()) - { - readingConnection = currentTransaction->GetConnection(); - for (size_t i = 0; i < TCP_MSS / 3; i++) - { - char c; - if (currentTransaction->Read(c)) - { - // Each ProtocolInterpreter must take care of the current NetworkTransaction by calling either Commit(), Discard() or Defer() - if (interpreter->CharFromClient(c)) - { - readingConnection = nullptr; - break; - } - } - else - { - // We ran out of data before finding a complete request. This happens when the incoming - // message length exceeds the TCP MSS. Notify the current ProtocolInterpreter about this, - // which will remove the current transaction too - interpreter->NoMoreDataAvailable(); - readingConnection = nullptr; - break; - } - } - } - } - else if (readingConnection != nullptr) - { - // We failed to find a transaction for a reading connection. - // This should never happen, but if it does, terminate this connection instantly - platform->Message(HOST_MESSAGE, "Error: Transaction for reading connection not found\n"); - readingConnection->Terminate(); - } - network->Unlock(); // unlock LWIP again - } - } - platform->ClassReport(longWait); -} - -void Webserver::Exit() -{ - httpInterpreter->CancelUpload(); - ftpInterpreter->CancelUpload(); - //telnetInterpreter->CancelUpload(); // Telnet doesn't support fast file uploads - - platform->Message(HOST_MESSAGE, "Webserver class exited.\n"); - webserverActive = false; -} - -void Webserver::Diagnostics(MessageType mtype) -{ - platform->Message(mtype, "=== Webserver ===\n"); - httpInterpreter->Diagnostics(mtype); - ftpInterpreter->Diagnostics(mtype); - telnetInterpreter->Diagnostics(mtype); -} - -bool Webserver::GCodeAvailable(const WebSource source) const -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->GCodeAvailable(); - - case WebSource::Telnet: - return telnetInterpreter->GCodeAvailable(); - } - - return false; -} - -char Webserver::ReadGCode(const WebSource source) -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->ReadGCode(); - - case WebSource::Telnet: - return telnetInterpreter->ReadGCode(); - } - - return 0; -} - -void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) -{ - switch (source) - { - case WebSource::HTTP: - httpInterpreter->HandleGCodeReply(reply); - break; - - case WebSource::Telnet: - telnetInterpreter->HandleGCodeReply(reply); - break; - } -} - -void Webserver::HandleGCodeReply(const WebSource source, const char *reply) -{ - switch (source) - { - case WebSource::HTTP: - httpInterpreter->HandleGCodeReply(reply); - break; - - case WebSource::Telnet: - telnetInterpreter->HandleGCodeReply(reply); - break; - } -} - -uint16_t Webserver::GetGCodeBufferSpace(const WebSource source) const -{ - switch (source) - { - case WebSource::HTTP: - return httpInterpreter->GetGCodeBufferSpace(); - - case WebSource::Telnet: - return telnetInterpreter->GetGCodeBufferSpace(); - } - - return 0; -} - -// Handle immediate disconnects here (cs will be freed after this call) -// May be called by ISR, but not while LwIP is NOT locked -void Webserver::ConnectionLost(const ConnectionState *cs) -{ - // Inform protocol handlers that this connection has been lost - uint16_t localPort = cs->GetLocalPort(); - ProtocolInterpreter *interpreter; - switch (localPort) - { - case FTP_PORT: /* FTP */ - interpreter = ftpInterpreter; - break; - - case TELNET_PORT: /* Telnet */ - interpreter = telnetInterpreter; - break; - - default: /* HTTP and FTP data */ - if (localPort == network->GetHttpPort()) - { - interpreter = httpInterpreter; - break; - } - else if (localPort == network->GetDataPort()) - { - interpreter = ftpInterpreter; - break; - } - - platform->MessageF(GENERIC_MESSAGE, "Error: Webserver should handle disconnect event at local port %d, but no handler was found!\n", localPort); - return; - } - - // Print some debug information and notify the protocol interpreter - if (reprap.Debug(moduleWebserver)) - { - platform->MessageF(HOST_MESSAGE, "ConnectionLost called for local port %d (remote port %d)\n", localPort, cs->GetRemotePort()); - } - interpreter->ConnectionLost(cs); - - // Don't process any more data from this connection if has gone down - if (readingConnection == cs) - { - readingConnection = nullptr; - } -} - - -//******************************************************************************************** -// -//********************** Generic Procotol Interpreter implementation ************************* -// -//******************************************************************************************** - -ProtocolInterpreter::ProtocolInterpreter(Platform *p, Webserver *ws, Network *n) - : platform(p), webserver(ws), network(n) -{ - uploadState = notUploading; - filenameBeingUploaded[0] = 0; -} - -void ProtocolInterpreter::Spin() -{ - // Check if anything went wrong while writing upload data, and delete the file again if that is the case - if (uploadState == uploadError) - { - if (fileBeingUploaded.IsLive()) - { - fileBeingUploaded.Close(); - } - if (filenameBeingUploaded[0] != 0) - { - platform->GetMassStorage()->Delete(FS_PREFIX, filenameBeingUploaded); - } - - uploadState = notUploading; - filenameBeingUploaded[0] = 0; - } -} - -void ProtocolInterpreter::ConnectionEstablished() -{ - // Don't care about incoming connections by default - webserver->currentTransaction->Discard(); -} - -void ProtocolInterpreter::NoMoreDataAvailable() -{ - // Request is not complete yet, but don't care. Interpreters that do not explicitly - // overwrite this method don't support more than one connected client anyway - webserver->currentTransaction->Discard(); -} - -// Start writing to a new file -bool ProtocolInterpreter::StartUpload(FileStore *file, const char *fileName) -{ - if (file != nullptr) - { - fileBeingUploaded.Set(file); - strncpy(filenameBeingUploaded, fileName, ARRAY_SIZE(filenameBeingUploaded)); - filenameBeingUploaded[ARRAY_UPB(filenameBeingUploaded)] = 0; - - uploadState = uploadOK; - return true; - } - - platform->Message(GENERIC_MESSAGE, "Error: Could not open file while starting upload!\n"); - return false; -} - -void ProtocolInterpreter::CancelUpload() -{ - if (uploadState == uploadOK) - { - // Do the file handling next time when Spin is called - uploadState = uploadError; - } -} - -void ProtocolInterpreter::DoFastUpload() -{ - NetworkTransaction *transaction = webserver->currentTransaction; - - const char *buffer; - size_t len; - if (transaction->ReadBuffer(buffer, len)) - { - // See if we can output a debug message - if (reprap.Debug(moduleWebserver)) - { - platform->MessageF(HOST_MESSAGE, "Writing %u bytes of upload data\n", len); - } - - // Writing data usually takes a while, so keep LwIP running while this is being done - network->Unlock(); - if (!fileBeingUploaded.Write(buffer, len)) - { - platform->Message(GENERIC_MESSAGE, "Error: Could not write upload data!\n"); - CancelUpload(); - - while (!network->Lock()); - transaction->Commit(false); - return; - } - while (!network->Lock()); - } - - if (uploadState != uploadOK || !transaction->HasMoreDataToRead()) - { - transaction->Discard(); - } -} - -bool ProtocolInterpreter::FinishUpload(uint32_t fileLength) -{ - // Flush remaining data for FSO - if (uploadState == uploadOK && !fileBeingUploaded.Flush()) - { - uploadState = uploadError; - platform->Message(GENERIC_MESSAGE, "Error: Could not flush remaining data while finishing upload!\n"); - } - - // Check the file length is as expected - if (uploadState == uploadOK && fileLength != 0 && fileBeingUploaded.Length() != fileLength) - { - uploadState = uploadError; - platform->MessageF(GENERIC_MESSAGE, "Error: Uploaded file size is different (%u vs. expected %u bytes)!\n", fileBeingUploaded.Length(), fileLength); - } - - // Close the file - if (fileBeingUploaded.IsLive()) - { - fileBeingUploaded.Close(); - } - - // Delete the file again if an error has occurred - if (uploadState == uploadError && filenameBeingUploaded[0] != 0) - { - platform->GetMassStorage()->Delete(FS_PREFIX, filenameBeingUploaded); - } - - // Clean up again - bool success = (uploadState == uploadOK); - uploadState = notUploading; - filenameBeingUploaded[0] = 0; - return success; -} - - -//******************************************************************************************** -// -// *********************** HTTP interpreter for the Webserver class ************************** -// -//******************************************************************************************** - - - -Webserver::HttpInterpreter::HttpInterpreter(Platform *p, Webserver *ws, Network *n) - : ProtocolInterpreter(p, ws, n), state(doingCommandWord), numSessions(0), clientsServed(0) -{ - gcodeReadIndex = gcodeWriteIndex = 0; - gcodeReply = new OutputStack(); - deferredRequestConnection = nullptr; - seq = 0; -} - -void Webserver::HttpInterpreter::Diagnostics(MessageType mt) -{ - platform->MessageF(mt, "HTTP sessions: %d of %d\n", numSessions, maxHttpSessions); -} - -void Webserver::HttpInterpreter::Spin() -{ - // Deal with aborted uploads - ProtocolInterpreter::Spin(); - - // Verify HTTP sessions - const uint32_t now = millis(); - for(int i = numSessions - 1; i >= 0; i--) - { - if (sessions[i].isPostUploading) - { - // Check for cancelled POST uploads - if (uploadState != uploadOK) - { - sessions[i].isPostUploading = false; - sessions[i].lastQueryTime = millis(); - } - } - else if ((now - sessions[i].lastQueryTime) > httpSessionTimeout) - { - // Check for timed out sessions - for(size_t k = i + 1; k < numSessions; k++) - { - memcpy(&sessions[k - 1], &sessions[k], sizeof(HttpSession)); - } - numSessions--; - clientsServed++; // assume the disconnected client hasn't fetched the G-Code reply yet - } - } - - // If we cannot send the G-Code reply to anyone, we may free up some run-time space by dumping it - if (numSessions == 0 || clientsServed >= numSessions) - { - while (!gcodeReply->IsEmpty()) - { - OutputBuffer::ReleaseAll(gcodeReply->Pop()); - } - clientsServed = 0; - } - -} - -// File Uploads - -bool Webserver::HttpInterpreter::DoingFastUpload() const -{ - uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP(); - uint16_t remotePort = webserver->currentTransaction->GetRemotePort(); - for(size_t i = 0; i < numSessions; i++) - { - if (sessions[i].ip == remoteIP && sessions[i].isPostUploading) - { - // There is only one session per IP address... - return (sessions[i].postPort == remotePort); - } - } - return false; -} - -void Webserver::HttpInterpreter::DoFastUpload() -{ - NetworkTransaction *transaction = webserver->currentTransaction; - - // Write some data on the SD card - const char *buffer; - size_t len; - if (transaction->ReadBuffer(buffer, len)) - { - network->Unlock(); - // Write data in sector-aligned chunks. This also means that the buffer in fatfs is only used to hold the FAT. - static const size_t writeBufLength = 2048; // use a multiple of the 512b sector size - static uint32_t writeBufStorage[writeBufLength/4]; // aligned buffer for file writes - static size_t writeBufIndex; - char* const writeBuf = (char *)writeBufStorage; - - if (uploadedBytes == 0) - { - writeBufIndex = 0; - } - - while (len != 0) - { - size_t lengthToCopy = min<size_t>(writeBufLength - writeBufIndex, len); - memcpy(writeBuf + writeBufIndex, buffer, lengthToCopy); - writeBufIndex += lengthToCopy; - uploadedBytes += lengthToCopy; - buffer += lengthToCopy; - len -= lengthToCopy; - if (writeBufIndex == writeBufLength || uploadedBytes >= postFileLength) - { - bool success = fileBeingUploaded.Write(writeBuf, writeBufIndex); - writeBufIndex = 0; - if (!success) - { - platform->Message(GENERIC_MESSAGE, "Error: Could not write upload data!\n"); - CancelUpload(); - - while (!network->Lock()); - SendJsonResponse("upload"); - return; - } - } - } - while (!network->Lock()); - } - - // See if the upload has finished - if (uploadState == uploadOK && uploadedBytes >= postFileLength) - { - // Reset POST upload state for this client - uint32_t remoteIP = transaction->GetRemoteIP(); - for(size_t i = 0; i < numSessions; i++) - { - if (sessions[i].ip == remoteIP && sessions[i].isPostUploading) - { - sessions[i].isPostUploading = false; - sessions[i].lastQueryTime = millis(); - break; - } - } - - // Grab a copy of the filename and finish this upload - char filename[FILENAME_LENGTH]; - strncpy(filename, filenameBeingUploaded, FILENAME_LENGTH); - FinishUpload(postFileLength); - - // Update the file timestamp if it was specified before - if (fileLastModified != 0) - { - (void)platform->GetMassStorage()->SetLastModifiedTime(nullptr, filename, fileLastModified); - } - - // Eventually send the JSON response - SendJsonResponse("upload"); - } - else if (uploadState != uploadOK || !transaction->HasMoreDataToRead()) - { - // We cannot read any more, discard the transaction again - transaction->Discard(); - } -} - -// Output to the client - -// Start sending a file or a JSON response. -void Webserver::HttpInterpreter::SendFile(const char* nameOfFileToSend, bool isWebFile) -{ - NetworkTransaction *transaction = webserver->currentTransaction; - FileStore *fileToSend; - bool zip = false; - - if (isWebFile) - { - if (nameOfFileToSend[0] == '/') - { - ++nameOfFileToSend; // all web files are relative to the /www folder, so remove the leading '/' - if (nameOfFileToSend[0] == 0) - { - nameOfFileToSend = INDEX_PAGE_FILE; - } - } - fileToSend = platform->GetFileStore(platform->GetWebDir(), nameOfFileToSend, false); - - // If we failed to open the file, see if we can open a file with the same name and a ".gz" extension - if (fileToSend == nullptr && !StringEndsWith(nameOfFileToSend, ".gz") && strlen(nameOfFileToSend) + 3 <= FILENAME_LENGTH) - { - char nameBuf[FILENAME_LENGTH + 1]; - strcpy(nameBuf, nameOfFileToSend); - strcat(nameBuf, ".gz"); - fileToSend = platform->GetFileStore(platform->GetWebDir(), nameBuf, false); - if (fileToSend != nullptr) - { - zip = true; - } - } - - // If we still couldn't find the file and it was an HTML file, return the 404 error page - if (fileToSend == nullptr && (StringEndsWith(nameOfFileToSend, ".html") || StringEndsWith(nameOfFileToSend, ".htm"))) - { - nameOfFileToSend = FOUR04_PAGE_FILE; - fileToSend = platform->GetFileStore(platform->GetWebDir(), nameOfFileToSend, false); - } - - if (fileToSend == nullptr) - { - RejectMessage("not found", 404); - return; - } - transaction->SetFileToWrite(fileToSend); - } - else - { - fileToSend = platform->GetFileStore(FS_PREFIX, nameOfFileToSend, false); - if (fileToSend == nullptr) - { - nameOfFileToSend = FOUR04_PAGE_FILE; - fileToSend = platform->GetFileStore(platform->GetWebDir(), nameOfFileToSend, false); - if (fileToSend == nullptr) - { - RejectMessage("not found", 404); - return; - } - } - transaction->SetFileToWrite(fileToSend); - } - - transaction->Write("HTTP/1.1 200 OK\n"); - - // Don't cache files served by rr_download - if (!isWebFile) - { - transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); - transaction->Write("Pragma: no-cache\n"); - transaction->Write("Expires: 0\n"); - } - - const char* contentType; - if (StringEndsWith(nameOfFileToSend, ".png")) - { - contentType = "image/png"; - } - else if (StringEndsWith(nameOfFileToSend, ".ico")) - { - contentType = "image/x-icon"; - } - else if (StringEndsWith(nameOfFileToSend, ".js")) - { - contentType = "application/javascript"; - } - else if (StringEndsWith(nameOfFileToSend, ".css")) - { - contentType = "text/css"; - } - else if (StringEndsWith(nameOfFileToSend, ".htm") || StringEndsWith(nameOfFileToSend, ".html")) - { - contentType = "text/html"; - } - else if (StringEndsWith(nameOfFileToSend, ".zip")) - { - contentType = "application/zip"; - zip = true; - } - else if (StringEndsWith(nameOfFileToSend, ".g") || StringEndsWith(nameOfFileToSend, ".gc") || StringEndsWith(nameOfFileToSend, ".gcode")) - { - contentType = "text/plain"; - } - else - { - contentType = "application/octet-stream"; - } - transaction->Printf("Content-Type: %s\n", contentType); - - if (zip && fileToSend != nullptr) - { - transaction->Write("Content-Encoding: gzip\n"); - transaction->Printf("Content-Length: %lu\n", fileToSend->Length()); - } - - transaction->Write("Connection: close\n\n"); - transaction->Commit(false); -} - -void Webserver::HttpInterpreter::SendGCodeReply() -{ - // Do we need to keep the G-Code reply for other clients? - bool clearReply = false; - if (!gcodeReply->IsEmpty()) - { - clientsServed++; - if (clientsServed < numSessions) - { - // Yes - make sure the Network class doesn't discard its buffers yet - // NB: This must happen here, because NetworkTransaction::Write() might already release OutputBuffers - gcodeReply->IncreaseReferences(1); - } - else - { - // No - clean up again later - clearReply = true; - } - - if (reprap.Debug(moduleWebserver)) - { - platform->MessageF(HOST_MESSAGE, "Sending G-Code reply to client %d of %d (length %u)\n", clientsServed, numSessions, gcodeReply->DataLength()); - } - } - - // Send the whole G-Code reply as plain text to the client - NetworkTransaction *transaction = webserver->currentTransaction; - transaction->Write("HTTP/1.1 200 OK\n"); - transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); - transaction->Write("Pragma: no-cache\n"); - transaction->Write("Expires: 0\n"); - transaction->Write("Content-Type: text/plain\n"); - transaction->Printf("Content-Length: %u\n", gcodeReply->DataLength()); - transaction->Write("Connection: close\n\n"); - transaction->Write(gcodeReply); - transaction->Commit(false); - - // Possibly clean up the G-code reply once again - if (clearReply) - { - gcodeReply->Clear(); - } -} - -void Webserver::HttpInterpreter::SendJsonResponse(const char* command) -{ - // Try to authorize the user automatically to retain compatibility with the old web interface - if (!IsAuthenticated() && reprap.NoPasswordSet()) - { - Authenticate(); - } - - // Update the authentication status and try handle "text/plain" requests here - if (IsAuthenticated()) - { - UpdateAuthentication(); - - if (StringEquals(command, "reply")) // rr_reply - { - SendGCodeReply(); - return; - } - - if (StringEquals(command, "configfile")) // rr_configfile [DEPRECATED] - { - const char *configPath = platform->GetMassStorage()->CombineName(platform->GetSysDir(), platform->GetConfigFile()); - char fileName[FILENAME_LENGTH]; - strncpy(fileName, configPath, FILENAME_LENGTH); - - SendFile(fileName, false); - return; - } - - if (StringEquals(command, "download") && StringEquals(qualifiers[0].key, "name")) - { - SendFile(qualifiers[0].value, false); - return; - } - } - - // Try to process a request for JSON responses - OutputBuffer *jsonResponse; - if (!OutputBuffer::Allocate(jsonResponse)) - { - // Reset the connection immediately if we cannot write any data. Should never happen - webserver->currentTransaction->GetConnection()->Terminate(); - return; - } - - bool keepOpen = false; - bool mayKeepOpen; - if (numQualKeys == 0) - { - GetJsonResponse(command, jsonResponse, "", "", 0, mayKeepOpen); - } - else - { - GetJsonResponse(command, jsonResponse, qualifiers[0].key, qualifiers[0].value, qualifiers[1].key - qualifiers[0].value - 1, mayKeepOpen); - } - - // Check special cases of deferred requests (rr_fileinfo) and rejected messages - NetworkTransaction *transaction = webserver->currentTransaction; - if (transaction->GetStatus() == deferred || transaction->GetStatus() == sending) - { - OutputBuffer::Release(jsonResponse); - return; - } - - // Send the JSON response - - if (mayKeepOpen) - { - // Check that the browser wants to persist the connection too - for (size_t i = 0; i < numHeaderKeys; ++i) - { - if (StringEquals(headers[i].key, "Connection")) - { - // Comment out the following line to disable persistent connections - keepOpen = StringEquals(headers[i].value, "keep-alive"); - break; - } - } - } - - transaction->Write("HTTP/1.1 200 OK\n"); - transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); - transaction->Write("Pragma: no-cache\n"); - transaction->Write("Expires: 0\n"); - transaction->Write("Content-Type: application/json\n"); - transaction->Printf("Content-Length: %u\n", (jsonResponse != nullptr) ? jsonResponse->Length() : 0); - transaction->Printf("Connection: %s\n\n", keepOpen ? "keep-alive" : "close"); - transaction->Write(jsonResponse); - - transaction->Commit(keepOpen); -} - -//---------------------------------------------------------------------------------------------------- - -// Input from the client - -// Get the Json response for this command. -// 'value' is null-terminated, but we also pass its length in case it contains embedded nulls, which matters when uploading files. -void Webserver::HttpInterpreter::GetJsonResponse(const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen) -{ - keepOpen = false; // assume we don't want to persist the connection - - if (StringEquals(request, "connect") && StringEquals(key, "password")) - { - if (IsAuthenticated() || reprap.CheckPassword(value)) - { - // Password OK - if (Authenticate()) - { - // See if we can update the current RTC date and time - if (numQualKeys > 1 && StringEquals(qualifiers[1].key, "time") && !platform->IsDateTimeSet()) - { - struct tm timeInfo; - memset(&timeInfo, 0, sizeof(timeInfo)); - if (strptime(qualifiers[1].value, "%Y-%m-%dT%H:%M:%S", &timeInfo) != nullptr) - { - time_t newTime = mktime(&timeInfo); - platform->SetDateTime(newTime); - } - } - - // Client has been logged in - response->printf("{\"err\":0,\"sessionTimeout\":%u,\"boardType\":\"%s\"}", httpSessionTimeout, platform->GetBoardString()); - } - else - { - // No more HTTP sessions available - response->copy("{\"err\":2}"); - } - } - else - { - // Wrong password - response->copy("{\"err\":1}"); - } - } - else if (!IsAuthenticated()) - { - RejectMessage("Not authorized", 500); - } - else if (StringEquals(request, "disconnect")) - { - response->printf("{\"err\":%d}", RemoveAuthentication() ? 0 : 1); - } - else if (StringEquals(request, "status")) - { - int type = 0; - if (StringEquals(key, "type")) - { - // New-style JSON status responses - type = atoi(value); - if (type < 1 || type > 3) - { - type = 1; - } - - OutputBuffer::Release(response); - response = reprap.GetStatusResponse(type, ResponseSource::HTTP); - } - else - { - // Deprecated - OutputBuffer::Release(response); - response = reprap.GetLegacyStatusResponse(1, 0); - } - } - else if (StringEquals(request, "gcode") && StringEquals(key, "gcode")) - { - LoadGcodeBuffer(value); - response->printf("{\"buff\":%u}", GetGCodeBufferSpace()); - } - else if (StringEquals(request, "upload")) - { - response->printf("{\"err\":%d}", (uploadedBytes == postFileLength) ? 0 : 1); - } - else if (StringEquals(request, "delete") && StringEquals(key, "name")) - { - bool ok = platform->GetMassStorage()->Delete(FS_PREFIX, value); - response->printf("{\"err\":%d}", (ok) ? 0 : 1); - } - else if (StringEquals(request, "filelist")) - { - OutputBuffer::Release(response); - response = reprap.GetFilelistResponse(value); - } - else if (StringEquals(request, "files")) - { - const char* dir = (StringEquals(key, "dir")) ? value : platform->GetGCodeDir(); - bool flagDirs = false; - if (numQualKeys >= 2) - { - if (StringEquals(qualifiers[1].key, "flagDirs")) - { - flagDirs = StringEquals(qualifiers[1].value, "1"); - } - } - OutputBuffer::Release(response); - response = reprap.GetFilesResponse(dir, flagDirs); - } - else if (StringEquals(request, "fileinfo")) - { - if (deferredRequestConnection != nullptr) - { - // Don't allow multiple deferred requests to be processed at once - webserver->currentTransaction->Defer(DeferralMode::ResetData); - } - else - { - if (StringEquals(qualifiers[0].key, "name")) - { - // Regular rr_fileinfo?name=xxx call - strncpy(filenameBeingProcessed, value, ARRAY_SIZE(filenameBeingProcessed)); - filenameBeingProcessed[ARRAY_UPB(filenameBeingProcessed)] = 0; - } - else - { - // Simple rr_fileinfo call to get info about the file being printed - filenameBeingProcessed[0] = 0; - } - - deferredRequestConnection = webserver->currentTransaction->GetConnection(); - ProcessDeferredRequest(); - } - } - else if (StringEquals(request, "move")) - { - if (numQualKeys >= 2) - { - if (StringEquals(key, "old") && StringEquals(qualifiers[1].key, "new")) - { - response->printf("{\"err\":%d}", platform->GetMassStorage()->Rename(value, qualifiers[1].value) ? 0 : 1); - } - else - { - response->printf("{\"err\":1}"); - } - } - else - { - response->printf("{\"err\":1}"); - } - } - else if (StringEquals(request, "mkdir") && StringEquals(key, "dir")) - { - bool ok = (platform->GetMassStorage()->MakeDirectory(value)); - response->printf("{\"err\":%d}", (ok) ? 0 : 1); - } - else if (StringEquals(request, "config")) - { - OutputBuffer::Release(response); - response = reprap.GetConfigResponse(); - } - else - { - RejectMessage("Unknown request", 500); - } -} - -void Webserver::HttpInterpreter::ResetState() -{ - clientPointer = 0; - state = doingCommandWord; - numCommandWords = 0; - numQualKeys = 0; - numHeaderKeys = 0; - commandWords[0] = clientMessage; -} - -void Webserver::HttpInterpreter::NoMoreDataAvailable() -{ - RejectMessage("Incomplete or too long HTTP request", 500); -} - -// May be called from ISR! -void Webserver::HttpInterpreter::ConnectionLost(const ConnectionState *cs) -{ - // Make sure deferred requests are cancelled - if (deferredRequestConnection == cs) - { - reprap.GetPrintMonitor()->StopParsing(filenameBeingProcessed); - deferredRequestConnection = nullptr; - } - - // If we couldn't read an entire request from a connection, reset our state here again - if (webserver->readingConnection == cs) - { - ResetState(); - } - - // Deal with aborted POST uploads. Note that we also check the remote port here, - // because the client *might* have two instances of the web interface running. - if (uploadState == uploadOK) - { - const uint32_t remoteIP = cs->GetRemoteIP(); - const uint16_t remotePort = cs->GetRemotePort(); - for(size_t i = 0; i < numSessions; i++) - { - if (sessions[i].ip == remoteIP && sessions[i].isPostUploading && sessions[i].postPort == remotePort) - { - if (reprap.Debug(moduleWebserver)) - { - platform->MessageF(HOST_MESSAGE, "POST upload for '%s' has been cancelled!\n", filenameBeingUploaded); - } - sessions[i].isPostUploading = false; - CancelUpload(); - break; - } - } - } -} - -bool Webserver::HttpInterpreter::CanParseData() -{ - // We want to send a response, but we need memory for that. Check if we have to truncate the G-Code reply - while (OutputBuffer::GetBytesLeft(nullptr) < minHttpResponseSize) - { - if (gcodeReply->IsEmpty()) - { - // We cannot truncate any G-Code reply and don't have enough free space, try again later - return false; - } - - if (OutputBuffer::Truncate(gcodeReply->GetFirstItem(), minHttpResponseSize) == 0) - { - // Truncating didn't work out, but see if we can free up a few more bytes by releasing the first reply item - OutputBuffer::ReleaseAll(gcodeReply->Pop()); - } - } - - // Are we still processing a deferred request? - if (deferredRequestConnection == webserver->currentTransaction->GetConnection()) - { - if (deferredRequestConnection->IsConnected()) - { - // Process more of this request. If it doesn't finish this time, it will be appended to the list - // of ready transactions again, which will ensure it can be processed later again - ProcessDeferredRequest(); - } - else - { - // Don't bother with this request if the connection has been closed. - // We expect a "disconnected" transaction to report this later, so don't clean up anything here - webserver->currentTransaction->Discard(); - } - return false; - } - - return true; -} - -// Process a character from the client -// Rewritten as a state machine by dc42 to increase capability and speed, and reduce RAM requirement. -// On entry: -// There is space for at least 1 character in clientMessage. -// On return: -// If we return false: -// We want more characters. There is space for at least 1 character in clientMessage. -// If we return true: -// We have processed the message and sent the reply. No more characters may be read from this message. -// Whenever this calls ProcessMessage: -// The first line has been split up into words. Variables numCommandWords and commandWords give the number of words we found -// and the pointers to each word. The second word is treated specially. It is assumed to be a filename followed by an optional -// qualifier comprising key/value pairs. Both may include %xx escapes, and the qualifier may include + to mean space. We store -// a pointer to the filename without qualifier in commandWords[1]. We store the qualifier key/value pointers in array 'qualifiers' -// and the number of them in numQualKeys. -// The remaining lines have been parsed as header name/value pairs. Pointers to them are stored in array 'headers' and the number -// of them in numHeaders. -// If one of our arrays is about to overflow, or the message is not in a format we expect, then we call RejectMessage with an -// appropriate error code and string. -bool Webserver::HttpInterpreter::CharFromClient(char c) -{ - switch(state) - { - case doingCommandWord: - switch(c) - { - case '\n': - clientMessage[clientPointer++] = 0; - ++numCommandWords; - numHeaderKeys = 0; - headers[0].key = clientMessage + clientPointer; - state = doingHeaderKey; - break; - case '\r': - break; - case ' ': - case '\t': - clientMessage[clientPointer++] = 0; - if (numCommandWords < maxCommandWords) - { - ++numCommandWords; - commandWords[numCommandWords] = clientMessage + clientPointer; - if (numCommandWords == 1) - { - state = doingFilename; - } - } - else - { - return RejectMessage("too many command words"); - } - break; - default: - clientMessage[clientPointer++] = c; - break; - } - break; - - case doingFilename: - switch(c) - { - case '\n': - clientMessage[clientPointer++] = 0; - ++numCommandWords; - numQualKeys = 0; - numHeaderKeys = 0; - headers[0].key = clientMessage + clientPointer; - state = doingHeaderKey; - break; - case '?': - clientMessage[clientPointer++] = 0; - ++numCommandWords; - numQualKeys = 0; - qualifiers[0].key = clientMessage + clientPointer; - state = doingQualifierKey; - break; - case '%': - state = doingFilenameEsc1; - break; - case '\r': - break; - case ' ': - case '\t': - clientMessage[clientPointer++] = 0; - if (numCommandWords < maxCommandWords) - { - ++numCommandWords; - commandWords[numCommandWords] = clientMessage + clientPointer; - state = doingCommandWord; - } - else - { - return RejectMessage("too many command words"); - } - break; - default: - clientMessage[clientPointer++] = c; - break; - } - break; - - case doingQualifierKey: - switch(c) - { - case '=': - clientMessage[clientPointer++] = 0; - qualifiers[numQualKeys].value = clientMessage + clientPointer; - ++numQualKeys; - state = doingQualifierValue; - break; - case '\n': // key with no value - case ' ': - case '\t': - case '\r': - case '%': // none of our keys needs escaping, so treat an escape within a key as an error - case '&': // key with no value - return RejectMessage("bad qualifier key"); - default: - clientMessage[clientPointer++] = c; - break; - } - break; - - case doingQualifierValue: - switch(c) - { - case '\n': - clientMessage[clientPointer++] = 0; - qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null - numHeaderKeys = 0; - headers[0].key = clientMessage + clientPointer; - state = doingHeaderKey; - break; - case ' ': - case '\t': - clientMessage[clientPointer++] = 0; - qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null - commandWords[numCommandWords] = clientMessage + clientPointer; - state = doingCommandWord; - break; - case '\r': - break; - case '%': - state = doingQualifierValueEsc1; - break; - case '&': - // Another variable is coming - clientMessage[clientPointer++] = 0; - qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null - if (numQualKeys < maxQualKeys) - { - state = doingQualifierKey; - } - else - { - return RejectMessage("too many keys in qualifier"); - } - break; - case '+': - clientMessage[clientPointer++] = ' '; - break; - default: - clientMessage[clientPointer++] = c; - break; - } - break; - - case doingFilenameEsc1: - case doingQualifierValueEsc1: - if (c >= '0' && c <= '9') - { - decodeChar = (c - '0') << 4; - state = (HttpState)(state + 1); - } - else if (c >= 'A' && c <= 'F') - { - decodeChar = (c - ('A' - 10)) << 4; - state = (HttpState)(state + 1); - } - else - { - return RejectMessage(badEscapeResponse); - } - break; - - case doingFilenameEsc2: - case doingQualifierValueEsc2: - if (c >= '0' && c <= '9') - { - clientMessage[clientPointer++] = decodeChar | (c - '0'); - state = (HttpState)(state - 2); - } - else if (c >= 'A' && c <= 'F') - { - clientMessage[clientPointer++] = decodeChar | (c - ('A' - 10)); - state = (HttpState)(state - 2); - } - else - { - return RejectMessage(badEscapeResponse); - } - break; - - case doingHeaderKey: - switch(c) - { - case '\n': - if (clientMessage + clientPointer == headers[numHeaderKeys].key) // if the key hasn't started yet, then this is the blank line at the end - { - if (ProcessMessage()) - { - return true; - } - } - else - { - return RejectMessage("unexpected newline"); - } - break; - case '\r': - break; - case ':': - if (numHeaderKeys == maxHeaders - 1) - { - return RejectMessage("too many header key-value pairs"); - } - clientMessage[clientPointer++] = 0; - headers[numHeaderKeys].value = clientMessage + clientPointer; - ++numHeaderKeys; - state = expectingHeaderValue; - break; - default: - clientMessage[clientPointer++] = c; - break; - } - break; - - case expectingHeaderValue: - if (c == ' ' || c == '\t') - { - break; // ignore spaces between header key and value - } - state = doingHeaderValue; - // no break - - case doingHeaderValue: - if (c == '\n') - { - state = doingHeaderContinuation; - } - else if (c != '\r') - { - clientMessage[clientPointer++] = c; - } - break; - - case doingHeaderContinuation: - switch(c) - { - case ' ': - case '\t': - // It's a continuation of the previous value - clientMessage[clientPointer++] = c; - state = doingHeaderValue; - break; - case '\n': - // It's the blank line - clientMessage[clientPointer] = 0; - if (ProcessMessage()) - { - return true; - } - // no break - case '\r': - break; - default: - // It's a new key - if (clientPointer + 3 <= ARRAY_SIZE(clientMessage)) - { - clientMessage[clientPointer++] = 0; - headers[numHeaderKeys].key = clientMessage + clientPointer; - clientMessage[clientPointer++] = c; - state = doingHeaderKey; - } - else - { - return RejectMessage(overflowResponse); - } - break; - } - break; - - default: - break; - } - - if (clientPointer == ARRAY_SIZE(clientMessage)) - { - return RejectMessage(overflowResponse); - } - return false; -} - -// Process the message received so far. We have reached the end of the headers. -// Return true if the message is complete, false if we want to continue receiving data (i.e. postdata) -bool Webserver::HttpInterpreter::ProcessMessage() -{ - if (reprap.Debug(moduleWebserver)) - { - platform->MessageF(HOST_MESSAGE, "HTTP req, command words {", numCommandWords); - for (size_t i = 0; i < numCommandWords; ++i) - { - platform->MessageF(HOST_MESSAGE, " %s", commandWords[i]); - } - platform->Message(HOST_MESSAGE, " }, parameters {"); - - for (size_t i = 0; i < numQualKeys; ++i) - { - platform->MessageF(HOST_MESSAGE, " %s=%s", qualifiers[i].key, qualifiers[i].value); - } - platform->Message(HOST_MESSAGE, " }\n"); - } - - if (numCommandWords < 2) - { - return RejectMessage("too few command words"); - } - - if (StringEquals(commandWords[0], "GET")) - { - if (StringStartsWith(commandWords[1], KO_START)) - { - SendJsonResponse(commandWords[1] + KO_FIRST); - } - else if (commandWords[1][0] == '/' && StringStartsWith(commandWords[1] + 1, KO_START)) - { - SendJsonResponse(commandWords[1] + 1 + KO_FIRST); - } - else - { - SendFile(commandWords[1], true); - } - - ResetState(); - return true; - } - else if (IsAuthenticated() && StringEquals(commandWords[0], "POST")) - { - bool isUploadRequest = (StringEquals(commandWords[1], KO_START "upload")); - isUploadRequest |= (commandWords[1][0] == '/' && StringEquals(commandWords[1] + 1, KO_START "upload")); - if (isUploadRequest) - { - if (numQualKeys > 0 && StringEquals(qualifiers[0].key, "name")) - { - // We cannot upload more than one file at once - if (IsUploading()) - { - return RejectMessage("cannot upload more than one file at once"); - } - - // See how many bytes we expect to read - bool contentLengthFound = false; - for(size_t i=0; i<numHeaderKeys; i++) - { - if (StringEquals(headers[i].key, "Content-Length")) - { - postFileLength = atoi(headers[i].value); - contentLengthFound = true; - break; - } - } - - // Start POST file upload - if (!contentLengthFound) - { - return RejectMessage("invalid POST upload request"); - } - - // Start a new file upload - FileStore *file = platform->GetFileStore(FS_PREFIX, qualifiers[0].value, true); - if (!StartUpload(file, qualifiers[0].value)) - { - return RejectMessage("could not start file upload"); - } - - // Try to get the last modified file date and time - if (numQualKeys > 1 && StringEquals(qualifiers[1].key, "time")) - { - struct tm timeInfo; - memset(&timeInfo, 0, sizeof(timeInfo)); - if (strptime(qualifiers[1].value, "%Y-%m-%dT%H:%M:%S", &timeInfo) != nullptr) - { - fileLastModified = mktime(&timeInfo); - } - else - { - fileLastModified = 0; - } - } - else - { - fileLastModified = 0; - } - - if (reprap.Debug(moduleWebserver)) - { - platform->MessageF(HOST_MESSAGE, "Start uploading file %s length %lu\n", qualifiers[0].value, postFileLength); - } - uploadedBytes = 0; - - // Keep track of the connection that is now uploading - uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP(); - uint16_t remotePort = webserver->currentTransaction->GetRemotePort(); - for(size_t i = 0; i < numSessions; i++) - { - if (sessions[i].ip == remoteIP) - { - sessions[i].postPort = remotePort; - sessions[i].isPostUploading = true; - break; - } - } - - ResetState(); - return true; - } - } - return RejectMessage("only rr_upload is supported for POST requests"); - } - else - { - return RejectMessage("Unknown message type or not authenticated"); - } -} - -// Reject the current message. Always returns true to indicate that we should stop reading the message. -bool Webserver::HttpInterpreter::RejectMessage(const char* response, unsigned int code) -{ - platform->MessageF(HOST_MESSAGE, "Webserver: rejecting message with: %s\n", response); - - NetworkTransaction *transaction = webserver->currentTransaction; - transaction->Printf("HTTP/1.1 %u %s\nConnection: close\n\n", code, response); - transaction->Commit(false); - - ResetState(); - - return true; -} - -// Authenticate current IP and return true on success -bool Webserver::HttpInterpreter::Authenticate() -{ - if (numSessions < maxHttpSessions) - { - sessions[numSessions].ip = webserver->currentTransaction->GetRemoteIP(); - sessions[numSessions].lastQueryTime = millis(); - sessions[numSessions].isPostUploading = false; - numSessions++; - return true; - } - return false; -} - -bool Webserver::HttpInterpreter::IsAuthenticated() const -{ - const uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP(); - for(size_t i = 0; i < numSessions; i++) - { - if (sessions[i].ip == remoteIP) - { - return true; - } - } - return false; -} - -void Webserver::HttpInterpreter::UpdateAuthentication() -{ - const uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP(); - for(size_t i = 0; i < numSessions; i++) - { - if (sessions[i].ip == remoteIP) - { - sessions[i].lastQueryTime = millis(); - break; - } - } -} - -bool Webserver::HttpInterpreter::RemoveAuthentication() -{ - const uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP(); - for(int i=(int)numSessions - 1; i>=0; i--) - { - if (sessions[i].ip == remoteIP) - { - if (sessions[i].isPostUploading) - { - // Don't allow sessions with active POST uploads to be removed - return false; - } - - for (size_t k = i + 1; k < numSessions; ++k) - { - memcpy(&sessions[k - 1], &sessions[k], sizeof(HttpSession)); - } - numSessions--; - return true; - } - } - return false; -} - -// Process a received string of gcodes -void Webserver::HttpInterpreter::LoadGcodeBuffer(const char* gc) -{ - char gcodeTempBuf[GCODE_LENGTH]; - uint16_t gtp = 0; - bool inComment = false; - for (;;) - { - char c = *gc++; - if (c == 0) - { - gcodeTempBuf[gtp] = 0; - ProcessGcode(gcodeTempBuf); - return; - } - - if (c == '\n') - { - gcodeTempBuf[gtp] = 0; - ProcessGcode(gcodeTempBuf); - gtp = 0; - inComment = false; - } - else - { - if (c == ';') - { - inComment = true; - } - - if (gtp == ARRAY_UPB(gcodeTempBuf)) - { - // gcode is too long, we haven't room for another character and a null - if (c != ' ' && !inComment) - { - platform->Message(HOST_MESSAGE, "Error: GCode local buffer overflow in HTTP webserver.\n"); - return; - } - // else we're either in a comment or the current character is a space. - // If we're in a comment, we'll silently truncate it. - // If the current character is a space, we'll wait until we see a non-comment character before reporting an error, - // in case the next character is end-of-line or the start of a comment. - } - else - { - gcodeTempBuf[gtp++] = c; - } - } - } -} - -// Process a null-terminated gcode -// We intercept one M Codes so we can deal with emergencies. That -// way things don't get out of sync, and - as a file name can contain -// a valid G code (!) - confusion is avoided. -void Webserver::HttpInterpreter::ProcessGcode(const char* gc) -{ - if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop - { - reprap.EmergencyStop(); - gcodeReadIndex = gcodeWriteIndex; // clear the buffer - reprap.GetGCodes()->Reset(); - } - else - { - StoreGcodeData(gc, strlen(gc) + 1); - } -} - -// Process a received string of gcodes -void Webserver::HttpInterpreter::StoreGcodeData(const char* data, uint16_t len) -{ - if (len > GetGCodeBufferSpace()) - { - platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in HTTP Webserver!\n"); - } - else - { - uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; - if (len <= remaining) - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, len); - } - else - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); - memcpy(gcodeBuffer, data + remaining, len - remaining); - } - gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; - } -} - -// Feeding G Codes to the GCodes class -char Webserver::HttpInterpreter::ReadGCode() -{ - char c; - if (gcodeReadIndex == gcodeWriteIndex) - { - c = 0; - } - else - { - c = gcodeBuffer[gcodeReadIndex]; - gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; - } - return c; -} - -// Handle a G Code reply from the GCodes class -void Webserver::HttpInterpreter::HandleGCodeReply(OutputBuffer *reply) -{ - if (reply != nullptr) - { - if (numSessions > 0) - { - // FIXME: This might cause G-code responses to be sent twice to fast HTTP clients, but - // I (chrishamm) cannot think of a nicer way to deal with slow clients at the moment... - gcodeReply->Push(reply); - clientsServed = 0; - seq++; - } - else - { - // Don't use buffers that may never get released... - OutputBuffer::ReleaseAll(reply); - } - } -} - -void Webserver::HttpInterpreter::HandleGCodeReply(const char *reply) -{ - if (numSessions > 0) - { - OutputBuffer *buffer = gcodeReply->GetLastItem(); - if (buffer == nullptr || buffer->IsReferenced()) - { - if (!OutputBuffer::Allocate(buffer)) - { - // No more space available, stop here - return; - } - gcodeReply->Push(buffer); - } - - buffer->cat(reply); - clientsServed = 0; - seq++; - } -} - -// Called to process a deferred request and takes care of the current Webserver transaction -void Webserver::HttpInterpreter::ProcessDeferredRequest() -{ - OutputBuffer *jsonResponse = nullptr; - const ConnectionState *lastDeferredConnection = deferredRequestConnection; - - // At the moment only file info requests are deferred. - // Parsing the file may take a while, so keep LwIP running while we're waiting - network->Unlock(); - bool gotFileInfo = reprap.GetPrintMonitor()->GetFileInfoResponse(filenameBeingProcessed, jsonResponse); - while (!network->Lock()); - - // Because LwIP was unlocked before, there is a chance that the ConnectionLost() call has already - // stopped the file parsing. Check this special case here - if (lastDeferredConnection == deferredRequestConnection) - { - NetworkTransaction *transaction = webserver->currentTransaction; - if (gotFileInfo) - { - deferredRequestConnection = nullptr; - - // Got it - send the response now - transaction->Write("HTTP/1.1 200 OK\n"); - transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n"); - transaction->Write("Pragma: no-cache\n"); - transaction->Write("Expires: 0\n"); - transaction->Write("Content-Type: application/json\n"); - transaction->Printf("Content-Length: %u\n", (jsonResponse != nullptr) ? jsonResponse->Length() : 0); - transaction->Printf("Connection: close\n\n"); - transaction->Write(jsonResponse); - - transaction->Commit(false); - } - else - { - // File hasn't been fully parsed yet, try again later - transaction->Defer(DeferralMode::DiscardData); - } - } - else - { - // Clean up again if we cannot send the response at all - OutputBuffer::ReleaseAll(jsonResponse); - } -} - -//******************************************************************************************** -// -//************************* FTP interpreter for the Webserver class ************************** -// -//******************************************************************************************** - -Webserver::FtpInterpreter::FtpInterpreter(Platform *p, Webserver *ws, Network *n) - : ProtocolInterpreter(p, ws, n), state(authenticating), clientPointer(0) -{ - connectedClients = 0; - strcpy(currentDir, "/"); -} - -void Webserver::FtpInterpreter::Diagnostics(MessageType mt) -{ - platform->MessageF(mt, "FTP connections: %d, state %d\n", connectedClients, state); -} - -void Webserver::FtpInterpreter::ConnectionEstablished() -{ - connectedClients++; - if (reprap.Debug(moduleWebserver)) - { - platform->Message(HOST_MESSAGE, "Webserver: FTP connection established!\n"); - } - - // Is this a new connection on the data port? - NetworkTransaction *transaction = webserver->currentTransaction; - if (transaction->GetLocalPort() != FTP_PORT) - { - if (state == waitingForPasvPort) - { - // Yes - save it for the main request - network->SaveDataConnection(); - state = pasvPortConnected; - transaction->Discard(); - } - else - { - // Should never get here... - transaction->Commit(false); - } - return; - } - - // A client is trying to connect to the main FTP port - switch (state) - { - case idle: - case authenticated: // added by DC because without it, we can't transfer any files with FileZilla - // We can safely deal with one connection on the main FTP port - state = authenticating; - SendReply(220, "RepRapFirmware FTP server", true); - break; - - default: - // But don't allow multiple ones, this could mess things up - SendReply(421, "Only one client can be connected at a time.", false); - return; - } -} - -// May be called from ISR! -void Webserver::FtpInterpreter::ConnectionLost(const ConnectionState *cs) -{ - connectedClients--; - - if (cs->GetLocalPort() != FTP_PORT) - { - // Did everything work out? Usually this is only called for uploads - if (network->AcquireFTPTransaction()) - { - webserver->currentTransaction = network->GetTransaction(); - if (state == doingPasvIO) - { - if (uploadState != uploadError && !cs->IsTerminated()) - { - SendReply(226, "Transfer complete."); - FinishUpload(0); - } - else - { - SendReply(526, "Transfer failed!"); - } - } - else - { - SendReply(550, "Lost data connection!"); - } - } - - // Close the data port and reset our state again - network->CloseDataPort(); - CancelUpload(); - state = authenticated; - } - - if (connectedClients == 0) - { - // Last one gone now... - ResetState(); - } -} - -bool Webserver::FtpInterpreter::CharFromClient(char c) -{ - if (clientPointer == ARRAY_UPB(clientMessage)) - { - clientPointer = 0; - platform->Message(HOST_MESSAGE, "Webserver: Buffer overflow in FTP server!\n"); - return true; - } - - switch (c) - { - case 0: - break; - - case '\r': - case '\n': - clientMessage[clientPointer++] = 0; - - if (reprap.Debug(moduleWebserver)) - { - platform->MessageF(HOST_MESSAGE, "FtpInterpreter::ProcessLine called with state %d:\n%s\n", state, clientMessage); - } - - if (clientPointer > 1) // only process a new line if we actually received data - { - ProcessLine(); - clientPointer = 0; - return true; - } - - if (reprap.Debug(moduleWebserver)) - { - platform->Message(HOST_MESSAGE, "FtpInterpreter::ProcessLine call finished.\n"); - } - - clientPointer = 0; - break; - - default: - clientMessage[clientPointer++] = c; - break; - } - - return false; -} - -void Webserver::FtpInterpreter::ResetState() -{ - clientPointer = 0; - strcpy(currentDir, "/"); - - network->CloseDataPort(); - CancelUpload(); - - state = idle; -} - -bool Webserver::FtpInterpreter::DoingFastUpload() const -{ - return (IsUploading() && webserver->currentTransaction->GetLocalPort() == network->GetDataPort()); -} - -// return true if an error has occurred, false otherwise -void Webserver::FtpInterpreter::ProcessLine() -{ - switch (state) - { - case idle: - case authenticating: - // don't check the user name - if (StringStartsWith(clientMessage, "USER")) - { - SendReply(331, "Please specify the password."); - } - // but check the password - else if (StringStartsWith(clientMessage, "PASS")) - { - char pass[PASSWORD_LENGTH]; - int pass_length = 0; - bool reading_pass = false; - for(size_t i = 4; i < clientPointer && i < PASSWORD_LENGTH + 3; i++) - { - reading_pass |= (clientMessage[i] != ' ' && clientMessage[i] != '\t'); - if (reading_pass) - { - pass[pass_length++] = clientMessage[i]; - } - } - pass[pass_length] = 0; - - if (reprap.CheckPassword(pass)) - { - state = authenticated; - SendReply(230, "Login successful."); - } - else - { - SendReply(530, "Login incorrect.", false); - } - } - // if it's different, send response 500 to indicate we don't know the code (might be AUTH or so) - else - { - SendReply(500, "Unknown login command."); - } - - break; - - case authenticated: - // get system type - if (StringEquals(clientMessage, "SYST")) - { - SendReply(215, "UNIX Type: L8"); - } - // get features - else if (StringEquals(clientMessage, "FEAT")) - { - SendFeatures(); - } - // get current dir - else if (StringEquals(clientMessage, "PWD")) - { - NetworkTransaction *transaction = webserver->currentTransaction; - transaction->Printf("257 \"%s\"\r\n", currentDir); - transaction->Commit(true); - } - // set current dir - else if (StringStartsWith(clientMessage, "CWD")) - { - ReadFilename(3); - ChangeDirectory(filename); - } - // change to parent of current directory - else if (StringEquals(clientMessage, "CDUP")) - { - ChangeDirectory(".."); - } - // switch transfer mode (sends response, but doesn't have any effects) - else if (StringStartsWith(clientMessage, "TYPE")) - { - for(size_t i = 4; i < clientPointer; i++) - { - if (clientMessage[i] == 'I') - { - SendReply(200, "Switching to Binary mode."); - return; - } - - if (clientMessage[i] == 'A') - { - SendReply(200, "Switching to ASCII mode."); - return; - } - } - - SendReply(500, "Unknown command."); - } - // enter passive mode mode - else if (StringEquals(clientMessage, "PASV")) - { - /* get local IP address */ - const uint8_t * const ip_address = network->GetIPAddress(); - - /* open random port > 1023 */ - //rand(); // TRNG doesn't require this - uint16_t pasv_port = random(1024, 65535); - network->OpenDataPort(pasv_port); - portOpenTime = millis(); - state = waitingForPasvPort; - - /* send FTP response */ - NetworkTransaction *transaction = webserver->currentTransaction; - transaction->Printf("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n", - ip_address[0], ip_address[1], ip_address[2], ip_address[3], - pasv_port / 256, pasv_port % 256); - transaction->Commit(true); - } - // PASV commands are not supported in this state - else if (StringEquals(clientMessage, "LIST") || StringStartsWith(clientMessage, "RETR") || StringStartsWith(clientMessage, "STOR")) - { - SendReply(425, "Use PASV first."); - } - // delete file - else if (StringStartsWith(clientMessage, "DELE")) - { - ReadFilename(4); - if (platform->GetMassStorage()->Delete(currentDir, filename)) - { - SendReply(250, "Delete operation successful."); - } - else - { - SendReply(550, "Delete operation failed."); - } - } - // delete directory - else if (StringStartsWith(clientMessage, "RMD")) - { - ReadFilename(3); - if (platform->GetMassStorage()->Delete(currentDir, filename)) - { - SendReply(250, "Remove directory operation successful."); - } - else - { - SendReply(550, "Remove directory operation failed."); - } - } - // make new directory - else if (StringStartsWith(clientMessage, "MKD")) - { - ReadFilename(3); - const char *location = (filename[0] == '/') - ? filename - : platform->GetMassStorage()->CombineName(currentDir, filename); - - if (platform->GetMassStorage()->MakeDirectory(location)) - { - NetworkTransaction *transaction = webserver->currentTransaction; - transaction->Printf("257 \"%s\" created\r\n", location); - transaction->Commit(true); - } - else - { - SendReply(550, "Create directory operation failed."); - } - } - // rename file or directory - else if (StringStartsWith(clientMessage, "RNFR")) - { - ReadFilename(4); - if (filename[0] != '/') - { - const char *temp = platform->GetMassStorage()->CombineName(currentDir, filename); - strncpy(filename, temp, FILENAME_LENGTH); - filename[FILENAME_LENGTH - 1] = 0; - } - - if (platform->GetMassStorage()->FileExists(filename)) - { - SendReply(350, "Ready to RNTO."); - } - else - { - SendReply(550, "Invalid file or directory."); - } - } - else if (StringStartsWith(clientMessage, "RNTO")) - { - // Copy origin path to temp oldFilename and read new path - char oldFilename[FILENAME_LENGTH]; - strncpy(oldFilename, filename, FILENAME_LENGTH); - oldFilename[FILENAME_LENGTH - 1] = 0; - ReadFilename(4); - - const char *newFilename = platform->GetMassStorage()->CombineName(currentDir, filename); - if (platform->GetMassStorage()->Rename(oldFilename, newFilename)) - { - SendReply(250, "Rename successful."); - } - else - { - SendReply(500, "Could not rename file or directory."); - } - } - // no op - else if (StringEquals(clientMessage, "NOOP")) - { - SendReply(200, "NOOP okay."); - } - // end connection - else if (StringEquals(clientMessage, "QUIT")) - { - SendReply(221, "Goodbye.", false); - ResetState(); - } - // unknown - else - { - SendReply(500, "Unknown command."); - } - - break; - - case waitingForPasvPort: - if (millis() - portOpenTime > ftpPasvPortTimeout) - { - SendReply(425, "Failed to establish connection."); - - network->CloseDataPort(); - state = authenticated; - } - else - { - webserver->currentTransaction->Defer(DeferralMode::ResetData); - } - - break; - - case pasvPortConnected: - // save current connection state so we can send '226 Transfer complete.' when ConnectionLost() is called - network->SaveFTPConnection(); - - // list directory entries - if (StringEquals(clientMessage, "LIST")) - { - if (network->AcquireDataTransaction()) - { - // send announcement via ftp main port - SendReply(150, "Here comes the directory listing."); - - // send directory listing via data port - NetworkTransaction *dataTransaction = network->GetTransaction(); - - FileInfo fileInfo; - if (platform->GetMassStorage()->FindFirst(currentDir, fileInfo)) - { - do { - // Example for a typical UNIX-like file list: - // "drwxr-xr-x 2 ftp ftp 0 Apr 11 2013 bin\r\n" - const char dirChar = (fileInfo.isDirectory) ? 'd' : '-'; - const struct tm * const timeInfo = gmtime(&fileInfo.lastModified); - dataTransaction->Printf("%crw-rw-rw- 1 ftp ftp %13lu %s %02d %04d %s\r\n", - dirChar, fileInfo.size, platform->GetMassStorage()->GetMonthName(timeInfo->tm_mon + 1), - timeInfo->tm_mday, timeInfo->tm_year + 1900, fileInfo.fileName); - } while (platform->GetMassStorage()->FindNext(fileInfo)); - } - - dataTransaction->Commit(false); - state = doingPasvIO; - } - else - { - SendReply(500, "Unknown error."); - network->CloseDataPort(); - state = authenticated; - } - } - // upload a file - else if (StringStartsWith(clientMessage, "STOR")) - { - ReadFilename(4); - - FileStore *file = platform->GetFileStore(currentDir, filename, true); - if (StartUpload(file, filename)) - { - SendReply(150, "OK to send data."); - state = doingPasvIO; - } - else - { - SendReply(550, "Failed to open file."); - network->CloseDataPort(); - state = authenticated; - } - } - // download a file - else if (StringStartsWith(clientMessage, "RETR")) - { - ReadFilename(4); - - FileStore *file = platform->GetFileStore(currentDir, filename, false); - if (file == nullptr) - { - SendReply(550, "Failed to open file."); - } - else - { - if (network->AcquireDataTransaction()) - { - // send announcement via main ftp port - NetworkTransaction *transaction = webserver->currentTransaction; - transaction->Printf("150 Opening data connection for %s (%lu bytes).\r\n", filename, file->Length()); - transaction->Commit(true); - - // send the file via data port - NetworkTransaction *dataTransaction = network->GetTransaction(); - dataTransaction->SetFileToWrite(file); - dataTransaction->Commit(false); - state = doingPasvIO; - } - else - { - file->Close(); - SendReply(500, "Unknown error."); - network->CloseDataPort(); - state = authenticated; - } - } - } - // unknown command - else - { - SendReply(500, "Unknown command."); - network->CloseDataPort(); - state = authenticated; - } - - break; - - case doingPasvIO: - // abort current transfer - if (StringEquals(clientMessage, "ABOR")) - { - if (IsUploading()) - { - CancelUpload(); - SendReply(226, "ABOR successful."); - } - else - { - network->CloseDataPort(); - SendReply(226, "ABOR successful."); - } - } - // unknown command - else - { - SendReply(500, "Unknown command."); - network->CloseDataPort(); - state = authenticated; - } - - break; - } -} - -void Webserver::FtpInterpreter::SendReply(int code, const char *message, bool keepConnection) -{ - NetworkTransaction *transaction = webserver->currentTransaction; - transaction->Printf("%d %s\r\n", code, message); - transaction->Commit(keepConnection); -} - -void Webserver::FtpInterpreter::SendFeatures() -{ - NetworkTransaction *transaction = webserver->currentTransaction; - transaction->Write("211-Features:\r\n"); - transaction->Write("PASV\r\n"); // support PASV mode - transaction->Write("211 End\r\n"); - transaction->Commit(true); -} - -void Webserver::FtpInterpreter::ReadFilename(uint16_t start) -{ - int filenameLength = 0; - bool readingPath = false; - for(int i = start; i < (int)clientPointer && filenameLength < (int)(FILENAME_LENGTH - 1); i++) - { - switch (clientMessage[i]) - { - // ignore quotes - case '"': - case '\'': - break; - - // skip whitespaces unless the actual filename is being read - case ' ': - case '\t': - if (readingPath) - { - filename[filenameLength++] = clientMessage[i]; - } - break; - - // read path name - default: - readingPath = true; - filename[filenameLength++] = clientMessage[i]; - break; - } - } - filename[filenameLength] = 0; -} - -void Webserver::FtpInterpreter::ChangeDirectory(const char *newDirectory) -{ - char combinedPath[FILENAME_LENGTH]; - - if (newDirectory[0] != 0) - { - /* Prepare the new directory path */ - if (newDirectory[0] == '/') // absolute path - { - strncpy(combinedPath, newDirectory, FILENAME_LENGTH); - combinedPath[FILENAME_LENGTH - 1] = 0; - } - else // relative path - { - if (StringEquals(newDirectory, "..")) // go up - { - if (StringEquals(currentDir, "/")) - { - // we're already at the root, so we can't go up any more - SendReply(550, "Failed to change directory."); - return; - } - else - { - strncpy(combinedPath, currentDir, FILENAME_LENGTH); - for(int i=strlen(combinedPath) -2; i>=0; i--) - { - if (combinedPath[i] == '/') - { - combinedPath[i +1] = 0; - break; - } - } - } - } - else // go to child directory - { - strncpy(combinedPath, currentDir, FILENAME_LENGTH); - if (strlen(currentDir) > 1) - { - strncat(combinedPath, "/", FILENAME_LENGTH - strlen(combinedPath) - 1); - } - strncat(combinedPath, newDirectory, FILENAME_LENGTH - strlen(combinedPath) - 1); - } - } - - /* Make sure the new path does not end with a '/', because FatFs won't see the directory otherwise */ - if (StringEndsWith(combinedPath, "/") && strlen(combinedPath) > 1) - { - combinedPath[strlen(combinedPath) -1] = 0; - } - - /* Verify path and change it */ - if (platform->GetMassStorage()->DirectoryExists(combinedPath)) - { - strncpy(currentDir, combinedPath, FILENAME_LENGTH); - SendReply(250, "Directory successfully changed."); - } - else - { - SendReply(550, "Failed to change directory."); - } - } - else - { - SendReply(550, "Failed to change directory."); - } -} - - -//******************************************************************************************** -// -//*********************** Telnet interpreter for the Webserver class ************************* -// -//******************************************************************************************** - -Webserver::TelnetInterpreter::TelnetInterpreter(Platform *p, Webserver *ws, Network *n) - : ProtocolInterpreter(p, ws, n), connectedClients(0), processNextLine(false), gcodeReadIndex(0), gcodeWriteIndex(0), gcodeReply(nullptr) -{ - ResetState(); -} - -void Webserver::TelnetInterpreter::Diagnostics(MessageType mt) -{ - platform->MessageF(mt, "Telnet connections: %d, state %d\n", connectedClients, state); -} - -void Webserver::TelnetInterpreter::ConnectionEstablished() -{ - connectedClients++; - NetworkTransaction *transaction = network->GetTransaction(); - - // Only one client may be connected via Telnet at once, so check this first - if (state != idle) - { - transaction->Write("Sorry, only one client may be connected via Telnet at once.\r\n"); - transaction->Commit(false); - return; - } - state = justConnected; - connectTime = millis(); - - // Check whether we need a password to log in - if (reprap.NoPasswordSet()) - { - // Don't send a login prompt if no password is set, so we don't mess up Pronterface - transaction->Discard(); - } - else - { - transaction->Write("RepRapFirmware Telnet interface\r\n\r\n"); - transaction->Write("Please enter your password:\r\n"); - transaction->Write("> "); - transaction->Commit(true); - } -} - -// May be called from ISR! -void Webserver::TelnetInterpreter::ConnectionLost(const ConnectionState *cs) -{ - connectedClients--; - if (connectedClients == 0) - { - ResetState(); - - // Don't save up output buffers if they can't be sent - OutputBuffer::ReleaseAll(gcodeReply); - gcodeReply = nullptr; - } -} - -bool Webserver::TelnetInterpreter::CanParseData() -{ - // Is this an acquired transaction using which we can send the G-code reply? - TransactionStatus status = webserver->currentTransaction->GetStatus(); - if (status == acquired) - { - SendGCodeReply(); - return false; - } - - // Is this connection still live? Check that for deferred requests - if (status == deferred && !webserver->currentTransaction->IsConnected()) - { - webserver->currentTransaction->Discard(); - return false; - } - - // In order to support TCP streaming mode, check if we can store any more data at this time - if (GetGCodeBufferSpace() < clientPointer + 1) - { - webserver->currentTransaction->Defer(DeferralMode::DeferOnly); - return false; - } - - // If that works and if the next line hasn't been processed yet, do it now - if (processNextLine) - { - return !ProcessLine(); - } - - // Otherwise just parse the next request - return true; -} - -bool Webserver::TelnetInterpreter::CharFromClient(char c) -{ - // If this is likely to be a Telnet setup message (with some garbage in it), dump the first - // received packet and move on to the next state - if (state == justConnected) - { - if (reprap.NoPasswordSet()) - { - state = authenticated; - network->SaveTelnetConnection(); - } - else - { - state = authenticating; - } - - if (millis() - connectTime < telnetSetupDuration) - { - network->GetTransaction()->Discard(); - return true; - } - } - - // Otherwise try to read one line at a time - switch (c) - { - case 0: - break; - - case '\b': - // Allow backspace for pure Telnet clients like PuTTY - if (clientPointer != 0) - { - clientPointer--; - } - break; - - case '\r': - case '\n': - if (clientPointer != 0) - { - // This line is complete, do we have enough space left to store it? - clientMessage[clientPointer] = 0; - if (GetGCodeBufferSpace() < clientPointer + 1) - { - // No - defer this transaction, so we can process more of it next time - webserver->currentTransaction->Defer(DeferralMode::DeferOnly); - processNextLine = true; - return true; - } - - // Yes - try to process it - return ProcessLine(); - } - break; - - default: - clientMessage[clientPointer++] = c; - - // Make sure we don't overflow the line buffer - if (clientPointer == ARRAY_UPB(clientMessage)) - { - clientPointer = 0; - platform->Message(HOST_MESSAGE, "Webserver: Buffer overflow in Telnet server!\n"); - return true; - } - break; - } - - return false; -} - -void Webserver::TelnetInterpreter::ResetState() -{ - state = idle; - connectTime = 0; - clientPointer = 0; - gcodeReadIndex = gcodeWriteIndex; // clear the buffer -} - -// Usually we should not try to send any data here, because that would purge the packet's -// payload and mess with TCP streaming mode if Pronterface is used. However, under special -// circumstances this must happen and in this case this method must always return true. -bool Webserver::TelnetInterpreter::ProcessLine() -{ - processNextLine = false; - clientPointer = 0; - - NetworkTransaction *transaction = network->GetTransaction(); - switch (state) - { - case idle: - case justConnected: - // Should never get here... - // no break - - case authenticating: - if (reprap.CheckPassword(clientMessage)) - { - network->SaveTelnetConnection(); - state = authenticated; - - transaction->Write("Log in successful!\r\n"); - transaction->Commit(true); - } - else - { - transaction->Write("Invalid password.\r\n> "); - transaction->Commit(true); - } - return true; - - case authenticated: - // Special commands for Telnet - if (StringEquals(clientMessage, "exit") || StringEquals(clientMessage, "quit")) - { - transaction->Write("Goodbye.\r\n"); - transaction->Commit(false); - return true; - } - // All other codes are stored for the GCodes class - ProcessGcode(clientMessage); - break; - } - return false; -} - -// Process a null-terminated gcode -// We intercept one M Codes so we can deal with emergencies. That -// way things don't get out of sync, and - as a file name can contain -// a valid G code (!) - confusion is avoided. -void Webserver::TelnetInterpreter::ProcessGcode(const char* gc) -{ - if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop - { - reprap.EmergencyStop(); - gcodeReadIndex = gcodeWriteIndex; // clear the buffer - reprap.GetGCodes()->Reset(); - } - else - { - StoreGcodeData(gc, strlen(gc) + 1); - } -} - -// Process a received string of gcodes -void Webserver::TelnetInterpreter::StoreGcodeData(const char* data, uint16_t len) -{ - if (len > GetGCodeBufferSpace()) - { - platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in Telnet Webserver!\n"); - } - else - { - uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; - if (len <= remaining) - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, len); - } - else - { - memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); - memcpy(gcodeBuffer, data + remaining, len - remaining); - } - gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; - } -} - -// Feeding G Codes to the GCodes class -char Webserver::TelnetInterpreter::ReadGCode() -{ - char c; - if (gcodeReadIndex == gcodeWriteIndex) - { - c = 0; - } - else - { - c = gcodeBuffer[gcodeReadIndex]; - gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; - } - return c; -} - -// Handle a G-Code reply from the GCodes class; replace \n with \r\n -void Webserver::TelnetInterpreter::HandleGCodeReply(OutputBuffer *reply) -{ - if (reply != nullptr && state >= authenticated) - { - if (!network->AcquireTelnetTransaction()) - { - // We must be able to send the response to the client on the next Spin call - return; - } - - // We need a valid OutputBuffer to start the conversion from NL to CRNL - if (gcodeReply == nullptr) - { - OutputBuffer *buffer; - if (!OutputBuffer::Allocate(buffer)) - { - OutputBuffer::Truncate(reply, OUTPUT_BUFFER_SIZE); - if (!OutputBuffer::Allocate(buffer)) - { - // If we're really short on memory, release the G-Code reply instantly - OutputBuffer::ReleaseAll(reply); - return; - } - } - gcodeReply = buffer; - } - - // Write entire content to new output buffers, but this time with \r\n instead of \n - do { - const char *data = reply->Data(); - for(size_t i = 0; i < reply->DataLength(); i++) - { - if (*data == '\n') - { - gcodeReply->cat('\r'); - } - - gcodeReply->cat(*data); - data++; - } - reply = OutputBuffer::Release(reply); - } while (reply != nullptr); - } - else - { - // Don't store buffers that may never get released... - OutputBuffer::ReleaseAll(reply); - } -} - -void Webserver::TelnetInterpreter::HandleGCodeReply(const char *reply) -{ - if (reply != nullptr && state >= authenticated) - { - if (!network->AcquireTelnetTransaction()) - { - // We must be able to send the response to the client on the next Spin call - return; - } - - // We need a valid OutputBuffer to start the conversion from NL to CRNL - if (gcodeReply == nullptr) - { - OutputBuffer *buffer; - if (!OutputBuffer::Allocate(buffer)) - { - // No more space available to store this reply, stop here - return; - } - gcodeReply = buffer; - } - - // Write entire content to new output buffers, but this time with \r\n instead of \n - while (*reply != 0) - { - if (*reply == '\n' && gcodeReply->cat('\r') == 0) - { - // No more space available, stop here - return; - } - if (gcodeReply->cat(*reply) == 0) - { - // No more space available, stop here - return; - } - reply++; - }; - } -} - -void Webserver::TelnetInterpreter::SendGCodeReply() -{ - NetworkTransaction *transaction = webserver->currentTransaction; - - if (gcodeReply == nullptr) - { - transaction->Discard(); - } - else - { - transaction->Write(gcodeReply); - transaction->Commit(true); - } - - gcodeReply = nullptr; -} - -// vim: ts=4:sw=4 diff --git a/src/DuetNG/DuetEthernet/Webserver.h b/src/DuetNG/DuetEthernet/Webserver.h deleted file mode 100644 index 1b4975bc..00000000 --- a/src/DuetNG/DuetEthernet/Webserver.h +++ /dev/null @@ -1,384 +0,0 @@ -/**************************************************************************************************** - -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 diff --git a/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.cpp b/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.cpp index a647d08a..0ad63a22 100644 --- a/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.cpp +++ b/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.cpp @@ -53,7 +53,7 @@ //! THE POSSIBILITY OF SUCH DAMAGE. // //***************************************************************************** -#include "socket.h" +#include <socketlib.h> #define _SOCKET_DEBUG_ @@ -272,6 +272,11 @@ int8_t disconnect(uint8_t sn) return SOCK_OK; } +void disconnectNoWait(uint8_t sn) +{ + ExecCommand(sn, Sn_CR_DISCON); +} + int32_t send(uint8_t sn, uint8_t * buf, uint16_t len) { CHECK_SOCKMODE(Sn_MR_TCP); diff --git a/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.h b/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.h index 24bb7f7c..5cd57c57 100644 --- a/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.h +++ b/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.h @@ -226,6 +226,8 @@ int8_t connect(uint8_t sn, uint8_t * addr, uint16_t port); */ int8_t disconnect(uint8_t sn); +void disconnectNoWait(uint8_t sn); + /** * @ingroup WIZnet_socket_APIs * @brief Send data to the connected peer in TCP socket. diff --git a/src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp b/src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp index 079f72bf..2d42feb0 100644 --- a/src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp +++ b/src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp @@ -49,7 +49,7 @@ // //***************************************************************************** -#include "socket.h" +#include <socketlib.h> #include "dhcp.h" #include <cstring> diff --git a/src/DuetNG/DuetWiFi/Network.cpp b/src/DuetNG/DuetWiFi/Network.cpp index 31aa3459..03e8f1d3 100644 --- a/src/DuetNG/DuetWiFi/Network.cpp +++ b/src/DuetNG/DuetWiFi/Network.cpp @@ -4,12 +4,12 @@ ****************************************************************************************************/ -#include "RepRapFirmware.h" -#include "compiler.h" -#include "Pins.h" -#include "WifiFirmwareUploader.h" +#include "Network.h" +#include "Platform.h" +#include "RepRap.h" #include "TransactionBuffer.h" #include "TransactionBufferReader.h" +#include "WifiFirmwareUploader.h" // Define exactly one of the following as 1, thje other as zero // The PDC seems to be too slow to work reliably without getting transmit underruns, so we use the DMAC now. diff --git a/src/DuetNG/DuetWiFi/Network.h b/src/DuetNG/DuetWiFi/Network.h index 2d403412..3258c34a 100644 --- a/src/DuetNG/DuetWiFi/Network.h +++ b/src/DuetNG/DuetWiFi/Network.h @@ -9,11 +9,7 @@ Separated out from Platform.h by dc42 and extended by zpl #ifndef NETWORK_H #define NETWORK_H -#include <cctype> -#include <cstring> -#include <cstdlib> -#include <climits> - +#include "RepRapFirmware.h" #include "MessageType.h" // Return code definitions diff --git a/src/DuetNG/DuetWiFi/Webserver.cpp b/src/DuetNG/DuetWiFi/Webserver.cpp index 2a2a94f4..7d16d3a4 100644 --- a/src/DuetNG/DuetWiFi/Webserver.cpp +++ b/src/DuetNG/DuetWiFi/Webserver.cpp @@ -82,7 +82,13 @@ ****************************************************************************************************/ -#include "RepRapFirmware.h" +#include "Webserver.h" + +#include "GCodes/GCodes.h" +#include "Network.h" +#include "Platform.h" +#include "PrintMonitor.h" +#include "RepRap.h" const char* overflowResponse = "overflow"; const char* badEscapeResponse = "bad escape"; diff --git a/src/DuetNG/DuetWiFi/Webserver.h b/src/DuetNG/DuetWiFi/Webserver.h index 5b4ba5fd..d8459d8d 100644 --- a/src/DuetNG/DuetWiFi/Webserver.h +++ b/src/DuetNG/DuetWiFi/Webserver.h @@ -30,6 +30,9 @@ Licence: GPL #ifndef WEBSERVER_H #define WEBSERVER_H +#include "RepRapFirmware.h" +#include "MessageType.h" +#include "Storage/FileData.h" // List of protocols that can execute G-Codes enum class WebSource diff --git a/src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp b/src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp index 070c86f2..cfe108d3 100644 --- a/src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp +++ b/src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp @@ -6,7 +6,11 @@ */ #include "WifiFirmwareUploader.h" -#include "RepRapFirmware.h" + +#include "Network.h" +#include "Platform.h" +#include "RepRap.h" +#include "Storage/FileStore.h" // ESP8266 command codes const uint8_t ESP_FLASH_BEGIN = 0x02; diff --git a/src/DuetNG/DuetWiFi/WifiFirmwareUploader.h b/src/DuetNG/DuetWiFi/WifiFirmwareUploader.h index 22b92fea..bc5012ec 100644 --- a/src/DuetNG/DuetWiFi/WifiFirmwareUploader.h +++ b/src/DuetNG/DuetWiFi/WifiFirmwareUploader.h @@ -8,8 +8,7 @@ #ifndef SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ #define SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ -#include "Core.h" -#include "Storage/FileStore.h" +#include "RepRapFirmware.h" class WifiFirmwareUploader { diff --git a/src/DuetNG/FirmwareUpdater.cpp b/src/DuetNG/FirmwareUpdater.cpp index 9f2104b3..3fd43179 100644 --- a/src/DuetNG/FirmwareUpdater.cpp +++ b/src/DuetNG/FirmwareUpdater.cpp @@ -6,7 +6,11 @@ */ #include "FirmwareUpdater.h" + #include "RepRapFirmware.h" +#include "Network.h" +#include "Platform.h" +#include "RepRap.h" #ifdef DUET_WIFI #include "WifiFirmwareUploader.h" diff --git a/src/DuetNG/Pins_DuetNG.h b/src/DuetNG/Pins_DuetNG.h index 60ce39f2..b01c8099 100644 --- a/src/DuetNG/Pins_DuetNG.h +++ b/src/DuetNG/Pins_DuetNG.h @@ -33,8 +33,6 @@ const size_t MaxDriversPerAxis = 4; // The maximum number of stepper drivers const size_t MAX_AXES = 6; // The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES const size_t MIN_AXES = 3; // The minimum and default number of axes -const size_t DELTA_AXES = 3; // The number of axis involved in delta movement -const size_t CART_AXES = 3; // The number of Cartesian axes const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders const size_t NUM_SERIAL_CHANNELS = 2; // The number of serial IO channels (USB and one auxiliary UART) diff --git a/src/Fan.cpp b/src/Fan.cpp index 83c56e45..67206112 100644 --- a/src/Fan.cpp +++ b/src/Fan.cpp @@ -5,7 +5,9 @@ * Author: David */ -#include "RepRapFirmware.h" +#include "Fan.h" +#include "Platform.h" +#include "RepRap.h" void Fan::Init(Pin p_pin, bool hwInverted) { @@ -8,7 +8,7 @@ #ifndef SRC_FAN_H_ #define SRC_FAN_H_ -#include "Core.h" +#include "RepRapFirmware.h" class Fan { diff --git a/src/GCodes/GCodeBuffer.cpp b/src/GCodes/GCodeBuffer.cpp index 8fa81bac..fcc8d3a1 100644 --- a/src/GCodes/GCodeBuffer.cpp +++ b/src/GCodes/GCodeBuffer.cpp @@ -7,7 +7,9 @@ //************************************************************************************* -#include "RepRapFirmware.h" +#include "GCodeBuffer.h" +#include "Platform.h" +#include "RepRap.h" // Create a default GCodeBuffer GCodeBuffer::GCodeBuffer(const char* id, MessageType mt) diff --git a/src/GCodes/GCodeBuffer.h b/src/GCodes/GCodeBuffer.h index b1540871..198512d1 100644 --- a/src/GCodes/GCodeBuffer.h +++ b/src/GCodes/GCodeBuffer.h @@ -8,7 +8,9 @@ #ifndef GCODEBUFFER_H_ #define GCODEBUFFER_H_ +#include "RepRapFirmware.h" #include "GCodeMachineState.h" +#include "MessageType.h" // Class to hold an individual GCode and provide functions to allow it to be parsed class GCodeBuffer diff --git a/src/GCodes/GCodeMachineState.h b/src/GCodes/GCodeMachineState.h index db8fdd01..eec66ebc 100644 --- a/src/GCodes/GCodeMachineState.h +++ b/src/GCodes/GCodeMachineState.h @@ -8,13 +8,9 @@ #ifndef SRC_GCODES_GCODEMACHINESTATE_H_ #define SRC_GCODES_GCODEMACHINESTATE_H_ -#include <cstdint> -#include "Configuration.h" +#include "RepRapFirmware.h" #include "Storage/FileData.h" -const float minutesToSeconds = 60.0; -const float secondsToMinutes = 1.0/minutesToSeconds; - // Enumeration to list all the possible states that the Gcode processing machine may be in enum class GCodeState : uint8_t { diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp index a4f249d7..fe84e7f3 100644 --- a/src/GCodes/GCodes.cpp +++ b/src/GCodes/GCodes.cpp @@ -23,7 +23,15 @@ ****************************************************************************************************/ -#include "RepRapFirmware.h" +#include "GCodes.h" +#include "GCodeBuffer.h" +#include "Heating/Heat.h" +#include "Platform.h" +#include "Movement/Move.h" +#include "PrintMonitor.h" +#include "RepRap.h" +#include "Tool.h" +#include "Webserver.h" #ifdef DUET_NG #include "FirmwareUpdater.h" @@ -160,6 +168,11 @@ void GCodes::Reset() } } +bool GCodes::DoingFileMacro() const +{ + return fileGCode->IsDoingFileMacro(); +} + float GCodes::FractionOfFilePrinted() const { const FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState; diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h index fa145eef..5aaea8ce 100644 --- a/src/GCodes/GCodes.h +++ b/src/GCodes/GCodes.h @@ -22,8 +22,11 @@ Licence: GPL #ifndef GCODES_H #define GCODES_H -#include "GCodeBuffer.h" +#include "RepRapFirmware.h" #include "Libraries/sha1/sha1.h" +#include "Platform.h" // for type EndStopHit + +class GCodeBuffer; const char feedrateLetter = 'F'; // GCode feedrate const char extrudeLetter = 'E'; // GCode extrude @@ -312,9 +315,4 @@ private: //***************************************************************************************************** -inline bool GCodes::DoingFileMacro() const -{ - return fileGCode->IsDoingFileMacro(); -} - #endif diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp index 34c0baab..cce862fe 100644 --- a/src/GCodes/GCodes2.cpp +++ b/src/GCodes/GCodes2.cpp @@ -7,7 +7,16 @@ * This file contains the code to see what G, M or T command we have and start processing it. */ -#include "RepRapFirmware.h" +#include "GCodes.h" + +#include "GCodeBuffer.h" +#include "Heating/Heat.h" +#include "Movement/Move.h" +#include "Network.h" +#include "PrintMonitor.h" +#include "RepRap.h" +#include "Tool.h" +#include "Version.h" #ifdef DUET_NG #include "FirmwareUpdater.h" diff --git a/src/Heating/FOPDT.cpp b/src/Heating/FOPDT.cpp index e3bc22c6..e2d9244e 100644 --- a/src/Heating/FOPDT.cpp +++ b/src/Heating/FOPDT.cpp @@ -6,8 +6,6 @@ */ #include "FOPDT.h" -#include "Core.h" -#include "Configuration.h" #include "Storage/FileStore.h" #include "Libraries/General/StringRef.h" diff --git a/src/Heating/FOPDT.h b/src/Heating/FOPDT.h index 4f703039..80850865 100644 --- a/src/Heating/FOPDT.h +++ b/src/Heating/FOPDT.h @@ -10,7 +10,7 @@ #ifndef SRC_HEATING_FOPDT_H_ #define SRC_HEATING_FOPDT_H_ -#include <cstddef> +#include "RepRapFirmware.h" // This is how PID parameters are stored internally struct PidParameters diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp index 57df8e95..674cd60d 100644 --- a/src/Heating/Heat.cpp +++ b/src/Heating/Heat.cpp @@ -18,11 +18,12 @@ Licence: GPL ****************************************************************************************************/ -#include "RepRapFirmware.h" -#include "Pid.h" +#include "Heat.h" +#include "Platform.h" +#include "RepRap.h" Heat::Heat(Platform* p) - : platform(p), active(false), coldExtrude(false), bedHeater(DefaultBedHeater), chamberHeater(-1), heaterBeingTuned(-1), lastHeaterTuned(-1) + : platform(p), active(false), coldExtrude(false), bedHeater(DefaultBedHeater), chamberHeater(DefaultChamberHeater), heaterBeingTuned(-1), lastHeaterTuned(-1) { for (size_t heater = 0; heater < HEATERS; heater++) { @@ -37,7 +38,7 @@ void Heat::ResetHeaterModels() { if (pids[heater]->IsHeaterEnabled()) { - if (heater == DefaultBedHeater) + if (heater == DefaultBedHeater || heater == DefaultChamberHeater) { pids[heater]->SetModel(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, false); } @@ -53,7 +54,7 @@ void Heat::Init() { for (int heater = 0; heater < HEATERS; heater++) { - if (heater == DefaultBedHeater) + if (heater == DefaultBedHeater || heater == DefaultChamberHeater) { pids[heater]->Init(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, DefaultBedTemperatureLimit, false); diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h index e6395a27..e71ab847 100644 --- a/src/Heating/Heat.h +++ b/src/Heating/Heat.h @@ -25,7 +25,9 @@ Licence: GPL * The master class that controls all the heaters in the RepRap machine */ +#include "RepRapFirmware.h" #include "Pid.h" +#include "MessageType.h" class Heat { diff --git a/src/Heating/Pid.cpp b/src/Heating/Pid.cpp index 039729a6..0347f066 100644 --- a/src/Heating/Pid.cpp +++ b/src/Heating/Pid.cpp @@ -5,8 +5,11 @@ * Author: David */ -#include "RepRapFirmware.h" #include "Pid.h" +#include "GCodes/GCodes.h" +#include "Heat.h" +#include "Platform.h" +#include "RepRap.h" // Private constants const uint32_t InitialTuningReadingInterval = 250; // the initial reading interval in milliseconds @@ -31,6 +34,11 @@ PID::PID(Platform* p, int8_t h) : platform(p), heater(h), mode(HeaterMode::off) { } +inline void PID::SetHeater(float power) const +{ + platform->SetHeater(heater, power); +} + void PID::Init(float pGain, float pTc, float pTd, float tempLimit, bool usePid) { temperatureLimit = tempLimit; diff --git a/src/Heating/Pid.h b/src/Heating/Pid.h index b5ecb8cb..523eea95 100644 --- a/src/Heating/Pid.h +++ b/src/Heating/Pid.h @@ -12,7 +12,9 @@ * This class implements a PID controller for the heaters */ +#include "RepRapFirmware.h" #include "FOPDT.h" +#include "TemperatureError.h" class PID { @@ -182,11 +184,6 @@ inline float PID::GetAccumulator() const return iAccumulator; } -inline void PID::SetHeater(float power) const -{ - platform->SetHeater(heater, power); -} - inline bool PID::IsTuning() const { return mode >= HeaterMode::tuning0; diff --git a/src/Heating/TemperatureSensor.cpp b/src/Heating/TemperatureSensor.cpp index 31a1e248..7ed05eef 100644 --- a/src/Heating/TemperatureSensor.cpp +++ b/src/Heating/TemperatureSensor.cpp @@ -1,5 +1,6 @@ #include "TemperatureSensor.h" -#include "RepRapFirmware.h" +#include "Platform.h" +#include "RepRap.h" // MAX31855 thermocouple chip // diff --git a/src/Heating/TemperatureSensor.h b/src/Heating/TemperatureSensor.h index d29f6f74..38cb5e33 100644 --- a/src/Heating/TemperatureSensor.h +++ b/src/Heating/TemperatureSensor.h @@ -1,8 +1,8 @@ #ifndef TEMPERATURESENSOR_H #define TEMPERATURESENSOR_H +#include "RepRapFirmware.h" #include "TemperatureError.h" // for result codes -#include "Core.h" #include "SharedSpi.h" // for sspi_device class TemperatureSensor diff --git a/src/Heating/Thermistor.cpp b/src/Heating/Thermistor.cpp index 991dc441..d8357573 100644 --- a/src/Heating/Thermistor.cpp +++ b/src/Heating/Thermistor.cpp @@ -6,8 +6,6 @@ */ #include "Thermistor.h" -#include "Pins.h" -#include "Configuration.h" // The Steinhart-Hart equation for thermistor resistance is: // 1/T = A + B ln(R) + C [ln(R)]^3 diff --git a/src/Heating/Thermistor.h b/src/Heating/Thermistor.h index 58c504c2..eabc45f9 100644 --- a/src/Heating/Thermistor.h +++ b/src/Heating/Thermistor.h @@ -8,7 +8,7 @@ #ifndef SRC_HEATING_THERMISTOR_H_ #define SRC_HEATING_THERMISTOR_H_ -#include "Core.h" +#include "RepRapFirmware.h" // The Steinhart-Hart equation for thermistor resistance is: // 1/T = A + B ln(R) + C [ln(R)]^3 diff --git a/src/Libraries/Fatfs/fattime_rtc.cpp b/src/Libraries/Fatfs/fattime_rtc.cpp index 9c416759..8584585f 100644 --- a/src/Libraries/Fatfs/fattime_rtc.cpp +++ b/src/Libraries/Fatfs/fattime_rtc.cpp @@ -41,6 +41,8 @@ * */ #include "RepRapFirmware.h" +#include "RepRap.h" +#include "Platform.h" /** * \brief Current time returned is packed into a DWORD value. diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp index 6456d6e2..52333877 100644 --- a/src/Movement/DDA.cpp +++ b/src/Movement/DDA.cpp @@ -5,7 +5,10 @@ * Author: David */ -#include "RepRapFirmware.h" +#include "DDA.h" +#include "RepRap.h" +#include "Platform.h" +#include "Move.h" #ifdef DUET_NG # define DDA_MOVE_DEBUG (1) diff --git a/src/Movement/DDA.h b/src/Movement/DDA.h index e0ceb1d6..423fdbb3 100644 --- a/src/Movement/DDA.h +++ b/src/Movement/DDA.h @@ -8,7 +8,9 @@ #ifndef DDA_H_ #define DDA_H_ +#include "RepRapFirmware.h" #include "DriveMovement.h" +#include "GCodes/GCodes.h" // for class RawMove #ifdef DUET_NG #define DDA_LOG_PROBE_CHANGES 1 diff --git a/src/Movement/DeltaParameters.cpp b/src/Movement/DeltaParameters.cpp index 4594ae6c..9dc96a4b 100644 --- a/src/Movement/DeltaParameters.cpp +++ b/src/Movement/DeltaParameters.cpp @@ -5,7 +5,10 @@ * Author: David */ -#include "RepRapFirmware.h" +#include "DeltaParameters.h" +#include "Pins.h" +#include "Configuration.h" +#include "Storage/FileStore.h" void DeltaParameters::Init() { diff --git a/src/Movement/DeltaParameters.h b/src/Movement/DeltaParameters.h index b4d677ff..90a84690 100644 --- a/src/Movement/DeltaParameters.h +++ b/src/Movement/DeltaParameters.h @@ -8,6 +8,24 @@ #ifndef DELTAPARAMETERS_H_ #define DELTAPARAMETERS_H_ +#include "RepRapFirmware.h" + +#ifdef DUET_NG +typedef double floatc_t; // type of matrix element used for delta calibration +#else +// We are more memory-constrained on the SAM3X +typedef float floatc_t; // type of matrix element used for delta calibration +#endif + +// Delta parameter defaults +const float defaultPrintRadius = 50; // mm +const float defaultDeltaHomedHeight = 200; // mm + +const size_t DELTA_AXES = 3; +const size_t A_AXIS = 0; +const size_t B_AXIS = 1; +const size_t C_AXIS = 2; + // Class to hold the parameter for a delta machine. class DeltaParameters { diff --git a/src/Movement/DeltaProbe.cpp b/src/Movement/DeltaProbe.cpp index d52472d0..b557fb52 100644 --- a/src/Movement/DeltaProbe.cpp +++ b/src/Movement/DeltaProbe.cpp @@ -5,7 +5,10 @@ * Author: David */ -#include "RepRapFirmware.h" +#include "Deltaprobe.h" +#include "DDA.h" +#include "Platform.h" +#include "RepRap.h" // Set up to probe bool DeltaProbe::Init(float frequency, float amplitude, float rate, float height) diff --git a/src/Movement/DeltaProbe.h b/src/Movement/DeltaProbe.h index 50e0cc50..90b2fb49 100644 --- a/src/Movement/DeltaProbe.h +++ b/src/Movement/DeltaProbe.h @@ -8,6 +8,8 @@ #ifndef DELTAPROBE_H_ #define DELTAPROBE_H_ +#include "RepRapFirmware.h" + // Class to hold the parameters for my new Z probing method class DeltaProbe { diff --git a/src/Movement/DriveMovement.cpp b/src/Movement/DriveMovement.cpp index 447d7aac..59e3f1ba 100644 --- a/src/Movement/DriveMovement.cpp +++ b/src/Movement/DriveMovement.cpp @@ -5,7 +5,10 @@ * Author: David */ -#include "RepRapFirmware.h" +#include "DriveMovement.h" +#include "DDA.h" +#include "Move.h" +#include "RepRap.h" #include "Libraries/Math/Isqrt.h" // Prepare this DM for a Cartesian axis move diff --git a/src/Movement/DriveMovement.h b/src/Movement/DriveMovement.h index e30020bd..e27319dd 100644 --- a/src/Movement/DriveMovement.h +++ b/src/Movement/DriveMovement.h @@ -8,7 +8,7 @@ #ifndef DRIVEMOVEMENT_H_ #define DRIVEMOVEMENT_H_ -class DDA; +#include "RepRapFirmware.h" // Struct for passing parameters to the DriveMovement Prepare methods struct PrepParams diff --git a/src/Movement/Grid.cpp b/src/Movement/Grid.cpp index ac1709ca..a0a8fb87 100644 --- a/src/Movement/Grid.cpp +++ b/src/Movement/Grid.cpp @@ -6,7 +6,9 @@ */ #include "Grid.h" -#include "RepRapFirmware.h" +#include "Platform.h" +#include "RepRap.h" +#include "Storage/FileStore.h" #include <cmath> const char *GridDefinition::HeightMapLabelLine = "xmin,xmax,ymin,ymax,radius,spacing,xnum,ynum"; diff --git a/src/Movement/Grid.h b/src/Movement/Grid.h index 6dba8b38..2de4e0ba 100644 --- a/src/Movement/Grid.h +++ b/src/Movement/Grid.h @@ -9,11 +9,8 @@ #define SRC_MOVEMENT_GRID_H_ #include <cstdint> -#include "ecv.h" +#include "RepRapFirmware.h" #include "Libraries/General/StringRef.h" -#include "Configuration.h" - -class FileStore; // This class defines the bed probing grid class GridDefinition diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp index a2fec5ca..a63ab16d 100644 --- a/src/Movement/Move.cpp +++ b/src/Movement/Move.cpp @@ -5,7 +5,9 @@ * Author: David */ -#include "RepRapFirmware.h" +#include "Move.h" +#include "Platform.h" +#include "RepRap.h" Move::Move(Platform* p, GCodes* g) : currentDda(NULL), grid(zBedProbePoints) { diff --git a/src/Movement/Move.h b/src/Movement/Move.h index e427176a..b0d1015d 100644 --- a/src/Movement/Move.h +++ b/src/Movement/Move.h @@ -8,16 +8,16 @@ #ifndef MOVE_H_ #define MOVE_H_ -#include "DDA.h" +#include "RepRapFirmware.h" +#include "MessageType.h" +#include "DDA.h" // needed because of our inline functions #include "Libraries/Math/Matrix.h" #ifdef DUET_NG const unsigned int DdaRingLength = 40; -typedef double floatc_t; // type of matrix element used for delta calibration #else // We are more memory-constrained on the SAM3X const unsigned int DdaRingLength = 20; -typedef float floatc_t; // type of matrix element used for delta calibration #endif #include "DeltaParameters.h" diff --git a/src/OutputMemory.cpp b/src/OutputMemory.cpp index b907cfa3..d825ecaa 100644 --- a/src/OutputMemory.cpp +++ b/src/OutputMemory.cpp @@ -6,7 +6,8 @@ */ #include "OutputMemory.h" -#include "RepRapFirmware.h" +#include "Platform.h" +#include "RepRap.h" #include <cstdarg> /*static*/ OutputBuffer * volatile OutputBuffer::freeOutputBuffers = nullptr; // Messages may also be sent by ISRs, diff --git a/src/OutputMemory.h b/src/OutputMemory.h index 629fbe4f..671bac9f 100644 --- a/src/OutputMemory.h +++ b/src/OutputMemory.h @@ -8,9 +8,7 @@ #ifndef OUTPUTMEMORY_H_ #define OUTPUTMEMORY_H_ -#include "Core.h" -#include "Configuration.h" -#include "Libraries/General/StringRef.h" +#include "RepRapFirmware.h" #include "MessageType.h" const size_t OUTPUT_STACK_DEPTH = 4; // Number of OutputBuffer chains that can be pushed onto one stack instance diff --git a/src/Platform.cpp b/src/Platform.cpp index b118ba5d..9f686279 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -19,7 +19,14 @@ ****************************************************************************************************/ -#include "RepRapFirmware.h" +#include "Platform.h" + +#include "Heating/Heat.h" +#include "Movement/DDA.h" +#include "Movement/Move.h" +#include "Network.h" +#include "RepRap.h" +#include "Webserver.h" #include "sam/drivers/tc/tc.h" #include "sam/drivers/hsmci/hsmci.h" @@ -30,6 +37,9 @@ # include "FirmwareUpdater.h" #endif +#include <climits> +#include <malloc.h> + extern char _end; extern "C" char *sbrk(int i); diff --git a/src/Platform.h b/src/Platform.h index 0cde3f1d..cbbd4ec2 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -36,22 +36,19 @@ Licence: GPL // Language-specific includes -#include <cctype> -#include <cstring> -#include <malloc.h> -#include <cstdlib> -#include <climits> -#include <ctime> - // Platform-specific includes -#include "Core.h" +#include "RepRapFirmware.h" #include "DueFlashStorage.h" +#include "Fan.h" #include "Heating/TemperatureSensor.h" #include "Heating/Thermistor.h" #include "Heating/TemperatureError.h" #include "OutputMemory.h" -#include "Libraries/Fatfs/ff.h" +#include "Storage/FileStore.h" +#include "Storage/FileData.h" +#include "Storage/MassStorage.h" // must be after Pins.h because it needs NumSdCards defined +#include "MessageType.h" #if defined(DUET_NG) # include "DueXn.h" @@ -59,23 +56,9 @@ Licence: GPL # include "MCP4461/MCP4461.h" #endif -#include "Storage/FileStore.h" -#include "Storage/FileData.h" -#include "MessageType.h" - -// Definitions needed by Fan.h -const float SecondsToMillis = 1000.0; -const float MillisToSeconds = 0.001; - -#include "Fan.h" - -// Definitions needed by Pins.h const bool FORWARDS = true; const bool BACKWARDS = !FORWARDS; -#include "Pins.h" -#include "Storage/MassStorage.h" // must be after Pins.h because it needs NumSdCards defined - /**************************************************************************************************/ // Some constants @@ -101,15 +84,9 @@ const float INSTANT_DVS[DRIVES] = DRIVES_(15.0, 15.0, 0.2, 2.0, 2.0, 2.0, 2.0, 2 // AXES -const size_t X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E0_AXIS = 3; // The indices of the Cartesian axes in drive arrays -const size_t A_AXIS = 0, B_AXIS = 1, C_AXIS = 2; // The indices of the 3 tower motors of a delta printer in drive arrays - const float AXIS_MINIMA[MAX_AXES] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; // mm const float AXIS_MAXIMA[MAX_AXES] = { 230.0, 210.0, 200.0, 0.0, 0.0, 0.0 }; // mm -const float defaultPrintRadius = 50; // mm -const float defaultDeltaHomedHeight = 200; // mm - // Z PROBE const float Z_PROBE_STOP_HEIGHT = 0.7; // Millimetres @@ -132,9 +109,6 @@ const unsigned int ThermistorAverageReadings = 32; const uint32_t maxPidSpinDelay = 5000; // Maximum elapsed time in milliseconds between successive temp samples by Pid::Spin() permitted for a temp sensor -const size_t DefaultBedHeater = 0; // Index of the default bed heater -const size_t DefaultE0Heater = 1; // Index of the default first extruder heater - /****************************************************************************************************/ // File handling diff --git a/src/PrintMonitor.cpp b/src/PrintMonitor.cpp index 102ba4aa..83fdd3da 100644 --- a/src/PrintMonitor.cpp +++ b/src/PrintMonitor.cpp @@ -17,7 +17,13 @@ Licence: GPL ****************************************************************************************************/ -#include "RepRapFirmware.h" +#include "PrintMonitor.h" + +#include "GCodes/GCodes.h" +#include "Heating/Heat.h" +#include "Movement/Move.h" +#include "Platform.h" +#include "RepRap.h" PrintMonitor::PrintMonitor(Platform *p, GCodes *gc) : platform(p), gCodes(gc), isPrinting(false), printStartTime(0), pauseStartTime(0.0), totalPauseTime(0.0), heatingUp(false), currentLayer(0), warmUpDuration(0.0), diff --git a/src/PrintMonitor.h b/src/PrintMonitor.h index c71770f0..36eb26cc 100644 --- a/src/PrintMonitor.h +++ b/src/PrintMonitor.h @@ -20,6 +20,8 @@ Licence: GPL #ifndef PRINTMONITOR_H #define PRINTMONITOR_H +#include "RepRapFirmware.h" + const FilePosition GCODE_HEADER_SIZE = 8192uL; // How many bytes to read from the header const FilePosition GCODE_FOOTER_SIZE = 400000uL; // How many bytes to read from the footer diff --git a/src/RADDS/Network.h b/src/RADDS/Network.h index 564702c9..4f91e86e 100644 --- a/src/RADDS/Network.h +++ b/src/RADDS/Network.h @@ -1,13 +1,13 @@ #ifndef NETWORK_H #define NETWORK_H -#include <inttypes.h> -#include "Platform.h" +#include "RepRapFirmware.h" +#include "MessageType.h" -const uint8_t MAC_ADDRESS[6] = { 0, 0, 0, 0, 0, 0 }; -const uint8_t IP_ADDRESS[4] = { 0, 0, 0, 0 }; -const uint8_t NET_MASK[4] = { 0, 0, 0, 0 }; -const uint8_t GATE_WAY[4] = { 0, 0, 0, 0 }; +const uint8_t DefaultMacAddress[6] = { 0, 0, 0, 0, 0, 0 }; +const uint8_t DefaultIpAddress[4] = { 0, 0, 0, 0 }; +const uint8_t DefaultNetMask[4] = { 0, 0, 0, 0 }; +const uint8_t DefaultGateway[4] = { 0, 0, 0, 0 }; // The main network class that drives the network. class Network @@ -23,8 +23,8 @@ public: void Interrupt() const { }; void Diagnostics(MessageType mtype) const { }; - boolean IsEnabled() const { return false; } - boolean InLwip() const { return false; } + bool IsEnabled() const { return false; } + bool InLwip() const { return false; } void SetHostname(const char *name) const { }; void SetHttpPort(uint16_t port) const { }; uint16_t GetHttpPort() const { return (uint16_t)0; } diff --git a/src/RADDS/Pins_radds.h b/src/RADDS/Pins_radds.h index 3cb48c4d..ced457c3 100644 --- a/src/RADDS/Pins_radds.h +++ b/src/RADDS/Pins_radds.h @@ -34,8 +34,6 @@ const int8_t HEATERS = 4; const size_t MAX_AXES = 6; // FIXME The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES const size_t MIN_AXES = 3; // The minimum and default number of axes -const size_t DELTA_AXES = 3; // The number of axis involved in delta movement -const size_t CART_AXES = 3; // The number of Cartesian axes const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders const size_t NUM_SERIAL_CHANNELS = 2; diff --git a/src/RADDS/Webserver.h b/src/RADDS/Webserver.h index 0c8accb0..de65d6cf 100644 --- a/src/RADDS/Webserver.h +++ b/src/RADDS/Webserver.h @@ -1,8 +1,8 @@ #ifndef WEBSERVER_H #define WEBSERVER_H -#include <inttypes.h> -#include "OutputMemory.h" +#include "RepRapFirmware.h" +#include "MessageType.h" // List of protocols that can execute G-Codes enum class WebSource @@ -32,7 +32,9 @@ public: inline void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) const { if (reply != (OutputBuffer *)0) + { OutputBuffer::ReleaseAll(reply); + } } #endif diff --git a/src/RepRapFirmware.cpp b/src/RepRapFirmware.cpp index d73ec507..9a09fbd3 100644 --- a/src/RepRapFirmware.cpp +++ b/src/RepRapFirmware.cpp @@ -158,6 +158,10 @@ Licence: GPL #include "RepRapFirmware.h" +#include "MessageType.h" +#include "Platform.h" +#include "RepRap.h" + // We just need one instance of RepRap; everything else is contained within it and hidden RepRap reprap; diff --git a/src/RepRapFirmware.h b/src/RepRapFirmware.h index 74640eee..03783fb6 100644 --- a/src/RepRapFirmware.h +++ b/src/RepRapFirmware.h @@ -26,8 +26,10 @@ Licence: GPL #include <cfloat> #include <cstdarg> +#include "ecv.h" #include "Core.h" #include "Configuration.h" +#include "Pins.h" #include "Libraries/General/StringRef.h" // Module numbers and names, used for diagnostics and debug @@ -49,50 +51,51 @@ enum Module : uint8_t extern const char *moduleName[]; -// Warn of what's to come, so we can use pointers to classes... - +// Warn of what's to come, so we can use pointers to classes without including the entire header files class Network; class Platform; class Webserver; class GCodes; class Move; +class DDA; class Heat; class Tool; class Roland; class PrintMonitor; class RepRap; class FileStore; +class OutputBuffer; +class OutputStack; // A single instance of the RepRap class contains all the others - extern RepRap reprap; // Functions and globals not part of any class - extern "C" void debugPrintf(const char* fmt, ...); bool StringEndsWith(const char* string, const char* ending); bool StringStartsWith(const char* string, const char* starting); bool StringEquals(const char* s1, const char* s2); int StringContains(const char* string, const char* match); - -// Macro to assign an array from an initializer list + +// Macro to assign an array from an initialiser list #define ARRAY_INIT(_dest, _init) static_assert(sizeof(_dest) == sizeof(_init), "Incompatible array types"); memcpy(_dest, _init, sizeof(_init)); +// A string buffer used for temporary purposes extern StringRef scratchString; -#include "OutputMemory.h" -#include "Network.h" -#include "Platform.h" -#include "Webserver.h" -#include "GCodes/GCodes.h" -#include "Movement/Move.h" -#include "Heating/Heat.h" -#include "Tool.h" -#include "Roland.h" -#include "PrintMonitor.h" -#include "Reprap.h" +// Common definitions used by more than one module +const size_t CART_AXES = 3; // The number of Cartesian axes +const size_t X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E0_AXIS = 3; // The indices of the Cartesian axes in drive arrays -#endif +// Common conversion factors +const float minutesToSeconds = 60.0; +const float secondsToMinutes = 1.0/minutesToSeconds; +const float SecondsToMillis = 1000.0; +const float MillisToSeconds = 0.001; +// Type of an offset in a file +typedef uint32_t FilePosition; +const FilePosition noFilePosition = 0xFFFFFFFF; +#endif diff --git a/src/Reprap.cpp b/src/Reprap.cpp index d4d4a692..6459c801 100644 --- a/src/Reprap.cpp +++ b/src/Reprap.cpp @@ -1,5 +1,14 @@ -#include "RepRapFirmware.h" -#include <ctime> +#include "RepRap.h" + +#include "Network.h" +#include "Movement/Move.h" +#include "GCodes/GCodes.h" +#include "Heating/Heat.h" +#include "Platform.h" +#include "PrintMonitor.h" +#include "Tool.h" +#include "Webserver.h" +#include "Version.h" // RepRap member functions. diff --git a/src/Reprap.h b/src/Reprap.h index 8b80d527..7d63152a 100644 --- a/src/Reprap.h +++ b/src/Reprap.h @@ -21,6 +21,9 @@ Licence: GPL #ifndef REPRAP_H #define REPRAP_H +#include "RepRapFirmware.h" +#include "MessageType.h" + enum class ResponseSource { HTTP, diff --git a/src/Storage/FileStore.cpp b/src/Storage/FileStore.cpp index cc8207fd..df435e9e 100644 --- a/src/Storage/FileStore.cpp +++ b/src/Storage/FileStore.cpp @@ -4,6 +4,7 @@ #include "FileStore.h" #include "MassStorage.h" #include "Platform.h" +#include "RepRap.h" uint32_t FileStore::longestWriteTime = 0; diff --git a/src/Storage/FileStore.h b/src/Storage/FileStore.h index e998e3e8..29ace01c 100644 --- a/src/Storage/FileStore.h +++ b/src/Storage/FileStore.h @@ -6,8 +6,6 @@ #include "Core.h" #include "Libraries/Fatfs/ff.h" -typedef uint32_t FilePosition; -const FilePosition noFilePosition = 0xFFFFFFFF; const size_t FileBufLen = 256; // 512 would be more efficient, but need to free up some RAM first enum class IOStatus : uint8_t diff --git a/src/Storage/MassStorage.cpp b/src/Storage/MassStorage.cpp index b296329d..2084b359 100644 --- a/src/Storage/MassStorage.cpp +++ b/src/Storage/MassStorage.cpp @@ -1,4 +1,6 @@ -#include "RepRapFirmware.h" +#include "MassStorage.h" +#include "Platform.h" +#include "RepRap.h" #include "sd_mmc.h" // Static helper functions - not declared as class members to avoid having to include sd_mmc.h everywhere diff --git a/src/Storage/MassStorage.h b/src/Storage/MassStorage.h index ee355796..2371e671 100644 --- a/src/Storage/MassStorage.h +++ b/src/Storage/MassStorage.h @@ -1,10 +1,11 @@ #ifndef MASSSTORAGE_H #define MASSSTORAGE_H +#include "RepRapFirmware.h" +#include "Pins.h" +#include "Libraries/FatFs/ff.h" #include <ctime> -class Platform; - // Info returned by FindFirst/FindNext calls struct FileInfo { diff --git a/src/Tool.cpp b/src/Tool.cpp index e5e4bcbe..e98beaa8 100644 --- a/src/Tool.cpp +++ b/src/Tool.cpp @@ -23,7 +23,12 @@ ****************************************************************************************************/ -#include "RepRapFirmware.h" +#include "Tool.h" + +#include "GCodes/GCodes.h" +#include "Heating/Heat.h" +#include "Platform.h" +#include "RepRap.h" Tool * Tool::freelist = nullptr; @@ -26,6 +26,8 @@ Licence: GPL #ifndef TOOL_H_ #define TOOL_H_ +#include "RepRapFirmware.h" + const uint32_t DefaultXAxisMapping = 0x0001; // by default, X is mapped to X class Tool diff --git a/src/Version.h b/src/Version.h new file mode 100644 index 00000000..5f7a3706 --- /dev/null +++ b/src/Version.h @@ -0,0 +1,21 @@ +/* + * Version.h + * + * Created on: 25 Dec 2016 + * Author: David + */ + +#ifndef SRC_VERSION_H_ +#define SRC_VERSION_H_ + +#ifndef VERSION +# define VERSION "1.17+2" +#endif + +#ifndef DATE +# define DATE "2016-12-25" +#endif + +#define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman" + +#endif /* SRC_VERSION_H_ */ diff --git a/src/Duet/Webserver.cpp b/src/Webserver/Webserver.cpp index 18cad76b..15d34e5e 100644 --- a/src/Duet/Webserver.cpp +++ b/src/Webserver/Webserver.cpp @@ -83,13 +83,19 @@ ****************************************************************************************************/ #include "RepRapFirmware.h" +#include "Webserver.h" +#include "NetworkTransaction.h" +#include "Platform.h" +#include "Network.h" +#include "RepRap.h" +#include "GCodes/GCodes.h" +#include "PrintMonitor.h" //*************************************************************************************************** const char* overflowResponse = "overflow"; const char* badEscapeResponse = "bad escape"; - //******************************************************************************************** // //**************************** Generic Webserver implementation ****************************** @@ -110,7 +116,7 @@ void Webserver::Init() // initialise the webserver class longWait = platform->Time(); webserverActive = true; - readingConnection = nullptr; + readingConnection = NoConnection; // initialise all protocol handlers httpInterpreter->ResetState(); @@ -221,7 +227,7 @@ void Webserver::Spin() // calling either Commit(), Discard() or Defer() if (interpreter->CharFromClient(c)) { - readingConnection = nullptr; + readingConnection = NoConnection; break; } } @@ -231,18 +237,18 @@ void Webserver::Spin() // message length exceeds the TCP MSS. Notify the current ProtocolInterpreter about this, // which will remove the current transaction too interpreter->NoMoreDataAvailable(); - readingConnection = nullptr; + readingConnection = NoConnection; break; } } } } - else if (readingConnection != nullptr) + else if (readingConnection != NoConnection) { // We failed to find a transaction for a reading connection. // This should never happen, but if it does, terminate this connection instantly platform->Message(HOST_MESSAGE, "Error: Transaction for reading connection not found\n"); - readingConnection->Terminate(); + Network::Terminate(readingConnection); } network->Unlock(); // unlock LWIP again } @@ -339,10 +345,10 @@ uint16_t Webserver::GetGCodeBufferSpace(const WebSource source) const // Handle immediate disconnects here (cs will be freed after this call) // May be called by ISR, but not while LwIP is NOT locked -void Webserver::ConnectionLost(const ConnectionState *cs) +void Webserver::ConnectionLost(Connection conn) { // Inform protocol handlers that this connection has been lost - uint16_t localPort = cs->GetLocalPort(); + uint16_t localPort = Network::GetLocalPort(conn); ProtocolInterpreter *interpreter; switch (localPort) { @@ -373,14 +379,14 @@ void Webserver::ConnectionLost(const ConnectionState *cs) // Print some debug information and notify the protocol interpreter if (reprap.Debug(moduleWebserver)) { - platform->MessageF(HOST_MESSAGE, "ConnectionLost called for local port %d (remote port %d)\n", localPort, cs->GetRemotePort()); + platform->MessageF(HOST_MESSAGE, "ConnectionLost called for local port %d (remote port %d)\n", localPort, Network::GetRemotePort(conn)); } - interpreter->ConnectionLost(cs); + interpreter->ConnectionLost(conn); // Don't process any more data from this connection if has gone down - if (readingConnection == cs) + if (readingConnection == conn) { - readingConnection = nullptr; + readingConnection = NoConnection; } } @@ -539,7 +545,7 @@ Webserver::HttpInterpreter::HttpInterpreter(Platform *p, Webserver *ws, Network { gcodeReadIndex = gcodeWriteIndex = 0; gcodeReply = new OutputStack(); - deferredRequestConnection = nullptr; + deferredRequestConnection = NoConnection; seq = 0; } @@ -900,7 +906,7 @@ void Webserver::HttpInterpreter::SendJsonResponse(const char* command) if (!OutputBuffer::Allocate(jsonResponse)) { // Reset the connection immediately if we cannot write any data. Should never happen - webserver->currentTransaction->GetConnection()->Terminate(); + Network::Terminate(webserver->currentTransaction->GetConnection()); return; } @@ -1060,7 +1066,7 @@ void Webserver::HttpInterpreter::GetJsonResponse(const char* request, OutputBuff } else if (StringEquals(request, "fileinfo")) { - if (deferredRequestConnection != nullptr) + if (deferredRequestConnection != NoConnection) { // Don't allow multiple deferred requests to be processed at once webserver->currentTransaction->Defer(DeferralMode::ResetData); @@ -1133,17 +1139,17 @@ void Webserver::HttpInterpreter::NoMoreDataAvailable() } // May be called from ISR! -void Webserver::HttpInterpreter::ConnectionLost(const ConnectionState *cs) +void Webserver::HttpInterpreter::ConnectionLost(Connection conn) { // Make sure deferred requests are cancelled - if (deferredRequestConnection == cs) + if (deferredRequestConnection == conn) { reprap.GetPrintMonitor()->StopParsing(filenameBeingProcessed); - deferredRequestConnection = nullptr; + deferredRequestConnection = NoConnection; } // If we couldn't read an entire request from a connection, reset our state here again - if (webserver->readingConnection == cs) + if (webserver->readingConnection == conn) { ResetState(); } @@ -1152,8 +1158,8 @@ void Webserver::HttpInterpreter::ConnectionLost(const ConnectionState *cs) // because the client *might* have two instances of the web interface running. if (uploadState == uploadOK) { - const uint32_t remoteIP = cs->GetRemoteIP(); - const uint16_t remotePort = cs->GetRemotePort(); + const uint32_t remoteIP = Network::GetRemoteIP(conn); + const uint16_t remotePort = Network::GetRemotePort(conn); for(size_t i = 0; i < numSessions; i++) { if (sessions[i].ip == remoteIP && sessions[i].isPostUploading && sessions[i].postPort == remotePort) @@ -1191,7 +1197,7 @@ bool Webserver::HttpInterpreter::CanParseData() // Are we still processing a deferred request? if (deferredRequestConnection == webserver->currentTransaction->GetConnection()) { - if (deferredRequestConnection->IsConnected()) + if (Network::IsConnected(deferredRequestConnection)) { // Process more of this request. If it doesn't finish this time, it will be appended to the list // of ready transactions again, which will ensure it can be processed later again @@ -1557,8 +1563,8 @@ bool Webserver::HttpInterpreter::ProcessMessage() } else if (IsAuthenticated() && StringEquals(commandWords[0], "POST")) { - bool isUploadRequest = (StringEquals(commandWords[1], KO_START "upload")); - isUploadRequest |= (commandWords[1][0] == '/' && StringEquals(commandWords[1] + 1, KO_START "upload")); + const bool isUploadRequest = (StringEquals(commandWords[1], KO_START "upload")) + || (commandWords[1][0] == '/' && StringEquals(commandWords[1] + 1, KO_START "upload")); if (isUploadRequest) { if (numQualKeys > 0 && StringEquals(qualifiers[0].key, "name")) @@ -1876,7 +1882,7 @@ void Webserver::HttpInterpreter::HandleGCodeReply(const char *reply) void Webserver::HttpInterpreter::ProcessDeferredRequest() { OutputBuffer *jsonResponse = nullptr; - const ConnectionState *lastDeferredConnection = deferredRequestConnection; + const Connection lastDeferredConnection = deferredRequestConnection; // At the moment only file info requests are deferred. // Parsing the file may take a while, so keep LwIP running while we're waiting @@ -1891,7 +1897,7 @@ void Webserver::HttpInterpreter::ProcessDeferredRequest() NetworkTransaction *transaction = webserver->currentTransaction; if (gotFileInfo) { - deferredRequestConnection = nullptr; + deferredRequestConnection = NoConnection; // Got it - send the response now transaction->Write("HTTP/1.1 200 OK\n"); @@ -1981,11 +1987,11 @@ void Webserver::FtpInterpreter::ConnectionEstablished() } // May be called from ISR! -void Webserver::FtpInterpreter::ConnectionLost(const ConnectionState *cs) +void Webserver::FtpInterpreter::ConnectionLost(Connection conn) { connectedClients--; - if (cs->GetLocalPort() != FTP_PORT) + if (Network::GetLocalPort(conn) != FTP_PORT) { // Did everything work out? Usually this is only called for uploads if (network->AcquireFTPTransaction()) @@ -1993,7 +1999,7 @@ void Webserver::FtpInterpreter::ConnectionLost(const ConnectionState *cs) webserver->currentTransaction = network->GetTransaction(); if (state == doingPasvIO) { - if (uploadState != uploadError && !cs->IsTerminated()) + if (uploadState != uploadError && !Network::IsTerminated(conn)) { SendReply(226, "Transfer complete."); FinishUpload(0); @@ -2615,7 +2621,7 @@ void Webserver::TelnetInterpreter::ConnectionEstablished() } // May be called from ISR! -void Webserver::TelnetInterpreter::ConnectionLost(const ConnectionState *cs) +void Webserver::TelnetInterpreter::ConnectionLost(Connection conn) { connectedClients--; if (connectedClients == 0) diff --git a/src/Webserver/Webserver.h b/src/Webserver/Webserver.h new file mode 100644 index 00000000..ef14bcab --- /dev/null +++ b/src/Webserver/Webserver.h @@ -0,0 +1,385 @@ +/**************************************************************************************************** + +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 + +#include "NetworkDefs.h" +#include "RepRapFirmware.h" +#include "MessageType.h" +#include "Storage/FileData.h" + +/* Generic values */ + +const size_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2 + +/* 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(Connection conn /*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(Connection conn /*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(Connection conn /*const ConnectionState *cs*/) override; + 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) + volatile Connection 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(Connection conn /*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(Connection conn /*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; + volatile Connection 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 |