gilmaimon / ArduinoWebsockets

A library for writing modern websockets applications with Arduino (ESP8266 and ESP32)
GNU General Public License v3.0
464 stars 97 forks source link

Secure sendBinary issue when data is over 16384 in size ('highWatermark') #61

Closed kosso closed 4 years ago

kosso commented 4 years ago

I am running a Secure Websocket Server using NodeJS andhttps and ws and sending image data to it from an ESP32-CAM.

All messages are being sent and received OK, including image data from the ESP32-CAM as long as the message/image data is under 16384 bytes.

I can see on my node server that the 16384 value is known as the maximum 'highWaterMark' by the TSLSocket connection.

If the message data is over 16384 bytes, the websocket connection gets closed and the server reports an error: "RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear"

Is there something I need to add in the ESP32 Arduino code to deal with this? Or is it something I need to configure on the server?

Note: All works fine with larger message data when using insecure (ws://) ports, so this is solely do to with the secure implementation (wss://).

Any pointers would be very much appreciated, thank you!

gilmaimon commented 4 years ago

Hi @kosso, thank you for openning the issue. The error message seems to me like the websockets server tries to read a ssl management message as websockets message. The fact that stuff work without SSL makes me think that the issue is not related to my library (cause the logic for ssl clients and normal clients is the same, just the underlying esp client is diff). Consider splitting the big message to parts, you could use streams and fragmented messages which are part of the library and the websockets protocol :) Some info here: https://github.com/gilmaimon/TinyWebsockets/wiki/Client#Streaming---Sending-Fragmented-Messages

Gil.

kosso commented 4 years ago

Hi @gilmaimon

Thanks for the pointer to the TinyWebsockets library. It turns out that's exactly what I needed to do!

Here's some (kind of) pseudo-code which might help others:


// The chunk sender method
void sendChunk(char* data, uint16_t len){
  client.sendBinary(data, len);
}

// Method to chunk a buffer then pass it to sendChunk() 
uint16_t sendChunkedBuffer(char* ptrData, uint16_t dataLength, uint16_t chunkLength,void(*action)(char*, uint16_t)) {
    char chunkData[chunkLength +1];
    chunkData[chunkLength] = 0;                             //terminate chunk buffer
    uint16_t chunks = dataLength / chunkLength;             //complete chunks
    uint16_t lastChunkLength = dataLength % chunkLength;    //partial or empty
    ptrData[dataLength] = 0;                                //prevent buffer overrun
    if(chunks) {
        for(uint16_t i = 0; i < chunks; i++) {
            memcpy(chunkData, ptrData, chunkLength);
            ptrData += chunkLength;
            action(chunkData, chunkLength);
        }
        if(lastChunkLength) {
            memcpy(chunkData, ptrData, lastChunkLength + 1);      //copy terminator from source
            action(chunkData, lastChunkLength);
            chunks++;
        }
    }
    return chunks;
}

// in my sendImage method/loop ... 
// fb = an image frame buffer obtained from an ESP32 (OV2640) camera. 
// ... 
// start chunked stream..
client.streamBinary();
// split buffer into chunks and send with client.sendBinary(buf, len)
sendChunkedBuffer( (char *)fb->buf, fb->len, CHUNK_LENGTH, sendChunk);
// end the chunked stream
client.end();

Thanks again! :+1:

gilmaimon commented 4 years ago

Thank you for the detailed info, I'm sure many others will thank you in the future 😄

araminimichael commented 2 years ago

Hi @gilmaimon

Thanks for the pointer to the TinyWebsockets library. It turns out that's exactly what I needed to do!

Here's some (kind of) pseudo-code which might help others:


// The chunk sender method
void sendChunk(char* data, uint16_t len){
  client.sendBinary(data, len);
}

// Method to chunk a buffer then pass it to sendChunk() 
uint16_t sendChunkedBuffer(char* ptrData, uint16_t dataLength, uint16_t chunkLength,void(*action)(char*, uint16_t)) {
    char chunkData[chunkLength +1];
    chunkData[chunkLength] = 0;                             //terminate chunk buffer
    uint16_t chunks = dataLength / chunkLength;             //complete chunks
    uint16_t lastChunkLength = dataLength % chunkLength;    //partial or empty
    ptrData[dataLength] = 0;                                //prevent buffer overrun
    if(chunks) {
        for(uint16_t i = 0; i < chunks; i++) {
            memcpy(chunkData, ptrData, chunkLength);
            ptrData += chunkLength;
            action(chunkData, chunkLength);
        }
        if(lastChunkLength) {
            memcpy(chunkData, ptrData, lastChunkLength + 1);      //copy terminator from source
            action(chunkData, lastChunkLength);
            chunks++;
        }
    }
    return chunks;
}

// in my sendImage method/loop ... 
// fb = an image frame buffer obtained from an ESP32 (OV2640) camera. 
// ... 
// start chunked stream..
client.streamBinary();
// split buffer into chunks and send with client.sendBinary(buf, len)
sendChunkedBuffer( (char *)fb->buf, fb->len, CHUNK_LENGTH, sendChunk);
// end the chunked stream
client.end();

Thanks again! 👍

Hi @Kosso, I'm a complete beginner. I tried to follow the tutorial on youtube https://www.youtube.com/watch?v=kE0idrJYPII and I came across your comment. If I understand correctly, you can stream in high resolution. I tried to implement your piece of code. but Arduino IDE tells me that CHUNK_LENGTH function is not declared. on this piece of code: sendChunkedBuffer( (char *)fb->buf, fb->len, CHUNK_LENGTH, sendChunk); I tried putting the void (the first part of your comment) before or after the void loop/setup. but that doesn't change anything. I googled the term but it got me nowhere.

do you have client side code (and server side code if it changes from video) that you could share?

I'm sorry to ask you to help me but I'm a ball of code and managed to get the code in the video to work with some tweaking. but I would have liked to have a better resolution but I don't understand how to implement your piece of code.

thank you for your help if you can help me. otherwise thank you very much for sharing your code, it will be useful to others who understand much better than me. really sorry for English (translated) my language is French. Michael

kosso commented 2 years ago

@araminimichael This was quite some time ago. But I think you can choose the number for CHUNK_SIZE yourself, as long as it's lower than the 'highWaterMark' of 16384.

araminimichael commented 2 years ago

@araminimichael This was quite some time ago. But I think you can choose the number for CHUNK_SIZE yourself, as long as it's lower than the 'highWaterMark' of 16384. @kosso Thank you For your answer. I'll look at that soon.

havingfu commented 3 months ago

I had the same problem, finally found the solution:

Instead of client.sendBinary((const char *)fb->buf, fb->len)

do this:

// Stream the image data in chunks
const size_t chunkSize = 16000;
size_t offset = 0;
client.streamBinary();
while (offset < fb->len)
{
    size_t sendSize = min(chunkSize, fb->len - offset);
    client.sendBinary((const char *)(fb->buf + offset), sendSize);
    offset += sendSize;
}
client.end();

esp_camera_fb_return(fb);

That worked like a charm, no need to change any nodejs code. it will adapt to the streaming change.

@gilmaimon תודה, אחלה של ספרייה