String(char *) corrupts value

I’m curious why the following causes good data for the first publish and corrupted data for the second publish? Seems that using String(data) corrupts the value for any code that reads it afterwards.

void processRemoteData(const char *event, const char *data)
{
Spark.publish(“test”,"Received data: " + String(data), 60, PRIVATE);
Spark.publish(“test2”,"Received data: " + String(data), 60, PRIVATE);
}

FYI the response for test2 starts like this: Received data: ;���ДYZ�Lǂ�_�L�Y�0hs�Y`J>���

  • Photon or Core?
  • A longer code snippet might be useful…

Sure here’s a full test program that illustrates the issue/question:

void setup() {
    Spark.subscribe("testSub", processRemoteData, MY_DEVICES);
    Spark.publish("testSub","test data", 60, PRIVATE);
}

void processRemoteData(const char *event, const char *data)
{
    Spark.publish("test","Received data: " + String(data), 60, PRIVATE);
    Spark.publish("test2","Received data: " + String(data), 60, PRIVATE);
}

void loop() {
}

Here’s the result of that code from the dashboard:

testSub - test data    
testReceived - test data
test2Received - �%d�u Cv!�e4�]���nk���n��]r�r�_,D�n����v��wt����|

What is data and what is it declared as?

That’s from the handler input: const char *data
Which has worked fine until I tried String(data) twice. Got the handler signature from: https://docs.particle.io/reference/firmware/core/#spark-subscribe-

void handler(const char *eventName, const char *data) {
  Serial.println(data);
}

Hi @Fragma

The pointers that your subscription function gets for the event and and data are pointers into the internal data structure used by the cloud connection. That means when you call the cloud connection to publish, you can’t expect the underlying memory to contain that same bytes–it has been overwritten by your first publish call’s interaction with the cloud data structures. It wouldn’t really be efficient for the cloud connection to make copies of all the data it hands out pointers to, so that job falls to you if you need to receive from cloud and transmit to the cloud in a subscription function handler.

If you want to do what you are trying here, you need to copy the data into your own memory so it won’t get overwritten like this:

void setup() {
    Spark.subscribe("testSub", processRemoteData, MY_DEVICES);
    Spark.publish("testSub","test data", 60, PRIVATE);
}

void processRemoteData(const char *event, const char *data)
{
    char buf[64];
    memcpy(buf,data,strlen(data)+1);  //plus one to get the terminating zero
    Spark.publish("test","Received data: " + String(buf), 60, PRIVATE);
    Spark.publish("test2","Received data: " + String(buf), 60, PRIVATE);
}

void loop() {
}
2 Likes

Makes sense, thanks.

Well, it makes some sense, but I’m not sure it’s documented anywhere.

If calls to any Spark.foo() function can modify data that was already supplied by other Spark.* infrastructure, this needs to be stated.

Extra credit for at least not fubar’ing any data until the callback specified in Spark.subscribe() has returned, regardless of what other Spark.foo() is called therein.

The docs need to be checked to ensure they clearly state that *data is not persistent outside the scope of the callback (this test proves that at present it’s not even persistent inside that scope), and the user is responsible for copying the content, not just the reference; if they want to continue to use it out of scope.

2 Likes