├── Makefile ├── Makefile-mqtthttpd ├── README.markdown ├── accesslog.c ├── accesslog.h ├── common ├── b64.c ├── b64.h ├── cJSON.c ├── cJSON.h ├── common.h ├── jitter.c ├── jitter.h ├── logging.c ├── logging.h ├── path.c ├── path.h ├── pidfile.c ├── pidfile.h ├── queue.h ├── random.c ├── random.h ├── tdate_parse.c ├── tdate_parse.h ├── tree.h ├── util.c └── util.h ├── evhelper.h ├── htdocs ├── about.html ├── api.js ├── index.html └── jquery.min.js ├── httpd.c ├── httpd.h ├── httpdconnlist.c ├── httpdconnlist.h ├── idset.c ├── idset.h ├── libebb ├── .gitignore ├── LICENSE ├── Makefile ├── README ├── config.mk ├── doc │ ├── icon.png │ └── index.html ├── ebb.c ├── ebb.h ├── ebb_request_parser.h ├── ebb_request_parser.rl ├── examples │ ├── ca-cert.pem │ ├── ca-key.pem │ └── hello_world.c ├── rbtree.c ├── rbtree.h ├── test_examples.rb ├── test_rbtree.c └── test_request_parser.c ├── midconnlist.c ├── midconnlist.h ├── mqtt.c ├── mqtt.h ├── mqtthttpd.c ├── mqtthttpd.h ├── msgcache.c ├── msgcache.h ├── opt.c ├── opt.h ├── stream.c ├── stream.h ├── streamlist.c ├── streamlist.h ├── sublist.c ├── sublist.h ├── tailq.c ├── tailq.h ├── topic.c ├── topic.h ├── webapi.c ├── webapi.h ├── webapi_serve.c └── webapi_serve.h /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | make -C libebb 3 | make -f Makefile-mqtthttpd 4 | 5 | clean: 6 | -make -C libebb clean 7 | -make -f Makefile-mqtthttpd clean 8 | rm -f access.log mqtthttpd.pid 9 | 10 | -------------------------------------------------------------------------------- /Makefile-mqtthttpd: -------------------------------------------------------------------------------- 1 | TARGET=mqtthttpd 2 | 3 | CC = gcc 4 | OBJDIR=obj 5 | CFLAGS += -Wl,-Map=$(TARGET).map -Wall -g -O2 -I. -Ilibebb -DHAVE_GNUTLS -D_GNU_SOURCE 6 | CFLAGS += -Werror 7 | LDFLAGS = -lev -lm -Llibebb -lebb -lpthread -lssl -lmosquitto -luriparser -lgnutls #-lefence 8 | 9 | vpath %.c common 10 | vpath %.h common 11 | 12 | OBJS=\ 13 | $(OBJDIR)/$(TARGET).o \ 14 | $(OBJDIR)/mqtt.o \ 15 | $(OBJDIR)/logging.o \ 16 | $(OBJDIR)/util.o \ 17 | $(OBJDIR)/jitter.o \ 18 | $(OBJDIR)/pidfile.o \ 19 | $(OBJDIR)/httpdconnlist.o \ 20 | $(OBJDIR)/b64.o \ 21 | $(OBJDIR)/tdate_parse.o \ 22 | $(OBJDIR)/httpd.o \ 23 | $(OBJDIR)/webapi.o \ 24 | $(OBJDIR)/midconnlist.o \ 25 | $(OBJDIR)/streamlist.o \ 26 | $(OBJDIR)/stream.o \ 27 | $(OBJDIR)/topic.o \ 28 | $(OBJDIR)/sublist.o \ 29 | $(OBJDIR)/cJSON.o \ 30 | $(OBJDIR)/webapi_serve.o \ 31 | $(OBJDIR)/path.o \ 32 | $(OBJDIR)/idset.o \ 33 | $(OBJDIR)/tailq.o \ 34 | $(OBJDIR)/random.o \ 35 | $(OBJDIR)/msgcache.o \ 36 | $(OBJDIR)/accesslog.o \ 37 | $(OBJDIR)/opt.o 38 | 39 | all: $(TARGET) 40 | 41 | $(TARGET): $(OBJS) 42 | $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $(TARGET) 43 | 44 | $(OBJDIR)/%.o : %.c *.h common/*.h 45 | @mkdir -p $(OBJDIR) 46 | $(CC) $(CFLAGS) -o $@ -c $< 47 | 48 | clean: 49 | rm -rf $(OBJDIR) $(TARGET) package $(TARGET).map 50 | 51 | 52 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | mqtt-http-server 2 | ================ 3 | Toby Jaffey 4 | 5 | An MQTT to HTTP bridge, allowing web clients access to MQTT. 6 | Supporting long polling, JSON, Javascript API, SSL. 7 | 8 | More documentation is needed. For now, see the examples in htdocs. 9 | 10 | Building 11 | -------- 12 | 13 | Install dependencies 14 | 15 | sudo apt-get install make gcc libgnutls-dev ragel liburiparser-dev libmosquitto0-dev libev-dev libssl-dev 16 | 17 | Build it 18 | 19 | make 20 | 21 | Run it 22 | 23 | ./mqtthttpd -v -r htdocs -l 8080 -n foo -s test.mosquitto.org -p 1883 24 | 25 | Point a web browser at: 26 | 27 | http://localhost:8080 28 | 29 | 30 | HTTPS 31 | ----- 32 | 33 | To run with HTTPS, provide a certificate and key, eg. 34 | 35 | ./mqtthttpd -v -r htdocs -l 443 -n foo -s test.mosquitto.org -p 1883 -c ./libebb/examples/ca-cert.pem -k ./libebb/examples/ca-key.pem 36 | 37 | 38 | Using ports 80 and 443 39 | ---------------------- 40 | 41 | mqtthttpd should not be run as root. Install authbind (apt-get install authbind) to allow users to run services on low numbered ports. 42 | 43 | sudo apt-get install authbind 44 | sudo touch /etc/authbind/byport/80 45 | sudo chmod 755 /etc/authbind/byport/80 46 | sudo chown username.username /etc/authbind/byport/80 47 | authbind ./mqtthttpd -v -r htdocs -l 80 -n foo -s test.mosquitto.org -p 1883 48 | 49 | 50 | -------------------------------------------------------------------------------- /accesslog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "accesslog.h" 27 | 28 | #define ACCESSLOG_FILENAME "access.log" 29 | static FILE *fp; 30 | 31 | int accesslog_init(void) 32 | { 33 | if (NULL == (fp = fopen(ACCESSLOG_FILENAME, "a"))) 34 | return 1; 35 | return 0; 36 | } 37 | 38 | void accesslog_write(const char *format, ...) 39 | { 40 | va_list args; 41 | const char *timeformat = "%Y-%m-%dT%H:%M:%S"; 42 | char timestr[256]; 43 | time_t t; 44 | 45 | t = time(NULL); 46 | strftime(timestr, sizeof(timestr), timeformat, gmtime(&t)); 47 | 48 | va_start(args, format); 49 | fprintf(fp, "[%s] ", timestr); 50 | vfprintf(fp, format, args); 51 | fprintf(fp, "\r\n"); 52 | va_end(args); 53 | 54 | fflush(fp); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /accesslog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef ACCESSLOG_H 17 | #define ACCESSLOG_H 1 18 | 19 | extern int accesslog_init(void); 20 | extern void accesslog_write(const char *format, ...); 21 | 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /common/b64.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static const char cd64[]="|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq"; 8 | static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 9 | 10 | static void b64_decodeblock( uint8_t in[4], uint8_t out[3] ) 11 | { 12 | out[0] = (uint8_t) (in[0] << 2 | in[1] >> 4); 13 | out[1] = (uint8_t) (in[1] << 4 | in[2] >> 2); 14 | out[2] = (uint8_t) (((in[2] << 6) & 0xc0) | in[3]); 15 | } 16 | 17 | static void encodeblock( uint8_t in[3], uint8_t out[4], size_t len ) 18 | { 19 | out[0] = cb64[ in[0] >> 2 ]; 20 | out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ]; 21 | out[2] = (uint8_t) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '='); 22 | out[3] = (uint8_t) (len > 2 ? cb64[ in[2] & 0x3f ] : '='); 23 | } 24 | 25 | size_t b64_decode(char *outstr, const char *indata, size_t inlen) 26 | { 27 | uint8_t in[4], out[3], v; 28 | size_t i, len; 29 | char *orig_outstr = outstr; 30 | size_t inp = 0; 31 | 32 | while( inp <= inlen ) 33 | { 34 | for( len = 0, i = 0; i < 4 && inp <= inlen; i++ ) 35 | { 36 | v = 0; 37 | while( inp <= inlen && v == 0 ) 38 | { 39 | v = (uint8_t) indata[inp++]; 40 | v = (uint8_t) ((v < 43 || v > 122) ? 0 : cd64[ v - 43 ]); 41 | if( v ) 42 | { 43 | v = (uint8_t) ((v == '$') ? 0 : v - 61); 44 | } 45 | } 46 | if( inp <= inlen ) 47 | { 48 | len++; 49 | if( v ) 50 | { 51 | in[ i ] = (uint8_t) (v - 1); 52 | } 53 | } 54 | else 55 | { 56 | in[i] = 0; 57 | } 58 | } 59 | if( len ) 60 | { 61 | b64_decodeblock( in, out ); 62 | for( i = 0; i < len - 1; i++ ) 63 | { 64 | *outstr++ = out[i]; 65 | } 66 | } 67 | } 68 | 69 | len = (outstr - orig_outstr); 70 | 71 | while((size_t)(outstr - orig_outstr) < inlen) 72 | { 73 | *outstr++ = 0; // pad with terminator 74 | } 75 | 76 | return len; 77 | } 78 | 79 | 80 | void b64_encode( char *dest, char *src, size_t src_len ) 81 | { 82 | uint8_t in[3], out[4]; 83 | size_t i, len; 84 | char *src_end = src + src_len; 85 | 86 | while( src <= src_end ) 87 | { 88 | len = 0; 89 | for( i = 0; i < 3; i++ ) 90 | { 91 | in[i] = (uint8_t)*src; 92 | src++; 93 | if( src <= src_end ) 94 | { 95 | len++; 96 | } 97 | else 98 | { 99 | in[i] = 0; 100 | } 101 | } 102 | if( len ) 103 | { 104 | encodeblock( in, out, len ); 105 | for( i = 0; i < 4; i++ ) 106 | { 107 | *dest = out[i]; 108 | dest++; 109 | } 110 | } 111 | } 112 | *dest = 0; 113 | } 114 | 115 | /* 116 | MODULE NAME: b64.c 117 | 118 | AUTHOR: Bob Trower 08/04/01 119 | 120 | PROJECT: Crypt Data Packaging 121 | 122 | COPYRIGHT: Copyright (c) Trantor Standard Systems Inc., 2001 123 | 124 | NOTE: This source code may be used as you wish, subject to 125 | the MIT license. See the LICENCE section below. 126 | 127 | DESCRIPTION: 128 | This little utility implements the Base64 129 | Content-Transfer-Encoding standard described in 130 | RFC1113 (http://www.faqs.org/rfcs/rfc1113.html). 131 | 132 | This is the coding scheme used by MIME to allow 133 | binary data to be transferred by SMTP mail. 134 | 135 | Groups of 3 bytes from a binary stream are coded as 136 | groups of 4 bytes in a text stream. 137 | 138 | The input stream is 'padded' with zeros to create 139 | an input that is an even multiple of 3. 140 | 141 | A special character ('=') is used to denote padding so 142 | that the stream can be decoded back to its exact size. 143 | 144 | Encoded output is formatted in lines which should 145 | be a maximum of 72 characters to conform to the 146 | specification. This program defaults to 72 characters, 147 | but will allow more or less through the use of a 148 | switch. The program enforces a minimum line size 149 | of 4 characters. 150 | 151 | Example encoding: 152 | 153 | The stream 'ABCD' is 32 bits long. It is mapped as 154 | follows: 155 | 156 | ABCD 157 | 158 | A (65) B (66) C (67) D (68) (None) (None) 159 | 01000001 01000010 01000011 01000100 160 | 161 | 16 (Q) 20 (U) 9 (J) 3 (D) 17 (R) 0 (A) NA (=) NA (=) 162 | 010000 010100 001001 000011 010001 000000 000000 000000 163 | 164 | 165 | QUJDRA== 166 | 167 | Decoding is the process in reverse. A 'decode' lookup 168 | table has been created to avoid string scans. 169 | 170 | DESIGN GOALS: Specifically: 171 | Code is a stand-alone utility to perform base64 172 | encoding/decoding. It should be genuinely useful 173 | when the need arises and it meets a need that is 174 | likely to occur for some users. 175 | Code acts as sample code to show the author's 176 | design and coding style. 177 | 178 | Generally: 179 | This program is designed to survive: 180 | Everything you need is in a single source file. 181 | It compiles cleanly using a vanilla ANSI C compiler. 182 | It does its job correctly with a minimum of fuss. 183 | The code is not overly clever, not overly simplistic 184 | and not overly verbose. 185 | Access is 'cut and paste' from a web page. 186 | Terms of use are reasonable. 187 | 188 | VALIDATION: Non-trivial code is never without errors. This 189 | file likely has some problems, since it has only 190 | been tested by the author. It is expected with most 191 | source code that there is a period of 'burn-in' when 192 | problems are identified and corrected. That being 193 | said, it is possible to have 'reasonably correct' 194 | code by following a regime of unit test that covers 195 | the most likely cases and regression testing prior 196 | to release. This has been done with this code and 197 | it has a good probability of performing as expected. 198 | 199 | Unit Test Cases: 200 | 201 | case 0:empty file: 202 | CASE0.DAT -> -> 203 | (Zero length target file created 204 | on both encode and decode.) 205 | 206 | case 1:One input character: 207 | CASE1.DAT A -> QQ== -> A 208 | 209 | case 2:Two input characters: 210 | CASE2.DAT AB -> QUJD -> AB 211 | 212 | case 3:Three input characters: 213 | CASE3.DAT ABC -> QUJD -> ABC 214 | 215 | case 4:Four input characters: 216 | case4.dat ABCD -> QUJDRA== -> ABCD 217 | 218 | case 5:All chars from 0 to ff, linesize set to 50: 219 | 220 | AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIj 221 | JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZH 222 | SElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWpr 223 | bG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P 224 | kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz 225 | tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX 226 | 2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7 227 | /P3+/w== 228 | 229 | case 6:Mime Block from e-mail: 230 | (Data same as test case 5) 231 | 232 | case 7: Large files: 233 | Tested 28 MB file in/out. 234 | 235 | case 8: Random Binary Integrity: 236 | This binary program (b64.exe) was encoded to base64, 237 | back to binary and then executed. 238 | 239 | case 9 Stress: 240 | All files in a working directory encoded/decoded 241 | and compared with file comparison utility to 242 | ensure that multiple runs do not cause problems 243 | such as exhausting file handles, tmp storage, etc. 244 | 245 | ------------- 246 | 247 | Syntax, operation and failure: 248 | All options/switches tested. Performs as 249 | expected. 250 | 251 | case 10: 252 | No Args -- Shows Usage Screen 253 | Return Code 1 (Invalid Syntax) 254 | case 11: 255 | One Arg (invalid) -- Shows Usage Screen 256 | Return Code 1 (Invalid Syntax) 257 | case 12: 258 | One Arg Help (-?) -- Shows detailed Usage Screen. 259 | Return Code 0 (Success -- help request is valid). 260 | case 13: 261 | One Arg Help (-h) -- Shows detailed Usage Screen. 262 | Return Code 0 (Success -- help request is valid). 263 | case 14: 264 | One Arg (valid) -- Uses stdin/stdout (filter) 265 | Return Code 0 (Sucess) 266 | case 15: 267 | Two Args (invalid file) -- shows system error. 268 | Return Code 2 (File Error) 269 | case 16: 270 | Encode non-existent file -- shows system error. 271 | Return Code 2 (File Error) 272 | case 17: 273 | Out of disk space -- shows system error. 274 | Return Code 3 (File I/O Error) 275 | 276 | ------------- 277 | 278 | Compile/Regression test: 279 | gcc compiled binary under Cygwin 280 | Microsoft Visual Studio under Windows 2000 281 | Microsoft Version 6.0 C under Windows 2000 282 | 283 | DEPENDENCIES: None 284 | 285 | LICENCE: Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc. 286 | 287 | Permission is hereby granted, free of charge, to any person 288 | obtaining a copy of this software and associated 289 | documentation files (the "Software"), to deal in the 290 | Software without restriction, including without limitation 291 | the rights to use, copy, modify, merge, publish, distribute, 292 | sublicense, and/or sell copies of the Software, and to 293 | permit persons to whom the Software is furnished to do so, 294 | subject to the following conditions: 295 | 296 | The above copyright notice and this permission notice shall 297 | be included in all copies or substantial portions of the 298 | Software. 299 | 300 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 301 | KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 302 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 303 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 304 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 305 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 306 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 307 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 308 | 309 | VERSION HISTORY: 310 | Bob Trower 08/04/01 -- Create Version 0.00.00B 311 | */ 312 | -------------------------------------------------------------------------------- /common/b64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef B64_H 17 | #define B64_H 1 18 | 19 | size_t b64_decode(char *outstr, const char *indata, size_t in_len); 20 | void b64_encode( char *dest, char *src, size_t src_len); 21 | 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /common/cJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef cJSON__h 24 | #define cJSON__h 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | #include 32 | 33 | // cJSON Types: 34 | #define cJSON_False 0 35 | #define cJSON_True 1 36 | #define cJSON_NULL 2 37 | #define cJSON_Number 3 38 | #define cJSON_String 4 39 | #define cJSON_Array 5 40 | #define cJSON_Object 6 41 | 42 | #define cJSON_IsReference 256 43 | 44 | // The cJSON structure: 45 | typedef struct cJSON { 46 | struct cJSON *next,*prev; // next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem 47 | struct cJSON *child; // An array or object item will have a child pointer pointing to a chain of the items in the array/object. 48 | 49 | int type; // The type of the item, as above. 50 | 51 | char *valuestring; // The item's string, if type==cJSON_String 52 | int valueint; // The item's number, if type==cJSON_Number 53 | double valuedouble; // The item's number, if type==cJSON_Number 54 | 55 | char *string; // The item's name string, if this item is the child of, or is in the list of subitems of an object. 56 | } cJSON; 57 | 58 | typedef struct cJSON_Hooks { 59 | void *(*malloc_fn)(size_t sz); 60 | void (*free_fn)(void *ptr); 61 | } cJSON_Hooks; 62 | 63 | // Supply malloc, realloc and free functions to cJSON 64 | extern void cJSON_InitHooks(cJSON_Hooks* hooks); 65 | 66 | 67 | // Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. 68 | extern cJSON *cJSON_Parse(const char *value); 69 | // Render a cJSON entity to text for transfer/storage. Free the char* when finished. 70 | extern char *cJSON_Print(cJSON *item); 71 | // Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. 72 | extern char *cJSON_PrintUnformatted(cJSON *item); 73 | // Delete a cJSON entity and all subentities. 74 | extern void cJSON_Delete(cJSON *c); 75 | 76 | // Returns the number of items in an array (or object). 77 | extern int cJSON_GetArraySize(cJSON *array); 78 | // Retrieve item number "item" from array "array". Returns NULL if unsuccessful. 79 | extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); 80 | // Get item "string" from object. Case insensitive. 81 | extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); 82 | 83 | // These calls create a cJSON item of the appropriate type. 84 | extern cJSON *cJSON_CreateNull(); 85 | extern cJSON *cJSON_CreateTrue(); 86 | extern cJSON *cJSON_CreateFalse(); 87 | extern cJSON *cJSON_CreateNumber(double num); 88 | extern cJSON *cJSON_CreateString(const char *string); 89 | extern cJSON *cJSON_CreateArray(); 90 | extern cJSON *cJSON_CreateObject(); 91 | 92 | // These utilities create an Array of count items. 93 | extern cJSON *cJSON_CreateIntArray(int *numbers,int count); 94 | extern cJSON *cJSON_CreateFloatArray(float *numbers,int count); 95 | extern cJSON *cJSON_CreateDoubleArray(double *numbers,int count); 96 | extern cJSON *cJSON_CreateStringArray(const char **strings,int count); 97 | 98 | // Append item to the specified array/object. 99 | extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); 100 | extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); 101 | // Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. 102 | extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); 103 | extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item); 104 | 105 | // Remove/Detatch items from Arrays/Objects. 106 | extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); 107 | extern void cJSON_DeleteItemFromArray(cJSON *array,int which); 108 | extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); 109 | extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string); 110 | 111 | // Update array items. 112 | extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); 113 | extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); 114 | 115 | #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) 116 | #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) 117 | #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) 118 | #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) 119 | #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) 120 | 121 | #ifdef __cplusplus 122 | } 123 | #endif 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /common/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef COMMON_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define TRUE true 25 | #define FALSE false 26 | #define BOOLEAN bool 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /common/jitter.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include "jitter.h" 23 | 24 | static bool jitter_enabled; 25 | static struct timeval last; 26 | static double last_delay_us = 0; 27 | 28 | void jitter_enable(bool enabled) 29 | { 30 | jitter_enabled = enabled; 31 | } 32 | 33 | void jitter_check(void) 34 | { 35 | double jitter_us; 36 | double delay_us; 37 | struct timeval now; 38 | 39 | if (jitter_enabled) 40 | { 41 | gettimeofday(&now, NULL); 42 | 43 | delay_us = (now.tv_sec - last.tv_sec) * 1000000.0; 44 | delay_us += now.tv_usec - last.tv_usec; 45 | 46 | jitter_us = delay_us - last_delay_us; 47 | 48 | if (jitter_us >= 0) 49 | LOG_USER("jitter: +%5.4fms", jitter_us / 1000.0); 50 | else 51 | LOG_USER("jitter: %5.4fms", jitter_us / 1000.0); 52 | 53 | memcpy(&last, &now, sizeof(struct timeval)); 54 | last_delay_us = delay_us; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /common/jitter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef JITTER_H 17 | #define JITTER_H 1 18 | #include 19 | 20 | void jitter_enable(bool enabled); 21 | void jitter_check(void); 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /common/logging.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "logging.h" 26 | #include "util.h" 27 | 28 | static int loglevel = LOGLEVEL_INFO; 29 | 30 | void log_init(void) 31 | { 32 | } 33 | 34 | void log_setlevel(int level) 35 | { 36 | loglevel = level; 37 | } 38 | 39 | void log_msg(int level, const char *func, const char *format, ...) 40 | { 41 | va_list args; 42 | const char *timeformat = "%Y-%m-%dT%H:%M:%S"; 43 | char timestr[256]; 44 | time_t t; 45 | 46 | t = time(NULL); 47 | strftime(timestr, sizeof(timestr), timeformat, gmtime(&t)); 48 | 49 | 50 | if (loglevel >= level || level == LOGLEVEL_CRITICAL) 51 | { 52 | if (level == LOGLEVEL_DEBUG) 53 | fprintf(stderr, "[%s()] ", func); 54 | va_start(args, format); 55 | fprintf(stderr, "[%s] ", timestr); 56 | vfprintf(stderr, format, args); 57 | fprintf(stderr, "\r\n"); 58 | va_end(args); 59 | } 60 | 61 | if (level == LOGLEVEL_CRITICAL) 62 | exit(1); 63 | } 64 | 65 | void log_msg_noln(int level, const char *func, const char *format, ...) 66 | { 67 | va_list args; 68 | 69 | if (loglevel >= level || loglevel == LOGLEVEL_CRITICAL) 70 | { 71 | if (level == LOGLEVEL_DEBUG) 72 | fprintf(stderr, "[%s()] ", func); 73 | va_start(args, format); 74 | vfprintf(stderr, format, args); 75 | va_end(args); 76 | } 77 | 78 | if (level == LOGLEVEL_CRITICAL) 79 | exit(1); 80 | } 81 | 82 | -------------------------------------------------------------------------------- /common/logging.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef LOGGING_H 17 | #define LOGGING_H 1 18 | 19 | #include 20 | 21 | #define LOGLEVEL_CRITICAL (-2) 22 | #define LOGLEVEL_USER (-1) 23 | #define LOGLEVEL_ERROR 0 24 | #define LOGLEVEL_WARN 2 25 | #define LOGLEVEL_INFO 3 26 | #define LOGLEVEL_DEBUG 4 27 | 28 | #define LOG_CRITICAL(...) log_msg(LOGLEVEL_CRITICAL, __FUNCTION__, __VA_ARGS__) 29 | #define LOG_USER(...) log_msg(LOGLEVEL_USER, __FUNCTION__, __VA_ARGS__) 30 | #define LOG_ERROR(...) log_msg(LOGLEVEL_ERROR, __FUNCTION__, __VA_ARGS__) 31 | #define LOG_WARN(...) log_msg(LOGLEVEL_WARN, __FUNCTION__, __VA_ARGS__) 32 | #define LOG_INFO(...) log_msg(LOGLEVEL_INFO, __FUNCTION__, __VA_ARGS__) 33 | #define LOG_DEBUG(...) log_msg(LOGLEVEL_DEBUG, __FUNCTION__, __VA_ARGS__) 34 | 35 | #define LOG_USER_NOLN(...) log_msg_noln(LOGLEVEL_USER, __FUNCTION__, __VA_ARGS__) 36 | #define LOG_ERROR_NOLN(...) log_msg_noln(LOGLEVEL_ERROR, __FUNCTION__, __VA_ARGS__) 37 | #define LOG_WARN_NOLN(...) log_msg_noln(LOGLEVEL_WARN, __FUNCTION__, __VA_ARGS__) 38 | #define LOG_INFO_NOLN(...) log_msg_noln(LOGLEVEL_INFO, __FUNCTION__, __VA_ARGS__) 39 | #define LOG_DEBUG_NOLN(...) log_msg_noln(LOGLEVEL_DEBUG, __FUNCTION__, __VA_ARGS__) 40 | 41 | void log_init(void); 42 | void log_setlevel(int level); 43 | void log_msg(int level, const char *func, const char *format, ...); 44 | void log_msg_noln(int level, const char *func, const char *format, ...); 45 | 46 | #endif 47 | 48 | -------------------------------------------------------------------------------- /common/path.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | bool path_in_dir(const char *root, const char *path) 24 | { 25 | char path_full[PATH_MAX]; 26 | char root_full[PATH_MAX]; 27 | 28 | if (strlen(root) >= PATH_MAX || strlen(path) >= PATH_MAX) 29 | { 30 | LOG_ERROR("too long"); 31 | return false; 32 | } 33 | if (NULL == realpath(path, path_full)) 34 | { 35 | LOG_DEBUG("bad path '%s'", path); 36 | return false; 37 | } 38 | if (NULL == realpath(root, root_full)) 39 | { 40 | LOG_ERROR("bad root"); 41 | return false; 42 | } 43 | if (strlen(root_full) > strlen(path_full)) 44 | { 45 | LOG_ERROR("too short"); 46 | return false; 47 | } 48 | if (0 != strncmp(root_full, path_full, strlen(root_full))) 49 | { 50 | LOG_ERROR("path root mismatch"); 51 | return false; 52 | } 53 | return true; 54 | } 55 | -------------------------------------------------------------------------------- /common/path.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef PATH_H 17 | #define PATH_H 1 18 | 19 | bool path_in_dir(const char *root, const char *path); 20 | 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /common/pidfile.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "pidfile.h" 26 | 27 | int pidfile_write(const char *filename) 28 | { 29 | FILE *fp; 30 | 31 | fp = fopen(filename, "w"); 32 | if (NULL == fp) 33 | return 1; 34 | 35 | fprintf(fp, "%d", getpid()); 36 | fclose(fp); 37 | 38 | return 0; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /common/pidfile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef PIDFILE_H 17 | #define PIDFILE_H 18 | 19 | int pidfile_write(const char *filename); 20 | 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /common/random.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | static int random_fd = -1; 34 | 35 | int random_init(void) 36 | { 37 | random_fd = open("/dev/urandom", O_RDONLY); 38 | if (random_fd < 0) 39 | return 1; 40 | return 0; 41 | } 42 | 43 | void random_read(uint8_t *buf, size_t len) 44 | { 45 | #if 1 46 | if (random_fd < 0) 47 | LOG_CRITICAL("random_init failed/not called"); 48 | 49 | if (len != read(random_fd, buf, len)) 50 | LOG_CRITICAL("random_read failed"); 51 | #else 52 | #warning BUST RANDOM 53 | while(len--) 54 | *buf++ = 4; 55 | #endif 56 | } 57 | 58 | -------------------------------------------------------------------------------- /common/random.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef RANDOM_H 17 | #define RANDOM_H 1 18 | 19 | int random_init(void); 20 | void random_read(uint8_t *buf, size_t len); 21 | 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /common/tdate_parse.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobyjaffey/mqtt-http-server/HEAD/common/tdate_parse.c -------------------------------------------------------------------------------- /common/tdate_parse.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobyjaffey/mqtt-http-server/HEAD/common/tdate_parse.h -------------------------------------------------------------------------------- /common/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | //#include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | void crash(void) 36 | { 37 | // crash for benefit of debugger 38 | char *p = NULL; 39 | *p = 0; 40 | } 41 | 42 | time_t getTime(void) 43 | { 44 | struct timeval tv; 45 | gettimeofday(&tv, NULL); 46 | return tv.tv_sec; 47 | } 48 | 49 | static uint8_t tolowercase(uint8_t ch) 50 | { 51 | if ((ch >= 'A') && (ch <= 'Z')) 52 | return ch + 0x20; // Convert uppercase to lowercase 53 | return ch; // Simply return original character if it doesn't require any adjustments 54 | } 55 | 56 | static int8_t parseHexDigit(uint8_t digit) 57 | { 58 | digit = tolowercase(digit); 59 | if (isdigit(digit)) 60 | return (int8_t)digit - '0'; 61 | if ((digit >= 'a') && (digit <= 'f')) 62 | return (int8_t)digit + 0xA - 'a'; 63 | return -1; // Error case - input wasn't a valid hex digit 64 | } 65 | 66 | int hexstring_parse(const char *hexstr, uint8_t *buf, size_t *buflen) 67 | { 68 | size_t hexstrlen = strlen(hexstr); 69 | size_t i; 70 | 71 | if (hexstrlen & 0x1) 72 | { 73 | LOG_DEBUG("hexstring_parse: not even"); 74 | return 1; 75 | } 76 | 77 | if (*buflen < hexstrlen/2) 78 | { 79 | LOG_DEBUG("hexstring_parse: buffer too small %d < %d", *buflen, hexstrlen/2); 80 | return 1; 81 | } 82 | 83 | for (i=0;i 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef UTIL_H 17 | #define UTIL_H 1 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | void crash(void); 24 | 25 | int hexstring_parse(const char *hexstr, uint8_t *buf, size_t *buflen); 26 | time_t getTime(void); 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /evhelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef EV_HELPER_H 17 | #define EV_HELPER_H 18 | 19 | #include 20 | 21 | struct ev_io_helper 22 | { 23 | ev_io io; 24 | void *userdata; 25 | }; 26 | typedef struct ev_io_helper ev_io_helper_t; 27 | 28 | struct ev_timer_helper 29 | { 30 | ev_timer timer; 31 | void *userdata; 32 | }; 33 | typedef struct ev_timer_helper ev_timer_helper_t; 34 | 35 | #endif 36 | 37 | -------------------------------------------------------------------------------- /htdocs/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MQTT-HTTP-Server 5 | 6 | 7 | 8 |

