0

I am trying to send a specific message type using the MQTT protocol. I am using the paho.mqtt.c library, and my broker is RabbitMQ 3.6.12, running Erlang 20.0. I am working on a virtual machine running CentOS 6.9. I first tried doing it by creating a struct for my specific message type, then before asking this question, I also tried using JSON to create my specific message type. I installed cJSON (from here).

Here is my whole code using cJSON :

pubframe.c :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"
#include "../tools.c"
#include <cjson/cJSON.h>

#define ADDRESS      "tcp://localhost:1883"
#define CLIENTID     "MY_PUB"
#define TOPIC        "MQTT/Test"

int main(int argc, char* argv[])
{   

        frame1 test = {42,"test"};

        cJSON* frm = NULL;
        frm = cJSON_CreateObject();

        cJSON_AddNumberToObject(frm,"entier",test.E);
        cJSON_AddStringToObject(frm,"string",test.S);

        print_frame1(frm);

        int i = cJSON_GetArraySize(frm);
        printf("number of items in frame : %d\n",i);
        cJSON* entier = NULL;
        entier = cJSON_GetObjectItem(frm,"entier");
        char* pt = cJSON_Print(entier);
        printf("entier : %s\n",pt);
        cJSON* str = NULL;
        str = cJSON_GetObjectItem(frm,"string");
        char* st = cJSON_Print(str);
        printf("string : %s\n",st);

        printf("size of message : %d\n",sizeof(cJSON));

        MQTTClient publisher;
        MQTTClient_connectOptions connexion = MQTTClient_connectOptions_initializer;

        MQTTClient_message msg = msg_creation(frm,sizeof(cJSON),0,0);
    MQTTClient_deliveryToken token;
        int rc;
        MQTTClient_create(&publisher, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);
    connexion.cleansession = 1;
        MQTTClient_setCallbacks(publisher, NULL, connlost, frame_json_arrvd, NULL);
        if ((rc = MQTTClient_connect(publisher,&connexion)) != MQTTCLIENT_SUCCESS)
        {
            printf("Failed to connect, return code %d\n", rc);
        }
        MQTTClient_publishMessage(publisher, TOPIC,&msg,NULL);
        printf("Message sent!\n");
        cJSON_Delete(frm);
        MQTTClient_disconnect(publisher,10000);
        MQTTClient_destroy(&publisher);
        return rc;
}

subframe.c :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"
#include "../tools.c"
#include <cjson/cJSON.h>


#define ADDRESS     "tcp://localhost:1883"
#define CLIENTID    "ClientID"
#define TOPIC       "MQTT/Test"
#define QOS         0
#define TIMEOUT     10000L

int main(int argc, char* argv[])
{
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    int rc;
    int ch;
    MQTTClient_create(&client, ADDRESS, CLIENTID,
        MQTTCLIENT_PERSISTENCE_NONE, NULL);
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;
    MQTTClient_setCallbacks(client, NULL, connlost, frame_json_arrvd, NULL);
    if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
    {
        printf("Failed to connect, return code %d\n", rc);
        exit(EXIT_FAILURE);
    }
    printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n"
           "Press Q<Enter> to quit\n\n", TOPIC, CLIENTID, QOS);
    MQTTClient_subscribe(client, TOPIC, QOS);
    do
    {
        ch = getchar();
    } while(ch!='Q' && ch != 'q');
    MQTTClient_disconnect(client, 10000);
    MQTTClient_destroy(&client);
    return rc;
}

tools.c :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <cjson/cJSON.h>
#include "MQTTClient.h"

int compteur;

// message frames

typedef struct frame1
{
    int E;
    char* S;
} frame1;

void print_frame1bis(frame1* F)
{
        printf("     %s\n",F->S);
        printf("     %d\n",F->E);
}

void print_frame1(cJSON* frame1)
{
    char * str = cJSON_Print(frame1);
    printf("%s\n",str);
}

int frame_json_arrvd(void* context, char* topicName, int topicLen, MQTTClient_message* msg)
{

        cJSON* payload_ptr = NULL;

        printf("size of message : %d\n",sizeof(msg->payload));

        int j = cJSON_GetArraySize(msg->payload);
        printf("ok\n");
        printf("number of items in frame : %d\n",j);

        payload_ptr = cJSON_CreateObject();
        payload_ptr = msg->payload;

        int i = cJSON_GetArraySize(payload_ptr);
        printf("number of items in frame : %d\n",i);

        print_frame1(payload_ptr);

        cJSON_Delete(payload_ptr);
        MQTTClient_freeMessage(&msg);
        MQTTClient_free(topicName);
        return 1;
}

