Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WiFiClientSecure: don't trash unread decrypted data when writing #4024

Merged
merged 2 commits into from
Dec 26, 2017

Conversation

igrr
Copy link
Member

@igrr igrr commented Dec 25, 2017

When application requests to write data, check if there is any unread decrypted data left. If there is, don't write immediately, but save the data to be written. When all decrypted data has been consumed by the application, send out the saved outgoing data.

Also don't use available() in the test for connected() to reduce the chance that plaintext buffer gets filled with incoming data when the application does not actually need it, and does not need to know its exact size.

Fixes #2256.

Copy link
Collaborator

@devyte devyte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of queue and all the C-style data handling, wouldn't it be better to use std C++ containers? E.g.: a std::list of std::vector or something along those lines. It would reduce code complexity. The user-facing interface can be wrapped hide the container syntax.

@igrr
Copy link
Member Author

igrr commented Dec 25, 2017

Vector doesn't fit: when you try to append a new block of data, it will try to allocate contiguous region to fit all existing data and new data. With fragmented heap, this might not be possible. List-of-buffers implemented here does not have this limitation.
List from STL could be used, but it has higher overhead: two allocations (one for list entry, one for the data itself) instead of one, and two pointers instead of one to maintain the list. Pulling in std::list also increases code size by a few kB, compared to the proposed solution.

(Not to say I'm totally happy with the existing code — which is the reason this is marked as WIP).

@igrr
Copy link
Member Author

igrr commented Dec 25, 2017

std::list might be okay though, if I can make the code size hit less noticable.

@earlephilhower
Copy link
Collaborator

Does this mean that BearSSL replacement is no longer on the table? It's separate RX and TX buffers (of separately configurable sizes) would avoid contortions like this, no?

@igrr
Copy link
Member Author

igrr commented Dec 25, 2017

@earlephilhower It doesn't meant that. This workaround is intended for axTLS only. I assume that if/when BearSSL is adopted, WiFiClientSecure will need a more or less full rewrite.

@igrr igrr force-pushed the bugfix/wificlientsecure_full_duplex branch from d5dbaa6 to 4dbab22 Compare December 26, 2017 00:35
@igrr
Copy link
Member Author

igrr commented Dec 26, 2017

@devyte Please take a look, i have replaced all C-style buffer handling with std::list + std::unique_ptr. Turns out I have already pulled in std::list from ESP8266WiFiGeneric.cpp, so code size overhead from this change is just a couple hundred bytes, compared to STAILQ.

@igrr igrr changed the title WIP: WiFiClientSecure: don't trash unread decrypted data when writing WiFiClientSecure: don't trash unread decrypted data when writing Dec 26, 2017
@devyte
Copy link
Collaborator

devyte commented Dec 26, 2017

I was doing just that. Looks much better! I'll add specific comments to code change lines.

typedef struct BufferItem
{
BufferItem(const uint8_t* data_, size_t size_) : size(size_) {
data.reset(new uint8_t[size]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest removing this reset() in the body and directly constructing:
BufferItem(const uint8_t* data_, size_t size_) :
size(size_),
data(new uint8_t[size_]) {

}
return result;
}

int write(const uint8_t* src, size_t size)
{
if (!_available) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check !_available or !hasData()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to check for hasData, as far as i can tell — we only need to make sure that the fragment buffer is empty.

*/
return _writeBufferAdd(src, size);
}

int peek()
{
if (!_available) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check !available or !hasData()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. The logic is:

  1. Is there some incoming plaintext data? If not, we'll try to get any received data from the TCP stack, and decrypt it.
  2. If after step 1 there is some plaintext data, return the first byte of it.

data.reset(new uint8_t[size]);
if (data.get() != nullptr) {
memcpy(data.get(), data_, size);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe an else here with a DEBUGV msg?


_writeBuffers.emplace_back(data, size);
if (_writeBuffers.back().data.get() == nullptr) {
_writeBuffers.pop_back();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mayeb add a DEBUGV msg here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a DEBUGV into BufferItem constructor, where the memory allocation failure is first detected.


int _writeBuffersSend()
{
while (!_writeBuffers.empty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a lengthy operation? Are there timing concerns due to the exection time of this loop?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on the amount of data queued for transmission. In most cases, this will be zero. For protocols like MQTT this may send a few packets. There will be yields in ClientContext, so we don't need to worry about starving the WiFi stack here.

auto& first = _writeBuffers.front();
int rc = _write(first.data.get(), first.size);
_writeBuffers.pop_front();
if (rc < 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a DEBUGV msg here for the failure and for the buffers getting dropped (data drop)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added DEBUGV for ssl_write failure into _write and a message about buffers being dropped here.

When application requests to write data, check if there is any unread
decrypted data left. If there is, don't write immediately, but save the
data to be written. When all decrypted data has been consumed by the
application, send out the saved outgoing data.

Fixes #2256.
@igrr igrr force-pushed the bugfix/wificlientsecure_full_duplex branch from 4dbab22 to 02bc538 Compare December 26, 2017 03:50
@vshymanskyy
Copy link

@igrr Thank you. Looks stable in our scenario

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants