/* * OutputMemory.cpp * * Created on: 10 Jan 2016 * Authors: David and Christian */ #include "OutputMemory.h" #include "Platform.h" #include "RepRap.h" #include /*static*/ OutputBuffer * volatile OutputBuffer::freeOutputBuffers = nullptr; // Messages may also be sent by ISRs, /*static*/ volatile size_t OutputBuffer::usedOutputBuffers = 0; // so make these volatile. /*static*/ volatile size_t OutputBuffer::maxUsedOutputBuffers = 0; //************************************************************************************************* // OutputBuffer class implementation void OutputBuffer::Append(OutputBuffer *other) { if (other != nullptr) { last->next = other; last = other->last; if (other->hadOverflow) { hadOverflow = true; } for (OutputBuffer *item = Next(); item != other; item = item->Next()) { item->last = last; } } } void OutputBuffer::IncreaseReferences(size_t refs) { if (refs > 0) { TaskCriticalSectionLocker lock; for(OutputBuffer *item = this; item != nullptr; item = item->Next()) { item->references += refs; item->isReferenced = true; } } } size_t OutputBuffer::Length() const { size_t totalLength = 0; for (const OutputBuffer *current = this; current != nullptr; current = current->Next()) { totalLength += current->DataLength(); } return totalLength; } char &OutputBuffer::operator[](size_t index) { // Get the right buffer to access OutputBuffer *itemToIndex = this; while (index > itemToIndex->DataLength()) { index -= itemToIndex->DataLength(); itemToIndex = itemToIndex->Next(); } // Return the char reference return itemToIndex->data[index]; } char OutputBuffer::operator[](size_t index) const { // Get the right buffer to access const OutputBuffer *itemToIndex = this; while (index > itemToIndex->DataLength()) { index -= itemToIndex->DataLength(); itemToIndex = itemToIndex->Next(); } // Return the char reference return itemToIndex->data[index]; } const char *OutputBuffer::Read(size_t len) { size_t offset = bytesRead; bytesRead += len; return data + offset; } size_t OutputBuffer::printf(const char *fmt, ...) { char formatBuffer[FORMAT_STRING_LENGTH]; va_list vargs; va_start(vargs, fmt); SafeVsnprintf(formatBuffer, ARRAY_SIZE(formatBuffer), fmt, vargs); va_end(vargs); return copy(formatBuffer); } size_t OutputBuffer::vprintf(const char *fmt, va_list vargs) { char formatBuffer[FORMAT_STRING_LENGTH]; SafeVsnprintf(formatBuffer, ARRAY_SIZE(formatBuffer), fmt, vargs); return cat(formatBuffer); } size_t OutputBuffer::catf(const char *fmt, ...) { char formatBuffer[FORMAT_STRING_LENGTH]; va_list vargs; va_start(vargs, fmt); SafeVsnprintf(formatBuffer, ARRAY_SIZE(formatBuffer), fmt, vargs); va_end(vargs); formatBuffer[ARRAY_UPB(formatBuffer)] = 0; return cat(formatBuffer); } size_t OutputBuffer::copy(const char c) { // Unlink existing entries before starting the copy process if (next != nullptr) { ReleaseAll(next); next = nullptr; last = this; } // Set the data data[0] = c; dataLength = 1; return 1; } size_t OutputBuffer::copy(const char *src) { return copy(src, strlen(src)); } size_t OutputBuffer::copy(const char *src, size_t len) { // Unlink existing entries before starting the copy process if (next != nullptr) { ReleaseAll(next); next = nullptr; last = this; } dataLength = 0; return cat(src, len); } size_t OutputBuffer::cat(const char c) { // See if we can append a char if (last->dataLength == OUTPUT_BUFFER_SIZE) { // No - allocate a new item and copy the data OutputBuffer *nextBuffer; if (!Allocate(nextBuffer)) { // We cannot store any more data hadOverflow = true; return 0; } nextBuffer->references = references; nextBuffer->copy(c); // Link the new item to this list last->next = nextBuffer; for (OutputBuffer *item = this; item != nextBuffer; item = item->Next()) { item->last = nextBuffer; } } else { // Yes - we have enough space left last->data[last->dataLength++] = c; } return 1; } size_t OutputBuffer::cat(const char *src) { return cat(src, strlen(src)); } size_t OutputBuffer::cat(const char *src, size_t len) { size_t copied = 0; while (copied < len) { if (last->dataLength == OUTPUT_BUFFER_SIZE) { // The last buffer is full OutputBuffer *nextBuffer; if (!Allocate(nextBuffer)) { // We cannot store any more data, stop here hadOverflow = true; break; } nextBuffer->references = references; last->next = nextBuffer; last = nextBuffer->last; for (OutputBuffer *item = Next(); item != nextBuffer; item = item->Next()) { item->last = last; } } const size_t copyLength = min(len - copied, OUTPUT_BUFFER_SIZE - last->dataLength); memcpy(last->data + last->dataLength, src + copied, copyLength); last->dataLength += copyLength; copied += copyLength; } return copied; } size_t OutputBuffer::cat(StringRef &str) { return cat(str.c_str(), str.strlen()); } // Encode a string in JSON format and append it to a string buffer and return the number of bytes written size_t OutputBuffer::EncodeString(const char *src, size_t srcLength, bool allowControlChars, bool encapsulateString) { size_t bytesWritten = 0; if (encapsulateString) { bytesWritten += cat('"'); } if (srcLength != 0) { size_t srcPointer = 1; char c = *src++; while (srcPointer <= srcLength && c != 0 && (c >= ' ' || allowControlChars)) { char esc; switch (c) { case '\r': esc = 'r'; break; case '\n': esc = 'n'; break; case '\t': esc = 't'; break; case '"': case '\\': #if 1 // In theory we should escape '/' as well. However, we never used to, and doing so confuses PanelDue. // This will be fixed in PanelDue firmware version 1.15, but in the mean time, don't escape '/'. #else case '/': #endif esc = c; break; default: esc = 0; break; } if (esc != 0) { bytesWritten += cat('\\'); bytesWritten += cat(esc); } else { bytesWritten += cat(c); } c = *src++; srcPointer++; } } if (encapsulateString) { bytesWritten += cat('"'); } return bytesWritten; } size_t OutputBuffer::EncodeString(const StringRef& str, bool allowControlChars, bool encapsulateString) { return EncodeString(str.c_str(), str.Capacity(), encapsulateString); } size_t OutputBuffer::EncodeReply(OutputBuffer *src, bool allowControlChars) { size_t bytesWritten = cat('"'); while (src != nullptr) { bytesWritten += EncodeString(src->Data(), src->DataLength(), allowControlChars, false); src = Release(src); } bytesWritten += cat('"'); return bytesWritten; } // Write all the data to file, but don't release the buffers // Returns true if successful bool OutputBuffer::WriteToFile(FileData& f) const { bool endedInNewline = false; const OutputBuffer *current = this; do { if (current->dataLength != 0) { if (!f.Write(current->data, current->dataLength)) { return false; } endedInNewline = current->data[current->dataLength - 1] == '\n'; } current = current->Next(); } while (current != nullptr); if (!endedInNewline) { return f.Write('\n'); } return true; } // Initialise the output buffers manager /*static*/ void OutputBuffer::Init() { freeOutputBuffers = nullptr; for (size_t i = 0; i < OUTPUT_BUFFER_COUNT; i++) { freeOutputBuffers = new OutputBuffer(freeOutputBuffers); } } // Allocates an output buffer instance which can be used for (large) string outputs. This must be thread safe. Not safe to call from interrupts! /*static*/ bool OutputBuffer::Allocate(OutputBuffer *&buf) { { TaskCriticalSectionLocker lock; buf = freeOutputBuffers; if (buf != nullptr) { freeOutputBuffers = buf->next; usedOutputBuffers++; if (usedOutputBuffers > maxUsedOutputBuffers) { maxUsedOutputBuffers = usedOutputBuffers; } } } if (buf == nullptr) { reprap.GetPlatform().LogError(ErrorCode::OutputStarvation); return false; } buf->next = nullptr; buf->last = buf; buf->dataLength = buf->bytesRead = 0; buf->references = 1; // assume it's only used once by default buf->isReferenced = false; buf->hadOverflow = false; return true; } // Get the number of bytes left for continuous writing /*static*/ size_t OutputBuffer::GetBytesLeft(const OutputBuffer *writingBuffer) { const size_t freeOutputBuffers = OUTPUT_BUFFER_COUNT - usedOutputBuffers; if (writingBuffer == nullptr) { // Only return the total number of bytes left return freeOutputBuffers * OUTPUT_BUFFER_SIZE; } // We're doing a possibly long response like a filelist const size_t bytesLeft = OUTPUT_BUFFER_SIZE - writingBuffer->last->DataLength(); if (freeOutputBuffers < RESERVED_OUTPUT_BUFFERS) { // Keep some space left to encapsulate the responses (e.g. via an HTTP header) return bytesLeft; } return bytesLeft + (freeOutputBuffers - RESERVED_OUTPUT_BUFFERS) * OUTPUT_BUFFER_SIZE; } // Truncate an output buffer to free up more memory. Returns the number of released bytes. /*static */ size_t OutputBuffer::Truncate(OutputBuffer *buffer, size_t bytesNeeded) { // Can we free up space from this chain? Don't break it up if it's referenced anywhere else if (buffer == nullptr || buffer->Next() == nullptr || buffer->IsReferenced()) { // No return 0; } // Yes - free up the last entries size_t releasedBytes = 0; OutputBuffer *previousItem; do { // Get two the last entries from the chain previousItem = buffer; OutputBuffer *lastItem = previousItem->Next(); while (lastItem->Next() != nullptr) { previousItem = lastItem; lastItem = lastItem->Next(); } // Unlink and free the last entry previousItem->next = nullptr; Release(lastItem); releasedBytes += OUTPUT_BUFFER_SIZE; } while (previousItem != buffer && releasedBytes < bytesNeeded); // Update all the references to the last item for(OutputBuffer *item = buffer; item != nullptr; item = item->Next()) { item->last = previousItem; } return releasedBytes; } // Releases an output buffer instance and returns the next entry from the chain /*static */ OutputBuffer *OutputBuffer::Release(OutputBuffer *buf) { TaskCriticalSectionLocker lock; OutputBuffer * const nextBuffer = buf->next; // If this one is reused by another piece of code, don't free it up if (buf->references > 1) { buf->references--; buf->bytesRead = 0; } else { // Otherwise prepend it to the list of free output buffers again buf->next = freeOutputBuffers; freeOutputBuffers = buf; usedOutputBuffers--; } return nextBuffer; } /*static */ void OutputBuffer::ReleaseAll(OutputBuffer *buf) { while (buf != nullptr) { buf = Release(buf); } } /*static*/ void OutputBuffer::Diagnostics(MessageType mtype) { reprap.GetPlatform().MessageF(mtype, "Used output buffers: %d of %d (%d max)\n", usedOutputBuffers, OUTPUT_BUFFER_COUNT, maxUsedOutputBuffers); } //************************************************************************************************* // OutputStack class implementation // Push an OutputBuffer chain to the stack void OutputStack::Push(OutputBuffer *buffer) volatile { if (count == OUTPUT_STACK_DEPTH) { OutputBuffer::ReleaseAll(buffer); reprap.GetPlatform().LogError(ErrorCode::OutputStackOverflow); return; } if (buffer != nullptr) { buffer->whenQueued = millis(); TaskCriticalSectionLocker lock; items[count++] = buffer; } } // Pop an OutputBuffer chain or return nullptr if none is available OutputBuffer *OutputStack::Pop() volatile { if (count == 0) { return nullptr; } TaskCriticalSectionLocker lock; OutputBuffer *item = items[0]; for(size_t i = 1; i < count; i++) { items[i - 1] = items[i]; } count--; return item; } // Returns the first item from the stack or NULL if none is available OutputBuffer *OutputStack::GetFirstItem() const volatile { if (count == 0) { return nullptr; } return items[0]; } // Set the first item of the stack. If it's NULL, then the first item will be removed void OutputStack::SetFirstItem(OutputBuffer *buffer) volatile { TaskCriticalSectionLocker lock; if (buffer == nullptr) { // If buffer is NULL, then the first item is removed from the stack for(size_t i = 1; i < count; i++) { items[i - 1] = items[i]; } count--; } else { // Else only the first item is updated items[0] = buffer; buffer->whenQueued = millis(); } } // Returns the last item from the stack or NULL if none is available OutputBuffer *OutputStack::GetLastItem() const volatile { if (count == 0) { return nullptr; } return items[count - 1]; } // Get the total length of all queued buffers size_t OutputStack::DataLength() const volatile { size_t totalLength = 0; TaskCriticalSectionLocker lock; for(size_t i = 0; i < count; i++) { totalLength += items[i]->Length(); } return totalLength; } // Append another OutputStack to this instance. If no more space is available, // all OutputBuffers that can't be added are automatically released void OutputStack::Append(volatile OutputStack& stack) volatile { for(size_t i = 0; i < stack.count; i++) { if (count < OUTPUT_STACK_DEPTH) { items[count++] = stack.items[i]; } else { reprap.GetPlatform().LogError(ErrorCode::OutputStackOverflow); OutputBuffer::ReleaseAll(stack.items[i]); } } } // Increase the number of references for each OutputBuffer on the stack void OutputStack::IncreaseReferences(size_t num) volatile { TaskCriticalSectionLocker lock; for(size_t i = 0; i < count; i++) { items[i]->IncreaseReferences(num); } } // Release all buffers and clean up void OutputStack::ReleaseAll() volatile { for(size_t i = 0; i < count; i++) { OutputBuffer::ReleaseAll(items[i]); } count = 0; } // End