// in case connexion is lost

void connlost(void *context, char *cause)
{
    printf("\nConnection lost\n");
    printf("     cause: %s\n", cause);
}


// create message

MQTTClient_message msg_creation(void* payload, int length, int qos, int retained)
{
    MQTTClient_message pubmsg = MQTTClient_message_initializer;
    pubmsg.payload = payload;
    pubmsg.payloadlen = length;
    pubmsg.qos = qos;
    pubmsg.retained = retained;
    return pubmsg;
}

The outpus from the subscriber is

Subscribing to topic MQTT/Test
for client ClientID using QoS0

Press Q<Enter> to quit

size of message : 8
Segmentation fault (core dumped)

So it seems the message is not correctly received as the subscriber crashes as soon as it tries to get the number of items.

This is what I get when I run gdb, not sure if it helps.

Program terminated with signal 11, Segmentation fault.
#0  0x00007f4e49a8b63e in cJSON_GetArraySize () from /usr/lib64/libcjson.so.1
(gdb) bt
#0  0x00007f4e49a8b63e in cJSON_GetArraySize () from /usr/lib64/libcjson.so.1
#1  0x00000000004010c9 in frame_json_arrvd (context=0x0,
    topicName=0x7f4e440009e4 "MQTT/Test", topicLen=0, msg=0x7f4e44000bb4)
    at ../tools.c:110
#2  0x00007f4e49c962a5 in MQTTClient_run (n=<value optimized out>)
    at src/MQTTClient.c:604
#3  0x000000378b807aa1 in start_thread (arg=0x7f4e49a83700) at pthread_create.c:301
#4  0x000000378b4e8bcd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:115

The output from the publisher is

{
        "entier":       42,
        "string":       "test"
}
number of items in frame : 2
entier : 42
string : "test"
size of message : 64
Message sent!

So it looks like the message is correctly created.

I just looked into cJSON recently, so I will keep investigating in case I am using it wrong, but any help would be appreciated. I tried to narrow it down as suggested in the comments, but I don't see what I can do, seeing as it crashes as soon as it tries to access to the payload of the message.

Please keep in mind I am only a student with no more than a year of experience in computer science. Also, english is not my native language, I hope I am explaining myself clearly enough.

Flxnt
  • 177
  • 4
  • 22
  • Use a debugger like gdb to check what you have received into `*msg` – Ôrel Nov 30 '17 at 13:42
  • I used gdb to see what the segmentation fault was about, but I had never used it before, I don't know how to use it to check what was received, could you explain to me how to do that? – Flxnt Nov 30 '17 at 13:48
  • You are destroying the mqtt client as soon as you have called the publish, so perhaps it’s not so surprising that things go wrong. Your mainline code needs to hang around polling the mqtt code for the subscription to receive the message back from the broker. Suggest you decouple the twin problems you are attempting to solve simultaneously - of publishing and of subscribing - by using an mqtt client to FIRST check that the publish is successful, and THEN again once you have your mainline code waiting for a message on the subscription, to provide a known good publish to the topic. – DisappointedByUnaccountableMod Dec 02 '17 at 08:24
  • BTW you have not posted an MCVE - https://stackoverflow.com/help/mcve - that makes it much harder (so less likely) for people to help you. – DisappointedByUnaccountableMod Dec 02 '17 at 23:36
  • So it's is subframe.c that doesn't receive a message from the publish, instead it crashes with a segmentation error, yes? Have you tested it with a different client to publish (e.g. moquitto_pub) and does it give the same fault? – DisappointedByUnaccountableMod Dec 04 '17 at 16:30
  • Unsurprisingly, now I have your MCVE, I have found out where your error is. It's not hard to find. Use your debugger, or if nothing else just add printf statements (including \n so they get flushed to console before the segfault) into frame1_arrvd (which is where the error had to be because it is triggered by a message arriving) until you narrow it down to one line, then think about it. You may have noticed, that as you told me I am a bit rude person, I have decided not to share this information with you. Bonne chance. – DisappointedByUnaccountableMod Dec 04 '17 at 17:09
  • stackoverflow.com/help/how-to-ask – DisappointedByUnaccountableMod Dec 04 '17 at 17:29
  • @barny Yes it is indeed subframe.c that crashes. Ha, I appreciate your humor. I have tried to narrow it down already but clearly I have been going at it the wrong way. Thanks for the help, hope I can manage to solve this by myself. – Flxnt Dec 05 '17 at 08:07

1 Answers1

0

Problem solved (kinda)

I just send the whole message frame as a string and parse it in the msgarrvd function :