What is this?

9 |

10 | MQTT is a lightweight publish subscribe protocol. This server provides an HTTP bridge to an MQTT broker. It uses long-polling to provide streaming data to browsers. 11 | 12 |

Contact

13 |

14 | toby@sensemote.com. Find me on Twitter as @tobyjaffey and on #mqtt as trj. 15 | 16 |

Why?

17 | Bridging MQTT onto the web allows for browser based apps, mashups and experiments with real-time data. 18 | 19 | MQTT and HTTP are very different protocols. Read more about my take on the problem here. 20 | 21 | For browsers which support WebSockets, http://test.mosquitto.org/ws.html provides similar functionality 22 | 23 |

Example

24 | 25 |

Publish and subscribe

26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 |
37 | 38 | 39 | 40 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /htdocs/api.js: -------------------------------------------------------------------------------- 1 | var sensemote = (function () { 2 | "use strict"; 3 | // private 4 | var STREAM_TIMEOUT = 10000, // ms before timing out /stream request 5 | RETRY_DELAY = 500, // ms delay between /stream requests 6 | 7 | ids = {}, 8 | data_cb = null, 9 | error_cb = null, 10 | pollxhr = null, 11 | pollxhrRepeat = false, 12 | timedout = false, 13 | streamtimer, 14 | pollTimer, 15 | 16 | isdefined = function (o) { return typeof o !== 'undefined'; }, 17 | 18 | stop = function () { 19 | pollxhrRepeat = false; 20 | if (pollxhr !== null) 21 | pollxhr.abort(); 22 | }, 23 | 24 | reset = function () { 25 | stop(); 26 | ids = {}; 27 | }, 28 | 29 | pollLoop = function () { 30 | var i, lines, pair, topic, obj; 31 | pollxhr = new XMLHttpRequest(); 32 | pollxhr.onreadystatechange = function () { 33 | if (pollxhr.readyState === 4) { 34 | if (pollxhr.status === 200) { 35 | clearTimeout(streamtimer); 36 | if (null !== data_cb) { 37 | lines = pollxhr.responseText.split(/\r\n/); 38 | for (i = 0; i < lines.length; i++) { 39 | try { 40 | obj = JSON.parse(lines[i]); 41 | for (pair in obj) 42 | for (topic in obj[pair]) 43 | if (null != data_cb) 44 | data_cb(topic, obj[pair][topic]); 45 | } catch(e) { 46 | reset(); 47 | if (null != error_cb) 48 | error_cb('bad json'); 49 | } 50 | } 51 | } 52 | if (pollxhrRepeat) 53 | pollTimer = setTimeout(function (){pollLoop();}, RETRY_DELAY); 54 | } 55 | else { 56 | if (!timedout && pollxhrRepeat) { 57 | reset(); 58 | if (null != error_cb) 59 | error_cb(pollxhr.statusText, pollxhr.responseText); 60 | } 61 | } 62 | } 63 | }; 64 | 65 | streamtimer = setTimeout(function () { 66 | timedout = true; 67 | pollxhr.abort(); 68 | clearTimeout(pollTimer); 69 | timedout = false; 70 | if (pollxhrRepeat) 71 | setTimeout(function (){pollLoop();}, RETRY_DELAY); 72 | }, STREAM_TIMEOUT); 73 | 74 | pollxhr.open('POST', '/stream', true); 75 | pollxhr.send(JSON.stringify(ids)); 76 | }, 77 | 78 | start = function () { 79 | pollxhrRepeat = true; 80 | pollLoop(); 81 | }; 82 | 83 | reset(); 84 | 85 | // public 86 | return { 87 | stream : function () { 88 | stop(); 89 | start(); 90 | }, 91 | 92 | subscribe : function (topic, cbs) { 93 | var req = new XMLHttpRequest(); 94 | req.onreadystatechange = function () { 95 | if (req.readyState == 4) { 96 | if (req.status == 200) { 97 | var o = JSON.parse(req.responseText); 98 | ids[o.id] = o.seckey; 99 | if (isdefined(cbs) && isdefined(cbs.success)) 100 | cbs.success(); 101 | } 102 | else { 103 | if (isdefined(cbs) && isdefined(cbs.error)) 104 | cbs.error(req.statusText, req.responseText); 105 | reset(); 106 | if (null != error_cb) 107 | error_cb(); 108 | } 109 | } 110 | }; 111 | req.open('POST', '/subscribe', true); 112 | req.send(JSON.stringify([topic])); 113 | }, 114 | 115 | publish : function (topic, msg, cbs) { 116 | var o = {}; 117 | if (typeof msg === 'string') 118 | o[topic] = msg; 119 | else 120 | o[topic] = JSON.stringify(msg); 121 | var req = new XMLHttpRequest(); 122 | req.onreadystatechange = function () { 123 | if (req.readyState == 4) { 124 | if (req.status == 200) { 125 | if (isdefined(cbs) && isdefined(cbs.success)) 126 | cbs.success(topic, msg); 127 | } 128 | else { 129 | if (isdefined(cbs) && isdefined(cbs.error)) 130 | cbs.error(textStatus, errorThrown); 131 | reset(); 132 | if (null != error_cb) 133 | error_cb(); 134 | } 135 | } 136 | }; 137 | req.open('POST', '/publish', true); 138 | req.send(JSON.stringify(o)); 139 | }, 140 | 141 | register : function (cbs) { 142 | if (isdefined(cbs) && isdefined(cbs.data)) 143 | data_cb = cbs.data; 144 | if (isdefined(cbs) && isdefined(cbs.error)) 145 | error_cb = cbs.error; 146 | } 147 | } 148 | })(); // end sensemote 149 | 150 | 151 | ////////////////////////////////////// 152 | 153 | /* 154 | // sensemote.publish('sensemote/test', 'wibble', {success:function () { window.console.log('ok');}}) 155 | // sensemote.publish('sensemote/test', {'bob':'bob'}, {success:function () { window.console.log('ok');}}) 156 | 157 | var timer; 158 | function reconnect() 159 | { 160 | clearTimeout(timer); 161 | timer = setTimeout(function (){startStreaming();}, 1000); 162 | } 163 | 164 | function startStreaming() 165 | { 166 | sensemote.register({ 167 | data: function (t, m) {window.console.log('rx '+t+' '+m);}, 168 | error: function () {reconnect();}, 169 | }); 170 | sensemote.subscribe('sensemote/apidemo/#', { 171 | success: function () { 172 | sensemote.subscribe('sensemote/monkey/#', {success: sensemote.stream}); 173 | } 174 | }); 175 | } 176 | 177 | setInterval(function (){sensemote.publish('sensemote/apidemo/foo', 'bar');}, 10000); 178 | startStreaming(); 179 | */ 180 | -------------------------------------------------------------------------------- /htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Checkbox example 4 | 5 | 6 | 7 | 8 | 9 |

10 |

Well done, you've successfull installed mqtt-http-server

11 | For more information see about.html. 12 |

13 | 14 |
15 | 16 |

17 | This page shows a simple example of subscribing and publishing to MQTT topics. Clicking the buttons publishes. The checkboxes are subscribed to topics. 18 |

19 | 20 |

21 | sensemote/devices/button1 22 |

23 | 24 | 25 | 26 |

27 | 28 | sensemote/devices/button2 29 |

30 | 31 | 32 | 33 |

34 | 35 |

36 | 37 |

38 | The checkboxes can be controlled from any MQTT client, eg. 39 |

 40 | mosquitto_pub -h test.mosquitto.org -t "sensemote/devices/button1" -m "1"
 41 | mosquitto_pub -h test.mosquitto.org -t "sensemote/devices/button1" -m "0"
 42 | mosquitto_pub -h test.mosquitto.org -t "sensemote/devices/button2" -m "1"
 43 | mosquitto_pub -h test.mosquitto.org -t "sensemote/devices/button2" -m "1"
 44 | 
45 |

46 | 47 |

48 | A client can also subscribe to the buttons, eg. 49 |

 50 | mosquitto_sub -v -h test.mosquitto.org -t "sensemote/devices/button1"
 51 | 
52 |

53 | 54 |

55 | This page can be opened by multiple clients and used to share data via the MQTT broker. 56 |

57 | 58 | 59 | 60 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /httpd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef HTTPD_H 17 | #define HTTPD_H 1 18 | 19 | #include 20 | #include "ebb.h" 21 | #include "idset.h" 22 | #include "tailq.h" 23 | 24 | typedef int (*query_iterate_func)(const char *key, const char *val, void *userdata); 25 | 26 | #define MAX_VHOST 32 27 | 28 | typedef struct 29 | { 30 | char *rootdir; 31 | char *hostname; 32 | char *name; 33 | } httpd_virt; 34 | 35 | typedef struct 36 | { 37 | ebb_connection *conn; 38 | uint32_t connid; 39 | char *body; // request body 40 | size_t body_len; 41 | char *uri; // request uri (including query str) 42 | char *path; // request path part of uri 43 | char *auth; 44 | char host[512]; 45 | bool parsing_auth_header; 46 | bool parsing_ifmodifiedsince_header; 47 | bool parsing_host_header; 48 | } ebb_request_info; 49 | 50 | typedef struct 51 | { 52 | ebb_connection *conn; 53 | uint32_t connid; // unique id for this connection 54 | bool keepalive; // should this connection be kept alive after request (>= HTTP1.1) 55 | tailq_t *rsp_q; 56 | bool writing; // busy writing an item from rsp_q 57 | bool finished; // user sets this to say, after flushing queue, hangup. 58 | size_t push_len; // total bytes pending 59 | 60 | bool have_mid; 61 | int mid; 62 | 63 | int version_major; 64 | int version_minor; 65 | 66 | bool reset; 67 | 68 | char writebuf[16384]; 69 | char mimetype[512]; 70 | int http_status; 71 | char *errorbody; 72 | time_t ifmodifiedsince; 73 | char last_modified[512]; 74 | 75 | char *managed_str; // userdata, but owned by this struct and freed by destructor 76 | 77 | idset_t *streamids; // set of streams associated with this connection 78 | bool rawmode; // immediate response wanted 79 | 80 | char ipaddr[32]; 81 | 82 | } ebb_connection_info; 83 | 84 | extern int httpd_init(ebb_server *server, struct ev_loop *loop); 85 | extern int httpd_listen(ebb_server *server, const char *rootdir, uint16_t port, const char *cert, const char *key); 86 | extern int httpd_push(uint32_t connid, const char *msg, bool flush); 87 | extern int httpd_pushbin(uint32_t connid, const uint8_t *data, size_t len, bool flush); 88 | extern int httpd_close(uint32_t connid); 89 | extern void httpd_open_cb(uint32_t connid); 90 | extern void httpd_close_cb(uint32_t connid); 91 | extern int httpd_request_cb(uint32_t connid, const char *host, uint32_t method, const char *uri, const char *path, const char *body_data, size_t body_len, const char *auth); 92 | extern int httpd_flush_stale(uint32_t connid); 93 | extern int httpd_printf(uint32_t connid, const char *format, ...); 94 | extern int httpd_query_iterate(const char *uri_str, query_iterate_func f, void *userdata); 95 | extern ebb_connection_info *httpd_get_conninfo(uint32_t connid); 96 | extern void httpd_timeout_conn(uint32_t connid); 97 | extern void httpd_request_complete(uint32_t connid, ebb_connection_info *conninfo); 98 | 99 | extern int httpd_addvirt(ebb_server *server, const char *rootdir, const char *hostname, const char *name); 100 | extern int httpd_lookupvirt(const char *host); 101 | 102 | #endif 103 | 104 | -------------------------------------------------------------------------------- /httpdconnlist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include "httpd.h" 19 | #include "httpdconnlist.h" 20 | #include 21 | 22 | struct httpdconn_node 23 | { 24 | RB_ENTRY(httpdconn_node) entry; 25 | uint32_t connid; 26 | ebb_connection *conn; 27 | }; 28 | 29 | static int httpdconn_cmp(struct httpdconn_node *e1, struct httpdconn_node *e2) 30 | { 31 | return (e1->connid < e2->connid ? -1 : e1->connid > e2->connid); 32 | } 33 | 34 | RB_HEAD(httpdconnlist, httpdconn_node) httpdconn_head = RB_INITIALIZER(&httpdconn_head); 35 | RB_PROTOTYPE(httpdconnlist, httpdconn_node, entry, httpdconn_cmp); 36 | RB_GENERATE(httpdconnlist, httpdconn_node, entry, httpdconn_cmp); 37 | 38 | int httpdconnlist_insert(uint32_t connid, ebb_connection *conn) 39 | { 40 | struct httpdconn_node *data; 41 | 42 | if (NULL == (data = malloc(sizeof(struct httpdconn_node)))) 43 | return 1; 44 | 45 | data->connid = connid; 46 | data->conn = conn; 47 | 48 | if (NULL == RB_INSERT(httpdconnlist, &httpdconn_head, data)) // NULL means OK 49 | { 50 | return 0; 51 | } 52 | else 53 | { 54 | LOG_CRITICAL("httpdconnlist_insert connid collision"); // can't happen 55 | return 1; 56 | } 57 | 58 | } 59 | 60 | int httpdconnlist_remove(uint32_t connid) 61 | { 62 | struct httpdconn_node key; 63 | struct httpdconn_node *res; 64 | key.connid = connid; 65 | res = RB_FIND(httpdconnlist, &httpdconn_head, &key); 66 | if (NULL == res) 67 | return 1; 68 | RB_REMOVE(httpdconnlist, &httpdconn_head, res); 69 | free(res); 70 | return 0; 71 | } 72 | 73 | struct ebb_connection *httpdconnlist_find(uint32_t connid) 74 | { 75 | struct httpdconn_node key; 76 | struct httpdconn_node *res; 77 | key.connid = connid; 78 | res = RB_FIND(httpdconnlist, &httpdconn_head, &key); 79 | if (NULL == res) 80 | return NULL; 81 | return res->conn; 82 | } 83 | 84 | void httpdconnlist_foreach(httpdconnlist_iterate_func f, void *userdata) 85 | { 86 | struct httpdconn_node *i; 87 | RB_FOREACH(i, httpdconnlist, &httpdconn_head) 88 | { 89 | f(i->connid, i->conn, userdata); 90 | } 91 | } 92 | 93 | 94 | -------------------------------------------------------------------------------- /httpdconnlist.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef HTTPDCONNLIST_H 17 | #define HTTPDCONNLIST_H 1 18 | 19 | #include "httpd.h" 20 | 21 | typedef void(*httpdconnlist_iterate_func)(uint32_t connid, ebb_connection *conn, void *userdata); 22 | 23 | extern int httpdconnlist_insert(uint32_t connid, ebb_connection *conn); 24 | extern int httpdconnlist_remove(uint32_t connid); 25 | extern struct ebb_connection *httpdconnlist_find(uint32_t connid); 26 | extern void httpdconnlist_foreach(httpdconnlist_iterate_func f, void *data); 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /idset.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include "idset.h" 20 | #include 21 | 22 | static int id_cmp(struct idset_node *e1, struct idset_node *e2) 23 | { 24 | return (e1->id < e2->id ? -1 : e1->id > e2->id); 25 | } 26 | 27 | RB_GENERATE(idset, idset_node, entry, id_cmp); 28 | 29 | void idset_destroy(idset_t *ids) 30 | { 31 | if (NULL != ids) 32 | { 33 | struct idset_node *var; 34 | struct idset_node *nxt; 35 | 36 | for (var = RB_MIN(idset, &ids->root); var != NULL; var = nxt) 37 | { 38 | nxt = RB_NEXT(idset, &ids->root, var); 39 | RB_REMOVE(idset, &ids->root, var); 40 | free(var); 41 | } 42 | 43 | free(ids); 44 | } 45 | } 46 | 47 | idset_t *idset_create(void) 48 | { 49 | idset_t *ids; 50 | 51 | if (NULL == (ids = (idset_t *)calloc(sizeof(idset_t), 1))) 52 | goto fail; 53 | 54 | RB_INIT(&ids->root); 55 | 56 | return ids; 57 | fail: 58 | idset_destroy(ids); 59 | return NULL; 60 | } 61 | 62 | int idset_insert(idset_t *ids, uint32_t id) 63 | { 64 | struct idset_node *data; 65 | 66 | if (NULL == (data = malloc(sizeof(struct idset_node)))) 67 | return 1; 68 | 69 | data->id = id; 70 | 71 | if (NULL == RB_INSERT(idset, &ids->root, data)) // NULL means OK 72 | { 73 | ids->count++; 74 | return 0; 75 | } 76 | else 77 | { 78 | LOG_ERROR("idset_insert id collision"); 79 | crash(); 80 | free(data); 81 | return 1; 82 | } 83 | } 84 | 85 | int idset_remove(idset_t *ids, uint32_t id) 86 | { 87 | struct idset_node key; 88 | struct idset_node *res; 89 | 90 | key.id = id; 91 | res = RB_FIND(idset, &ids->root, &key); 92 | if (NULL == res) 93 | return 1; 94 | RB_REMOVE(idset, &ids->root, res); 95 | free(res); 96 | ids->count--; 97 | return 0; 98 | } 99 | 100 | bool idset_find(idset_t *ids, uint32_t id) 101 | { 102 | struct idset_node key; 103 | struct idset_node *res; 104 | 105 | key.id = id; 106 | res = RB_FIND(idset, &ids->root, &key); 107 | if (NULL == res) 108 | return false; 109 | return true; 110 | } 111 | 112 | void idset_foreach(idset_t *ids, idset_iterate_func f, void *userdata) 113 | { 114 | struct idset_node *var, *nxt; 115 | // can't use foreach if we want to allow deletion 116 | for (var = RB_MIN(idset, &ids->root); var != NULL; var = nxt) 117 | { 118 | nxt = RB_NEXT(idset, &ids->root, var); 119 | if (f(ids, var->id, userdata)) 120 | { 121 | RB_REMOVE(idset, &ids->root, var); 122 | free(var); 123 | } 124 | } 125 | } 126 | 127 | 128 | void idset_print(idset_t *ids) 129 | { 130 | struct idset_node *i; 131 | RB_FOREACH(i, idset, &ids->root) 132 | { 133 | LOG_INFO("%u", i->id); 134 | } 135 | } 136 | 137 | 138 | size_t idset_size(idset_t *ids) 139 | { 140 | return ids->count; 141 | } 142 | 143 | -------------------------------------------------------------------------------- /idset.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef IDSET_H 17 | #define IDSET_H 1 18 | 19 | #include 20 | 21 | struct idset_node 22 | { 23 | RB_ENTRY(idset_node) entry; 24 | uint32_t id; 25 | }; 26 | 27 | typedef struct 28 | { 29 | RB_HEAD(idset, idset_node) root; 30 | size_t count; 31 | } idset_t; 32 | 33 | RB_PROTOTYPE(idset, idset_node, entry, id_cmp); 34 | 35 | typedef bool(*idset_iterate_func)(idset_t *ids, uint32_t id, void *userdata); 36 | 37 | extern idset_t *idset_create(void); 38 | extern void idset_destroy(idset_t *ids); 39 | extern int idset_insert(idset_t *ids, uint32_t id); 40 | extern int idset_remove(idset_t *ids, uint32_t id); 41 | extern bool idset_find(idset_t *ids, uint32_t id); 42 | extern void idset_foreach(idset_t *ids, idset_iterate_func f, void *data); 43 | extern size_t idset_size(idset_t *ids); 44 | extern void idset_print(idset_t *ids); 45 | 46 | #endif 47 | 48 | -------------------------------------------------------------------------------- /libebb/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | examples/hello_world 3 | test_request_parser 4 | ebb_request_parser.c 5 | tags 6 | -------------------------------------------------------------------------------- /libebb/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /libebb/Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | DEP = ebb.h ebb_request_parser.h rbtree.h 4 | SRC = ebb.c ebb_request_parser.c rbtree.c 5 | OBJ = ${SRC:.c=.o} 6 | 7 | VERSION = 0.1 8 | NAME=libebb 9 | OUTPUT_LIB=$(NAME).$(SUFFIX).$(VERSION) 10 | OUTPUT_A=$(NAME).a 11 | 12 | LINKER=$(CC) $(LDOPT) 13 | 14 | all: $(OUTPUT_A) 15 | 16 | $(OUTPUT_LIB): $(OBJ) 17 | @echo LINK $@ 18 | $(LINKER) -o $(OUTPUT_LIB) $(OBJ) $(SONAME) $(LIBS) 19 | 20 | $(OUTPUT_A): $(OBJ) 21 | @echo AR $@ 22 | $(AR) cru $(OUTPUT_A) $(OBJ) 23 | @echo RANLIB $@ 24 | $(RANLIB) $(OUTPUT_A) 25 | 26 | .c.o: 27 | ${CC} -c ${CFLAGS} $< 28 | 29 | ${OBJ}: ${DEP} 30 | 31 | ebb_request_parser.c: ebb_request_parser.rl 32 | @echo RAGEL $< 33 | ragel -s -G2 $< -o $@ 34 | 35 | test: test_request_parser test_rbtree 36 | time ./test_request_parser 37 | ./test_rbtree 38 | 39 | test_rbtree: test_rbtree.o $(OUTPUT_A) 40 | @echo BUILDING test_rbtree 41 | @$(CC) $(CFLAGS) -o $@ $< $(OUTPUT_A) 42 | 43 | test_request_parser: test_request_parser.o $(OUTPUT_A) 44 | @echo BUILDING test_request_parser 45 | @$(CC) $(CFLAGS) -o $@ $< $(OUTPUT_A) 46 | 47 | examples: examples/hello_world 48 | 49 | examples/hello_world: examples/hello_world.c $(OUTPUT_A) 50 | @echo BUILDING examples/hello_world 51 | @$(CC) -I. $(LIBS) $(CFLAGS) -lev -o $@ $^ 52 | 53 | clean: 54 | @echo CLEANING 55 | @rm -f ${OBJ} $(OUTPUT_A) $(OUTPUT_LIB) libebb-${VERSION}.tar.gz 56 | @rm -f test_rbtree test_request_parser 57 | @rm -f examples/hello_world examples/hello_world.o 58 | 59 | clobber: clean 60 | @echo CLOBBERING 61 | @rm -f ebb_request_parser.c 62 | 63 | dist: clean $(SRC) 64 | @echo CREATING dist tarball 65 | @mkdir -p ${NAME}-${VERSION} 66 | @cp -R doc examples LICENSE Makefile README config.mk \ 67 | ebb_request_parser.rl ${SRC} ${DEP} ${NAME}-${VERSION} 68 | @tar -cf ${NAME}-${VERSION}.tar ${NAME}-${VERSION} 69 | @gzip ${NAME}-${VERSION}.tar 70 | @rm -rf ${NAME}-${VERSION} 71 | 72 | install: $(OUTPUT_LIB) $(OUTPUT_A) 73 | @echo INSTALLING ${OUTPUT_A} and ${OUTPUT_LIB} to ${PREFIX}/lib 74 | install -d -m755 ${PREFIX}/lib 75 | install -d -m755 ${PREFIX}/include 76 | install -m644 ${OUTPUT_A} ${PREFIX}/lib 77 | install -m755 ${OUTPUT_LIB} ${PREFIX}/lib 78 | ln -sfn $(PREFIX)/lib/$(OUTPUT_LIB) $(PREFIX)/lib/$(NAME).so 79 | @echo INSTALLING headers to ${PREFIX}/include 80 | install -m644 ebb.h ebb_request_parser.h ${PREFIX}/include 81 | 82 | uninstall: 83 | @echo REMOVING so from ${PREFIX}/lib 84 | rm -f ${PREFIX}/lib/${NAME}.* 85 | @echo REMOVING headers from ${PREFIX}/include 86 | rm -f ${PREFIX}/include/ebb.h 87 | rm -f ${PREFIX}/include/ebb_request_parser.h 88 | 89 | upload_website: 90 | scp -r doc/index.html doc/icon.png rydahl@tinyclouds.org:~/web/public/libebb 91 | 92 | .PHONY: all options clean clobber dist install uninstall test examples upload_website 93 | -------------------------------------------------------------------------------- /libebb/README: -------------------------------------------------------------------------------- 1 | see doc/index.html and examples/hello_world.c for explanation 2 | 3 | webpage: http://tinyclouds.org/libebb/ 4 | git repository: http://github.com/ry/libebb/tree/master 5 | 6 | To build libebb please edit config.mk to reflect your system's parameters. 7 | 8 | -------------------------------------------------------------------------------- /libebb/config.mk: -------------------------------------------------------------------------------- 1 | # libev 2 | EVINC = ../libev 3 | EVLIB = ../libev/.libs 4 | EVLIBS = -L${EVLIB} -lev 5 | 6 | # GnuTLS, comment if you don't want it (necessary for HTTPS) 7 | GNUTLSLIB = /usr/lib 8 | GNUTLSINC = /usr/include 9 | GNUTLSLIBS = -L${GNUTLSLIB} -lgnutls 10 | GNUTLSFLAGS = -DHAVE_GNUTLS 11 | INCS += -I${GNUTLSINC} 12 | 13 | # includes and libs 14 | INCS += -I${EVINC} 15 | LIBS = ${EVLIBS} ${GNUTLSLIBS} 16 | 17 | # flags 18 | CPPFLAGS = -DVERSION=\"$(VERSION)\" ${GNUTLSFLAGS} 19 | CFLAGS += -O2 -g -Werror -Wall ${INCS} ${CPPFLAGS} -fPIC -DEBB_DEFAULT_TIMEOUT=30.0 20 | 21 | LDFLAGS = -s ${LIBS} 22 | LDOPT = -shared 23 | SUFFIX = so 24 | SONAME = -Wl,-soname,$(OUTPUT_LIB) 25 | 26 | # Solaris 27 | #CFLAGS = -fast ${INCS} -DVERSION=\"$(VERSION)\" -fPIC 28 | #LDFLAGS = ${LIBS} 29 | #SONAME = 30 | 31 | # Darwin 32 | # LDOPT = -dynamiclib 33 | # SUFFIX = dylib 34 | # SONAME = -current_version $(VERSION) -compatibility_version $(VERSION) 35 | 36 | # compiler and linker 37 | CC = cc 38 | RANLIB = ranlib 39 | -------------------------------------------------------------------------------- /libebb/doc/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobyjaffey/mqtt-http-server/HEAD/libebb/doc/icon.png -------------------------------------------------------------------------------- /libebb/doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 |
42 | 43 |

libebb

44 | 45 |

46 | libebb is a lightweight HTTP server library for C. It lays the 47 | foundation for writing a web server by providing the socket juggling 48 | and request parsing. By implementing the HTTP/1.1 grammar provided in 49 | RFC2612, libebb understands most most valid HTTP/1.1 connections 50 | (persistent, pipelined, and chunked requests included) and rejects 51 | invalid or malicious requests. libebb supports SSL over HTTP. 52 |

53 | 54 |

55 | The library embraces a minimalistic single-threaded evented design. 56 | No control is removed from the user. For example, all allocations are 57 | done through callbacks so that the user might implement in optimal 58 | ways for their specific application. By design libebb is not 59 | thread-safe and all provided callbacks must not block. libebb uses 60 | the high-performance 61 | libev event loop, but does not control it. The user of the library may 62 | start and stop the loop at will, they may attach thier own watchers. 63 |

64 | 65 |

66 | libebb depends on POSIX sockets, libev, and optionally GnuTLS. 67 |

68 | 69 |

70 | libebb is in the early stages of development and probably contains 71 | many bugs. The API is subject to radical changes. If you're 72 | interested checkout 73 | the source code and join the mailing 75 | list. A release will be made when it proves stable. 76 |

77 | 78 |

libebb is released under the 80 | X11 license.

81 | 82 |

Usage

83 | 84 |

85 | libebb is a simple API, mostly it is providing callbacks. There are 86 | two types of callbacks that one will work with: 87 |

88 | 89 |
    90 |
  • callbacks to allocate and initialize data for libebb. These are 91 | named new_* like new_connection and 92 | new_request
  • 93 |
  • callbacks which happen on an event and might provide a pointer to 94 | a chunk of data. These are named on_* like 95 | on_body and on_close.
  • 96 |
97 | 98 |

99 | In libebb there are three important classes: ebb_server, 100 | ebb_connection, and ebb_request. 101 | Each server has many peer connections. Each peer connection may have many 102 | requests. 103 | There are two additional classes ebb_buf and ebb_request_parser 104 | which may or may not be useful. 105 |

106 | 107 |

ebb_server

108 |

109 | ebb_server represents a single web server listening on a 110 | single port. The user must allocate the structure themselves, then 111 | call ebb_server_init() and provide a libev event loop. 112 | ebb_server_set_secure() will make the server understand 113 | HTTPS connections. 114 |

115 | 116 |

117 | After initialized the ebb_server_listen_on_port() can be 118 | called to open the server up to new connections. libebb does not 119 | control the event loop, it is the user's responsibility to start the 120 | event loop (using ev_loop()) after 121 | ebb_server_listen_on_port() is called. 122 |

123 | 124 |

125 | To accept connections you must provide the new server with a callback 126 | called new_connection. This callback must return an allocated 127 | and initialized ebb_connection structure. 128 | To set this callback do 129 |

130 | 131 |
my_server->new_connection = my_new_connection_callback;
132 | 133 |

134 | Additional documentation can be found in ebb.h 135 |

136 | 137 |

ebb_connection

138 | 139 |

140 | This structure contains information and callbacks for a single client 141 | connection. It is allocated and initialized through the 142 | new_connection callback in ebb_server. 143 | To initialize a newly allocated ebb_connection use 144 | ebb_connection_init(). 145 |

146 | 147 |

148 | After ebb_connection_init() is called a number of 149 | callbacks can be set: new_request, new_buf, 150 | on_timeout, and on_close. 151 |

152 | 153 |

154 | When an ebb_connection is returned to an 155 | ebb_server, data is immediately data is read from the 156 | socket. This data must be stored somewhere. Because libebb is 157 | agnostic about allocation decisions, it passes this off to the user in 158 | the form of a callback: connection->new_buf. This 159 | callback returns a newly allocated and initialized 160 | ebb_buf structure. How much libebb attempts to read from 161 | the socket is determined by how large the returned 162 | ebb_buf structure is. Using new_buf is 163 | optional. By default libebb reads data into a static buffer 164 | (allocated at compile time), writing over it on each read. In many 165 | web server using the static buffer will be sufficent because callbacks 166 | made during the parsing will buffer the data elsewhere. Providing a 167 | new_buf callback is necessary only if you want to save 168 | the raw data coming from the socket. 169 |

170 | 171 |

172 | The new_request callback is called at the beginning of a 173 | request. It must return a newly allocated and initialized 174 | ebb_request structure. Because HTTP/1.1 supports peristant 176 | connections, there may be many requests per connection. 177 |

178 | 179 |

180 | You may access the file descriptor for the client socket inside the 181 | ebb_connection structure. Writing the response, in valid 182 | HTTP, is the user's responsibility. Remember, requests must be 183 | returned to client in the same order that they were received. 184 |

185 | 186 |

187 | A convience function, ebb_connection_write, is provided 188 | which will write a single string to the peer. You may use 189 | this function or you may write to the file descriptor directly. 190 |

191 | 192 |

193 | To close a peer connection use 194 | ebb_connnection_schedule_close(). Because SSL may require 195 | some additional communication to close the connection properly, the 196 | file descriptor cannot be closed immediately. The 197 | on_close callback will be made when the peer socket is 198 | finally closed. 199 | Only once on_close is called may the 200 | user free the ebb_connection structure. 201 |

202 | 203 | 204 |

ebb_request

205 | 206 |

207 | This structure provides information about a request. For example, 208 | request->method == EBB_POST would mean the method of 209 | the request is POST. There are also many callbacks 210 | which can be set to handle data from a request as it is parsed. 211 |

212 | 213 |

214 | The on_uri callback and all other 215 | ebb_element_cb callbacks provide pointers to request 216 | data. The annoying thing is they do not necessarily provide a 217 | complete string. This is because the reads from the socket may not 218 | contain an entire request and for efficency libebb does not attempt to 219 | buffer the data. Theoretically, one might receive an 220 | on_uri callback 10 times, each providing just a single 221 | character of the request's URI. See ebb_request_parser.h for 222 | a full list of callbacks that you may provide. (If you don't set them, 223 | they are ignored.) 224 |

225 | 226 |

227 | The on_complete callback is called at the end of 228 | each request. 229 | Only once on_complete is called may the 230 | user free the ebb_request structure. 231 |

232 | 233 |

Example

234 | 235 |

236 | A simple example is provided in examples/hello_world.c. 237 |

238 | 239 |
240 | 241 | -------------------------------------------------------------------------------- /libebb/ebb.h: -------------------------------------------------------------------------------- 1 | /* This file is part of libebb. 2 | * 3 | * Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | #ifndef EBB_H 26 | #define EBB_H 27 | 28 | #include 29 | #include 30 | #include 31 | #ifdef HAVE_GNUTLS 32 | # include 33 | # include "rbtree.h" /* for ebb_server.session_cache */ 34 | #endif 35 | #include "ebb_request_parser.h" 36 | 37 | #define EBB_MAX_CONNECTIONS 1024 38 | // #define EBB_DEFAULT_TIMEOUT 30.0 39 | 40 | #define EBB_AGAIN 0 41 | #define EBB_STOP 1 42 | 43 | typedef struct ebb_server ebb_server; 44 | typedef struct ebb_connection ebb_connection; 45 | typedef void (*ebb_after_write_cb) (ebb_connection *connection); 46 | typedef void (*ebb_connection_cb)(ebb_connection *connection, void *data); 47 | 48 | struct ebb_server { 49 | int fd; /* ro */ 50 | struct sockaddr_in sockaddr; /* ro */ 51 | socklen_t socklen; /* ro */ 52 | char port[6]; /* ro */ 53 | struct ev_loop *loop; /* ro */ 54 | unsigned listening:1; /* ro */ 55 | unsigned secure:1; /* ro */ 56 | #ifdef HAVE_GNUTLS 57 | gnutls_certificate_credentials_t credentials; /* private */ 58 | struct rbtree_t session_cache; /* private */ 59 | #endif 60 | ev_io connection_watcher; /* private */ 61 | 62 | /* Public */ 63 | 64 | /* Allocates and initializes an ebb_connection. NULL by default. */ 65 | ebb_connection* (*new_connection) (ebb_server*, struct sockaddr_in*); 66 | 67 | void *data; 68 | }; 69 | 70 | struct ebb_connection { 71 | int fd; /* ro */ 72 | struct sockaddr_in sockaddr; /* ro */ 73 | socklen_t socklen; /* ro */ 74 | ebb_server *server; /* ro */ 75 | char *ip; /* ro */ 76 | unsigned open:1; /* ro */ 77 | 78 | const char *to_write; /* ro */ 79 | size_t to_write_len; /* ro */ 80 | size_t written; /* ro */ 81 | ebb_after_write_cb after_write_cb; /* ro */ 82 | 83 | ebb_request_parser parser; /* private */ 84 | ev_io write_watcher; /* private */ 85 | ev_io read_watcher; /* private */ 86 | ev_timer timeout_watcher; /* private */ 87 | ev_timer goodbye_watcher; /* private */ 88 | #ifdef HAVE_GNUTLS 89 | ev_io handshake_watcher; /* private */ 90 | gnutls_session_t session; /* private */ 91 | ev_io goodbye_tls_watcher; /* private */ 92 | #endif 93 | 94 | /* Public */ 95 | 96 | ebb_request* (*new_request) (ebb_connection*); 97 | 98 | /* Returns EBB_STOP or EBB_AGAIN. NULL by default. */ 99 | int (*on_timeout) (ebb_connection*); 100 | 101 | void (*on_close) (ebb_connection*); 102 | 103 | void *data; 104 | }; 105 | 106 | void ebb_server_init (ebb_server *server, struct ev_loop *loop); 107 | #ifdef HAVE_GNUTLS 108 | int ebb_server_set_secure (ebb_server *server, const char *cert_file, 109 | const char *key_file); 110 | #endif 111 | int ebb_server_listen_on_port (ebb_server *server, const int port); 112 | int ebb_server_listen_on_fd (ebb_server *server, const int sfd); 113 | void ebb_server_unlisten (ebb_server *server); 114 | 115 | void ebb_connection_init (ebb_connection *); 116 | void ebb_connection_schedule_close (ebb_connection *); 117 | void ebb_connection_reset_timeout (ebb_connection *); 118 | int ebb_connection_write (ebb_connection *, const char *buf, size_t len, ebb_after_write_cb); 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /libebb/ebb_request_parser.h: -------------------------------------------------------------------------------- 1 | /* This file is part of the libebb web server library 2 | * 3 | * Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) 4 | * All rights reserved. 5 | * 6 | * This parser is based on code from Zed Shaw's Mongrel. 7 | * Copyright (c) 2005 Zed A. Shaw 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining 10 | * a copy of this software and associated documentation files (the 11 | * "Software"), to deal in the Software without restriction, including 12 | * without limitation the rights to use, copy, modify, merge, publish, 13 | * distribute, sublicense, and/or sell copies of the Software, and to 14 | * permit persons to whom the Software is furnished to do so, subject to 15 | * the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | #ifndef ebb_request_parser_h 29 | #define ebb_request_parser_h 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | 35 | #include 36 | 37 | typedef struct ebb_request ebb_request; 38 | typedef struct ebb_request_parser ebb_request_parser; 39 | typedef void (*ebb_header_cb)(ebb_request*, const char *at, size_t length, int header_index); 40 | typedef void (*ebb_element_cb)(ebb_request*, const char *at, size_t length); 41 | 42 | #define EBB_MAX_MULTIPART_BOUNDARY_LEN 20 43 | 44 | /* HTTP Methods */ 45 | #define EBB_COPY 0x00000001 46 | #define EBB_DELETE 0x00000002 47 | #define EBB_GET 0x00000004 48 | #define EBB_HEAD 0x00000008 49 | #define EBB_LOCK 0x00000010 50 | #define EBB_MKCOL 0x00000020 51 | #define EBB_MOVE 0x00000040 52 | #define EBB_OPTIONS 0x00000080 53 | #define EBB_POST 0x00000100 54 | #define EBB_PROPFIND 0x00000200 55 | #define EBB_PROPPATCH 0x00000400 56 | #define EBB_PUT 0x00000800 57 | #define EBB_TRACE 0x00001000 58 | #define EBB_UNLOCK 0x00002000 59 | 60 | /* Transfer Encodings */ 61 | #define EBB_IDENTITY 0x00000001 62 | #define EBB_CHUNKED 0x00000002 63 | 64 | struct ebb_request { 65 | int method; 66 | int transfer_encoding; /* ro */ 67 | int expect_continue; /* ro */ 68 | unsigned int version_major; /* ro */ 69 | unsigned int version_minor; /* ro */ 70 | int number_of_headers; /* ro */ 71 | int keep_alive; /* private - use ebb_request_should_keep_alive */ 72 | size_t content_length; /* ro - 0 if unknown */ 73 | size_t body_read; /* ro */ 74 | 75 | /* Public - ordered list of callbacks */ 76 | ebb_element_cb on_path; 77 | ebb_element_cb on_query_string; 78 | ebb_element_cb on_uri; 79 | ebb_element_cb on_fragment; 80 | ebb_header_cb on_header_field; 81 | ebb_header_cb on_header_value; 82 | void (*on_headers_complete)(ebb_request *); 83 | ebb_element_cb on_body; 84 | void (*on_complete)(ebb_request *); 85 | void *data; 86 | }; 87 | 88 | struct ebb_request_parser { 89 | int cs; /* private */ 90 | size_t chunk_size; /* private */ 91 | unsigned eating:1; /* private */ 92 | ebb_request *current_request; /* ro */ 93 | const char *header_field_mark; 94 | const char *header_value_mark; 95 | const char *query_string_mark; 96 | const char *path_mark; 97 | const char *uri_mark; 98 | const char *fragment_mark; 99 | 100 | /* Public */ 101 | ebb_request* (*new_request)(void*); 102 | void *data; 103 | }; 104 | 105 | void ebb_request_parser_init(ebb_request_parser *parser); 106 | size_t ebb_request_parser_execute(ebb_request_parser *parser, const char *data, size_t len); 107 | int ebb_request_parser_has_error(ebb_request_parser *parser); 108 | int ebb_request_parser_is_finished(ebb_request_parser *parser); 109 | void ebb_request_init(ebb_request *); 110 | int ebb_request_should_keep_alive(ebb_request *request); 111 | #define ebb_request_has_body(request) \ 112 | (request->transfer_encoding == EBB_CHUNKED || request->content_length > 0 ) 113 | 114 | #ifdef __cplusplus 115 | } 116 | #endif 117 | #endif 118 | -------------------------------------------------------------------------------- /libebb/ebb_request_parser.rl: -------------------------------------------------------------------------------- 1 | /* This file is part of the libebb web server library 2 | * 3 | * Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) 4 | * All rights reserved. 5 | * 6 | * This parser is based on code from Zed Shaw's Mongrel. 7 | * Copyright (c) 2005 Zed A. Shaw 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining 10 | * a copy of this software and associated documentation files (the 11 | * "Software"), to deal in the Software without restriction, including 12 | * without limitation the rights to use, copy, modify, merge, publish, 13 | * distribute, sublicense, and/or sell copies of the Software, and to 14 | * permit persons to whom the Software is furnished to do so, subject to 15 | * the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | #include "ebb_request_parser.h" 29 | 30 | #include 31 | #include 32 | 33 | static int unhex[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 34 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 35 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 36 | , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 37 | ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 38 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 39 | ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 40 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 41 | }; 42 | #define TRUE 1 43 | #define FALSE 0 44 | #define MIN(a,b) (a < b ? a : b) 45 | 46 | #define REMAINING (pe - p) 47 | #define CURRENT (parser->current_request) 48 | #define CONTENT_LENGTH (parser->current_request->content_length) 49 | #define CALLBACK(FOR) \ 50 | if(CURRENT && parser->FOR##_mark && CURRENT->on_##FOR) { \ 51 | CURRENT->on_##FOR( CURRENT \ 52 | , parser->FOR##_mark \ 53 | , p - parser->FOR##_mark \ 54 | ); \ 55 | } 56 | #define HEADER_CALLBACK(FOR) \ 57 | if(CURRENT && parser->FOR##_mark && CURRENT->on_##FOR) { \ 58 | CURRENT->on_##FOR( CURRENT \ 59 | , parser->FOR##_mark \ 60 | , p - parser->FOR##_mark \ 61 | , CURRENT->number_of_headers \ 62 | ); \ 63 | } 64 | #define END_REQUEST \ 65 | if(CURRENT && CURRENT->on_complete) \ 66 | CURRENT->on_complete(CURRENT); \ 67 | CURRENT = NULL; 68 | 69 | 70 | %%{ 71 | machine ebb_request_parser; 72 | 73 | action mark_header_field { parser->header_field_mark = p; } 74 | action mark_header_value { parser->header_value_mark = p; } 75 | action mark_fragment { parser->fragment_mark = p; } 76 | action mark_query_string { parser->query_string_mark = p; } 77 | action mark_request_path { parser->path_mark = p; } 78 | action mark_request_uri { parser->uri_mark = p; } 79 | 80 | action write_field { 81 | HEADER_CALLBACK(header_field); 82 | parser->header_field_mark = NULL; 83 | } 84 | 85 | action write_value { 86 | HEADER_CALLBACK(header_value); 87 | parser->header_value_mark = NULL; 88 | } 89 | 90 | action request_uri { 91 | CALLBACK(uri); 92 | parser->uri_mark = NULL; 93 | } 94 | 95 | action fragment { 96 | CALLBACK(fragment); 97 | parser->fragment_mark = NULL; 98 | } 99 | 100 | action query_string { 101 | CALLBACK(query_string); 102 | parser->query_string_mark = NULL; 103 | } 104 | 105 | action request_path { 106 | CALLBACK(path); 107 | parser->path_mark = NULL; 108 | } 109 | 110 | action content_length { 111 | if(CURRENT){ 112 | CURRENT->content_length *= 10; 113 | CURRENT->content_length += *p - '0'; 114 | } 115 | } 116 | 117 | action use_identity_encoding { if(CURRENT) CURRENT->transfer_encoding = EBB_IDENTITY; } 118 | action use_chunked_encoding { if(CURRENT) CURRENT->transfer_encoding = EBB_CHUNKED; } 119 | 120 | action set_keep_alive { if(CURRENT) CURRENT->keep_alive = TRUE; } 121 | action set_not_keep_alive { if(CURRENT) CURRENT->keep_alive = FALSE; } 122 | 123 | action expect_continue { 124 | if(CURRENT) CURRENT->expect_continue = TRUE; 125 | } 126 | 127 | action trailer { 128 | /* not implemenetd yet. (do requests even have trailing headers?) */ 129 | } 130 | 131 | action version_major { 132 | if(CURRENT) { 133 | CURRENT->version_major *= 10; 134 | CURRENT->version_major += *p - '0'; 135 | } 136 | } 137 | 138 | action version_minor { 139 | if(CURRENT) { 140 | CURRENT->version_minor *= 10; 141 | CURRENT->version_minor += *p - '0'; 142 | } 143 | } 144 | 145 | action end_header_line { 146 | if(CURRENT) CURRENT->number_of_headers++; 147 | } 148 | 149 | action end_headers { 150 | if(CURRENT && CURRENT->on_headers_complete) 151 | CURRENT->on_headers_complete(CURRENT); 152 | } 153 | 154 | action add_to_chunk_size { 155 | parser->chunk_size *= 16; 156 | parser->chunk_size += unhex[(int)*p]; 157 | } 158 | 159 | action skip_chunk_data { 160 | skip_body(&p, parser, MIN(parser->chunk_size, REMAINING)); 161 | fhold; 162 | if(parser->chunk_size > REMAINING) { 163 | fbreak; 164 | } else { 165 | fgoto chunk_end; 166 | } 167 | } 168 | 169 | action end_chunked_body { 170 | END_REQUEST; 171 | fnext main; 172 | } 173 | 174 | action start_req { 175 | assert(CURRENT == NULL); 176 | CURRENT = parser->new_request(parser->data); 177 | } 178 | 179 | action body_logic { 180 | if(CURRENT) { 181 | if(CURRENT->transfer_encoding == EBB_CHUNKED) { 182 | fnext ChunkedBody; 183 | } else { 184 | /* this is pretty stupid. i'd prefer to combine this with skip_chunk_data */ 185 | parser->chunk_size = CURRENT->content_length; 186 | p += 1; 187 | skip_body(&p, parser, MIN(REMAINING, CURRENT->content_length)); 188 | fhold; 189 | if(parser->chunk_size > REMAINING) { 190 | fbreak; 191 | } 192 | } 193 | } 194 | } 195 | 196 | # 197 | ## 198 | ### 199 | #### HTTP/1.1 STATE MACHINE 200 | ### 201 | ## RequestHeaders and character types are from 202 | # Zed Shaw's beautiful Mongrel parser. 203 | 204 | CRLF = "\r\n"; 205 | 206 | # character types 207 | CTL = (cntrl | 127); 208 | safe = ("$" | "-" | "_" | "."); 209 | extra = ("!" | "*" | "'" | "(" | ")" | ","); 210 | reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"); 211 | unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">"); 212 | national = any -- (alpha | digit | reserved | extra | safe | unsafe); 213 | unreserved = (alpha | digit | safe | extra | national); 214 | escape = ("%" xdigit xdigit); 215 | uchar = (unreserved | escape); 216 | pchar = (uchar | ":" | "@" | "&" | "=" | "+"); 217 | tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); 218 | 219 | # elements 220 | token = (ascii -- (CTL | tspecials)); 221 | quote = "\""; 222 | # qdtext = token -- "\""; 223 | # quoted_pair = "\" ascii; 224 | # quoted_string = "\"" (qdtext | quoted_pair )* "\""; 225 | 226 | # headers 227 | 228 | Method = ( "COPY" %{ if(CURRENT) CURRENT->method = EBB_COPY; } 229 | | "DELETE" %{ if(CURRENT) CURRENT->method = EBB_DELETE; } 230 | | "GET" %{ if(CURRENT) CURRENT->method = EBB_GET; } 231 | | "HEAD" %{ if(CURRENT) CURRENT->method = EBB_HEAD; } 232 | | "LOCK" %{ if(CURRENT) CURRENT->method = EBB_LOCK; } 233 | | "MKCOL" %{ if(CURRENT) CURRENT->method = EBB_MKCOL; } 234 | | "MOVE" %{ if(CURRENT) CURRENT->method = EBB_MOVE; } 235 | | "OPTIONS" %{ if(CURRENT) CURRENT->method = EBB_OPTIONS; } 236 | | "POST" %{ if(CURRENT) CURRENT->method = EBB_POST; } 237 | | "PROPFIND" %{ if(CURRENT) CURRENT->method = EBB_PROPFIND; } 238 | | "PROPPATCH" %{ if(CURRENT) CURRENT->method = EBB_PROPPATCH; } 239 | | "PUT" %{ if(CURRENT) CURRENT->method = EBB_PUT; } 240 | | "TRACE" %{ if(CURRENT) CURRENT->method = EBB_TRACE; } 241 | | "UNLOCK" %{ if(CURRENT) CURRENT->method = EBB_UNLOCK; } 242 | ); # Not allowing extension methods 243 | 244 | HTTP_Version = "HTTP/" digit+ $version_major "." digit+ $version_minor; 245 | 246 | scheme = ( alpha | digit | "+" | "-" | "." )* ; 247 | absolute_uri = (scheme ":" (uchar | reserved )*); 248 | path = ( pchar+ ( "/" pchar* )* ) ; 249 | query = ( uchar | reserved )* >mark_query_string %query_string ; 250 | param = ( pchar | "/" )* ; 251 | params = ( param ( ";" param )* ) ; 252 | rel_path = ( path? (";" params)? ) ; 253 | absolute_path = ( "/"+ rel_path ) >mark_request_path %request_path ("?" query)?; 254 | Request_URI = ( "*" | absolute_uri | absolute_path ) >mark_request_uri %request_uri; 255 | Fragment = ( uchar | reserved )* >mark_fragment %fragment; 256 | 257 | field_name = ( token -- ":" )+; 258 | Field_Name = field_name >mark_header_field %write_field; 259 | 260 | field_value = ((any - " ") any*)?; 261 | Field_Value = field_value >mark_header_value %write_value; 262 | 263 | hsep = ":" " "*; 264 | header = (field_name hsep field_value) :> CRLF; 265 | Header = ( ("Content-Length"i hsep digit+ $content_length) 266 | | ("Connection"i hsep 267 | ( "Keep-Alive"i %set_keep_alive 268 | | "close"i %set_not_keep_alive 269 | ) 270 | ) 271 | | ("Transfer-Encoding"i %use_chunked_encoding hsep "identity" %use_identity_encoding) 272 | # | ("Expect"i hsep "100-continue"i %expect_continue) 273 | # | ("Trailer"i hsep field_value %trailer) 274 | | (Field_Name hsep Field_Value) 275 | ) :> CRLF; 276 | 277 | Request_Line = ( Method " " Request_URI ("#" Fragment)? " " HTTP_Version CRLF ) ; 278 | RequestHeader = Request_Line (Header %end_header_line)* :> CRLF @end_headers; 279 | 280 | # chunked message 281 | trailing_headers = header*; 282 | #chunk_ext_val = token | quoted_string; 283 | chunk_ext_val = token*; 284 | chunk_ext_name = token*; 285 | chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*; 286 | last_chunk = "0"+ chunk_extension CRLF; 287 | chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size; 288 | chunk_end = CRLF; 289 | chunk_body = any >skip_chunk_data; 290 | chunk_begin = chunk_size chunk_extension CRLF; 291 | chunk = chunk_begin chunk_body chunk_end; 292 | ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body; 293 | 294 | Request = RequestHeader >start_req @body_logic; 295 | 296 | main := Request*; # sequence of requests (for keep-alive) 297 | }%% 298 | 299 | %% write data; 300 | 301 | static void 302 | skip_body(const char **p, ebb_request_parser *parser, size_t nskip) { 303 | if(CURRENT && CURRENT->on_body && nskip > 0) { 304 | CURRENT->on_body(CURRENT, *p, nskip); 305 | } 306 | if(CURRENT) CURRENT->body_read += nskip; 307 | parser->chunk_size -= nskip; 308 | *p += nskip; 309 | if(0 == parser->chunk_size) { 310 | parser->eating = FALSE; 311 | if(CURRENT && CURRENT->transfer_encoding == EBB_IDENTITY) { 312 | END_REQUEST; 313 | } 314 | } else { 315 | parser->eating = TRUE; 316 | } 317 | } 318 | 319 | void ebb_request_parser_init(ebb_request_parser *parser) 320 | { 321 | int cs = 0; 322 | %% write init; 323 | parser->cs = cs; 324 | 325 | parser->chunk_size = 0; 326 | parser->eating = 0; 327 | 328 | parser->current_request = NULL; 329 | 330 | parser->header_field_mark = parser->header_value_mark = 331 | parser->query_string_mark = parser->path_mark = 332 | parser->uri_mark = parser->fragment_mark = NULL; 333 | 334 | parser->new_request = NULL; 335 | } 336 | 337 | 338 | /** exec **/ 339 | size_t ebb_request_parser_execute(ebb_request_parser *parser, const char *buffer, size_t len) 340 | { 341 | const char *p, *pe; 342 | int cs = parser->cs; 343 | 344 | assert(parser->new_request && "undefined callback"); 345 | 346 | p = buffer; 347 | pe = buffer+len; 348 | 349 | if(0 < parser->chunk_size && parser->eating) { 350 | /* eat body */ 351 | size_t eat = MIN(len, parser->chunk_size); 352 | skip_body(&p, parser, eat); 353 | } 354 | 355 | if(parser->header_field_mark) parser->header_field_mark = buffer; 356 | if(parser->header_value_mark) parser->header_value_mark = buffer; 357 | if(parser->fragment_mark) parser->fragment_mark = buffer; 358 | if(parser->query_string_mark) parser->query_string_mark = buffer; 359 | if(parser->path_mark) parser->path_mark = buffer; 360 | if(parser->uri_mark) parser->uri_mark = buffer; 361 | 362 | %% write exec; 363 | 364 | parser->cs = cs; 365 | 366 | HEADER_CALLBACK(header_field); 367 | HEADER_CALLBACK(header_value); 368 | CALLBACK(fragment); 369 | CALLBACK(query_string); 370 | CALLBACK(path); 371 | CALLBACK(uri); 372 | 373 | assert(p <= pe && "buffer overflow after parsing execute"); 374 | 375 | return(p - buffer); 376 | } 377 | 378 | int ebb_request_parser_has_error(ebb_request_parser *parser) 379 | { 380 | return parser->cs == ebb_request_parser_error; 381 | } 382 | 383 | int ebb_request_parser_is_finished(ebb_request_parser *parser) 384 | { 385 | return parser->cs == ebb_request_parser_first_final; 386 | } 387 | 388 | void ebb_request_init(ebb_request *request) 389 | { 390 | request->expect_continue = FALSE; 391 | request->body_read = 0; 392 | request->content_length = 0; 393 | request->version_major = 0; 394 | request->version_minor = 0; 395 | request->number_of_headers = 0; 396 | request->transfer_encoding = EBB_IDENTITY; 397 | request->keep_alive = -1; 398 | 399 | request->on_complete = NULL; 400 | request->on_headers_complete = NULL; 401 | request->on_body = NULL; 402 | request->on_header_field = NULL; 403 | request->on_header_value = NULL; 404 | request->on_uri = NULL; 405 | request->on_fragment = NULL; 406 | request->on_path = NULL; 407 | request->on_query_string = NULL; 408 | } 409 | 410 | int ebb_request_should_keep_alive(ebb_request *request) 411 | { 412 | if(request->keep_alive == -1) 413 | if(request->version_major == 1) 414 | return (request->version_minor != 0); 415 | else if(request->version_major == 0) 416 | return FALSE; 417 | else 418 | return TRUE; 419 | else 420 | return request->keep_alive; 421 | } 422 | -------------------------------------------------------------------------------- /libebb/examples/ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBzDCCATegAwIBAgIESIuNVTALBgkqhkiG9w0BAQUwADAeFw0wODA3MjYyMDQ3 3 | MTlaFw0xMTA0MjIyMDQ3MjVaMAAwgZwwCwYJKoZIhvcNAQEBA4GMADCBiAKBgOm9 4 | l/FoXbTIcEusk/QlS5YrlR04+oWIbSdZIf3GJBEWEUPljDxAX96qHsTcaVnGK+EP 5 | keU4cIZvdY+hzbqa5cc1j2/9IeJNejL8gpQ/ocyMM69yq5Ib2F8K4mGWm1xr30hU 6 | bYpY5D0MrZ1b0HtYFVc8KVAr0ADGG+pye0P9c3B/AgMBAAGjWjBYMAwGA1UdEwEB 7 | /wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MBMGA1UdJQQMMAoGCCsGAQUFBwMB 8 | MB0GA1UdDgQWBBTYgVB7kJnnm+jgX9DgrapzGfUmxjALBgkqhkiG9w0BAQUDgYEA 9 | GkadA2H8CAzU3w4oCGZu9Ry9Tj/9Agw1XMFKvoJuG7VLPk7+B25JvNFVsmpROLxO 10 | 0TJ6mIU2hz5/rLvEfTBGQ+DYtbsjIxCz1fD7R5c1kKBtA0d0u8mY8pTlPNlxFPSW 11 | 3ymx5DB2zyDa/HuX6m6/VmzMYmA0vp7Dp1cl+pA9Nhs= 12 | -----END CERTIFICATE----- 13 | -------------------------------------------------------------------------------- /libebb/examples/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDpvZfxaF20yHBLrJP0JUuWK5UdOPqFiG0nWSH9xiQRFhFD5Yw8 3 | QF/eqh7E3GlZxivhD5HlOHCGb3WPoc26muXHNY9v/SHiTXoy/IKUP6HMjDOvcquS 4 | G9hfCuJhlptca99IVG2KWOQ9DK2dW9B7WBVXPClQK9AAxhvqcntD/XNwfwIDAQAB 5 | AoGAJqo3LTbfcV1KvinhG5zjwQaalwfq4RXtQHoNFmalZrIozvt01C6t7S5lApmX 6 | T8NpVMR3lNxeOM7NOqJAXuLqqVVqk81YEYuMx6E4gB/Ifl7jVZk1jstmLILhh59D 7 | pXrlpzvvm5X2hVsI7lp/YGAvtdLS1iVy37bGgmQWfCeeZiECQQDtZLfcJb4oE1yR 8 | ZfLOcPDlBCw02wGMNFpAjwbspf/du3Yn3ONWHVfhSCCcCe262h9PLblL97LoB+gF 9 | OHOlM9JvAkEA/A+U3/p9pwL4L742pxZP62/rmk6p5mZFIykk2jIUTpdilXBWBlcT 10 | OjgjnpquZpwnaClmvgFpkzdhIPF7Nq4K8QJBAITVKagOmnOUOeTF1fI78h9DkXTV 11 | 4uzP0nxzS52ZWS16Gqg9ihuCecz97flB+Prn2EMWw6tFY58/5U0ehF85OxMCQQDF 12 | 08TYdVSg+6emcPeb89sNwW18UjjuZ13j1qrhxWRCunXZK62YlEa27tCl7mjqh6w2 13 | CChm/9zIejJ1FJHLvJVBAkBj63ZbwggMYkxuj60jIBbNrEtDx9y7zM0sXkiJqcKp 14 | 5uGtJNafG+yZrLAHE6/b4aqUOtGsCGsiZpT9ms7CoaVr 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /libebb/examples/hello_world.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include "ebb.h" 9 | 10 | #define MSG ("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nhello world\n") 11 | static int c = 0; 12 | 13 | struct hello_connection { 14 | unsigned int responses_to_write; 15 | }; 16 | 17 | void on_close(ebb_connection *connection) 18 | { 19 | free(connection->data); 20 | free(connection); 21 | } 22 | 23 | static void continue_responding(ebb_connection *connection) 24 | { 25 | int r; 26 | struct hello_connection *connection_data = connection->data; 27 | //printf("response complete \n"); 28 | if(--connection_data->responses_to_write > 0) { 29 | /* write another response */ 30 | r = ebb_connection_write(connection, MSG, sizeof MSG, continue_responding); 31 | assert(r); 32 | } else { 33 | ebb_connection_schedule_close(connection); 34 | } 35 | } 36 | 37 | static void request_complete(ebb_request *request) 38 | { 39 | //printf("request complete \n"); 40 | ebb_connection *connection = request->data; 41 | struct hello_connection *connection_data = connection->data; 42 | 43 | if(ebb_request_should_keep_alive(request)) 44 | connection_data->responses_to_write++; 45 | else 46 | connection_data->responses_to_write = 1; 47 | 48 | ebb_connection_write(connection, MSG, sizeof MSG, continue_responding); 49 | free(request); 50 | } 51 | 52 | static ebb_request* new_request(ebb_connection *connection) 53 | { 54 | //printf("request %d\n", ++c); 55 | ebb_request *request = malloc(sizeof(ebb_request)); 56 | ebb_request_init(request); 57 | request->data = connection; 58 | request->on_complete = request_complete; 59 | return request; 60 | } 61 | 62 | ebb_connection* new_connection(ebb_server *server, struct sockaddr_in *addr) 63 | { 64 | struct hello_connection *connection_data = malloc(sizeof(struct hello_connection)); 65 | if(connection_data == NULL) 66 | return NULL; 67 | connection_data->responses_to_write = 0; 68 | 69 | ebb_connection *connection = malloc(sizeof(ebb_connection)); 70 | if(connection == NULL) { 71 | free(connection_data); 72 | return NULL; 73 | } 74 | 75 | ebb_connection_init(connection); 76 | connection->data = connection_data; 77 | connection->new_request = new_request; 78 | connection->on_close = on_close; 79 | 80 | printf("connection: %d\n", c++); 81 | return connection; 82 | } 83 | 84 | int main(int argc, char **_) 85 | { 86 | struct ev_loop *loop = ev_default_loop(0); 87 | ebb_server server; 88 | 89 | ebb_server_init(&server, loop); 90 | if(argc > 1) { 91 | printf("using SSL\n"); 92 | ebb_server_set_secure(&server, "ca-cert.pem", "ca-key.pem"); 93 | } 94 | server.new_connection = new_connection; 95 | 96 | printf("hello_world listening on port 5000\n"); 97 | ebb_server_listen_on_port(&server, 5000); 98 | ev_loop(loop, 0); 99 | 100 | return 0; 101 | } 102 | -------------------------------------------------------------------------------- /libebb/rbtree.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2008 Derrick Coetzee 2 | * http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to permit 9 | * persons to whom the Software is furnished to do so, subject to the 10 | * following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 20 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 21 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include "rbtree.h" 25 | #include 26 | 27 | #ifndef NULL 28 | # define NULL ((void*)0) 29 | #endif 30 | 31 | 32 | typedef rbtree_node node; 33 | typedef enum rbtree_node_color color; 34 | 35 | 36 | static node grandparent(node n); 37 | static node sibling(node n); 38 | static node uncle(node n); 39 | static void verify_properties(rbtree t); 40 | /* static void verify_property_2(node root); */ 41 | static color node_color(node n); 42 | /* static void verify_property_5(node root); */ 43 | 44 | static node lookup_node(rbtree t, void* key); 45 | static void rotate_left(rbtree t, node n); 46 | static void rotate_right(rbtree t, node n); 47 | 48 | static void replace_node(rbtree t, node oldn, node newn); 49 | static void insert_case1(rbtree t, node n); 50 | static void insert_case2(rbtree t, node n); 51 | static void insert_case3(rbtree t, node n); 52 | static void insert_case4(rbtree t, node n); 53 | static void insert_case5(rbtree t, node n); 54 | static node maximum_node(node root); 55 | static void delete_case1(rbtree t, node n); 56 | static void delete_case2(rbtree t, node n); 57 | static void delete_case3(rbtree t, node n); 58 | static void delete_case4(rbtree t, node n); 59 | static void delete_case5(rbtree t, node n); 60 | static void delete_case6(rbtree t, node n); 61 | 62 | node grandparent(node n) { 63 | assert (n != NULL); 64 | assert (n->parent != NULL); /* Not the root node */ 65 | assert (n->parent->parent != NULL); /* Not child of root */ 66 | return n->parent->parent; 67 | } 68 | 69 | node sibling(node n) { 70 | assert (n != NULL); 71 | assert (n->parent != NULL); /* Root node has no sibling */ 72 | if (n == n->parent->left) 73 | return n->parent->right; 74 | else 75 | return n->parent->left; 76 | } 77 | 78 | node uncle(node n) { 79 | assert (n != NULL); 80 | assert (n->parent != NULL); /* Root node has no uncle */ 81 | assert (n->parent->parent != NULL); /* Children of root have no uncle */ 82 | return sibling(n->parent); 83 | } 84 | 85 | void verify_properties(rbtree t) { 86 | #ifdef VERIFY_RBTREE 87 | verify_property_1(t->root); 88 | verify_property_2(t->root); 89 | /* Property 3 is implicit */ 90 | verify_property_4(t->root); 91 | verify_property_5(t->root); 92 | #endif 93 | } 94 | 95 | /* 96 | void verify_property_2(node root) { 97 | assert(node_color(root) == BLACK); 98 | } 99 | */ 100 | 101 | color node_color(node n) { 102 | return n == NULL ? BLACK : n->color; 103 | } 104 | 105 | /* 106 | void verify_property_5(node root) { 107 | int black_count_path = -1; 108 | verify_property_5_helper(root, 0, &black_count_path); 109 | } 110 | */ 111 | 112 | void rbtree_init(rbtree t, rbtree_compare_func compare) { 113 | t->root = NULL; 114 | t->compare = compare; 115 | verify_properties(t); 116 | } 117 | 118 | node lookup_node(rbtree t, void* key) { 119 | node n = t->root; 120 | while (n != NULL) { 121 | int comp_result = t->compare(key, n->key); 122 | if (comp_result == 0) { 123 | return n; 124 | } else if (comp_result < 0) { 125 | n = n->left; 126 | } else { 127 | assert(comp_result > 0); 128 | n = n->right; 129 | } 130 | } 131 | return n; 132 | } 133 | 134 | void* rbtree_lookup(rbtree t, void* key) { 135 | node n = lookup_node(t, key); 136 | return n == NULL ? NULL : n->value; 137 | } 138 | 139 | void rotate_left(rbtree t, node n) { 140 | node r = n->right; 141 | replace_node(t, n, r); 142 | n->right = r->left; 143 | if (r->left != NULL) { 144 | r->left->parent = n; 145 | } 146 | r->left = n; 147 | n->parent = r; 148 | } 149 | 150 | void rotate_right(rbtree t, node n) { 151 | node L = n->left; 152 | replace_node(t, n, L); 153 | n->left = L->right; 154 | if (L->right != NULL) { 155 | L->right->parent = n; 156 | } 157 | L->right = n; 158 | n->parent = L; 159 | } 160 | 161 | void replace_node(rbtree t, node oldn, node newn) { 162 | if (oldn->parent == NULL) { 163 | t->root = newn; 164 | } else { 165 | if (oldn == oldn->parent->left) 166 | oldn->parent->left = newn; 167 | else 168 | oldn->parent->right = newn; 169 | } 170 | if (newn != NULL) { 171 | newn->parent = oldn->parent; 172 | } 173 | } 174 | 175 | void rbtree_insert(rbtree t, rbtree_node inserted_node) { 176 | inserted_node->color = RED; 177 | inserted_node->left = NULL; 178 | inserted_node->right = NULL; 179 | inserted_node->parent = NULL; 180 | 181 | if (t->root == NULL) { 182 | t->root = inserted_node; 183 | } else { 184 | node n = t->root; 185 | while (1) { 186 | int comp_result = t->compare(inserted_node->key, n->key); 187 | if (comp_result == 0) { 188 | n->value = inserted_node->value; 189 | return; 190 | } else if (comp_result < 0) { 191 | if (n->left == NULL) { 192 | n->left = inserted_node; 193 | break; 194 | } else { 195 | n = n->left; 196 | } 197 | } else { 198 | assert (comp_result > 0); 199 | if (n->right == NULL) { 200 | n->right = inserted_node; 201 | break; 202 | } else { 203 | n = n->right; 204 | } 205 | } 206 | } 207 | inserted_node->parent = n; 208 | } 209 | insert_case1(t, inserted_node); 210 | verify_properties(t); 211 | } 212 | 213 | void insert_case1(rbtree t, node n) { 214 | if (n->parent == NULL) 215 | n->color = BLACK; 216 | else 217 | insert_case2(t, n); 218 | } 219 | 220 | void insert_case2(rbtree t, node n) { 221 | if (node_color(n->parent) == BLACK) 222 | return; /* Tree is still valid */ 223 | else 224 | insert_case3(t, n); 225 | } 226 | 227 | void insert_case3(rbtree t, node n) { 228 | if (node_color(uncle(n)) == RED) { 229 | n->parent->color = BLACK; 230 | uncle(n)->color = BLACK; 231 | grandparent(n)->color = RED; 232 | insert_case1(t, grandparent(n)); 233 | } else { 234 | insert_case4(t, n); 235 | } 236 | } 237 | 238 | void insert_case4(rbtree t, node n) { 239 | if (n == n->parent->right && n->parent == grandparent(n)->left) { 240 | rotate_left(t, n->parent); 241 | n = n->left; 242 | } else if (n == n->parent->left && n->parent == grandparent(n)->right) { 243 | rotate_right(t, n->parent); 244 | n = n->right; 245 | } 246 | insert_case5(t, n); 247 | } 248 | 249 | void insert_case5(rbtree t, node n) { 250 | n->parent->color = BLACK; 251 | grandparent(n)->color = RED; 252 | if (n == n->parent->left && n->parent == grandparent(n)->left) { 253 | rotate_right(t, grandparent(n)); 254 | } else { 255 | assert (n == n->parent->right && n->parent == grandparent(n)->right); 256 | rotate_left(t, grandparent(n)); 257 | } 258 | } 259 | 260 | rbtree_node rbtree_delete(rbtree t, void* key) { 261 | node child; 262 | node n = lookup_node(t, key); 263 | if (n == NULL) return NULL; /* Key not found, do nothing */ 264 | if (n->left != NULL && n->right != NULL) { 265 | /* Copy key/value from predecessor and then delete it instead */ 266 | node pred = maximum_node(n->left); 267 | n->key = pred->key; 268 | n->value = pred->value; 269 | n = pred; 270 | } 271 | 272 | assert(n->left == NULL || n->right == NULL); 273 | child = n->right == NULL ? n->left : n->right; 274 | if (node_color(n) == BLACK) { 275 | n->color = node_color(child); 276 | delete_case1(t, n); 277 | } 278 | replace_node(t, n, child); 279 | 280 | verify_properties(t); 281 | return n; 282 | } 283 | 284 | static node maximum_node(node n) { 285 | assert (n != NULL); 286 | while (n->right != NULL) { 287 | n = n->right; 288 | } 289 | return n; 290 | } 291 | 292 | void delete_case1(rbtree t, node n) { 293 | if (n->parent == NULL) 294 | return; 295 | else 296 | delete_case2(t, n); 297 | } 298 | 299 | void delete_case2(rbtree t, node n) { 300 | if (node_color(sibling(n)) == RED) { 301 | n->parent->color = RED; 302 | sibling(n)->color = BLACK; 303 | if (n == n->parent->left) 304 | rotate_left(t, n->parent); 305 | else 306 | rotate_right(t, n->parent); 307 | } 308 | delete_case3(t, n); 309 | } 310 | 311 | void delete_case3(rbtree t, node n) { 312 | if (node_color(n->parent) == BLACK && 313 | node_color(sibling(n)) == BLACK && 314 | node_color(sibling(n)->left) == BLACK && 315 | node_color(sibling(n)->right) == BLACK) 316 | { 317 | sibling(n)->color = RED; 318 | delete_case1(t, n->parent); 319 | } 320 | else 321 | delete_case4(t, n); 322 | } 323 | 324 | void delete_case4(rbtree t, node n) { 325 | if (node_color(n->parent) == RED && 326 | node_color(sibling(n)) == BLACK && 327 | node_color(sibling(n)->left) == BLACK && 328 | node_color(sibling(n)->right) == BLACK) 329 | { 330 | sibling(n)->color = RED; 331 | n->parent->color = BLACK; 332 | } 333 | else 334 | delete_case5(t, n); 335 | } 336 | 337 | void delete_case5(rbtree t, node n) { 338 | if (n == n->parent->left && 339 | node_color(sibling(n)) == BLACK && 340 | node_color(sibling(n)->left) == RED && 341 | node_color(sibling(n)->right) == BLACK) 342 | { 343 | sibling(n)->color = RED; 344 | sibling(n)->left->color = BLACK; 345 | rotate_right(t, sibling(n)); 346 | } 347 | else if (n == n->parent->right && 348 | node_color(sibling(n)) == BLACK && 349 | node_color(sibling(n)->right) == RED && 350 | node_color(sibling(n)->left) == BLACK) 351 | { 352 | sibling(n)->color = RED; 353 | sibling(n)->right->color = BLACK; 354 | rotate_left(t, sibling(n)); 355 | } 356 | delete_case6(t, n); 357 | } 358 | 359 | void delete_case6(rbtree t, node n) { 360 | sibling(n)->color = node_color(n->parent); 361 | n->parent->color = BLACK; 362 | if (n == n->parent->left) { 363 | assert (node_color(sibling(n)->right) == RED); 364 | sibling(n)->right->color = BLACK; 365 | rotate_left(t, n->parent); 366 | } 367 | else 368 | { 369 | assert (node_color(sibling(n)->left) == RED); 370 | sibling(n)->left->color = BLACK; 371 | rotate_right(t, n->parent); 372 | } 373 | } 374 | 375 | 376 | -------------------------------------------------------------------------------- /libebb/rbtree.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2008 Derrick Coetzee 2 | * http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982 3 | * Small changes by Ryah Dahl 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 7 | * "Software"), to deal in the Software without restriction, including 8 | * without limitation the rights to use, copy, modify, merge, publish, 9 | * distribute, sublicense, and/or sell copies of the Software, and to permit 10 | * persons to whom the Software is furnished to do so, subject to the 11 | * following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 21 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 22 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef _RBTREE_H_ 26 | #define _RBTREE_H_ 27 | 28 | enum rbtree_node_color { RED, BLACK }; 29 | 30 | typedef int (*rbtree_compare_func)(void* left_key, void* right_key); 31 | 32 | typedef struct rbtree_node_t { 33 | struct rbtree_node_t* left; /* private */ 34 | struct rbtree_node_t* right; /* private */ 35 | struct rbtree_node_t* parent; /* private */ 36 | enum rbtree_node_color color; /* private */ 37 | void* key; /* public */ 38 | void* value; /* public */ 39 | } *rbtree_node; 40 | 41 | typedef struct rbtree_t { 42 | rbtree_node root; /* private */ 43 | rbtree_compare_func compare; /* private */ 44 | } *rbtree; 45 | 46 | 47 | void rbtree_init(rbtree t, rbtree_compare_func); 48 | void* rbtree_lookup(rbtree t, void* key); 49 | void rbtree_insert(rbtree t, rbtree_node); 50 | /* you must free the returned node */ 51 | rbtree_node rbtree_delete(rbtree t, void* key); 52 | 53 | #endif 54 | 55 | -------------------------------------------------------------------------------- /libebb/test_examples.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'socket' 3 | 4 | REQ = "GET /hello/%d HTTP/1.1\r\n\r\n" 5 | HOST = '0.0.0.0' 6 | PORT = 5000 7 | 8 | class TCPSocket 9 | def full_send(string) 10 | written = 0 11 | while(written < string.length) 12 | sent = write(string) 13 | string = string.slice(sent, 10000) 14 | end 15 | written 16 | end 17 | 18 | def full_read 19 | response = "" 20 | while chunk = read(10000) 21 | response += chunk 22 | end 23 | response 24 | end 25 | 26 | end 27 | 28 | class EbbTest < Test::Unit::TestCase 29 | 30 | def setup 31 | @socket = TCPSocket.new(HOST, PORT) 32 | end 33 | 34 | def test_bad_req 35 | @socket.full_send("hello") 36 | assert_equal "", @socket.full_read 37 | #assert @socket.closed? 38 | end 39 | 40 | def test_single 41 | written = 0 42 | req = REQ % 1 43 | @socket.full_send(req) 44 | response = @socket.full_read() 45 | count = 0 46 | response.scan("hello world") { count += 1 } 47 | 48 | assert_equal 1, count 49 | end 50 | 51 | def test_pipeline 52 | written = 0 53 | req = (REQ % 1) + (REQ % 2) + (REQ % 3) + (REQ % 4) 54 | @socket.full_send(req) 55 | response = @socket.full_read() 56 | count = 0 57 | response.scan("hello world") { count += 1 } 58 | assert_equal 4, count 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /libebb/test_rbtree.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2008 Derrick Coetzee 2 | * http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to permit 9 | * persons to whom the Software is furnished to do so, subject to the 10 | * following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 20 | * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 21 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include "rbtree.h" 25 | #include 26 | #include 27 | #include /* rand(), malloc(), free() */ 28 | 29 | static int compare_int(void* left, void* right); 30 | static void print_tree(rbtree t); 31 | static void print_tree_helper(rbtree_node n, int indent); 32 | 33 | int compare_int(void* leftp, void* rightp) { 34 | int left = (int)leftp; 35 | int right = (int)rightp; 36 | if (left < right) 37 | return -1; 38 | else if (left > right) 39 | return 1; 40 | else { 41 | assert (left == right); 42 | return 0; 43 | } 44 | } 45 | 46 | #define INDENT_STEP 4 47 | 48 | void print_tree_helper(rbtree_node n, int indent); 49 | 50 | void print_tree(rbtree t) { 51 | print_tree_helper(t->root, 0); 52 | puts(""); 53 | } 54 | 55 | void print_tree_helper(rbtree_node n, int indent) { 56 | int i; 57 | if (n == NULL) { 58 | fputs("", stdout); 59 | return; 60 | } 61 | if (n->right != NULL) { 62 | print_tree_helper(n->right, indent + INDENT_STEP); 63 | } 64 | for(i=0; icolor == BLACK) 67 | printf("%d\n", (int)n->key); 68 | else 69 | printf("<%d>\n", (int)n->key); 70 | if (n->left != NULL) { 71 | print_tree_helper(n->left, indent + INDENT_STEP); 72 | } 73 | } 74 | 75 | int main() { 76 | int i; 77 | struct rbtree_t t; 78 | rbtree_init(&t, compare_int); 79 | print_tree(&t); 80 | 81 | for(i=0; i<5000; i++) { 82 | int x = rand() % 10000; 83 | int y = rand() % 10000; 84 | #ifdef TRACE 85 | print_tree(&t); 86 | printf("Inserting %d -> %d\n\n", x, y); 87 | #endif 88 | rbtree_node node = (rbtree_node) malloc(sizeof(struct rbtree_node_t)); 89 | node->key = (void*)x; 90 | node->value = (void*)y; 91 | rbtree_insert(&t, node); 92 | assert(rbtree_lookup(&t, (void*)x) == (void*)y); 93 | } 94 | for(i=0; i<60000; i++) { 95 | int x = rand() % 10000; 96 | #ifdef TRACE 97 | print_tree(&t); 98 | printf("Deleting key %d\n\n", x); 99 | #endif 100 | rbtree_node n = rbtree_delete(&t, (void*)x); 101 | if(n != NULL) { 102 | free(n); 103 | } 104 | } 105 | printf("Okay\n"); 106 | return 0; 107 | } 108 | 109 | -------------------------------------------------------------------------------- /midconnlist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include "midconnlist.h" 20 | 21 | struct midconn_node 22 | { 23 | RB_ENTRY(midconn_node) entry; 24 | int mid; 25 | uint32_t connid; 26 | }; 27 | 28 | static int midconn_cmp(struct midconn_node *e1, struct midconn_node *e2) 29 | { 30 | return (e1->mid < e2->mid ? -1 : e1->mid > e2->mid); 31 | } 32 | 33 | RB_HEAD(midconnlist, midconn_node) midconn_head = RB_INITIALIZER(&midconn_head); 34 | RB_PROTOTYPE(midconnlist, midconn_node, entry, midconn_cmp); 35 | RB_GENERATE(midconnlist, midconn_node, entry, midconn_cmp); 36 | 37 | int midconn_find(int mid, uint32_t *connid) 38 | { 39 | struct midconn_node key; 40 | struct midconn_node *res; 41 | key.mid = mid; 42 | res = RB_FIND(midconnlist, &midconn_head, &key); 43 | if (NULL == res) 44 | return 1; 45 | *connid = res->connid; 46 | return 0; 47 | } 48 | 49 | int midconn_insert(int mid, uint32_t connid) 50 | { 51 | struct midconn_node *data; 52 | 53 | if (NULL == (data = malloc(sizeof(struct midconn_node)))) 54 | return 1; 55 | 56 | data->mid = mid; 57 | data->connid = connid; 58 | 59 | if (NULL == RB_INSERT(midconnlist, &midconn_head, data)) // NULL means OK 60 | { 61 | return 0; 62 | } 63 | else 64 | { 65 | LOG_CRITICAL("middconn_insert mid collision"); // can't happen 66 | return 1; 67 | } 68 | } 69 | 70 | int midconn_remove(int mid) 71 | { 72 | struct midconn_node key; 73 | struct midconn_node *res; 74 | key.mid = mid; 75 | res = RB_FIND(midconnlist, &midconn_head, &key); 76 | if (NULL == res) 77 | return 1; 78 | RB_REMOVE(midconnlist, &midconn_head, res); 79 | free(res); 80 | return 0; 81 | } 82 | 83 | void midconn_print(void) 84 | { 85 | struct midconn_node *i; 86 | RB_FOREACH(i, midconnlist, &midconn_head) 87 | { 88 | LOG_INFO("mid=%d -> connid=%d\n", i->mid, i->connid); 89 | } 90 | } 91 | 92 | void midconn_foreach(midconn_iterate_func f, void *userdata) 93 | { 94 | struct midconn_node *i; 95 | RB_FOREACH(i, midconnlist, &midconn_head) 96 | { 97 | f(i->mid, i->connid, userdata); 98 | } 99 | } 100 | 101 | #if 0 102 | void midconn_test(void) 103 | { 104 | uint32_t connid; 105 | 106 | midconn_insert(10, 100); 107 | midconn_insert(20, 200); 108 | midconn_insert(30, 300); 109 | 110 | midconn_print(); 111 | 112 | if (midconn_find(20, &connid) < 0) 113 | LOG_CRITICAL("Sanity failure"); 114 | LOG_INFO("mid 20 found on -> connid %d", connid); 115 | } 116 | #endif 117 | 118 | -------------------------------------------------------------------------------- /midconnlist.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef MIDCONNLIST_H 17 | #define MIDCONNLIST_H 1 18 | 19 | typedef void (*midconn_iterate_func)(int mid, uint32_t connid, void *userdata); 20 | 21 | extern int midconn_find(int mid, uint32_t *connid); 22 | extern int midconn_insert(int mid, uint32_t connid); 23 | extern int midconn_remove(int mid); 24 | extern void midconn_foreach(midconn_iterate_func f, void *userdata); 25 | extern void midconn_print(void); 26 | 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /mqtt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "mqtthttpd.h" 22 | #include "evhelper.h" 23 | #include "mqtt.h" 24 | #include "webapi.h" 25 | 26 | extern server_context_t g_srvctx; 27 | 28 | #if LIBMOSQUITTO_VERSION_NUMBER <= 1000000 29 | #define MOSQ_MID_T uint16_t 30 | #else 31 | #define MOSQ_MID_T int 32 | #endif 33 | 34 | static void mqtt_timeout_cb(struct ev_loop *loop, struct ev_io *w, int revents) 35 | { 36 | ev_timer_helper_t *watcher = (ev_timer_helper_t *)w; 37 | mqtt_context_t *mqttctx = (mqtt_context_t *)watcher->userdata; 38 | 39 | if (EV_TIMEOUT & revents) 40 | { 41 | if (mqttctx->connected) 42 | { 43 | if (MOSQ_ERR_SUCCESS != mosquitto_loop_misc(mqttctx->mosq)) 44 | { 45 | LOG_ERROR("mosquitto_loop_misc fail"); 46 | mqtt_disconnect(mqttctx); 47 | } 48 | 49 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 50 | if (mosquitto_want_write(mqttctx->mosq)) 51 | #endif 52 | ev_io_start(g_srvctx.loop, &mqttctx->write_watcher.io); 53 | } 54 | } 55 | } 56 | 57 | static void mqtt_ev_cb(struct ev_loop *loop, ev_io *w, int revents) 58 | { 59 | ev_io_helper_t *watcher = (ev_io_helper_t *)w; 60 | mqtt_context_t *mqttctx = (mqtt_context_t *)watcher->userdata; 61 | 62 | if (EV_READ & revents) 63 | { 64 | if (MOSQ_ERR_SUCCESS != mosquitto_loop_read(mqttctx->mosq 65 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 66 | , 1 67 | #endif 68 | )) 69 | { 70 | LOG_ERROR("read fail"); 71 | mqtt_disconnect(mqttctx); 72 | } 73 | } 74 | 75 | if (EV_WRITE & revents) 76 | { 77 | if (MOSQ_ERR_SUCCESS != mosquitto_loop_write(mqttctx->mosq 78 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 79 | , 1 80 | #endif 81 | )) 82 | { 83 | LOG_ERROR("write fail"); 84 | mqtt_disconnect(mqttctx); 85 | } 86 | 87 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 88 | if (!mosquitto_want_write(mqttctx->mosq)) 89 | #endif 90 | ev_io_stop(g_srvctx.loop, &mqttctx->write_watcher.io); 91 | } 92 | } 93 | 94 | static void mosq_connect_cb( 95 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 96 | struct mosquitto *mosq, 97 | #endif 98 | void *userdata, int rc) 99 | { 100 | mqtt_context_t *mqttctx = (mqtt_context_t *)userdata; 101 | 102 | if (rc != 0) 103 | LOG_ERROR("mqtt connect failure %d", rc); 104 | else 105 | { 106 | LOG_INFO("MQTT connected"); 107 | mqttctx->connected = true; 108 | } 109 | } 110 | 111 | static void mosq_message_cb( 112 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 113 | struct mosquitto *mosq, 114 | #endif 115 | void *userdata, const struct mosquitto_message *msg) 116 | { 117 | if (msg->payloadlen) 118 | { 119 | LOG_INFO("rx %s %s", msg->topic, msg->payload); 120 | webapi_message_cb(msg->mid, msg->topic, (char *)msg->payload); 121 | } 122 | else 123 | { 124 | LOG_INFO("%s (null)", msg->topic); 125 | webapi_message_cb(msg->mid, msg->topic, ""); 126 | } 127 | } 128 | 129 | static void mosq_unsubscribe_cb( 130 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 131 | struct mosquitto *mosq, 132 | #endif 133 | void *userdata, MOSQ_MID_T mid) 134 | { 135 | LOG_INFO("unsub OK mid=%d", mid); 136 | } 137 | 138 | static void mosq_subscribe_cb( 139 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 140 | struct mosquitto *mosq, 141 | void *userdata, MOSQ_MID_T mid, int qos_count, const int *granted_qos) 142 | #else 143 | void *userdata, MOSQ_MID_T mid, int qos_count, const uint8_t *granted_qos) 144 | #endif 145 | { 146 | LOG_INFO("sub OK mid=%d", mid); 147 | webapi_subscribe_cb(mid); 148 | } 149 | 150 | static void mosq_publish_cb( 151 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 152 | struct mosquitto *mosq, 153 | #endif 154 | void *userdata, MOSQ_MID_T mid) 155 | { 156 | LOG_INFO("pub OK mid=%d", mid); 157 | webapi_publish_cb(mid); 158 | } 159 | 160 | 161 | int mqtt_init(struct ev_loop *loop, mqtt_context_t *mqttctx) 162 | { 163 | if (MOSQ_ERR_SUCCESS != mosquitto_lib_init()) 164 | return -1; 165 | 166 | mqttctx->connected = false; 167 | return 0; 168 | } 169 | 170 | int mqtt_connect(mqtt_context_t *mqttctx, const char *name, const char *host, uint16_t port) // FIXME, blocks 171 | { 172 | ev_io *w; 173 | ev_timer *t; 174 | 175 | if (NULL != mqttctx->mosq) 176 | mosquitto_destroy(mqttctx->mosq); 177 | 178 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 179 | if (NULL == (mqttctx->mosq = mosquitto_new(name, true, (void *)mqttctx))) 180 | #else 181 | if (NULL == (mqttctx->mosq = mosquitto_new(name, (void *)mqttctx))) 182 | #endif 183 | { 184 | LOG_ERROR("mqtt context creation failed"); 185 | return 1; 186 | } 187 | 188 | mosquitto_connect_callback_set(mqttctx->mosq, mosq_connect_cb); 189 | mosquitto_message_callback_set(mqttctx->mosq, mosq_message_cb); 190 | mosquitto_subscribe_callback_set(mqttctx->mosq, mosq_subscribe_cb); 191 | mosquitto_unsubscribe_callback_set(mqttctx->mosq, mosq_unsubscribe_cb); 192 | mosquitto_publish_callback_set(mqttctx->mosq, mosq_publish_cb); 193 | 194 | if (0 != strcmp(host, g_srvctx.mqtt_name)) 195 | strncpy(g_srvctx.mqtt_name, name, sizeof(g_srvctx.mqtt_name)); 196 | if (0 != strcmp(host, g_srvctx.mqtt_server)) 197 | strncpy(g_srvctx.mqtt_server, host, sizeof(g_srvctx.mqtt_server)); 198 | g_srvctx.mqtt_port = port; 199 | 200 | #if LIBMOSQUITTO_VERSION_NUMBER <= 1000000 201 | if (0 != mosquitto_connect(mqttctx->mosq, g_srvctx.mqtt_server, g_srvctx.mqtt_port, 10, true)) // FIXME, this should be async, with below in cb 202 | #else 203 | if (0 != mosquitto_connect(mqttctx->mosq, g_srvctx.mqtt_server, g_srvctx.mqtt_port, 10)) // FIXME, this should be async, with below in cb 204 | #endif 205 | { 206 | LOG_INFO("mqtt connect failed"); 207 | return 1; 208 | } 209 | 210 | w = &mqttctx->read_watcher.io; 211 | mqttctx->read_watcher.userdata = (void *)mqttctx; 212 | ev_io_init(w, mqtt_ev_cb, mosquitto_socket(mqttctx->mosq), EV_READ); 213 | ev_io_start(g_srvctx.loop, w); 214 | 215 | w = &mqttctx->write_watcher.io; 216 | mqttctx->write_watcher.userdata = (void *)mqttctx; 217 | ev_io_init(w, mqtt_ev_cb, mosquitto_socket(mqttctx->mosq), EV_WRITE); 218 | 219 | t = &mqttctx->timer.timer; 220 | mqttctx->timer.userdata = (void *)mqttctx; 221 | ev_timer_init(t, (void *)mqtt_timeout_cb, 0, 0.1); 222 | ev_timer_start(g_srvctx.loop, t); 223 | 224 | return 0; 225 | } 226 | 227 | int mqtt_disconnect(mqtt_context_t *mqttctx) 228 | { 229 | LOG_INFO("mqtt_disconnect"); 230 | 231 | mosquitto_disconnect(mqttctx->mosq); 232 | ev_io_stop(g_srvctx.loop, &mqttctx->write_watcher.io); 233 | ev_io_stop(g_srvctx.loop, &mqttctx->read_watcher.io); 234 | ev_timer_stop(g_srvctx.loop, &mqttctx->timer.timer); 235 | mqttctx->connected = false; 236 | 237 | webapi_mqtt_failall_cb(); // fail any connections waiting on a pub response 238 | 239 | LOG_CRITICAL("mqtt connection broken"); // FIXME, we should restart 240 | 241 | return 0; 242 | } 243 | 244 | 245 | int mqtt_publish(mqtt_context_t *mqttctx, const char *topic, const char *msg, int qos, int *mid) 246 | { 247 | int rc; 248 | MOSQ_MID_T m; 249 | 250 | rc = mosquitto_publish(mqttctx->mosq, &m, topic, strlen(msg), (const uint8_t *)msg, qos, true); 251 | LOG_DEBUG("mqtt_publish topic=%s msg=%s rc=%d", topic, msg, rc); 252 | 253 | if (NULL != mid) 254 | *mid = m; 255 | 256 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 257 | if (mosquitto_want_write(mqttctx->mosq)) 258 | #endif 259 | ev_io_start(g_srvctx.loop, &mqttctx->write_watcher.io); 260 | 261 | return rc; 262 | } 263 | 264 | int mqtt_subscribe(mqtt_context_t *mqttctx, const char *topic, int qos, int *mid) 265 | { 266 | int rc; 267 | MOSQ_MID_T m; 268 | 269 | rc = mosquitto_subscribe(mqttctx->mosq, &m, topic, qos); 270 | LOG_DEBUG("mqtt_subscribe topic=%s rc=%d", topic, rc); 271 | 272 | if (NULL != mid) 273 | *mid = m; 274 | 275 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 276 | if (mosquitto_want_write(mqttctx->mosq)) 277 | #endif 278 | ev_io_start(g_srvctx.loop, &mqttctx->write_watcher.io); 279 | 280 | return rc; 281 | } 282 | 283 | int mqtt_unsubscribe(mqtt_context_t *mqttctx, const char *topic, int *mid) 284 | { 285 | int rc; 286 | MOSQ_MID_T m; 287 | 288 | rc = mosquitto_unsubscribe(mqttctx->mosq, &m, topic); 289 | LOG_DEBUG("mqtt_unsubscribe topic=%s rc=%d", topic, rc); 290 | 291 | if (NULL != mid) 292 | *mid = m; 293 | 294 | #if LIBMOSQUITTO_VERSION_NUMBER > 1000000 295 | if (mosquitto_want_write(mqttctx->mosq)) 296 | #endif 297 | ev_io_start(g_srvctx.loop, &mqttctx->write_watcher.io); 298 | 299 | return rc; 300 | } 301 | 302 | -------------------------------------------------------------------------------- /mqtt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef MQTT_H 17 | #define MQTT_H 1 18 | 19 | #include "evhelper.h" 20 | 21 | struct mqtt_context 22 | { 23 | struct mosquitto *mosq; 24 | ev_io_helper_t read_watcher; 25 | ev_io_helper_t write_watcher; 26 | ev_timer_helper_t timer; 27 | bool connected; 28 | }; 29 | typedef struct mqtt_context mqtt_context_t; 30 | 31 | extern int mqtt_init(struct ev_loop *loop, mqtt_context_t *mqctx); 32 | extern int mqtt_connect(mqtt_context_t *mqctx, const char *name, const char *host, uint16_t port); 33 | extern int mqtt_disconnect(mqtt_context_t *mqctx); 34 | extern int mqtt_publish(mqtt_context_t *mqctx, const char *topic, const char *msg, int qos, int *mid); 35 | extern int mqtt_subscribe(mqtt_context_t *mqttctx, const char *topic, int qos, int *mid); 36 | extern int mqtt_unsubscribe(mqtt_context_t *mqttctx, const char *topic, int *mid); 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /mqtthttpd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "mqtthttpd.h" 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "mqtt.h" 36 | #include "httpd.h" 37 | #include "stream.h" 38 | #include "streamlist.h" 39 | #include "accesslog.h" 40 | #include "opt.h" 41 | 42 | server_context_t g_srvctx; 43 | 44 | void exitfunc(void) 45 | { 46 | } 47 | 48 | void idle_cb(struct ev_loop *loop, struct ev_idle *w, int revents) 49 | { 50 | } 51 | 52 | void timeout_1hz_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) 53 | { 54 | if (EV_TIMEOUT & revents) 55 | { 56 | streamlist_foreach(stream_1hz, NULL); 57 | } 58 | } 59 | 60 | void timeout_10hz_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) 61 | { 62 | if (EV_TIMEOUT & revents) 63 | { 64 | } 65 | } 66 | 67 | 68 | int main(int argc, char *argv[]) 69 | { 70 | if (0 == getuid()) 71 | LOG_CRITICAL("Don't run as root!"); 72 | 73 | memset(&g_srvctx, 0, sizeof(g_srvctx)); 74 | 75 | signal(SIGPIPE, SIG_IGN); // ignore sigpipe 76 | g_srvctx.loop = ev_default_loop(0); 77 | 78 | log_init(); 79 | random_init(); 80 | 81 | if (0 != accesslog_init()) 82 | LOG_CRITICAL("Failed to open access log"); 83 | if (NULL == (g_srvctx.timer_1hz = (struct ev_timer *)malloc(sizeof(struct ev_timer)))) 84 | LOG_CRITICAL("out of mem"); 85 | if (NULL == (g_srvctx.timer_10hz = (struct ev_timer *)malloc(sizeof(struct ev_timer)))) 86 | LOG_CRITICAL("out of mem"); 87 | if (NULL == (g_srvctx.idle_watcher = (struct ev_idle *)malloc(sizeof(struct ev_idle)))) 88 | LOG_CRITICAL("out of mem"); 89 | if (0 != pidfile_write("mqtthttpd.pid")) 90 | LOG_CRITICAL("pidfile write mqtthttpd.pid failed"); 91 | 92 | ev_timer_init(g_srvctx.timer_1hz, (void *)timeout_1hz_cb, 0, 1); 93 | ev_set_priority(g_srvctx.timer_1hz, EV_MAXPRI); 94 | ev_timer_start(g_srvctx.loop, g_srvctx.timer_1hz); 95 | ev_timer_init(g_srvctx.timer_10hz, (void *)timeout_10hz_cb, 0, 0.1); 96 | ev_set_priority(g_srvctx.timer_10hz, EV_MAXPRI); 97 | ev_timer_start(g_srvctx.loop, g_srvctx.timer_10hz); 98 | ev_idle_init(g_srvctx.idle_watcher, idle_cb); 99 | 100 | if (0 != httpd_init(&(g_srvctx.httpd), g_srvctx.loop)) 101 | LOG_CRITICAL("httpd_init failed"); 102 | if (0 != mqtt_init(g_srvctx.loop, &(g_srvctx.mqttctx))) 103 | LOG_CRITICAL("mqtt_init failed"); 104 | 105 | atexit(exitfunc); 106 | 107 | if (0 != parse_options(argc, argv)) 108 | { 109 | usage(); 110 | return 1; 111 | } 112 | 113 | // Start infinite loop 114 | while (1) 115 | { 116 | ev_loop(g_srvctx.loop, 0); 117 | } 118 | 119 | return 0; 120 | } 121 | 122 | -------------------------------------------------------------------------------- /mqtthttpd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef mqtthttpd_H 17 | #define mqtthttpd_H 1 18 | 19 | #include 20 | 21 | #include "mqtt.h" 22 | #include "httpd.h" 23 | 24 | struct server_context 25 | { 26 | struct ev_loop *loop; 27 | 28 | ev_timer *timer_1hz; 29 | ev_timer *timer_10hz; 30 | struct ev_idle *idle_watcher; 31 | 32 | mqtt_context_t mqttctx; 33 | char mqtt_server[512]; 34 | uint16_t mqtt_port; 35 | char mqtt_name[256]; 36 | 37 | ebb_server httpd; 38 | char *httpd_root; 39 | httpd_virt vhttpd[MAX_VHOST]; 40 | uint32_t num_vhost; 41 | 42 | uint32_t httpdconnid; // global ids 43 | uint32_t streamid; // global ids 44 | }; 45 | typedef struct server_context server_context_t; 46 | 47 | 48 | int mqtthttpd_listen(int port); 49 | void mqtthttpd_start_sleep(int t); 50 | int mqtthttpd_run_script(char *filename); 51 | 52 | #endif 53 | 54 | -------------------------------------------------------------------------------- /msgcache.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include "msgcache.h" 19 | #include 20 | 21 | struct mc_node 22 | { 23 | RB_ENTRY(mc_node) entry; 24 | char *topic; 25 | char *msg; 26 | }; 27 | 28 | static int mc_cmp(struct mc_node *e1, struct mc_node *e2) 29 | { 30 | return strcmp(e1->topic, e2->topic); 31 | } 32 | 33 | RB_HEAD(msgcache, mc_node) mc_head = RB_INITIALIZER(&mc_head); 34 | RB_PROTOTYPE(msgcache, mc_node, entry, mc_cmp); 35 | RB_GENERATE(msgcache, mc_node, entry, mc_cmp); 36 | 37 | const char *msgcache_lookup(char *topic) 38 | { 39 | struct mc_node key; 40 | key.topic = topic; 41 | if (NULL == RB_FIND(msgcache, &mc_head, &key)) 42 | return NULL; 43 | return key.msg; 44 | } 45 | 46 | int msgcache_insert(char *topic, char *msg) 47 | { 48 | struct mc_node *found; 49 | struct mc_node *data = NULL; 50 | struct mc_node key; 51 | 52 | key.topic = topic; 53 | if (NULL != (found = RB_FIND(msgcache, &mc_head, &key))) 54 | { 55 | free(found->msg); 56 | if (NULL == (found->msg = strdup(msg))) 57 | return 1; 58 | else 59 | return 0; 60 | } 61 | else 62 | { 63 | if (NULL == (data = calloc(sizeof(struct mc_node), 1))) 64 | goto fail; 65 | 66 | if (NULL == (data->topic = strdup(topic))) 67 | goto fail; 68 | if (NULL == (data->msg = strdup(msg))) 69 | goto fail; 70 | 71 | if (NULL != RB_INSERT(msgcache, &mc_head, data)) // NULL means OK 72 | { 73 | LOG_CRITICAL("msgcache_insert mc collision"); // can't happen 74 | return 1; 75 | } 76 | } 77 | return 0; 78 | 79 | fail: 80 | if (NULL != data) 81 | { 82 | if (NULL != data->topic) 83 | free(topic); 84 | if (NULL != data->msg) 85 | free(msg); 86 | free(data); 87 | } 88 | return 1; 89 | } 90 | 91 | bool msgcache_remove(char *topic) 92 | { 93 | struct mc_node key; 94 | struct mc_node *res; 95 | 96 | key.topic = topic; 97 | res = RB_FIND(msgcache, &mc_head, &key); 98 | if (NULL == res) 99 | return false; 100 | 101 | RB_REMOVE(msgcache, &mc_head, res); 102 | free(res->topic); 103 | free(res->msg); 104 | free(res); 105 | return true; 106 | } 107 | 108 | 109 | void msgcache_foreach(msgcache_iterate_func f, void *userdata) 110 | { 111 | struct mc_node *i; 112 | RB_FOREACH(i, msgcache, &mc_head) 113 | { 114 | f(i->topic, i->msg, userdata); 115 | } 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /msgcache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef MSGCACHE_H 17 | #define MSGCACHE_H 1 18 | 19 | typedef void(*msgcache_iterate_func)(const char *topic, const char *msg, void *userdata); 20 | 21 | extern int msgcache_insert(char *topic, char *msg); 22 | extern bool msgcache_remove(char *topic); 23 | extern void msgcache_foreach(msgcache_iterate_func f, void *data); 24 | const char *msgcache_lookup(char *topic); 25 | 26 | #endif 27 | 28 | -------------------------------------------------------------------------------- /opt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "mqtthttpd.h" 30 | #include "mqtt.h" 31 | #include "httpd.h" 32 | #include "stream.h" 33 | #include "streamlist.h" 34 | #include "accesslog.h" 35 | #include "opt.h" 36 | 37 | static char *mqtt_server = NULL; 38 | static int mqtt_port = -1; 39 | static int httpd_port = -1; 40 | static char *httpd_root = NULL; 41 | static int verbose = 0; 42 | static char *client_name = NULL; 43 | static char *cert_file = NULL; 44 | static char *key_file = NULL; 45 | 46 | static struct option long_options[] = 47 | { 48 | {"help", no_argument, 0, 'h'}, 49 | {"device", required_argument, 0, 'd'}, 50 | {"server", required_argument, 0, 's'}, 51 | {"port", required_argument, 0, 'p'}, 52 | {"verbose", no_argument, 0, 'v'}, 53 | {"name", required_argument, 0, 'n'}, 54 | {"root", required_argument, 0, 'r'}, 55 | {"listen", required_argument, 0, 'l'}, 56 | {"sslcert", required_argument, 0, 'c'}, 57 | {"sslkey", required_argument, 0, 'k'}, 58 | {0, 0, 0, 0} 59 | }; 60 | 61 | extern server_context_t g_srvctx; 62 | 63 | void usage(void) 64 | { 65 | fprintf(stderr, "mqtthttpd\n"); 66 | fprintf(stderr, "mqtthttpd -v -r htdocs -l 8080 -n foo -s test.mosquitto.org -p 1883\n"); 67 | fprintf(stderr, " --verbose -v Chatty mode, -vv for more\n"); 68 | fprintf(stderr, " --server=test.mosquitto.org -s MQTT broker host\n"); 69 | fprintf(stderr, " --port=1883 -p MQTT broker port\n"); 70 | fprintf(stderr, " --name=mqttname -n MQTT client name\n"); 71 | fprintf(stderr, " --listen=8080 -l HTTP server port\n"); 72 | fprintf(stderr, " --root=htdocs -r HTTP server root\n"); 73 | fprintf(stderr, " --sslcert=ca-cert.pem -c SSL certificate\n"); 74 | fprintf(stderr, " --sslkey=ca-key.pem -k SSL key\n"); 75 | } 76 | 77 | int parse_options(int argc, char **argv) 78 | { 79 | int c; 80 | int option_index; 81 | 82 | while(1) 83 | { 84 | c = getopt_long(argc, argv, "n:vhs:p:l:r:c:k:", long_options, &option_index); 85 | if (c == -1) 86 | break; 87 | switch(c) 88 | { 89 | case 'h': 90 | return 1; 91 | break; 92 | case 'c': 93 | cert_file = strdup(optarg); 94 | break; 95 | case 'k': 96 | key_file = strdup(optarg); 97 | break; 98 | case 'n': 99 | client_name = strdup(optarg); 100 | break; 101 | case 's': 102 | mqtt_server = strdup(optarg); 103 | break; 104 | case 'r': 105 | httpd_root = strdup(optarg); 106 | break; 107 | case 'p': 108 | mqtt_port = atoi(optarg); 109 | break; 110 | case 'l': 111 | httpd_port = atoi(optarg); 112 | break; 113 | case 'v': 114 | verbose++; 115 | break; 116 | default: 117 | return 1; 118 | break; 119 | } 120 | } 121 | 122 | if (NULL == client_name || NULL == mqtt_server || -1 == mqtt_port || -1 == httpd_port || NULL == httpd_root) 123 | return 1; 124 | 125 | if (verbose == 0) 126 | log_setlevel(LOGLEVEL_WARN); 127 | else 128 | if (verbose == 1) 129 | log_setlevel(LOGLEVEL_INFO); 130 | else 131 | if (verbose == 2) 132 | log_setlevel(LOGLEVEL_DEBUG); 133 | 134 | if (0 != mqtt_connect(&(g_srvctx.mqttctx), client_name, mqtt_server, mqtt_port)) 135 | LOG_CRITICAL("Failed to connect to MQTT broker %s:%d", mqtt_server, mqtt_port); 136 | 137 | if (0 != httpd_listen(&(g_srvctx.httpd), httpd_root, httpd_port, cert_file, key_file)) 138 | LOG_CRITICAL("Failed to start HTTP server on port %d, serving %s", httpd_port, httpd_root); 139 | return 0; 140 | } 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /opt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef OPT_H 17 | #define OPT_H 1 18 | 19 | extern int parse_options(int argc, char **argv); 20 | extern void usage(void); 21 | 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /stream.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | 20 | #include "mqtthttpd.h" 21 | #include "stream.h" 22 | #include "streamlist.h" 23 | #include "topic.h" 24 | #include "sublist.h" 25 | 26 | extern server_context_t g_srvctx; 27 | 28 | stream_t *stream_create(const char *topic_patt_str) 29 | { 30 | stream_t *stream; 31 | 32 | if (NULL == (stream = (stream_t *)calloc(sizeof(stream_t), 1))) 33 | goto fail; 34 | if (NULL == (stream->topic_patt = topic_create_from_string(topic_patt_str))) 35 | goto fail; 36 | if (NULL == (stream->rsp_q = tailq_create())) 37 | goto fail; 38 | if (NULL == (stream->topicstr = strdup(topic_patt_str))) 39 | goto fail; 40 | stream->timeoutS = DEFAULT_STREAM_TIMEOUT_S; 41 | stream->streamid = g_srvctx.streamid++; 42 | 43 | random_read(stream->seckey, STREAM_SECKEY_SIZE); 44 | 45 | return stream; 46 | fail: 47 | stream_destroy(stream); 48 | return NULL; 49 | } 50 | 51 | void stream_destroy(stream_t *stream) 52 | { 53 | if (NULL != stream) 54 | { 55 | while(!tailq_empty(stream->rsp_q)) 56 | free(tailq_pop_head(stream->rsp_q)); 57 | if (NULL != stream->topic_patt) 58 | topic_destroy(stream->topic_patt); 59 | if (stream->have_connid) 60 | httpd_timeout_conn(stream->connid); 61 | if (NULL != stream->topicstr) 62 | free(stream->topicstr); 63 | if (NULL != stream->rsp_q) 64 | tailq_destroy(stream->rsp_q); 65 | free(stream); 66 | } 67 | } 68 | 69 | int stream_push(stream_t *stream, char *str) 70 | { 71 | char *s; 72 | if (NULL == (s = strdup(str))) 73 | return 1; 74 | return tailq_push_tail(stream->rsp_q, s); 75 | } 76 | 77 | void stream_refresh_timer(stream_t *stream) 78 | { 79 | stream->timeoutS = DEFAULT_STREAM_TIMEOUT_S; // refresh timer 80 | } 81 | 82 | void stream_drain(stream_t *stream, stream_drain_func f, void *userdata) 83 | { 84 | bool first = true; 85 | while(!tailq_empty(stream->rsp_q)) 86 | { 87 | char *s; 88 | s = (char *)tailq_pop_head(stream->rsp_q); 89 | f(stream, s, first, userdata); 90 | free(s); 91 | first = false; 92 | } 93 | stream_refresh_timer(stream); 94 | } 95 | 96 | bool stream_isempty(stream_t *stream) 97 | { 98 | return tailq_empty(stream->rsp_q); 99 | } 100 | 101 | void stream_set_connection(stream_t *stream, uint32_t connid) 102 | { 103 | if (stream->have_connid) 104 | LOG_DEBUG("stream hijacked, was connid=%u now %u", stream->connid, connid); 105 | stream->connid = connid; 106 | stream->have_connid = true; 107 | } 108 | 109 | bool stream_get_connection(stream_t *stream, uint32_t *connid) 110 | { 111 | if (stream->have_connid) 112 | { 113 | *connid = stream->connid; 114 | return true; 115 | } 116 | return false; 117 | } 118 | 119 | void stream_clear_connection(stream_t *stream) 120 | { 121 | stream->have_connid = false; 122 | } 123 | 124 | bool stream_1hz(uint32_t streamid, stream_t *stream, void *userdata) 125 | { 126 | if (0 == stream->timeoutS--) 127 | { 128 | LOG_DEBUG("streamid=%u timeout", streamid); 129 | if (sublist_remove(stream->topicstr)) 130 | { 131 | LOG_INFO("unsubscribing from %s", stream->topicstr); 132 | mqtt_unsubscribe(&g_srvctx.mqttctx, stream->topicstr, NULL); 133 | } 134 | return true; 135 | } 136 | return false; 137 | } 138 | 139 | -------------------------------------------------------------------------------- /stream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef STREAM_H 17 | #define STREAM_H 1 18 | 19 | #include "topic.h" 20 | #include "tailq.h" 21 | 22 | #define DEFAULT_STREAM_TIMEOUT_S 30 23 | #define STREAM_SECKEY_SIZE 16 24 | 25 | typedef struct 26 | { 27 | uint32_t streamid; 28 | topic_node_t *topic_patt; 29 | char *topicstr; 30 | tailq_t *rsp_q; 31 | bool have_connid; 32 | uint32_t connid; 33 | uint32_t timeoutS; 34 | uint8_t seckey[STREAM_SECKEY_SIZE]; 35 | } stream_t; 36 | 37 | 38 | typedef void(*stream_drain_func)(stream_t *stream, const char *str, bool first, void *userdata); 39 | 40 | extern stream_t *stream_create(const char *topic_patt_str); 41 | extern void stream_destroy(stream_t *stream); 42 | extern int stream_push(stream_t *stream, char *str); 43 | extern void stream_drain(stream_t *stream, stream_drain_func f, void *userdata); 44 | extern bool stream_isempty(stream_t *stream); 45 | extern void stream_set_connection(stream_t *stream, uint32_t connid); 46 | extern bool stream_get_connection(stream_t *stream, uint32_t *connid); 47 | extern void stream_clear_connection(stream_t *stream); 48 | extern bool stream_1hz(uint32_t streamid, stream_t *s, void *userdata); // return true to destroy 49 | extern void stream_refresh_timer(stream_t *stream); 50 | 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /streamlist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include "stream.h" 19 | #include "streamlist.h" 20 | #include 21 | 22 | struct stream_node 23 | { 24 | RB_ENTRY(stream_node) entry; 25 | uint32_t streamid; 26 | stream_t *stream; 27 | }; 28 | 29 | static int stream_cmp(struct stream_node *e1, struct stream_node *e2) 30 | { 31 | return (e1->streamid < e2->streamid ? -1 : e1->streamid > e2->streamid); 32 | } 33 | 34 | RB_HEAD(streamlist, stream_node) stream_head = RB_INITIALIZER(&stream_head); 35 | RB_PROTOTYPE(streamlist, stream_node, entry, stream_cmp); 36 | RB_GENERATE(streamlist, stream_node, entry, stream_cmp); 37 | 38 | int streamlist_insert(uint32_t streamid, stream_t *stream) 39 | { 40 | struct stream_node *data; 41 | 42 | if (NULL == (data = malloc(sizeof(struct stream_node)))) 43 | return 1; 44 | 45 | data->streamid = streamid; 46 | data->stream = stream; 47 | 48 | if (NULL == RB_INSERT(streamlist, &stream_head, data)) // NULL means OK 49 | { 50 | return 0; 51 | } 52 | else 53 | { 54 | LOG_CRITICAL("streamlist_insert streamid collision"); // can't happen 55 | return 1; 56 | } 57 | 58 | } 59 | 60 | stream_t *streamlist_remove(uint32_t streamid) 61 | { 62 | struct stream_node key; 63 | struct stream_node *res; 64 | stream_t *stream; 65 | 66 | key.streamid = streamid; 67 | res = RB_FIND(streamlist, &stream_head, &key); 68 | if (NULL == res) 69 | return NULL; 70 | RB_REMOVE(streamlist, &stream_head, res); 71 | stream = res->stream; 72 | free(res); 73 | 74 | return stream; 75 | } 76 | 77 | stream_t *streamlist_find(uint32_t streamid) 78 | { 79 | struct stream_node key; 80 | struct stream_node *res; 81 | 82 | key.streamid = streamid; 83 | res = RB_FIND(streamlist, &stream_head, &key); 84 | if (NULL == res) 85 | return NULL; 86 | return res->stream; 87 | } 88 | 89 | void streamlist_foreach(streamlist_iterate_func f, void *userdata) 90 | { 91 | struct stream_node *var, *nxt; 92 | // can't use foreach if we want to allow deletion 93 | for (var = RB_MIN(streamlist, &stream_head); var != NULL; var = nxt) 94 | { 95 | nxt = RB_NEXT(streamlist, &stream_head, var); 96 | if (f(var->streamid, var->stream, userdata)) 97 | { 98 | RB_REMOVE(streamlist, &stream_head, var); 99 | if (0 != streamlist_remove(var->streamid)) 100 | stream_destroy(var->stream); 101 | free(var); 102 | } 103 | } 104 | } 105 | 106 | 107 | -------------------------------------------------------------------------------- /streamlist.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef STREAMLIST_H 17 | #define STREAMLIST_H 1 18 | 19 | #include "stream.h" 20 | 21 | typedef bool (*streamlist_iterate_func)(uint32_t streamid, stream_t *s, void *userdata); // true deletes it 22 | 23 | extern int streamlist_insert(uint32_t streamid, stream_t *s); 24 | extern stream_t *streamlist_remove(uint32_t streamid); 25 | extern stream_t *streamlist_find(uint32_t streamid); 26 | extern void streamlist_foreach(streamlist_iterate_func f, void *data); 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /sublist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include "sublist.h" 19 | #include 20 | 21 | struct sub_node 22 | { 23 | RB_ENTRY(sub_node) entry; 24 | char *topic; 25 | uint32_t count; 26 | }; 27 | 28 | static int sub_cmp(struct sub_node *e1, struct sub_node *e2) 29 | { 30 | return strcmp(e1->topic, e2->topic); 31 | } 32 | 33 | RB_HEAD(sublist, sub_node) sub_head = RB_INITIALIZER(&sub_head); 34 | RB_PROTOTYPE(sublist, sub_node, entry, sub_cmp); 35 | RB_GENERATE(sublist, sub_node, entry, sub_cmp); 36 | 37 | static struct sub_node *sublist_find(char *topic) 38 | { 39 | struct sub_node key; 40 | key.topic = topic; 41 | return RB_FIND(sublist, &sub_head, &key); 42 | } 43 | 44 | bool sublist_insert(char *topic) 45 | { 46 | struct sub_node *found; 47 | 48 | if (NULL != (found = sublist_find(topic))) 49 | { 50 | found->count++; 51 | return false; 52 | } 53 | else 54 | { 55 | struct sub_node *data; 56 | 57 | if (NULL == (data = malloc(sizeof(struct sub_node)))) 58 | return false; 59 | 60 | if (NULL == (data->topic = strdup(topic))) 61 | { 62 | free(data); 63 | return false; 64 | } 65 | data->count = 1; 66 | 67 | if (NULL == RB_INSERT(sublist, &sub_head, data)) // NULL means OK 68 | return true; 69 | else 70 | { 71 | LOG_CRITICAL("sublist_insert sub collision"); // can't happen 72 | return false; 73 | } 74 | } 75 | } 76 | 77 | bool sublist_remove(char *topic) 78 | { 79 | struct sub_node key; 80 | struct sub_node *res; 81 | 82 | key.topic = topic; 83 | res = RB_FIND(sublist, &sub_head, &key); 84 | if (NULL == res) 85 | return false; 86 | 87 | if (--res->count == 0) 88 | { 89 | RB_REMOVE(sublist, &sub_head, res); 90 | free(res->topic); 91 | free(res); 92 | return true; 93 | } 94 | 95 | return false; 96 | } 97 | 98 | 99 | void sublist_foreach(sublist_iterate_func f, void *userdata) 100 | { 101 | struct sub_node *i; 102 | RB_FOREACH(i, sublist, &sub_head) 103 | { 104 | f(i->topic, i->count, userdata); 105 | } 106 | } 107 | 108 | 109 | -------------------------------------------------------------------------------- /sublist.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef SUBLIST_H 17 | #define SUBLIST_H 1 18 | 19 | typedef void(*sublist_iterate_func)(const char *topic, uint32_t count, void *userdata); 20 | 21 | extern bool sublist_insert(char *topic); 22 | extern bool sublist_remove(char *topic); 23 | extern void sublist_foreach(sublist_iterate_func f, void *data); 24 | 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /tailq.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "tailq.h" 22 | 23 | void tailq_destroy(tailq_t *tq) 24 | { 25 | if (NULL != tq) 26 | { 27 | struct tailq_node *n1; 28 | struct tailq_node *n2; 29 | 30 | n1 = TAILQ_FIRST(&tq->root); 31 | while (n1 != NULL) 32 | { 33 | n2 = TAILQ_NEXT(n1, entry); 34 | free(n1); 35 | n1 = n2; 36 | } 37 | 38 | free(tq); 39 | } 40 | } 41 | 42 | tailq_t *tailq_create(void) 43 | { 44 | tailq_t *tq; 45 | 46 | if (NULL == (tq = (tailq_t *)calloc(sizeof(tailq_t), 1))) 47 | goto fail; 48 | 49 | TAILQ_INIT(&tq->root); 50 | 51 | return tq; 52 | fail: 53 | tailq_destroy(tq); 54 | return NULL; 55 | } 56 | 57 | int tailq_push_head(tailq_t *tq, void *userdata) 58 | { 59 | struct tailq_node *n1; 60 | LOG_DEBUG("tailq_push_head %p %p", tq, userdata); 61 | if (NULL == (n1 = calloc(sizeof(struct tailq_node), 1))) 62 | return 1; 63 | n1->userdata = userdata; 64 | TAILQ_INSERT_HEAD(&tq->root, n1, entry); 65 | return 0; 66 | } 67 | 68 | int tailq_push_tail(tailq_t *tq, void *userdata) 69 | { 70 | struct tailq_node *n1; 71 | LOG_DEBUG("tailq_push_tail %p %p", tq, userdata); 72 | if (NULL == (n1 = calloc(sizeof(struct tailq_node), 1))) 73 | return 1; 74 | n1->userdata = userdata; 75 | TAILQ_INSERT_TAIL(&tq->root, n1, entry); 76 | return 0; 77 | } 78 | 79 | void *tailq_pop_head(tailq_t *tq) 80 | { 81 | struct tailq_node *n1; 82 | void *userdata; 83 | if (NULL == (n1 = TAILQ_FIRST(&tq->root))) 84 | return NULL; 85 | userdata = n1->userdata; 86 | LOG_DEBUG("tailq_pop_head %p %p", tq, userdata); 87 | TAILQ_REMOVE(&tq->root, n1, entry); 88 | free(n1); 89 | return userdata; 90 | } 91 | 92 | void *tailq_pop_tail(tailq_t *tq) 93 | { 94 | struct tailq_node *n1; 95 | void *userdata; 96 | if (NULL == (n1 = TAILQ_LAST(&tq->root, tailq))) 97 | return NULL; 98 | userdata = n1->userdata; 99 | LOG_DEBUG("tailq_pop_tail %p %p", tq, userdata); 100 | TAILQ_REMOVE(&tq->root, n1, entry); 101 | free(n1); 102 | return userdata; 103 | } 104 | 105 | bool tailq_empty(tailq_t *tq) 106 | { 107 | return TAILQ_EMPTY(&tq->root); 108 | } 109 | 110 | #if 0 111 | void tailq_dump(tailq_t *tq) 112 | { 113 | struct tailq_node *item; 114 | size_t sz; 115 | 116 | TAILQ_FOREACH(item, &tq->root, entry) 117 | { 118 | memcpy(&sz, item->userdata, sizeof(size_t)); 119 | LOG_DEBUG("%p %u", item->userdata, sz); 120 | if (sz > 1024 && tq->magic) 121 | { 122 | crash(); 123 | } 124 | } 125 | } 126 | #endif 127 | -------------------------------------------------------------------------------- /tailq.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef TAILQ_H 17 | #define TAILQ_H 1 18 | 19 | #include 20 | 21 | struct tailq_node 22 | { 23 | TAILQ_ENTRY(tailq_node) entry; 24 | void *userdata; 25 | }; 26 | 27 | typedef struct 28 | { 29 | TAILQ_HEAD(tailq, tailq_node) root; 30 | bool magic; 31 | } tailq_t; 32 | 33 | extern tailq_t *tailq_create(void); 34 | extern void tailq_destroy(tailq_t *tq); 35 | 36 | extern int tailq_push_head(tailq_t *tq, void *userdata); 37 | extern int tailq_push_tail(tailq_t *tq, void *userdata); 38 | 39 | extern void *tailq_pop_head(tailq_t *tq); 40 | extern void *tailq_pop_tail(tailq_t *tq); 41 | 42 | extern bool tailq_empty(tailq_t *tq); 43 | extern void tailq_dump(tailq_t *tq); 44 | 45 | #endif 46 | 47 | -------------------------------------------------------------------------------- /topic.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | 19 | #include "topic.h" 20 | 21 | 22 | topic_node_t *topic_create(void) 23 | { 24 | topic_node_t *tn; 25 | tn = (topic_node_t *)malloc(sizeof(topic_node_t)); 26 | tn->next = NULL; 27 | tn->str = NULL; 28 | return tn; 29 | } 30 | 31 | void topic_destroy(topic_node_t *tn) 32 | { 33 | while(NULL != tn) 34 | { 35 | topic_node_t *next; 36 | next = tn->next; 37 | if (NULL != tn->str) 38 | free(tn->str); 39 | free(tn); 40 | tn = next; 41 | } 42 | } 43 | 44 | void topic_print(topic_node_t *root) 45 | { 46 | while(NULL != root) 47 | { 48 | switch(root->type) 49 | { 50 | case TOPIC_NODE_TYPE_STR: 51 | LOG_USER_NOLN("%s", root->str); 52 | break; 53 | case TOPIC_NODE_TYPE_PLUS: 54 | LOG_USER_NOLN("+"); 55 | break; 56 | case TOPIC_NODE_TYPE_HASH: 57 | LOG_USER_NOLN("#"); 58 | break; 59 | } 60 | if (NULL != root->next) 61 | LOG_USER_NOLN("/"); 62 | root = root->next; 63 | } 64 | LOG_USER(""); 65 | } 66 | 67 | bool topic_match_string(topic_node_t *rule, const char *topicstr) 68 | { 69 | const char *elem_start; 70 | size_t len; 71 | 72 | elem_start = topicstr; 73 | 74 | while(1) 75 | { 76 | if (NULL == rule) 77 | return false; 78 | 79 | if (*topicstr == '/' || *topicstr == 0) 80 | { 81 | switch(rule->type) 82 | { 83 | case TOPIC_NODE_TYPE_STR: 84 | len = strlen(rule->str); 85 | if (len != (topicstr - elem_start)) 86 | return false; 87 | if (0!=memcmp(elem_start, rule->str, len)) 88 | return false; 89 | break; 90 | case TOPIC_NODE_TYPE_PLUS: 91 | // matches 92 | break; 93 | case TOPIC_NODE_TYPE_HASH: 94 | return true; 95 | break; 96 | } 97 | 98 | rule = rule->next; 99 | elem_start = topicstr+1; 100 | } 101 | 102 | if (0 == *topicstr++) 103 | break; 104 | } 105 | 106 | return true; 107 | } 108 | 109 | topic_node_t *topic_create_from_string(const char *topicstr) 110 | { 111 | topic_node_t *root = NULL; 112 | topic_node_t *p = NULL; 113 | bool seen_hash = false; 114 | const char *elem_start; 115 | 116 | elem_start = topicstr; 117 | 118 | while(1) 119 | { 120 | if (*topicstr == '/' || *topicstr == 0) 121 | { 122 | if (seen_hash) // hash must be last item 123 | { 124 | LOG_ERROR("Hierarchy should end at #"); 125 | goto fail; 126 | } 127 | 128 | if (NULL == root) 129 | { 130 | if (NULL == (root = topic_create())) 131 | { 132 | LOG_ERROR("create root failed"); 133 | goto fail; 134 | } 135 | p = root; 136 | } 137 | else 138 | { 139 | if (NULL == (p->next = topic_create())) 140 | { 141 | LOG_ERROR("create child failed"); 142 | goto fail; 143 | } 144 | p = p->next; 145 | } 146 | 147 | if ((1 == topicstr - elem_start) && *elem_start == '+') 148 | p->type = TOPIC_NODE_TYPE_PLUS; 149 | else 150 | if ((1 == topicstr - elem_start) && *elem_start == '#') 151 | p->type = TOPIC_NODE_TYPE_HASH; 152 | else 153 | { 154 | p->type = TOPIC_NODE_TYPE_STR; 155 | if (NULL == (p->str = (char *)malloc((topicstr - elem_start)+1))) 156 | { 157 | LOG_ERROR("string alloc"); 158 | goto fail; 159 | } 160 | memcpy(p->str, elem_start, topicstr - elem_start); 161 | p->str[topicstr - elem_start] = 0; 162 | } 163 | 164 | 165 | elem_start = topicstr+1; 166 | } 167 | 168 | if (0 == *topicstr++) 169 | break; 170 | } 171 | 172 | return root; 173 | fail: 174 | topic_destroy(root); 175 | return NULL; 176 | } 177 | 178 | #if 0 179 | int topic_test(void) 180 | { 181 | topic_node_t *tn; 182 | const char *topic = "a/b/c/d"; 183 | const char *good_rules[] = { 184 | "a/b/c/d", "+/b/c/d", "a/+/c/d", "a/+/+/d", "+/+/+/+", 185 | "#", "a/#", "a/b/#", "a/b/c/#", "+/b/c/#" 186 | }; 187 | const char *bad_rules[] = { 188 | "a/b/c", "b/+/c/d", "+/+/", 189 | "b/#", "/#", "/", "" 190 | }; 191 | int good_rules_len = sizeof(good_rules) / sizeof(char *); 192 | int bad_rules_len = sizeof(bad_rules) / sizeof(char *); 193 | int i; 194 | 195 | for (i=0;i 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef TOPIC_H 17 | #define TOPIC_H 1 18 | 19 | typedef enum 20 | { 21 | TOPIC_NODE_TYPE_STR, 22 | TOPIC_NODE_TYPE_PLUS, 23 | TOPIC_NODE_TYPE_HASH 24 | } topic_node_type_t; 25 | 26 | struct topic_node 27 | { 28 | topic_node_type_t type; 29 | char *str; 30 | struct topic_node *next; 31 | }; 32 | typedef struct topic_node topic_node_t; 33 | 34 | 35 | extern topic_node_t *topic_create(void); 36 | extern void topic_destroy(topic_node_t *tn); 37 | extern void topic_print(topic_node_t *root); 38 | extern bool topic_match_string(topic_node_t *rule, const char *topicstr); 39 | extern topic_node_t *topic_create_from_string(const char *topicstr); 40 | 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /webapi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef WEBAPI_H 17 | #define WEBAPI_H 1 18 | 19 | void webapi_publish_cb(int mid); 20 | void webapi_subscribe_cb(int mid); 21 | void webapi_message_cb(int mid, char *topic, char *msg); 22 | void webapi_mqtt_failall_cb(void); 23 | 24 | #endif 25 | 26 | -------------------------------------------------------------------------------- /webapi_serve.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "mqtthttpd.h" 22 | #include "httpd.h" 23 | #include "webapi.h" 24 | #include "webapi_serve.h" 25 | #include "httpdconnlist.h" 26 | #include "accesslog.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #define DEFAULT_FILENAME "index.html" 38 | 39 | extern server_context_t g_srvctx; 40 | 41 | /*************** mimetype handling */ 42 | typedef struct 43 | { 44 | const char *ext; 45 | const char *mime; 46 | } filext_t; 47 | 48 | static filext_t extensions[] = 49 | { 50 | {".html", "text/html"}, 51 | {".js", "application/x-javascript"}, 52 | {".css", "text/css"}, 53 | {".gif", "image/gif"}, 54 | {".png", "image/png"}, 55 | {".svg", "image/svg+xml"}, 56 | {".mpg", "video/mpeg"}, 57 | {".c", "text/plain"}, 58 | {".h", "text/plain"}, 59 | {".gz", "text/plain"}, 60 | {".zip", "text/plain"}, 61 | {NULL, "text/plain"} 62 | }; 63 | 64 | static const char *lookup_mimetype(const char *filename) 65 | { 66 | filext_t *p = extensions; 67 | while(p->ext != NULL) 68 | { 69 | if (NULL != strcasestr(filename, p->ext)) 70 | break; 71 | p++; 72 | } 73 | return p->mime; 74 | } 75 | /************************************/ 76 | 77 | int webapi_serve(uint32_t connid, uint32_t method, int argc, char **argv, const char *body, const char *reqpath, int vhost) 78 | { 79 | int fd = -1; 80 | size_t count; 81 | ebb_connection *conn; 82 | ebb_connection_info *conninfo = NULL; 83 | struct stat st; 84 | char path[PATH_MAX]; 85 | uint8_t buf[512]; 86 | const char *rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT"; 87 | char reqpath_fname[4096]; 88 | 89 | LOG_DEBUG("webapi_serve %s", reqpath); 90 | 91 | if (strlen(reqpath) > sizeof(reqpath_fname)/2) // FIXME 92 | { 93 | LOG_INFO("path too long"); 94 | return 1; 95 | } 96 | if (0 != uriUriStringToUnixFilenameA(reqpath, reqpath_fname)) 97 | { 98 | LOG_INFO("bad req %s", reqpath); 99 | return 1; 100 | } 101 | 102 | reqpath = reqpath_fname; 103 | 104 | if (NULL == (conn = httpdconnlist_find(connid))) 105 | { 106 | LOG_WARN("no such connid %d", connid); 107 | goto fail; 108 | } 109 | conninfo = (ebb_connection_info *)(conn->data); 110 | if (NULL == conninfo) 111 | goto fail; 112 | 113 | if (method != EBB_GET) 114 | goto fail; 115 | 116 | if (NULL == reqpath || reqpath[0] == 0) 117 | reqpath = "/"; 118 | 119 | if (vhost < 0) 120 | { 121 | snprintf(path, sizeof(path), "%s%s", g_srvctx.httpd_root, reqpath); 122 | if (!path_in_dir(g_srvctx.httpd_root, path)) 123 | { 124 | LOG_DEBUG("request for %s out of root (%s)", path, g_srvctx.httpd_root); 125 | goto fail; 126 | } 127 | } 128 | else 129 | { 130 | snprintf(path, sizeof(path), "%s%s", g_srvctx.vhttpd[vhost].rootdir, reqpath); 131 | if (!path_in_dir(g_srvctx.vhttpd[vhost].rootdir, path)) 132 | { 133 | LOG_DEBUG("request for %s out of root (%s)", path, g_srvctx.vhttpd[vhost].rootdir); 134 | goto fail; 135 | } 136 | } 137 | 138 | if ((fd = open(path, O_RDONLY)) < 0) 139 | goto fail; 140 | if (fstat(fd, &st) < 0) 141 | goto fail; 142 | 143 | if (S_ISDIR(st.st_mode)) 144 | { 145 | char path2[PATH_MAX]; 146 | strcpy(path2, path); 147 | snprintf(path, sizeof(path), "%s%s", path2, DEFAULT_FILENAME); 148 | close(fd); 149 | fd = -1; 150 | if (vhost < 0) 151 | { 152 | if (!path_in_dir(g_srvctx.httpd_root, path)) 153 | { 154 | LOG_DEBUG("request for %s out of root", path); 155 | goto fail; 156 | } 157 | } 158 | else 159 | { 160 | if (!path_in_dir(g_srvctx.vhttpd[vhost].rootdir, path)) 161 | { 162 | LOG_DEBUG("request for %s out of root", path); 163 | goto fail; 164 | } 165 | } 166 | if ((fd = open(path, O_RDONLY)) < 0) 167 | goto fail; 168 | if (fstat(fd, &st) < 0) 169 | goto fail; 170 | } 171 | 172 | if(!S_ISREG(st.st_mode)) 173 | goto fail; 174 | 175 | strcpy(conninfo->mimetype, lookup_mimetype(path)); 176 | 177 | // fill out a last-modified header 178 | strftime( conninfo->last_modified, sizeof(conninfo->last_modified), rfc1123fmt, localtime( &st.st_mtime ) ); 179 | 180 | if (st.st_mtime <= conninfo->ifmodifiedsince && conninfo->ifmodifiedsince != 0) 181 | { 182 | // LOG_INFO("not modified since ifms=%u mtime=%u", conninfo->ifmodifiedsince, st.st_mtime); 183 | conninfo->http_status = 304; // not modified 184 | LOG_DEBUG("not modified %s", path); 185 | httpd_close(connid); 186 | if (fd != 1) 187 | close(fd); 188 | return 0; 189 | } 190 | 191 | while((count = read(fd, buf, sizeof(buf)-1)) > 0) 192 | httpd_pushbin(connid, buf, count, false); 193 | 194 | httpd_close(connid); 195 | LOG_DEBUG("served %s", path); 196 | 197 | accesslog_write("%s %s %s", conninfo->ipaddr, path, reqpath); 198 | 199 | if (fd != -1) 200 | close(fd); 201 | return 0; 202 | fail: 203 | if (fd != -1) 204 | close(fd); 205 | if (NULL != conninfo) 206 | conninfo->http_status = 404; // not found 207 | return 1; 208 | } 209 | 210 | 211 | -------------------------------------------------------------------------------- /webapi_serve.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Toby Jaffey 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #ifndef WEBAPI_SERVE_H 17 | #define WEBAPI_SERVE_H 1 18 | int webapi_serve(uint32_t connid, uint32_t method, int argc, char **argv, const char *body, const char *reqpath, int vhost); 19 | #endif 20 | 21 | --------------------------------------------------------------------------------