Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0164332a99 | ||
|
|
18b01764fd | ||
|
|
1680eaf357 | ||
|
|
e54def88e6 | ||
|
|
3ce256cf7b | ||
|
|
f0f02ba162 | ||
|
|
4e9221f3b3 | ||
|
|
e1c922096c | ||
|
|
5f1ed96692 | ||
|
|
d0b6b9ab76 | ||
|
|
c923fdf06f | ||
|
|
7532d85081 | ||
|
|
16ea09090a | ||
|
|
2b61fd1a28 | ||
|
|
1f6a4d8c6a | ||
|
|
1ab4fc95f2 | ||
|
|
fde8520274 | ||
|
|
e4df5892ea | ||
|
|
2afcc9c76e | ||
|
|
3a91923d61 | ||
|
|
993981a0d3 | ||
|
|
2ef4a5be15 | ||
|
|
50c41814a8 | ||
|
|
2a37c4a100 | ||
|
|
d991528368 | ||
|
|
b71887216d | ||
|
|
796b2f1f51 | ||
|
|
b2dff31807 | ||
|
|
5146cf994b | ||
|
|
db1b4e9659 | ||
|
|
ef8f0a613b | ||
|
|
2defe23277 | ||
|
|
f7097a170e | ||
|
|
fce264e385 | ||
|
|
125cf58cec | ||
|
|
6138ef4aca | ||
|
|
d0eb3f6511 | ||
|
|
6c990440ee | ||
|
|
3be5869671 | ||
|
|
8e7745605e | ||
|
|
85dac0d63f | ||
|
|
afb462698f | ||
|
|
ee39aff4bc | ||
|
|
ee99d5a3d1 | ||
|
|
87e9451e6b | ||
|
|
7ad207fb6d | ||
|
|
945aae00d8 | ||
|
|
0a66b7c02b | ||
|
|
4757c6d150 | ||
|
|
307e29ac8c | ||
|
|
fbdcfae4a3 | ||
|
|
58737d9f3b | ||
|
|
c37187f8ed | ||
|
|
aa1b9c12b6 | ||
|
|
300b3d32da | ||
|
|
9bb22da68a | ||
|
|
a52925d59a | ||
|
|
11ad647ad6 | ||
|
|
6d3577ef66 | ||
|
|
67cf82240e | ||
|
|
92e6324343 | ||
|
|
d9759edb70 | ||
|
|
910d46c53d | ||
|
|
52daeaff70 | ||
|
|
271cca8d8c | ||
|
|
b9e0d8a6e8 | ||
|
|
7e773ee238 | ||
|
|
47f23f0a16 | ||
|
|
07db464b53 | ||
|
|
10da4544a6 | ||
|
|
c06801cba9 | ||
|
|
aacf12c2de | ||
|
|
6ccf65d5c4 | ||
|
|
74d000ae26 | ||
|
|
5bd66cae90 | ||
|
|
87d9d48b1b | ||
|
|
54a39608e0 | ||
|
|
7540faf39d | ||
|
|
306c97dcd4 | ||
|
|
1375e4008c | ||
|
|
70fe4177ab | ||
|
|
dfc0c796e3 | ||
|
|
77a045ff4d | ||
|
|
bb2d09113c | ||
|
|
c288dd29b9 | ||
|
|
679b231774 | ||
|
|
71f280f065 | ||
|
|
7d260dd77b | ||
|
|
a866667fd4 | ||
|
|
b852caf345 | ||
|
|
e791703fdd | ||
|
|
62a36ab1f9 | ||
|
|
c6bbe8403f | ||
|
|
a69942ccd7 | ||
|
|
4d3f9aa1b7 | ||
|
|
fdc802349f | ||
|
|
4234b1cf2c | ||
|
|
29617b1a65 | ||
|
|
82555dfaad | ||
|
|
32d86ef979 | ||
|
|
d9c056721f | ||
|
|
b550b8c800 | ||
|
|
628a961f86 | ||
|
|
6e98f37390 | ||
|
|
9adb3aeda7 | ||
|
|
ee443ce59d | ||
|
|
45ed2729ee | ||
|
|
87055888be | ||
|
|
b50af6c539 | ||
|
|
64288d2cc4 | ||
|
|
2fc0af5479 | ||
|
|
e43597aee8 | ||
|
|
21858350e8 | ||
|
|
095b376573 | ||
|
|
ec934e92d8 | ||
|
|
3c378b8422 | ||
|
|
a25cd930dc | ||
|
|
df7348053b | ||
|
|
ec7eaed9f2 | ||
|
|
fd5898290f | ||
|
|
3672c7465f | ||
|
|
80acfd9d24 | ||
|
|
ef0864c50f | ||
|
|
09346dccf5 | ||
|
|
0426ddade3 | ||
|
|
b127f31e12 | ||
|
|
6134606604 | ||
|
|
e5f1193101 | ||
|
|
bf14e87a10 | ||
|
|
b6e6624aa8 | ||
|
|
e7e12adef1 | ||
|
|
bd96d1f9fe | ||
|
|
b42a144c1e | ||
|
|
5a46b31173 | ||
|
|
acf174fba7 | ||
|
|
4088a8f918 | ||
|
|
5f1212ed6e | ||
|
|
422bd45393 | ||
|
|
572a96744d | ||
|
|
73b6930e07 | ||
|
|
bb95fb448d | ||
|
|
112019a08d | ||
|
|
03a860884b | ||
|
|
032ed29a1f | ||
|
|
37c16d6d60 | ||
|
|
9b4666788a | ||
|
|
b919612b8b | ||
|
|
dcb783abf8 | ||
|
|
96a5c166cb | ||
|
|
4783ee2733 | ||
|
|
d128380b93 | ||
|
|
4ae503d1a9 | ||
|
|
e7d8b0c78a | ||
|
|
624ba36502 | ||
|
|
5d94b82adf | ||
|
|
b9427f4110 | ||
|
|
d96c4c9e8b | ||
|
|
d64575b236 | ||
|
|
cbe59756f8 |
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
/hiredis-test
|
||||
/hiredis-example*
|
||||
/*.o
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.a
|
||||
*.o
|
||||
*.so
|
||||
*.dylib
|
||||
*.a
|
||||
|
||||
55
Makefile
55
Makefile
@ -3,54 +3,13 @@
|
||||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
OBJ=net.o hiredis.o sds.o async.o
|
||||
include ./Makefile.common
|
||||
|
||||
OBJ=net.o hiredis.o sds.o async.o parser.o object.o handle.o format.o context.o address.o request.o fd.o
|
||||
BINS=hiredis-example hiredis-test
|
||||
LIBNAME=libhiredis
|
||||
|
||||
HIREDIS_MAJOR=0
|
||||
HIREDIS_MINOR=10
|
||||
|
||||
# Fallback to gcc when $CC is not in $PATH.
|
||||
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
|
||||
DEBUG?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG)
|
||||
REAL_LDFLAGS=$(LDFLAGS)
|
||||
|
||||
DYLIBSUFFIX=so
|
||||
STLIBSUFFIX=a
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
|
||||
|
||||
# Platform-specific overrides
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
ifeq ($(uname_S),SunOS)
|
||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||
INSTALL= cp -r
|
||||
endif
|
||||
ifeq ($(uname_S),Darwin)
|
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(DYLIBNAME) $(BINS)
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
net.o: net.c fmacros.h net.h hiredis.h
|
||||
async.o: async.c async.h hiredis.h sds.h dict.c dict.h
|
||||
example.o: example.c hiredis.h
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h net.h sds.h
|
||||
sds.o: sds.c sds.h
|
||||
test.o: test.c hiredis.h
|
||||
|
||||
$(DYLIBNAME): $(OBJ)
|
||||
$(DYLIB_MAKE_CMD) $(OBJ)
|
||||
|
||||
@ -95,13 +54,15 @@ check: hiredis-test
|
||||
kill `cat /tmp/hiredis-test-redis.pid`
|
||||
|
||||
.c.o:
|
||||
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
|
||||
$(CC) -std=c99 -pedantic -c $(OPTIMIZATION) $(REAL_CFLAGS) $<
|
||||
|
||||
clean:
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov test/*.o
|
||||
|
||||
-include ./Makefile.dep
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
$(CC) -MM *.c > Makefile.dep
|
||||
|
||||
# Installation related variables and target
|
||||
PREFIX?=/usr/local
|
||||
|
||||
35
Makefile.common
Normal file
35
Makefile.common
Normal file
@ -0,0 +1,35 @@
|
||||
LIBNAME=libhiredis
|
||||
|
||||
HIREDIS_MAJOR=0
|
||||
HIREDIS_MINOR=10
|
||||
|
||||
# Fallback to gcc when $CC is not in $PATH.
|
||||
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-unused-label
|
||||
DEBUG?= -g -ggdb
|
||||
REAL_CFLAGS=-fPIC $(CFLAGS) $(WARNINGS) $(DEBUG)
|
||||
REAL_LDFLAGS=$(LDFLAGS)
|
||||
|
||||
DYLIBSUFFIX=so
|
||||
STLIBSUFFIX=a
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
|
||||
|
||||
# Platform-specific overrides
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
ifeq ($(uname_S),SunOS)
|
||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||
INSTALL= cp -r
|
||||
endif
|
||||
ifeq ($(uname_S),Darwin)
|
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
endif
|
||||
16
fmacros.h
16
fmacros.h
@ -1,16 +0,0 @@
|
||||
#ifndef __HIREDIS_FMACRO_H
|
||||
#define __HIREDIS_FMACRO_H
|
||||
|
||||
#if !defined(_BSD_SOURCE)
|
||||
#define _BSD_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__sun__)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#elif defined(__linux__)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#else
|
||||
#define _XOPEN_SOURCE
|
||||
#endif
|
||||
|
||||
#endif
|
||||
39
src/Makefile
Normal file
39
src/Makefile
Normal file
@ -0,0 +1,39 @@
|
||||
# Hiredis Makefile
|
||||
# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
include ./Makefile.common
|
||||
|
||||
OBJ= \
|
||||
address.o \
|
||||
context.o \
|
||||
fd.o \
|
||||
format.o \
|
||||
handle.o \
|
||||
object.o \
|
||||
parser.o \
|
||||
request.o \
|
||||
sds.o \
|
||||
|
||||
all: $(DYLIBNAME)
|
||||
|
||||
$(DYLIBNAME): $(OBJ)
|
||||
$(DYLIB_MAKE_CMD) $(OBJ)
|
||||
|
||||
$(STLIBNAME): $(OBJ)
|
||||
$(STLIB_MAKE_CMD) $(OBJ)
|
||||
|
||||
dynamic: $(DYLIBNAME)
|
||||
static: $(STLIBNAME)
|
||||
|
||||
.c.o:
|
||||
$(HIREDIS_CC) -c $<
|
||||
|
||||
clean:
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) *.o
|
||||
|
||||
-include ./Makefile.dep
|
||||
|
||||
dep:
|
||||
$(HIREDIS_CC) -MM *.c > Makefile.dep
|
||||
42
src/Makefile.common
Normal file
42
src/Makefile.common
Normal file
@ -0,0 +1,42 @@
|
||||
LIBNAME= libhiredis
|
||||
HIREDIS_MAJOR= 0
|
||||
HIREDIS_MINOR= 11
|
||||
|
||||
DYLIBSUFFIX= so
|
||||
STLIBSUFFIX= a
|
||||
DYLIB_MINOR_NAME= $(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
|
||||
DYLIB_MAJOR_NAME= $(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||
DYLIBNAME= $(LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD= $(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(FINAL_LDFLAGS)
|
||||
STLIBNAME= $(LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD= ar rcs $(STLIBNAME)
|
||||
|
||||
# Default settings
|
||||
STD= -std=c99 -pedantic -D_POSIX_C_SOURCE=200112L
|
||||
WARN= -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-unused-label
|
||||
OPT= -O3
|
||||
FINAL_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) -fPIC $(CFLAGS) $(HIREDIS_CFLAGS)
|
||||
FINAL_LDFLAGS= $(DEBUG) $(LDFLAGS) $(HIREDIS_LDFLAGS)
|
||||
FINAL_LIBS= $(HIREDIS_LIBS)
|
||||
DEBUG= -g -ggdb
|
||||
HIREDIS_CC= $(CC) $(FINAL_CFLAGS)
|
||||
HIREDIS_LD= $(CC) $(FINAL_LDFLAGS)
|
||||
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
# SunOS overrides
|
||||
ifeq ($(uname_S),SunOS)
|
||||
HIREDIS_CFLAGS= -D__EXTENSIONS__ -D_XPG6
|
||||
HIREDIS_LDFLAGS=
|
||||
HIREDIS_LIBS= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(FINAL_LDFLAGS) $(FINAL_LIBS)
|
||||
INSTALL= cp -r
|
||||
endif
|
||||
|
||||
# Darwin overrides
|
||||
ifeq ($(uname_S),Darwin)
|
||||
DYLIBSUFFIX= dylib
|
||||
DYLIB_MINOR_NAME= $(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAJOR_NAME= $(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD= $(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(FINAL_LDFLAGS) $(FINAL_LIBS)
|
||||
endif
|
||||
11
src/Makefile.dep
Normal file
11
src/Makefile.dep
Normal file
@ -0,0 +1,11 @@
|
||||
address.o: address.c address.h
|
||||
context.o: context.c context.h handle.h address.h common.h parser.h \
|
||||
format.h object.h
|
||||
fd.o: fd.c fd.h address.h common.h
|
||||
format.o: format.c format.h sds.h
|
||||
handle.o: handle.c fd.h address.h common.h handle.h parser.h sds.h
|
||||
object.o: object.c object.h parser.h
|
||||
parser.o: parser.c parser.h
|
||||
request.o: request.c handle.h address.h common.h parser.h object.h \
|
||||
request.h ngx-queue.h
|
||||
sds.o: sds.c sds.h
|
||||
65
src/address.c
Normal file
65
src/address.c
Normal file
@ -0,0 +1,65 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "address.h"
|
||||
|
||||
redis_address redis_address_from_in(struct sockaddr_in sa) {
|
||||
redis_address addr;
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sa_family = sa.sin_family;
|
||||
addr.sa_addrlen = sizeof(sa);
|
||||
addr.sa_addr.in = sa;
|
||||
return addr;
|
||||
}
|
||||
|
||||
redis_address redis_address_from_in6(struct sockaddr_in6 sa) {
|
||||
redis_address addr;
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sa_family = sa.sin6_family;
|
||||
addr.sa_addrlen = sizeof(sa);
|
||||
addr.sa_addr.in6 = sa;
|
||||
return addr;
|
||||
}
|
||||
|
||||
redis_address redis_address_from_un(struct sockaddr_un sa) {
|
||||
redis_address addr;
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sa_family = sa.sun_family;
|
||||
addr.sa_addrlen = sizeof(sa);
|
||||
addr.sa_addr.un = sa;
|
||||
return addr;
|
||||
}
|
||||
|
||||
redis_address redis_address_in(const char *ip, int port) {
|
||||
struct sockaddr_in sa;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
assert(inet_pton(AF_INET, ip, &sa.sin_addr) == 1);
|
||||
return redis_address_from_in(sa);
|
||||
}
|
||||
|
||||
redis_address redis_address_in6(const char *ip, int port) {
|
||||
struct sockaddr_in6 sa;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin6_family = AF_INET6;
|
||||
sa.sin6_port = htons(port);
|
||||
assert(inet_pton(AF_INET6, ip, &sa.sin6_addr) == 1);
|
||||
return redis_address_from_in6(sa);
|
||||
}
|
||||
|
||||
redis_address redis_address_un(const char *path) {
|
||||
struct sockaddr_un sa;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sun_family = AF_UNIX;
|
||||
strncpy((char*)&sa.sun_path, path, sizeof(sa.sun_path));
|
||||
sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
|
||||
return redis_address_from_un(sa);
|
||||
}
|
||||
27
src/address.h
Normal file
27
src/address.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef HIREDIS_ADDRESS_H
|
||||
#define HIREDIS_ADDRESS_H 1
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
typedef struct redis_address_s redis_address;
|
||||
|
||||
struct redis_address_s {
|
||||
int sa_family;
|
||||
socklen_t sa_addrlen;
|
||||
union {
|
||||
struct sockaddr addr;
|
||||
struct sockaddr_in in;
|
||||
struct sockaddr_in6 in6;
|
||||
struct sockaddr_un un;
|
||||
} sa_addr;
|
||||
};
|
||||
|
||||
redis_address redis_address_from_in(struct sockaddr_in sa);
|
||||
redis_address redis_address_from_in6(struct sockaddr_in6 sa);
|
||||
redis_address redis_address_from_un(struct sockaddr_un sa);
|
||||
redis_address redis_address_in(const char *ip, int port);
|
||||
redis_address redis_address_in6(const char *ip, int port);
|
||||
redis_address redis_address_un(const char *path);
|
||||
|
||||
#endif
|
||||
10
src/common.h
Normal file
10
src/common.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef HIREDIS_COMMON_H
|
||||
#define HIREDIS_COMMON_H 1
|
||||
|
||||
#define REDIS_OK 0
|
||||
#define REDIS_ESYS -1
|
||||
#define REDIS_EGAI -2
|
||||
#define REDIS_EPARSER -3
|
||||
#define REDIS_EEOF -4
|
||||
|
||||
#endif
|
||||
281
src/context.c
Normal file
281
src/context.c
Normal file
@ -0,0 +1,281 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "context.h"
|
||||
#include "format.h"
|
||||
#include "object.h"
|
||||
|
||||
static void redis__clear_address(redis_context *ctx) {
|
||||
memset(&ctx->address, 0, sizeof(ctx->address));
|
||||
}
|
||||
|
||||
int redis_context_init(redis_context *ctx) {
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(&ctx->handle);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Pull default timeout from handle */
|
||||
ctx->timeout = redis_handle_get_timeout(&ctx->handle);
|
||||
|
||||
/* Reinitialize parser with bundled object functions */
|
||||
redis_parser_init(&ctx->handle.parser, &redis_object_parser_callbacks);
|
||||
|
||||
/* Clear address field */
|
||||
redis__clear_address(ctx);
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_context_destroy(redis_context *ctx) {
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_destroy(&ctx->handle);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int redis_context_set_timeout(redis_context *ctx, unsigned long timeout) {
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_set_timeout(&ctx->handle, timeout);
|
||||
if (rv == REDIS_OK) {
|
||||
ctx->timeout = timeout;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
unsigned long redis_context_get_timeout(redis_context *ctx) {
|
||||
return ctx->timeout;
|
||||
}
|
||||
|
||||
static int redis__connect(redis_context *ctx) {
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_connect_address(&ctx->handle, ctx->address);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = redis_handle_wait_connected(&ctx->handle);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_context_connect_address(redis_context *ctx, const redis_address addr) {
|
||||
int rv;
|
||||
|
||||
ctx->address = addr;
|
||||
|
||||
rv = redis__connect(ctx);
|
||||
if (rv != REDIS_OK) {
|
||||
redis__clear_address(ctx);
|
||||
return rv;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_context_connect_in(redis_context *ctx, struct sockaddr_in addr) {
|
||||
return redis_context_connect_address(ctx, redis_address_from_in(addr));
|
||||
}
|
||||
|
||||
int redis_context_connect_in6(redis_context *ctx, struct sockaddr_in6 addr) {
|
||||
return redis_context_connect_address(ctx, redis_address_from_in6(addr));
|
||||
}
|
||||
|
||||
int redis_context_connect_un(redis_context *ctx, struct sockaddr_un addr) {
|
||||
return redis_context_connect_address(ctx, redis_address_from_un(addr));
|
||||
}
|
||||
|
||||
int redis_context_connect_gai(redis_context *ctx, const char *host, int port) {
|
||||
redis_address address;
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_connect_gai(&ctx->handle, AF_INET, host, port, &address);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = redis_handle_wait_connected(&ctx->handle);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Store address that getaddrinfo resolved and we are now connected to */
|
||||
ctx->address = address;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_context_flush(redis_context *ctx) {
|
||||
int rv;
|
||||
int drained = 0;
|
||||
|
||||
while (!drained) {
|
||||
rv = redis_handle_write_from_buffer(&ctx->handle, &drained);
|
||||
|
||||
if (rv == REDIS_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Wait for the socket to become writable on EAGAIN */
|
||||
if (rv == REDIS_ESYS && errno == EAGAIN) {
|
||||
rv = redis_handle_wait_writable(&ctx->handle);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_context_read(redis_context *ctx, redis_protocol **reply) {
|
||||
int rv;
|
||||
redis_protocol *aux = NULL;
|
||||
|
||||
rv = redis_handle_read_from_buffer(&ctx->handle, &aux);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* No error, no reply: make sure write buffers are flushed */
|
||||
if (aux == NULL) {
|
||||
rv = redis_context_flush(ctx);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
while (aux == NULL) {
|
||||
rv = redis_handle_wait_readable(&ctx->handle);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = redis_handle_read_to_buffer(&ctx->handle);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = redis_handle_read_from_buffer(&ctx->handle, &aux);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
assert(aux != NULL);
|
||||
*reply = aux;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_context_write_vcommand(redis_context *ctx,
|
||||
const char *format,
|
||||
va_list ap)
|
||||
{
|
||||
char *buf;
|
||||
int len;
|
||||
int rv;
|
||||
|
||||
len = redis_format_vcommand(&buf, format, ap);
|
||||
if (len < 0) {
|
||||
errno = ENOMEM;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
/* Write command to output buffer in handle */
|
||||
rv = redis_handle_write_to_buffer(&ctx->handle, buf, len);
|
||||
free(buf);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int redis_context_write_command(redis_context *ctx,
|
||||
const char *format,
|
||||
...)
|
||||
{
|
||||
va_list ap;
|
||||
int rv;
|
||||
|
||||
va_start(ap, format);
|
||||
rv = redis_context_write_vcommand(ctx, format, ap);
|
||||
va_end(ap);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int redis_context_write_command_argv(redis_context *ctx,
|
||||
int argc,
|
||||
const char **argv,
|
||||
const size_t *argvlen)
|
||||
{
|
||||
char *buf;
|
||||
int len;
|
||||
int rv;
|
||||
|
||||
len = redis_format_command_argv(&buf, argc, argv, argvlen);
|
||||
if (len < 0) {
|
||||
errno = ENOMEM;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
/* Write command to output buffer in handle */
|
||||
rv = redis_handle_write_to_buffer(&ctx->handle, buf, len);
|
||||
free(buf);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int redis_context_call_vcommand(redis_context *ctx,
|
||||
redis_protocol **reply,
|
||||
const char *format,
|
||||
va_list ap)
|
||||
{
|
||||
int rv;
|
||||
|
||||
rv = redis_context_write_vcommand(ctx, format, ap);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return redis_context_read(ctx, reply);
|
||||
}
|
||||
|
||||
int redis_context_call_command(redis_context *ctx,
|
||||
redis_protocol **reply,
|
||||
const char *format,
|
||||
...)
|
||||
{
|
||||
va_list ap;
|
||||
int rv;
|
||||
|
||||
va_start(ap, format);
|
||||
rv = redis_context_call_vcommand(ctx, reply, format, ap);
|
||||
va_end(ap);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int redis_context_call_command_argv(redis_context *ctx,
|
||||
redis_protocol **reply,
|
||||
int argc,
|
||||
const char **argv,
|
||||
const size_t *argvlen)
|
||||
{
|
||||
int rv;
|
||||
|
||||
rv = redis_context_write_command_argv(ctx, argc, argv, argvlen);
|
||||
if (rv != REDIS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return redis_context_read(ctx, reply);
|
||||
}
|
||||
57
src/context.h
Normal file
57
src/context.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef HIREDIS_CONTEXT_H
|
||||
#define HIREDIS_CONTEXT_H 1
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "handle.h"
|
||||
|
||||
typedef struct redis_context_s redis_context;
|
||||
|
||||
struct redis_context_s {
|
||||
redis_handle handle;
|
||||
unsigned long timeout;
|
||||
|
||||
/* private: used to reconnect */
|
||||
redis_address address;
|
||||
};
|
||||
|
||||
int redis_context_init(redis_context *ctx);
|
||||
int redis_context_destroy(redis_context *ctx);
|
||||
int redis_context_set_timeout(redis_context *ctx, unsigned long us);
|
||||
unsigned long redis_handle_get_timeout(redis_handle *ctx);
|
||||
|
||||
int redis_context_connect_address(redis_context *ctx, const redis_address addr);
|
||||
int redis_context_connect_in(redis_context *ctx, struct sockaddr_in sa);
|
||||
int redis_context_connect_in6(redis_context *ctx, struct sockaddr_in6 sa);
|
||||
int redis_context_connect_un(redis_context *ctx, struct sockaddr_un sa);
|
||||
int redis_context_connect_gai(redis_context *ctx, const char *addr, int port);
|
||||
|
||||
int redis_context_flush(redis_context *ctx);
|
||||
int redis_context_read(redis_context *ctx, redis_protocol **reply);
|
||||
|
||||
int redis_context_write_vcommand(redis_context *ctx,
|
||||
const char *format,
|
||||
va_list ap);
|
||||
int redis_context_write_command(redis_context *ctx,
|
||||
const char *format,
|
||||
...);
|
||||
int redis_context_write_command_argv(redis_context *ctx,
|
||||
int argc,
|
||||
const char **argv,
|
||||
const size_t *argvlen);
|
||||
|
||||
int redis_context_call_vcommand(redis_context *ctx,
|
||||
redis_protocol **reply,
|
||||
const char *format,
|
||||
va_list ap);
|
||||
int redis_context_call_command(redis_context *ctx,
|
||||
redis_protocol **reply,
|
||||
const char *format,
|
||||
...);
|
||||
int redis_context_call_command_argv(redis_context *ctx,
|
||||
redis_protocol **reply,
|
||||
int argc,
|
||||
const char **argv,
|
||||
const size_t *argvlen);
|
||||
|
||||
#endif
|
||||
169
src/fd.c
Normal file
169
src/fd.c
Normal file
@ -0,0 +1,169 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fd.h"
|
||||
|
||||
int redis_fd_error(int fd) {
|
||||
int err = 0;
|
||||
socklen_t errlen = sizeof(err);
|
||||
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int redis_fd_read(int fildes, void *buf, size_t nbyte) {
|
||||
int nread;
|
||||
|
||||
do {
|
||||
nread = read(fildes, buf, nbyte);
|
||||
} while (nread == -1 && errno == EINTR);
|
||||
|
||||
if (nread == -1) {
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
if (nread == 0) {
|
||||
return REDIS_EEOF;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
int redis_fd_write(int fildes, const void *buf, size_t nbyte) {
|
||||
int nwritten;
|
||||
|
||||
do {
|
||||
nwritten = write(fildes, buf, nbyte);
|
||||
} while (nwritten == -1 && errno == EINTR);
|
||||
|
||||
if (nwritten == -1) {
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
static int redis__nonblock(int fd, int nonblock) {
|
||||
int flags;
|
||||
|
||||
if ((flags = fcntl(fd, F_GETFL)) == -1) {
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
if (nonblock) {
|
||||
flags |= O_NONBLOCK;
|
||||
} else {
|
||||
flags &= ~O_NONBLOCK;
|
||||
}
|
||||
|
||||
if (fcntl(fd, F_SETFL, flags) == -1) {
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int redis__connect(int family, const struct sockaddr *addr, socklen_t addrlen) {
|
||||
int fd, rv;
|
||||
int on = 1;
|
||||
|
||||
fd = socket(family, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (family == AF_INET || family == AF_INET6) {
|
||||
rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
|
||||
if (rv == -1) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* The socket needs to be non blocking to be able to timeout connect(2). */
|
||||
rv = redis__nonblock(fd, 1);
|
||||
if (rv != REDIS_OK) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
rv = connect(fd, addr, addrlen);
|
||||
if (rv == -1 && errno != EINPROGRESS) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
|
||||
if (rv == -1) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return fd;
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
int redis_fd_connect_address(const redis_address addr) {
|
||||
return redis__connect(addr.sa_family, &addr.sa_addr.addr, addr.sa_addrlen);
|
||||
}
|
||||
|
||||
int redis_fd_connect_gai(int family,
|
||||
const char *addr,
|
||||
int port,
|
||||
redis_address *_addr)
|
||||
{
|
||||
char _port[6]; /* strlen("65535"); */
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
int rv, fd;
|
||||
|
||||
snprintf(_port, 6, "%d", port);
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = family;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if ((rv = getaddrinfo(addr, _port, &hints, &servinfo)) != 0) {
|
||||
errno = rv;
|
||||
return REDIS_EGAI;
|
||||
}
|
||||
|
||||
/* Expect at least one record. */
|
||||
assert(servinfo != NULL);
|
||||
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
fd = redis__connect(p->ai_family, p->ai_addr, p->ai_addrlen);
|
||||
if (fd == -1) {
|
||||
if (errno == EHOSTUNREACH) {
|
||||
/* AF_INET6 record on a machine without IPv6 support.
|
||||
* See c4ed06d9 for more information. */
|
||||
continue;
|
||||
}
|
||||
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Pass address we connect to back to caller */
|
||||
if (_addr != NULL) {
|
||||
memset(_addr, 0, sizeof(*_addr));
|
||||
memcpy(&_addr->sa_addr, p->ai_addr, p->ai_addrlen);
|
||||
_addr->sa_family = p->ai_family;
|
||||
_addr->sa_addrlen = p->ai_addrlen;
|
||||
}
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
return fd;
|
||||
}
|
||||
|
||||
error:
|
||||
freeaddrinfo(servinfo);
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
13
src/fd.h
Normal file
13
src/fd.h
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef HIREDIS_FD_H
|
||||
#define HIREDIS_FD_H 1
|
||||
|
||||
#include "address.h"
|
||||
#include "common.h"
|
||||
|
||||
int redis_fd_error(int fd);
|
||||
int redis_fd_read(int fildes, void *buf, size_t nbyte);
|
||||
int redis_fd_write(int fildes, const void *buf, size_t nbyte);
|
||||
int redis_fd_connect_address(const redis_address addr);
|
||||
int redis_fd_connect_gai(int family, const char *host, int port, redis_address *addr);
|
||||
|
||||
#endif
|
||||
315
src/format.c
Normal file
315
src/format.c
Normal file
@ -0,0 +1,315 @@
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "sds.h"
|
||||
|
||||
/* Number of bytes to represent specified integer as string */
|
||||
static int intlen(int i) {
|
||||
int len = 0;
|
||||
if (i < 0) {
|
||||
len++;
|
||||
i = -i;
|
||||
}
|
||||
do {
|
||||
len++;
|
||||
i /= 10;
|
||||
} while(i);
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Protocol length of bulk with specified length */
|
||||
static size_t bulklen(size_t len) {
|
||||
return 1+intlen(len)+2+len+2;
|
||||
}
|
||||
|
||||
int redis_format_vcommand(char **target, const char *format, va_list ap) {
|
||||
const char *c = format;
|
||||
char *cmd = NULL; /* final command */
|
||||
int pos; /* position in final command */
|
||||
sds curarg, newarg; /* current argument */
|
||||
int touched = 0; /* was the current argument touched? */
|
||||
char **curargv = NULL, **newargv = NULL;
|
||||
int argc = 0;
|
||||
int totlen = 0;
|
||||
int j;
|
||||
|
||||
/* Abort if there is not target to set */
|
||||
if (target == NULL)
|
||||
return -1;
|
||||
|
||||
/* Build the command string accordingly to protocol */
|
||||
curarg = sdsempty();
|
||||
if (curarg == NULL)
|
||||
return -1;
|
||||
|
||||
while(*c != '\0') {
|
||||
if (*c != '%' || c[1] == '\0') {
|
||||
if (*c == ' ') {
|
||||
if (touched) {
|
||||
newargv = realloc(curargv,sizeof(char*)*(argc+1));
|
||||
if (newargv == NULL) goto err;
|
||||
curargv = newargv;
|
||||
curargv[argc++] = curarg;
|
||||
totlen += bulklen(sdslen(curarg));
|
||||
|
||||
/* curarg is put in argv so it can be overwritten. */
|
||||
curarg = sdsempty();
|
||||
if (curarg == NULL) goto err;
|
||||
touched = 0;
|
||||
}
|
||||
} else {
|
||||
newarg = sdscatlen(curarg,c,1);
|
||||
if (newarg == NULL) goto err;
|
||||
curarg = newarg;
|
||||
touched = 1;
|
||||
}
|
||||
} else {
|
||||
char *arg;
|
||||
size_t size;
|
||||
|
||||
/* Set newarg so it can be checked even if it is not touched. */
|
||||
newarg = curarg;
|
||||
|
||||
switch(c[1]) {
|
||||
case 's':
|
||||
arg = va_arg(ap,char*);
|
||||
size = strlen(arg);
|
||||
if (size > 0)
|
||||
newarg = sdscatlen(curarg,arg,size);
|
||||
break;
|
||||
case 'b':
|
||||
arg = va_arg(ap,char*);
|
||||
size = va_arg(ap,size_t);
|
||||
if (size > 0)
|
||||
newarg = sdscatlen(curarg,arg,size);
|
||||
break;
|
||||
case '%':
|
||||
newarg = sdscat(curarg,"%");
|
||||
break;
|
||||
default:
|
||||
/* Try to detect printf format */
|
||||
{
|
||||
static const char int_fmts[] = "diouxX";
|
||||
static const char double_fmts[] = "eEfFgGaA";
|
||||
char _format[16];
|
||||
const char *_p = c+1;
|
||||
size_t _l = 0;
|
||||
va_list _cpy;
|
||||
|
||||
/* Flags */
|
||||
if (*_p != '\0' && *_p == '#') _p++;
|
||||
if (*_p != '\0' && *_p == '0') _p++;
|
||||
if (*_p != '\0' && *_p == '-') _p++;
|
||||
if (*_p != '\0' && *_p == ' ') _p++;
|
||||
if (*_p != '\0' && *_p == '+') _p++;
|
||||
|
||||
/* Field width */
|
||||
while (*_p != '\0' && isdigit(*_p)) _p++;
|
||||
|
||||
/* Precision */
|
||||
if (*_p == '.') {
|
||||
_p++;
|
||||
while (*_p != '\0' && isdigit(*_p)) _p++;
|
||||
}
|
||||
|
||||
/* Copy va_list before consuming with va_arg */
|
||||
va_copy(_cpy,ap);
|
||||
|
||||
/* Integer conversion (without modifiers) */
|
||||
if (strchr(int_fmts, *_p) != NULL) {
|
||||
va_arg(ap,int);
|
||||
goto fmt_valid;
|
||||
}
|
||||
|
||||
/* Double conversion (without modifiers) */
|
||||
if (strchr(double_fmts, *_p) != NULL) {
|
||||
va_arg(ap,double);
|
||||
goto fmt_valid;
|
||||
}
|
||||
|
||||
/* Size: char */
|
||||
if (_p[0] == 'h' && _p[1] == 'h') {
|
||||
_p += 2;
|
||||
if (*_p != '\0' && strchr(int_fmts, *_p) != NULL) {
|
||||
va_arg(ap,int); /* char gets promoted to int */
|
||||
goto fmt_valid;
|
||||
}
|
||||
goto fmt_invalid;
|
||||
}
|
||||
|
||||
/* Size: short */
|
||||
if (_p[0] == 'h') {
|
||||
_p += 1;
|
||||
if (*_p != '\0' && strchr(int_fmts, *_p) != NULL) {
|
||||
va_arg(ap,int); /* short gets promoted to int */
|
||||
goto fmt_valid;
|
||||
}
|
||||
goto fmt_invalid;
|
||||
}
|
||||
|
||||
/* Size: long long */
|
||||
if (_p[0] == 'l' && _p[1] == 'l') {
|
||||
_p += 2;
|
||||
if (*_p != '\0' && strchr(int_fmts, *_p) != NULL) {
|
||||
va_arg(ap,long long);
|
||||
goto fmt_valid;
|
||||
}
|
||||
goto fmt_invalid;
|
||||
}
|
||||
|
||||
/* Size: long */
|
||||
if (_p[0] == 'l') {
|
||||
_p += 1;
|
||||
if (*_p != '\0' && strchr(int_fmts, *_p) != NULL) {
|
||||
va_arg(ap,long);
|
||||
goto fmt_valid;
|
||||
}
|
||||
goto fmt_invalid;
|
||||
}
|
||||
|
||||
fmt_invalid:
|
||||
va_end(_cpy);
|
||||
goto err;
|
||||
|
||||
fmt_valid:
|
||||
_l = (_p+1)-c;
|
||||
if (_l < sizeof(_format)-2) {
|
||||
memcpy(_format,c,_l);
|
||||
_format[_l] = '\0';
|
||||
newarg = sdscatvprintf(curarg,_format,_cpy);
|
||||
|
||||
/* Update current position (note: outer blocks
|
||||
* increment c twice so compensate here) */
|
||||
c = _p-1;
|
||||
}
|
||||
|
||||
va_end(_cpy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newarg == NULL) goto err;
|
||||
curarg = newarg;
|
||||
|
||||
touched = 1;
|
||||
c++;
|
||||
}
|
||||
c++;
|
||||
}
|
||||
|
||||
/* Add the last argument if needed */
|
||||
if (touched) {
|
||||
newargv = realloc(curargv,sizeof(char*)*(argc+1));
|
||||
if (newargv == NULL) goto err;
|
||||
curargv = newargv;
|
||||
curargv[argc++] = curarg;
|
||||
totlen += bulklen(sdslen(curarg));
|
||||
} else {
|
||||
sdsfree(curarg);
|
||||
}
|
||||
|
||||
/* Clear curarg because it was put in curargv or was free'd. */
|
||||
curarg = NULL;
|
||||
|
||||
/* Add bytes needed to hold multi bulk count */
|
||||
totlen += 1+intlen(argc)+2;
|
||||
|
||||
/* Build the command at protocol level */
|
||||
cmd = malloc(totlen+1);
|
||||
if (cmd == NULL) goto err;
|
||||
|
||||
pos = sprintf(cmd,"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j]));
|
||||
memcpy(cmd+pos,curargv[j],sdslen(curargv[j]));
|
||||
pos += sdslen(curargv[j]);
|
||||
sdsfree(curargv[j]);
|
||||
cmd[pos++] = '\r';
|
||||
cmd[pos++] = '\n';
|
||||
}
|
||||
assert(pos == totlen);
|
||||
cmd[pos] = '\0';
|
||||
|
||||
free(curargv);
|
||||
*target = cmd;
|
||||
return totlen;
|
||||
|
||||
err:
|
||||
while(argc--)
|
||||
sdsfree(curargv[argc]);
|
||||
free(curargv);
|
||||
|
||||
if (curarg != NULL)
|
||||
sdsfree(curarg);
|
||||
|
||||
/* No need to check cmd since it is the last statement that can fail,
|
||||
* but do it anyway to be as defensive as possible. */
|
||||
if (cmd != NULL)
|
||||
free(cmd);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Format a command according to the Redis protocol. This function
|
||||
* takes a format similar to printf:
|
||||
*
|
||||
* %s represents a C null terminated string you want to interpolate
|
||||
* %b represents a binary safe string
|
||||
*
|
||||
* When using %b you need to provide both the pointer to the string
|
||||
* and the length in bytes. Examples:
|
||||
*
|
||||
* len = redisFormatCommand(target, "GET %s", mykey);
|
||||
* len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
|
||||
*/
|
||||
int redis_format_command(char **target, const char *format, ...) {
|
||||
va_list ap;
|
||||
int len;
|
||||
va_start(ap,format);
|
||||
len = redis_format_vcommand(target,format,ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Format a command according to the Redis protocol. This function takes the
|
||||
* number of arguments, an array with arguments and an array with their
|
||||
* lengths. If the latter is set to NULL, strlen will be used to compute the
|
||||
* argument lengths.
|
||||
*/
|
||||
int redis_format_command_argv(char **target, int argc, const char **argv, const size_t *argvlen) {
|
||||
char *cmd = NULL; /* final command */
|
||||
int pos; /* position in final command */
|
||||
size_t len;
|
||||
int totlen, j;
|
||||
|
||||
/* Calculate number of bytes needed for the command */
|
||||
totlen = 1+intlen(argc)+2;
|
||||
for (j = 0; j < argc; j++) {
|
||||
len = argvlen ? argvlen[j] : strlen(argv[j]);
|
||||
totlen += bulklen(len);
|
||||
}
|
||||
|
||||
/* Build the command at protocol level */
|
||||
cmd = malloc(totlen+1);
|
||||
if (cmd == NULL)
|
||||
return -1;
|
||||
|
||||
pos = sprintf(cmd,"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
len = argvlen ? argvlen[j] : strlen(argv[j]);
|
||||
pos += sprintf(cmd+pos,"$%zu\r\n",len);
|
||||
memcpy(cmd+pos,argv[j],len);
|
||||
pos += len;
|
||||
cmd[pos++] = '\r';
|
||||
cmd[pos++] = '\n';
|
||||
}
|
||||
assert(pos == totlen);
|
||||
cmd[pos] = '\0';
|
||||
|
||||
*target = cmd;
|
||||
return totlen;
|
||||
}
|
||||
10
src/format.h
Normal file
10
src/format.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef HIREDIS_FORMAT_H
|
||||
#define HIREDIS_FORMAT_H 1
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
int redis_format_vcommand(char **target, const char *format, va_list ap);
|
||||
int redis_format_command(char **target, const char *format, ...);
|
||||
int redis_format_command_argv(char **target, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
#endif
|
||||
288
src/handle.c
Normal file
288
src/handle.c
Normal file
@ -0,0 +1,288 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fd.h"
|
||||
#include "handle.h"
|
||||
#include "sds.h"
|
||||
|
||||
#define REDIS__READABLE 1
|
||||
#define REDIS__WRITABLE 2
|
||||
|
||||
int redis_handle_init(redis_handle *h) {
|
||||
h->fd = -1;
|
||||
h->timeout.tv_sec = 5;
|
||||
h->timeout.tv_usec = 0;
|
||||
redis_parser_init(&h->parser, NULL);
|
||||
h->wbuf = NULL;
|
||||
h->rbuf = NULL;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Associate timeout with handle. Only used in redis_handle_wait_* calls. */
|
||||
int redis_handle_set_timeout(redis_handle *h, unsigned long us) {
|
||||
struct timeval to;
|
||||
to.tv_sec = us / 1000000;
|
||||
to.tv_usec = us - (1000000 * to.tv_sec);
|
||||
h->timeout = to;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
unsigned long redis_handle_get_timeout(redis_handle *h) {
|
||||
return h->timeout.tv_sec * 1000000 + h->timeout.tv_usec;
|
||||
}
|
||||
|
||||
int redis_handle_close(redis_handle *h) {
|
||||
if (h->fd >= 0) {
|
||||
close(h->fd);
|
||||
h->fd = -1;
|
||||
}
|
||||
|
||||
if (h->wbuf) {
|
||||
sdsfree(h->wbuf);
|
||||
h->wbuf = NULL;
|
||||
}
|
||||
|
||||
if (h->rbuf) {
|
||||
sdsfree(h->rbuf);
|
||||
h->rbuf = NULL;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_handle_destroy(redis_handle *h) {
|
||||
redis_handle_close(h);
|
||||
redis_parser_destroy(&h->parser);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int redis__finish_connect(redis_handle *h, int fd) {
|
||||
h->fd = fd;
|
||||
h->wbuf = sdsempty();
|
||||
h->rbuf = sdsempty();
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_handle_connect_address(redis_handle *h, const redis_address addr) {
|
||||
int fd;
|
||||
|
||||
if (h->fd >= 0) {
|
||||
errno = EALREADY;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
fd = redis_fd_connect_address(addr);
|
||||
if (fd < 0) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
return redis__finish_connect(h, fd);
|
||||
}
|
||||
|
||||
int redis_handle_connect_in(redis_handle *h, struct sockaddr_in sa) {
|
||||
return redis_handle_connect_address(h, redis_address_from_in(sa));
|
||||
}
|
||||
|
||||
int redis_handle_connect_in6(redis_handle *h, struct sockaddr_in6 sa) {
|
||||
return redis_handle_connect_address(h, redis_address_from_in6(sa));
|
||||
}
|
||||
|
||||
int redis_handle_connect_un(redis_handle *h, struct sockaddr_un sa) {
|
||||
return redis_handle_connect_address(h, redis_address_from_un(sa));
|
||||
}
|
||||
|
||||
int redis_handle_connect_gai(redis_handle *h,
|
||||
int family,
|
||||
const char *addr,
|
||||
int port,
|
||||
redis_address *_addr) {
|
||||
int fd;
|
||||
|
||||
if (h->fd >= 0) {
|
||||
errno = EALREADY;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
fd = redis_fd_connect_gai(family, addr, port, _addr);
|
||||
if (fd < 0) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
return redis__finish_connect(h, fd);
|
||||
}
|
||||
|
||||
static int redis__poll(int mode, int fd, struct timeval timeout) {
|
||||
struct pollfd pfd;
|
||||
int msec;
|
||||
int rv;
|
||||
|
||||
pfd.fd = fd;
|
||||
pfd.events = 0;
|
||||
pfd.revents = 0;
|
||||
|
||||
if (mode & REDIS__READABLE) {
|
||||
pfd.events |= POLLIN;
|
||||
}
|
||||
|
||||
if (mode & REDIS__WRITABLE) {
|
||||
pfd.events |= POLLOUT;
|
||||
}
|
||||
|
||||
msec = (timeout.tv_sec * 1000) + ((timeout.tv_usec + 999) / 1000);
|
||||
if (msec < 0) {
|
||||
msec = 0;
|
||||
}
|
||||
|
||||
rv = poll(&pfd, 1, msec);
|
||||
if (rv == -1) {
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
/* No events means poll(2) timed out */
|
||||
if (rv == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
/* POLLERR is always bad */
|
||||
if (pfd.revents & POLLERR) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* POLLHUP is only bad when interested in POLLOUT.
|
||||
* When interested in POLLIN, reads can continue until EOF. */
|
||||
if ((pfd.revents & POLLHUP) && (pfd.events & POLLOUT)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
|
||||
error:
|
||||
rv = redis_fd_error(fd);
|
||||
if (rv < 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* We got POLLERR so the socket error MUST be non-zero */
|
||||
assert(rv && "Expected socket error");
|
||||
|
||||
/* Act as if the socket error occured */
|
||||
errno = rv;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
static int redis__wait(redis_handle *h, int mode) {
|
||||
int rv;
|
||||
|
||||
if (h->fd < 0) {
|
||||
errno = EINVAL;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
rv = redis__poll(mode, h->fd, h->timeout);
|
||||
if (rv < 0) {
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_handle_wait_connected(redis_handle *h) {
|
||||
return redis__wait(h, REDIS__WRITABLE);
|
||||
}
|
||||
|
||||
int redis_handle_wait_readable(redis_handle *h) {
|
||||
return redis__wait(h, REDIS__READABLE);
|
||||
}
|
||||
|
||||
int redis_handle_wait_writable(redis_handle *h) {
|
||||
return redis__wait(h, REDIS__WRITABLE);
|
||||
}
|
||||
|
||||
int redis_handle_write_from_buffer(redis_handle *h, int *drained) {
|
||||
int nwritten;
|
||||
|
||||
if (h->fd < 0) {
|
||||
errno = EINVAL;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
if (sdslen(h->wbuf)) {
|
||||
nwritten = redis_fd_write(h->fd, h->wbuf, sdslen(h->wbuf));
|
||||
if (nwritten < 0) {
|
||||
/* Let all errors bubble, including EAGAIN */
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
if (nwritten) {
|
||||
h->wbuf = sdsrange(h->wbuf, nwritten, -1);
|
||||
}
|
||||
}
|
||||
|
||||
if (drained) {
|
||||
*drained = (sdslen(h->wbuf) == 0);
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_handle_write_to_buffer(redis_handle *h, const char *buf, size_t len) {
|
||||
if (h->fd < 0) {
|
||||
errno = EINVAL;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
h->wbuf = sdscatlen(h->wbuf, buf, len);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_handle_read_to_buffer(redis_handle *h) {
|
||||
char buf[2048];
|
||||
int nread;
|
||||
|
||||
if (h->fd < 0) {
|
||||
errno = EINVAL;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
nread = redis_fd_read(h->fd, buf, sizeof(buf));
|
||||
if (nread < 0) {
|
||||
return nread;
|
||||
}
|
||||
|
||||
h->rbuf = sdscatlen(h->rbuf, buf, nread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int redis_handle_read_from_buffer(redis_handle *h, redis_protocol **p) {
|
||||
size_t navail, nparsed;
|
||||
|
||||
if (h->fd < 0) {
|
||||
errno = EINVAL;
|
||||
return REDIS_ESYS;
|
||||
}
|
||||
|
||||
assert(p != NULL);
|
||||
*p = NULL;
|
||||
|
||||
navail = sdslen(h->rbuf);
|
||||
if (navail) {
|
||||
nparsed = redis_parser_execute(&h->parser, p, h->rbuf, navail);
|
||||
|
||||
/* Trim read buffer */
|
||||
h->rbuf = sdsrange(h->rbuf, nparsed, -1);
|
||||
|
||||
/* Test for parse error */
|
||||
if (nparsed < navail && *p == NULL) {
|
||||
errno = redis_parser_err(&h->parser);
|
||||
return REDIS_EPARSER;
|
||||
}
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
42
src/handle.h
Normal file
42
src/handle.h
Normal file
@ -0,0 +1,42 @@
|
||||
#ifndef HIREDIS_HANDLE_H
|
||||
#define HIREDIS_HANDLE_H 1
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "address.h"
|
||||
#include "common.h"
|
||||
#include "parser.h"
|
||||
|
||||
typedef struct redis_handle_s redis_handle;
|
||||
|
||||
struct redis_handle_s {
|
||||
int fd;
|
||||
struct timeval timeout;
|
||||
redis_parser parser;
|
||||
char *wbuf;
|
||||
char *rbuf;
|
||||
};
|
||||
|
||||
int redis_handle_init(redis_handle *h);
|
||||
int redis_handle_close(redis_handle *h);
|
||||
int redis_handle_destroy(redis_handle *h);
|
||||
int redis_handle_set_timeout(redis_handle *h, unsigned long us);
|
||||
unsigned long redis_handle_get_timeout(redis_handle *h);
|
||||
|
||||
int redis_handle_connect_address(redis_handle *h, const redis_address addr);
|
||||
int redis_handle_connect_in(redis_handle *h, struct sockaddr_in sa);
|
||||
int redis_handle_connect_in6(redis_handle *h, struct sockaddr_in6 sa);
|
||||
int redis_handle_connect_un(redis_handle *h, struct sockaddr_un sa);
|
||||
int redis_handle_connect_gai(redis_handle *h, int family, const char *host, int port, redis_address *addr);
|
||||
|
||||
int redis_handle_wait_connected(redis_handle *h);
|
||||
int redis_handle_wait_readable(redis_handle *h);
|
||||
int redis_handle_wait_writable(redis_handle *h);
|
||||
|
||||
int redis_handle_write_from_buffer(redis_handle *h, int *drained);
|
||||
int redis_handle_write_to_buffer(redis_handle *h, const char *buf, size_t len);
|
||||
|
||||
int redis_handle_read_to_buffer(redis_handle *h);
|
||||
int redis_handle_read_from_buffer(redis_handle *h, redis_protocol **p);
|
||||
|
||||
#endif
|
||||
106
src/ngx-queue.h
Normal file
106
src/ngx-queue.h
Normal file
@ -0,0 +1,106 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_QUEUE_H_INCLUDED_
|
||||
#define _NGX_QUEUE_H_INCLUDED_
|
||||
|
||||
|
||||
typedef struct ngx_queue_s ngx_queue_t;
|
||||
|
||||
struct ngx_queue_s {
|
||||
ngx_queue_t *prev;
|
||||
ngx_queue_t *next;
|
||||
};
|
||||
|
||||
|
||||
#define ngx_queue_init(q) \
|
||||
(q)->prev = q; \
|
||||
(q)->next = q
|
||||
|
||||
|
||||
#define ngx_queue_empty(h) \
|
||||
(h == (h)->prev)
|
||||
|
||||
|
||||
#define ngx_queue_insert_head(h, x) \
|
||||
(x)->next = (h)->next; \
|
||||
(x)->next->prev = x; \
|
||||
(x)->prev = h; \
|
||||
(h)->next = x
|
||||
|
||||
|
||||
#define ngx_queue_insert_after ngx_queue_insert_head
|
||||
|
||||
|
||||
#define ngx_queue_insert_tail(h, x) \
|
||||
(x)->prev = (h)->prev; \
|
||||
(x)->prev->next = x; \
|
||||
(x)->next = h; \
|
||||
(h)->prev = x
|
||||
|
||||
|
||||
#define ngx_queue_head(h) \
|
||||
(h)->next
|
||||
|
||||
|
||||
#define ngx_queue_last(h) \
|
||||
(h)->prev
|
||||
|
||||
|
||||
#define ngx_queue_sentinel(h) \
|
||||
(h)
|
||||
|
||||
|
||||
#define ngx_queue_next(q) \
|
||||
(q)->next
|
||||
|
||||
|
||||
#define ngx_queue_prev(q) \
|
||||
(q)->prev
|
||||
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
|
||||
#define ngx_queue_remove(x) \
|
||||
(x)->next->prev = (x)->prev; \
|
||||
(x)->prev->next = (x)->next; \
|
||||
(x)->prev = NULL; \
|
||||
(x)->next = NULL
|
||||
|
||||
#else
|
||||
|
||||
#define ngx_queue_remove(x) \
|
||||
(x)->next->prev = (x)->prev; \
|
||||
(x)->prev->next = (x)->next
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#define ngx_queue_split(h, q, n) \
|
||||
(n)->prev = (h)->prev; \
|
||||
(n)->prev->next = n; \
|
||||
(n)->next = q; \
|
||||
(h)->prev = (q)->prev; \
|
||||
(h)->prev->next = h; \
|
||||
(q)->prev = n;
|
||||
|
||||
|
||||
#define ngx_queue_add(h, n) \
|
||||
(h)->prev->next = (n)->next; \
|
||||
(n)->next->prev = (h)->prev; \
|
||||
(h)->prev = (n)->prev; \
|
||||
(h)->prev->next = h;
|
||||
|
||||
|
||||
#define ngx_queue_data(q, type, link) \
|
||||
(type *) ((unsigned char *) q - offsetof(type, link))
|
||||
|
||||
|
||||
#define ngx_queue_foreach(q, h) \
|
||||
for ((q) = ngx_queue_head(h); (q) != (h); (q) = ngx_queue_next(q))
|
||||
|
||||
|
||||
#endif /* _NGX_QUEUE_H_INCLUDED_ */
|
||||
178
src/object.c
Normal file
178
src/object.c
Normal file
@ -0,0 +1,178 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "object.h"
|
||||
|
||||
void redis_object_free(redis_object **ptr) {
|
||||
redis_object *obj = *ptr;
|
||||
|
||||
switch(obj->type) {
|
||||
case REDIS_STRING:
|
||||
case REDIS_STATUS:
|
||||
case REDIS_ERROR:
|
||||
if (obj->str != NULL) {
|
||||
free(obj->str);
|
||||
obj->str = NULL;
|
||||
obj->len = 0;
|
||||
}
|
||||
break;
|
||||
case REDIS_ARRAY:
|
||||
if (obj->element != NULL) {
|
||||
unsigned int j;
|
||||
for (j = 0; j < obj->elements; j++) {
|
||||
if (obj->element[j] != NULL) {
|
||||
redis_object_free(&obj->element[j]);
|
||||
}
|
||||
}
|
||||
free(obj->element);
|
||||
obj->element = NULL;
|
||||
obj->elements = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
free(*ptr);
|
||||
*ptr = NULL;
|
||||
}
|
||||
|
||||
static redis_object *_object_create_from_protocol(redis_protocol *p) {
|
||||
redis_object *obj, *parent;
|
||||
|
||||
/* Create object when the callback was not fired before. */
|
||||
if (p->data == NULL) {
|
||||
obj = p->data = malloc(sizeof(redis_object));
|
||||
if (obj == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
obj->type = p->type;
|
||||
|
||||
obj->str = NULL;
|
||||
obj->len = 0;
|
||||
|
||||
obj->element = NULL;
|
||||
obj->elements = 0;
|
||||
|
||||
if (p->parent) {
|
||||
parent = (redis_object*)p->parent->data;
|
||||
assert(parent->type == REDIS_ARRAY);
|
||||
parent->element[p->parent->cursor] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
obj = (redis_object*)p->data;
|
||||
assert(obj && obj->type == p->type);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static int _object_string_cb(redis_parser *parser, redis_protocol *p, const char *buf, size_t len) {
|
||||
redis_object *self;
|
||||
char *dst;
|
||||
|
||||
((void)parser);
|
||||
|
||||
self = _object_create_from_protocol(p);
|
||||
if (self == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (self->type == REDIS_STRING) {
|
||||
assert(p->size >= 0);
|
||||
|
||||
dst = self->str;
|
||||
|
||||
/* The size is known upfront: allocate memory */
|
||||
if (dst == NULL) {
|
||||
dst = malloc(p->size+1);
|
||||
if (dst == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(p->size < 0);
|
||||
|
||||
/* The size is not known upfront: dynamically allocate memory */
|
||||
dst = realloc(self->str, self->len + len + 1);
|
||||
if (dst == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy provided buffer */
|
||||
memcpy(dst + self->len, buf, len);
|
||||
self->str = dst;
|
||||
self->len += len;
|
||||
self->str[self->len] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _object_array_cb(redis_parser *parser, redis_protocol *p, size_t len) {
|
||||
redis_object *self;
|
||||
|
||||
((void)parser);
|
||||
|
||||
self = _object_create_from_protocol(p);
|
||||
if (self == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
self->element = calloc(len, sizeof(redis_object*));
|
||||
if (self->element == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
self->elements = len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _object_integer_cb(redis_parser *parser, redis_protocol *p, int64_t value) {
|
||||
redis_object *self;
|
||||
|
||||
((void)parser);
|
||||
|
||||
self = _object_create_from_protocol(p);
|
||||
if (self == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
self->integer = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _object_nil_cb(redis_parser *parser, redis_protocol *p) {
|
||||
redis_object *self;
|
||||
|
||||
((void)parser);
|
||||
|
||||
self = _object_create_from_protocol(p);
|
||||
if (self == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _object_destroy_cb(redis_parser *parser, redis_protocol *p) {
|
||||
redis_object *self;
|
||||
|
||||
((void)parser);
|
||||
|
||||
self = p->data;
|
||||
redis_object_free(&self);
|
||||
}
|
||||
|
||||
/* Set of callbacks that can be passed to the parser. */
|
||||
redis_parser_callbacks redis_object_parser_callbacks = {
|
||||
.on_string = _object_string_cb,
|
||||
.on_array = _object_array_cb,
|
||||
.on_integer = _object_integer_cb,
|
||||
.on_nil = _object_nil_cb,
|
||||
.destroy = _object_destroy_cb
|
||||
};
|
||||
20
src/object.h
Normal file
20
src/object.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef HIREDIS_OBJECT_H
|
||||
#define HIREDIS_OBJECT_H 1
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
typedef struct redis_object_s redis_object;
|
||||
|
||||
struct redis_object_s {
|
||||
int type; /* Object type */
|
||||
int64_t integer; /* Value for REDIS_INTEGER */
|
||||
char *str; /* REDIS_STRING, REDIS_STATUS, REDIS_ERROR */
|
||||
unsigned int len; /* String length */
|
||||
struct redis_object_s **element; /* Actual elements in REDIS_ARRAY */
|
||||
unsigned int elements; /* Number of elements REDIS_ARRAY */
|
||||
};
|
||||
|
||||
void redis_object_free(redis_object **obj);
|
||||
extern redis_parser_callbacks redis_object_parser_callbacks;
|
||||
|
||||
#endif
|
||||
524
src/parser.c
Normal file
524
src/parser.c
Normal file
@ -0,0 +1,524 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
#define ERRNO(code) RPE_##code
|
||||
#define SET_ERRNO(code) do { \
|
||||
parser->err = ERRNO(code); \
|
||||
} while(0)
|
||||
|
||||
/* The redis_protocol argument to the callback function must be provided by
|
||||
* the caller because "ISO C99 requires rest arguments to be used". */
|
||||
#define CALLBACK(X, ...) do { \
|
||||
if (callbacks && callbacks->on_##X) { \
|
||||
if (callbacks->on_##X(parser, __VA_ARGS__) != 0) { \
|
||||
SET_ERRNO(CALLBACK); \
|
||||
goto error; \
|
||||
} \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define RESET_PROTOCOL(ptr) do { \
|
||||
redis_protocol *__tmp = (ptr); \
|
||||
__tmp->poff = 0; \
|
||||
__tmp->plen = 0; \
|
||||
__tmp->coff = 0; \
|
||||
__tmp->clen = 0; \
|
||||
__tmp->type = 0; \
|
||||
__tmp->size = -1; \
|
||||
__tmp->cursor = -1; \
|
||||
__tmp->data = NULL; \
|
||||
} while(0)
|
||||
|
||||
#define REDIS_PARSER_STATES(_X) \
|
||||
_X(unused) /* = 0 in enum */ \
|
||||
_X(type_char) \
|
||||
_X(integer_start) \
|
||||
_X(integer_pos_19) \
|
||||
_X(integer_pos_09) \
|
||||
_X(integer_neg_19) \
|
||||
_X(integer_neg_09) \
|
||||
_X(integer_cr) \
|
||||
_X(integer_lf) \
|
||||
_X(bulk) \
|
||||
_X(bulk_cr) \
|
||||
_X(bulk_lf) \
|
||||
_X(line) \
|
||||
_X(line_lf) \
|
||||
|
||||
#define _GEN(name) s_##name,
|
||||
enum state {
|
||||
REDIS_PARSER_STATES(_GEN)
|
||||
};
|
||||
#undef _GEN
|
||||
|
||||
#define _GEN(code, description) description,
|
||||
static const char *strerror_map[] = {
|
||||
REDIS_PARSER_ERRNO_MAP(_GEN)
|
||||
};
|
||||
#undef _GEN
|
||||
|
||||
#define STATE(st) \
|
||||
case s_##st: \
|
||||
l_##st: \
|
||||
state = s_##st; \
|
||||
if (pos >= end) { /* No more data */ \
|
||||
goto finalize; \
|
||||
}
|
||||
|
||||
#define ADVANCE(bytes) do { \
|
||||
pos += (bytes); nread += (bytes); \
|
||||
} while(0)
|
||||
|
||||
#define MOVE(st) do { \
|
||||
goto l_##st; \
|
||||
} while(0)
|
||||
|
||||
#define ADVANCE_AND_MOVE(st) do { \
|
||||
ADVANCE(1); \
|
||||
MOVE(st); \
|
||||
} while(0)
|
||||
|
||||
#ifdef DEBUG
|
||||
#define _ENUM_GEN(s) #s,
|
||||
static const char *state_str[] = {
|
||||
REDIS_PARSER_STATES(_ENUM_GEN)
|
||||
};
|
||||
#undef _ENUM_GEN
|
||||
|
||||
#define LOG(fmt, ...) do { \
|
||||
fprintf(stderr, fmt "\n", __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
/* Can hold 10 char representations per LOG call */
|
||||
static char _chrtos_buf[10][8];
|
||||
static int _chrtos_idx = 0;
|
||||
|
||||
static const char *chrtos(char byte) {
|
||||
char *buf = _chrtos_buf[_chrtos_idx++ %
|
||||
(sizeof(_chrtos_buf) / sizeof(_chrtos_buf[0]))];
|
||||
|
||||
switch(byte) {
|
||||
case '\\':
|
||||
case '"':
|
||||
sprintf(buf,"\\%c",byte);
|
||||
break;
|
||||
case '\n': sprintf(buf,"\\n"); break;
|
||||
case '\r': sprintf(buf,"\\r"); break;
|
||||
case '\t': sprintf(buf,"\\t"); break;
|
||||
case '\a': sprintf(buf,"\\a"); break;
|
||||
case '\b': sprintf(buf,"\\b"); break;
|
||||
default:
|
||||
if (isprint(byte))
|
||||
sprintf(buf,"%c",byte);
|
||||
else
|
||||
sprintf(buf,"\\x%02x",(unsigned char)byte);
|
||||
break;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
#else
|
||||
#define LOG(fmt, ...) do { ; } while (0)
|
||||
#endif
|
||||
|
||||
void redis_parser_init(redis_parser *parser, const redis_parser_callbacks *callbacks) {
|
||||
parser->stackidx = -1;
|
||||
parser->callbacks = callbacks;
|
||||
parser->err = 0;
|
||||
}
|
||||
|
||||
/* Execute the parser against len bytes in buf. When a full message was read,
|
||||
* the "dst" pointer is populated with the address of the root payload object.
|
||||
* This pointer is set to NULL when no full message could be parsed.
|
||||
*
|
||||
* This function returns the number of bytes that could be parsed. When no full
|
||||
* message was parsed and the return value is smaller than the number of bytes
|
||||
* that were available, an error occured and the parser should be
|
||||
* re-initialized before parsing more data.
|
||||
*/
|
||||
size_t redis_parser_execute(redis_parser *parser, redis_protocol **dst, const char *buf, size_t len) {
|
||||
redis_protocol *stack = parser->stack;
|
||||
const redis_parser_callbacks *callbacks = parser->callbacks;
|
||||
const char *pos;
|
||||
const char *end;
|
||||
size_t nread;
|
||||
int stackidx;
|
||||
unsigned char state;
|
||||
struct redis_parser_int64_s i64;
|
||||
redis_protocol *cur;
|
||||
|
||||
/* Abort immediately if the parser is in an error state. It should be
|
||||
* re-initialized before attempting to execute it with new data. */
|
||||
if (parser->err) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Reset destination */
|
||||
if (dst) *dst = NULL;
|
||||
|
||||
/* Reset root protocol object for new messages */
|
||||
if (parser->stackidx == -1) {
|
||||
RESET_PROTOCOL(&stack[0]);
|
||||
parser->nread = 0;
|
||||
parser->stackidx = 0;
|
||||
parser->state = s_type_char;
|
||||
}
|
||||
|
||||
pos = buf;
|
||||
end = buf+len;
|
||||
|
||||
nread = parser->nread;
|
||||
stackidx = parser->stackidx;
|
||||
state = parser->state;
|
||||
i64 = parser->i64;
|
||||
|
||||
while (pos < end && stackidx >= 0) {
|
||||
cur = &stack[stackidx];
|
||||
cur->parent = stackidx > 0 ? &stack[stackidx-1] : NULL;
|
||||
|
||||
switch (state) {
|
||||
STATE(type_char) {
|
||||
cur->poff = nread;
|
||||
|
||||
switch (*pos) {
|
||||
case '$':
|
||||
cur->type = REDIS_STRING;
|
||||
ADVANCE_AND_MOVE(integer_start);
|
||||
case '*':
|
||||
cur->type = REDIS_ARRAY;
|
||||
ADVANCE_AND_MOVE(integer_start);
|
||||
case ':':
|
||||
cur->type = REDIS_INTEGER;
|
||||
ADVANCE_AND_MOVE(integer_start);
|
||||
case '+':
|
||||
cur->type = REDIS_STATUS;
|
||||
cur->cursor = 0;
|
||||
ADVANCE_AND_MOVE(line);
|
||||
case '-':
|
||||
cur->type = REDIS_ERROR;
|
||||
cur->cursor = 0;
|
||||
ADVANCE_AND_MOVE(line);
|
||||
}
|
||||
|
||||
SET_ERRNO(INVALID_TYPE);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(integer_start) {
|
||||
char ch = *pos;
|
||||
i64.i64 = 0;
|
||||
i64.ui64 = 0;
|
||||
|
||||
/* Start unsigned, thus positive */
|
||||
if (ch >= '1' && ch <= '9') {
|
||||
i64.ui64 = ch - '0';
|
||||
ADVANCE_AND_MOVE(integer_pos_09);
|
||||
}
|
||||
|
||||
/* Start with negative sign */
|
||||
if (ch == '-') {
|
||||
ADVANCE_AND_MOVE(integer_neg_19);
|
||||
}
|
||||
|
||||
/* Start with positive sign */
|
||||
if (ch == '+') {
|
||||
ADVANCE_AND_MOVE(integer_pos_19);
|
||||
}
|
||||
|
||||
/* Single integer character is a zero */
|
||||
if (ch == '0') {
|
||||
ADVANCE_AND_MOVE(integer_cr);
|
||||
}
|
||||
|
||||
SET_ERRNO(INVALID_INT);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(integer_pos_19) {
|
||||
char ch = *pos;
|
||||
|
||||
if (ch >= '1' && ch <= '9') {
|
||||
i64.ui64 = ch - '0';
|
||||
ADVANCE_AND_MOVE(integer_pos_09);
|
||||
}
|
||||
|
||||
SET_ERRNO(INVALID_INT);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(integer_pos_09) {
|
||||
char ch = *pos;
|
||||
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
if (i64.ui64 > ((uint64_t)INT64_MAX / 10)) { /* Overflow */
|
||||
SET_ERRNO(OVERFLOW);
|
||||
goto error;
|
||||
}
|
||||
i64.ui64 *= 10;
|
||||
if (i64.ui64 > ((uint64_t)INT64_MAX - (ch - '0'))) { /* Overflow */
|
||||
SET_ERRNO(OVERFLOW);
|
||||
goto error;
|
||||
}
|
||||
i64.ui64 += ch - '0';
|
||||
ADVANCE_AND_MOVE(integer_pos_09);
|
||||
} else if (ch == '\r') {
|
||||
i64.i64 = i64.ui64;
|
||||
ADVANCE_AND_MOVE(integer_lf);
|
||||
}
|
||||
|
||||
SET_ERRNO(INVALID_INT);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(integer_neg_19) {
|
||||
char ch = *pos;
|
||||
|
||||
if (ch >= '1' && ch <= '9') {
|
||||
i64.ui64 = ch - '0';
|
||||
ADVANCE_AND_MOVE(integer_neg_09);
|
||||
}
|
||||
|
||||
SET_ERRNO(INVALID_INT);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(integer_neg_09) {
|
||||
char ch = *pos;
|
||||
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
if (i64.ui64 > (((uint64_t)-(INT64_MIN+1)+1) / 10)) { /* Overflow */
|
||||
SET_ERRNO(OVERFLOW);
|
||||
goto error;
|
||||
}
|
||||
i64.ui64 *= 10;
|
||||
if (i64.ui64 > (((uint64_t)-(INT64_MIN+1)+1) - (ch - '0'))) { /* Overflow */
|
||||
SET_ERRNO(OVERFLOW);
|
||||
goto error;
|
||||
}
|
||||
i64.ui64 += ch - '0';
|
||||
ADVANCE_AND_MOVE(integer_neg_09);
|
||||
} else if (ch == '\r') {
|
||||
i64.i64 = -i64.ui64;
|
||||
ADVANCE_AND_MOVE(integer_lf);
|
||||
}
|
||||
|
||||
SET_ERRNO(INVALID_INT);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(integer_cr) {
|
||||
if (*pos == '\r') {
|
||||
ADVANCE_AND_MOVE(integer_lf);
|
||||
}
|
||||
|
||||
SET_ERRNO(EXPECTED_CR);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(integer_lf) {
|
||||
if (*pos != '\n') {
|
||||
SET_ERRNO(EXPECTED_LF);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Protocol length can be set regardless of type */
|
||||
cur->plen = nread - cur->poff + 1; /* include \n */
|
||||
|
||||
if (cur->type == REDIS_STRING) {
|
||||
if (i64.i64 < 0) { /* nil bulk */
|
||||
cur->type = REDIS_NIL;
|
||||
CALLBACK(nil, cur);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Setup content offset and length */
|
||||
cur->coff = nread + 1; /* include \n */
|
||||
cur->clen = (unsigned)i64.i64;
|
||||
cur->plen += cur->clen + 2; /* include \r\n */
|
||||
|
||||
/* Store size of complete bulk */
|
||||
cur->size = (unsigned)i64.i64;
|
||||
cur->cursor = 0;
|
||||
ADVANCE_AND_MOVE(bulk);
|
||||
}
|
||||
|
||||
if (cur->type == REDIS_ARRAY) {
|
||||
if (i64.i64 < 0) { /* nil multi bulk */
|
||||
cur->type = REDIS_NIL;
|
||||
CALLBACK(nil, cur);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Store size of complete multi bulk */
|
||||
cur->size = (unsigned)i64.i64;
|
||||
cur->cursor = 0;
|
||||
CALLBACK(array, cur, cur->size);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (cur->type == REDIS_INTEGER) {
|
||||
/* Setup content offset and length */
|
||||
cur->coff = cur->poff + 1;
|
||||
cur->clen = nread - cur->coff - 1; /* remove \r */
|
||||
CALLBACK(integer, cur, i64.i64);
|
||||
goto done;
|
||||
}
|
||||
|
||||
assert(NULL && "unexpected object type in s_integer_lf");
|
||||
}
|
||||
|
||||
STATE(bulk) {
|
||||
size_t remaining = cur->size - cur->cursor;
|
||||
size_t available = (end-pos);
|
||||
|
||||
/* Everything can be read */
|
||||
if (remaining <= available) {
|
||||
CALLBACK(string, cur, pos, remaining);
|
||||
ADVANCE(remaining);
|
||||
MOVE(bulk_cr);
|
||||
}
|
||||
|
||||
/* Not everything can be read */
|
||||
CALLBACK(string, cur, pos, available);
|
||||
ADVANCE(available);
|
||||
|
||||
/* Add number of processed bytes to cursor */
|
||||
cur->cursor += available;
|
||||
goto finalize;
|
||||
}
|
||||
|
||||
STATE(bulk_cr) {
|
||||
if (*pos == '\r') {
|
||||
ADVANCE_AND_MOVE(bulk_lf);
|
||||
}
|
||||
|
||||
SET_ERRNO(EXPECTED_CR);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(bulk_lf) {
|
||||
if (*pos == '\n') {
|
||||
goto done;
|
||||
}
|
||||
|
||||
SET_ERRNO(EXPECTED_LF);
|
||||
goto error;
|
||||
}
|
||||
|
||||
STATE(line) {
|
||||
const char *mark = pos;
|
||||
|
||||
/* Remove tight loop and add function-wide "line mark" once
|
||||
* limits on line length are added. */
|
||||
while(pos < end) {
|
||||
if (*pos == '\r') {
|
||||
cur->coff = cur->poff + 1;
|
||||
cur->clen = nread - cur->coff;
|
||||
CALLBACK(string, cur, mark, pos-mark);
|
||||
ADVANCE_AND_MOVE(line_lf);
|
||||
}
|
||||
|
||||
ADVANCE(1);
|
||||
}
|
||||
|
||||
/* No more data */
|
||||
CALLBACK(string, cur, mark, pos-mark);
|
||||
|
||||
/* Add number of processed bytes to cursor */
|
||||
cur->cursor += pos-mark;
|
||||
goto finalize;
|
||||
}
|
||||
|
||||
STATE(line_lf) {
|
||||
if (*pos == '\n') {
|
||||
cur->plen = nread - cur->poff + 1; /* include \n */
|
||||
goto done;
|
||||
}
|
||||
|
||||
SET_ERRNO(EXPECTED_LF);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Transitions should be made from within the switch */
|
||||
assert(NULL && "invalid code path");
|
||||
|
||||
done:
|
||||
/* Message is done when root object is done */
|
||||
do {
|
||||
/* Move to nested object when we see an incomplete array */
|
||||
if (cur->type == REDIS_ARRAY && (cur->cursor < cur->size)) {
|
||||
RESET_PROTOCOL(&stack[++stackidx]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Aggregate plen for nested objects */
|
||||
if (stackidx > 0) {
|
||||
assert(stack[stackidx-1].type == REDIS_ARRAY);
|
||||
stack[stackidx-1].plen += cur->plen;
|
||||
stack[stackidx-1].cursor++;
|
||||
}
|
||||
|
||||
cur = &stack[--stackidx];
|
||||
} while (stackidx >= 0);
|
||||
|
||||
/* Always move back to start state */
|
||||
state = s_type_char;
|
||||
ADVANCE(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set destination pointer when full message was read */
|
||||
if (stackidx == -1) {
|
||||
if (dst) *dst = &stack[0];
|
||||
}
|
||||
|
||||
finalize:
|
||||
|
||||
parser->nread = nread;
|
||||
parser->stackidx = stackidx;
|
||||
parser->state = state;
|
||||
parser->i64 = i64;
|
||||
return pos-buf;
|
||||
|
||||
error:
|
||||
|
||||
if (parser->err == ERRNO(OK)) {
|
||||
SET_ERRNO(UNKNOWN);
|
||||
}
|
||||
|
||||
return pos-buf;
|
||||
}
|
||||
|
||||
void redis_parser_destroy(redis_parser *parser) {
|
||||
/* Only trigger destroy callback when the parser has unfinished
|
||||
* redis_protocol instances. */
|
||||
if (parser->stackidx >= 0 &&
|
||||
parser->callbacks &&
|
||||
parser->callbacks->destroy)
|
||||
{
|
||||
parser->callbacks->destroy(parser, redis_parser_root(parser));
|
||||
}
|
||||
}
|
||||
|
||||
redis_protocol *redis_parser_root(redis_parser *parser) {
|
||||
return &parser->stack[0];
|
||||
}
|
||||
|
||||
enum redis_parser_errno redis_parser_err(redis_parser *parser) {
|
||||
return parser->err;
|
||||
}
|
||||
|
||||
const char *redis_parser_strerror(enum redis_parser_errno err) {
|
||||
if (err < (sizeof(strerror_map)/sizeof(strerror_map[0])))
|
||||
return strerror_map[err];
|
||||
return NULL;
|
||||
}
|
||||
96
src/parser.h
Normal file
96
src/parser.h
Normal file
@ -0,0 +1,96 @@
|
||||
#ifndef HIREDIS_PARSER_H
|
||||
#define HIREDIS_PARSER_H 1
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Compat */
|
||||
#define REDIS_REPLY_STRING 1
|
||||
#define REDIS_REPLY_ARRAY 2
|
||||
#define REDIS_REPLY_INTEGER 3
|
||||
#define REDIS_REPLY_NIL 4
|
||||
#define REDIS_REPLY_STATUS 5
|
||||
#define REDIS_REPLY_ERROR 6
|
||||
|
||||
#define REDIS_STRING REDIS_REPLY_STRING
|
||||
#define REDIS_ARRAY REDIS_REPLY_ARRAY
|
||||
#define REDIS_INTEGER REDIS_REPLY_INTEGER
|
||||
#define REDIS_NIL REDIS_REPLY_NIL
|
||||
#define REDIS_STATUS REDIS_REPLY_STATUS
|
||||
#define REDIS_ERROR REDIS_REPLY_ERROR
|
||||
|
||||
typedef struct redis_parser_callbacks_s redis_parser_callbacks;
|
||||
typedef struct redis_protocol_s redis_protocol;
|
||||
typedef struct redis_parser_s redis_parser;
|
||||
|
||||
typedef int (*redis_parser_string_cb)(redis_parser *, redis_protocol *, const char *, size_t);
|
||||
typedef int (*redis_parser_array_cb)(redis_parser *, redis_protocol *, size_t);
|
||||
typedef int (*redis_parser_integer_cb)(redis_parser *, redis_protocol *, int64_t);
|
||||
typedef int (*redis_parser_nil_cb)(redis_parser *, redis_protocol *);
|
||||
typedef void (*redis_parser_destroy_cb)(redis_parser *, redis_protocol *);
|
||||
|
||||
struct redis_parser_callbacks_s {
|
||||
redis_parser_string_cb on_string;
|
||||
redis_parser_array_cb on_array;
|
||||
redis_parser_integer_cb on_integer;
|
||||
redis_parser_nil_cb on_nil;
|
||||
redis_parser_destroy_cb destroy;
|
||||
};
|
||||
|
||||
#define REDIS_PARSER_ERRNO_MAP(_X) \
|
||||
_X(OK, NULL) /* = 0 in enum */ \
|
||||
_X(UNKNOWN, "unknown") \
|
||||
_X(CALLBACK, "callback failed") \
|
||||
_X(INVALID_TYPE, "invalid type character") \
|
||||
_X(INVALID_INT, "invalid integer character") \
|
||||
_X(OVERFLOW, "overflow") \
|
||||
_X(EXPECTED_CR, "expected \\r") \
|
||||
_X(EXPECTED_LF, "expected \\n")
|
||||
|
||||
#define _REDIS_PARSER_ERRNO_GEN(code, description) RPE_##code,
|
||||
enum redis_parser_errno {
|
||||
REDIS_PARSER_ERRNO_MAP(_REDIS_PARSER_ERRNO_GEN)
|
||||
};
|
||||
#undef _REDIS_PARSER_ERRNO_GEN
|
||||
|
||||
struct redis_protocol_s {
|
||||
unsigned char type; /* payload type */
|
||||
void *data; /* payload data (to be populated by the callback functions) */
|
||||
const redis_protocol* parent; /* when nested, parent object */
|
||||
int size; /* size of complete bulk (bytes)/multi bulk (nested objects) */
|
||||
int cursor; /* number of processed bytes/nested objects */
|
||||
size_t poff; /* protocol offset */
|
||||
size_t plen; /* protocol length */
|
||||
size_t coff; /* content offset */
|
||||
size_t clen; /* content length */
|
||||
};
|
||||
|
||||
struct redis_parser_s {
|
||||
/* private: callbacks */
|
||||
const redis_parser_callbacks *callbacks;
|
||||
|
||||
/* private: number of consumed bytes for a single message */
|
||||
size_t nread;
|
||||
|
||||
/* private: protocol_t stack (multi-bulk, nested multi-bulk) */
|
||||
redis_protocol stack[3];
|
||||
int stackidx;
|
||||
|
||||
/* private: parser state */
|
||||
unsigned char state;
|
||||
enum redis_parser_errno err;
|
||||
|
||||
/* private: temporary integer (integer reply, bulk length) */
|
||||
struct redis_parser_int64_s {
|
||||
uint64_t ui64; /* accumulator */
|
||||
int64_t i64; /* result */
|
||||
} i64;
|
||||
};
|
||||
|
||||
void redis_parser_init(redis_parser *parser, const redis_parser_callbacks *callbacks);
|
||||
size_t redis_parser_execute(redis_parser *parser, redis_protocol **dst, const char *buf, size_t len);
|
||||
void redis_parser_destroy(redis_parser *parser);
|
||||
redis_protocol *redis_parser_root(redis_parser *parser);
|
||||
enum redis_parser_errno redis_parser_err(redis_parser *parser);
|
||||
const char *redis_parser_strerror(enum redis_parser_errno err);
|
||||
|
||||
#endif // _REDIS_PARSER_H
|
||||
230
src/request.c
Normal file
230
src/request.c
Normal file
@ -0,0 +1,230 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "handle.h"
|
||||
#include "object.h"
|
||||
#include "request.h"
|
||||
|
||||
int redis_request_init(redis_request *self) {
|
||||
memset(self, 0, sizeof(*self));
|
||||
ngx_queue_init(&self->wq);
|
||||
ngx_queue_init(&self->rq);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void redis_request_destroy(redis_request *self) {
|
||||
ngx_queue_remove(&self->rq);
|
||||
ngx_queue_remove(&self->wq);
|
||||
self->free(self);
|
||||
}
|
||||
|
||||
int redis_request_queue_init(redis_request_queue *self) {
|
||||
memset(self, 0, sizeof(*self));
|
||||
ngx_queue_init(&self->request_to_write);
|
||||
ngx_queue_init(&self->request_wait_write);
|
||||
ngx_queue_init(&self->request_wait_read);
|
||||
redis_parser_init(&self->parser, &redis_object_parser_callbacks);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redis_request_queue_destroy(redis_request_queue *self) {
|
||||
ngx_queue_t *q;
|
||||
redis_request *req;
|
||||
|
||||
while (!ngx_queue_empty(&self->request_wait_read)) {
|
||||
q = ngx_queue_last(&self->request_wait_read);
|
||||
req = ngx_queue_data(q, redis_request, rq);
|
||||
redis_request_destroy(req);
|
||||
}
|
||||
|
||||
while (!ngx_queue_empty(&self->request_wait_write)) {
|
||||
q = ngx_queue_last(&self->request_wait_write);
|
||||
req = ngx_queue_data(q, redis_request, wq);
|
||||
redis_request_destroy(req);
|
||||
}
|
||||
|
||||
while (!ngx_queue_empty(&self->request_to_write)) {
|
||||
q = ngx_queue_last(&self->request_to_write);
|
||||
req = ngx_queue_data(q, redis_request, wq);
|
||||
redis_request_destroy(req);
|
||||
}
|
||||
|
||||
redis_parser_destroy(&self->parser);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void redis_request_queue_write(redis_request_queue *self) {
|
||||
if (self->write_fn) {
|
||||
self->write_fn(self);
|
||||
}
|
||||
}
|
||||
|
||||
void redis_request_queue_start_read(redis_request_queue *self) {
|
||||
if (self->start_read_fn) {
|
||||
self->start_read_fn(self);
|
||||
}
|
||||
}
|
||||
|
||||
void redis_request_queue_stop_read(redis_request_queue *self) {
|
||||
if (self->stop_read_fn) {
|
||||
self->stop_read_fn(self);
|
||||
}
|
||||
}
|
||||
|
||||
void redis_request_queue_insert(redis_request_queue *self, redis_request *request) {
|
||||
request->request_queue = self;
|
||||
ngx_queue_insert_head(&self->request_to_write, &request->wq);
|
||||
|
||||
if (self->request_to_write_cb) {
|
||||
self->request_to_write_cb(self, request);
|
||||
}
|
||||
|
||||
/* Kickstart writes when this is the first pending write */
|
||||
self->pending_writes++;
|
||||
if (self->pending_writes == 1) {
|
||||
redis_request_queue_write(self);
|
||||
}
|
||||
}
|
||||
|
||||
int redis_request_queue_write_ptr(redis_request_queue *self, const char **dstbuf, size_t *dstlen) {
|
||||
ngx_queue_t *q = NULL;
|
||||
redis_request *req = NULL;
|
||||
const char *buf = NULL;
|
||||
size_t len = 0;
|
||||
int done = 0;
|
||||
|
||||
if (!ngx_queue_empty(&self->request_wait_write)) {
|
||||
q = ngx_queue_head(&self->request_wait_write);
|
||||
req = ngx_queue_data(q, redis_request, wq);
|
||||
}
|
||||
|
||||
/* Continue until write_ptr returned a non-empty buffer */
|
||||
while (len == 0) {
|
||||
|
||||
/* Make sure that `req` is assigned a non-done request */
|
||||
if (req == NULL || req->write_ptr_done) {
|
||||
if (ngx_queue_empty(&self->request_to_write)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
q = ngx_queue_last(&self->request_to_write);
|
||||
req = ngx_queue_data(q, redis_request, wq);
|
||||
|
||||
/* Remove from tail of `to_write`, insert on head of `wait_write` */
|
||||
ngx_queue_remove(q);
|
||||
ngx_queue_insert_head(&self->request_wait_write, q);
|
||||
|
||||
if (self->request_wait_write_cb) {
|
||||
self->request_wait_write_cb(self, req);
|
||||
}
|
||||
}
|
||||
|
||||
buf = NULL;
|
||||
len = 0;
|
||||
done = 0;
|
||||
|
||||
assert(req->write_ptr);
|
||||
req->write_ptr(req, &buf, &len, &done);
|
||||
req->write_ptr_len += len;
|
||||
|
||||
if (done) {
|
||||
req->write_ptr_done = 1;
|
||||
self->pending_writes--;
|
||||
}
|
||||
}
|
||||
|
||||
*dstbuf = buf;
|
||||
*dstlen = len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int redis_request_queue_write_cb(redis_request_queue *self, size_t len) {
|
||||
ngx_queue_t *q = NULL;
|
||||
redis_request *req = NULL;
|
||||
size_t nwritten;
|
||||
|
||||
while (len) {
|
||||
if (ngx_queue_empty(&self->request_wait_write)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
q = ngx_queue_last(&self->request_wait_write);
|
||||
req = ngx_queue_data(q, redis_request, wq);
|
||||
|
||||
/* Add this request to `wait_read` if necessary */
|
||||
if (ngx_queue_empty(&req->rq)) {
|
||||
ngx_queue_insert_head(&self->request_wait_read, &req->rq);
|
||||
|
||||
if (self->request_wait_read_cb) {
|
||||
self->request_wait_read_cb(self, req);
|
||||
}
|
||||
}
|
||||
|
||||
nwritten = req->write_ptr_len - req->write_cb_len;
|
||||
if (nwritten > len) {
|
||||
nwritten = len;
|
||||
}
|
||||
|
||||
assert(req->write_cb);
|
||||
req->write_cb(req, nwritten);
|
||||
req->write_cb_len += nwritten;
|
||||
|
||||
/* Remove this request from `wait_write` when done writing */
|
||||
if (req->write_ptr_done && req->write_ptr_len == req->write_cb_len) {
|
||||
ngx_queue_remove(q);
|
||||
ngx_queue_init(q);
|
||||
req->write_cb_done = 1;
|
||||
}
|
||||
|
||||
len -= nwritten;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int redis_request_queue_read_cb(redis_request_queue *self, const char *buf, size_t len) {
|
||||
ngx_queue_t *q = NULL;
|
||||
redis_request *req = NULL;
|
||||
redis_protocol *p = NULL;
|
||||
size_t nparsed = 0;
|
||||
int done;
|
||||
|
||||
while (len) {
|
||||
if (ngx_queue_empty(&self->request_wait_read)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
q = ngx_queue_last(&self->request_wait_read);
|
||||
req = ngx_queue_data(q, redis_request, rq);
|
||||
done = 0;
|
||||
|
||||
nparsed = redis_parser_execute(&self->parser, &p, buf, len);
|
||||
if (nparsed < len && p == NULL) {
|
||||
errno = redis_parser_err(&self->parser);
|
||||
return REDIS_EPARSER;
|
||||
}
|
||||
|
||||
assert(req->read_cb);
|
||||
req->read_cb(req, p, buf, nparsed, &done);
|
||||
req->read_cb_len += nparsed;
|
||||
|
||||
/* Request cannot be done on partial reply */
|
||||
assert(p || !done);
|
||||
|
||||
/* Update buffer */
|
||||
buf += nparsed;
|
||||
len -= nparsed;
|
||||
|
||||
/* Remove this request from `wait_read` when done reading */
|
||||
if (done) {
|
||||
ngx_queue_remove(q);
|
||||
ngx_queue_init(q);
|
||||
req->read_cb_done = 1;
|
||||
redis_request_destroy(req);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
154
src/request.h
Normal file
154
src/request.h
Normal file
@ -0,0 +1,154 @@
|
||||
#ifndef HIREDIS_REQUEST_H
|
||||
#define HIREDIS_REQUEST_H 1
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ngx-queue.h"
|
||||
#include "parser.h"
|
||||
|
||||
typedef struct redis_request_s redis_request;
|
||||
|
||||
typedef struct redis_request_queue_s redis_request_queue;
|
||||
|
||||
/*
|
||||
* Obtain a char* to a buffer representing (some part of) the request.
|
||||
*
|
||||
* Arguments:
|
||||
* self the request as previously inserted in the queue
|
||||
* buf buffer with request data
|
||||
* len length of the buffer with request data
|
||||
* done set to non-zero by the request when it doesn't have more ptrs
|
||||
*/
|
||||
typedef void (redis_request_write_ptr)(redis_request *self,
|
||||
const char **buf,
|
||||
size_t *len,
|
||||
int *done);
|
||||
|
||||
/*
|
||||
* Let the request know that (some part of) it has been written.
|
||||
*
|
||||
* Arguments:
|
||||
* self the request as previously inserted in the queue
|
||||
* n the number of bytes written
|
||||
*/
|
||||
typedef void (redis_request_write_cb)(redis_request *self, int n);
|
||||
|
||||
/*
|
||||
* Let the request know the wire-level data that was fed to the parser on its
|
||||
* behalf. This is merely a convenience function that can be used to buffer
|
||||
* the wire-level representation of the response, for example.
|
||||
*
|
||||
* Arguments:
|
||||
* self the request as previously inserted in the queue
|
||||
* buf buffer with reply data
|
||||
* len length of buffer with reply data
|
||||
* done set to non-zero by the request when it has been read in full
|
||||
*/
|
||||
typedef void (redis_request_read_cb)(redis_request *self,
|
||||
redis_protocol *p,
|
||||
const char *buf,
|
||||
size_t len,
|
||||
int *done);
|
||||
|
||||
/*
|
||||
* Free the request. This function is called after the last reply has been
|
||||
* read, and passed to the request via `read_cb`.
|
||||
|
||||
*
|
||||
* Arguments:
|
||||
* self the request as previously inserted in the queue
|
||||
*/
|
||||
typedef void (redis_request_free)(redis_request *self);
|
||||
|
||||
#define REDIS_REQUEST_COMMON \
|
||||
redis_request_queue *request_queue; \
|
||||
ngx_queue_t wq, rq; \
|
||||
unsigned write_ptr_done:1; \
|
||||
unsigned write_cb_done:1; \
|
||||
unsigned read_cb_done:1; \
|
||||
size_t write_ptr_len; \
|
||||
size_t write_cb_len; \
|
||||
size_t read_cb_len; \
|
||||
redis_request_write_ptr *write_ptr; \
|
||||
redis_request_write_cb *write_cb; \
|
||||
redis_request_read_cb *read_cb; \
|
||||
redis_request_free *free;
|
||||
|
||||
struct redis_request_s {
|
||||
REDIS_REQUEST_COMMON
|
||||
};
|
||||
|
||||
/*
|
||||
* These functions are called whenever a request is inserted in a new queue.
|
||||
* When a request is initially inserted via the `redis_request_queue_insert`
|
||||
* function, it ends up in the `to_write` queue and the `to_write_cb` callback
|
||||
* is called. Before the request emits one or more pointers to its buffers, it
|
||||
* is placed in the `wait_write` queue and the `wait_write_cb` callback is
|
||||
* called. Finally, when one or more bytes of the request have been put on the
|
||||
* wire, the request is placed in the `wait_read` queue and the `wait_read_cb`
|
||||
* callback is called.
|
||||
*
|
||||
* Arguments:
|
||||
* self request queue
|
||||
* request request that was moved to a new queue
|
||||
*/
|
||||
typedef void (redis_request_queue_to_write_cb)(redis_request_queue *self,
|
||||
redis_request *request);
|
||||
typedef void (redis_request_queue_wait_write_cb)(redis_request_queue *self,
|
||||
redis_request *request);
|
||||
typedef void (redis_request_queue_wait_read_cb)(redis_request_queue *self,
|
||||
redis_request *request);
|
||||
|
||||
/*
|
||||
* This type of function is called when I/O needs to happen. All requests that
|
||||
* a request queue takes in eventually need to be written to a socket. When a
|
||||
* request is inserted while the I/O library was not yet busy draining the
|
||||
* queue of requests to write, one of the I/O functions will be called to
|
||||
* _kickstart_ writes. After the kickstart the I/O library is supposed to
|
||||
* continue writing until it no longer can retrieve `char *`'s to write. After
|
||||
* that, it should be kickstarted by user code when user code stopped emitting
|
||||
* `char*`'s, or is automatically kickstarted when the queue of requests to
|
||||
* write was drained.
|
||||
*
|
||||
* Arguments:
|
||||
* self request queue
|
||||
*/
|
||||
|
||||
typedef void (redis_request_queue_io_fn)(redis_request_queue *self);
|
||||
|
||||
struct redis_request_queue_s {
|
||||
size_t pending_writes;
|
||||
|
||||
ngx_queue_t request_to_write;
|
||||
ngx_queue_t request_wait_write;
|
||||
ngx_queue_t request_wait_read;
|
||||
|
||||
redis_request_queue_to_write_cb *request_to_write_cb;
|
||||
redis_request_queue_wait_write_cb *request_wait_write_cb;
|
||||
redis_request_queue_wait_read_cb *request_wait_read_cb;
|
||||
|
||||
redis_request_queue_io_fn *write_fn;
|
||||
redis_request_queue_io_fn *start_read_fn;
|
||||
redis_request_queue_io_fn *stop_read_fn;
|
||||
|
||||
redis_parser parser;
|
||||
|
||||
void *data;
|
||||
};
|
||||
|
||||
int redis_request_init(redis_request *self);
|
||||
void redis_request_destroy(redis_request *self);
|
||||
|
||||
int redis_request_queue_init(redis_request_queue *self);
|
||||
int redis_request_queue_destroy(redis_request_queue *self);
|
||||
|
||||
void redis_request_queue_start_write(redis_request_queue *self);
|
||||
void redis_request_queue_start_read(redis_request_queue *self);
|
||||
void redis_request_queue_stop_read(redis_request_queue *self);
|
||||
|
||||
void redis_request_queue_insert(redis_request_queue *self, redis_request *request);
|
||||
int redis_request_queue_write_ptr(redis_request_queue *self, const char **buf, size_t *len);
|
||||
int redis_request_queue_write_cb(redis_request_queue *self, size_t len);
|
||||
int redis_request_queue_read_cb(redis_request_queue *self, const char *buf, size_t len);
|
||||
|
||||
#endif
|
||||
2
test/.gitignore
vendored
Normal file
2
test/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
test-*
|
||||
!test-*.[ch]
|
||||
30
test/Makefile
Normal file
30
test/Makefile
Normal file
@ -0,0 +1,30 @@
|
||||
include ../src/Makefile.common
|
||||
|
||||
STD= -std=c99 -pedantic -D_POSIX_C_SOURCE=200112L
|
||||
OPT= -O0
|
||||
WARN= -Wall -W
|
||||
HIREDIS_CFLAGS+= -I../src
|
||||
|
||||
TESTS=test-format test-parser test-object test-handle test-context test-request
|
||||
OBJ=spawn.o net-helper.o
|
||||
|
||||
all: $(TESTS)
|
||||
|
||||
../src/$(STLIBNAME):
|
||||
cd ../src && $(MAKE) $(STLIBNAME)
|
||||
|
||||
test-%: test-%.o $(OBJ) ../src/$(STLIBNAME)
|
||||
$(HIREDIS_LD) -o $@ $^ $(FINAL_LIBS)
|
||||
|
||||
.c.o:
|
||||
$(HIREDIS_CC) -c $<
|
||||
|
||||
clean:
|
||||
rm -f *.o $(TESTS)
|
||||
|
||||
-include ./Makefile.dep
|
||||
|
||||
dep:
|
||||
$(HIREDIS_CC) -MM *.c > Makefile.dep
|
||||
|
||||
.PHONY: all clean dep
|
||||
18
test/Makefile.dep
Normal file
18
test/Makefile.dep
Normal file
@ -0,0 +1,18 @@
|
||||
net-helper.o: net-helper.c net-helper.h ../src/handle.h ../src/address.h \
|
||||
../src/common.h ../src/parser.h
|
||||
spawn.o: spawn.c spawn.h
|
||||
test-context.o: test-context.c ../src/context.h ../src/handle.h \
|
||||
../src/address.h ../src/common.h ../src/parser.h net-helper.h \
|
||||
../src/object.h test-helper.h spawn.h
|
||||
test-format.o: test-format.c ../src/format.h test-helper.h \
|
||||
../src/handle.h ../src/address.h ../src/common.h ../src/parser.h \
|
||||
spawn.h
|
||||
test-handle.o: test-handle.c ../src/handle.h ../src/address.h \
|
||||
../src/common.h ../src/parser.h test-helper.h spawn.h
|
||||
test-object.o: test-object.c ../src/object.h ../src/parser.h \
|
||||
test-helper.h ../src/handle.h ../src/address.h ../src/common.h spawn.h
|
||||
test-parser.o: test-parser.c ../src/parser.h ../src/sds.h test-helper.h \
|
||||
../src/handle.h ../src/address.h ../src/common.h spawn.h
|
||||
test-request.o: test-request.c ../src/request.h ../src/ngx-queue.h \
|
||||
../src/parser.h test-helper.h ../src/handle.h ../src/address.h \
|
||||
../src/common.h spawn.h
|
||||
59
test/net-helper.c
Normal file
59
test/net-helper.c
Normal file
@ -0,0 +1,59 @@
|
||||
#include <errno.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "net-helper.h"
|
||||
|
||||
void run_server(void *ptr) {
|
||||
run_server_args *args = ptr;
|
||||
int family = args->address.sa_family;
|
||||
int fd;
|
||||
int rv;
|
||||
|
||||
fd = socket(family, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr, "socket: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (family == AF_INET || family == AF_INET6) {
|
||||
int on = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
fprintf(stderr, "setsockopt: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
rv = bind(fd, &args->address.sa_addr.addr, args->address.sa_addrlen);
|
||||
if (rv == -1) {
|
||||
fprintf(stderr, "bind: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
rv = listen(fd, 128);
|
||||
if (rv == -1) {
|
||||
fprintf(stderr, "listen: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
redis_address remote;
|
||||
|
||||
remote.sa_addrlen = sizeof(remote.sa_addr);
|
||||
|
||||
rv = accept(fd, &remote.sa_addr.addr, &remote.sa_addrlen);
|
||||
if (rv == -1) {
|
||||
fprintf(stderr, "accept: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Execute specified function, if given */
|
||||
if (args->fn.ptr != NULL) {
|
||||
args->fn.ptr(rv, args->fn.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
test/net-helper.h
Normal file
18
test/net-helper.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef TEST_NET_HELPER
|
||||
#define TEST_NET_HELPER 1
|
||||
|
||||
#include "handle.h"
|
||||
|
||||
typedef struct run_server_args_s run_server_args;
|
||||
|
||||
struct run_server_args_s {
|
||||
redis_address address;
|
||||
struct {
|
||||
void (*ptr)(int fd, void *data);
|
||||
void *data;
|
||||
} fn;
|
||||
};
|
||||
|
||||
void run_server(void *ptr);
|
||||
|
||||
#endif
|
||||
70
test/spawn.c
Normal file
70
test/spawn.c
Normal file
@ -0,0 +1,70 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "spawn.h"
|
||||
|
||||
int pidc = 0;
|
||||
pid_t *pidv = NULL;
|
||||
|
||||
void spawn_init(void) {
|
||||
assert(pidc == 0);
|
||||
assert(pidv == NULL);
|
||||
}
|
||||
|
||||
void spawn_destroy(void) {
|
||||
pid_t pid;
|
||||
int i;
|
||||
int rv;
|
||||
|
||||
/* Kill children */
|
||||
for (i = 0; i < pidc; i++) {
|
||||
int status;
|
||||
|
||||
pid = waitpid(pidv[i], &status, WNOHANG);
|
||||
if (pid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rv = kill(pidv[i], SIGKILL);
|
||||
if (rv == -1) {
|
||||
fprintf(stderr, "kill: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
pid = waitpid(pidv[i], &status, 0);
|
||||
assert(pid == pidv[i]);
|
||||
}
|
||||
|
||||
free(pidv);
|
||||
pidc = 0;
|
||||
pidv = NULL;
|
||||
}
|
||||
|
||||
void spawn(spawn_function *fn, void *ptr) {
|
||||
pid_t pid;
|
||||
|
||||
pid = fork();
|
||||
if (pid) {
|
||||
pidv = realloc(pidv, sizeof(pid_t) * (pidc + 1));
|
||||
pidv[pidc++] = pid;
|
||||
} else {
|
||||
fn(ptr);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
struct timespec ts;
|
||||
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 1000 * 1000;
|
||||
|
||||
/* Give the fork some time to boot... */
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
10
test/spawn.h
Normal file
10
test/spawn.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef TEST_SPAWN_H
|
||||
#define TEST_SPAWN_H 1
|
||||
|
||||
typedef void (spawn_function)(void *);
|
||||
|
||||
void spawn_init(void);
|
||||
void spawn_destroy(void);
|
||||
void spawn(spawn_function *fn, void *ptr);
|
||||
|
||||
#endif
|
||||
332
test/test-context.c
Normal file
332
test/test-context.c
Normal file
@ -0,0 +1,332 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "context.h"
|
||||
#include "net-helper.h"
|
||||
#include "object.h"
|
||||
#include "test-helper.h"
|
||||
|
||||
#define SETUP_CONNECT() \
|
||||
redis_context c; \
|
||||
int rv; \
|
||||
rv = redis_context_init(&c); \
|
||||
assert_equal_return(rv, REDIS_OK); \
|
||||
rv = redis_context_set_timeout(&c, 10000); \
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
|
||||
TEST(connect_in_refused) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
redis_address addr = redis_address_in("127.0.0.1", redis_port() + 1);
|
||||
rv = redis_context_connect_in(&c, addr.sa_addr.in);
|
||||
assert_equal_return(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ECONNREFUSED);
|
||||
|
||||
/* Address shouldn't be set when no connection could be made */
|
||||
assert_equal_int(c.address.sa_family, 0);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(connect_in6_refused) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
redis_address addr = redis_address_in6("::1", redis_port() + 1);
|
||||
rv = redis_context_connect_in6(&c, addr.sa_addr.in6);
|
||||
assert_equal_return(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ECONNREFUSED);
|
||||
|
||||
/* Address shouldn't be set when no connection could be made */
|
||||
assert_equal_int(c.address.sa_family, 0);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(connect_un_noent) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
redis_address addr = redis_address_un("/tmp/idontexist.sock");
|
||||
rv = redis_context_connect_un(&c, addr.sa_addr.un);
|
||||
assert_equal_return(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ENOENT);
|
||||
|
||||
/* Address shouldn't be set when no connection could be made */
|
||||
assert_equal_int(c.address.sa_family, 0);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(connect_timeout) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
redis_address addr = redis_address_in("10.255.255.254", redis_port());
|
||||
|
||||
long long t1, t2;
|
||||
t1 = usec();
|
||||
rv = redis_context_connect_in(&c, addr.sa_addr.in);
|
||||
t2 = usec();
|
||||
|
||||
assert_equal_return(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ETIMEDOUT);
|
||||
assert((t2 - t1) < 15000); /* 5ms of slack should be enough */
|
||||
|
||||
/* Address shouldn't be set when no connection could be made */
|
||||
assert_equal_int(c.address.sa_family, 0);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(connect_success) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
redis_address addr = redis_address_in("127.0.0.1", redis_port());
|
||||
rv = redis_context_connect_in(&c, addr.sa_addr.in);
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
|
||||
/* Address should be set when connection was made */
|
||||
assert_equal_int(c.address.sa_family, AF_INET);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(connect_gai_unknown_host) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
rv = redis_context_connect_gai(&c, "idontexist.foo", redis_port());
|
||||
assert_equal_return(rv, REDIS_EGAI);
|
||||
|
||||
/* Don't care about the specific error for now. */
|
||||
assert(1);
|
||||
|
||||
/* Address shouldn't be set when no connection could be made */
|
||||
assert_equal_int(c.address.sa_family, 0);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(connect_gai_timeout) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
long long t1, t2;
|
||||
t1 = usec();
|
||||
rv = redis_context_connect_gai(&c, "10.255.255.254", redis_port());
|
||||
t2 = usec();
|
||||
|
||||
assert_equal_return(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ETIMEDOUT);
|
||||
assert((t2 - t1) < 15000); /* 5ms of slack should be enough */
|
||||
|
||||
/* Address shouldn't be set when no connection could be made */
|
||||
assert_equal_int(c.address.sa_family, 0);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(connect_gai_success) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
rv = redis_context_connect_gai(&c, "localhost", redis_port());
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
|
||||
/* Address should be set when connection was made */
|
||||
assert_equal_int(c.address.sa_family, AF_INET);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
#define SETUP_CONNECTED() \
|
||||
SETUP_CONNECT(); \
|
||||
rv = redis_context_connect_gai(&c, "localhost", redis_port()); \
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
|
||||
static redis_object *read_from_context(redis_context *ctx) {
|
||||
redis_protocol *reply;
|
||||
int rv;
|
||||
|
||||
rv = redis_context_read(ctx, &reply);
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
|
||||
return (redis_object*)reply->data;
|
||||
}
|
||||
|
||||
static void compare_ok_status(redis_object *obj) {
|
||||
assert_equal_int(obj->type, REDIS_STATUS);
|
||||
assert_equal_string(obj->str, "OK");
|
||||
assert_equal_int(obj->len, 2);
|
||||
}
|
||||
|
||||
static void compare_string(redis_object *obj, const char *str, size_t len) {
|
||||
assert_equal_int(obj->type, REDIS_STRING);
|
||||
assert_equal_string(obj->str, str);
|
||||
assert_equal_int(obj->len, len);
|
||||
}
|
||||
|
||||
TEST(write_command) {
|
||||
SETUP_CONNECTED();
|
||||
|
||||
rv = redis_context_write_command(&c, "set foo bar");
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
compare_ok_status(read_from_context(&c));
|
||||
|
||||
/* Check that the command was executed as intended */
|
||||
rv = redis_context_write_command(&c, "get foo");
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
compare_string(read_from_context(&c), "bar", 3);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(write_command_argv) {
|
||||
SETUP_CONNECTED();
|
||||
|
||||
int argc1 = 3;
|
||||
const char *argv1[] = { "set", "foo", "bar" };
|
||||
size_t argvlen1[] = { 3, 3, 3 };
|
||||
rv = redis_context_write_command_argv(&c, argc1, argv1, argvlen1);
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
compare_ok_status(read_from_context(&c));
|
||||
|
||||
/* Check that the command was executed as intended */
|
||||
int argc2 = 2;
|
||||
const char *argv2[] = { "get", argv1[1] };
|
||||
size_t argvlen2[] = { 3, argvlen1[1] };
|
||||
rv = redis_context_write_command_argv(&c, argc2, argv2, argvlen2);
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
compare_string(read_from_context(&c), argv1[2], argvlen1[2]);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(call_command) {
|
||||
SETUP_CONNECTED();
|
||||
|
||||
redis_protocol *reply;
|
||||
|
||||
rv = redis_context_call_command(&c, &reply, "set foo bar");
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
compare_ok_status(reply->data);
|
||||
|
||||
/* Check that the command was executed as intended */
|
||||
rv = redis_context_call_command(&c, &reply, "get foo");
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
compare_string(reply->data, "bar", 3);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
TEST(call_command_argv) {
|
||||
SETUP_CONNECTED();
|
||||
|
||||
redis_protocol *reply;
|
||||
|
||||
int argc1 = 3;
|
||||
const char *argv1[] = { "set", "foo", "bar" };
|
||||
size_t argvlen1[] = { 3, 3, 3 };
|
||||
rv = redis_context_call_command_argv(&c, &reply, argc1, argv1, argvlen1);
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
compare_ok_status(reply->data);
|
||||
|
||||
/* Check that the command was executed as intended */
|
||||
int argc2 = 2;
|
||||
const char *argv2[] = { "get", argv1[1] };
|
||||
size_t argvlen2[] = { 3, argvlen1[1] };
|
||||
rv = redis_context_call_command_argv(&c, &reply, argc2, argv2, argvlen2);
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
compare_string(reply->data, argv1[2], argvlen1[2]);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
void run_server_ignore_connection(int fd, void *data) {
|
||||
((void) fd);
|
||||
((void) data);
|
||||
}
|
||||
|
||||
TEST(flush_against_full_kernel_buffer) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
redis_address addr = redis_address_in("127.0.0.1", redis_port() + 1);
|
||||
|
||||
run_server_args args;
|
||||
args.address = addr;
|
||||
args.fn.ptr = run_server_ignore_connection;
|
||||
|
||||
spawn(run_server, &args);
|
||||
|
||||
rv = redis_context_connect_in(&c, addr.sa_addr.in);
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
|
||||
/* Now write and flush until error */
|
||||
while (1) {
|
||||
rv = redis_context_write_command(&c, "ping");
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
|
||||
rv = redis_context_flush(&c);
|
||||
if (rv != REDIS_OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* When the write buffer cannot be flushed, the operation should time out
|
||||
* instead of directly returning EAGAIN to the caller */
|
||||
assert_equal_return(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ETIMEDOUT);
|
||||
}
|
||||
|
||||
void run_server_close_after_accept(int fd, void *data) {
|
||||
((void) data);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(read_against_closed_connection) {
|
||||
SETUP_CONNECT();
|
||||
|
||||
redis_address addr = redis_address_in("127.0.0.1", redis_port() + 1);
|
||||
|
||||
run_server_args args;
|
||||
args.address = addr;
|
||||
args.fn.ptr = run_server_close_after_accept;
|
||||
|
||||
spawn(run_server, &args);
|
||||
|
||||
rv = redis_context_connect_in(&c, addr.sa_addr.in);
|
||||
assert_equal_return(rv, REDIS_OK);
|
||||
|
||||
redis_protocol *reply = NULL;
|
||||
|
||||
/* Read should immediately EOF */
|
||||
rv = redis_context_read(&c, &reply);
|
||||
assert_equal_return(rv, REDIS_EEOF);
|
||||
|
||||
/* ... and should be idempotent... */
|
||||
rv = redis_context_read(&c, &reply);
|
||||
assert_equal_return(rv, REDIS_EEOF);
|
||||
|
||||
redis_context_destroy(&c);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_connect_in_refused();
|
||||
test_connect_in6_refused();
|
||||
test_connect_un_noent();
|
||||
test_connect_timeout();
|
||||
test_connect_success();
|
||||
test_connect_gai_unknown_host();
|
||||
test_connect_gai_timeout();
|
||||
test_connect_gai_success();
|
||||
|
||||
test_write_command();
|
||||
test_write_command_argv();
|
||||
test_call_command();
|
||||
test_call_command_argv();
|
||||
test_flush_against_full_kernel_buffer();
|
||||
test_read_against_closed_connection();
|
||||
}
|
||||
146
test/test-format.c
Normal file
146
test/test-format.c
Normal file
@ -0,0 +1,146 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "test-helper.h"
|
||||
|
||||
#define SETUP() \
|
||||
char *cmd; \
|
||||
int len;
|
||||
|
||||
TEST(format_without_interpolation) {
|
||||
SETUP();
|
||||
|
||||
len = redis_format_command(&cmd, "SET foo bar");
|
||||
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n", len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
TEST(format_with_string_interpolation) {
|
||||
SETUP();
|
||||
|
||||
/* Regular */
|
||||
len = redis_format_command(&cmd, "SET %s %s", "foo", "bar");
|
||||
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n", len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
/* With empty string */
|
||||
len = redis_format_command(&cmd, "SET %s %s", "foo", "");
|
||||
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n", len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||
free(cmd);
|
||||
|
||||
/* Empty string between non-empty args */
|
||||
len = redis_format_command(&cmd, "SET %s %s", "", "bar");
|
||||
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nbar\r\n", len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(0+2)+4+(3+2));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
TEST(format_with_binary_interpolation) {
|
||||
SETUP();
|
||||
|
||||
/* Regular */
|
||||
len = redis_format_command(&cmd, "SET %b %b", "f\0o", 3, "b\0r", 3);
|
||||
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nf\0o\r\n$3\r\nb\0r\r\n", len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
/* With empty string */
|
||||
len = redis_format_command(&cmd, "SET %b %b", "f\0o", 3, "", 0);
|
||||
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$3\r\nf\0o\r\n$0\r\n\r\n", len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||
free(cmd);
|
||||
|
||||
/* Empty string between non-empty args */
|
||||
len = redis_format_command(&cmd, "SET %b %b", "", 0, "b\0r", 3);
|
||||
assert(strncmp(cmd, "*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nb\0r\r\n", len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(0+2)+4+(3+2));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
TEST(format_with_literal_percent) {
|
||||
SETUP();
|
||||
|
||||
len = redis_format_command(&cmd,"SET %% %%");
|
||||
assert(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(1+2)+4+(1+2));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
TEST(format_with_printf_format) {
|
||||
SETUP();
|
||||
|
||||
/* Vararg width depends on the type. These tests make sure that the
|
||||
* width is correctly determined using the format and subsequent varargs
|
||||
* can correctly be interpolated. */
|
||||
#define INTEGER_WIDTH_TEST(fmt, type) do { \
|
||||
type value = 123; \
|
||||
len = redis_format_command(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
|
||||
assert(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
|
||||
len == 4+5+(12+2)+4+(9+2)); \
|
||||
free(cmd); \
|
||||
} while(0)
|
||||
|
||||
#define FLOAT_WIDTH_TEST(type) do { \
|
||||
type value = 123.0; \
|
||||
len = redis_format_command(&cmd,"key:%08.3f str:%s", value, "hello"); \
|
||||
assert(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
|
||||
len == 4+5+(12+2)+4+(9+2)); \
|
||||
free(cmd); \
|
||||
} while(0)
|
||||
|
||||
INTEGER_WIDTH_TEST("d", int);
|
||||
INTEGER_WIDTH_TEST("hhd", char);
|
||||
INTEGER_WIDTH_TEST("hd", short);
|
||||
INTEGER_WIDTH_TEST("ld", long);
|
||||
INTEGER_WIDTH_TEST("lld", long long);
|
||||
INTEGER_WIDTH_TEST("u", unsigned int);
|
||||
INTEGER_WIDTH_TEST("hhu", unsigned char);
|
||||
INTEGER_WIDTH_TEST("hu", unsigned short);
|
||||
INTEGER_WIDTH_TEST("lu", unsigned long);
|
||||
INTEGER_WIDTH_TEST("llu", unsigned long long);
|
||||
FLOAT_WIDTH_TEST(float);
|
||||
FLOAT_WIDTH_TEST(double);
|
||||
}
|
||||
|
||||
TEST(format_with_invalid_printf_format) {
|
||||
SETUP();
|
||||
|
||||
len = redis_format_command(&cmd,"key:%08p %b",1234,"foo",3);
|
||||
assert(len == -1);
|
||||
}
|
||||
|
||||
TEST(format_argv) {
|
||||
SETUP();
|
||||
|
||||
int argc = 3;
|
||||
const char *argv[3] = { "SET", "foo\0xxx", "bar" };
|
||||
size_t lens[3] = { 3, 7, 3 };
|
||||
|
||||
/* Without argument length */
|
||||
len = redis_format_command_argv(&cmd,argc,argv,NULL);
|
||||
assert(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
/* With argument length */
|
||||
len = redis_format_command_argv(&cmd,argc,argv,lens);
|
||||
assert(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_format_without_interpolation();
|
||||
test_format_with_string_interpolation();
|
||||
test_format_with_binary_interpolation();
|
||||
test_format_with_literal_percent();
|
||||
test_format_with_printf_format();
|
||||
test_format_with_invalid_printf_format();
|
||||
test_format_argv();
|
||||
}
|
||||
265
test/test-handle.c
Normal file
265
test/test-handle.c
Normal file
@ -0,0 +1,265 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "handle.h"
|
||||
#include "test-helper.h"
|
||||
|
||||
TEST(connect_in_refused) {
|
||||
redis_handle h;
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(&h);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
struct sockaddr_in sa;
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(redis_port() + 1);
|
||||
assert(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr) == 1);
|
||||
|
||||
rv = redis_handle_connect_in(&h, sa);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
rv = redis_handle_wait_connected(&h);
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ECONNREFUSED);
|
||||
|
||||
redis_handle_destroy(&h);
|
||||
}
|
||||
|
||||
TEST(connect_in6_refused) {
|
||||
redis_handle h;
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(&h);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
struct sockaddr_in6 sa;
|
||||
sa.sin6_family = AF_INET6;
|
||||
sa.sin6_port = htons(redis_port() + 1);
|
||||
assert(inet_pton(AF_INET6, "::1", &sa.sin6_addr) == 1);
|
||||
|
||||
rv = redis_handle_connect_in6(&h, sa);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
rv = redis_handle_wait_connected(&h);
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ECONNREFUSED);
|
||||
|
||||
redis_handle_destroy(&h);
|
||||
}
|
||||
|
||||
TEST(connect_un_noent) {
|
||||
redis_handle h;
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(&h);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
struct sockaddr_un sa;
|
||||
sa.sun_family = AF_UNIX;
|
||||
strcpy((char*)&sa.sun_path, "/tmp/idontexist.sock");
|
||||
|
||||
rv = redis_handle_connect_un(&h, sa);
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ENOENT);
|
||||
|
||||
redis_handle_destroy(&h);
|
||||
}
|
||||
|
||||
TEST(connect_timeout) {
|
||||
redis_handle h;
|
||||
long long t1, t2;
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(&h);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
struct sockaddr_in sa;
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(redis_port());
|
||||
assert(inet_pton(AF_INET, "10.255.255.254", &sa.sin_addr) == 1);
|
||||
|
||||
rv = redis_handle_set_timeout(&h, 10000);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
rv = redis_handle_connect_in(&h, sa);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
t1 = usec();
|
||||
rv = redis_handle_wait_connected(&h);
|
||||
t2 = usec();
|
||||
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ETIMEDOUT);
|
||||
assert((t2 - t1) < 15000); /* 5ms of slack should be enough */
|
||||
|
||||
redis_handle_destroy(&h);
|
||||
}
|
||||
|
||||
TEST(connect_gai_unknown_host) {
|
||||
redis_handle h;
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(&h);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
rv = redis_handle_connect_gai(&h, AF_INET, "idontexist.foo", redis_port(), NULL);
|
||||
assert_equal_int(rv, REDIS_EGAI);
|
||||
/* Don't care about the specific error for now. */
|
||||
|
||||
redis_handle_destroy(&h);
|
||||
}
|
||||
|
||||
TEST(connect_gai_success) {
|
||||
redis_handle h;
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(&h);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
rv = redis_handle_connect_gai(&h, AF_INET, "localhost", redis_port(), NULL);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
rv = redis_handle_wait_connected(&h);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
redis_handle_destroy(&h);
|
||||
}
|
||||
|
||||
TEST(connect_gai_redis_address) {
|
||||
redis_handle h;
|
||||
redis_address address;
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(&h);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
rv = redis_handle_connect_gai(&h, AF_INET, "localhost", redis_port(), &address);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
/* Match common fields */
|
||||
assert(address.sa_family == AF_INET);
|
||||
assert(address.sa_addrlen == sizeof(struct sockaddr_in));
|
||||
|
||||
/* Match sockaddr specific fields */
|
||||
assert(address.sa_addr.in.sin_family == AF_INET);
|
||||
assert(ntohs(address.sa_addr.in.sin_port) == redis_port());
|
||||
assert(strcmp(inet_ntoa(address.sa_addr.in.sin_addr), "127.0.0.1") == 0);
|
||||
|
||||
redis_handle_destroy(&h);
|
||||
}
|
||||
|
||||
redis_handle *setup(void) {
|
||||
redis_handle *h = malloc(sizeof(*h));
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_init(h);
|
||||
assert(rv == REDIS_OK);
|
||||
rv = redis_handle_set_timeout(h, 10000);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
rv = redis_handle_connect_gai(h, AF_INET, "localhost", redis_port(), NULL);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
rv = redis_handle_wait_connected(h);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
void teardown(redis_handle *h) {
|
||||
redis_handle_destroy(h);
|
||||
free(h);
|
||||
}
|
||||
|
||||
TEST(eof_after_quit) {
|
||||
redis_handle *h = setup();
|
||||
int rv, drained;
|
||||
|
||||
rv = redis_handle_write_to_buffer(h, "quit\r\n", 6);
|
||||
assert(rv == REDIS_OK);
|
||||
rv = redis_handle_write_from_buffer(h, &drained);
|
||||
assert(rv == REDIS_OK && drained);
|
||||
rv = redis_handle_wait_readable(h);
|
||||
assert(rv == REDIS_OK);
|
||||
rv = redis_handle_read_to_buffer(h);
|
||||
assert(rv == REDIS_OK);
|
||||
|
||||
redis_protocol *p;
|
||||
rv = redis_handle_read_from_buffer(h, &p);
|
||||
assert(rv == REDIS_OK);
|
||||
assert_equal_int(p->type, REDIS_STATUS);
|
||||
assert_equal_int(p->plen, 5); /* +ok\r\n */
|
||||
|
||||
/* wait_readable should return REDIS_OK because EOF is readable */
|
||||
rv = redis_handle_wait_readable(h);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
/* 1: read EOF */
|
||||
rv = redis_handle_read_to_buffer(h);
|
||||
assert_equal_int(rv, REDIS_EEOF);
|
||||
|
||||
/* 2: reading EOF should be idempotent (user is responsible for closing the handle) */
|
||||
rv = redis_handle_read_to_buffer(h);
|
||||
assert_equal_int(rv, REDIS_EEOF);
|
||||
|
||||
teardown(h);
|
||||
}
|
||||
|
||||
TEST(read_timeout) {
|
||||
redis_handle *h = setup();
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_wait_readable(h);
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, ETIMEDOUT);
|
||||
|
||||
teardown(h);
|
||||
}
|
||||
|
||||
TEST(einval_against_closed_handle) {
|
||||
redis_handle *h = setup();
|
||||
int rv;
|
||||
|
||||
rv = redis_handle_close(h);
|
||||
assert_equal_int(rv, REDIS_OK);
|
||||
|
||||
rv = redis_handle_write_to_buffer(h, "ping\r\n", 6);
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, EINVAL);
|
||||
|
||||
int drained;
|
||||
rv = redis_handle_write_from_buffer(h, &drained);
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, EINVAL);
|
||||
|
||||
rv = redis_handle_read_to_buffer(h);
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, EINVAL);
|
||||
|
||||
redis_protocol *p;
|
||||
rv = redis_handle_read_from_buffer(h, &p);
|
||||
assert_equal_int(rv, REDIS_ESYS);
|
||||
assert_equal_int(errno, EINVAL);
|
||||
|
||||
teardown(h);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_connect_in_refused();
|
||||
test_connect_in6_refused();
|
||||
test_connect_un_noent();
|
||||
test_connect_timeout();
|
||||
test_connect_gai_unknown_host();
|
||||
test_connect_gai_success();
|
||||
test_connect_gai_redis_address();
|
||||
|
||||
test_eof_after_quit();
|
||||
test_read_timeout();
|
||||
test_einval_against_closed_handle();
|
||||
}
|
||||
149
test/test-helper.h
Normal file
149
test/test-helper.h
Normal file
@ -0,0 +1,149 @@
|
||||
#ifndef _TEST_HELPER_H
|
||||
#define _TEST_HELPER_H 1
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "handle.h"
|
||||
#include "spawn.h"
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define UNUSED __attribute__ ((unused))
|
||||
#else
|
||||
#define UNUSED
|
||||
#endif
|
||||
|
||||
/******************************************************************************/
|
||||
/* ASSERTS ********************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
#define assert_equal(a, b, type, fmt) do { \
|
||||
type a_ = (a); \
|
||||
type b_ = (b); \
|
||||
if (a_ != b_) { \
|
||||
fprintf(stderr, \
|
||||
"%s:%d: " fmt " != " fmt "\n", \
|
||||
__FILE__, __LINE__, a_, b_); \
|
||||
assert(0); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define assert_equal_int(a,b) do { \
|
||||
assert_equal(a, b, int, "%d"); \
|
||||
} while(0)
|
||||
|
||||
#define assert_equal_size_t(a,b) do { \
|
||||
assert_equal(a, b, size_t, "%lu"); \
|
||||
} while(0)
|
||||
|
||||
/* Use long long to avoid compiler warnings about the printf format. */
|
||||
#define assert_equal_int64_t(a,b) do { \
|
||||
assert_equal(a, b, long long, "%lld"); \
|
||||
} while(0)
|
||||
|
||||
#define assert_equal_double(a,b) do { \
|
||||
assert_equal(a, b, double, "%f"); \
|
||||
} while(0)
|
||||
|
||||
#define assert_equal_string(a,b) do { \
|
||||
if (strcmp(a, b)) { \
|
||||
fprintf(stderr, \
|
||||
"%s:%d: %s != %s\n", \
|
||||
__FILE__, __LINE__, a, b); \
|
||||
assert(0); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ERR_TO_STR_OFFSET 4
|
||||
UNUSED static const char *err_to_str[] = {
|
||||
[REDIS_OK + ERR_TO_STR_OFFSET] = "REDIS_OK",
|
||||
[REDIS_ESYS + ERR_TO_STR_OFFSET] = "REDIS_ESYS",
|
||||
[REDIS_EGAI + ERR_TO_STR_OFFSET] = "REDIS_EGAI",
|
||||
[REDIS_EPARSER + ERR_TO_STR_OFFSET] = "REDIS_EPARSER",
|
||||
[REDIS_EEOF + ERR_TO_STR_OFFSET] = "REDIS_EEOF"
|
||||
};
|
||||
|
||||
UNUSED static void assert_equal_return_failure(int actual, int expected, const char *file, int line) {
|
||||
const char *actualstr = err_to_str[actual + ERR_TO_STR_OFFSET];
|
||||
const char *expectedstr = err_to_str[expected + ERR_TO_STR_OFFSET];
|
||||
char reason[128];
|
||||
|
||||
switch (actual) {
|
||||
case REDIS_ESYS:
|
||||
sprintf(reason, "%d:%s", errno, strerror(errno));
|
||||
break;
|
||||
case REDIS_EGAI:
|
||||
sprintf(reason, "%d:%s", errno, gai_strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s:%d: ", file, line);
|
||||
fprintf(stderr, "%s != %s (%s)\n", actualstr, expectedstr, reason);
|
||||
}
|
||||
|
||||
#define assert_equal_return(a, b) do { \
|
||||
if ((a) != (b)) { \
|
||||
assert_equal_return_failure( \
|
||||
(a), (b), \
|
||||
__FILE__, __LINE__); \
|
||||
assert(0); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/******************************************************************************/
|
||||
/* TEST DEFINITION ************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
UNUSED static const char *current_test = NULL;
|
||||
|
||||
#define TEST(name) \
|
||||
static void test__##name(void); \
|
||||
static void test_##name(void) { \
|
||||
current_test = #name; \
|
||||
spawn_init(); \
|
||||
test__##name(); \
|
||||
spawn_destroy(); \
|
||||
printf("%s: PASSED\n", current_test); \
|
||||
} \
|
||||
static void test__##name(void)
|
||||
|
||||
/******************************************************************************/
|
||||
/* TIMING *********************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
UNUSED static long long usec(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (long long)tv.tv_sec * 1000000 + tv.tv_usec;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* MISC ***********************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
#define REDIS_DEFAULT_PORT 6379
|
||||
|
||||
UNUSED static int redis_port(void) {
|
||||
char *env, *eptr;
|
||||
long port = REDIS_DEFAULT_PORT;
|
||||
|
||||
env = getenv("REDIS_PORT");
|
||||
if (env != NULL) {
|
||||
port = strtol(env, &eptr, 10);
|
||||
|
||||
/* Reset to default when the var contains garbage. */
|
||||
if (*eptr != '\0') {
|
||||
port = REDIS_DEFAULT_PORT;
|
||||
}
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
#endif
|
||||
259
test/test-object.c
Normal file
259
test/test-object.c
Normal file
@ -0,0 +1,259 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "object.h"
|
||||
#include "parser.h"
|
||||
#include "test-helper.h"
|
||||
|
||||
void test_string(redis_parser *parser) {
|
||||
const char *buf = "$5\r\nhello\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, 11) == 11);
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_STRING);
|
||||
assert_equal_string(obj->str, "hello");
|
||||
assert_equal_int(obj->len, 5);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_chunked_string(redis_parser *parser) {
|
||||
const char *buf = "$5\r\nhello\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf+0, 6) == 6);
|
||||
assert(redis_parser_execute(parser, &res, buf+6, 5) == 5);
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_STRING);
|
||||
assert_equal_string(obj->str, "hello");
|
||||
assert_equal_int(obj->len, 5);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_empty_string(redis_parser *parser) {
|
||||
const char *buf = "$0\r\n\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, 6) == 6);
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_STRING);
|
||||
assert_equal_string(obj->str, "");
|
||||
assert_equal_int(obj->len, 0);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_nil(redis_parser *parser) {
|
||||
const char *buf = "$-1\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, 5) == 5);
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_NIL);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_array(redis_parser *parser) {
|
||||
const char *buf =
|
||||
"*2\r\n"
|
||||
"$5\r\nhello\r\n"
|
||||
"$5\r\nworld\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, 26) == 26);
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_ARRAY);
|
||||
assert_equal_int(obj->elements, 2);
|
||||
|
||||
assert_equal_int(obj->element[0]->type, REDIS_STRING);
|
||||
assert_equal_string(obj->element[0]->str, "hello");
|
||||
assert_equal_int(obj->element[0]->len, 5);
|
||||
|
||||
assert_equal_int(obj->element[1]->type, REDIS_STRING);
|
||||
assert_equal_string(obj->element[1]->str, "world");
|
||||
assert_equal_int(obj->element[1]->len, 5);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_empty_array(redis_parser *parser) {
|
||||
const char *buf = "*0\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, 4) == 4);
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_ARRAY);
|
||||
assert_equal_int(obj->elements, 0);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_integer(redis_parser *parser) {
|
||||
const char *buf = ":37\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, 5) == 5);
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_INTEGER);
|
||||
assert_equal_int(obj->integer, 37);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_status(redis_parser *parser) {
|
||||
const char *buf = "+foo\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_STATUS);
|
||||
assert_equal_string(obj->str, "foo");
|
||||
assert_equal_int(obj->len, 3);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_empty_status(redis_parser *parser) {
|
||||
const char *buf = "+\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_STATUS);
|
||||
assert_equal_string(obj->str, "");
|
||||
assert_equal_int(obj->len, 0);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_error(redis_parser *parser) {
|
||||
const char *buf = "-err\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_ERROR);
|
||||
assert_equal_string(obj->str, "err");
|
||||
assert_equal_int(obj->len, 3);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_empty_error(redis_parser *parser) {
|
||||
const char *buf = "-\r\n";
|
||||
redis_protocol *res;
|
||||
redis_object *obj;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
|
||||
|
||||
obj = (redis_object*)res->data;
|
||||
assert(obj != NULL);
|
||||
|
||||
assert_equal_int(obj->type, REDIS_ERROR);
|
||||
assert_equal_string(obj->str, "");
|
||||
assert_equal_int(obj->len, 0);
|
||||
|
||||
redis_object_free(&obj);
|
||||
}
|
||||
|
||||
void test_destroy_callback(redis_parser *parser) {
|
||||
const char *buf = "+ok\r";
|
||||
redis_protocol *res;
|
||||
|
||||
redis_parser_init(parser, &redis_object_parser_callbacks);
|
||||
assert(redis_parser_execute(parser, &res, buf, strlen(buf)) == strlen(buf));
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(parser) == RPE_OK);
|
||||
|
||||
/* Go ahead and let the parser clean up */
|
||||
redis_parser_destroy(parser);
|
||||
|
||||
/* No way to check if the temporary object gets properly free'd.
|
||||
* Let's hope that Valgrind starts complaining when this is buggy. */
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
((void)argc);
|
||||
((void)argv);
|
||||
|
||||
redis_parser *parser = malloc(sizeof(redis_parser));
|
||||
|
||||
printf("redis_object: %lu bytes\n", sizeof(redis_object));
|
||||
|
||||
test_string(parser);
|
||||
test_chunked_string(parser);
|
||||
test_empty_string(parser);
|
||||
|
||||
test_nil(parser);
|
||||
|
||||
test_array(parser);
|
||||
test_empty_array(parser);
|
||||
|
||||
test_integer(parser);
|
||||
|
||||
test_status(parser);
|
||||
test_empty_status(parser);
|
||||
|
||||
test_error(parser);
|
||||
test_empty_error(parser);
|
||||
|
||||
test_destroy_callback(parser);
|
||||
|
||||
free(parser);
|
||||
return 0;
|
||||
}
|
||||
650
test/test-parser.c
Normal file
650
test/test-parser.c
Normal file
@ -0,0 +1,650 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "parser.h"
|
||||
#include "sds.h"
|
||||
#include "test-helper.h"
|
||||
|
||||
typedef struct log_entry_s log_entry_t;
|
||||
|
||||
struct log_entry_s {
|
||||
redis_protocol obj;
|
||||
int type;
|
||||
|
||||
/* string_t specifics */
|
||||
char string_buf[1024];
|
||||
int string_size;
|
||||
|
||||
/* array_t specifics */
|
||||
size_t array_len;
|
||||
|
||||
/* integer_t specifics */
|
||||
int64_t integer_value;
|
||||
};
|
||||
|
||||
#define CB_LOG_SIZE 10
|
||||
static log_entry_t cb_log[CB_LOG_SIZE];
|
||||
static int cb_log_idx = 0;
|
||||
|
||||
static void reset_cb_log(void) {
|
||||
memset(cb_log, 0xff, sizeof(cb_log));
|
||||
cb_log_idx = 0;
|
||||
}
|
||||
|
||||
static log_entry_t *dup_cb_log(void) {
|
||||
log_entry_t *log = malloc(sizeof(cb_log));
|
||||
memcpy(log, cb_log, sizeof(cb_log));
|
||||
return log;
|
||||
}
|
||||
|
||||
int on_string(redis_parser *parser, redis_protocol *p, const char *buf, size_t len) {
|
||||
((void)parser);
|
||||
|
||||
log_entry_t *log = (log_entry_t*)p->data;
|
||||
|
||||
/* Use new entry when this is the first call */
|
||||
if (log == NULL) {
|
||||
log = p->data = &cb_log[cb_log_idx++];
|
||||
log->obj = *p;
|
||||
log->type = p->type;
|
||||
log->string_buf[0] = '\0';
|
||||
log->string_size = p->size;
|
||||
}
|
||||
|
||||
/* Type should never change when called multiple times */
|
||||
assert(log->type == p->type);
|
||||
|
||||
/* Cursor should equal current string length */
|
||||
assert(p->cursor == (signed)strlen(log->string_buf));
|
||||
|
||||
/* Append string data */
|
||||
strncat(log->string_buf, buf, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int on_array(redis_parser *parser, redis_protocol *p, size_t len) {
|
||||
((void)parser);
|
||||
|
||||
log_entry_t *log = (log_entry_t*)p->data;
|
||||
|
||||
/* Should only be called once */
|
||||
assert(log == NULL);
|
||||
|
||||
/* Use new entry */
|
||||
log = p->data = &cb_log[cb_log_idx++];
|
||||
log->obj = *p;
|
||||
log->type = p->type;
|
||||
log->array_len = len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int on_integer(redis_parser *parser, redis_protocol *p, int64_t value) {
|
||||
((void)parser);
|
||||
|
||||
log_entry_t *log = (log_entry_t*)p->data;
|
||||
|
||||
/* Should only be called once */
|
||||
assert(log == NULL);
|
||||
|
||||
/* Use new entry */
|
||||
log = p->data = &cb_log[cb_log_idx++];
|
||||
log->obj = *p;
|
||||
log->type = p->type;
|
||||
log->integer_value = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int on_nil(redis_parser *parser, redis_protocol *p) {
|
||||
((void)parser);
|
||||
|
||||
log_entry_t *log = (log_entry_t*)p->data;
|
||||
|
||||
/* Should only be called once */
|
||||
assert(log == NULL);
|
||||
|
||||
/* Use new entry */
|
||||
log = p->data = &cb_log[cb_log_idx++];
|
||||
log->obj = *p;
|
||||
log->type = p->type;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int destroy_called = 0;
|
||||
|
||||
void destroy(redis_parser *parser, redis_protocol *p) {
|
||||
((void)parser);
|
||||
((void)p);
|
||||
|
||||
destroy_called++;
|
||||
}
|
||||
|
||||
static redis_parser_callbacks callbacks = {
|
||||
&on_string,
|
||||
&on_array,
|
||||
&on_integer,
|
||||
&on_nil,
|
||||
&destroy
|
||||
};
|
||||
|
||||
#define RESET_PARSER(__parser) do { \
|
||||
reset_cb_log(); \
|
||||
redis_parser_init((__parser), &callbacks); \
|
||||
destroy_called = 0; \
|
||||
} while(0)
|
||||
|
||||
void test_char_by_char(const char *buf, size_t len) {
|
||||
log_entry_t *ref;
|
||||
redis_parser *p;
|
||||
redis_protocol *res;
|
||||
size_t i, j, k;
|
||||
|
||||
ref = dup_cb_log();
|
||||
p = malloc(sizeof(redis_parser));
|
||||
|
||||
for (i = 0; i < (len-1); i++) {
|
||||
for (j = i+1; j < len; j++) {
|
||||
RESET_PARSER(p);
|
||||
|
||||
#ifdef DEBUG
|
||||
sds debug = sdsempty();
|
||||
debug = sdscatrepr(debug, buf, i);
|
||||
debug = sdscatprintf(debug, " + ");
|
||||
debug = sdscatrepr(debug, buf+i, j-i);
|
||||
debug = sdscatprintf(debug, " + ");
|
||||
debug = sdscatrepr(debug, buf+j, len-j);
|
||||
fprintf(stderr, "%s\n", debug);
|
||||
sdsfree(debug);
|
||||
#endif
|
||||
|
||||
/* Slice 1 */
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, i), i);
|
||||
assert(NULL == res); /* no result */
|
||||
|
||||
/* Slice 2 */
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf+i, j-i), j-i);
|
||||
assert(NULL == res); /* no result */
|
||||
|
||||
/* Slice 3 */
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf+j, len-j), len-j);
|
||||
assert(NULL != res);
|
||||
|
||||
/* Compare callback log with reference */
|
||||
for (k = 0; k < CB_LOG_SIZE; k++) {
|
||||
log_entry_t expect = ref[k];
|
||||
log_entry_t actual = cb_log[k];
|
||||
|
||||
/* Not interested in the redis_protocol data */
|
||||
memset(&expect.obj, 0, sizeof(expect.obj));
|
||||
memset(&actual.obj, 0, sizeof(actual.obj));
|
||||
assert(memcmp(&expect, &actual, sizeof(expect)) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(p);
|
||||
free(ref);
|
||||
}
|
||||
|
||||
#define SETUP(var) \
|
||||
redis_parser _parser, *(var) = &_parser; \
|
||||
redis_parser_init((var), &callbacks);
|
||||
|
||||
/* Not a real test, just print sizeof's */
|
||||
TEST(struct_size) {
|
||||
printf("redis_protocol: %lu bytes\n", sizeof(redis_protocol));
|
||||
printf("redis_parser: %lu bytes\n", sizeof(redis_parser));
|
||||
}
|
||||
|
||||
TEST(string) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = "$5\r\nhello\r\n";
|
||||
size_t len = 11;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_STRING);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 11);
|
||||
assert_equal_size_t(res->coff, 4);
|
||||
assert_equal_size_t(res->clen, 5);
|
||||
|
||||
/* Check callbacks */
|
||||
assert(cb_log_idx == 1);
|
||||
assert_equal_int(cb_log[0].string_size, 5);
|
||||
assert(!strncmp(cb_log[0].string_buf, buf+4, 5));
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(empty_string) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = "$0\r\n\r\n";
|
||||
size_t len = 6;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_STRING);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 6);
|
||||
assert_equal_size_t(res->coff, 4);
|
||||
assert_equal_size_t(res->clen, 0);
|
||||
|
||||
/* Check callbacks */
|
||||
assert(cb_log_idx == 1);
|
||||
assert_equal_int(cb_log[0].string_size, 0);
|
||||
assert(!strncmp(cb_log[0].string_buf, buf+4, 0));
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(nil_string) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = "$-1\r\n";
|
||||
size_t len = 5;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_NIL);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 5);
|
||||
assert_equal_size_t(res->coff, 0);
|
||||
assert_equal_size_t(res->clen, 0);
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(array) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf =
|
||||
"*2\r\n"
|
||||
"$5\r\nhello\r\n"
|
||||
"$5\r\nworld\r\n";
|
||||
size_t len = 26;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_ARRAY);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 26);
|
||||
assert_equal_size_t(res->coff, 0);
|
||||
assert_equal_size_t(res->clen, 0);
|
||||
|
||||
/* Check callbacks */
|
||||
assert_equal_size_t(cb_log_idx, 3);
|
||||
|
||||
assert_equal_size_t(cb_log[0].obj.poff, 0);
|
||||
assert_equal_size_t(cb_log[0].obj.plen, 4);
|
||||
assert_equal_size_t(cb_log[0].array_len, 2);
|
||||
|
||||
assert_equal_size_t(cb_log[1].obj.poff, 4);
|
||||
assert_equal_size_t(cb_log[1].obj.plen, 4+5+2);
|
||||
assert_equal_size_t(cb_log[1].obj.coff, 4+4);
|
||||
assert_equal_size_t(cb_log[1].obj.clen, 5);
|
||||
assert_equal_int(cb_log[1].string_size, 5);
|
||||
assert(!strncmp(cb_log[1].string_buf, buf+4+4, 5));
|
||||
|
||||
assert_equal_size_t(cb_log[2].obj.poff, 4+11);
|
||||
assert_equal_size_t(cb_log[2].obj.plen, 4+5+2);
|
||||
assert_equal_size_t(cb_log[2].obj.coff, 4+11+4);
|
||||
assert_equal_size_t(cb_log[2].obj.clen, 5);
|
||||
assert_equal_int(cb_log[2].string_size, 5);
|
||||
assert(!strncmp(cb_log[2].string_buf, buf+4+11+4, 5));
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(empty_array) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = "*0\r\n";
|
||||
size_t len = 4;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_ARRAY);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 4);
|
||||
|
||||
/* Check callbacks */
|
||||
assert_equal_size_t(cb_log_idx, 1);
|
||||
assert_equal_size_t(cb_log[0].array_len, 0);
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(nil_array) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = "*-1\r\n";
|
||||
size_t len = 5;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_NIL);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 5);
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(integer) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = ":1234\r\n";
|
||||
size_t len = 7;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_INTEGER);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 7);
|
||||
assert_equal_size_t(res->coff, 1);
|
||||
assert_equal_size_t(res->clen, 4);
|
||||
|
||||
/* Check callbacks */
|
||||
assert_equal_size_t(cb_log_idx, 1);
|
||||
assert_equal_size_t(cb_log[0].integer_value, 1234);
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
|
||||
/* Negative sign */
|
||||
buf = ":-123\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(cb_log_idx, 1);
|
||||
assert_equal_int64_t(cb_log[0].integer_value, -123);
|
||||
test_char_by_char(buf, strlen(buf));
|
||||
|
||||
/* Positive sign */
|
||||
buf = ":+123\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(cb_log_idx, 1);
|
||||
assert_equal_int64_t(cb_log[0].integer_value, 123);
|
||||
test_char_by_char(buf, strlen(buf));
|
||||
|
||||
/* Zero */
|
||||
buf = ":0\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(cb_log_idx, 1);
|
||||
assert_equal_int64_t(cb_log[0].integer_value, 0);
|
||||
test_char_by_char(buf, strlen(buf));
|
||||
|
||||
/* Signed zero, positive */
|
||||
buf = ":+0\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 2);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_INVALID_INT);
|
||||
|
||||
/* Signed zero, negative */
|
||||
buf = ":-0\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 2);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_INVALID_INT);
|
||||
|
||||
/* Start with 0 */
|
||||
buf = ":0123\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 2);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_EXPECTED_CR);
|
||||
|
||||
/* Start with non-digit */
|
||||
buf = ":x123\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 1);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_INVALID_INT);
|
||||
|
||||
/* Non-digit in the middle */
|
||||
buf = ":12x3\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 3);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_INVALID_INT);
|
||||
|
||||
/* Non-digit at the end */
|
||||
buf = ":123x\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), 4);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_INVALID_INT);
|
||||
|
||||
/* Signed 64-bit maximum */
|
||||
buf = ":9223372036854775807\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(cb_log_idx, 1);
|
||||
assert_equal_int64_t(cb_log[0].integer_value, INT64_MAX);
|
||||
test_char_by_char(buf, strlen(buf));
|
||||
|
||||
/* Signed 64-bit maximum overflow */
|
||||
buf = ":9223372036854775808\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf)-3);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_OVERFLOW);
|
||||
|
||||
/* Signed 64-bit minimum */
|
||||
buf = ":-9223372036854775808\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf));
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(cb_log_idx, 1);
|
||||
assert_equal_int64_t(cb_log[0].integer_value, INT64_MIN);
|
||||
test_char_by_char(buf, strlen(buf));
|
||||
|
||||
/* Signed 64-bit minimum overflow (or underflow...) */
|
||||
buf = ":-9223372036854775809\r\n";
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, strlen(buf)), strlen(buf)-3);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_OVERFLOW);
|
||||
}
|
||||
|
||||
TEST(nil) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = "$-1\r\n";
|
||||
size_t len = 5;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_NIL);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 5);
|
||||
assert_equal_size_t(res->coff, 0);
|
||||
assert_equal_size_t(res->clen, 0);
|
||||
|
||||
/* Check callbacks */
|
||||
assert_equal_size_t(cb_log_idx, 1);
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(status) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = "+status\r\n";
|
||||
size_t len = 9;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_STATUS);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 9);
|
||||
assert_equal_size_t(res->coff, 1);
|
||||
assert_equal_size_t(res->clen, 6);
|
||||
assert(!strncmp(cb_log[0].string_buf, buf+1, 6));
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(error) {
|
||||
SETUP(p);
|
||||
|
||||
const char *buf = "-error\r\n";
|
||||
size_t len = 8;
|
||||
redis_protocol *res;
|
||||
|
||||
/* Parse and check resulting protocol_t */
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, buf, len), len);
|
||||
assert(res != NULL);
|
||||
assert_equal_size_t(res->type, REDIS_ERROR);
|
||||
assert_equal_size_t(res->poff, 0);
|
||||
assert_equal_size_t(res->plen, 8);
|
||||
assert_equal_size_t(res->coff, 1);
|
||||
assert_equal_size_t(res->clen, 5);
|
||||
assert(!strncmp(cb_log[0].string_buf, buf+1, 5));
|
||||
|
||||
/* Chunked check */
|
||||
test_char_by_char(buf, len);
|
||||
}
|
||||
|
||||
TEST(abort_after_error) {
|
||||
SETUP(p);
|
||||
|
||||
redis_protocol *res;
|
||||
enum redis_parser_errno err;
|
||||
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, "+ok\r", 4), 4);
|
||||
assert(res == NULL);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, "\r", 1), 0);
|
||||
assert(res == NULL);
|
||||
|
||||
/* Test if the error matches what we expect */
|
||||
err = redis_parser_err(p);
|
||||
assert(err == RPE_EXPECTED_LF);
|
||||
assert(strcmp("expected \\n", redis_parser_strerror(err)) == 0);
|
||||
|
||||
/* Test that the parser doesn't continue after an error */
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, "\n", 1), 0);
|
||||
assert(res == NULL);
|
||||
}
|
||||
|
||||
TEST(destroy_callback_after_success) {
|
||||
SETUP(p);
|
||||
|
||||
redis_protocol *res;
|
||||
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, "+ok\r\n", 5), 5);
|
||||
assert(res != NULL);
|
||||
assert(redis_parser_err(p) == RPE_OK);
|
||||
|
||||
/* Test that the destroy callback is NOT called when there are
|
||||
* no errors or the parser is in-flight */
|
||||
redis_parser_destroy(p);
|
||||
assert_equal_int(destroy_called, 0);
|
||||
}
|
||||
|
||||
TEST(destroy_callback_after_error) {
|
||||
SETUP(p);
|
||||
|
||||
redis_protocol *res;
|
||||
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, "+ok\r\r", 5), 4);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) != RPE_OK);
|
||||
|
||||
/* Test that the destroy callback is called after an error */
|
||||
redis_parser_destroy(p);
|
||||
assert_equal_int(destroy_called, 1);
|
||||
}
|
||||
|
||||
TEST(destroy_callback_in_flight) {
|
||||
SETUP(p);
|
||||
|
||||
redis_protocol *res;
|
||||
|
||||
RESET_PARSER(p);
|
||||
assert_equal_size_t(redis_parser_execute(p, &res, "+ok\r", 4), 4);
|
||||
assert(res == NULL);
|
||||
assert(redis_parser_err(p) == RPE_OK);
|
||||
|
||||
/* Test that the destroy callback is called when the parser is in-flight */
|
||||
redis_parser_destroy(p);
|
||||
assert_equal_int(destroy_called, 1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
((void)argc);
|
||||
((void)argv);
|
||||
|
||||
test_struct_size();
|
||||
|
||||
test_string();
|
||||
test_empty_string();
|
||||
test_nil_string();
|
||||
|
||||
test_array();
|
||||
test_empty_array();
|
||||
test_nil_array();
|
||||
|
||||
test_integer();
|
||||
test_nil();
|
||||
test_status();
|
||||
test_error();
|
||||
|
||||
test_abort_after_error();
|
||||
test_destroy_callback_after_success();
|
||||
test_destroy_callback_after_error();
|
||||
test_destroy_callback_in_flight();
|
||||
|
||||
return 0;
|
||||
}
|
||||
572
test/test-request.c
Normal file
572
test/test-request.c
Normal file
@ -0,0 +1,572 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "request.h"
|
||||
#include "test-helper.h"
|
||||
|
||||
static struct request_to_write_cb_s {
|
||||
int callc;
|
||||
struct {
|
||||
redis_request_queue *q;
|
||||
redis_request *r;
|
||||
} *callv;
|
||||
} request_to_write_cb_calls = { .callc = 0, .callv = NULL };
|
||||
|
||||
void request_to_write_cb_reset(void) {
|
||||
struct request_to_write_cb_s *s = &request_to_write_cb_calls;
|
||||
|
||||
if (s->callc) {
|
||||
free(s->callv);
|
||||
}
|
||||
|
||||
s->callc = 0;
|
||||
s->callv = NULL;
|
||||
}
|
||||
|
||||
void request_to_write_cb(redis_request_queue *q, redis_request *r) {
|
||||
struct request_to_write_cb_s *s = &request_to_write_cb_calls;
|
||||
|
||||
s->callv = realloc(s->callv, sizeof(*s->callv) * (s->callc + 1));
|
||||
assert(s->callv != NULL);
|
||||
|
||||
s->callv[s->callc].q = q;
|
||||
s->callv[s->callc].r = r;
|
||||
s->callc++;
|
||||
}
|
||||
|
||||
static struct request_wait_write_cb_s {
|
||||
int callc;
|
||||
struct {
|
||||
redis_request_queue *q;
|
||||
redis_request *r;
|
||||
} *callv;
|
||||
} request_wait_write_cb_calls = { .callc = 0, .callv = NULL };
|
||||
|
||||
void request_wait_write_cb_reset(void) {
|
||||
struct request_wait_write_cb_s *s = &request_wait_write_cb_calls;
|
||||
|
||||
if (s->callc) {
|
||||
free(s->callv);
|
||||
}
|
||||
|
||||
s->callc = 0;
|
||||
s->callv = NULL;
|
||||
}
|
||||
|
||||
void request_wait_write_cb(redis_request_queue *q, redis_request *r) {
|
||||
struct request_wait_write_cb_s *s = &request_wait_write_cb_calls;
|
||||
|
||||
s->callv = realloc(s->callv, sizeof(*s->callv) * (s->callc + 1));
|
||||
assert(s->callv != NULL);
|
||||
|
||||
s->callv[s->callc].q = q;
|
||||
s->callv[s->callc].r = r;
|
||||
s->callc++;
|
||||
}
|
||||
|
||||
static struct request_wait_read_cb_s {
|
||||
int callc;
|
||||
struct {
|
||||
redis_request_queue *q;
|
||||
redis_request *r;
|
||||
} *callv;
|
||||
} request_wait_read_cb_calls = { .callc = 0, .callv = NULL };
|
||||
|
||||
void request_wait_read_cb_reset(void) {
|
||||
struct request_wait_read_cb_s *s = &request_wait_read_cb_calls;
|
||||
|
||||
if (s->callc) {
|
||||
free(s->callv);
|
||||
}
|
||||
|
||||
s->callc = 0;
|
||||
s->callv = NULL;
|
||||
}
|
||||
|
||||
void request_wait_read_cb(redis_request_queue *q, redis_request *r) {
|
||||
struct request_wait_read_cb_s *s = &request_wait_read_cb_calls;
|
||||
|
||||
s->callv = realloc(s->callv, sizeof(*s->callv) * (s->callc + 1));
|
||||
assert(s->callv != NULL);
|
||||
|
||||
s->callv[s->callc].q = q;
|
||||
s->callv[s->callc].r = r;
|
||||
s->callc++;
|
||||
}
|
||||
|
||||
static struct write_fn_s {
|
||||
int callc;
|
||||
struct {
|
||||
redis_request_queue *q;
|
||||
} *callv;
|
||||
} write_fn_calls = { .callc = 0, .callv = NULL };
|
||||
|
||||
void write_fn_reset(void) {
|
||||
struct write_fn_s *s = &write_fn_calls;
|
||||
|
||||
if (s->callc) {
|
||||
free(s->callv);
|
||||
}
|
||||
|
||||
s->callc = 0;
|
||||
s->callv = NULL;
|
||||
}
|
||||
|
||||
void write_fn(redis_request_queue *q) {
|
||||
struct write_fn_s *s = &write_fn_calls;
|
||||
|
||||
s->callv = realloc(s->callv, sizeof(*s->callv) * (s->callc + 1));
|
||||
assert(s->callv != NULL);
|
||||
|
||||
s->callv[s->callc].q = q;
|
||||
s->callc++;
|
||||
}
|
||||
|
||||
#define SETUP() \
|
||||
redis_request_queue q; \
|
||||
int rv; \
|
||||
\
|
||||
request_to_write_cb_reset(); \
|
||||
request_wait_write_cb_reset(); \
|
||||
request_wait_read_cb_reset(); \
|
||||
write_fn_reset(); \
|
||||
\
|
||||
rv = redis_request_queue_init(&q); \
|
||||
assert_equal_return(rv, REDIS_OK); \
|
||||
\
|
||||
q.request_to_write_cb = request_to_write_cb; \
|
||||
q.request_wait_write_cb = request_wait_write_cb; \
|
||||
q.request_wait_read_cb = request_wait_read_cb; \
|
||||
q.write_fn = write_fn;
|
||||
|
||||
#define TEARDOWN() \
|
||||
redis_request_queue_destroy(&q);
|
||||
|
||||
typedef struct t1_redis_request_s t1_redis_request;
|
||||
|
||||
struct t1_redis_request_s {
|
||||
REDIS_REQUEST_COMMON
|
||||
|
||||
const char *buf;
|
||||
size_t len;
|
||||
size_t emit;
|
||||
size_t nemitted;
|
||||
size_t nwritten;
|
||||
|
||||
const char *read_raw_buf;
|
||||
size_t read_raw_len;
|
||||
|
||||
redis_protocol *reply;
|
||||
|
||||
int free_calls;
|
||||
};
|
||||
|
||||
void t1_write_ptr(redis_request *_self, const char **buf, size_t *len, int *done) {
|
||||
t1_redis_request *self = (t1_redis_request*)_self;
|
||||
size_t to_emit;
|
||||
|
||||
assert(self->nemitted <= self->len);
|
||||
|
||||
to_emit = self->len - self->nemitted;
|
||||
if (to_emit > self->emit) {
|
||||
to_emit = self->emit;
|
||||
}
|
||||
|
||||
*buf = self->buf + self->nemitted;
|
||||
*len = to_emit;
|
||||
self->nemitted += to_emit;
|
||||
|
||||
if (self->nemitted == self->len) {
|
||||
*done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void t1_write_cb(redis_request *_self, int n) {
|
||||
t1_redis_request *self = (t1_redis_request*)_self;
|
||||
self->nwritten += n;
|
||||
}
|
||||
|
||||
void t1_read_cb(redis_request *_self, redis_protocol *p, const char *buf, size_t len, int *done) {
|
||||
t1_redis_request *self = (t1_redis_request*)_self;
|
||||
|
||||
if (p != NULL) {
|
||||
self->reply = p;
|
||||
*done = 1;
|
||||
}
|
||||
|
||||
self->read_raw_buf = buf;
|
||||
self->read_raw_len = len;
|
||||
}
|
||||
|
||||
void t1_free(redis_request *_self) {
|
||||
t1_redis_request *self = (t1_redis_request*)_self;
|
||||
self->free_calls++;
|
||||
}
|
||||
|
||||
void t1_init(t1_redis_request *self) {
|
||||
memset(self, 0, sizeof(*self));
|
||||
redis_request_init((redis_request*)self);
|
||||
|
||||
self->write_ptr = t1_write_ptr;
|
||||
self->write_cb = t1_write_cb;
|
||||
self->read_cb = t1_read_cb;
|
||||
self->free = t1_free;
|
||||
}
|
||||
|
||||
TEST(insert_request) {
|
||||
SETUP();
|
||||
|
||||
t1_redis_request req;
|
||||
t1_init(&req);
|
||||
|
||||
req.buf = "hello";
|
||||
req.len = strlen(req.buf);
|
||||
req.emit = 2;
|
||||
|
||||
redis_request_queue_insert(&q, (redis_request*)&req);
|
||||
|
||||
/* Test that callback was correctly triggered */
|
||||
assert_equal_int(request_to_write_cb_calls.callc, 1);
|
||||
assert(request_to_write_cb_calls.callv[0].q == &q);
|
||||
assert(request_to_write_cb_calls.callv[0].r == (redis_request*)&req);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
#define INIT_REQUEST(_var, _str) \
|
||||
t1_redis_request (_var); \
|
||||
t1_init(&(_var)); \
|
||||
(_var).buf = (_str); \
|
||||
(_var).len = strlen((_var).buf); \
|
||||
(_var).emit = (_var).len;
|
||||
|
||||
#define SETUP_INSERTED(_var1, _var2) \
|
||||
SETUP(); \
|
||||
INIT_REQUEST(req1, (_var1)); \
|
||||
INIT_REQUEST(req2, (_var2)); \
|
||||
redis_request_queue_insert(&q, (redis_request*)&req1); \
|
||||
redis_request_queue_insert(&q, (redis_request*)&req2); \
|
||||
|
||||
TEST(write_ptr) {
|
||||
SETUP_INSERTED("hello", "world");
|
||||
|
||||
const char *buf;
|
||||
size_t len;
|
||||
|
||||
req1.emit = 3;
|
||||
req2.emit = 3;
|
||||
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(len, 3);
|
||||
assert(strncmp(buf, "hel", len) == 0);
|
||||
assert(req1.write_ptr_done == 0);
|
||||
|
||||
/* The first request should have moved to the wait_write queue now */
|
||||
assert_equal_int(request_wait_write_cb_calls.callc, 1);
|
||||
assert(request_wait_write_cb_calls.callv[0].q == &q);
|
||||
assert(request_wait_write_cb_calls.callv[0].r == (redis_request*)&req1);
|
||||
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(len, 2);
|
||||
assert(strncmp(buf, "lo", len) == 0);
|
||||
assert(req1.write_ptr_done == 1);
|
||||
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(len, 3);
|
||||
assert(strncmp(buf, "wor", len) == 0);
|
||||
assert(req2.write_ptr_done == 0);
|
||||
|
||||
/* The second request should have moved to the wait_write queue now */
|
||||
assert_equal_int(request_wait_write_cb_calls.callc, 2);
|
||||
assert(request_wait_write_cb_calls.callv[1].q == &q);
|
||||
assert(request_wait_write_cb_calls.callv[1].r == (redis_request*)&req2);
|
||||
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(len, 2);
|
||||
assert(strncmp(buf, "ld", len) == 0);
|
||||
assert(req2.write_ptr_done == 1);
|
||||
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len);
|
||||
assert_equal_size_t(rv, -1);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
TEST(write_ptr_skip_empty) {
|
||||
SETUP_INSERTED("", "hello");
|
||||
|
||||
const char *buf;
|
||||
size_t len;
|
||||
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(len, 5);
|
||||
assert(strncmp(buf, "hello", len) == 0);
|
||||
|
||||
/* Both req1 and req2 should be marked as done */
|
||||
assert(req1.write_ptr_done == 1);
|
||||
assert(req2.write_ptr_done == 1);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
#define SETUP_DRAIN_WRITE_PTR() \
|
||||
do { \
|
||||
const char *buf; \
|
||||
size_t len; \
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len); \
|
||||
} while (rv == 0);
|
||||
|
||||
#define SETUP_WRITTEN_UNCONFIRMED() \
|
||||
SETUP_INSERTED("hello", "world"); \
|
||||
SETUP_DRAIN_WRITE_PTR();
|
||||
|
||||
TEST(write_cb) {
|
||||
SETUP_WRITTEN_UNCONFIRMED();
|
||||
|
||||
/* Assume 3 bytes were written ("hel" in req1) */
|
||||
rv = redis_request_queue_write_cb(&q, 3);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(req1.nwritten, 3);
|
||||
assert_equal_size_t(req2.nwritten, 0);
|
||||
assert(req1.write_cb_done == 0);
|
||||
assert(req2.write_cb_done == 0);
|
||||
|
||||
/* The first request should have moved to the wait_read queue now */
|
||||
assert_equal_int(request_wait_read_cb_calls.callc, 1);
|
||||
assert(request_wait_read_cb_calls.callv[0].q == &q);
|
||||
assert(request_wait_read_cb_calls.callv[0].r == (redis_request*)&req1);
|
||||
|
||||
/* Assume another 3 bytes were written ("lo" in req1, "w" in req2) */
|
||||
rv = redis_request_queue_write_cb(&q, 3);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(req1.nwritten, 5);
|
||||
assert_equal_size_t(req2.nwritten, 1);
|
||||
assert(req1.write_cb_done == 1);
|
||||
assert(req2.write_cb_done == 0);
|
||||
|
||||
/* The second request should have moved to the wait_read queue now */
|
||||
assert_equal_int(request_wait_read_cb_calls.callc, 2);
|
||||
assert(request_wait_read_cb_calls.callv[1].q == &q);
|
||||
assert(request_wait_read_cb_calls.callv[1].r == (redis_request*)&req2);
|
||||
|
||||
/* Run callback for remaining bytes */
|
||||
rv = redis_request_queue_write_cb(&q, 4);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(req1.nwritten, 5);
|
||||
assert_equal_size_t(req2.nwritten, 5);
|
||||
assert(req1.write_cb_done == 1);
|
||||
assert(req2.write_cb_done == 1);
|
||||
|
||||
/* More bytes cannot be mapped to requests... */
|
||||
rv = redis_request_queue_write_cb(&q, 4);
|
||||
assert_equal_size_t(rv, -1);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
TEST(write_cb_skip_empty) {
|
||||
SETUP_INSERTED("", "hello");
|
||||
SETUP_DRAIN_WRITE_PTR();
|
||||
|
||||
/* Assume 1 byte was written ("h") */
|
||||
rv = redis_request_queue_write_cb(&q, 1);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(req1.nwritten, 0);
|
||||
assert_equal_size_t(req2.nwritten, 1);
|
||||
assert(req1.write_cb_done == 1);
|
||||
assert(req2.write_cb_done == 0);
|
||||
|
||||
/* Both requests should have moved to the wait_read queue now */
|
||||
assert_equal_int(request_wait_read_cb_calls.callc, 2);
|
||||
assert(request_wait_read_cb_calls.callv[0].q == &q);
|
||||
assert(request_wait_read_cb_calls.callv[0].r == (redis_request*)&req1);
|
||||
assert(request_wait_read_cb_calls.callv[1].q == &q);
|
||||
assert(request_wait_read_cb_calls.callv[1].r == (redis_request*)&req2);
|
||||
|
||||
/* Run callback for remaining bytes */
|
||||
rv = redis_request_queue_write_cb(&q, 4);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(req1.nwritten, 0);
|
||||
assert_equal_size_t(req2.nwritten, 5);
|
||||
assert(req1.write_cb_done == 1);
|
||||
assert(req2.write_cb_done == 1);
|
||||
|
||||
/* More bytes cannot be mapped to requests... */
|
||||
rv = redis_request_queue_write_cb(&q, 4);
|
||||
assert_equal_size_t(rv, -1);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
#define SETUP_WRITTEN_CONFIRMED() \
|
||||
SETUP_WRITTEN_UNCONFIRMED(); \
|
||||
rv = redis_request_queue_write_cb(&q, req1.len); \
|
||||
assert_equal_size_t(rv, 0); \
|
||||
rv = redis_request_queue_write_cb(&q, req2.len); \
|
||||
assert_equal_size_t(rv, 0);
|
||||
|
||||
TEST(read_cb) {
|
||||
SETUP_WRITTEN_CONFIRMED();
|
||||
|
||||
/* Feed part of the reply for request 1 */
|
||||
rv = redis_request_queue_read_cb(&q, "+stat", 5);
|
||||
assert_equal_size_t(rv, 0);
|
||||
|
||||
assert_equal_size_t(req1.read_raw_len, 5);
|
||||
assert(strncmp(req1.read_raw_buf, "+stat", req1.read_raw_len) == 0);
|
||||
assert(req1.reply == NULL);
|
||||
assert(req1.free_calls == 0);
|
||||
assert(req1.read_cb_done == 0);
|
||||
|
||||
/* Feed remaining part for request 1, and first part for request 2 */
|
||||
rv = redis_request_queue_read_cb(&q, "us\r\n+st", 7);
|
||||
assert_equal_size_t(rv, 0);
|
||||
|
||||
assert_equal_size_t(req1.read_raw_len, 4);
|
||||
assert(strncmp(req1.read_raw_buf, "us\r\n", req1.read_raw_len) == 0);
|
||||
assert(req1.reply != NULL && req1.reply->type == REDIS_STATUS);
|
||||
assert(req1.free_calls == 1);
|
||||
assert(req1.read_cb_done == 1);
|
||||
|
||||
assert_equal_size_t(req2.read_raw_len, 3);
|
||||
assert(strncmp(req2.read_raw_buf, "+st", req2.read_raw_len) == 0);
|
||||
assert(req2.reply == NULL);
|
||||
assert(req2.free_calls == 0);
|
||||
assert(req2.read_cb_done == 0);
|
||||
|
||||
/* Feed remaining part for request 2 */
|
||||
rv = redis_request_queue_read_cb(&q, "atus\r\n", 6);
|
||||
assert_equal_size_t(rv, 0);
|
||||
|
||||
assert_equal_size_t(req2.read_raw_len, 6);
|
||||
assert(strncmp(req2.read_raw_buf, "atus\r\n", req2.read_raw_len) == 0);
|
||||
assert(req2.reply != NULL && req1.reply->type == REDIS_STATUS);
|
||||
assert(req2.free_calls == 1);
|
||||
assert(req2.read_cb_done == 1);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
TEST(read_cb_with_parse_error) {
|
||||
SETUP_WRITTEN_CONFIRMED();
|
||||
|
||||
/* Feed part of an erroneous reply */
|
||||
rv = redis_request_queue_read_cb(&q, "+x\r\r", 4);
|
||||
assert_equal_return(rv, REDIS_EPARSER);
|
||||
|
||||
/* This should keep failing */
|
||||
rv = redis_request_queue_read_cb(&q, "\n", 1);
|
||||
assert_equal_return(rv, REDIS_EPARSER);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
TEST(read_cb_with_pending_write_ptr_emit) {
|
||||
SETUP_INSERTED("hello", "world");
|
||||
|
||||
const char *buf;
|
||||
size_t len;
|
||||
|
||||
req1.emit = 3;
|
||||
|
||||
/* Grab first 3 bytes from request */
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(len, 3);
|
||||
assert(strncmp(buf, "hel", len) == 0);
|
||||
|
||||
/* Assume 3 bytes were written ("hel" in req1) */
|
||||
rv = redis_request_queue_write_cb(&q, 3);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(req1.nwritten, 3);
|
||||
|
||||
/* Feed part of the reply for request 1 */
|
||||
rv = redis_request_queue_read_cb(&q, "+stat", 5);
|
||||
assert_equal_size_t(rv, 0);
|
||||
|
||||
/* Grab remaining bytes from request */
|
||||
rv = redis_request_queue_write_ptr(&q, &buf, &len);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(len, 2);
|
||||
assert(strncmp(buf, "lo", len) == 0);
|
||||
|
||||
/* Assume remaining bytes were written */
|
||||
rv = redis_request_queue_write_cb(&q, 2);
|
||||
assert_equal_size_t(rv, 0);
|
||||
assert_equal_size_t(req1.nwritten, 5);
|
||||
|
||||
/* Feed remaining part of the reply for request 1 */
|
||||
rv = redis_request_queue_read_cb(&q, "us\r\n", 4);
|
||||
assert_equal_size_t(rv, 0);
|
||||
|
||||
assert(req1.reply != NULL && req1.reply->type == REDIS_STATUS);
|
||||
assert(req1.free_calls == 1);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
TEST(write_fn_call_on_first_insert) {
|
||||
SETUP();
|
||||
|
||||
INIT_REQUEST(req, "hello");
|
||||
|
||||
redis_request_queue_insert(&q, (redis_request*)&req);
|
||||
|
||||
/* Test that the write fn was called */
|
||||
assert_equal_int(write_fn_calls.callc, 1);
|
||||
assert(write_fn_calls.callv[0].q == &q);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
TEST(write_fn_no_call_on_second_insert) {
|
||||
SETUP();
|
||||
|
||||
INIT_REQUEST(req1, "hello");
|
||||
INIT_REQUEST(req2, "world");
|
||||
|
||||
redis_request_queue_insert(&q, (redis_request*)&req1);
|
||||
redis_request_queue_insert(&q, (redis_request*)&req2);
|
||||
|
||||
/* Test that the write fn wasn't called twice */
|
||||
assert_equal_int(write_fn_calls.callc, 1);
|
||||
assert(write_fn_calls.callv[0].q == &q);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
TEST(write_fn_call_after_drain) {
|
||||
SETUP_WRITTEN_CONFIRMED();
|
||||
|
||||
INIT_REQUEST(reqN, "hi");
|
||||
|
||||
redis_request_queue_insert(&q, (redis_request*)&reqN);
|
||||
|
||||
/* Test that the write fn was called */
|
||||
assert_equal_int(write_fn_calls.callc, 2);
|
||||
assert(write_fn_calls.callv[0].q == &q);
|
||||
|
||||
TEARDOWN();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_insert_request();
|
||||
test_write_ptr();
|
||||
test_write_ptr_skip_empty();
|
||||
test_write_cb();
|
||||
test_write_cb_skip_empty();
|
||||
test_read_cb();
|
||||
test_read_cb_with_parse_error();
|
||||
test_read_cb_with_pending_write_ptr_emit();
|
||||
test_write_fn_call_on_first_insert();
|
||||
test_write_fn_no_call_on_second_insert();
|
||||
test_write_fn_call_after_drain();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user