Edge Client Socket Interface

The socket used for communication with the IoT server is provided via the socket field of the mco_edge_params_t structure when creating the edge database. The interface is defined by the following structure:

    typedef struct mco_edge_socket_t_ {
        MCO_RET (*f_connect)(struct mco_edge_socket_t_ *s);
        // Establish a connection to the server.

        MCO_RET (*f_send)(struct mco_edge_socket_t_ *s, const void *buf, mco_size_t n_bytes);
        // Send n_bytes of data from buffer buf.

        MCO_RET (*f_recv)(struct mco_edge_socket_t_ *s, void *buf, mco_size_t min_bytes,
                        mco_size_t max_bytes, mco_size_t *rcvd_bytes);
        // Receive between min_bytes and max_bytes into buf.
        // The actual number of bytes received is returned via rcvd_bytes.

        MCO_RET (*f_disconnect)(struct mco_edge_socket_t_ *s);
        // Close the connection to the server.

        timer_unit deadline;
        // Absolute timeout (system timer value). connect(), send(), and recv() must
        // complete before this deadline (either successfully or with an error).

    } mco_edge_socket_t;
    

Note on the relationship between the deadline and the timeout parameter of the mco_edge_synk() function. A timeout is relative-it means “wait up to N microseconds from now.” A deadline is absolute-it means “finish by this specific time.” mco_edge_sync() takes a timeout, converts it into a deadline, and from that point on everything operates against that fixed deadline. The edge runtime coordinates multiple socket operations within the mco_edge_sync() function under one overall time budget. Also note that the The socket::deadline is not a parameter-it’s an internal runtime variable. Applications only need to know or care about it if it is necessary to implement a new socket type. It is exposed through the edge interfa because the socket structure “lives" outside the edge buffer / database, and its sizeof() has to be “known” to the application.

Currently, two implementations of this interface are available: the POSIX socket and the TLS socket.

POSIX Socket

The POSIX socket (mco_net_socket_t) is initialized using the mco_edge_create_net4_socket() function:

    MCO_RET mco_edge_create_net4_socket(const char *ip, int port, mco_net_socket_t *s);
    

Where:

TLS Socket

The TLS socket (mco_mbedtls_socket_t), based on the mbedTLS library, is initialized using the mco_edge_create_mbedtls_socket() function:

    MCO_RET mco_edge_create_mbedtls_socket(mco_mbedtls_socket_params_t *p,
                                           mco_mbedtls_socket_t *ssl);
    

The configuration parameters are passed via the mco_mbedtls_socket_params_t structure:

    // Helper structure for representing binary data
    typedef struct {
        const unsigned char *ptr;
        unsigned int         len;
    } mco_mbedtls_bindata_t;

    typedef struct {
        mco_edge_socket_t          *bio;        // Base I/O socket (e.g., mco_net_socket_t)
        const char                 *hostname;   // Common Name (CN) for verification
        int                         authmode;   // Certificate verification mode:
                                                // MBEDTLS_SSL_VERIFY_NONE,
                                                // MBEDTLS_SSL_VERIFY_OPTIONAL, or
                                                // MBEDTLS_SSL_VERIFY_REQUIRED
        mbedtls_ssl_protocol_version tls_version; // TLS version:
                                                // MBEDTLS_SSL_VERSION_TLS1_2 or
                                                // MBEDTLS_SSL_VERSION_TLS1_3
        mco_mbedtls_bindata_t       ca_cert;    // Trusted CA certificate(s):
                                                // DER-encoded or concatenated PEM blocks
        mco_mbedtls_bindata_t       own_cert;   // Client certificate (DER or PEM)
        mco_mbedtls_bindata_t       own_pkey;   // Private key (DER or PEM)
        const int                  *ciphersuites; // NULL-terminated list of IANA
                                                // ciphersuite identifiers, or NULL
    } mco_mbedtls_socket_params_t;
    

Full Example (Linux)

The following example demonstrates how to create a POSIX socket, connect to the IoT server, and perform basic data synchronization:

    #include <mcoedgenet.h>
    #include "mindb.h"

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>

    #define CHECK(call) \
        { \
            MCO_RET rc_ = call; \
            if (rc_ != MCO_S_OK) { \
                printf("Call %s at %s:%d failed with error %d\n", \
                       #call, __FILE__, __LINE__, rc_); \
                abort(); \
            } \
        }

    #define MEM_BUF_SIZE (1024)
    unsigned char memory[MEM_BUF_SIZE];
    const char db_name[] = "rdb";

    void errhandler(MCO_RET errcode)
    {
        printf("Fatal error %d\n", errcode);
        exit(1);
    }

    int main(int argc, char *argv[])
    {
        mco_net_socket_t net_sock;
        mco_edge_params_t edge_params;

        mco_edge_error_set_handler(errhandler);
        CHECK(mco_edge_start());

        mco_edge_params_init(&edge_params);

        // Create socket: use args if provided, otherwise defaults
        CHECK(mco_edge_create_net4_socket(
            argc > 2 ? argv[1] : "127.0.0.1",
            argc > 2 ? atoi(argv[2]) : 15000,
            &net_sock));

        edge_params.recv_buf_size = 128;
        edge_params.send_buf_size = 128;
        edge_params.socket = &net_sock.es;

        CHECK(mco_edge_open(db_name, mindb_get_dictionary(),
                            memory, sizeof(memory), &edge_params));

        {
            mco_edge_h eh = 0;
            mco_trans_h t = 0;
            Sensor sensor = {0};
            int controller_id = 111;
            mco_size_t total_space, used_space;

            CHECK(mco_edge_connect(db_name, &eh));

            for (int i = 0; i < 100; ++i) {
                mco_datetime ts = mco_system_get_current_time() / 1000;

                CHECK(mco_edge_trans_start(eh, &t));

                for (int id = 1; id < 3; ++id) {
                    // Check if buffer is full
                    if (mco_edge_has_space(t, Sensor_code) == MCO_S_BUSY) {
                        // Sync and clear acknowledged transactions
                        CHECK(mco_edge_sync(eh,
                                            MCO_EDGE_SYNC_WAIT | MCO_EDGE_SYNC_AUTO_CLEAR,
                                            2000));
                    }

                    CHECK(Sensor_new(t, &sensor));
                    CHECK(Sensor_ts_put(&sensor, ts));
                    CHECK(Sensor_controller_id_put(&sensor, controller_id));
                    CHECK(Sensor_sensor_id_put(&sensor, id));
                    CHECK(Sensor_value_put(&sensor, (rand() % 10000) / 1000.));
                }

                CHECK(mco_edge_trans_commit(t));

                mco_edge_total_space(eh, &total_space);
                mco_edge_used_space(eh, &used_space);

                printf("[%d] Insert sensor pack, ts = %llu. Used %d of %d bytes\n",
                       controller_id, ts, (int)used_space, (int)total_space);

                sleep(1);
            }

            // Final sync
            CHECK(mco_edge_sync(eh, MCO_EDGE_SYNC_WAIT, 2000));
            CHECK(mco_edge_disconnect(eh));
        }

        CHECK(mco_edge_close(db_name));
        CHECK(mco_edge_stop());

        return 0;
    }