mirror of
https://github.com/sogou/workflow.git
synced 2026-02-08 01:33:17 +08:00
419 lines
7.9 KiB
C
419 lines
7.9 KiB
C
/*
|
|
Copyright (c) 2019 Sogou, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Authors: Wu Jiaxu (wujiaxu@sogou-inc.com)
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "list.h"
|
|
#include "redis_parser.h"
|
|
|
|
#define MIN(x, y) ((x) <= (y) ? (x) : (y))
|
|
#define MAX(x, y) ((x) >= (y) ? (x) : (y))
|
|
|
|
#define REDIS_MSGBUF_INIT_SIZE 8
|
|
|
|
enum
|
|
{
|
|
//REDIS_PARSE_INIT = 0,
|
|
REDIS_GET_CMD = 1,
|
|
REDIS_GET_CR,
|
|
REDIS_GET_LF,
|
|
REDIS_UNTIL_CRLF,
|
|
REDIS_GET_NCHAR,
|
|
REDIS_PARSE_END
|
|
};
|
|
|
|
typedef struct __read_record
|
|
{
|
|
redis_reply_t *reply;
|
|
struct list_head read_list;
|
|
}__read_record;
|
|
|
|
void redis_reply_deinit(redis_reply_t *reply)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < reply->elements; i++)
|
|
{
|
|
redis_reply_deinit(reply->element[i]);
|
|
free(reply->element[i]);
|
|
}
|
|
|
|
free(reply->element);
|
|
}
|
|
|
|
static redis_reply_t **__create_array(size_t size, redis_reply_t *reply)
|
|
{
|
|
size_t elements = 0;
|
|
redis_reply_t **element = (redis_reply_t **)malloc(size * sizeof (void *));
|
|
|
|
if (element)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
element[i] = (redis_reply_t *)malloc(sizeof (redis_reply_t));
|
|
if (element[i])
|
|
{
|
|
redis_reply_init(element[i]);
|
|
elements++;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (elements == size)
|
|
return element;
|
|
|
|
while (elements > 0)
|
|
free(element[--elements]);
|
|
|
|
free(element);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int redis_reply_set_array(size_t size, redis_reply_t *reply)
|
|
{
|
|
redis_reply_t **element = __create_array(size, reply);
|
|
|
|
if (element == NULL)
|
|
return -1;
|
|
|
|
redis_reply_deinit(reply);
|
|
reply->element = element;
|
|
reply->elements = size;
|
|
reply->type = REDIS_REPLY_TYPE_ARRAY;
|
|
return 0;
|
|
}
|
|
|
|
static int __redis_parse_cmd(const char ch, redis_parser_t *parser)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case '+':
|
|
case '-':
|
|
case ':':
|
|
case '$':
|
|
case '*':
|
|
parser->cmd = ch;
|
|
parser->status = REDIS_UNTIL_CRLF;
|
|
parser->findidx = parser->msgidx;
|
|
return 0;
|
|
}
|
|
|
|
return -2;
|
|
}
|
|
|
|
static int __redis_parse_cr(const char ch, redis_parser_t *parser)
|
|
{
|
|
if (ch != '\r')
|
|
return -2;
|
|
|
|
parser->status = REDIS_GET_LF;
|
|
return 0;
|
|
}
|
|
|
|
static int __redis_parse_lf(const char ch, redis_parser_t *parser)
|
|
{
|
|
if (ch != '\n')
|
|
return -2;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int __redis_parse_line(redis_parser_t *parser)
|
|
{
|
|
char *buf = (char *)parser->msgbuf;
|
|
char *str = buf + parser->msgidx;
|
|
size_t slen = parser->findidx - parser->msgidx;
|
|
char data[32];
|
|
int i, n;
|
|
const char *offset = (const char *)parser->msgidx;
|
|
__read_record *node;
|
|
|
|
parser->msgidx = parser->findidx + 2;
|
|
switch (parser->cmd)
|
|
{
|
|
case '+':
|
|
redis_reply_set_status(offset, slen, parser->cur);
|
|
return 1;
|
|
|
|
case '-':
|
|
redis_reply_set_error(offset, slen, parser->cur);
|
|
return 1;
|
|
|
|
case ':':
|
|
if (slen == 0 || slen > 30)
|
|
return -1;
|
|
|
|
memcpy(data, str, slen);
|
|
data[slen] = '\0';
|
|
redis_reply_set_integer(atoll(data), parser->cur);
|
|
return 1;
|
|
|
|
case '$':
|
|
n = atoi(str);
|
|
if (n < 0)
|
|
{
|
|
redis_reply_set_null(parser->cur);
|
|
return 1;
|
|
}
|
|
else if (n == 0)
|
|
{
|
|
redis_reply_set_string(offset, 0, parser->cur);
|
|
parser->status = REDIS_GET_CR;
|
|
return 0;
|
|
}
|
|
|
|
parser->nchar = n;
|
|
parser->status = REDIS_GET_NCHAR;
|
|
return 0;
|
|
|
|
case '*':
|
|
n = atoi(str);
|
|
if (n < 0)
|
|
{
|
|
redis_reply_set_null(parser->cur);
|
|
return 1;
|
|
}
|
|
|
|
parser->nleft += n;
|
|
if (redis_reply_set_array(n, parser->cur) < 0)
|
|
return -1;
|
|
|
|
if (n == 0)
|
|
return 1;
|
|
|
|
parser->nleft--;
|
|
for (i = 0; i < n - 1; i++)
|
|
{
|
|
node = (__read_record *)malloc(sizeof (__read_record));
|
|
if (!node)
|
|
return -1;
|
|
|
|
node->reply = parser->cur->element[n - 1 - i];
|
|
list_add(&node->read_list, &parser->read_list);
|
|
}
|
|
|
|
parser->cur = parser->cur->element[0];
|
|
parser->status = REDIS_GET_CMD;
|
|
return 0;
|
|
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int __redis_parse_crlf(redis_parser_t *parser)
|
|
{
|
|
char *buf = (char *)parser->msgbuf;
|
|
|
|
for (; parser->findidx + 1 < parser->msgsize; parser->findidx++)
|
|
{
|
|
if (buf[parser->findidx] == '\r' && buf[parser->findidx + 1] == '\n')
|
|
return __redis_parse_line(parser);
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
static int __redis_parse_nchar(redis_parser_t *parser)
|
|
{
|
|
//char *buf = (char *)parser->msgbuf;
|
|
|
|
if (parser->nchar <= parser->msgsize - parser->msgidx)
|
|
{
|
|
redis_reply_set_string((const char *)parser->msgidx, parser->nchar,
|
|
parser->cur);
|
|
|
|
parser->msgidx += parser->nchar;
|
|
parser->status = REDIS_GET_CR;
|
|
return 0;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
//-1 error | 0 continue | 1 finish-one | 2 not-enough
|
|
static int __redis_parser_forward(redis_parser_t *parser)
|
|
{
|
|
char *buf = (char *)parser->msgbuf;
|
|
|
|
if (parser->msgidx >= parser->msgsize)
|
|
return 2;
|
|
|
|
switch (parser->status)
|
|
{
|
|
case REDIS_GET_CMD:
|
|
return __redis_parse_cmd(buf[parser->msgidx++], parser);
|
|
|
|
case REDIS_GET_CR:
|
|
return __redis_parse_cr(buf[parser->msgidx++], parser);
|
|
|
|
case REDIS_GET_LF:
|
|
return __redis_parse_lf(buf[parser->msgidx++], parser);
|
|
|
|
case REDIS_UNTIL_CRLF:
|
|
return __redis_parse_crlf(parser);
|
|
|
|
case REDIS_GET_NCHAR:
|
|
return __redis_parse_nchar(parser);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void redis_parser_init(redis_parser_t *parser)
|
|
{
|
|
redis_reply_init(&parser->reply);
|
|
parser->parse_succ = 0;
|
|
parser->msgbuf = NULL;
|
|
parser->msgsize = 0;
|
|
parser->bufsize = 0;
|
|
//parser->status = REDIS_PARSE_INIT;
|
|
//parser->nleft = 0;
|
|
parser->status = REDIS_GET_CMD;
|
|
parser->nleft = 1;
|
|
parser->cur = &parser->reply;
|
|
INIT_LIST_HEAD(&parser->read_list);
|
|
parser->msgidx = 0;
|
|
parser->cmd = '\0';
|
|
parser->nchar = 0;
|
|
parser->findidx = 0;
|
|
}
|
|
|
|
void redis_parser_deinit(redis_parser_t *parser)
|
|
{
|
|
struct list_head *pos, *tmp;
|
|
__read_record *next;
|
|
|
|
list_for_each_safe(pos, tmp, &parser->read_list)
|
|
{
|
|
next = list_entry(pos, __read_record, read_list);
|
|
list_del(pos);
|
|
free(next);
|
|
}
|
|
|
|
redis_reply_deinit(&parser->reply);
|
|
free(parser->msgbuf);
|
|
}
|
|
|
|
static void __redis_parse_done(redis_reply_t *reply, char *buf)
|
|
{
|
|
size_t i;
|
|
|
|
switch (reply->type)
|
|
{
|
|
case REDIS_REPLY_TYPE_INTEGER:
|
|
break;
|
|
|
|
case REDIS_REPLY_TYPE_ARRAY:
|
|
for (i = 0; i < reply->elements; i++)
|
|
__redis_parse_done(reply->element[i], buf);
|
|
|
|
break;
|
|
|
|
case REDIS_REPLY_TYPE_STATUS:
|
|
case REDIS_REPLY_TYPE_ERROR:
|
|
case REDIS_REPLY_TYPE_STRING:
|
|
reply->str = buf + (size_t)reply->str;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int redis_parser_append_message(const void *buf,
|
|
size_t *size,
|
|
redis_parser_t *parser)
|
|
{
|
|
size_t msgsize_bak = parser->msgsize;
|
|
|
|
if (parser->status == REDIS_PARSE_END)
|
|
{
|
|
*size = 0;
|
|
return 1;
|
|
}
|
|
|
|
if (parser->msgsize + *size > parser->bufsize)
|
|
{
|
|
size_t new_size = MAX(REDIS_MSGBUF_INIT_SIZE, 2 * parser->bufsize);
|
|
void *new_base;
|
|
|
|
while (new_size < parser->msgsize + *size)
|
|
new_size *= 2;
|
|
|
|
new_base = realloc(parser->msgbuf, new_size);
|
|
if (!new_base)
|
|
return -1;
|
|
|
|
parser->msgbuf = new_base;
|
|
parser->bufsize = new_size;
|
|
}
|
|
|
|
memcpy((char *)parser->msgbuf + parser->msgsize, buf, *size);
|
|
parser->msgsize += *size;
|
|
/*
|
|
if (parser->status == REDIS_PARSE_INIT)
|
|
{
|
|
parser->nleft = 1;
|
|
parser->status = REDIS_GET_CMD;
|
|
}
|
|
*/
|
|
|
|
do
|
|
{
|
|
int ret = __redis_parser_forward(parser);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret == 1)
|
|
{
|
|
struct list_head *lnext = parser->read_list.next;
|
|
__read_record *next;
|
|
|
|
parser->nleft--;
|
|
if (lnext && lnext != &parser->read_list)
|
|
{
|
|
next = list_entry(lnext, __read_record, read_list);
|
|
parser->cur = next->reply;
|
|
list_del(lnext);
|
|
free(next);
|
|
}
|
|
|
|
if (parser->nleft > 0)
|
|
parser->status = REDIS_GET_CMD;
|
|
else
|
|
{
|
|
parser->parse_succ = 1;
|
|
parser->status = REDIS_PARSE_END;
|
|
}
|
|
}
|
|
else if (ret == 2)
|
|
return 0;
|
|
|
|
} while (parser->status != REDIS_PARSE_END);
|
|
|
|
*size = parser->msgidx - msgsize_bak;
|
|
__redis_parse_done(&parser->reply, (char *)parser->msgbuf);
|
|
return 1;
|
|
}
|
|
|