Memory Corruption in ESP32 WebSocket Server

Hello,
The code that manages the (ws-data (resource)) is creating a memory corruption. The data passed from the webpage and transmitted on the socket is randomly corrupted in the process.
Here is a simple test… Use code/webpage BELOW and monitor the output from WS_EVT_DATA (*data) on the serial monitor. You will see the random corruptions, randomly. Sometimes is starts after the first few transmissions, other times it takes 100’s of transmits to show itself. Press “AUTO DATA”, the page simply pulls strings from an array and sends it on the socket.
The library is unusable as it is.
Thank you,
Chris Loubier

CODE:

// Import required libraries
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>

// Replace with your network credentials
const char* ssid = "<censored>";
const char* password = "<censored>";

bool ledState = 0;
const int ledPin = 2;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

void notifyClients() {
  ws.textAll(String(ledState));
  
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  //Serial.printf("data= %s\n", data);
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (strcmp((char*)data, "toggle") == 0) {
      ledState = !ledState;
      notifyClients();
    }
  }
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      Serial.printf("WS_EVT_DATA_0 = %s\n", (char*)data);
      
      handleWebSocketMessage(arg, data, len);
     
      
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if (ledState){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  if(!SPIFFS.begin()){
     Serial.println("An Error has occurred while mounting SPIFFS");
     return;
  }
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

// You can remove the password parameter if you want the AP to be open.
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, password);
  Serial.println("Wait 100 ms for AP_START…");
  delay(100);
  
  Serial.println("Set softAPConfig");
  IPAddress Ip(10,0,0,1);
  IPAddress NMask(255, 0, 0, 0);
  WiFi.softAPConfig(Ip, Ip, NMask);
  
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);
 

  initWebSocket();
  // Route for root / web page
  server.on("/html", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/index.html", "text/html");
  });

  server.on("/jquery-3.3.1.min.js", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/jquery-3.6.0.min.js", "text/javascript");
  });

  server.begin();
  // Route for root / web page
  //server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  //  request->send_P(200, "text/html", index_html, processor);
  //});

  // Start server
  //server.begin();
}

void loop() {
  ws.cleanupClients();
  digitalWrite(ledPin, ledState);
}

WEB PAGE:

<!DOCTYPE HTML><html>
<head>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">

<script>
var counter = 0;
var autoSend = false;
var autoDate = ['P0F0,1231', 'P0F0,1232','P0F0,1234','P0F0,1235','P0F0,1236','P0F0,1237', 'P0F0,1238','P0F0,1239','P0F0,1230','P0F0,0000',]
</script>

<style>
html {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
}
h1 {
font-size: 1.8rem;
color: white;
}
h2{
font-size: 1.5rem;
font-weight: bold;
color: #143642;
}
.topnav {
overflow: hidden;
background-color: #143642;
}
body {
margin: 0;
}
.content {
padding: 30px;
max-width: 600px;
margin: 0 auto;
}
.card {
background-color: #F8F7F9;;
box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
padding-top:10px;
padding-bottom:20px;
}
.button {
padding: 15px 50px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #0f8b8d;
border: none;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
/*.button:hover {background-color: #0f8b8d}*/
.button:active {
background-color: #0f8b8d;
box-shadow: 2 2px #CDCDCD;
transform: translateY(2px);
}
.state {
font-size: 1.5rem;
color:#8c8c8c;
font-weight: bold;
}
</style>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>

<script>
var myVar = setInterval(myTimer, 250);
function myTimer() {
if(autoSend == true)
{
counter ++;
if(counter >9)
{
counter = 0;
}
document.getElementById('autoData').value = autoDate[counter];
websocket.send(document.getElementById('autoData').value);
}
}
</script>

<div class="topnav">
<h1>ESP WebSocket Server</h1>
</div>
<div class="content">
<div class="card">
<h2>Web Socket Data Test</h2>
<p><input id="autoData"> <input id="dataIn"></p>
<p><button id="setAutoSend" onClick="toggleAutoState()" class="button">Auto Data</button> <button id="button" class="button">Send Data</button></p>
</div>
</div>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onLoad);
function initWebSocket() {
console.log('Trying to open a WebSocket connection…');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <– add this line
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}

function onMessage(event) {
var state = event.data;

}

function onLoad(event) {
initWebSocket();
initButton();
}

function toggleAutoState(){

autoSend = !autoSend;

}

function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}

function toggle(){
websocket.send(document.getElementById('dataIn').value);
}
</script>
</body>
</html>

Isn’t that an out-of-bounds memory write when data is only length bytes long?

Same here, you treat it as string, but is the data guaranteed to have the null terminator?

Hi Max,

Here is a message I just got off of the serial monitor from ws.data:
dhcps: send_nak>>udp_sendto results 0
what was sent to the socket was:
P0F0,1238

void handleWebSocketMessage was written by the library author or Rui Santos