Index: asp3_tinet_ecnl_arm/trunk/app1_usb_watt_meter/src/client.c
===================================================================
--- asp3_tinet_ecnl_arm/trunk/app1_usb_watt_meter/src/client.c	(revision 352)
+++ asp3_tinet_ecnl_arm/trunk/app1_usb_watt_meter/src/client.c	(revision 364)
@@ -1,7 +1,7 @@
 /*
  *  TOPPERS ECHONET Lite Communication Middleware
- * 
+ *
  *  Copyright (C) 2016 Cores Co., Ltd. Japan
- * 
+ *
  *  上記著作権者は，以下の(1)～(4)の条件を満たす場合に限り，本ソフトウェ
  *  ア（本ソフトウェアを改変したものを含む．以下同じ）を使用・複製・改
@@ -26,5 +26,5 @@
  *      由に基づく請求からも，上記著作権者およびTOPPERSプロジェクトを
  *      免責すること．
- * 
+ *
  *  本ソフトウェアは，無保証で提供されているものである．上記著作権者お
  *  よびTOPPERSプロジェクトは，本ソフトウェアに関して，特定の使用目的
@@ -32,5 +32,5 @@
  *  アの利用により直接的または間接的に生じたいかなる損害に関しても，そ
  *  の責任を負わない．
- * 
+ *
  *  @(#) $Id$
  */
@@ -48,12 +48,38 @@
 #include "ff.h"
 #include "util/ntstdio.h"
+#include "client.h"
+#include "jsonsl.h"
 
 #define SKIP_PEER_VERIFICATION
 //#define SKIP_HOSTNAME_VERIFICATION
+
+const char SCOPE_DRIVE_FILE[] = "https://www.googleapis.com/auth/drive.file";
+const char GRANT_TYPE_DEVICE[] = "http://oauth.net/grant_type/device/1.0";
+
 char response[80];
-extern ntstdio_t ntstdio;
+char errbuf[CURL_ERROR_SIZE];
+
+google_drive_t google_drive;
+
+void curl_setopt_common(CURL *curl);
+int google_drive_error_callback(jsonsl_t jsn, jsonsl_error_t err,
+	struct jsonsl_state_st *state, char *errat);
+void google_drive_state_callback(jsonsl_t jsn, jsonsl_action_t action,
+	struct jsonsl_state_st *state, const char *buf);
 
 void client_init(void)
 {
+	google_drive_t *gd = &google_drive;
+	jsonsl_t jsn;
+
+	memset(gd, 0, sizeof(google_drive_t));
+	memcpy(gd->fname, "1:/log/20160101000000.log", sizeof(gd->fname));
+
+	jsn = gd->jsn = jsonsl_new(3);
+	jsn->data = gd;
+	jsn->error_callback = google_drive_error_callback;
+	jsn->action_callback = google_drive_state_callback;
+	jsonsl_enable_all_callbacks(jsn);
+
 	//uITRON4_minit(ITRON_POOL_SIZE);
 
@@ -61,7 +87,548 @@
 }
 
-size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
-{
-	int rest = size * nmemb;
+size_t write_callback(void *buffer, size_t size, size_t nmemb, void *arg)
+{
+	google_drive_t *gd = (google_drive_t *)arg;
+	jsonsl_t jsn = gd->jsn;
+	size_t len = size * nmemb, pos;
+	jsonsl_char_t data;
+
+	for (pos = 0; pos < len; pos++) {
+		data = *((jsonsl_char_t *)&((uint8_t *)buffer)[pos]);
+
+		if ((gd->jsn_buf_pos >= 0) && (gd->jsn_buf_pos < sizeof(gd->jsn_buf)))
+			gd->jsn_buf[gd->jsn_buf_pos++] = data;
+		jsonsl_feed(jsn, &data, 1);
+	}
+
+	return len;
+}
+
+void get_device_id_state_start(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+	switch (gd->state) {
+	case psRoot:
+		if (state->level != 2) {
+			break;
+		}
+		else if (strcmp(buf, "device_code") == 0) {
+			gd->state = psDeviceCode;
+		}
+		else if (strcmp(buf, "user_code") == 0) {
+			gd->state = psUserCode;
+		}
+		else if (strcmp(buf, "expires_in") == 0) {
+			gd->state = psExpiresIn;
+		}
+		else if (strcmp(buf, "interval") == 0) {
+			gd->state = psInterval;
+		}
+		else if (strcmp(buf, "verification_url") == 0) {
+			gd->state = psVerificationUrl;
+		}
+		else if (strcmp(buf, "error") == 0) {
+			gd->state = psError;
+		}
+		else if (strcmp(buf, "error_description") == 0) {
+			gd->state = psErrorDescription;
+		}
+		break;
+	case psDeviceCode:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.device_code, buf, sizeof(gd->credential.device_code));
+			gd->state = psRoot;
+		}
+		break;
+	case psUserCode:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.user_code, buf, sizeof(gd->credential.user_code));
+			gd->state = psRoot;
+		}
+		break;
+	case psExpiresIn:
+		if (state->type == JSONSL_T_SPECIAL) {
+			gd->credential.expires_in = atoi(buf);
+			gd->state = psRoot;
+		}
+		break;
+	case psInterval:
+		if (state->type == JSONSL_T_SPECIAL) {
+			gd->credential.interval = atoi(buf);
+			gd->state = psRoot;
+		}
+		break;
+	case psVerificationUrl:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.verification_url, buf, sizeof(gd->credential.verification_url));
+			gd->state = psRoot;
+		}
+		break;
+	case psError:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->error.error, buf, sizeof(gd->error.error));
+			gd->state = psRoot;
+		}
+		break;
+	case psErrorDescription:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->error.error_description, buf, sizeof(gd->error.error_description));
+			gd->state = psRoot;
+		}
+		break;
+	}
+}
+
+void get_device_id_state_end(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+}
+
+int get_device_id(google_drive_t *gd, const char *scope)
+{
+	client_info_t *client_info = &gd->client_info;
+	credential_t *credential = &gd->credential;
+	CURLcode ret;
+	CURL *curl;
+	struct curl_slist *slist1;
+	size_t len;
+	char *postdata;
+	jsonsl_t jsn = gd->jsn;
+
+	gd->jsn_buf_pos = -1;
+
+	jsonsl_reset(jsn);
+	gd->start = get_device_id_state_start;
+	gd->end = get_device_id_state_end;
+
+	slist1 = NULL;
+	slist1 = curl_slist_append(slist1, "Content-Type: application/x-www-form-urlencoded");
+
+	curl = curl_easy_init();
+
+	char *client_id = curl_easy_escape(curl, client_info->client_id, strnlen(client_info->client_id, sizeof(client_info->client_id)));
+	char *esc_scope = curl_easy_escape(curl, scope, strlen(scope));
+
+	len = sizeof("client_id=") + strlen(client_id) + sizeof("scope=") + strlen(esc_scope);
+	postdata = malloc(len);
+	snprintf(postdata, len, "client_id=%s&scope=%s", client_id, esc_scope);
+
+	curl_free(client_id);
+	curl_free(esc_scope);
+
+	curl_easy_setopt(curl, CURLOPT_URL, "https://accounts.google.com/o/oauth2/device/code");
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata);
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)strnlen(postdata, len));
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1);
+	curl_setopt_common(curl);
+
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, gd);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+
+	ret = curl_easy_perform(curl);
+
+	free(postdata);
+
+	curl_easy_cleanup(curl);
+	curl = NULL;
+	curl_slist_free_all(slist1);
+	slist1 = NULL;
+
+	return (int)ret;
+}
+
+void get_access_token_state_start(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+	switch (gd->state) {
+	case psRoot:
+		if (state->level != 2) {
+			break;
+		}
+		else if (strcmp(buf, "access_token") == 0) {
+			gd->state = psAccessToken;
+		}
+		else if (strcmp(buf, "expires_in") == 0) {
+			gd->state = psExpiresIn;
+		}
+		else if (strcmp(buf, "refresh_token") == 0) {
+			gd->state = psRefreshToken;
+		}
+		else if (strcmp(buf, "scope") == 0) {
+			gd->state = psScope;
+		}
+		else if (strcmp(buf, "token_type") == 0) {
+			gd->state = psTokenType;
+		}
+		else if (strcmp(buf, "error") == 0) {
+			gd->state = psError;
+		}
+		else if (strcmp(buf, "error_description") == 0) {
+			gd->state = psErrorDescription;
+		}
+		break;
+	case psAccessToken:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.access_token, buf, sizeof(gd->credential.access_token));
+			gd->state = psRoot;
+		}
+		break;
+	case psExpiresIn:
+		if (state->type == JSONSL_T_SPECIAL) {
+			gd->credential.expires_in = atoi(buf);
+			gd->state = psRoot;
+		}
+		break;
+	case psRefreshToken:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.refresh_token, buf, sizeof(gd->credential.refresh_token));
+			gd->state = psRoot;
+		}
+		break;
+	case psScope:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.scope, buf, sizeof(gd->credential.scope));
+			gd->state = psRoot;
+		}
+		break;
+	case psTokenType:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.token_type, buf, sizeof(gd->credential.token_type));
+			gd->state = psRoot;
+		}
+		break;
+	case psError:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->error.error, buf, sizeof(gd->error.error));
+			gd->state = psRoot;
+		}
+		break;
+	case psErrorDescription:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->error.error_description, buf, sizeof(gd->error.error_description));
+			gd->state = psRoot;
+		}
+		break;
+	}
+}
+
+void get_access_token_state_end(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+}
+
+int get_access_token(google_drive_t *gd)
+{
+	client_info_t *client_info = &gd->client_info;
+	credential_t *credential = &gd->credential;
+	CURLcode ret;
+	CURL *curl;
+	struct curl_slist *slist1;
+	size_t len;
+	char *postdata;
+	jsonsl_t jsn = gd->jsn;
+
+	gd->jsn_buf_pos = -1;
+
+	jsonsl_reset(jsn);
+	gd->start = get_access_token_state_start;
+	gd->end = get_access_token_state_end;
+
+	slist1 = NULL;
+	slist1 = curl_slist_append(slist1, "Content-Type: application/x-www-form-urlencoded");
+
+	curl = curl_easy_init();
+
+	char *client_id = curl_easy_escape(curl, client_info->client_id, strnlen(client_info->client_id, sizeof(client_info->client_id)));
+	char *client_secret = curl_easy_escape(curl, client_info->client_secret, strnlen(client_info->client_secret, sizeof(client_info->client_secret)));
+	char *grant_type = curl_easy_escape(curl, GRANT_TYPE_DEVICE, strnlen(GRANT_TYPE_DEVICE, sizeof(GRANT_TYPE_DEVICE)));
+
+	len = sizeof("client_id=") + strlen(client_id) + sizeof("client_secret=") + strlen(client_secret)
+		+ sizeof("code=") + strlen(credential->device_code) + sizeof("grant_type=") + strlen(grant_type);
+	postdata = malloc(len);
+	snprintf(postdata, len, "client_id=%s&client_secret=%s&code=%s&grant_type=%s",
+		client_id, client_secret, credential->device_code, grant_type);
+
+	curl_free(client_id);
+	curl_free(client_secret);
+	curl_free(grant_type);
+
+	curl_easy_setopt(curl, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata);
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)strnlen(postdata, len));
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1);
+	curl_setopt_common(curl);
+
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, gd);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+
+	ret = curl_easy_perform(curl);
+
+	free(postdata);
+
+	curl_easy_cleanup(curl);
+	curl = NULL;
+	curl_slist_free_all(slist1);
+	slist1 = NULL;
+
+	return (int)ret;
+}
+
+void update_access_token_state_start(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+	switch (gd->state) {
+	case psRoot:
+		if (state->level != 2) {
+			break;
+		}
+		else if (strcmp(buf, "access_token") == 0) {
+			gd->state = psAccessToken;
+		}
+		else if (strcmp(buf, "expires_in") == 0) {
+			gd->state = psExpiresIn;
+		}
+		else if (strcmp(buf, "scope") == 0) {
+			gd->state = psScope;
+		}
+		else if (strcmp(buf, "token_type") == 0) {
+			gd->state = psTokenType;
+		}
+		else if (strcmp(buf, "error") == 0) {
+			gd->state = psError;
+		}
+		else if (strcmp(buf, "error_description") == 0) {
+			gd->state = psErrorDescription;
+		}
+		break;
+	case psAccessToken:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.access_token, buf, sizeof(gd->credential.access_token));
+			gd->state = psRoot;
+		}
+		break;
+	case psExpiresIn:
+		if (state->type == JSONSL_T_SPECIAL) {
+			gd->credential.expires_in = atoi(buf);
+			gd->state = psRoot;
+		}
+		break;
+	case psScope:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.scope, buf, sizeof(gd->credential.scope));
+			gd->state = psRoot;
+		}
+		break;
+	case psTokenType:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->credential.token_type, buf, sizeof(gd->credential.token_type));
+			gd->state = psRoot;
+		}
+		break;
+	case psError:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->error.error, buf, sizeof(gd->error.error));
+			gd->state = psRoot;
+		}
+		break;
+	case psErrorDescription:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->error.error_description, buf, sizeof(gd->error.error_description));
+			gd->state = psRoot;
+		}
+		break;
+	}
+}
+
+void update_access_token_state_end(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+}
+
+int update_access_token(google_drive_t *gd)
+{
+	client_info_t *client_info = &gd->client_info;
+	credential_t *credential = &gd->credential;
+	CURLcode ret;
+	CURL *curl;
+	struct curl_slist *slist1;
+	size_t len;
+	char *postdata;
+	jsonsl_t jsn = gd->jsn;
+
+	gd->jsn_buf_pos = -1;
+
+	jsonsl_reset(jsn);
+	gd->start = update_access_token_state_start;
+	gd->end = update_access_token_state_end;
+
+	slist1 = NULL;
+	slist1 = curl_slist_append(slist1, "Content-Type: application/x-www-form-urlencoded");
+
+	curl = curl_easy_init();
+
+	char *client_id = curl_easy_escape(curl, client_info->client_id, strnlen(client_info->client_id, sizeof(client_info->client_id)));
+	char *client_secret = curl_easy_escape(curl, client_info->client_secret, strnlen(client_info->client_secret, sizeof(client_info->client_secret)));
+
+	len = sizeof("client_id=") + strlen(client_id) + sizeof("client_secret=") + strlen(client_secret)
+		+ sizeof("refresh_token=") + strlen(credential->refresh_token);
+	postdata = malloc(len);
+	snprintf(postdata, len, "client_id=%s&client_secret=%s&code=%s&grant_type=refresh_token",
+		client_id, client_secret, credential->refresh_token);
+
+	curl_free(client_id);
+	curl_free(client_secret);
+
+	curl_easy_setopt(curl, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata);
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)strnlen(postdata, len));
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1);
+	curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
+	curl_setopt_common(curl);
+
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, gd);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+
+	ret = curl_easy_perform(curl);
+
+	free(postdata);
+
+	curl_easy_cleanup(curl);
+	curl = NULL;
+	curl_slist_free_all(slist1);
+	slist1 = NULL;
+
+	return (int)ret;
+}
+
+void revoke_device_state_start(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+}
+
+void revoke_device_state_end(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+}
+
+int revoke_device(google_drive_t *gd)
+{
+	client_info_t *client_info = &gd->client_info;
+	credential_t *credential = &gd->credential;
+	CURLcode ret;
+	CURL *curl;
+	size_t len;
+	struct curl_slist *slist1;
+	char *postdata;
+	jsonsl_t jsn = gd->jsn;
+
+	gd->jsn_buf_pos = -1;
+
+	jsonsl_reset(jsn);
+	gd->start = revoke_device_state_start;
+	gd->end = revoke_device_state_end;
+
+	slist1 = NULL;
+	slist1 = curl_slist_append(slist1, "Content-Type: application/x-www-form-urlencoded");
+
+	curl = curl_easy_init();
+
+	len = sizeof("token=") + strlen(credential->access_token);
+	postdata = malloc(len);
+	snprintf(postdata, len, "token=%s", credential->access_token);
+
+	curl_easy_setopt(curl, CURLOPT_URL, "https://accounts.google.com/o/oauth2/revoke");
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata);
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)strnlen(postdata, len));
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1);
+	curl_setopt_common(curl);
+
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, gd);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+
+	ret = curl_easy_perform(curl);
+
+	free(postdata);
+
+	curl_easy_cleanup(curl);
+	curl = NULL;
+	curl_slist_free_all(slist1);
+	slist1 = NULL;
+
+	return (int)ret;
+}
+
+void upload_file_state_start(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+	switch (gd->state) {
+	case psRoot:
+		if (state->level != 2) {
+			break;
+		}
+		else if (strcmp(buf, "kind") == 0) {
+			gd->state = psKind;
+		}
+		else if (strcmp(buf, "id") == 0) {
+			gd->state = psId;
+		}
+		else if (strcmp(buf, "name") == 0) {
+			gd->state = psName;
+		}
+		else if (strcmp(buf, "mimeType") == 0) {
+			gd->state = psMimeType;
+		}
+		else if (strcmp(buf, "error") == 0) {
+			gd->state = psError;
+		}
+		else if (strcmp(buf, "error_description") == 0) {
+			gd->state = psErrorDescription;
+		}
+		break;
+	case psKind:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->file.kind, buf, sizeof(gd->file.kind));
+			gd->state = psRoot;
+		}
+		break;
+	case psId:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->file.id, buf, sizeof(gd->file.id));
+			gd->state = psRoot;
+		}
+		break;
+	case psName:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->file.name, buf, sizeof(gd->file.name));
+			gd->state = psRoot;
+		}
+		break;
+	case psMimeType:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->file.mimeType, buf, sizeof(gd->file.mimeType));
+			gd->state = psRoot;
+		}
+		break;
+	case psError:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->error.error, buf, sizeof(gd->error.error));
+			gd->state = psRoot;
+		}
+		break;
+	case psErrorDescription:
+		if (state->type == JSONSL_T_STRING) {
+			strlcpy(gd->error.error_description, buf, sizeof(gd->error.error_description));
+			gd->state = psRoot;
+		}
+		break;
+	}
+}
+
+void upload_file_state_end(google_drive_t *gd, struct jsonsl_state_st *state, const char *buf)
+{
+}
+
+size_t read_callback(char *buffer, size_t size, size_t nitems, void *arg)
+{
+	FIL *file = (FIL *)arg;
+	UINT ret = 0;
+	FRESULT res;
+
+	res = f_read(file, buffer, size * nitems, &ret);
+	if (res != FR_OK)
+		return 0;
+
+	int rest = ret;
 	int len;
 
@@ -76,7 +643,7 @@
 		response[len] = '\0';
 
-		ntstdio_printf(&ntstdio, response);
-
-		dly_tsk(100 * 1000);
+		printf(response);
+
+		dly_tsk(100);
 
 		rest -= len;
@@ -84,42 +651,159 @@
 	}
 
