/*
* rpc_server.cpp
*
* Copyright (c) 2012 seeed technology inc.
* Website : www.seeed.cc
* Author : Jack Shao (jacky.shaoxg@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "stdlib.h"
#include "Arduino.h"
#include "rpc_stream.h"
#include "rpc_server.h"
#include "rpc_queue.h"
#include "network.h"
#include "ota.h"
resource_t *p_first_resource;
resource_t *p_cur_resource;
event_t *p_event_queue_head;
event_t *p_event_queue_tail;
static Queue event_q_external(100);
static Queue event_q_internal(100);
static int parse_stage_data;
static int parse_stage_cmd;
extern void print_well_known();
void drain_event_queue();
RPCStreamProcessor rpc_stream_processor_data(ARG_BUFFER_LEN, STREAM_DATA);
RPCStreamProcessor rpc_stream_processor_cmd(CMD_ARG_BUFFER_LEN, STREAM_CMD);
void rpc_server_init()
{
//init rpc stream
stream_init(STREAM_DATA);
stream_init(STREAM_CMD);
//init rpc server
p_first_resource = p_cur_resource = NULL;
p_event_queue_head = p_event_queue_tail = NULL;
parse_stage_data = PARSE_REQ_TYPE;
parse_stage_cmd = PARSE_REQ_TYPE;
rpc_server_register_resources();
rpc_server_register_plugins();
//printf("rpc server init done!\n");
}
void rpc_server_register_method(char *grove_name, char *method_name, method_dir_t rw, method_ptr_t ptr, void *class_ptr, uint8_t *arg_types)
{
resource_t *p_res = (resource_t*)malloc(sizeof(resource_t));
if (!p_res) return;
if (p_first_resource == NULL)
{
p_first_resource = p_res;
p_cur_resource = p_res;
} else
{
p_cur_resource->next = p_res;
p_cur_resource = p_res;
}
p_cur_resource->grove_name = grove_name;
p_cur_resource->method_name = method_name;
p_cur_resource->rw = rw;
p_cur_resource->method_ptr = ptr;
p_cur_resource->class_ptr = class_ptr;
p_cur_resource->next = NULL;
memcpy(p_cur_resource->arg_types, arg_types, sizeof(p_cur_resource->arg_types));
}
void rpc_server_unregister_all()
{
if (p_first_resource == NULL)
{
return;
}
resource_t *p_res = p_first_resource;
while (p_res)
{
delete p_res->class_ptr;
resource_t *tmp = p_res;
p_res = p_res->next;
free(tmp);
}
p_first_resource = NULL;
}
resource_t* __find_resource(char *name, char *method, int req_type)
{
/*
if (!p_first_resource)
{
return NULL;
}
*/
resource_t *ptr;
for (ptr = p_first_resource; ptr; ptr = ptr->next)
{
if (strncmp(name, ptr->grove_name, 33) == 0 && strncmp(method, ptr->method_name, 33) == 0
&& req_type == ptr->rw)
{
return ptr;
}
}
return NULL;
}
int __convert_arg(uint8_t *arg_buff, void *buff, int type)
{
int i;
uint32_t ui;
switch (type)
{
case TYPE_BOOL:
{
i = atoi((const char *)(buff));
ui = abs(i);
memcpy(arg_buff, &ui, sizeof(bool));
return sizeof(bool);
break;
}
case TYPE_UINT8:
{
i = atoi((const char *)(buff));
ui = abs(i);
memcpy(arg_buff, &ui, 1);
return 1;
break;
}
case TYPE_UINT16:
{
i = atoi((const char *)(buff));
ui = abs(i);
memcpy(arg_buff, &ui, 2);
return 2;
break;
}
case TYPE_UINT32:
{
int32_t l = atol((const char *)(buff));
ui = abs(l);
memcpy(arg_buff, &ui, 4);
return 4;
break;
}
case TYPE_INT8:
{
i = atoi((const char *)(buff));
char c = i;
memcpy(arg_buff, &c, 1);
return 1;
break;
}
case TYPE_INT16:
{
i = atoi((const char *)(buff));
memcpy(arg_buff, &i, 2);
return 2;
break;
}
case TYPE_INT32:
{
int32_t l = atol((const char *)(buff));
memcpy(arg_buff, &l, 4);
return 4;
break;
}
case TYPE_INT:
{
int l = atol((const char *)(buff));
memcpy(arg_buff, &l, sizeof(int));
return sizeof(int);
break;
}
case TYPE_FLOAT:
{
float f = atof((const char *)(buff));
memcpy(arg_buff, &f, sizeof(float));
return sizeof(float);
break;
}
case TYPE_STRING:
{
uint32_t ptr = (uint32_t)buff;
memcpy(arg_buff, &ptr, 4);
return 4;
break;
}
default:
break;
}
return 0;
}
RPCStreamProcessor::RPCStreamProcessor(int buff_len, int streamid)
{
this->buff = (char *)malloc(buff_len);
this->stream_id = streamid;
}
void RPCStreamProcessor::process_stream()
{
while (stream_available(stream_id) > 0 || parse_stage == PARSE_CALL)
{
//Serial1.println(stream_available(stream_id));
switch (parse_stage)
{
case PARSE_REQ_TYPE:
{
bool parsed_req_type = false;
buff[0] = buff[1]; buff[1] = buff[2]; buff[2] = buff[3];
buff[3] = stream_read(stream_id);
if (memcmp(buff, "GET", 3) == 0 || memcmp(buff, "get", 3) == 0)
{
req_type = REQ_GET;
parsed_req_type = true;
response_msg_open(stream_id, "resp_get");
}
if (memcmp(buff, "POST", 4) == 0 || memcmp(buff, "post", 4) == 0)
{
req_type = REQ_POST;
parsed_req_type = true;
stream_read(stream_id); //read " " out
response_msg_open(stream_id, "resp_post");
}
if (memcmp(buff, "OTA", 3) == 0 || memcmp(buff, "ota", 3) == 0)
{
parse_stage = DIVE_INTO_OTA;
response_msg_open(stream_id, "ota_trig_ack");
stream_print(stream_id, TYPE_STRING, "null");
response_msg_close(stream_id);
break;
}
if (memcmp(buff, "APP", 3) == 0 || memcmp(buff, "app", 3) == 0)
{
parse_stage = GET_APP_NUM;
response_msg_open(stream_id, "resp_app");
break;
}
if (parsed_req_type)
{
ch = stream_read(stream_id);
if (ch != '/')
{
//error request format
stream_print(stream_id, TYPE_STRING, "\"BAD REQUEST: missing root:'/'.\"");
response_msg_append_404(stream_id);
response_msg_close(stream_id);
} else
{
parse_stage = PARSE_GROVE_NAME;
p_resource = NULL;
offset = 0;
}
}
break;
}
case PARSE_GROVE_NAME:
{
ch = stream_read(stream_id);
if (ch == '\r' || ch == '\n')
{
buff[offset] = '\0';
if (strcmp(buff, ".well-known") == 0)
{
//writer_print(TYPE_STRING, "\"/.well-known is not implemented\"");
print_well_known();
response_msg_close(stream_id);
parse_stage = PARSE_REQ_TYPE;
} else
{
stream_print(stream_id, TYPE_STRING, "\"BAD REQUEST: missing method name.\"");
response_msg_append_404(stream_id);
response_msg_close(stream_id);
parse_stage = PARSE_REQ_TYPE;
}
} else if (ch != '/' && offset < (NAME_LEN-1))
{
buff[offset++] = ch;
} else
{
buff[offset] = '\0';
memcpy(grove_name, buff, offset + 1);
while (ch != '/')
{
ch = stream_read(stream_id);
}
parse_stage = PARSE_METHOD;
offset = 0;
}
break;
}
case PARSE_METHOD:
{
ch = stream_read(stream_id);
if (ch == '\r' || ch == '\n')
{
buff[offset] = '\0';
memcpy(method_name, buff, offset + 1);
parse_stage = CHECK_ARGS; //to check if req missing arg
} else if (ch == '/')
{
buff[offset] = '\0';
memcpy(method_name, buff, offset + 1);
parse_stage = PRE_PARSE_ARGS;
} else if (offset >= (NAME_LEN-1))
{
buff[offset] = '\0';
memcpy(method_name, buff, offset + 1);
while (ch != '/' && ch != '\r' && ch != '\n')
{
ch = stream_read(stream_id);
}
if (ch == '\r' || ch == '\n') parse_stage = CHECK_ARGS;
else parse_stage = PRE_PARSE_ARGS;
} else
{
buff[offset++] = ch;
}
break;
}
case CHECK_ARGS:
{
p_resource = __find_resource((char *)grove_name, (char *)method_name, req_type);
if (!p_resource)
{
stream_print(stream_id, TYPE_STRING, "\"METHOD NOT FOUND WHEN CHECK ARGS\"");
response_msg_append_404(stream_id);
response_msg_close(stream_id);
parse_stage = PARSE_REQ_TYPE;
break;
}
if (p_resource->arg_types[0] != TYPE_NONE)
{
stream_print(stream_id, TYPE_STRING, "\"MISSING ARGS\"");
response_msg_append_404(stream_id);
response_msg_close(stream_id);
parse_stage = PARSE_REQ_TYPE;
break;
}
parse_stage = PARSE_CALL;
break;
}
case PRE_PARSE_ARGS:
{
p_resource = __find_resource((char *)grove_name, (char *)method_name, req_type);
if (!p_resource)
{
stream_print(stream_id, TYPE_STRING, "\"METHOD NOT FOUND WHEN PARSE ARGS\"");
response_msg_append_404(stream_id);
response_msg_close(stream_id);
parse_stage = PARSE_REQ_TYPE;
break;
}
parse_stage = PARSE_ARGS;
arg_index = 0;
arg_offset = 0;
offset = 0;
break;
}
case PARSE_ARGS:
{
bool overlen = false;
ch = stream_read(stream_id);
if (ch == '\r' || ch == '\n' || ch == '/')
{
buff[offset] = '\0';
} else if (offset >= ARG_BUFFER_LEN)
{
buff[offset] = '\0';
while (ch != '/' && ch != '\r' && ch != '\n')
{
ch = stream_read(stream_id);
}
overlen = true;
} else
{
buff[offset++] = ch;
}
if (ch == '/')
{
char *p = buff;
int len = __convert_arg(arg_buff + arg_offset, p, p_resource->arg_types[arg_index++]);
arg_offset += len;
offset = 0;
}
if (ch == '\r' || ch == '\n' || overlen)
{
if ((arg_index < 3 && p_resource->arg_types[arg_index + 1] != TYPE_NONE) ||
(arg_index <= 3 && p_resource->arg_types[arg_index] != TYPE_NONE && strlen(buff) < 1))
{
stream_print(stream_id, TYPE_STRING, "\"MISSING ARGS\"");
response_msg_append_404(stream_id);
response_msg_close(stream_id);
parse_stage = PARSE_REQ_TYPE;
break;
}
char *p = buff;
int len = __convert_arg(arg_buff + arg_offset, p, p_resource->arg_types[arg_index++]);
arg_offset += len;
offset = 0;
parse_stage = PARSE_CALL;
}
break;
}
case PARSE_CALL:
{
if (!p_resource) p_resource = __find_resource((char *)grove_name, (char *)method_name, req_type);
if (!p_resource)
{
stream_print(stream_id, TYPE_STRING, "\"METHOD NOT FOUND WHEN CALL\"");
response_msg_close(stream_id);
parse_stage = PARSE_REQ_TYPE;
break;
}
//writer_print(TYPE_STRING, "{");
if (false == p_resource->method_ptr(p_resource->class_ptr, p_resource->method_name, arg_buff)) response_msg_append_400(stream_id);
//writer_print(TYPE_STRING, "}");
response_msg_close(stream_id);
parse_stage = PARSE_REQ_TYPE;
break;
}
case DIVE_INTO_OTA:
{
//TODO: refer to the ota related code here
ch = stream_read(stream_id);
while (ch != '\r' && ch != '\n')
{
ch = stream_read(stream_id);
}
//espconn_disconnect(&tcp_conn[0]);
parse_stage = PARSE_REQ_TYPE;
ota_start();
while (!ota_fini)
{
digitalWrite(STATUS_LED, ~digitalRead(STATUS_LED));
delay(100);
keepalive_last_recv_time[0] = keepalive_last_recv_time[1] = millis(); //to prevent online check and offline-reconnect during ota
}
break;
}
case GET_APP_NUM:
{
ch = stream_read(stream_id);
while (ch != '\r' && ch != '\n')
{
ch = stream_read(stream_id);
}
parse_stage = PARSE_REQ_TYPE;
int bin_num = 1;
if(system_upgrade_userbin_check() == UPGRADE_FW_BIN1)
{
Serial1.printf("Running user1.bin \r\n\r\n");
//os_memcpy(user_bin, "user2.bin", 10);
bin_num = 1;
} else if(system_upgrade_userbin_check() == UPGRADE_FW_BIN2)
{
Serial1.printf("Running user2.bin \r\n\r\n");
//os_memcpy(user_bin, "user1.bin", 10);
bin_num = 2;
}
stream_print(stream_id, TYPE_INT, &bin_num);
response_msg_close(stream_id);
break;
}
default:
break;
}
delay(0); //yield the cpu for critical use or the watch dog will be hungry enough to trigger the reset.
}
}
void rpc_server_loop()
{
drain_event_queue();
rpc_stream_processor_data.process_stream();
rpc_stream_processor_cmd.process_stream();
}
static event_t event;
void drain_event_queue()
{
int cnt = 0;
/* report event if event queue is not empty */
while (rpc_server_event_queue_pop(&event) && cnt++ < 5)
{
response_msg_open(STREAM_DATA,"event");
writer_print(TYPE_STRING, "{\"");
writer_print(TYPE_STRING, event.event_name);
writer_print(TYPE_STRING, "\":\"");
if (event.event_data_type != TYPE_STRING)
{
writer_print((type_t)event.event_data_type, &event.event_data);
} else
{
writer_print((type_t)event.event_data_type, event.event_data.ptr);
}
writer_print(TYPE_STRING, "\"}");
response_msg_close(STREAM_DATA);
keepalive_last_recv_time[0] = millis();
optimistic_yield(100);
}
}
int rpc_server_event_put_data(event_t *event, void *p_data, int event_data_type)
{
int sz = 0;
memset(event->event_data.raw, 0, 4);
event->event_data_type = event_data_type;
switch (event_data_type)
{
case TYPE_BOOL:
sz = sizeof(bool);
break;
case TYPE_UINT8:
sz = sizeof(uint8_t);
break;
case TYPE_INT8:
sz = sizeof(int8_t);
break;
case TYPE_UINT16:
sz = sizeof(uint16_t);
break;
case TYPE_INT16:
sz = sizeof(int16_t);
break;
case TYPE_INT:
sz = sizeof(int);
break;
case TYPE_UINT32:
sz = sizeof(uint32_t);
break;
case TYPE_INT32:
sz = sizeof(int32_t);
break;
case TYPE_FLOAT:
sz = sizeof(float);
break;
case TYPE_STRING:
sz = sizeof(char *);
event->event_data.p_str = p_data;
return sz;
break;
case TYPE_NONE:
default:
break;
}
memcpy(event->event_data.raw, p_data, sz);
return sz;
}
void _rpc_server_event_report(char *event_name, void *event_data, int event_data_type, int src)
{
event_t *ev;
ev = (event_t *)malloc(sizeof(event_t));
if (ev)
{
ev->event_name = event_name;
rpc_server_event_put_data(ev, event_data, event_data_type);
{
InterruptLock lock;
if (!event_q_external.push(ev))
{
free(ev);
}
}
}
// copy event to internal event queue if the event comes from driver framework
if (src == FROM_DRIVER)
{
ev = (event_t *)malloc(sizeof(event_t));
if (ev)
{
ev->event_name = event_name;
rpc_server_event_put_data(ev, event_data, event_data_type);
{
InterruptLock lock;
if (!event_q_internal.push(ev))
{
free(ev);
}
}
}
}
}
void rpc_server_event_report(char *event_name, void *event_data, int event_data_type)
{
_rpc_server_event_report(event_name, event_data, event_data_type, FROM_DRIVER);
}
void rpc_server_event_report_from_user(char *event_name, void *event_data, int event_data_type)
{
_rpc_server_event_report(event_name, event_data, event_data_type, FROM_USER_SPACE);
}
bool _rpc_server_event_queue_pop(event_t *event, int target)
{
bool ret = false;
Queue *p_q;
if (target == POP_FROM_EXTERNAL_Q)
{
p_q = &event_q_external;
} else
{
p_q = &event_q_internal;
}
{
InterruptLock lock;
if (p_q->get_size() == 0)
{
return ret;
}
}
event_t *p_ev;
{
InterruptLock lock;
if (p_q->pop(&p_ev))
{
memcpy(event, p_ev, sizeof(event_t));
free(p_ev);
ret = true;
}
}
return ret;
}
bool rpc_server_event_queue_pop(event_t *event)
{
return _rpc_server_event_queue_pop(event, POP_FROM_EXTERNAL_Q);
}
bool rpc_server_event_queue_pop_from_internal(event_t *event)
{
return _rpc_server_event_queue_pop(event, POP_FROM_INTERNAL_Q);
}
int rpc_server_event_queue_size(int target)
{
Queue *p_q;
if (target == POP_FROM_EXTERNAL_Q)
{
p_q = &event_q_external;
} else
{
p_q = &event_q_internal;
}
int sz;
{
InterruptLock lock;
sz = p_q->get_size();
}
return sz;
}