pubframe.c :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"
#include "../tools.c"
#include <cjson/cJSON.h>

#define ADDRESS      "tcp://localhost:1883"
#define CLIENTID     "MY_PUB"
#define TOPIC        "MQTT/Test"

volatile MQTTClient_deliveryToken deliveredtoken;
void delivered(void *context, MQTTClient_deliveryToken dt)
{
    printf("Message with token value %d delivery confirmed\n", dt);
    deliveredtoken = dt;
}

int main(int argc, char* argv[])
{   

    char* test = "{\"string\" : \"whatever\", \"entier\" : 42}";

    MQTTClient publisher;
    MQTTClient_connectOptions connexion = MQTTClient_connectOptions_initializer;

    MQTTClient_message msg = msg_creation(test,strlen(test),0,0);

    int rc;
    MQTTClient_create(&publisher, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);
    connexion.cleansession = 1;
    MQTTClient_setCallbacks(publisher, NULL, connlost, frame_test_arrvd, NULL);
    if ((rc = MQTTClient_connect(publisher,&connexion)) != MQTTCLIENT_SUCCESS)
    {
        printf("Failed to connect, return code %d\n", rc);
    }
    MQTTClient_publishMessage(publisher, TOPIC,&msg,&token);
    printf("Message sent!\n");
    cJSON_Delete(frm);
    MQTTClient_disconnect(publisher,10000);
    MQTTClient_destroy(&publisher);
    return rc;
}

tools.c :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <cjson/cJSON.h>
#include "MQTTClient.h"

// message frames

void print_frame1(cJSON* frame1)
{
    char * str = cJSON_Print(frame1);
    printf("%s\n",str);
}

int frame_test_arrvd(void* context, char* topicName, int topicLen, MQTTClient_message* msg)
{
    printf("message original : %s\n",msg->payload);
    cJSON* payload_ptr = cJSON_Parse(msg->payload);

    print_frame1(payload_ptr);
    int i = cJSON_GetArraySize(payload_ptr);
    printf("number of items in frame : %d\n",i);
    cJSON* entier = NULL;
    entier = cJSON_GetObjectItem(payload_ptr,"entier");
    char* pt = cJSON_Print(entier);
    printf("entier : %s\n",pt);
    cJSON* str = NULL;
    str = cJSON_GetObjectItem(payload_ptr,"string");
    char* st = cJSON_Print(str);
    printf("string : %s\n",st);

    cJSON_Delete(payload_ptr);

    MQTTClient_freeMessage(&msg);
    MQTTClient_free(topicName);
    return 1;
}

// in case connexion is lost

void connlost(void *context, char *cause)
{
    printf("\nConnection lost\n");
    printf("     cause: %s\n", cause);
}


// create message

MQTTClient_message msg_creation(void* payload, int length, int qos, int retained)
{
    MQTTClient_message pubmsg = MQTTClient_message_initializer;
    pubmsg.payload = payload;
    pubmsg.payloadlen = length;
    pubmsg.qos = qos;
    pubmsg.retained = retained;
    return pubmsg;
}

subframe.c :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"
#include "../tools.c"
#include <cjson/cJSON.h>


#define ADDRESS     "tcp://localhost:1883"
#define CLIENTID    "ClientID"
#define TOPIC       "MQTT/Test"
#define QOS         0
#define TIMEOUT     10000L

int main(int argc, char* argv[])
{
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    int rc;
    int ch;
    MQTTClient_create(&client, ADDRESS, CLIENTID,
        MQTTCLIENT_PERSISTENCE_NONE, NULL);
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;
    MQTTClient_setCallbacks(client, NULL, connlost, frame_test_arrvd, NULL);
    if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
    {
        printf("Failed to connect, return code %d\n", rc);
        exit(EXIT_FAILURE);
    }
    printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n"
           "Press Q<Enter> to quit\n\n", TOPIC, CLIENTID, QOS);
    MQTTClient_subscribe(client, TOPIC, QOS);
    do
    {
        ch = getchar();
    } while(ch!='Q' && ch != 'q');
    MQTTClient_disconnect(client, 10000);
    MQTTClient_destroy(&client);
    return rc;
}

It is not ideal as I still would like to create specific types of message, and I haven't found out what was causing the segmentation fault, but it works perfectly this way.

UPDATE

I found a way to create specific types of message and still send them as string to parse them in the msgarrvd function, I just created a function cJSON_ToString to convert a cJSON* object to a string, so that I can create a cJOSN from any struct I want and then convert it to a string to send it.

Flxnt
  • 177
  • 4
  • 22