-	return size * nmemb;
-}
-
-size_t read_data(char *buffer, size_t size, size_t nitems, void *instream)
-{
-	FIL *file = (FIL *)instream;
-	UINT ret = 0;
-	FRESULT res;
-
-	res = f_read(file, buffer, size * nitems, &ret);
-	if (res != FR_OK)
-		return 0;
-
-	int rest = ret;
-	int len;
-
-	while (rest > 0) {
-		len = rest;
-		if (len > (sizeof(response) - 1)) {
-			len = sizeof(response) - 1;
-		}
-
-		memcpy(response, buffer, len);
-
-		response[len] = '\0';
-
-		ntstdio_printf(&ntstdio, response);
-
-		dly_tsk(100);
-
-		rest -= len;
-		buffer = (char *)buffer + len;
-	}
-
 	return ret;
 }
 
-char errbuf[CURL_ERROR_SIZE];
+int seek_callback(void *arg, curl_off_t offset, int origin)
+{
+	FIL *file = (FIL *)arg;
+	BYTE mode;
+	FRESULT ret;
+
+	switch (origin) {
+	case SEEK_SET:
+		mode = F_SEEK_SET;
+		break;
+	case SEEK_CUR:
+		mode = F_SEEK_CUR;
+		break;
+	case SEEK_END:
+		mode = F_SEEK_END;
+		break;
+	default:
+		return CURL_SEEKFUNC_FAIL;
+	}
+
+	ret = f_seek(file, offset, mode);
+	if (ret != F_OK)
+		return CURL_SEEKFUNC_FAIL;
+
+	return CURL_SEEKFUNC_OK;
+}
+
+int upload_file(google_drive_t *gd, const char *filename, const char *localfilepath)
+{
+	CURLcode ret;
+	CURL *curl;
+	curl_mime *mime;
+	curl_mimepart *part;
+	struct curl_slist *slist1;
+	size_t len;
+	char *postdata, *authorization;
+	static const char buf[] = "Expect:";
+	jsonsl_t jsn = gd->jsn;
+	FIL file;
+	FRESULT fret;
+
+	gd->jsn_buf_pos = -1;
+
+	jsonsl_reset(jsn);
+	gd->start = upload_file_state_start;
+	gd->end = upload_file_state_end;
+
+	fret = f_open(&file, localfilepath, FA_READ);
+	if (fret != FR_OK) {
+		printf("log file open error %d\n", fret);
+		return -1;
+	}
+
+	len = sizeof("{\"name\":\"\"}") + strlen(filename);
+	postdata = malloc(len);
+	snprintf(postdata, len, "{\"name\":\"%s\"}", filename);
+
+	curl = curl_easy_init();
+
+	mime = curl_mime_init(curl);
+	part = curl_mime_addpart(mime);
+	curl_mime_name(part, "metadata");
+	curl_mime_type(part, "application/json;charset=utf-8");
+	curl_mime_data(part, postdata, CURL_ZERO_TERMINATED);
+	free(postdata);
+
+	part = curl_mime_addpart(mime);
+	curl_mime_name(part, "file");
+	curl_mime_type(part, "application/json");
+	curl_mime_data_cb(part, file.fsize, read_callback, seek_callback, NULL, &file);
+
+	len = sizeof("Authorization: Bearer ") + strnlen(gd->credential.access_token, sizeof(gd->credential.access_token));
+	authorization = malloc(len);
+	snprintf(authorization, len, "Authorization: Bearer %s", gd->credential.access_token);
+	slist1 = NULL;
+	slist1 = curl_slist_append(slist1, authorization);
+	free(authorization);
+
+	slist1 = curl_slist_append(slist1, buf);
+
+	curl_easy_setopt(curl, CURLOPT_URL, "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart");
+	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+	curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1);
+	curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
+	curl_setopt_common(curl);
+
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, gd);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+
+	ret = curl_easy_perform(curl);
+
+	curl_easy_cleanup(curl);
+	curl = NULL;
+	curl_mime_free(mime);
+	mime = NULL;
+	curl_slist_free_all(slist1);
+	slist1 = NULL;
+
+	f_close(&file);
+
+	return (int)ret;
+}
+
+int google_drive_error_callback(jsonsl_t jsn, jsonsl_error_t err,
+	struct jsonsl_state_st *state, char *errat)
+{
+	return 0;
+}
+
+void google_drive_state_callback(jsonsl_t jsn, jsonsl_action_t action,
+	struct jsonsl_state_st *state, const char *buf)
+{
+	google_drive_t *gd = (google_drive_t *)jsn->data;
+
+	switch (action) {
+	case JSONSL_ACTION_PUSH:
+		switch (state->type) {
+		case JSONSL_T_SPECIAL:
+			gd->jsn_buf[0] = *buf;
+			gd->jsn_buf_pos = 1;
+			break;
+		case JSONSL_T_STRING:
+		case JSONSL_T_HKEY:
+			gd->jsn_buf_pos = 0;
+			break;
+		default:
+			gd->jsn_buf_pos = -1;
+		}
+		break;
+	case JSONSL_ACTION_POP:
+		switch (state->type) {
+		case JSONSL_T_SPECIAL:
+		case JSONSL_T_STRING:
+		case JSONSL_T_HKEY:
+			gd->jsn_buf_pos--;
+			if (gd->jsn_buf_pos < sizeof(gd->jsn_buf)) {
+				gd->jsn_buf[gd->jsn_buf_pos] = '\0';
+			}
+			gd->start(gd, state, gd->jsn_buf);
+			break;
+		default:
+			gd->jsn_buf[0] = '\0';
+			break;
+		}
+		gd->jsn_buf_pos = -1;
+		break;
+	default:
+		gd->jsn_buf_pos = -1;
+		break;
+	}
+}
 
 static void get_logfname(char *fname)
