본문 바로가기

AWS IoT/펌웨어

[AWS LED Button] ESP32 (3) : C언어에서 JSON 문서 분석하기

C언어에서 문자열로 된 JSON 문서를 분석해주는 라이브러리로 cJSONjsmn이 있습니다. 전 cJSON을 사용했습니다. 

 

cJSON 사용법은 간단합니다. 인스턴스를 가지고 모듈을 초기화할 필요도 없고, (내부에서 malloc 함수가 쓰이긴 하지만)반환 값을 위해 malloc 함수로 따로 공간을 할당할 필요도 없습니다.

 

먼저 JSON 포맷의 문자열을 cJSON_Parse 함수에 파라미터로 넣어주면, cJSON 타입 체인의 시작 주소를 반환합니다. cJSON 구조체는 다음과 같습니다. 

 

/* The cJSON structure: */
typedef struct cJSON {
	struct cJSON *next,*prev;	/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
	struct cJSON *child;		/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */

	int type;					/* The type of the item, as above. */

	char *valuestring;			/* The item's string, if type==cJSON_String */
	int valueint;				/* The item's number, if type==cJSON_Number */
	double valuedouble;			/* The item's number, if type==cJSON_Number */

	char *string;				/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;

 

cJSON은 기본적으로 "키"-"값" 쌍 구조체이고, "값"이 또다른 "키"-"값" 쌍이라면 그 쌍 cJSON 구조체를 child 포인터 맴버 변수가 가리킵니다. 자신과 동일한 레벨의 또다른 "키"-"값" 쌍이 있다면 next 포인터 맴버 변수가 가리킵니다. 필요한 모든 공간은 cJSON 모듈 내에서 할당되고 반환됩니다. cJSON 변수를 모두 사용한 후에는 cJSON_Delete로 할당받은 공간을 반환해야 합니다. 그림으로 그리면 다음과 같습니다.

예를 들어 cjson 포인터 변수가 cJSON::child을 가리킨다고 하면, "키"에 해당하는 "operation"은 cjson->string로 참조할 수 있습니다. 그리고 cjon->child로 위 그림의 cJSON::child::child를 참조할 수 있습니다. cjson->child->string = "operation_code"이고, cjson->child->type = cJSON_String, cjson->child->valueInt = 123이 될 것입니다. 이때 cjson->child->valueString = NULL이 됩니다.

 

cJSON_Parse 함수는 분석을 실패하면 NULL을 반환합니다. 이때 NULL 포인터 변수를 참조하면 시스템이 리붓되기 때문에 NULL을 탐지하는 과정이 포함됩니다. 이것은 해커는 사물의 operation key가 없으면 사물을 동작시킬 수는 없지만, 불한정한 JSON 문서를 악의적으로 보내서 리붓시키는 경우를 방지하기 위함입니다.

 

#define TYPE_VALID	(1U << 0) // top json validation
#define OPKEY_VALID (1U << 1) // "operation_key" json validation
#define OPCODE_VALID (1U << 2) // "operation_code" json valideation
#define OPDATA_VALID (1U << 3) // "operation_data" json validsation when "operation_code" = 2
#define VALID_OK 	TYPE_VALID | OPKEY_VALID | OPCODE_VALID | OPDATA_VALID

cJSON * json = cJSON_Parse(buffer);
cJSON * operation_key = NULL;
cJSON * operation_code = NULL;
cJSON * operation_data = NULL;
uint32_t valid_json = 0;
bool led_operation = false;

// check json validation
if(json != NULL) {
    valid_json |= TYPE_VALID;
    operation_key = cJSON_GetObjectItem(json, "operation_key");
    if(operation_key != NULL && operation_key->valuestring != NULL) {
    	valid_json |= OPKEY_VALID;
    }

    operation_code = cJSON_GetObjectItem(json, "operation_code");

    if(operation_code != NULL &&
    		!(operation_code->valueint < OP_LED_OFF) &&
			!(operation_code->valueint > OP_KEY_CHANGE) &&
				operation_code->valuestring == NULL) {

    	valid_json |= OPCODE_VALID;

    	if(operation_code->valueint == OP_KEY_CHANGE) {
    		operation_data = cJSON_GetObjectItem(json, "operation_data");
    		if(operation_data != NULL && operation_data->valuestring != NULL) {
    			valid_json |= OPDATA_VALID;
    		}
    	} else {
    		valid_json |= OPDATA_VALID;
    	}
    }
}

if(valid_json == (VALID_OK)) {
    // json type is valid
    stage = 1;

    if(strcmp(operation_key->valuestring, get_opkey()) == 0)
    	err = ESP_OK;

    if(err == ESP_OK) {
    	// confirmed key
    	stage = 2;

    	switch(operation_code->valueint) {
    	case OP_LED_OFF:
    		led_status = false;
    		led_operation = true;
    		break;
    	case OP_LED_ON:
    		led_status = true;
    		led_operation = true;
    		break;
    	case OP_KEY_CHANGE:
    		err = set_opkey(operation_data->valuestring);
    		ESP_LOGW(TAG_SUBPUB, "set_opkey err code = %d", err);
    		if(err == ESP_OK)
    			stage = 3;
    		ESP_LOGW(TAG_SUBPUB, "op_code : KEY CHANGE, result : %s", (err == ESP_OK) ? "success" : "fail");
    		break;
    	default:
    		ESP_LOGW(TAG_SUBPUB, "op_code : Unknown");
    		break;
    	}
    } else {
    	ESP_LOGW(TAG_SUBPUB, "operation key is wrong");
    }
} else { // Reference line 176
    ESP_LOGW(TAG_SUBPUB, "JSON document is wrong (%u%u%u%u)",
    		(valid_json & OPDATA_VALID) >> 3,
			(valid_json & OPCODE_VALID) >> 2,
			(valid_json & OPKEY_VALID) >> 1,
			(valid_json & TYPE_VALID));
}

IoT_Error_t rc = FAILURE;

if(valid_json == (VALID_OK) && err == ESP_OK && led_operation) {
    ESP_LOGI(TAG_SHADOW, "Updating shadow...");
    rc = aws_iot_shadow_init_json_document(JsonDocumentBuffer, sizeOfJsonDocumentBuffer);
    if(SUCCESS == rc) {
    	rc = aws_iot_shadow_add_reported(JsonDocumentBuffer, sizeOfJsonDocumentBuffer, 1, &ledStatus);
    	if(SUCCESS == rc) {
    		rc = aws_iot_finalize_json_document(JsonDocumentBuffer, sizeOfJsonDocumentBuffer);
    		if(SUCCESS == rc) {
    			//ESP_LOGI(TAG_SHADOW, "Update Shadow: %s", JsonDocumentBuffer);
    				rc = aws_iot_shadow_update(&shadowClient, CONFIG_AWS_EXAMPLE_THING_NAME, JsonDocumentBuffer,
    						ShadowUpdateStatusCallback, (void *)&led_status, 4, true);
    		}
    	}
    }

    if(SUCCESS != rc) {
    	boot_button_in_progress = false;
    	led_status = !led_status; // return to previous status.
    } else {
    	esp_err_t err = control_led(LED_GPIO_PIN, (uint32_t)led_status); // actual led toggle
    	if(err != ESP_OK) {
    		led_status = !led_status; // return to previous status.
    	} else {
    		ESP_LOGI(TAG_SHADOW, "op_code : LED %s, result : %s", led_status ? "ON" : "OFF" ,(err == ESP_OK) ? "success" : "fail");
    	}
    }
}

ESP_LOGW(TAG_SUBPUB, "Result publishing...");

IoT_Publish_Message_Params pubParams;
int op_code = 3;
char * result_data;

if(valid_json & OPCODE_VALID)
    op_code = operation_code->valueint;

if(valid_json != (VALID_OK)) {
    result_data = "json document is wrong";
} else if(stage == 1) {
    result_data = "operation key is wrong";
} else if(stage == 3){
    result_data = "operation key is exchanged";
} else {
    result_data = "nothing";
}

sprintf(cPayload, "{\"operation_code\":%d,\"operation_result\":%s,\"result_data\":\"%s\"}", op_code, (err == ESP_OK) ? "true" : "false", result_data);
//ESP_LOGW(TAG_SUBPUB, "payload : %s", (char *)cPayload);

pubParams.qos = QOS1;
pubParams.payload = (void *) cPayload;
pubParams.isRetained = 0;
pubParams.payloadLen = strlen(cPayload);

const int TOPIC_LEN = strlen(RESULT_TOPIC);

rc = aws_iot_mqtt_publish(&shadowClient, RESULT_TOPIC, TOPIC_LEN, &pubParams);
if (rc == MQTT_REQUEST_TIMEOUT_ERROR) {
    	ESP_LOGW(TAG_SUBPUB, "QOS1 publish ack not received.");
}
if(rc == SUCCESS) {
    	ESP_LOGW(TAG_SUBPUB, "Result published\n");
}

if(valid_json & TYPE_VALID) {
    	cJSON_Delete(json);
}