未验证 提交 c720c0d9 编写于 作者: D david gauchard 提交者: GitHub

Stream::send() (#6979)

上级 4cc14728
...@@ -26,15 +26,15 @@ ...@@ -26,15 +26,15 @@
class Client: public Stream { class Client: public Stream {
public: public:
virtual int connect(IPAddress ip, uint16_t port) =0; virtual int connect(IPAddress ip, uint16_t port) = 0;
virtual int connect(const char *host, uint16_t port) =0; virtual int connect(const char *host, uint16_t port) = 0;
virtual size_t write(uint8_t) =0; virtual size_t write(uint8_t) override = 0;
virtual size_t write(const uint8_t *buf, size_t size) =0; virtual size_t write(const uint8_t *buf, size_t size) override = 0;
virtual int available() = 0; virtual int available() override = 0;
virtual int read() = 0; virtual int read() override = 0;
virtual int read(uint8_t *buf, size_t size) = 0; virtual int read(uint8_t *buf, size_t size) override = 0;
virtual int peek() = 0; virtual int peek() override = 0;
virtual void flush() = 0; virtual void flush() override = 0;
virtual void stop() = 0; virtual void stop() = 0;
virtual uint8_t connected() = 0; virtual uint8_t connected() = 0;
virtual operator bool() = 0; virtual operator bool() = 0;
......
...@@ -66,7 +66,7 @@ int File::read() { ...@@ -66,7 +66,7 @@ int File::read() {
return result; return result;
} }
size_t File::read(uint8_t* buf, size_t size) { int File::read(uint8_t* buf, size_t size) {
if (!_p) if (!_p)
return 0; return 0;
......
...@@ -67,13 +67,14 @@ public: ...@@ -67,13 +67,14 @@ public:
size_t readBytes(char *buffer, size_t length) override { size_t readBytes(char *buffer, size_t length) override {
return read((uint8_t*)buffer, length); return read((uint8_t*)buffer, length);
} }
size_t read(uint8_t* buf, size_t size); int read(uint8_t* buf, size_t size) override;
bool seek(uint32_t pos, SeekMode mode); bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) { bool seek(uint32_t pos) {
return seek(pos, SeekSet); return seek(pos, SeekSet);
} }
size_t position() const; size_t position() const;
size_t size() const; size_t size() const;
virtual ssize_t streamRemaining() override { return (ssize_t)size() - (ssize_t)position(); }
void close(); void close();
operator bool() const; operator bool() const;
const char* name() const; const char* name() const;
...@@ -84,6 +85,7 @@ public: ...@@ -84,6 +85,7 @@ public:
bool isDirectory() const; bool isDirectory() const;
// Arduino "class SD" methods for compatibility // Arduino "class SD" methods for compatibility
//TODO use stream::send / check read(buf,size) result
template<typename T> size_t write(T &src){ template<typename T> size_t write(T &src){
uint8_t obuf[256]; uint8_t obuf[256];
size_t doneLen = 0; size_t doneLen = 0;
......
...@@ -30,7 +30,7 @@ class FileImpl { ...@@ -30,7 +30,7 @@ class FileImpl {
public: public:
virtual ~FileImpl() { } virtual ~FileImpl() { }
virtual size_t write(const uint8_t *buf, size_t size) = 0; virtual size_t write(const uint8_t *buf, size_t size) = 0;
virtual size_t read(uint8_t* buf, size_t size) = 0; virtual int read(uint8_t* buf, size_t size) = 0;
virtual void flush() = 0; virtual void flush() = 0;
virtual bool seek(uint32_t pos, SeekMode mode) = 0; virtual bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0; virtual size_t position() const = 0;
......
...@@ -135,16 +135,45 @@ public: ...@@ -135,16 +135,45 @@ public:
// return -1 when data is unvailable (arduino api) // return -1 when data is unvailable (arduino api)
return uart_peek_char(_uart); return uart_peek_char(_uart);
} }
virtual bool hasPeekBufferAPI () const override
{
return true;
}
// return a pointer to available data buffer (size = available())
// semantic forbids any kind of read() before calling peekConsume()
const char* peekBuffer () override
{
return uart_peek_buffer(_uart);
}
// return number of byte accessible by peekBuffer()
size_t peekAvailable () override
{
return uart_peek_available(_uart);
}
// consume bytes after use (see peekBuffer)
void peekConsume (size_t consume) override
{
return uart_peek_consume(_uart, consume);
}
int read(void) override int read(void) override
{ {
// return -1 when data is unvailable (arduino api) // return -1 when data is unvailable (arduino api)
return uart_read_char(_uart); return uart_read_char(_uart);
} }
// ::read(buffer, size): same as readBytes without timeout // ::read(buffer, size): same as readBytes without timeout
size_t read(char* buffer, size_t size) int read(char* buffer, size_t size)
{ {
return uart_read(_uart, buffer, size); return uart_read(_uart, buffer, size);
} }
int read(uint8_t* buffer, size_t size) override
{
return uart_read(_uart, (char*)buffer, size);
}
size_t readBytes(char* buffer, size_t size) override; size_t readBytes(char* buffer, size_t size) override;
size_t readBytes(uint8_t* buffer, size_t size) override size_t readBytes(uint8_t* buffer, size_t size) override
{ {
......
...@@ -33,15 +33,7 @@ ...@@ -33,15 +33,7 @@
/* default implementation: may be overridden */ /* default implementation: may be overridden */
size_t Print::write(const uint8_t *buffer, size_t size) { size_t Print::write(const uint8_t *buffer, size_t size) {
IAMSLOW();
#ifdef DEBUG_ESP_CORE
static char not_the_best_way [] PROGMEM STORE_ATTR = "Print::write(data,len) should be overridden for better efficiency\r\n";
static bool once = false;
if (!once) {
once = true;
os_printf_plus(not_the_best_way);
}
#endif
size_t n = 0; size_t n = 0;
while (size--) { while (size--) {
......
...@@ -111,6 +111,10 @@ class Print { ...@@ -111,6 +111,10 @@ class Print {
size_t println(void); size_t println(void);
virtual void flush() { /* Empty implementation for backward compatibility */ } virtual void flush() { /* Empty implementation for backward compatibility */ }
// by default write timeout is possible (outgoing data from network,serial..)
// (children can override to false (like String))
virtual bool outputCanTimeout () { return true; }
}; };
template<> size_t Print::printNumber(double number, uint8_t digits); template<> size_t Print::printNumber(double number, uint8_t digits);
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <Stream.h> #include <Stream.h>
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait #define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field #define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field
...@@ -210,6 +211,8 @@ float Stream::parseFloat(char skipChar) { ...@@ -210,6 +211,8 @@ float Stream::parseFloat(char skipChar) {
// the buffer is NOT null terminated. // the buffer is NOT null terminated.
// //
size_t Stream::readBytes(char *buffer, size_t length) { size_t Stream::readBytes(char *buffer, size_t length) {
IAMSLOW();
size_t count = 0; size_t count = 0;
while(count < length) { while(count < length) {
int c = timedRead(); int c = timedRead();
...@@ -258,3 +261,20 @@ String Stream::readStringUntil(char terminator) { ...@@ -258,3 +261,20 @@ String Stream::readStringUntil(char terminator) {
} }
return ret; return ret;
} }
// read what can be read, immediate exit on unavailable data
// prototype similar to Arduino's `int Client::read(buf, len)`
int Stream::read (uint8_t* buffer, size_t maxLen)
{
IAMSLOW();
size_t nbread = 0;
while (nbread < maxLen && available())
{
int c = read();
if (c == -1)
break;
buffer[nbread++] = read();
}
return nbread;
}
...@@ -22,10 +22,13 @@ ...@@ -22,10 +22,13 @@
#ifndef Stream_h #ifndef Stream_h
#define Stream_h #define Stream_h
#include <debug.h>
#include <inttypes.h> #include <inttypes.h>
#include "Print.h" #include <Print.h>
#include <PolledTimeout.h>
#include <sys/types.h> // ssize_t
// compatability macros for testing // compatibility macros for testing
/* /*
#define getInt() parseInt() #define getInt() parseInt()
#define getInt(skipChar) parseInt(skipchar) #define getInt(skipChar) parseInt(skipchar)
...@@ -35,6 +38,15 @@ ...@@ -35,6 +38,15 @@
readBytesBetween( pre_string, terminator, buffer, length) readBytesBetween( pre_string, terminator, buffer, length)
*/ */
// Arduino `Client: public Stream` class defines `virtual int read(uint8_t *buf, size_t size) = 0;`
// This function is now imported into `Stream::` for `Stream::send*()`.
// Other classes inheriting from `Stream::` and implementing `read(uint8_t *buf, size_t size)`
// must consequently use `int` as return type, namely Hardware/SoftwareSerial, FileSystems...
#define STREAM_READ_RETURNS_INT 1
// Stream::send API is present
#define STREAMSEND_API 1
class Stream: public Print { class Stream: public Print {
protected: protected:
unsigned long _timeout = 1000; // number of milliseconds to wait for the next char before aborting timed read unsigned long _timeout = 1000; // number of milliseconds to wait for the next char before aborting timed read
...@@ -53,6 +65,7 @@ class Stream: public Print { ...@@ -53,6 +65,7 @@ class Stream: public Print {
// parsing methods // parsing methods
void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
unsigned long getTimeout () const { return _timeout; }
bool find(const char *target); // reads data from the stream until the target string is found bool find(const char *target); // reads data from the stream until the target string is found
bool find(uint8_t *target) { bool find(uint8_t *target) {
...@@ -102,12 +115,114 @@ class Stream: public Print { ...@@ -102,12 +115,114 @@ class Stream: public Print {
virtual String readString(); virtual String readString();
String readStringUntil(char terminator); String readStringUntil(char terminator);
virtual int read (uint8_t* buffer, size_t len);
int read (char* buffer, size_t len) { return read((uint8_t*)buffer, len); }
//////////////////// extension: direct access to input buffer
// to provide when possible a pointer to available data for read
// informs user and ::to*() on effective buffered peek API implementation
// by default: not available
virtual bool hasPeekBufferAPI () const { return false; }
// returns number of byte accessible by peekBuffer()
virtual size_t peekAvailable () { return 0; }
// returns a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of ::read()
// - after calling peekBuffer()
// - and before calling peekConsume()
virtual const char* peekBuffer () { return nullptr; }
// consumes bytes after peekBuffer() use
// (then ::read() is allowed)
virtual void peekConsume (size_t consume) { (void)consume; }
// by default read timeout is possible (incoming data from network,serial..)
// children can override to false (like String::)
virtual bool inputCanTimeout () { return true; }
// (outputCanTimeout() is defined in Print::)
////////////////////////
//////////////////// extensions: Streaming streams to streams
// Stream::send*()
//
// Stream::send*() uses 1-copy transfers when peekBuffer API is
// available, or makes a regular transfer through a temporary buffer.
//
// - for efficiency, Stream classes should implement peekAPI when
// possible
// - for an efficient timeout management, Print/Stream classes
// should implement {output,input}CanTimeout()
using oneShotMs = esp8266::polledTimeout::oneShotFastMs;
static constexpr int temporaryStackBufferSize = 64;
// ::send*() methods:
// - always stop before timeout when "no-more-input-possible-data"
// or "no-more-output-possible-data" condition is met
// - always return number of transfered bytes
// When result is 0 or less than requested maxLen, Print::getLastSend()
// contains an error reason.
// transfers already buffered / immediately available data (no timeout)
// returns number of transfered bytes
size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); }
size_t sendAvailable (Print& to) { return sendAvailable(&to); }
// transfers data until timeout
// returns number of transfered bytes
size_t sendAll (Print* to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, -1, timeoutMs); }
size_t sendAll (Print& to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendAll(&to, timeoutMs); }
// transfers data until a char is encountered (the char is swallowed but not transfered) with timeout
// returns number of transfered bytes
size_t sendUntil (Print* to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, readUntilChar, timeoutMs); }
size_t sendUntil (Print& to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendUntil(&to, readUntilChar, timeoutMs); }
// transfers data until requested size or timeout
// returns number of transfered bytes
size_t sendSize (Print* to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, maxLen, -1, timeoutMs); }
size_t sendSize (Print& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); }
// remaining size (-1 by default = unknown)
virtual ssize_t streamRemaining () { return -1; }
enum class Report
{
Success = 0,
TimedOut,
ReadError,
WriteError,
ShortOperation,
};
Report getLastSendReport () const { return _sendReport; }
protected:
size_t sendGeneric (Print* to,
const ssize_t len = -1,
const int readUntilChar = -1,
oneShotMs::timeType timeoutMs = oneShotMs::neverExpires /* neverExpires=>getTimeout() */);
size_t SendGenericPeekBuffer(Print* to, const ssize_t len, const int readUntilChar, const oneShotMs::timeType timeoutMs);
size_t SendGenericRegularUntil(Print* to, const ssize_t len, const int readUntilChar, const oneShotMs::timeType timeoutMs);
size_t SendGenericRegular(Print* to, const ssize_t len, const oneShotMs::timeType timeoutMs);
void setReport (Report report) { _sendReport = report; }
private:
Report _sendReport = Report::Success;
//////////////////// end of extensions
protected: protected:
long parseInt(char skipChar); // as above but the given skipChar is ignored long parseInt(char skipChar); // as parseInt() but the given skipChar is ignored
// as above but the given skipChar is ignored
// this allows format characters (typically commas) in values to be ignored // this allows format characters (typically commas) in values to be ignored
float parseFloat(char skipChar); // as above but the given skipChar is ignored float parseFloat(char skipChar); // as parseFloat() but the given skipChar is ignored
}; };
#endif #endif
/*
StreamDev.h - Stream helpers
Copyright (c) 2019 David Gauchard. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __STREAMDEV_H
#define __STREAMDEV_H
#include <limits>
#include <esp_priv.h>
#include <StreamString.h>
///////////////////////////////////////////////
// /dev/null
// - black hole as output, swallow everything, availableForWrite = infinite
// - black hole as input, nothing to read, available = 0
class StreamNull: public Stream
{
public:
// Print
virtual size_t write(uint8_t) override
{
return 1;
}
virtual size_t write(const uint8_t* buffer, size_t size) override
{
(void)buffer;
(void)size;
return size;
}
virtual int availableForWrite() override
{
return std::numeric_limits<int16_t>::max();
}
// Stream
virtual int available() override
{
return 0;
}
virtual int read() override
{
return -1;
}
virtual int peek() override
{
return -1;
}
virtual size_t readBytes(char* buffer, size_t len) override
{
(void)buffer;
(void)len;
return 0;
}
virtual int read(uint8_t* buffer, size_t len) override
{
(void)buffer;
(void)len;
return 0;
}
virtual bool outputCanTimeout() override
{
return false;
}
virtual bool inputCanTimeout() override
{
return false;
}
virtual ssize_t streamRemaining() override
{
return 0;
}
};
///////////////////////////////////////////////
// /dev/zero
// - black hole as output, swallow everything, availableForWrite = infinite
// - big bang as input, gives infinity to read, available = infinite
class StreamZero: public StreamNull
{
protected:
char _zero;
public:
StreamZero(char zero = 0): _zero(zero) { }
// Stream
virtual int available() override
{
return std::numeric_limits<int16_t>::max();
}
virtual int read() override
{
return _zero;
}
virtual int peek() override
{
return _zero;
}
virtual size_t readBytes(char* buffer, size_t len) override
{
memset(buffer, _zero, len);
return len;
}
virtual int read(uint8_t* buffer, size_t len) override
{
memset((char*)buffer, _zero, len);
return len;
}
virtual ssize_t streamRemaining() override
{
return std::numeric_limits<int16_t>::max();
}
};
///////////////////////////////////////////////
// static buffer (in flash or ram)
// - black hole as output, swallow everything, availableForWrite = infinite
// - Stream buffer out as input, resettable
class StreamConstPtr: public StreamNull
{
protected:
const char* _buffer;
size_t _size;
bool _byteAddressable;
size_t _peekPointer = 0;
public:
StreamConstPtr(const String& string): _buffer(string.c_str()), _size(string.length()), _byteAddressable(true) { }
StreamConstPtr(const char* buffer, size_t size): _buffer(buffer), _size(size), _byteAddressable(__byteAddressable(buffer)) { }
StreamConstPtr(const uint8_t* buffer, size_t size): _buffer((const char*)buffer), _size(size), _byteAddressable(__byteAddressable(buffer)) { }
StreamConstPtr(const __FlashStringHelper* buffer, size_t size): _buffer(reinterpret_cast<const char*>(buffer)), _size(size), _byteAddressable(false) { }
StreamConstPtr(const __FlashStringHelper* text): _buffer(reinterpret_cast<const char*>(text)), _size(strlen_P((PGM_P)text)), _byteAddressable(false) { }
void resetPointer(int pointer = 0)
{
_peekPointer = pointer;
}
// Stream
virtual int available() override
{
return peekAvailable();
}
virtual int read() override
{
return _peekPointer < _size ? _buffer[_peekPointer++] : -1;
}
virtual int peek() override
{
return _peekPointer < _size ? _buffer[_peekPointer] : -1;
}
virtual size_t readBytes(char* buffer, size_t len) override
{
if (_peekPointer >= _size)
{
return 0;
}
size_t cpylen = std::min(_size - _peekPointer, len);
memcpy_P(buffer, _buffer + _peekPointer, cpylen); // whether byte adressible is true
_peekPointer += cpylen;
return cpylen;
}
virtual int read(uint8_t* buffer, size_t len) override
{
return readBytes((char*)buffer, len);
}
virtual ssize_t streamRemaining() override
{
return _size;
}
// peekBuffer
virtual bool hasPeekBufferAPI() const override
{
return _byteAddressable;
}
virtual size_t peekAvailable() override
{
return _peekPointer < _size ? _size - _peekPointer : 0;
}
virtual const char* peekBuffer() override
{
return _peekPointer < _size ? _buffer + _peekPointer : nullptr;
}
virtual void peekConsume(size_t consume) override
{
_peekPointer += consume;
}
};
///////////////////////////////////////////////
Stream& operator << (Stream& out, String& string);
Stream& operator << (Stream& out, Stream& stream);
Stream& operator << (Stream& out, StreamString& stream);
Stream& operator << (Stream& out, const char* text);
Stream& operator << (Stream& out, const __FlashStringHelper* text);
///////////////////////////////////////////////
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_STREAMDEV)
extern StreamNull devnull;
#endif
#endif // __STREAMDEV_H
/*
StreamDev.cpp - 1-copy transfer related methods
Copyright (c) 2019 David Gauchard. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
parsing functions based on TextFinder library by Michael Margolis
*/
#include <Arduino.h>
#include <StreamDev.h>
size_t Stream::sendGeneric(Print* to,
const ssize_t len,
const int readUntilChar,
const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
{
setReport(Report::Success);
if (len == 0)
{
return 0; // conveniently avoids timeout for no requested data
}
// There are two timeouts:
// - read (network, serial, ...)
// - write (network, serial, ...)
// However
// - getTimeout() is for reading only
// - there is no getOutputTimeout() api
// So we use getTimeout() for both,
// (also when inputCanTimeout() is false)
if (hasPeekBufferAPI())
{
return SendGenericPeekBuffer(to, len, readUntilChar, timeoutMs);
}
if (readUntilChar >= 0)
{
return SendGenericRegularUntil(to, len, readUntilChar, timeoutMs);
}
return SendGenericRegular(to, len, timeoutMs);
}
size_t Stream::SendGenericPeekBuffer(Print* to, const ssize_t len, const int readUntilChar, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
{
// "neverExpires (default, impossible)" is translated to default timeout
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
// len==-1 => maxLen=0 <=> until starvation
const size_t maxLen = std::max((ssize_t)0, len);
size_t written = 0;
while (!maxLen || written < maxLen)
{
size_t avpk = peekAvailable();
if (avpk == 0 && !inputCanTimeout())
{
// no more data to read, ever
break;
}
size_t w = to->availableForWrite();
if (w == 0 && !to->outputCanTimeout())
{
// no more data can be written, ever
break;
}
w = std::min(w, avpk);
if (maxLen)
{
w = std::min(w, maxLen - written);
}
if (w)
{
const char* directbuf = peekBuffer();
bool foundChar = false;
if (readUntilChar >= 0)
{
const char* last = (const char*)memchr(directbuf, readUntilChar, w);
if (last)
{
w = std::min((size_t)(last - directbuf), w);
foundChar = true;
}
}
if (w && ((w = to->write(directbuf, w))))
{
peekConsume(w);
written += w;
if (maxLen)
{
timedOut.reset();
}
}
if (foundChar)
{
peekConsume(1);
break;
}
}
if (!w && !maxLen && readUntilChar < 0)
{
// nothing has been transferred and no specific condition is requested
break;
}
if (timedOut)
{
// either (maxLen>0) nothing has been transferred for too long
// or readUntilChar >= 0 but char is not encountered for too long
// or (maxLen=0) too much time has been spent here
break;
}
optimistic_yield(1000);
}
if (getLastSendReport() == Report::Success && maxLen > 0)
{
if (timeoutMs && timedOut)
{
setReport(Report::TimedOut);
}
else if ((ssize_t)written != len)
{
// This is happening when source cannot timeout (ex: a String)
// but has not enough data, or a dest has closed or cannot
// timeout but is too small (String, buffer...)
//
// Mark it as an error because user usually wants to get what is
// asked for.
setReport(Report::ShortOperation);
}
}
return written;
}
size_t Stream::SendGenericRegularUntil(Print* to, const ssize_t len, const int readUntilChar, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
{
// regular Stream API
// no other choice than reading byte by byte
// "neverExpires (default, impossible)" is translated to default timeout
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
// len==-1 => maxLen=0 <=> until starvation
const size_t maxLen = std::max((ssize_t)0, len);
size_t written = 0;
while (!maxLen || written < maxLen)
{
size_t avr = available();
if (avr == 0 && !inputCanTimeout())
{
// no more data to read, ever
break;
}
size_t w = to->availableForWrite();
if (w == 0 && !to->outputCanTimeout())
{
// no more data can be written, ever
break;
}
int c = read();
if (c != -1)
{
if (c == readUntilChar)
{
break;
}
w = to->write(c);
if (w != 1)
{
setReport(Report::WriteError);
break;
}
written += 1;
if (maxLen)
{
timedOut.reset();
}
}
if (!w && !maxLen && readUntilChar < 0)
{
// nothing has been transferred and no specific condition is requested
break;
}
if (timedOut)
{
// either (maxLen>0) nothing has been transferred for too long
// or readUntilChar >= 0 but char is not encountered for too long
// or (maxLen=0) too much time has been spent here
break;
}
optimistic_yield(1000);
}
if (getLastSendReport() == Report::Success && maxLen > 0)
{
if (timeoutMs && timedOut)
{
setReport(Report::TimedOut);
}
else if ((ssize_t)written != len)
{
// This is happening when source cannot timeout (ex: a String)
// but has not enough data, or a dest has closed or cannot
// timeout but is too small (String, buffer...)
//
// Mark it as an error because user usually wants to get what is
// asked for.
setReport(Report::ShortOperation);
}
}
return written;
}
size_t Stream::SendGenericRegular(Print* to, const ssize_t len, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
{
// regular Stream API
// use an intermediary buffer
// "neverExpires (default, impossible)" is translated to default timeout
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
// len==-1 => maxLen=0 <=> until starvation
const size_t maxLen = std::max((ssize_t)0, len);
size_t written = 0;
while (!maxLen || written < maxLen)
{
size_t avr = available();
if (avr == 0 && !inputCanTimeout())
{
// no more data to read, ever
break;
}
size_t w = to->availableForWrite();
if (w == 0 && !to->outputCanTimeout())
// no more data can be written, ever
{
break;
}
w = std::min(w, avr);
if (maxLen)
{
w = std::min(w, maxLen - written);
}
w = std::min(w, (decltype(w))temporaryStackBufferSize);
if (w)
{
char temp[w];
ssize_t r = read(temp, w);
if (r < 0)
{
setReport(Report::ReadError);
break;
}
w = to->write(temp, r);
written += w;
if ((size_t)r != w)
{
setReport(Report::WriteError);
break;
}
if (maxLen && w)
{
timedOut.reset();
}
}
if (!w && !maxLen)
{
// nothing has been transferred and no specific condition is requested
break;
}
if (timedOut)
{
// either (maxLen>0) nothing has been transferred for too long
// or readUntilChar >= 0 but char is not encountered for too long
// or (maxLen=0) too much time has been spent here
break;
}
optimistic_yield(1000);
}
if (getLastSendReport() == Report::Success && maxLen > 0)
{
if (timeoutMs && timedOut)
{
setReport(Report::TimedOut);
}
else if ((ssize_t)written != len)
{
// This is happening when source cannot timeout (ex: a String)
// but has not enough data, or a dest has closed or cannot
// timeout but is too small (String, buffer...)
//
// Mark it as an error because user usually wants to get what is
// asked for.
setReport(Report::ShortOperation);
}
}
return written;
}
Stream& operator << (Stream& out, String& string)
{
StreamConstPtr(string).sendAll(out);
return out;
}
Stream& operator << (Stream& out, StreamString& stream)
{
stream.sendAll(out);
return out;
}
Stream& operator << (Stream& out, Stream& stream)
{
if (stream.streamRemaining() < 0)
{
if (stream.inputCanTimeout())
{
// restrict with only what's buffered on input
stream.sendAvailable(out);
}
else
{
// take all what is in input
stream.sendAll(out);
}
}
else
{
stream.sendSize(out, stream.streamRemaining());
}
return out;
}
Stream& operator << (Stream& out, const char* text)
{
StreamConstPtr(text).sendAll(out);
return out;
}
Stream& operator << (Stream& out, const __FlashStringHelper* text)
{
StreamConstPtr(text).sendAll(out);
return out;
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_STREAMDEV)
StreamNull devnull;
#endif
/**
StreamString.cpp
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "StreamString.h"
size_t StreamString::write(const uint8_t *data, size_t size) {
if(size && data) {
const unsigned int newlen = length() + size;
if(reserve(newlen + 1)) {
memcpy((void *) (wbuffer() + len()), (const void *) data, size);
setLen(newlen);
*(wbuffer() + newlen) = 0x00; // add null for string end
return size;
}
DEBUGV(":stream2string: OOM (%d->%d)\n", length(), newlen+1);
}
return 0;
}
size_t StreamString::write(uint8_t data) {
return concat((char) data);
}
int StreamString::available() {
return length();
}
int StreamString::read() {
if(length()) {
char c = charAt(0);
remove(0, 1);
return c;
}
return -1;
}
int StreamString::peek() {
if(length()) {
char c = charAt(0);
return c;
}
return -1;
}
void StreamString::flush() {
}
/** /**
StreamString.h StreamString.h
Copyright (c) 2015 Markus Sattler. All rights reserved. Copyright (c) 2020 D. Gauchard. All rights reserved.
This file is part of the esp8266 core for Arduino environment. This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#ifndef STREAMSTRING_H_ #ifndef __STREAMSTRING_H
#define STREAMSTRING_H_ #define __STREAMSTRING_H
#include <limits>
#include "WString.h"
class StreamString: public Stream, public String { ///////////////////////////////////////////////////////////////
// S2Stream points to a String and makes it a Stream
// (it is also the helper for StreamString)
class S2Stream: public Stream
{
public: public:
size_t write(const uint8_t *buffer, size_t size) override;
size_t write(uint8_t data) override;
int available() override; S2Stream(String& string, int peekPointer = -1):
int read() override; string(&string), peekPointer(peekPointer)
int peek() override; {
void flush() override; }
S2Stream(String* string, int peekPointer = -1):
string(string), peekPointer(peekPointer)
{
}
virtual int available() override
{
return string->length();
}
virtual int availableForWrite() override
{
return std::numeric_limits<int16_t>::max();
}
virtual int read() override
{
if (peekPointer < 0)
{
// consume chars
if (string->length())
{
char c = string->charAt(0);
string->remove(0, 1);
return c;
}
}
else if (peekPointer < (int)string->length())
{
// return pointed and move pointer
return string->charAt(peekPointer++);
}
// everything is read
return -1;
}
virtual size_t write(uint8_t data) override
{
return string->concat((char)data);
}
virtual int read(uint8_t* buffer, size_t len) override
{
if (peekPointer < 0)
{
// string will be consumed
size_t l = std::min(len, (size_t)string->length());
memcpy(buffer, string->c_str(), l);
string->remove(0, l);
return l;
}
if (peekPointer >= (int)string->length())
{
return 0;
}
// only the pointer is moved
size_t l = std::min(len, (size_t)(string->length() - peekPointer));
memcpy(buffer, string->c_str() + peekPointer, l);
peekPointer += l;
return l;
}
virtual size_t write(const uint8_t* buffer, size_t len) override
{
return string->concat((const char*)buffer, len) ? len : 0;
}
virtual int peek() override
{
if (peekPointer < 0)
{
if (string->length())
{
return string->charAt(0);
}
}
else if (peekPointer < (int)string->length())
{
return string->charAt(peekPointer);
}
return -1;
}
virtual void flush() override
{
// nothing to do
}
virtual bool inputCanTimeout() override
{
return false;
}
virtual bool outputCanTimeout() override
{
return false;
}
//// Stream's peekBufferAPI
virtual bool hasPeekBufferAPI() const override
{
return true;
}
virtual size_t peekAvailable()
{
if (peekPointer < 0)
{
return string->length();
}
return string->length() - peekPointer;
}
virtual const char* peekBuffer() override
{
if (peekPointer < 0)
{
return string->c_str();
}
if (peekPointer < (int)string->length())
{
return string->c_str() + peekPointer;
}
return nullptr;
}
virtual void peekConsume(size_t consume) override
{
if (peekPointer < 0)
{
// string is really consumed
string->remove(0, consume);
}
else
{
// only the pointer is moved
peekPointer = std::min((size_t)string->length(), peekPointer + consume);
}
}
virtual ssize_t streamRemaining() override
{
return peekPointer < 0 ? string->length() : string->length() - peekPointer;
}
// calling setConsume() will consume bytes as the stream is read
// (enabled by default)
void setConsume()
{
peekPointer = -1;
}
// Reading this stream will mark the string as read without consuming
// (not enabled by default)
// Calling resetPointer() resets the read state and allows rereading.
void resetPointer(int pointer = 0)
{
peekPointer = pointer;
}
protected:
String* string;
int peekPointer; // -1:String is consumed / >=0:resettable pointer
}; };
#endif /* STREAMSTRING_H_ */ // StreamString is a S2Stream holding the String
class StreamString: public String, public S2Stream
{
protected:
void resetpp()
{
if (peekPointer > 0)
{
peekPointer = 0;
}
}
public:
StreamString(StreamString&& bro): String(bro), S2Stream(this) { }
StreamString(const StreamString& bro): String(bro), S2Stream(this) { }
// duplicate String contructors and operator=:
StreamString(const char* text = nullptr): String(text), S2Stream(this) { }
StreamString(const String& string): String(string), S2Stream(this) { }
StreamString(const __FlashStringHelper *str): String(str), S2Stream(this) { }
StreamString(String&& string): String(string), S2Stream(this) { }
StreamString(StringSumHelper&& sum): String(sum), S2Stream(this) { }
explicit StreamString(char c): String(c), S2Stream(this) { }
explicit StreamString(unsigned char c, unsigned char base = 10): String(c, base), S2Stream(this) { }
explicit StreamString(int i, unsigned char base = 10): String(i, base), S2Stream(this) { }
explicit StreamString(unsigned int i, unsigned char base = 10): String(i, base), S2Stream(this) { }
explicit StreamString(long l, unsigned char base = 10): String(l, base), S2Stream(this) { }
explicit StreamString(unsigned long l, unsigned char base = 10): String(l, base), S2Stream(this) { }
explicit StreamString(float f, unsigned char decimalPlaces = 2): String(f, decimalPlaces), S2Stream(this) { }
explicit StreamString(double d, unsigned char decimalPlaces = 2): String(d, decimalPlaces), S2Stream(this) { }
StreamString& operator= (const StreamString& rhs)
{
String::operator=(rhs);
resetpp();
return *this;
}
StreamString& operator= (const String& rhs)
{
String::operator=(rhs);
resetpp();
return *this;
}
StreamString& operator= (const char* cstr)
{
String::operator=(cstr);
resetpp();
return *this;
}
StreamString& operator= (const __FlashStringHelper* str)
{
String::operator=(str);
resetpp();
return *this;
}
StreamString& operator= (String&& rval)
{
String::operator=(rval);
resetpp();
return *this;
}
StreamString& operator= (StringSumHelper&& rval)
{
String::operator=(rval);
resetpp();
return *this;
}
};
#endif // __STREAMSTRING_H
...@@ -22,6 +22,13 @@ ...@@ -22,6 +22,13 @@
#include "debug.h" #include "debug.h"
#include "osapi.h" #include "osapi.h"
#ifdef DEBUG_ESP_CORE
void __iamslow(const char* what)
{
DEBUGV("%s should be overridden for better efficiency\r\n", what);
}
#endif
IRAM_ATTR IRAM_ATTR
void hexdump(const void *mem, uint32_t len, uint8_t cols) void hexdump(const void *mem, uint32_t len, uint8_t cols)
{ {
......
...@@ -26,6 +26,20 @@ void __unhandled_exception(const char *str) __attribute__((noreturn)); ...@@ -26,6 +26,20 @@ void __unhandled_exception(const char *str) __attribute__((noreturn));
void __panic_func(const char* file, int line, const char* func) __attribute__((noreturn)); void __panic_func(const char* file, int line, const char* func) __attribute__((noreturn));
#define panic() __panic_func(PSTR(__FILE__), __LINE__, __func__) #define panic() __panic_func(PSTR(__FILE__), __LINE__, __func__)
#ifdef DEBUG_ESP_CORE
extern void __iamslow(const char* what);
#define IAMSLOW() \
do { \
static bool once = false; \
if (!once) { \
once = true; \
__iamslow((PGM_P)FPSTR(__FUNCTION__)); \
} \
} while (0)
#else
#define IAMSLOW() do { (void)0; } while (0)
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
......
/*
esp_priv.h - private esp8266 helpers
Copyright (c) 2020 esp8266/Arduino community. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __ESP_PRIV
#define __ESP_PRIV
#if defined(CORE_MOCK)
constexpr bool __byteAddressable(const void* addr)
{
(void)addr;
return true;
}
#else // on hardware
#include <sys/config.h>
// returns true when addr can be used without "pgm_" functions or non32xfer service
constexpr bool __byteAddressable(const void* addr)
{
return addr < (const void*)(XCHAL_DATARAM0_VADDR + XCHAL_DATARAM0_SIZE);
}
#endif // on hardware
#endif // __ESP_PRIV
...@@ -388,10 +388,10 @@ public: ...@@ -388,10 +388,10 @@ public:
return result; return result;
} }
size_t read(uint8_t* buf, size_t size) override int read(uint8_t* buf, size_t size) override
{ {
CHECKFD(); CHECKFD();
auto result = SPIFFS_read(_fs->getFs(), _fd, (void*) buf, size); int result = SPIFFS_read(_fs->getFs(), _fd, (void*) buf, size);
if (result < 0) { if (result < 0) {
DEBUGV("SPIFFS_read rc=%d\r\n", result); DEBUGV("SPIFFS_read rc=%d\r\n", result);
return 0; return 0;
......
...@@ -240,6 +240,41 @@ uart_peek_char(uart_t* uart) ...@@ -240,6 +240,41 @@ uart_peek_char(uart_t* uart)
return ret; return ret;
} }
// return number of byte accessible by uart_peek_buffer()
size_t uart_peek_available (uart_t* uart)
{
// path for further optimization:
// - return already copied buffer pointer (= older data)
// - or return fifo when buffer is empty but then any move from fifo to
// buffer should be blocked until peek_consume is called
ETS_UART_INTR_DISABLE();
uart_rx_copy_fifo_to_buffer_unsafe(uart);
auto rpos = uart->rx_buffer->rpos;
auto wpos = uart->rx_buffer->wpos;
ETS_UART_INTR_ENABLE();
if(wpos < rpos)
return uart->rx_buffer->size - rpos;
return wpos - rpos;
}
// return a pointer to available data buffer (size = available())
// semantic forbids any kind of read() between peekBuffer() and peekConsume()
const char* uart_peek_buffer (uart_t* uart)
{
return (const char*)&uart->rx_buffer->buffer[uart->rx_buffer->rpos];
}
// consume bytes after use (see uart_peek_buffer)
void uart_peek_consume (uart_t* uart, size_t consume)
{
ETS_UART_INTR_DISABLE();
uart->rx_buffer->rpos += consume;
if (uart->rx_buffer->rpos >= uart->rx_buffer->size)
uart->rx_buffer->rpos -= uart->rx_buffer->size;
ETS_UART_INTR_ENABLE();
}
int int
uart_read_char(uart_t* uart) uart_read_char(uart_t* uart)
{ {
......
...@@ -147,6 +147,16 @@ int uart_get_debug(); ...@@ -147,6 +147,16 @@ int uart_get_debug();
void uart_start_detect_baudrate(int uart_nr); void uart_start_detect_baudrate(int uart_nr);
int uart_detect_baudrate(int uart_nr); int uart_detect_baudrate(int uart_nr);
// return number of byte accessible by peekBuffer()
size_t uart_peek_available (uart_t* uart);
// return a pointer to available data buffer (size = available())
// semantic forbids any kind of read() before calling peekConsume()
const char* uart_peek_buffer (uart_t* uart);
// consume bytes after use (see peekBuffer)
void uart_peek_consume (uart_t* uart, size_t consume);
uint8_t uart_get_bit_length(const int uart_nr); uint8_t uart_get_bit_length(const int uart_nr);
#if defined (__cplusplus) #if defined (__cplusplus)
......
...@@ -326,3 +326,196 @@ C++ ...@@ -326,3 +326,196 @@ C++
This assures correct behavior, including handling of all subobjects, which guarantees stability. This assures correct behavior, including handling of all subobjects, which guarantees stability.
History: `#6269 <https://github.com/esp8266/Arduino/issues/6269>`__ `#6309 <https://github.com/esp8266/Arduino/pull/6309>`__ `#6312 <https://github.com/esp8266/Arduino/pull/6312>`__ History: `#6269 <https://github.com/esp8266/Arduino/issues/6269>`__ `#6309 <https://github.com/esp8266/Arduino/pull/6309>`__ `#6312 <https://github.com/esp8266/Arduino/pull/6312>`__
Streams
-------
Arduino API
Stream is one of the core classes in the Arduino API. Wire, serial, network and
filesystems are streams, from which data are read or written.
Making a transfer with streams is quite common, like for example the
historical WiFiSerial sketch:
.. code:: cpp
//check clients for data
//get data from the telnet client and push it to the UART
while (serverClient.available()) {
Serial.write(serverClient.read());
}
//check UART for data
if (Serial.available()) {
size_t len = Serial.available();
uint8_t sbuf[len];
Serial.readBytes(sbuf, len);
//push UART data to all connected telnet clients
if (serverClient && serverClient.connected()) {
serverClient.write(sbuf, len);
}
}
One will notice that in the network to serial direction, data are transfered
byte by byte while data are available. In the other direction, a temporary
buffer is created on stack, filled with available serial data, then
transferred to network.
The ``readBytes(buffer, length)`` method includes a timeout to ensure that
all required bytes are received. The ``write(buffer, length)`` (inherited
from ``Print::``) function is also usually blocking until the full buffer is
transmitted. Both functions return the number of transmitted bytes.
That's the way the Stream class works and is commonly used.
Classes derived from ``Stream::`` also usually introduce the ``read(buffer,
len)`` method, which is similar to ``readBytes(buffer, len)`` without
timeout: the returned value can be less than the requested size, so special
care must be taken with this function, introduced in the Arduino
``Client::`` class (cf. AVR reference implementation).
This function has also been introduced in other classes
that don't derive from ``Client::``, e.g. ``HardwareSerial::``.
Stream extensions
Stream extensions are designed to be compatible with Arduino API, and
offer additional methods to make transfers more efficient and easier to
use.
The serial to network transfer above can be written like this:
.. code:: cpp
serverClient.sendAvailable(Serial); // chunk by chunk
Serial.sendAvailable(serverClient); // chunk by chunk
An echo service can be written like this:
.. code:: cpp
serverClient.sendAvailable(serverClient); // tcp echo service
Serial.sendAvailable(Serial); // serial software loopback
Beside reducing coding time, these methods optimize transfers by avoiding
buffer copies when possible.
- User facing API: ``Stream::send()``
The goal of streams is to transfer data between producers and consumers,
like the telnet/serial example above. Four methods are provided, all of
them return the number of transmitted bytes:
- ``Stream::sendSize(dest, size [, timeout])``
This method waits up to the given or default timeout to transfer
``size`` bytes to the the ``dest`` Stream.
- ``Stream::sendUntil(dest, delim [, timeout])``
This method waits up to the given or default timeout to transfer data
until the character ``delim`` is met.
Note: The delimiter is read but not transferred (like ``readBytesUntil``)
- ``Stream::sendAvailable(dest)``
This method transfers all already available data to the destination.
There is no timeout and the returned value is 0 when there is nothing
to transfer or no room in the destination.
- ``Stream::sendAll(dest [, timeout])``
This method waits up to the given or default timeout to transfer all
available data. It is useful when source is able to tell that no more
data will be available for this call, or when destination can tell
that it will no be able to receive anymore.
For example, a source String will not grow during the transfer, or a
particular network connection supposed to send a fixed amount of data
before closing. ``::sendAll()`` will receive all bytes. Timeout is
useful when destination needs processing time (e.g. network or serial
input buffer full = please wait a bit).
- String, flash strings helpers
Two additional classes are provided.
- ``StreamPtr::`` is designed to hold a constant buffer (in ram or flash).
With this class, a ``Stream::`` can be made from ``const char*``,
``F("some words in flash")`` or ``PROGMEM`` strings. This class makes
no copy, even with data in flash. For flash content, byte-by-byte
transfers is a consequence when "memcpy_P" cannot be used. Other
contents can be transferred at once when possible.
.. code:: cpp
StreamPtr css(F("my long css data")); // CSS data not copied to RAM
server.sendAll(css);
- ``S2Stream::`` is designed to make a ``Stream::`` out of a ``String::`` without copy.
.. code:: cpp
String helloString("hello");
S2Stream hello(helloString);
hello.reset(0); // prevents ::read() to consume the string
hello.sendAll(Serial); // shows "hello"
hello.sendAll(Serial); // shows nothing, content has already been read
hello.reset(); // reset content pointer
hello.sendAll(Serial); // shows "hello"
hello.reset(3); // reset content pointer to a specific position
hello.sendAll(Serial); // shows "lo"
hello.setConsume(); // ::read() will consume, this is the default
Serial.println(helloString.length()); // shows 5
hello.sendAll(Serial); // shows "hello"
Serial.println(helloString.length()); // shows 0, string is consumed
``StreamString::`` derives from ``S2Stream``
.. code:: cpp
StreamString contentStream;
client.sendSize(contentStream, SOME_SIZE); // receives at most SOME_SIZE bytes
// equivalent to:
String content;
S2Stream contentStream(content);
client.sendSize(contentStream, SOME_SIZE); // receives at most SOME_SIZE bytes
// content has the data
- Internal Stream API: ``peekBuffer``
Here is the method list and their significations. They are currently
implemented in ``HardwareSerial``, ``WiFiClient`` and
``WiFiClientSecure``.
- ``virtual bool hasPeekBufferAPI ()`` returns ``true`` when the API is present in the class
- ``virtual size_t peekAvailable ()`` returns the number of reachable bytes
- ``virtual const char* peekBuffer ()`` returns the pointer to these bytes
This API requires that any kind of ``"read"`` function must not be called after ``peekBuffer()``
and until ``peekConsume()`` is called.
- ``virtual void peekConsume (size_t consume)`` tells to discard that number of bytes
- ``virtual bool inputCanTimeout ()``
A ``StringStream`` will return false. A closed network connection returns false.
This function allows ``Stream::sendAll()`` to return earlier.
- ``virtual bool outputCanTimeout ()``
A closed network connection returns false.
This function allows ``Stream::sendAll()`` to return earlier.
- ``virtual ssize_t streamRemaining()``
It returns -1 when stream remaining size is unknown, depending on implementation
(string size, file size..).
...@@ -25,9 +25,22 @@ ...@@ -25,9 +25,22 @@
#include "ESP8266HTTPClient.h" #include "ESP8266HTTPClient.h"
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <StreamString.h> #include <StreamDev.h>
#include <base64.h> #include <base64.h>
static int StreamReportToHttpClientReport (Stream::Report streamSendError)
{
switch (streamSendError)
{
case Stream::Report::TimedOut: return HTTPC_ERROR_READ_TIMEOUT;
case Stream::Report::ReadError: return HTTPC_ERROR_NO_STREAM;
case Stream::Report::WriteError: return HTTPC_ERROR_STREAM_WRITE;
case Stream::Report::ShortOperation: return HTTPC_ERROR_STREAM_WRITE;
case Stream::Report::Success: return 0;
}
return 0; // never reached, keep gcc quiet
}
/** /**
* constructor * constructor
*/ */
...@@ -429,24 +442,9 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s ...@@ -429,24 +442,9 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
} }
// send Payload if needed // transfer all of it, with send-timeout
if (payload && size > 0) { if (size && StreamConstPtr(payload, size).sendAll(_client) != size)
size_t bytesWritten = 0; return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
const uint8_t *p = payload;
size_t originalSize = size;
while (bytesWritten < originalSize) {
int written;
int towrite = std::min((int)size, (int)HTTP_TCP_BUFFER_SIZE);
written = _client->write(p + bytesWritten, towrite);
if (written < 0) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
} else if (written == 0) {
return returnError(HTTPC_ERROR_CONNECTION_LOST);
}
bytesWritten += written;
size -= written;
}
}
// handle Server Response (Header) // handle Server Response (Header)
code = handleHeaderResponse(); code = handleHeaderResponse();
...@@ -545,111 +543,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size) ...@@ -545,111 +543,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
} }
int buff_size = HTTP_TCP_BUFFER_SIZE; // transfer all of it, with timeout
size_t transferred = stream->sendSize(_client, size);
int len = size; if (transferred != size)
int bytesWritten = 0; {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", size, transferred);
if(len == 0) { return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
len = -1;
}
// if possible create smaller buffer then HTTP_TCP_BUFFER_SIZE
if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) {
buff_size = len;
}
// create buffer for read
uint8_t * buff = (uint8_t *) malloc(buff_size);
if(buff) {
// read all data from stream and send it to server
while(connected() && (stream->available() > 0) && (len > 0 || len == -1)) {
// get available data size
int sizeAvailable = stream->available();
if(sizeAvailable) {
int readBytes = sizeAvailable;
// read only the asked bytes
if(len > 0 && readBytes > len) {
readBytes = len;
}
// not read more the buffer can handle
if(readBytes > buff_size) {
readBytes = buff_size;
}
// read data
int bytesRead = stream->readBytes(buff, readBytes);
// write it to Stream
int bytesWrite = _client->write((const uint8_t *) buff, bytesRead);
bytesWritten += bytesWrite;
// are all Bytes a writen to stream ?
if(bytesWrite != bytesRead) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d retry...\n", bytesRead, bytesWrite);
// check for write error
if(_client->getWriteError()) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] stream write error %d\n", _client->getWriteError());
//reset write error for retry
_client->clearWriteError();
}
// some time for the stream
delay(1);
int leftBytes = (readBytes - bytesWrite);
// retry to send the missed bytes
bytesWrite = _client->write((const uint8_t *) (buff + bytesWrite), leftBytes);
bytesWritten += bytesWrite;
if(bytesWrite != leftBytes) {
// failed again
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", leftBytes, bytesWrite);
free(buff);
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
}
// check for write error
if(_client->getWriteError()) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] stream write error %d\n", _client->getWriteError());
free(buff);
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
// count bytes to read left
if(len > 0) {
len -= readBytes;
}
delay(0);
} else {
delay(1);
}
}
free(buff);
if(size && (int) size != bytesWritten) {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] Stream payload bytesWritten %d and size %zd mismatch!.\n", bytesWritten, size);
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] ERROR SEND PAYLOAD FAILED!");
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
} else {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] Stream payload written: %d\n", bytesWritten);
}
} else {
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] not enough ram! need %d\n", HTTP_TCP_BUFFER_SIZE);
return returnError(HTTPC_ERROR_TOO_LESS_RAM);
} }
// handle Server Response (Header) // handle Server Response (Header)
...@@ -725,13 +624,13 @@ int HTTPClient::writeToStream(Stream * stream) ...@@ -725,13 +624,13 @@ int HTTPClient::writeToStream(Stream * stream)
int ret = 0; int ret = 0;
if(_transferEncoding == HTTPC_TE_IDENTITY) { if(_transferEncoding == HTTPC_TE_IDENTITY) {
if(len > 0 || len == -1) { // len < 0: transfer all of it, with timeout
ret = writeToStreamDataBlock(stream, len); // len >= 0: max:len, with timeout
ret = _client->sendSize(stream, len);
// have we an error? // do we have an error?
if(ret < 0) { if(_client->getLastSendReport() != Stream::Report::Success) {
return returnError(ret); return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
}
} }
} else if(_transferEncoding == HTTPC_TE_CHUNKED) { } else if(_transferEncoding == HTTPC_TE_CHUNKED) {
int size = 0; int size = 0;
...@@ -754,11 +653,11 @@ int HTTPClient::writeToStream(Stream * stream) ...@@ -754,11 +653,11 @@ int HTTPClient::writeToStream(Stream * stream)
// data left? // data left?
if(len > 0) { if(len > 0) {
int r = writeToStreamDataBlock(stream, len); // read len bytes with timeout
if(r < 0) { int r = _client->sendSize(stream, len);
// error in writeToStreamDataBlock if (_client->getLastSendReport() != Stream::Report::Success)
return returnError(r); // not all data transferred
} return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
ret += r; ret += r;
} else { } else {
...@@ -948,9 +847,7 @@ bool HTTPClient::connect(void) ...@@ -948,9 +847,7 @@ bool HTTPClient::connect(void)
{ {
if(_reuse && _canReuse && connected()) { if(_reuse && _canReuse && connected()) {
DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n"); DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n");
while(_client->available() > 0) { _client->sendAvailable(devnull); // clear _client's output (all of it, no timeout)
_client->read();
}
return true; return true;
} }
...@@ -1032,7 +929,8 @@ bool HTTPClient::sendHeader(const char * type) ...@@ -1032,7 +929,8 @@ bool HTTPClient::sendHeader(const char * type)
DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str()); DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
return (_client->write((const uint8_t *) header.c_str(), header.length()) == header.length()); // transfer all of it, with timeout
return StreamConstPtr(header).sendAll(_client) == header.length();
} }
/** /**
...@@ -1150,116 +1048,6 @@ int HTTPClient::handleHeaderResponse() ...@@ -1150,116 +1048,6 @@ int HTTPClient::handleHeaderResponse()
return HTTPC_ERROR_CONNECTION_LOST; return HTTPC_ERROR_CONNECTION_LOST;
} }
/**
* write one Data Block to Stream
* @param stream Stream *
* @param size int
* @return < 0 = error >= 0 = size written
*/
int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
{
int buff_size = HTTP_TCP_BUFFER_SIZE;
int len = size; // left size to read
int bytesWritten = 0;
// if possible create smaller buffer then HTTP_TCP_BUFFER_SIZE
if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) {
buff_size = len;
}
// create buffer for read
uint8_t * buff = (uint8_t *) malloc(buff_size);
if(!buff) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] not enough ram! need %d\n", HTTP_TCP_BUFFER_SIZE);
return HTTPC_ERROR_TOO_LESS_RAM;
}
// read all data from server
while(connected() && (len > 0 || len == -1))
{
int readBytes = len;
// not read more the buffer can handle
if(readBytes > buff_size) {
readBytes = buff_size;
}
// len == -1 or len > what is available, read only what is available
int av = _client->available();
if (readBytes < 0 || readBytes > av) {
readBytes = av;
}
// read data
int bytesRead = _client->readBytes(buff, readBytes);
if (!bytesRead)
{
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] input stream timeout\n");
free(buff);
return HTTPC_ERROR_READ_TIMEOUT;
}
// write it to Stream
int bytesWrite = stream->write(buff, bytesRead);
bytesWritten += bytesWrite;
// are all Bytes a writen to stream ?
if(bytesWrite != bytesRead) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStream] short write asked for %d but got %d retry...\n", bytesRead, bytesWrite);
// check for write error
if(stream->getWriteError()) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] stream write error %d\n", stream->getWriteError());
//reset write error for retry
stream->clearWriteError();
}
// some time for the stream
delay(1);
int leftBytes = (bytesRead - bytesWrite);
// retry to send the missed bytes
bytesWrite = stream->write((buff + bytesWrite), leftBytes);
bytesWritten += bytesWrite;
if(bytesWrite != leftBytes) {
// failed again
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStream] short write asked for %d but got %d failed.\n", leftBytes, bytesWrite);
free(buff);
return HTTPC_ERROR_STREAM_WRITE;
}
}
// check for write error
if(stream->getWriteError()) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] stream write error %d\n", stream->getWriteError());
free(buff);
return HTTPC_ERROR_STREAM_WRITE;
}
// count bytes to read left
if(len > 0) {
len -= bytesRead;
}
delay(0);
}
free(buff);
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] end of chunk or data (transferred: %d).\n", bytesWritten);
if((size > 0) && (size != bytesWritten)) {
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] transferred size %d and request size %d mismatch!.\n", bytesWritten, size);
return HTTPC_ERROR_STREAM_WRITE;
}
return bytesWritten;
}
/** /**
* called to handle error return, may disconnect the connection if still exists * called to handle error return, may disconnect the connection if still exists
* @param error * @param error
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
#include <memory> #include <memory>
#include <Arduino.h> #include <Arduino.h>
#include <StreamString.h>
#include <WiFiClient.h> #include <WiFiClient.h>
#ifdef DEBUG_ESP_HTTP_CLIENT #ifdef DEBUG_ESP_HTTP_CLIENT
...@@ -148,8 +148,6 @@ typedef enum { ...@@ -148,8 +148,6 @@ typedef enum {
class TransportTraits; class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr; typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
class StreamString;
class HTTPClient class HTTPClient
{ {
public: public:
......
...@@ -115,9 +115,9 @@ void setup(void) { ...@@ -115,9 +115,9 @@ void setup(void) {
// swallow the exact amount matching the full request+content, // swallow the exact amount matching the full request+content,
// hence the tcp connection cannot be handled anymore by the // hence the tcp connection cannot be handled anymore by the
// webserver. // webserver.
#ifdef STREAMTO_API #ifdef STREAMSEND_API
// we are lucky // we are lucky
client->toWithTimeout(Serial, 500); client->sendAll(Serial, 500);
#else #else
auto last = millis(); auto last = millis();
while ((millis() - last) < 500) { while ((millis() - last) < 500) {
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include "FS.h" #include "FS.h"
#include "base64.h" #include "base64.h"
#include "detail/RequestHandlersImpl.h" #include "detail/RequestHandlersImpl.h"
#include <StreamDev.h>
static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization"; static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization";
static const char qop_auth[] PROGMEM = "qop=auth"; static const char qop_auth[] PROGMEM = "qop=auth";
...@@ -441,71 +442,68 @@ void ESP8266WebServerTemplate<ServerType>::_prepareHeader(String& response, int ...@@ -441,71 +442,68 @@ void ESP8266WebServerTemplate<ServerType>::_prepareHeader(String& response, int
} }
template <typename ServerType> template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, const String& content) { void ESP8266WebServerTemplate<ServerType>::send(int code, char* content_type, const String& content) {
String header; return send(code, (const char*)content_type, content);
// Can we asume the following?
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
// _contentLength = CONTENT_LENGTH_UNKNOWN;
_prepareHeader(header, code, content_type, content.length());
_currentClient.write((const uint8_t *)header.c_str(), header.length());
if(content.length())
sendContent(content);
} }
template <typename ServerType> template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content) { void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, const String& content) {
size_t contentLength = 0; return send(code, content_type, content.c_str(), content.length());
}
if (content != NULL) { template <typename ServerType>
contentLength = strlen_P(content); void ESP8266WebServerTemplate<ServerType>::send(int code, const String& content_type, const String& content) {
} return send(code, (const char*)content_type.c_str(), content);
}
String header; template <typename ServerType>
char type[64]; void ESP8266WebServerTemplate<ServerType>::sendContent(const String& content) {
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type)); StreamConstPtr ref(content.c_str(), content.length());
_prepareHeader(header, code, (const char* )type, contentLength); sendContent(&ref);
_currentClient.write((const uint8_t *)header.c_str(), header.length());
if (contentLength) {
sendContent_P(content);
}
} }
template <typename ServerType> template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) { void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, Stream* stream, size_t content_length /*= 0*/) {
String header; String header;
char type[64]; if (content_length == 0)
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type)); content_length = std::max((ssize_t)0, stream->streamRemaining());
_prepareHeader(header, code, (const char* )type, contentLength); _prepareHeader(header, code, content_type, content_length);
_currentClient.write((const uint8_t *)header.c_str(), header.length()); size_t sent = StreamConstPtr(header).sendAll(&_currentClient);
if (contentLength) { if (sent != header.length())
sendContent_P(content, contentLength); DBGWS("HTTPServer: error: sent %zd on %u bytes\n", sent, header.length());
} if (content_length)
return sendContent(stream, content_length);
} }
template <typename ServerType> template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send(int code, char* content_type, const String& content) { void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content) {
send(code, (const char*)content_type, content); StreamConstPtr ref(content, strlen_P(content));
return send(code, String(content_type).c_str(), &ref);
} }
template <typename ServerType> template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::send(int code, const String& content_type, const String& content) { void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
send(code, (const char*)content_type.c_str(), content); StreamConstPtr ref(content, contentLength);
return send(code, String(content_type).c_str(), &ref);
} }
template <typename ServerType> template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::sendContent(const String& content) { void ESP8266WebServerTemplate<ServerType>::sendContent(Stream* content, ssize_t content_length /* = 0*/) {
if (_currentMethod == HTTP_HEAD) return; if (_currentMethod == HTTP_HEAD)
const char * footer = "\r\n"; return;
size_t len = content.length(); if (content_length <= 0)
content_length = std::max((ssize_t)0, content->streamRemaining());
if(_chunked) { if(_chunked) {
char chunkSize[11]; _currentClient.printf("%zx\r\n", content_length);
sprintf(chunkSize, "%zx\r\n", len); }
_currentClient.write((const uint8_t *)chunkSize, strlen(chunkSize)); ssize_t sent = content->sendSize(&_currentClient, content_length);
if (sent != content_length)
{
DBGWS("HTTPServer: error: short send after timeout (%d<%d)\n", sent, content_length);
} }
_currentClient.write((const uint8_t *)content.c_str(), len); if(_chunked) {
if(_chunked){ _currentClient.printf_P(PSTR("\r\n"));
_currentClient.write((const uint8_t *)footer, 2); if (content_length == 0) {
if (len == 0) {
_chunked = false; _chunked = false;
} }
} }
...@@ -518,19 +516,8 @@ void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content) { ...@@ -518,19 +516,8 @@ void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content) {
template <typename ServerType> template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content, size_t size) { void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content, size_t size) {
const char * footer = "\r\n"; StreamConstPtr ptr(content, size);
if(_chunked) { return sendContent(&ptr, size);
char chunkSize[11];
sprintf(chunkSize, "%zx\r\n", size);
_currentClient.write((const uint8_t *)chunkSize, strlen(chunkSize));
}
_currentClient.write_P(content, size);
if(_chunked){
_currentClient.write((const uint8_t *)footer, 2);
if (size == 0) {
_chunked = false;
}
}
} }
template <typename ServerType> template <typename ServerType>
...@@ -694,7 +681,7 @@ void ESP8266WebServerTemplate<ServerType>::_handleRequest() { ...@@ -694,7 +681,7 @@ void ESP8266WebServerTemplate<ServerType>::_handleRequest() {
} }
if (!handled) { if (!handled) {
using namespace mime; using namespace mime;
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri); send(404, FPSTR(mimeTable[html].mimeType), String(F("Not found: ")) + _currentUri);
handled = true; handled = true;
} }
if (handled) { if (handled) {
......
...@@ -149,7 +149,7 @@ public: ...@@ -149,7 +149,7 @@ public:
// code - HTTP response code, can be 200 or 404 // code - HTTP response code, can be 200 or 404
// content_type - HTTP content type, like "text/plain" or "image/png" // content_type - HTTP content type, like "text/plain" or "image/png"
// content - actual content body // content - actual content body
void send(int code, const char* content_type = NULL, const String& content = String("")); void send(int code, const char* content_type = NULL, const String& content = emptyString);
void send(int code, char* content_type, const String& content); void send(int code, char* content_type, const String& content);
void send(int code, const String& content_type, const String& content); void send(int code, const String& content_type, const String& content);
void send(int code, const char *content_type, const char *content) { void send(int code, const char *content_type, const char *content) {
...@@ -164,14 +164,23 @@ public: ...@@ -164,14 +164,23 @@ public:
void send_P(int code, PGM_P content_type, PGM_P content); void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength); void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
void send(int code, const char* content_type, Stream* stream, size_t content_length = 0);
void send(int code, const char* content_type, Stream& stream, size_t content_length = 0);
void setContentLength(const size_t contentLength); void setContentLength(const size_t contentLength);
void sendHeader(const String& name, const String& value, bool first = false); void sendHeader(const String& name, const String& value, bool first = false);
void sendContent(const String& content); void sendContent(const String& content);
void sendContent(String& content) {
sendContent((const String&)content);
}
void sendContent_P(PGM_P content); void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size); void sendContent_P(PGM_P content, size_t size);
void sendContent(const char *content) { sendContent_P(content); } void sendContent(const char *content) { sendContent_P(content); }
void sendContent(const char *content, size_t size) { sendContent_P(content, size); } void sendContent(const char *content, size_t size) { sendContent_P(content, size); }
void sendContent(Stream* content, ssize_t content_length = 0);
void sendContent(Stream& content, ssize_t content_length = 0) { sendContent(&content, content_length); }
bool chunkedResponseModeStart_P (int code, PGM_P content_type) { bool chunkedResponseModeStart_P (int code, PGM_P content_type) {
if (_currentVersion == 0) if (_currentVersion == 0)
// no chunk mode in HTTP/1.0 // no chunk mode in HTTP/1.0
...@@ -220,6 +229,30 @@ public: ...@@ -220,6 +229,30 @@ public:
return contentLength; return contentLength;
} }
// Implement GET and HEAD requests for stream
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
template<typename T>
size_t stream(T &aStream, const String& contentType, HTTPMethod requestMethod, ssize_t size) {
setContentLength(size);
send(200, contentType, emptyString);
if (requestMethod == HTTP_GET)
size = aStream.sendSize(_currentClient, size);
return size;
}
// Implement GET and HEAD requests for stream
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
template<typename T>
size_t stream(T& aStream, const String& contentType, HTTPMethod requestMethod = HTTP_GET) {
ssize_t size = aStream.size();
if (size < 0)
{
send(500, F("text/html"), F("input stream: undetermined size"));
return 0;
}
return stream(aStream, contentType, requestMethod, size);
}
static String responseCodeToString(const int code); static String responseCodeToString(const int code);
void addHook (HookFunction hook) { void addHook (HookFunction hook) {
......
...@@ -37,22 +37,8 @@ namespace esp8266webserver { ...@@ -37,22 +37,8 @@ namespace esp8266webserver {
template <typename ServerType> template <typename ServerType>
static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t maxLength, String& data, int timeout_ms) static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t maxLength, String& data, int timeout_ms)
{ {
if (!data.reserve(maxLength + 1)) S2Stream dataStream(data);
return false; return client.sendSize(dataStream, maxLength, timeout_ms) == maxLength;
data[0] = 0; // data.clear()??
while (data.length() < maxLength) {
int tries = timeout_ms;
size_t avail;
while (!(avail = client.available()) && tries--)
delay(1);
if (!avail)
break;
if (data.length() + avail > maxLength)
avail = maxLength - data.length();
while (avail--)
data += (char)client.read();
}
return data.length() == maxLength;
} }
template <typename ServerType> template <typename ServerType>
......
/*
WiFiEcho - Echo server
released to public domain
*/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <PolledTimeout.h>
#include <algorithm> // std::min
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
constexpr int port = 23;
WiFiServer server(port);
WiFiClient client;
constexpr size_t sizes [] = { 0, 512, 384, 256, 128, 64, 16, 8, 4 };
constexpr uint32_t breathMs = 200;
esp8266::polledTimeout::oneShotFastMs enoughMs(breathMs);
esp8266::polledTimeout::periodicFastMs test(2000);
int t = 1; // test (1, 2 or 3, see below)
int s = 0; // sizes[] index
void setup() {
Serial.begin(115200);
Serial.println(ESP.getFullVersion());
WiFi.mode(WIFI_STA);
WiFi.begin(STASSID, STAPSK);
Serial.print("\nConnecting to ");
Serial.println(STASSID);
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(500);
}
Serial.println();
Serial.print("connected, address=");
Serial.println(WiFi.localIP());
server.begin();
MDNS.begin("echo23");
Serial.printf("Ready!\n"
"- Use 'telnet/nc echo23.local %d' to try echo\n\n"
"- Use 'python3 echo-client.py' bandwidth meter to compare transfer APIs\n\n"
" and try typing 1, 1, 1, 2, 2, 2, 3, 3, 3 on console during transfers\n\n",
port);
}
void loop() {
MDNS.update();
static uint32_t tot = 0;
static uint32_t cnt = 0;
if (test && cnt) {
Serial.printf("measured-block-size=%u min-free-stack=%u", tot / cnt, ESP.getFreeContStack());
if (t == 2 && sizes[s]) {
Serial.printf(" (blocks: at most %d bytes)", sizes[s]);
}
if (t == 3 && sizes[s]) {
Serial.printf(" (blocks: exactly %d bytes)", sizes[s]);
}
if (t == 3 && !sizes[s]) {
Serial.printf(" (blocks: any size)");
}
Serial.printf("\n");
}
//check if there are any new clients
if (server.hasClient()) {
client = server.available();
Serial.println("New client");
}
if (Serial.available()) {
s = (s + 1) % (sizeof(sizes) / sizeof(sizes[0]));
switch (Serial.read()) {
case '1': if (t != 1) s = 0; t = 1; Serial.println("byte-by-byte (watch then press 2 or 3)"); break;
case '2': if (t != 2) s = 1; t = 2; Serial.printf("through buffer (watch then press 2 again, or 1 or 3)\n"); break;
case '3': if (t != 3) s = 0; t = 3; Serial.printf("direct access (watch then press 3 again, or 1 or 2)\n"); break;
}
tot = cnt = 0;
ESP.resetFreeContStack();
}
enoughMs.reset(breathMs);
if (t == 1) {
// byte by byte
while (client.available() && client.availableForWrite() && !enoughMs) {
// working char by char is not efficient
client.write(client.read());
cnt++;
tot += 1;
}
}
else if (t == 2) {
// block by block through a local buffer (2 copies)
while (client.available() && client.availableForWrite() && !enoughMs) {
size_t maxTo = std::min(client.available(), client.availableForWrite());
maxTo = std::min(maxTo, sizes[s]);
uint8_t buf[maxTo];
size_t tcp_got = client.read(buf, maxTo);
size_t tcp_sent = client.write(buf, tcp_got);
if (tcp_sent != maxTo) {
Serial.printf("len mismatch: available:%zd tcp-read:%zd serial-write:%zd\n", maxTo, tcp_got, tcp_sent);
}
tot += tcp_sent;
cnt++;
}
}
else if (t == 3) {
// stream to print, possibly with only one copy
if (sizes[s]) {
tot += client.sendSize(&client, sizes[s]);
} else {
tot += client.sendAll(&client);
}
cnt++;
switch (client.getLastSendReport()) {
case Stream::Report::Success: break;
case Stream::Report::TimedOut: Serial.println("Stream::send: timeout"); break;
case Stream::Report::ReadError: Serial.println("Stream::send: read error"); break;
case Stream::Report::WriteError: Serial.println("Stream::send: write error"); break;
case Stream::Report::ShortOperation: Serial.println("Stream::send: short transfer"); break;
}
}
}
#!/usr/bin/env python3
import os
import asyncio
# 512 bytes
message = bytearray(512);
bufsize=len(message)
print('message len=', bufsize)
global recv
recv = 0
async def tcp_echo_open (ip, port):
return await asyncio.open_connection(ip, port)
async def tcp_echo_sender(message, writer):
print('Writer started')
while True:
writer.write(message)
await writer.drain()
async def tcp_echo_receiver(message, reader):
global recv
print('Reader started')
while True:
data = ''.encode('utf8')
while len(data) < bufsize:
data += await reader.read(bufsize - len(data))
recv += len(data);
if data != message:
print('error')
async def tcp_stat():
global recv
dur = 0
loopsec = 2
while True:
last = recv
await asyncio.sleep(loopsec) # drifting
dur += loopsec
print('BW=', (recv - last) * 2 * 8 / 1024 / loopsec, 'Kibits/s avg=', recv * 2 * 8 / 1024 / dur)
loop = asyncio.get_event_loop()
reader, writer = loop.run_until_complete(tcp_echo_open('echo23.local', 23))
loop.create_task(tcp_echo_receiver(message, reader))
loop.create_task(tcp_echo_sender(message, writer))
loop.create_task(tcp_stat())
loop.run_forever()
...@@ -125,7 +125,7 @@ int CertStore::initCertStore(fs::FS &fs, const char *indexFileName, const char * ...@@ -125,7 +125,7 @@ int CertStore::initCertStore(fs::FS &fs, const char *indexFileName, const char *
uint8_t fileHeader[60]; uint8_t fileHeader[60];
// 0..15 = filename in ASCII // 0..15 = filename in ASCII
// 48...57 = length in decimal ASCII // 48...57 = length in decimal ASCII
uint32_t length; int32_t length;
if (data.read(fileHeader, sizeof(fileHeader)) != sizeof(fileHeader)) { if (data.read(fileHeader, sizeof(fileHeader)) != sizeof(fileHeader)) {
break; break;
} }
...@@ -201,7 +201,7 @@ const br_x509_trust_anchor *CertStore::findHashedTA(void *ctx, void *hashed_dn, ...@@ -201,7 +201,7 @@ const br_x509_trust_anchor *CertStore::findHashedTA(void *ctx, void *hashed_dn,
free(der); free(der);
return nullptr; return nullptr;
} }
if (data.read((uint8_t *)der, ci.length) != ci.length) { if (data.read(der, ci.length) != (int)ci.length) {
free(der); free(der);
return nullptr; return nullptr;
} }
......
...@@ -244,7 +244,7 @@ size_t WiFiClient::write_P(PGM_P buf, size_t size) ...@@ -244,7 +244,7 @@ size_t WiFiClient::write_P(PGM_P buf, size_t size)
int WiFiClient::available() int WiFiClient::available()
{ {
if (!_client) if (!_client)
return false; return 0;
int result = _client->getSize(); int result = _client->getSize();
...@@ -262,10 +262,14 @@ int WiFiClient::read() ...@@ -262,10 +262,14 @@ int WiFiClient::read()
return _client->read(); return _client->read();
} }
int WiFiClient::read(uint8_t* buf, size_t size) int WiFiClient::read(uint8_t* buf, size_t size)
{ {
return (int) _client->read(reinterpret_cast<char*>(buf), size); return (int)_client->read((char*)buf, size);
}
int WiFiClient::read(char* buf, size_t size)
{
return (int)_client->read(buf, size);
} }
int WiFiClient::peek() int WiFiClient::peek()
...@@ -412,3 +416,28 @@ uint8_t WiFiClient::getKeepAliveCount () const ...@@ -412,3 +416,28 @@ uint8_t WiFiClient::getKeepAliveCount () const
{ {
return _client->getKeepAliveCount(); return _client->getKeepAliveCount();
} }
bool WiFiClient::hasPeekBufferAPI () const
{
return true;
}
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
const char* WiFiClient::peekBuffer ()
{
return _client? _client->peekBuffer(): nullptr;
}
// return number of byte accessible by peekBuffer()
size_t WiFiClient::peekAvailable ()
{
return _client? _client->peekAvailable(): 0;
}
// consume bytes after use (see peekBuffer)
void WiFiClient::peekConsume (size_t consume)
{
if (_client)
_client->peekConsume(consume);
}
...@@ -66,7 +66,9 @@ public: ...@@ -66,7 +66,9 @@ public:
virtual int available() override; virtual int available() override;
virtual int read() override; virtual int read() override;
virtual int read(uint8_t *buf, size_t size) override; virtual int read(uint8_t* buf, size_t size) override;
int read(char* buf, size_t size);
virtual int peek() override; virtual int peek() override;
virtual size_t peekBytes(uint8_t *buffer, size_t length); virtual size_t peekBytes(uint8_t *buffer, size_t length);
size_t peekBytes(char *buffer, size_t length) { size_t peekBytes(char *buffer, size_t length) {
...@@ -120,6 +122,22 @@ public: ...@@ -120,6 +122,22 @@ public:
bool getSync() const; bool getSync() const;
void setSync(bool sync); void setSync(bool sync);
// peek buffer API is present
virtual bool hasPeekBufferAPI () const override;
// return number of byte accessible by peekBuffer()
virtual size_t peekAvailable () override;
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
virtual const char* peekBuffer () override;
// consume bytes after use (see peekBuffer)
virtual void peekConsume (size_t consume) override;
virtual bool outputCanTimeout () override { return connected(); }
virtual bool inputCanTimeout () override { return connected(); }
protected: protected:
static int8_t _s_connected(void* arg, void* tpcb, int8_t err); static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
......
...@@ -362,6 +362,22 @@ int WiFiClientSecureCtx::read(uint8_t *buf, size_t size) { ...@@ -362,6 +362,22 @@ int WiFiClientSecureCtx::read(uint8_t *buf, size_t size) {
return 0; // If we're connected, no error but no read. return 0; // If we're connected, no error but no read.
} }
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
const char* WiFiClientSecureCtx::peekBuffer ()
{
return (const char*)_recvapp_buf;
}
// consume bytes after use (see peekBuffer)
void WiFiClientSecureCtx::peekConsume (size_t consume)
{
// according to WiFiClientSecureCtx::read:
br_ssl_engine_recvapp_ack(_eng, consume);
_recvapp_buf = nullptr;
_recvapp_len = 0;
}
int WiFiClientSecureCtx::read() { int WiFiClientSecureCtx::read() {
uint8_t c; uint8_t c;
if (1 == read(&c, 1)) { if (1 == read(&c, 1)) {
......
...@@ -48,6 +48,7 @@ class WiFiClientSecureCtx : public WiFiClient { ...@@ -48,6 +48,7 @@ class WiFiClientSecureCtx : public WiFiClient {
size_t write_P(PGM_P buf, size_t size) override; size_t write_P(PGM_P buf, size_t size) override;
size_t write(Stream& stream); // Note this is not virtual size_t write(Stream& stream); // Note this is not virtual
int read(uint8_t *buf, size_t size) override; int read(uint8_t *buf, size_t size) override;
int read(char *buf, size_t size) { return read((uint8_t*)buf, size); }
int available() override; int available() override;
int read() override; int read() override;
int peek() override; int peek() override;
...@@ -120,6 +121,19 @@ class WiFiClientSecureCtx : public WiFiClient { ...@@ -120,6 +121,19 @@ class WiFiClientSecureCtx : public WiFiClient {
bool setCiphers(const std::vector<uint16_t>& list); bool setCiphers(const std::vector<uint16_t>& list);
bool setCiphersLessSecure(); // Only use the limited set of RSA ciphers without EC bool setCiphersLessSecure(); // Only use the limited set of RSA ciphers without EC
// peek buffer API is present
virtual bool hasPeekBufferAPI () const override { return true; }
// return number of byte accessible by peekBuffer()
virtual size_t peekAvailable () override { return WiFiClientSecureCtx::available(); }
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
virtual const char* peekBuffer () override;
// consume bytes after use (see peekBuffer)
virtual void peekConsume (size_t consume) override;
protected: protected:
bool _connectSSL(const char *hostName); // Do initial SSL handshake bool _connectSSL(const char *hostName); // Do initial SSL handshake
...@@ -287,6 +301,19 @@ class WiFiClientSecure : public WiFiClient { ...@@ -287,6 +301,19 @@ class WiFiClientSecure : public WiFiClient {
static bool probeMaxFragmentLength(const char *hostname, uint16_t port, uint16_t len); static bool probeMaxFragmentLength(const char *hostname, uint16_t port, uint16_t len);
static bool probeMaxFragmentLength(const String& host, uint16_t port, uint16_t len); static bool probeMaxFragmentLength(const String& host, uint16_t port, uint16_t len);
// peek buffer API is present
virtual bool hasPeekBufferAPI () const override { return true; }
// return number of byte accessible by peekBuffer()
virtual size_t peekAvailable () override { return _ctx->available(); }
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
virtual const char* peekBuffer () override { return _ctx->peekBuffer(); }
// consume bytes after use (see peekBuffer)
virtual void peekConsume (size_t consume) override { return _ctx->peekConsume(consume); }
private: private:
std::shared_ptr<WiFiClientSecureCtx> _ctx; std::shared_ptr<WiFiClientSecureCtx> _ctx;
......
...@@ -29,7 +29,8 @@ typedef void (*discard_cb_t)(void*, ClientContext*); ...@@ -29,7 +29,8 @@ typedef void (*discard_cb_t)(void*, ClientContext*);
extern "C" void esp_yield(); extern "C" void esp_yield();
extern "C" void esp_schedule(); extern "C" void esp_schedule();
#include "DataSource.h" #include <assert.h>
#include <StreamDev.h>
bool getDefaultPrivateGlobalSyncValue (); bool getDefaultPrivateGlobalSyncValue ();
...@@ -374,7 +375,8 @@ public: ...@@ -374,7 +375,8 @@ public:
if (!_pcb) { if (!_pcb) {
return 0; return 0;
} }
return _write_from_source(new BufferDataSource(data, size)); StreamConstPtr ptr(data, size);
return _write_from_source(&ptr);
} }
size_t write(Stream& stream) size_t write(Stream& stream)
...@@ -382,7 +384,7 @@ public: ...@@ -382,7 +384,7 @@ public:
if (!_pcb) { if (!_pcb) {
return 0; return 0;
} }
return _write_from_source(new BufferedStreamDataSource<Stream>(stream, stream.available())); return _write_from_source(&stream);
} }
size_t write_P(PGM_P buf, size_t size) size_t write_P(PGM_P buf, size_t size)
...@@ -390,8 +392,8 @@ public: ...@@ -390,8 +392,8 @@ public:
if (!_pcb) { if (!_pcb) {
return 0; return 0;
} }
ProgmemStream stream(buf, size); StreamConstPtr ptr(buf, size);
return _write_from_source(new BufferedStreamDataSource<ProgmemStream>(stream, size)); return _write_from_source(&ptr);
} }
void keepAlive (uint16_t idle_sec = TCP_DEFAULT_KEEPALIVE_IDLE_SEC, uint16_t intv_sec = TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC, uint8_t count = TCP_DEFAULT_KEEPALIVE_COUNT) void keepAlive (uint16_t idle_sec = TCP_DEFAULT_KEEPALIVE_IDLE_SEC, uint16_t intv_sec = TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC, uint8_t count = TCP_DEFAULT_KEEPALIVE_COUNT)
...@@ -436,6 +438,29 @@ public: ...@@ -436,6 +438,29 @@ public:
_sync = sync; _sync = sync;
} }
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
const char* peekBuffer ()
{
if (!_rx_buf)
return nullptr;
return (const char*)_rx_buf->payload + _rx_buf_offset;
}
// return number of byte accessible by peekBuffer()
size_t peekAvailable ()
{
if (!_rx_buf)
return 0;
return _rx_buf->len - _rx_buf_offset;
}
// consume bytes after use (see peekBuffer)
void peekConsume (size_t consume)
{
_consume(consume);
}
protected: protected:
bool _is_timeout() bool _is_timeout()
...@@ -452,7 +477,7 @@ protected: ...@@ -452,7 +477,7 @@ protected:
} }
} }
size_t _write_from_source(DataSource* ds) size_t _write_from_source(Stream* ds)
{ {
assert(_datasource == nullptr); assert(_datasource == nullptr);
assert(!_send_waiting); assert(!_send_waiting);
...@@ -468,7 +493,6 @@ protected: ...@@ -468,7 +493,6 @@ protected:
if (_is_timeout()) { if (_is_timeout()) {
DEBUGV(":wtmo\r\n"); DEBUGV(":wtmo\r\n");
} }
delete _datasource;
_datasource = nullptr; _datasource = nullptr;
break; break;
} }
...@@ -495,20 +519,20 @@ protected: ...@@ -495,20 +519,20 @@ protected:
return false; return false;
} }
DEBUGV(":wr %d %d\r\n", _datasource->available(), _written); DEBUGV(":wr %d %d\r\n", _datasource->peekAvailable(), _written);
bool has_written = false; bool has_written = false;
while (_datasource) { while (_datasource) {
if (state() == CLOSED) if (state() == CLOSED)
return false; return false;
size_t next_chunk_size = std::min((size_t)tcp_sndbuf(_pcb), _datasource->available()); size_t next_chunk_size = std::min((size_t)tcp_sndbuf(_pcb), _datasource->peekAvailable());
if (!next_chunk_size) if (!next_chunk_size)
break; break;
const uint8_t* buf = _datasource->get_buffer(next_chunk_size); const char* buf = _datasource->peekBuffer();
uint8_t flags = 0; uint8_t flags = 0;
if (next_chunk_size < _datasource->available()) if (next_chunk_size < _datasource->peekAvailable())
// PUSH is meant for peer, telling to give data to user app as soon as received // PUSH is meant for peer, telling to give data to user app as soon as received
// PUSH "may be set" when sender has finished sending a "meaningful" data block // PUSH "may be set" when sender has finished sending a "meaningful" data block
// PUSH does not break Nagle // PUSH does not break Nagle
...@@ -522,15 +546,15 @@ protected: ...@@ -522,15 +546,15 @@ protected:
err_t err = tcp_write(_pcb, buf, next_chunk_size, flags); err_t err = tcp_write(_pcb, buf, next_chunk_size, flags);
DEBUGV(":wrc %d %d %d\r\n", next_chunk_size, _datasource->available(), (int)err); DEBUGV(":wrc %d %d %d\r\n", next_chunk_size, _datasource->peekAvailable(), (int)err);
if (err == ERR_OK) { if (err == ERR_OK) {
_datasource->release_buffer(buf, next_chunk_size); _datasource->peekConsume(next_chunk_size);
_written += next_chunk_size; _written += next_chunk_size;
has_written = true; has_written = true;
} else { } else {
// ERR_MEM(-1) is a valid error meaning // ERR_MEM(-1) is a valid error meaning
// "come back later". It leaves state() opened // "come back later". It leaves state() opened
break; break;
} }
} }
...@@ -565,8 +589,6 @@ protected: ...@@ -565,8 +589,6 @@ protected:
void _consume(size_t size) void _consume(size_t size)
{ {
if(_pcb)
tcp_recved(_pcb, size);
ptrdiff_t left = _rx_buf->len - _rx_buf_offset - size; ptrdiff_t left = _rx_buf->len - _rx_buf_offset - size;
if(left > 0) { if(left > 0) {
_rx_buf_offset += size; _rx_buf_offset += size;
...@@ -583,6 +605,8 @@ protected: ...@@ -583,6 +605,8 @@ protected:
pbuf_ref(_rx_buf); pbuf_ref(_rx_buf);
pbuf_free(head); pbuf_free(head);
} }
if(_pcb)
tcp_recved(_pcb, size);
} }
err_t _recv(tcp_pcb* pcb, pbuf* pb, err_t err) err_t _recv(tcp_pcb* pcb, pbuf* pb, err_t err)
...@@ -683,7 +707,7 @@ private: ...@@ -683,7 +707,7 @@ private:
discard_cb_t _discard_cb; discard_cb_t _discard_cb;
void* _discard_cb_arg; void* _discard_cb_arg;
DataSource* _datasource = nullptr; Stream* _datasource = nullptr;
size_t _written = 0; size_t _written = 0;
uint32_t _timeout_ms = 5000; uint32_t _timeout_ms = 5000;
uint32_t _op_start_time = 0; uint32_t _op_start_time = 0;
......
/* DataSource.h - a read-only object similar to Stream, but with less methods
* Copyright (c) 2016 Ivan Grokhotkov. All rights reserved.
* This file is distributed under MIT license.
*/
#ifndef DATASOURCE_H
#define DATASOURCE_H
#include <assert.h>
class DataSource {
public:
virtual ~DataSource() {}
virtual size_t available() = 0;
virtual const uint8_t* get_buffer(size_t size) = 0;
virtual void release_buffer(const uint8_t* buffer, size_t size) = 0;
};
class BufferDataSource : public DataSource {
public:
BufferDataSource(const uint8_t* data, size_t size) :
_data(data),
_size(size)
{
}
size_t available() override
{
return _size - _pos;
}
const uint8_t* get_buffer(size_t size) override
{
(void)size;
assert(_pos + size <= _size);
return _data + _pos;
}
void release_buffer(const uint8_t* buffer, size_t size) override
{
(void)buffer;
assert(buffer == _data + _pos);
_pos += size;
}
protected:
const uint8_t* _data;
const size_t _size;
size_t _pos = 0;
};
template<typename TStream>
class BufferedStreamDataSource : public DataSource {
public:
BufferedStreamDataSource(TStream& stream, size_t size) :
_stream(stream),
_size(size)
{
}
size_t available() override
{
return _size - _pos;
}
const uint8_t* get_buffer(size_t size) override
{
assert(_pos + size <= _size);
//Data that was already read from the stream but not released (e.g. if tcp_write error occured). Otherwise this should be 0.
const size_t stream_read = _streamPos - _pos;
//Min required buffer size: max(requested size, previous stream data already in buffer)
const size_t min_buffer_size = size > stream_read ? size : stream_read;
//Buffer too small?
if (_bufferSize < min_buffer_size) {
uint8_t *new_buffer = new uint8_t[min_buffer_size];
//If stream reading is ahead, than some data is already in the old buffer and needs to be copied to new resized buffer
if (_buffer && stream_read > 0) {
memcpy(new_buffer, _buffer.get(), stream_read);
}
_buffer.reset(new_buffer);
_bufferSize = min_buffer_size;
}
//Fetch remaining data from stream
//If error in tcp_write in ClientContext::_write_some() occured earlier and therefore release_buffer was not called last time, than the requested stream data is already in the buffer.
if (size > stream_read) {
//Remaining bytes to read from stream
const size_t stream_rem = size - stream_read;
const size_t cb = _stream.readBytes(reinterpret_cast<char*>(_buffer.get() + stream_read), stream_rem);
assert(cb == stream_rem);
(void)cb;
_streamPos += stream_rem;
}
return _buffer.get();
}
void release_buffer(const uint8_t* buffer, size_t size) override
{
if (size == 0) {
return;
}
(void)buffer;
_pos += size;
//Cannot release more than acquired through get_buffer
assert(_pos <= _streamPos);
//Release less than requested with get_buffer?
if (_pos < _streamPos) {
// Move unreleased stream data in buffer to front
assert(_buffer);
memmove(_buffer.get(), _buffer.get() + size, _streamPos - _pos);
}
}
protected:
TStream & _stream;
std::unique_ptr<uint8_t[]> _buffer;
size_t _size;
size_t _pos = 0;
size_t _bufferSize = 0;
size_t _streamPos = 0;
};
class ProgmemStream
{
public:
ProgmemStream(PGM_P buf, size_t size) :
_buf(buf),
_left(size)
{
}
size_t readBytes(char* dst, size_t size)
{
size_t will_read = (_left < size) ? _left : size;
memcpy_P((void*)dst, (PGM_VOID_P)_buf, will_read);
_left -= will_read;
_buf += will_read;
return will_read;
}
protected:
PGM_P _buf;
size_t _left;
};
#endif //DATASOURCE_H
...@@ -379,7 +379,7 @@ public: ...@@ -379,7 +379,7 @@ public:
return result; return result;
} }
size_t read(uint8_t* buf, size_t size) override { int read(uint8_t* buf, size_t size) override {
if (!_opened || !_fd | !buf) { if (!_opened || !_fd | !buf) {
return 0; return 0;
} }
......
...@@ -75,7 +75,7 @@ private: ...@@ -75,7 +75,7 @@ private:
WiFiClient tcpDumpClient; WiFiClient tcpDumpClient;
char* packetBuffer = nullptr; char* packetBuffer = nullptr;
size_t bufferIndex = 0; int bufferIndex = 0;
static constexpr int tcpBufferSize = 2048; static constexpr int tcpBufferSize = 2048;
static constexpr int maxPcapLength = 1024; static constexpr int maxPcapLength = 1024;
......
...@@ -287,7 +287,7 @@ public: ...@@ -287,7 +287,7 @@ public:
return _opened ? _fd->write(buf, size) : -1; return _opened ? _fd->write(buf, size) : -1;
} }
size_t read(uint8_t* buf, size_t size) override int read(uint8_t* buf, size_t size) override
{ {
return _opened ? _fd->read(buf, size) : -1; return _opened ? _fd->read(buf, size) : -1;
} }
......
// this example sketch in the public domain is also a host and device test
#include <StreamDev.h>
#include <StreamString.h>
void loop() {
delay(1000);
}
void checksketch(const char* what, const char* res1, const char* res2) {
if (strcmp(res1, res2) == 0) {
Serial << "PASSED: Test " << what << " (result: '" << res1 << "')\n";
} else {
Serial << "FAILED: Test " << what << ": '" << res1 << "' <> '" << res2 << "' !\n";
}
}
#ifndef check
#define check(what, res1, res2) checksketch(what, res1, res2)
#endif
void testStringPtrProgmem() {
static const char inProgmem [] PROGMEM = "I am in progmem";
auto inProgmem2 = F("I am too in progmem");
int heap = (int)ESP.getFreeHeap();
auto stream1 = StreamConstPtr(inProgmem, sizeof(inProgmem) - 1);
auto stream2 = StreamConstPtr(inProgmem2);
Serial << stream1 << " - " << stream2 << "\n";
heap -= (int)ESP.getFreeHeap();
check("NO heap occupation while streaming progmem strings", String(heap).c_str(), "0");
}
void testStreamString() {
String inputString = "hello";
StreamString result;
// By default, reading a S2Stream(String) or a StreamString will consume the String.
// It can be disabled by calling ::resetPointer(), (not default)
// and reenabled by calling ::setConsume(). (default)
//
// In default consume mode, reading a byte or a block will remove it from
// the String. Operations are O(n²).
//
// In non-default non-consume mode, it will just move a pointer. That one
// can be ::resetPointer(pos) anytime. See the example below.
// The String included in 'result' will not be modified by read:
// (this is not the default)
result.resetPointer();
{
// We use a a lighter StreamConstPtr(input) to make a read-only Stream out of
// a String that obviously should not be modified during the time the
// StreamConstPtr instance is used. It is used as a source to be sent to
// 'result'.
result.clear();
StreamConstPtr(inputString).sendAll(result);
StreamConstPtr(inputString).sendAll(result);
StreamConstPtr(inputString).sendAll(result);
check("StreamConstPtr.sendAll(StreamString)", result.c_str(), "hellohellohello");
}
{
// equivalent of the above
result.clear();
result << inputString;
result << inputString << inputString;
check("StreamString<<String", result.c_str(), "hellohellohello");
}
{
// Now inputString is made into a Stream using S2Stream,
// and set in non-consume mode (using ::resetPointer()).
// Then, after that input is read once, it won't be anymore readable
// until the pointer is reset.
S2Stream input(inputString);
input.resetPointer();
result.clear();
input.sendAll(result);
input.sendAll(result);
check("S2Stream.sendAll(StreamString)", result.c_str(), "hello");
check("unmodified String given to S2Stream", inputString.c_str(), "hello");
}
{
// Same as above, with an offset
result.clear();
S2Stream input(inputString);
// stream position set to offset 2 (0 by default)
input.resetPointer(2);
input.sendAll(result);
input.sendAll(result);
check("S2Stream.resetPointer(2):", result.c_str(), "llo");
}
{
// inputString made into a Stream
// reading the Stream consumes the String
result.clear();
S2Stream input(inputString);
// reading stream will consume the string
input.setConsume(); // can be ommitted, this is the default
input.sendSize(result, 1);
input.sendSize(result, 2);
check("setConsume(): S2Stream().sendSize(StreamString,3)", result.c_str(), "hel");
check("setConsume(): String given from S2Stream is swallowed", inputString.c_str(), "lo");
}
// Streaming with common String constructors
{
StreamString cons(inputString);
check("StreamString(String)", cons.c_str(), inputString.c_str());
}
{
StreamString cons(result);
check("StreamString(char*)", cons.c_str(), result.c_str());
}
{
StreamString cons("abc");
check("StreamString(char*)", cons.c_str(), "abc");
}
{
StreamString cons(F("abc"));
check("StreamString(F())", cons.c_str(), "abc");
}
{
StreamString cons(23);
check("StreamString(int)", cons.c_str(), "23");
}
{
StreamString cons('a');
check("StreamString(char)", cons.c_str(), "a");
}
{
StreamString cons(23.2);
check("StreamString(float)", cons.c_str(), "23.20");
}
#if !CORE_MOCK
// A progmem won't use Heap when StringPtr is used
testStringPtrProgmem();
// .. but it does when S2Stream or StreamString is used
{
int heap = (int)ESP.getFreeHeap();
auto stream = StreamString(F("I am in progmem"));
Serial << stream << "\n";
heap -= (int)ESP.getFreeHeap();
String heapStr(heap);
if (heap != 0) {
check("heap is occupied by String/StreamString(progmem)", heapStr.c_str(), heapStr.c_str());
} else {
check("ERROR: heap should be occupied by String/StreamString(progmem)", heapStr.c_str(), "-1");
}
}
// (check again to be sure)
testStringPtrProgmem();
#endif
}
#ifndef TEST_CASE
void setup() {
Serial.begin(115200);
delay(1000);
testStreamString();
Serial.printf("sizeof: String:%d Stream:%d StreamString:%d SStream:%d\n",
(int)sizeof(String), (int)sizeof(Stream), (int)sizeof(StreamString), (int)sizeof(S2Stream));
}
#endif
...@@ -64,7 +64,7 @@ $(TEST_LIST): ...@@ -64,7 +64,7 @@ $(TEST_LIST):
$(SILENT)mkdir -p $(LOCAL_BUILD_DIR) $(SILENT)mkdir -p $(LOCAL_BUILD_DIR)
ifeq ("$(MOCK)", "1") ifeq ("$(MOCK)", "1")
@echo Compiling $(notdir $@) @echo Compiling $(notdir $@)
(cd ../host; make ULIBDIRS=../device/libraries/BSTest ../device/$(@:%.ino=%)) (cd ../host; make D=$(V) ULIBDIRS=../device/libraries/BSTest ../device/$(@:%.ino=%))
$(SILENT)source $(BS_DIR)/virtualenv/bin/activate && \ $(SILENT)source $(BS_DIR)/virtualenv/bin/activate && \
$(PYTHON) $(BS_DIR)/runner.py \ $(PYTHON) $(BS_DIR)/runner.py \
$(RUNNER_DEBUG_FLAG) \ $(RUNNER_DEBUG_FLAG) \
......
#include <Arduino.h>
#include <BSTest.h>
#define check(what, res1, res2) CHECK(strcmp(res1, res2) == 0)
#include "../../../libraries/esp8266/examples/StreamString/StreamString.ino"
BS_ENV_DECLARE();
bool pretest ()
{
return true;
}
void setup ()
{
Serial.begin(115200);
BS_RUN(Serial);
}
TEST_CASE("StreamString tests", "[StreamString]")
{
testStream();
}
...@@ -11,6 +11,7 @@ def setup_echo_server(e): ...@@ -11,6 +11,7 @@ def setup_echo_server(e):
global stop_client_thread global stop_client_thread
global client_thread global client_thread
def echo_client_thread(): def echo_client_thread():
time.sleep(1) # let some time for mDNS to start
server_address = socket.gethostbyname('esp8266-wfs-test.local') server_address = socket.gethostbyname('esp8266-wfs-test.local')
count = 0 count = 0
while count < 5 and not stop_client_thread: while count < 5 and not stop_client_thread:
......
...@@ -76,7 +76,7 @@ $(shell mkdir -p $(BINDIR)) ...@@ -76,7 +76,7 @@ $(shell mkdir -p $(BINDIR))
CORE_CPP_FILES := \ CORE_CPP_FILES := \
$(addprefix $(abspath $(CORE_PATH))/,\ $(addprefix $(abspath $(CORE_PATH))/,\
debug.cpp \ debug.cpp \
StreamString.cpp \ StreamSend.cpp \
Stream.cpp \ Stream.cpp \
WString.cpp \ WString.cpp \
Print.cpp \ Print.cpp \
......
...@@ -80,4 +80,3 @@ cont_t* g_pcont = NULL; ...@@ -80,4 +80,3 @@ cont_t* g_pcont = NULL;
extern "C" void cont_yield(cont_t*) extern "C" void cont_yield(cont_t*)
{ {
} }
...@@ -89,7 +89,8 @@ ssize_t mockFillInBuf (int sock, char* ccinbuf, size_t& ccinbufsize) ...@@ -89,7 +89,8 @@ ssize_t mockFillInBuf (int sock, char* ccinbuf, size_t& ccinbufsize)
if (ret == 0) if (ret == 0)
{ {
// connection closed // connection closed
return -1; // nothing is read
return 0;
} }
if (ret == -1) if (ret == -1)
...@@ -97,16 +98,20 @@ ssize_t mockFillInBuf (int sock, char* ccinbuf, size_t& ccinbufsize) ...@@ -97,16 +98,20 @@ ssize_t mockFillInBuf (int sock, char* ccinbuf, size_t& ccinbufsize)
if (errno != EAGAIN) if (errno != EAGAIN)
{ {
fprintf(stderr, MOCK "ClientContext::(read/peek fd=%i): filling buffer for %zd bytes: %s\n", sock, maxread, strerror(errno)); fprintf(stderr, MOCK "ClientContext::(read/peek fd=%i): filling buffer for %zd bytes: %s\n", sock, maxread, strerror(errno));
// error
return -1; return -1;
} }
ret = 0; ret = 0;
} }
ccinbufsize += ret; ccinbufsize += ret;
return ret; return ret;
} }
ssize_t mockPeekBytes (int sock, char* dst, size_t usersize, int timeout_ms, char* ccinbuf, size_t& ccinbufsize) ssize_t mockPeekBytes (int sock, char* dst, size_t usersize, int timeout_ms, char* ccinbuf, size_t& ccinbufsize)
{ {
// usersize==0: peekAvailable()
if (usersize > CCBUFSIZE) if (usersize > CCBUFSIZE)
mockverbose("CCBUFSIZE(%d) should be increased by %zd bytes (-> %zd)\n", CCBUFSIZE, usersize - CCBUFSIZE, usersize); mockverbose("CCBUFSIZE(%d) should be increased by %zd bytes (-> %zd)\n", CCBUFSIZE, usersize - CCBUFSIZE, usersize);
...@@ -114,7 +119,7 @@ ssize_t mockPeekBytes (int sock, char* dst, size_t usersize, int timeout_ms, cha ...@@ -114,7 +119,7 @@ ssize_t mockPeekBytes (int sock, char* dst, size_t usersize, int timeout_ms, cha
size_t retsize = 0; size_t retsize = 0;
do do
{ {
if (usersize <= ccinbufsize) if (usersize && usersize <= ccinbufsize)
{ {
// data already buffered // data already buffered
retsize = usersize; retsize = usersize;
...@@ -123,7 +128,14 @@ ssize_t mockPeekBytes (int sock, char* dst, size_t usersize, int timeout_ms, cha ...@@ -123,7 +128,14 @@ ssize_t mockPeekBytes (int sock, char* dst, size_t usersize, int timeout_ms, cha
// check incoming data data // check incoming data data
if (mockFillInBuf(sock, ccinbuf, ccinbufsize) < 0) if (mockFillInBuf(sock, ccinbuf, ccinbufsize) < 0)
{
return -1; return -1;
}
if (usersize == 0 && ccinbufsize)
// peekAvailable
return ccinbufsize;
if (usersize <= ccinbufsize) if (usersize <= ccinbufsize)
{ {
// data just received // data just received
...@@ -179,7 +191,7 @@ ssize_t mockWrite (int sock, const uint8_t* data, size_t size, int timeout_ms) ...@@ -179,7 +191,7 @@ ssize_t mockWrite (int sock, const uint8_t* data, size_t size, int timeout_ms)
#endif #endif
if (ret == -1) if (ret == -1)
{ {
fprintf(stderr, MOCK "ClientContext::read: write(%d): %s\n", sock, strerror(errno)); fprintf(stderr, MOCK "ClientContext::write/send(%d): %s\n", sock, strerror(errno));
return -1; return -1;
} }
sent += ret; sent += ret;
...@@ -187,6 +199,8 @@ ssize_t mockWrite (int sock, const uint8_t* data, size_t size, int timeout_ms) ...@@ -187,6 +199,8 @@ ssize_t mockWrite (int sock, const uint8_t* data, size_t size, int timeout_ms)
fprintf(stderr, MOCK "ClientContext::write: sent %d bytes (%zd / %zd)\n", ret, sent, size); fprintf(stderr, MOCK "ClientContext::write: sent %d bytes (%zd / %zd)\n", ret, sent, size);
} }
} }
fprintf(stderr, MOCK "ClientContext::write: total sent %zd bytes\n", sent); #ifdef DEBUG_ESP_WIFI
mockverbose(MOCK "ClientContext::write: total sent %zd bytes\n", sent);
#endif
return sent; return sent;
} }
...@@ -491,3 +491,9 @@ uart_detect_baudrate(int uart_nr) ...@@ -491,3 +491,9 @@ uart_detect_baudrate(int uart_nr)
} }
}; };
size_t uart_peek_available (uart_t* uart) { return 0; }
const char* uart_peek_buffer (uart_t* uart) { return nullptr; }
void uart_peek_consume (uart_t* uart, size_t consume) { (void)uart; (void)consume; }
...@@ -27,7 +27,7 @@ class WiFiClient; ...@@ -27,7 +27,7 @@ class WiFiClient;
extern "C" void esp_yield(); extern "C" void esp_yield();
extern "C" void esp_schedule(); extern "C" void esp_schedule();
#include <include/DataSource.h> #include <assert.h>
bool getDefaultPrivateGlobalSyncValue (); bool getDefaultPrivateGlobalSyncValue ();
...@@ -300,6 +300,33 @@ public: ...@@ -300,6 +300,33 @@ public:
_sync = sync; _sync = sync;
} }
// return a pointer to available data buffer (size = peekAvailable())
// semantic forbids any kind of read() before calling peekConsume()
const char* peekBuffer ()
{
return _inbuf;
}
// return number of byte accessible by peekBuffer()
size_t peekAvailable ()
{
ssize_t ret = mockPeekBytes(_sock, nullptr, 0, 0, _inbuf, _inbufsize);
if (ret < 0)
{
abort();
return 0;
}
return _inbufsize;
}
// consume bytes after use (see peekBuffer)
void peekConsume (size_t consume)
{
assert(consume <= _inbufsize);
memmove(_inbuf, _inbuf + consume, _inbufsize - consume);
_inbufsize -= consume;
}
private: private:
discard_cb_t _discard_cb = nullptr; discard_cb_t _discard_cb = nullptr;
......
...@@ -15,8 +15,10 @@ libraries/ESP8266mDNS ...@@ -15,8 +15,10 @@ libraries/ESP8266mDNS
libraries/Wire libraries/Wire
libraries/lwIP* libraries/lwIP*
cores/esp8266/Lwip* cores/esp8266/Lwip*
cores/esp8266/core_esp8266_si2c.cpp
cores/esp8266/debug* cores/esp8266/debug*
cores/esp8266/core_esp8266_si2c.cpp
cores/esp8266/StreamString.*
cores/esp8266/StreamSend.*
libraries/Netdump libraries/Netdump
" "
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册