@@ -185,5 +869,5 @@
 	ret = f_open(&file, fname, FA_CREATE_ALWAYS | FA_WRITE);
 	if (ret != FR_OK) {
-		ntstdio_printf(&ntstdio, "not open a upload file %d\n", ret);
+		printf("not open a upload file %d\n", ret);
 		return ret;
 	}
@@ -232,5 +916,4 @@
 	f_putc('}', &file);
 
-file_close:
 	f_close(&file);
 
@@ -238,43 +921,12 @@
 }
 
-void client_task(intptr_t exinf)
-{
-	CURL *curl;
+void curl_setopt_common(CURL *curl)
+{
 	CURLcode res;
-	int error = 0;
-	//const char *data = "{\"value\":\"data post\"}";
-	struct curl_slist *list = NULL;
-	FIL file;
-	FRESULT ret;
-	char fname[] = {"1:/log/20160101000000.log"};
-
-	get_logfname(fname);
-
-	ret = write_log(fname);
-	if (ret != FR_OK) {
-		ntstdio_printf(&ntstdio, "log file write error %d\n", ret);
-		return;
-	}
-
-	ret = f_open(&file, fname, FA_READ);
-	if (ret != FR_OK) {
-		ntstdio_printf(&ntstdio, "log file open error %d\n", ret);
-		return;
-	}
-
-	ntstdio_printf(&ntstdio, "cURL start\n");
-
-	curl = curl_easy_init();
-	if (curl == NULL) {
-		ntstdio_printf(&ntstdio, "curl_easy_init() failed\n");
-		goto file_close;
-	}
 
 	/* ask libcurl to show us the verbose output */
-	curl_easy_setopt(curl, CURLOPT_VERBOSE, true);
-
-	res = curl_easy_setopt(curl, CURLOPT_URL, "https://www.example.com/");
+	res = curl_easy_setopt(curl, CURLOPT_VERBOSE, true);
 	if (res != CURLE_OK)
-		ntstdio_printf(&ntstdio, "CURLOPT_URL failed: %s\n",
+		printf("CURLOPT_VERBOSE failed: %s\n",
 			curl_easy_strerror(res));
 
@@ -283,5 +935,8 @@
 
 	/* provide a buffer to store errors in */
-	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
+	res = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
+	if (res != CURLE_OK)
+		printf("CURLOPT_ERRORBUFFER failed: %s\n",
+			curl_easy_strerror(res));
 
 #ifdef SKIP_PEER_VERIFICATION
@@ -298,20 +953,20 @@
 	res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
 	if (res != CURLE_OK)
-		ntstdio_printf(&ntstdio, "CURLOPT_SSL_VERIFYPEER failed: %s\n",
+		printf("CURLOPT_SSL_VERIFYPEER failed: %s\n",
 			curl_easy_strerror(res));
 #else
 	res = curl_easy_setopt(curl, CURLOPT_CAINFO, "0:/certs/ca-cert.pem");
 	if (res != CURLE_OK)
-		ntstdio_printf(&ntstdio, "CURLOPT_CAINFO failed: %s\n",
+		printf("CURLOPT_CAINFO failed: %s\n",
 			curl_easy_strerror(res));
 
 	res = curl_easy_setopt(curl, CURLOPT_SSLCERT, "0:/certs/client-cert.pem");
 	if (res != CURLE_OK)
-		ntstdio_printf(&ntstdio, "CURLOPT_SSLCERT failed: %s\n",
+		printf("CURLOPT_SSLCERT failed: %s\n",
 			curl_easy_strerror(res));
 
 	res = curl_easy_setopt(curl, CURLOPT_SSLKEY, "0:/certs/client-key.pem");
 	if (res != CURLE_OK)
-		ntstdio_printf(&ntstdio, "CURLOPT_SSLKEY failed: %s\n",
+		printf("CURLOPT_SSLKEY failed: %s\n",
 			curl_easy_strerror(res));
 #endif
@@ -326,5 +981,5 @@
 	res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
 	if (res != CURLE_OK)
-		ntstdio_printf(&ntstdio, "CURLOPT_SSL_VERIFYHOST failed: %s\n",
+		printf("CURLOPT_SSL_VERIFYHOST failed: %s\n",
 			curl_easy_strerror(res));
 #endif
@@ -332,53 +987,138 @@
 	/*res = curl_easy_setopt(curl, CURLOPT_PROXY, "https://proxy.example.com:8080");
 	if (res != CURLE_OK)
-		ntstdio_printf(&ntstdio, "CURLOPT_PROXY failed: %s\n",
+		printf("CURLOPT_PROXY failed: %s\n",
 			curl_easy_strerror(res));*/
 
-	curl_easy_setopt(curl, CURLOPT_POST, 1);
-
-	/* size of the POST data */
-	//curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(data));
-	curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, file.fsize);
-
-	/* pass in a pointer to the data - libcurl will not copy */
-	//curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
-
-	list = curl_slist_append(list, "Content-Type: application/json");
-
-	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
-
-	curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&error);
-
-	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
-
-	/* now specify which file to upload */
-	curl_easy_setopt(curl, CURLOPT_READDATA, &file);
-
-	/* we want to use our own read function */
-	curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_data);
-
-	ntstdio_printf(&ntstdio, "cURL perform the request\n");
-	tslp_tsk(100 * 1000);
-
-	/* Perform the request, res will get the return code */
-	res = curl_easy_perform(curl);
-	/* Check for errors */
-	if (res != CURLE_OK) {
-		ntstdio_printf(&ntstdio, "curl_easy_perform() failed: %s\n",
+	res = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
+	if (res != CURLE_OK)
+		printf("CURLOPT_NOPROGRESS failed: %s\n",
 			curl_easy_strerror(res));
-		ntstdio_printf(&ntstdio, errbuf);
-	}
-
-	/* always cleanup */
-	curl_easy_cleanup(curl);
-
-	ntstdio_printf(&ntstdio, "cURL end\n");
-
-file_close:
-	f_close(&file);
-}
-
-void client_fin(void)
-{
+
+	res = curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L);
+	if (res != CURLE_OK)
+		printf("CURLOPT_MAXREDIRS failed: %s\n",
+			curl_easy_strerror(res));
+
+	res = curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
+	if (res != CURLE_OK)
+		printf("CURLOPT_TCP_KEEPALIVE failed: %s\n",
+			curl_easy_strerror(res));
+}
+
+void client_set_client_id(const char *client_id)
+{
+	google_drive_t *gd = &google_drive;
+
+	strlcpy(gd->client_info.client_id, client_id, sizeof(gd->client_info.client_id));
+}
+
+void client_set_client_secret(const char *client_secret)
+{
+	google_drive_t *gd = &google_drive;
+
+	strlcpy(gd->client_info.client_secret, client_secret, sizeof(gd->client_info.client_secret));
+}
+
+int client_get_device_id(int argc, char **argv)
+{
+	google_drive_t *gd = &google_drive;
+	int ret;
+
+	ret = get_device_id(gd, SCOPE_DRIVE_FILE);
+	if (ret == 0) {
+		printf("Device was registered. Enter the code at the following URL\n");
+		printf("url:  %s\n", gd->credential.verification_url);
+		printf("code: %s\n", gd->credential.user_code);
+	}
+	else {
+		printf("Device register failed! %d\n", ret);
+	}
+
+	return ret;
+}
+
+int client_get_access_token(int argc, char **argv)
+{
+	google_drive_t *gd = &google_drive;
+	int ret;
+
+	ret = get_access_token(gd);
+	if (ret == 0) {
+		printf("Access token was give\n");
+		printf("Refresh token: %s\n", gd->credential.refresh_token);
+		printf("Access token: %s\n", gd->credential.access_token);
+	}
+	else {
+		printf("Access token gain failed. %d\n", ret);
+	}
+
+	return ret;
+}
+
+int client_update_access_token(int argc, char **argv)
+{
+	google_drive_t *gd = &google_drive;
+	int ret;
+
+	ret = update_access_token(gd);
+	if (ret == 0) {
+		printf("Access token was update\n");
+		printf("Access token: %s\n", gd->credential.access_token);
+	}
+	else {
+		printf("Access token update failed. %d\n", ret);
+	}
+
+	return ret;
+}
+
+int client_revoke(int argc, char **argv)
+{
+	google_drive_t *gd = &google_drive;
+	int ret;
+
+	ret = revoke_device(gd);
+	if (ret == 0) {
+		printf("Device was revoked.\n");
+	}
+	else {
+		printf("Device revoke failed. %d\n", ret);
+	}
+
+	return ret;
+}
+
+int client_upload_file(int argc, char **argv)
+{
+	google_drive_t *gd = &google_drive;
+	int ret;
+	FRESULT fret;
+	char fname[] = { "1:/log/20160101000000.log" };
+
+	get_logfname(fname);
+
+	fret = write_log(fname);
+	if (fret != FR_OK) {
+		printf("log file write error %d\n", fret);
+		return (int)fret;
+	}
+
+	ret = upload_file(gd, &fname[7], fname);
+	if (ret == 0) {
+		printf("%s was uploaded.\n", &fname[7]);
+	}
+	else {
+		printf("%s upload failed. %d\n", &fname[7], ret);
+	}
+
+	return ret;
+}
+
+void client_final(void)
+{
+	google_drive_t *gd = &google_drive;
+	jsonsl_t jsn = gd->jsn;
+
 	curl_global_cleanup();
-}
+	jsonsl_destroy(jsn);
+}
