使用国密算法接入云网关

本文介绍如何使用LinkSDK通过国密算法接入MQTT云网关。

背景信息

国密算法是国家密码管理局制定、认定和颁布的一系列国产密码算法,又称商用密码(能够实现商用密码算法的加密、解密和认证等功能的技术),保障金融,医疗等领域的信息传输安全。物联网设备可以使用国密算法接入物联网平台的MQTT云网关,通过MQTT云网关接入阿里云物联网平台。

前提条件

操作步骤

如下功能时序图,以设备的应用程序demos/device_gmssl_demo.c为例,介绍国密接入的使用流程。

image.png

步骤1:设备初始化

  • 添加头文件

    #include "aiot_device_api.h"
  • 创建设备句柄

    void *device_client = aiot_device_create(product_key, device_name);
    if (device_client == NULL) {
        printf("device_client failed\n");
        return -1;
    }

步骤2:配置事件回调函数

  • 编写消息回调函数。

    该回调函数会在设备接收到任意消息时执行,用于消息的监听。该回调函数原型为aiot_device_msg_result_callback_t

    /* 设备的消息回调示例,默认只做打印处理 */
    void demo_msg_callback(void *device, const aiot_msg_t *message, void *userdata)
    {
        printf("[message] <<, topic: %s\n", message->topic);
        printf("[message] <<, payload: %.*s\n", message->payload_lenth, message->payload);
    }
  • 编写状态回调函数。

    该回调函数会在设备连接状态变化时执行,用于监听设备的状态。该回调函数原型为aiot_device_status_callback_t

    void demo_status_callback(void *device, const aiot_device_status_t *status, void *userdata)
    {
        switch (status->type) {
        /* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
        case AIOT_DEVICE_STATUS_CONNECT: {
            printf("AIOT_DEVICE_STATUS_CONNECT\n");
            /* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
        }
        break;
    
        /* SDK因为网络状况被动断连后, 自动发起重连已成功 */
        case AIOT_DEVICE_STATUS_RECONNECT: {
            printf("AIOT_DEVICE_STATUS_CONNECT\n");
            /* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
        }
        break;
    
        /* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
        case AIOT_DEVICE_STATUS_DISCONNECT: {
            printf("AIOT_DEVICE_STATUS_CONNECT: %d\n", status->error_code);
            /* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
        }
        break;
        }
    }
  • 编写消息发送结果回调函数。

    该函数会在设备主动发送QoS 1消息,或执行订阅、取消订阅、云端有返回时执行。该回调函数的原型为aiot_device_msg_result_callback_t

    /* 主动发送的消息,或者订阅/取消订阅消息有结果后会回调该消息 */
    void demo_result_callback(void *device, const aiot_device_msg_result_t *result, void *userdata)
    {
        switch(result->type) {
            case AIOT_DEVICE_SEND_MESSAGE_RESULT: {
                printf("send message result, message id: %d, code -0x%04X\n", result->message_id, -result->code);
            }
            break;  
            case AIOT_DEVICE_REGISTER_TOPIC_RESULT:{
                printf("register, message id: %d, code code -0x%04X\n", result->message_id, -result->code);
            }
            break;  
            default:
                break;
        }
    }
  • 设置事件回调函数。

    上述定义了回调函数,执行设置动作才能生效,设置事件回调函数接口说明,请参见aiot_device_set_event_callback,该接口可设置三种事件的监听,如果不需要监听事件,可将对应参数设置为NULL。

    /* 设置设备消息回调及状态变化回调 */
    aiot_device_set_event_callback(device_client, demo_msg_callback, demo_status_callback, demo_result_callback, NULL);

步骤3:配置连接参数

配置连接参数,一般使用默认设置,更多连接参数信息,请参考aiot_linkconfig_api.h

  • 设置云网关接入域名和端口:aiot_linkconfig_host(config, host, port);

  • 设置TLS采用国密算法:aiot_linkconfig_tls_gm(config, 1);

  • 设置国密根证书:aiot_linkconfig_root_cert(config, demo_sm2_cert, strlen(demo_sm2_cert));

  • 设置MQTT认证参数:aiot_linkconfig_mqtt_auth(config, username, password, cilentid);

    /* 连接配置参数初始化 */
    aiot_linkconfig_t* config = aiot_linkconfig_init(MQTT_PROTOCOL);
    /* 设置连接参数:服务器的host、port */
    aiot_linkconfig_host(config, host, port);
    /* 设置连接参数:TLS采用国密 */
    aiot_linkconfig_tls_gm(config, 1);
    /* 设置连接参数:根证书 */
    aiot_linkconfig_root_cert(config, demo_sm2_cert, strlen(demo_sm2_cert));
    /* 设置连接参数:MQTT认证参数*/
    aiot_linkconfig_mqtt_auth(config, username, password, cilentid);
    /* 将连接参数上下文传递给设备 */
    aiot_device_set_linkconfig(device_client, config);

步骤4:设备建连

如下示例的连接为阻塞式的接口,SDK也支持异步建连,异步建连接口使用具体操作,请参考aiot_device_connect_async

/* 设备建连 */
res = aiot_device_connect(device_client);
if (res < STATE_SUCCESS) {
    /* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
    aiot_linkconfig_deinit(&config);
    aiot_device_delete(&device_client);
    printf("aiot_device_connect failed: -0x%04X\n\r\n", -res);
    return -1;
}

设备建连成功后,如果网络出现断连,SDK会自动进行设备重连,无需再调用aiot_device_connect接口。

步骤5:订阅自定义消息

  • 定义消息的回调函数。

    回调函数原型为aiot_device_msg_result_callback_t

    /* 自定义topic下行消息处理示例, 示例只做打印处理 */
    void demo_user_topic_callback(void *device, const aiot_msg_t *message, void *userdata)
    {
        printf("[thing_message] <<, topic: %s\n", message->topic);
        printf("[thing_message] <<, payload: %.*s\n", message->payload_lenth, message->payload);
    }
  • 向物联网平台发起消息订阅,并注册消息回调。

    注册自定义消息回调的接口aiot_device_register_topic_filter,该接口有两个功能:

    • (可选)向云平台发起消息订阅。

    • 向SDK注册接收到消息的回调。

      /* 订阅消息,设置该消息回调函数 */
      char sub_topic[128] = { 0 };
      snprintf(sub_topic, sizeof(sub_topic), "/%s/%s/user/get", product_key, device_name);
      int32_t msg_id = aiot_device_register_topic_filter(device_client, sub_topic, demo_user_topic_callback, 1, NULL);
      if(msg_id >= 0){
          printf("aiot_device_register_topic_filter topic %s msg_id %d\r\n", sub_topic, msg_id);
      }

如果需要取消已订阅的Topic,可通过接口aiot_device_deregister_topic_filter取消订阅。

步骤6:发送自定义消息

  • 创建消息。

    如下示例使用Topic和Payload创建消息。更多消息配置,请参考aiot_message_api.h

    char pub_topic[128] = { 0 };
    snprintf(pub_topic, sizeof(pub_topic), "/%s/%s/user/update", product_key, device_name);
    char *pub_payload = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}";
    aiot_msg_t *pub_message = aiot_msg_create_raw(pub_topic, (uint8_t *)pub_payload, strlen(pub_payload));
  • 发送消息。

    aiot_device_send_message(device_client, pub_message);
  • 删除消息。

    aiot_msg_delete(pub_message);

步骤7:接收自定义消息

设备订阅消息后,用户可通过云端SDK给设备发消息;调试阶段,也可通过OpenApi给设备发消息。通过OpenApi给设备发自定义消息步骤:

  1. 登录控制台,打开Pub接口链接。

  2. 输入必选参数:ProductKey、TopicFullName、MessageContent,若为企业实例,还需输入IotInstanceId。

  3. 发起调用:单机发起调用,若执行成功,即可看到设备端打印接收的消息。

步骤8:设备断开连接

设备和物联网平台断开连接。

res = aiot_device_disconnect(device_client);
if (res < STATE_SUCCESS) {
    printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
    return -1;
}

步骤9:删除设备句柄。

程序退出时,SDK执行资源回收。

/* 销毁设备实例, 一般不会运行到这里 */
aiot_device_delete(&device_client);
aiot_linkconfig_deinit(&config);

编译运行

  • Makefile编译

    编译:
    	make -j GMSSL=ON
    
    运行:
    	./output/bin/device_gmssl_demo
  • Cmake编译

    编译:
    	mkdir build && cd build
      cmake .. -DGMSSL=ON
      make -j
    运行:
    	./output/bin/device_gmssl_demo