Repository: circonus-labs/fq Branch: master Commit: aa2bb0149df7 Files: 78 Total size: 800.6 KB Directory structure: gitextract__9lkm2sj/ ├── .gitignore ├── ChangeLog.md ├── LICENSE ├── Makefile ├── README.md ├── coverity_model.c ├── docs/ │ └── fq_protocol.md ├── dtest.d ├── fq.d ├── fq.h ├── fq_bench.c ├── fq_client.c ├── fq_dtrace.blank.h ├── fq_dtrace.d ├── fq_msg.c ├── fq_rcvr.c ├── fq_sndr.c ├── fq_utils.c ├── fqc.c ├── fqd.c ├── fqd.h.in ├── fqd_ccs.c ├── fqd_config.c ├── fqd_dss.c ├── fqd_dyn_sample.c ├── fqd_http.c ├── fqd_listener.c ├── fqd_peer.c ├── fqd_private.h ├── fqd_prog.c ├── fqd_queue.c ├── fqd_queue_jlog.c ├── fqd_queue_mem.c ├── fqd_routemgr.c ├── fqs.c ├── fqtool.c ├── http_parser.c ├── http_parser.h ├── java/ │ ├── Makefile │ ├── fq_rcvr.java │ ├── pom.xml │ └── src/ │ ├── com/ │ │ └── omniti/ │ │ └── labs/ │ │ ├── FqClient.java │ │ ├── FqClientImplDebug.java │ │ ├── FqClientImplInterface.java │ │ ├── FqClientImplNoop.java │ │ ├── FqCommand.java │ │ ├── FqCommandProtocolError.java │ │ ├── FqDataProtocolError.java │ │ ├── FqHeartbeatException.java │ │ └── FqMessage.java │ └── main/ │ └── java/ │ └── com/ │ └── circonus/ │ ├── FqClient.java │ ├── FqClientImplDebug.java │ ├── FqClientImplInterface.java │ ├── FqClientImplNoop.java │ ├── FqCommand.java │ ├── FqCommandProtocolError.java │ ├── FqDataProtocolError.java │ ├── FqHeartbeatException.java │ └── FqMessage.java ├── lua/ │ ├── fq-proxy │ ├── fq-receiver │ ├── fq-sender │ ├── fqclient.lua.tail │ └── generatelua.sh ├── service-configs/ │ ├── 50-circonus-fq.preset │ ├── circonus-fq.service │ └── daemon_options ├── test/ │ ├── lua-support/ │ │ └── init.lua │ ├── run-tests.sh │ └── test_spec.lua └── web/ ├── css/ │ ├── bootstrap-theme.css │ ├── bootstrap.css │ ├── colorbrewer.css │ └── theme.css ├── index.html └── js/ ├── bootstrap.js ├── colorbrewer.js └── fq.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.a *.o *.lo *.so* fqd.h fqd fqc fqs fqtool fq_bench fq_dtrace.h fq_rcvr fq_sndr lua/fqclient.lua Makefile.build Makefile.depend *.dSYM/ .*.swp ck-*/Makefile ck-*/build/ck.spec ck-*/doc/Makefile java/classes/ java/*.class java/fqclient.jar *~ .autotools .cproject .project .settings/ test/fqd.sqlite test/out.log test/test_detail.xml ================================================ FILE: ChangeLog.md ================================================ # ChangeLog ### v0.13.11 * Fix test issue where libfq could not be found. * Minor change to compiler optimization flag. Level `O5` does not exist and is effectively `O3`. * Update systemd install path. The new path is equivalent to the old, but avoids potential issues with `/lib` being a symbolic link on popular Linux distros. * Fix potential race in host resolution. ### v0.13.10 * Correct a build issue when libbcd support is disabled. * Fix race condition in client connection status. ### v0.13.9 * Fix file descriptor leak on connection error. * Correct usage of `pthread_*` return values. Remove invalid use of `volatile`. * Add missing `volatile` in Java client, and replace `LinkedList` with `ArrayDeque`. ### v0.13.8 * Upgrade jquery to 3.5.1 for FQ user interface ### v0.13.7 * Queue drops are tracked as `dropped_to`. * Web UI updated to display queue drops/rate. * -b deprecated, -B added, and BCD is disabled by default. ### v0.13.6 * Track drops to queues as `dropped_in` in status. ### v0.13.5 * Force disconnect on message read/write error. * Reuse listener threads. ### v0.13.4 * Various code cleanups. * Better bounds checking on auth handshake (allow full size). * Fix BCD integration. ### v0.13.3 * Set `SO_REUSEPORT = 1` for listener. * Add `-b` to disable BCD/backtrace integration. ### v0.13.2 * Name threads on Linux to aid debugging. * Prevent abort when queue removal fails. ### v0.13.1 * Add libbcd support for catching faults with backtrace. ### v0.13.0 * Place fq modules in $(LIBEXECDIR)/fq * Automatically load all available modules ### v0.12.1 * Move the `valnode_t` definition into fq.h. * Fix hex construction macro. * Support var-args in loadable program functions. * Fix multi-argument parsing in routing grammar. ### v0.12.0 * Omit unneeded library dependencies on Illumos. * Make poll() calls resume after signal interruption. ### v0.11.0 * Use socket keep-alives for client/server connections. * Fix use-after-free bug in lua ffi client bindings. * Fix test suite. * Explicit registration of local function to better navigate changing dlsym "self" personalities. * ENABLE_DTRACE=1 Linux build flag. ### v0.10.14 * Fixes to fq-client.lua on OmniOS ### v0.10.13 * Add `fqs` tool for sending messages from stdin * Test suite utilizing `mtevbusted` from [libmtev](https://github.com/circonus-labs/libmtev/) (PR #37) ### v0.10.12 * Fix misuse of stack for freeing messages (0.10.11 fix was bad). * Add Linux futex support for lower-latency idle network wake-up. * Ensure message ordering on per-client data connections. ### v0.10.11 * Fix crash when shutting down client that has never seen a message. ### v0.10.10 * Fix source management issue. 0.10.9 tag exluded commits. * Change message free-lists to prevent use-after-free on thread exit. ### v0.10.9 * Fix builds on newer Mac OS X * Change message free-lists to prevent use-after-free on thread exit. * Fix bug in server->client heartbeats not beating. ### v0.10.8 * Fix querystring parsing crash when parameters are not k=v form. * Resume on-disk queues at the right checkpoint location. ### v0.10.7 * Fix bug in route binding prefix matching causing misdirected messages (clients could get more than they asked for). * Fix bug on some Linux systems regarding exposed symbols. ### v0.10.6 * Fix crashing issue enqueueing messages due to unsafe use of spsc fifos. * Add dynamic loading of routing program extensions. * Move the "sample" function to a dynamic extension for example purposes. ================================================ FILE: LICENSE ================================================ Copyright (c) 2013 OmniTI Computer Consulting, Inc. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # If you want a verbose make (visible commands) add V=1 to you invocation .SUFFIXES: .lo CC=gcc LD=gcc LN_S=ln -s COPT=-O3 TAR=tar SED=sed PREFIX=/usr/local INCLUDEDIR=$(PREFIX)/include LIBDIR=$(PREFIX)/lib LIBEXECDIR=$(PREFIX)/libexec BINDIR=$(PREFIX)/bin SBINDIR=$(PREFIX)/sbin VARLIBFQ=$(PREFIX)/var/lib/fq LUADIR=$(PREFIX)/share/lua/5.1 INSTALL=install SHLD=$(LD) -shared MODULELD=$(LD) -shared LIBEXT=so SHCFLAGS=-fPIC DTRACE=/usr/sbin/dtrace OS=$(shell uname) FQ_MAJOR=0 FQ_MINOR=13 FQ_MICRO=12 Q= ifeq ($(V),) Q=@ endif VENDOR_CFLAGS= VENDOR_LDFLAGS= DTRACEFLAGS= EXTRA_CFLAGS=$(VENDOR_CFLAGS) -g -D_REENTRANT -std=gnu99 -pedantic -Wall EXTRA_CFLAGS+=-DVARLIBFQDIR=\"$(VARLIBFQ)\" EXTRA_CFLAGS+=-DLIBEXECDIR=\"$(LIBEXECDIR)/fq\" #EXTRA_CFLAGS+=-DDEBUG CLIENT_OBJ=fq_client.o fq_msg.o fq_utils.o CLIENT_OBJ_LO=$(CLIENT_OBJ:%.o=%.lo) FQD_OBJ=fqd.o fqd_listener.o fqd_ccs.o fqd_dss.o fqd_config.o \ fqd_queue.o fqd_routemgr.o fqd_queue_mem.o fqd_queue_jlog.o \ fqd_http.o fqd_prog.o fqd_peer.o http_parser.o \ $(CLIENT_OBJ) FQC_OBJ=fqc.o $(CLIENT_OBJ) FQD_SAMPLE_OBJ=fqd_dyn_sample.lo FQD_DTRACE_OBJ= skip_bcd=$(NO_BCD) ifdef skip_bcd FQDLIBS=-ljlog -lsqlite3 EXTRA_CFLAGS+=-DNO_BCD else FQDLIBS=-ljlog -lsqlite3 -lbcd endif LIBS+=-lck SHLDFLAGS= ifeq ($(OS),SunOS) SHLDFLAGS+=-R$(LIBDIR) LIBS+=-lcrypto -lsocket -lnsl -lumem -luuid LIBLIBS+=-luuid -lsocket -lnsl EXTRA_CFLAGS+=-D_XOPEN_SOURCE=600 EXTRA_CFLAGS+=-D_BSD_SOURCE EXTRA_CFLAGS+=-D__EXTENSIONS__ -DHAVE_UINTXX_T -DSIZEOF_LONG_LONG_INT=8 -m64 -D_REENTRANT -DHAVE_GETHOSTBYNAME_R EXTRA_SHLDFLAGS=-m64 FQD_DTRACE_OBJ=fq_dtrace.o DTRACEFLAGS=-xnolibs else ifeq ($(OS),Darwin) MODULELD=ld -bundle LOADER=-bundle_loader fqd -lc EXTRA_CFLAGS+=-D_DARWIN_C_SOURCE -DHAVE_U_INTXX_T -DHAVE_INTXX_T -DHAVE_U_INT64_T -DHAVE_INT64_T \ -Wno-dollar-in-identifier-extension -Wno-gnu-statement-expression -Wno-deprecated-declarations #EXTRA_CFLAGS+=-Weverything LIBEXT=dylib else ifeq ($(OS),Linux) EXTRA_CFLAGS+=-D_XOPEN_SOURCE=600 EXTRA_CFLAGS+=-D_DEFAULT_SOURCE -DBYTE_ORDER=__BYTE_ORDER -DBIG_ENDIAN=__BIG_ENDIAN SHLDFLAGS+=-Wl,-rpath=$(LIBDIR) LDFLAGS+=-rdynamic -export-dynamic LIBS+=-lcrypto -lpthread -ldl -luuid -lrt LIBLIBS+=-lpthread -luuid -lrt DTRACE= ifeq ($(ENABLE_DTRACE),1) DTRACE=/bin/dtrace FQD_DTRACE_OBJ=fq_dtrace.o endif else ifeq ($(OS),FreeBSD) SHLDFLAGS+=-Wl,-rpath=$(LIBDIR) LDFLAGS+=-rdynamic LIBS+=-lcrypto -lpthread -luuid -lexecinfo LIBLIBS+=-lpthread -luuid -lexecinfo FQD_DTRACE_OBJ=fq_dtrace.o endif endif endif endif all: libfq.$(LIBEXT) libfq.a fqd fqc fqs fqtool fq_sndr fq_rcvr fq_bench \ fq-sample.so lua/fqclient.lua include Makefile.depend SHLDFLAGS+=$(VENDOR_LDFLAGS) -L$(LIBDIR) ifeq ($(OS),Darwin) SHLDFLAGS+=-current_version $(FQ_MAJOR).$(FQ_MINOR).$(FQ_MICRO) -install_name $(LIBDIR)/libfq.$(FQ_MAJOR).dylib SOLONG=libfq.$(FQ_MAJOR).$(FQ_MINOR).$(FQ_MICRO).dylib SOSHORT=libfq.$(FQ_MAJOR).dylib LIBNAME=libfq.dylib else SHLDFLAGS+=-Wl,-soname,libfq.so.$(FQ_MAJOR) SOLONG=libfq.so.$(FQ_MAJOR).$(FQ_MINOR).$(FQ_MICRO) SOSHORT=libfq.so.$(FQ_MAJOR) LIBNAME=libfq.so endif CFLAGS+=$(EXTRA_CFLAGS) SHCFLAGS+=$(EXTRA_CFLAGS) LDFLAGS+=$(VENDOR_LDFLAGS) fqd.h: fqd.h.in sed -e 's/@@FQ_MAJOR@@/'$(FQ_MAJOR)'/g;' \ -e 's/@@FQ_MINOR@@/'$(FQ_MINOR)'/g;' \ -e 's/@@FQ_MICRO@@/'$(FQ_MICRO)'/g;' < fqd.h.in > fqd.h fq_dtrace.h: fq_dtrace.d -$(DTRACE) $(DTRACEFLAGS) -h -o $@ -s $< if [ ! -f $@ ]; then cp fq_dtrace.blank.h $@; fi fq_dtrace.o: $(FQD_OBJ) $(DTRACE) $(DTRACEFLAGS) -64 -G -s fq_dtrace.d -o $@ $(FQD_OBJ) fq_dtrace.blank.h: fq_dtrace.h awk 'BEGIN{print "#if 0"} /#else/,/#endif/{print}' $< > $@ fqd: $(FQD_OBJ) $(FQD_DTRACE_OBJ) @echo " - linking $@" $(Q)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(FQD_OBJ) $(FQD_DTRACE_OBJ) $(LIBS) $(FQDLIBS) fqc: $(FQC_OBJ) @echo " - linking $@" $(Q)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(FQC_OBJ) $(LIBS) fq-sample.so: fqd $(FQD_SAMPLE_OBJ) $(Q)$(MODULELD) $(LOADER) $(EXTRA_SHLDFLAGS) -o $@ $(FQD_SAMPLE_OBJ) fq_sndr: fq_sndr.o libfq.a @echo " - linking $@" $(Q)$(CC) $(CFLAGS) $(LDFLAGS) -L. -lfq -o $@ $^ $(LIBS) fqs: fqs.o libfq.a @echo " - linking $@" $(Q)$(CC) $(CFLAGS) $(LDFLAGS) -L. -lfq -o $@ $^ $(LIBS) fq_rcvr: fq_rcvr.o libfq.a @echo " - linking $@" $(Q)$(CC) $(CFLAGS) $(LDFLAGS) -L. -lfq -o $@ $^ $(LIBS) fqtool: fqtool.o libfq.a @echo " - linking $@" $(Q)$(CC) $(CFLAGS) $(LDFLAGS) -L. -lfq -o $@ $^ $(LIBS) fq_bench: fq_bench.o libfq.a @echo " - linking $@" $(Q)$(CC) $(CFLAGS) $(LDFLAGS) -L. -lfq -o $@ $^ $(LIBS) libfq.$(LIBEXT): $(CLIENT_OBJ_LO) @echo " - creating $@" $(Q)$(SHLD) $(EXTRA_SHLDFLAGS) $(SHLDFLAGS) -o $@ $(CLIENT_OBJ_LO) $(LIBLIBS) $(LN_S) -f $@ $(SOSHORT) libfq.a: $(CLIENT_OBJ) @echo " - creating $@" $(Q)ar cr $@ $(CLIENT_OBJ) .c.o: $< @echo " - compiling $<" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(COPT) -o $@ -c $< .c.lo: $< @echo " - compiling $<" $(Q)$(CC) $(CPPFLAGS) $(SHCFLAGS) -o $@ -c $< Makefile.depend: fq_dtrace.h fqd.h @echo " - make depend" $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c > Makefile.depend java/fqclient.jar: (cd java && $(MAKE) fqclient.jar) lua/fqclient.lua: (cd lua; ./generatelua.sh) install: all $(INSTALL) -d $(DESTDIR)$(INCLUDEDIR) $(INSTALL) -m 0444 fq.h $(DESTDIR)$(INCLUDEDIR)/fq.h $(INSTALL) -d $(DESTDIR)$(LIBDIR) $(INSTALL) -m 0444 libfq.a $(DESTDIR)$(LIBDIR)/libfq.a $(INSTALL) -m 0555 libfq.$(LIBEXT) $(DESTDIR)$(LIBDIR)/$(SOLONG) $(LN_S) -f $(SOLONG) $(DESTDIR)$(LIBDIR)/$(SOSHORT) $(LN_S) -f $(SOLONG) $(DESTDIR)$(LIBDIR)/$(LIBNAME) $(INSTALL) -d $(DESTDIR)$(LIBEXECDIR)/fq $(INSTALL) -m 0555 fq-sample.so $(DESTDIR)$(LIBEXECDIR)/fq/fq-sample.so $(INSTALL) -d $(DESTDIR)$(BINDIR) $(INSTALL) -m 0555 fqtool $(DESTDIR)$(BINDIR)/fqtool $(INSTALL) -m 0555 fqs $(DESTDIR)$(BINDIR)/fqs $(INSTALL) -d $(DESTDIR)$(SBINDIR) $(INSTALL) -m 0555 fqd $(DESTDIR)$(SBINDIR)/fqd $(INSTALL) -d $(DESTDIR)$(VARLIBFQ) $(TAR) cf - web | (cd $(DESTDIR)$(VARLIBFQ) && $(TAR) xf -) $(INSTALL) -d $(DESTDIR)/usr/lib/dtrace $(INSTALL) -m 0444 fq.d $(DESTDIR)/usr/lib/dtrace/fq.d $(INSTALL) -d $(DESTDIR)$(LUADIR) $(INSTALL) -m 0644 lua/fqclient.lua $(DESTDIR)$(LUADIR) $(INSTALL) -m 0555 lua/fq-sender lua/fq-receiver lua/fq-proxy $(DESTDIR)$(BINDIR) install-systemd: install $(INSTALL) -d $(DESTDIR)/usr/lib/systemd/system $(INSTALL) -d $(DESTDIR)/usr/lib/systemd/system-preset $(INSTALL) -m 0644 service-configs/circonus-fq.service $(DESTDIR)/usr/lib/systemd/system/circonus-fq.service $(INSTALL) -m 0644 service-configs/50-circonus-fq.preset $(DESTDIR)/usr/lib/systemd/system-preset/50-circonus-fq.preset $(INSTALL) -m 0644 service-configs/daemon_options $(DESTDIR)$(VARLIBFQ)/daemon_options clean: rm -f *.o *.a fqc fqd fqs *.$(LIBEXT) $(SOSHORT) fq_dtrace.h lua/fqclient.lua .PHONY: test test: lua/fqclient.lua ./test/run-tests.sh ================================================ FILE: README.md ================================================ # fq. Coverity Scan Build Status fq is a *brokered* message queue using a publish subscribe model. It is architected for performance and isn't (today) designed for large numbers of connected clients. +------------+ +-----------+ |- exchange -|<-- (msg publication) --|- client0 -| +------------+ +-----------+ |- routemap -| +------------+ | | +---------+ | +--------------|- queue -| | +---------+ +---------+ | |- queue -| | +-----------+ +---------+ +---|- client1 -| | +-----------+ | +-----------+ +--|- client2 -| | +-----------+ | +-----------+ |- client3 -| +-----------+ ## Terminology ### Broker The `fqd` process. The daemon through which all knowledge passes. ### Peers Peers are connected `fqd` processes. It is important to note that peers are unidirectional. If A peers with B, then A will act as a client to B. If you want bidirectional peering, you must specify that A peers with B and B peers with A. The system aims to prevent cyclic delivery of messages efficiently. Adding peers is done directly via fqd's sqlite DB store: ``` ; sqlite3 /var/lib/fq/fqd.sqlite sqlite> INSERT INTO "upstream" (host, port, source, password, exchange, program, permanent_binding) VALUES('peerB',8765,'fqd-peera//mem:drop,private,backlog=4096','none','logging','prefix:"http.access.json."','false'); ``` ### Client * [C client - libfq](https://github.com/postwait/fq/blob/master/fq.h#L164-L205) * [Java client - fq.jar](https://github.com/postwait/fq/blob/master/java/src/com/omniti/labs/FqClientImplInterface.java) * [Node.js client - fq](https://www.npmjs.com/package/fq) * [Go client - fq](https://godoc.org/github.com/postwait/gofq) * submission-only /submit API (see below) A client is an applications connection to fq over TCP/IP to send or receive messages. A client makes two TCP/IP connections to fq. An application can present itself to fq as multiple clients at one time (by opening new pairs of connections). See Queues for reasons why. ### Exchanges Exchanges are like buses on which messages may be sent. You cannot send a message without doing so on an exchange. Exchanges are created within fq on-demand. ### Queues Queues are queues. If you stick something in one end, you should expect it to come out the other. A single queue may have multiple clients subscribed. When a client connects, it is attached to one and only one queue. If an application wishes to attach to more than one queue, it should present as multiple clients. Queues use a competitive consumption model meaning that if multiple clients are attached to a single queue, the messages sent to that queue will be distributed over the clients such that no two clients will see the same message. #### Queue Types Queues can be of type `mem` or `disk`. The contents of memory queues will not survive restarts. Various parameters can be set on a queue using the syntax `type:param1,param2`. #### Sharing Queues with the `public` parameter can have multiple clients connected to them (in which case they compete for messages). If you want a private queue you can specify the `private` parameter. #### Policy Queues can either have a `block` or `drop` policy. The drop policy means that messages that would be routed to a queue that is full will be dropped and never delivered. The block policy will cause the publisher to wait until there is room in the queue. The block policy makes no sense on a disk queue. #### Backlog The `backlog=` parameter will specify how many messages may be held in the queue before the block or drop policies are applied. #### Permanence If you want a queue to be remembered by fqd, you can specify `permanent` as a flag. If you'd like for fqd to forget the queue after all clients have disconnected, you can specify the `transient` flag. If neither flag is specified, then an existing queue will retain its previous permanence setting or a new transient queue will be created. #### Examples: A queue called `bob` will be in memory, allowed to have multiple clients connected to it, with a drop policy and an allowable message backlog of 100000 messages: `bob/mem:public,drop,backlog=100000` A connection client will specify username/queue. A user "USER" connecting to the aforementioned queue would connect as `USER/bob/mem:public,drop,backlog=100000` ### Messages Messages are, of course, a payload and metadata. #### Message metadata Some are set by the broker. * sender [set by the broker] * hops (a list of fqd via which the message passed) Others are set by the sender. * exchange (up to 127 bytes) * route (up to 127 bytes) * id (128 bits). The first 64 bits the sender shall control, the latter 64bits the broker *might* control. ### Routes and Programs Routes and programs define how messages sent on exchanges are placed in queues: - A receiver that connects to an fq-broker specifies a program that filters the messages on the exchange. - A sender specifies a route for every message as part of the metadata Programs follow the following syntax (cf. `fqd.h`): ``` PROGRAM: :string RULES* RULE: (RULE) RULE: (RULE && RULE) RULE: (RULE || RULE) RULE: EXPR EXPR: function(args) args: arg args: arg, args arg: "string" arg: true|false arg: [0-9][0-9]*(?:.[0-9]*) functions are dynamically loadable with type signature strings: s, booleans: b, integers: d function: substr_eq(9.3,10,"tailorings",true) C symbol: fqd_route_prog__substr_eq__ddsb(int nargs, valnode_t *args); ``` In particular: - Every program starts with either `prefix:` or `exact:` - The program `prefix:` matches all rules - The program string is matched against the message route The following rule functions are defined in `fq_prog.c`: - `fqd_route_prog__sample__d()` -- subsample the stream - `fqd_route_prog__route_contains__s()` -- check if route contains a string - `fqd_route_prog__payload_prefix__s()` -- check if payload starts with prefix - `fqd_route_prog__payload_contains__s()` -- check if payload contains a string - `fqd_route_prog__true__()` -- always true Examples: - `prefix:` -- matches all messages - `prefix:bla` or `prefix:"bla"` -- matches all messages with rules starting with the sting 'bla' - `prefix: payload_prefix("M")` -- matches messages where the payload starts with 'M' - `prefix:foo (payload_prefix("M") && route_contains("bar"))` -- matches messages where the payload starts with 'M' and route starts with "foo" and moreover contains "bar" ## Protocol Information on command and message protocol is found in `docs/fq_protocol.md` ### HTTP superposition The Fq protocol also acts as a non-compliant HTTP server (though compliant enough of most clients and browsers). Fq ships with a web UI that allows inspecting real-time state and performance. #### GET /stats.json exposes current exchange, queue, and client information. #### POST /submit An endpoint allowing message submission without a full and stateful Fq connection. It expects the following headers: * ```X-Fq-User```, * ```X-Fq-Route```, and * ```X-Fq-Exchange```. The HTTP client *MUST* provide a Content-Length header corresponding to the payload content (no chunked submission). The payload is treated as the raw message box without any special encoding. Example: ``` curl -X POST -H "X-Fq-User: user" -H 'X-Fq-Route: bla' -H 'X-Fq-Exchange: test' localhost:8765/submit --data "TEST" ``` ## Building Requirements: * C compiler * GNU make * libuuid * sqlite3 * [jlog](https://github.com/omniti-labs/jlog) * [libbcd](https://github.com/backtrace-labs/bcd) (optional, for crash tracing) Generally: ``` make make install ``` To build without libbcd support: ``` NO_BCD=1 make ``` ## Debugging FQ can be run in debug mode from the command line. To run FQ in debug mode, kill any and all existing FQ processes, then enter the following command: ``` fq -g fq FQ_DEBUG= /fqd -D -c /fqd.sqlite -p ``` Flag values determine debug output type and can have the following values: ``` FQ_DEBUG_MEM = 0x00000001, FQ_DEBUG_MSG = 0x00000002, FQ_DEBUG_ROUTE = 0x00000004, FQ_DEBUG_IO = 0x00000008, FQ_DEBUG_CONN = 0x00000010, FQ_DEBUG_CONFIG = 0x00000020, FQ_DEBUG = 0x00000040, FQ_DEBUG_PEER = 0x00000080, FQ_DEBUG_HTTP = 0x00000100, FQ_DEBUG_PANIC = 0x40000000 ``` To debug more than one flag, simply OR the flag values. For example, to output connection, configuration, and route information, set `FQ_DEBUG` equal to `0x00000034 (FQ_DEBUG_CONFIG|FQ_DEBUG_CONN|FQ_DEBUG_ROUTE)`. For example, you can run FQ in debug mode with the variables shown below to output configuration, connection, and route information to the console: ``` fq -g fq FQ_DEBUG=0x00000034 /opt/circonus/sbin/fqd -D -c /opt/circonus/var/lib/fq/fqd.sqlite -p 8765 ``` ================================================ FILE: coverity_model.c ================================================ typedef unsigned int uint32_t; typedef _Bool bool; void ck_pr_dec_uint_zero(uint32_t *v, bool *zero) { *v = (*v) - 1; if(zero) *zero = ((*v) == 0); } void ck_pr_inc_uint(uint32_t *v) { *v = (*v) + 1; } uint32_t ck_pr_load_uint(uint32_t *v) { return *v; } ================================================ FILE: docs/fq_protocol.md ================================================ # fq Protocol ## Client Clients maintain two paired tcp connections to fq. After the connections are made, some preliminary data is sent over the command socket. A "plain auth" command is then issued, return a client key. The client key is then used in some preliminary data sent over the data socket, pairing the two sockets for the session. ### Prefixes * Cmd Mode: `0xcc50cafe` * Data Mode: `0xcc50face` * Peer Mode: `0xcc50fade` ### Command Socket Length | Description ---------+----------------------------- 4 bytes | Cmd Mode ### Data Socket Length | Description ---------+----------------------------- 4 bytes | Data Mode 2 bytes | Client Key Length variable | Client Key ## Commands General form `(command prefix)(command)`, big endian. All non-heartbeat related commands have in-band responses corresponding to the order in which requests they were sent to fq. Heartbeat requests are used simply to tell fq to look for and to send heartbeats at a specific interval. Heartbeats should be checked for during normal command processing and not as a response to a specific request. ### Prefixes Commad prefixes are two bytes at the beginning of the command * Error: `0xeeee` * Heartbeat: `0xbea7` * Auth CMD: `0xaaaa` * Auth Plain: `0x0000` * Auth Response: `0xaa00` * Heartbeat Request: `0x4848` * Bind: `0xb171` * Bind Request: `0xb170` * Unbind: `0x171b` * Unbind Request: `0x071b` * Status: `0x57a7` * Status Request: `0xc7a7` #### Plain Auth Plain Auth is a subset of Auth and will have both prefixs ##### Request Length | Description ---------+----------------------------- 2 bytes | Auth CMD Prefix 2 bytes | Auth Plain Prefix 2 bytes | User Length variable | User 2 bytes | Queue Length (Queue + 1 byte + Queue type) variable | Queue 1 byte | 0 variable | Queue type ("mem" or "disk") + ":param,param" 2 bytes | Password Length (16 bit) variable | Password ##### Response Length | Description ---------+----------------------------- 2 bytes | Auth Response Prefix 2 bytes | Client Key Length Length variable | Client Key (0 < length < 127 bytes) #### Bind ##### Request Length | Description ---------+----------------------------- 2 bytes | Bind Request Prefix 2 bytes | Flags: Peer Mode (0 or 1) | perm(0110)/trans(0100) 2 bytes | Exchange Length 2 bytes | Exchange 2 bytes | Program Length variable | Program ##### Response Length | Description ---------+----------------------------- 2 bytes | Bind Prefix 4 bytes | Binding ID #### Unbind ##### Request Length | Description ---------+----------------------------- 2 bytes | Unbind Request Prefix 4 bytes | Binding ID 2 bytes | Exchange Length variable | Exchange ##### Response Length | Description ---------+----------------------------- 2 bytes | Unbind Prefix 4 bytes | Binding ID On success, the response binding id will be the same as the one sent in the request. #### Status ##### Request Length | Description ---------+----------------------------- 2 bytes | Status Request Prefix ##### Response Length | Description ---------+----------------------------- 2 bytes | Status Prefix 2 bytes | Key Length variable | Key 4 bytes | Value … (repeat key length, key, value sets) 2 bytes | Key Length 0 The response contains serveral key-value pairs. Each key is prefixed by a length and parsing of kv pairs should continue until a key length of 0 is read. #### Heartbeat Request ##### Request Length | Description ---------+----------------------------- 2 bytes | Heartbeat Request Prefix 2 bytes | Heartbeat Interval (milliseconds) ##### Response None #### Heartbeat ##### Request Length | Description ---------+----------------------------- 2 bytes | Heartbeat Prefix ##### Response Length | Description ---------+----------------------------- 2 bytes | Heartbeat Prefix ## Messages Length | Description | Note ---------+-----------------------------------+------------------------ 1 byte | Exchange Length | variable | Exchange | 1 byte | Route Length | 16 bytes | Message ID | 1 byte | Sender Length | Send for Peer Mode Only variable | Sender | Send for Peer Mode Only 1 byte | Number of Hops | Send for Peer Mode Only variable | Hops (numHops sets of 4-byte IPs) | Send for Peer Mode Only 4 bytes | Payload Length | variable | Payload (<= 128kb) | All properties will be present when receiving a message, while some propertiers are only sent when in peer mode. ================================================ FILE: dtest.d ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ fq*:::message-receive{ printf("sender: %s\n", args[2]->sender); printf("exchange: %s\n", args[2]->exchange); printf("route: %s\n", args[2]->route); printf("message len: %d\n", args[2]->payload_len); printf("message: %.*s\n", args[2]->payload_len, args[2]->payload); printf("client: %s\n", args[0]->pretty); printf("client: %s\n", args[1]->pretty); printf("latency: %d\n", args[2]->latency); } fq*:::queue-drop{ q = ((fq_queue_t *)arg0); printf("dropped message on queue %s\n", q->name); } fq*:::queue-block{ printf("blocking queue %s\n", ((fq_queue_t *)arg0)->name); } ================================================ FILE: fq.d ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ typedef struct { uintptr_t route; uintptr_t sender; uintptr_t exchange; uintptr_t payload; uint32_t payload_len; uint64_t latency; } fq_dtrace_msg_t; typedef struct { string route; string exchange; string sender; string payload; uint32_t payload_len; uint64_t latency; } fq_msg_t; translator fq_msg_t { route = copyinstr(*(uintptr_t *)copyin((uintptr_t)&m->route, sizeof(uintptr_t))); exchange = copyinstr(*(uintptr_t *)copyin((uintptr_t)&m->exchange, sizeof(uintptr_t))); sender = copyinstr(*(uintptr_t *)copyin((uintptr_t)&m->sender, sizeof(uintptr_t))); payload_len = *(uint32_t *)copyin((uintptr_t)&m->payload_len, sizeof(uint32_t)); payload = copyinstr(*(uintptr_t *)copyin((uintptr_t)&m->payload, sizeof(uintptr_t)), *(uint32_t *)copyin((uintptr_t)&m->payload_len, sizeof(uint32_t))); latency = *(uint64_t *)copyin((uintptr_t)&m->latency, sizeof(uint64_t)); }; typedef struct { uintptr_t name; int32_t isprivate; int32_t policy; uintptr_t type; } fq_dtrace_queue_t; typedef struct { string name; int32_t isprivate; int32_t policy; string type; } fq_queue_t; translator fq_queue_t { name = copyinstr(*(uintptr_t *)copyin((uintptr_t)&m->name, sizeof(uintptr_t))); type = copyinstr(*(uintptr_t *)copyin((uintptr_t)&m->type, sizeof(uintptr_t))); isprivate = *(uint32_t *)copyin((uintptr_t)&m->isprivate, sizeof(int32_t)); policy = *(uint32_t *)copyin((uintptr_t)&m->policy, sizeof(int32_t)); }; typedef struct { int32_t fd; uintptr_t pretty; } fq_dtrace_remote_anon_client_t; typedef struct { int32_t fd; string pretty; } fq_remote_anon_client_t; typedef struct { int32_t fd; uintptr_t pretty; } fq_dtrace_remote_client_t; typedef struct { int32_t fd; string pretty; } fq_remote_client_t; typedef struct { int32_t fd; uintptr_t pretty; } fq_dtrace_remote_data_client_t; typedef struct { int32_t fd; string pretty; } fq_remote_data_client_t; translator fq_remote_anon_client_t { fd = *(uint32_t *)copyin((uintptr_t)&c->fd, sizeof(int32_t)); pretty = copyinstr(*(uintptr_t *)copyin((uintptr_t)&c->pretty, sizeof(uintptr_t))); }; ================================================ FILE: fq.h ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef FQ_H #define FQ_H #ifndef _REENTRANT #error "You must compile with -D_REENTRANT" #endif #include #include #include #include #include #include #include #include #include #define FQ_PROTO_CMD_MODE 0xcc50cafe #define FQ_PROTO_DATA_MODE 0xcc50face #define FQ_PROTO_PEER_MODE 0xcc50feed #define FQ_PROTO_OLD_PEER_MODE 0xcc50fade #define FQ_PROTO_READ_STAT 0x47455420 /* "GET " */ #define FQ_PROTO_HTTP_GET 0x47455420 /* "GET " */ #define FQ_PROTO_HTTP_PUT 0x50555420 /* "PUT " */ #define FQ_PROTO_HTTP_POST 0x504f5354 /* "POST" */ #define FQ_PROTO_HTTP_HEAD 0x48454144 /* "HEAD" */ #define FQ_BIND_PEER 0x00000001 #define FQ_BIND_PERM 0x00000110 #define FQ_BIND_TRANS 0x00000100 #define FQ_PROTO_ERROR 0xeeee #define FQ_PROTO_AUTH_CMD 0xaaaa #define FQ_PROTO_AUTH_PLAIN 0 #define FQ_PROTO_AUTH_RESP 0xaa00 #define FQ_PROTO_HBREQ 0x4848 #define FQ_PROTO_HB 0xbea7 #define FQ_PROTO_BINDREQ 0xb170 #define FQ_PROTO_BIND 0xb171 #define FQ_PROTO_UNBINDREQ 0x071b #define FQ_PROTO_UNBIND 0x171b #define FQ_PROTO_STATUS 0x57a7 #define FQ_PROTO_STATUSREQ 0xc7a7 #define FQ_DEFAULT_QUEUE_TYPE "mem" #ifndef min #define min(a,b) ((a) < (b) ? (a) : (b)) #endif #define MAX_RK_LEN 127 /* !lua start */ typedef struct fq_rk { unsigned char name[MAX_RK_LEN]; uint8_t len; } fq_rk; static inline void fq_rk_from_str(fq_rk *rk, const char *str) { size_t len = strlen(str); memset(rk->name, 0, MAX_RK_LEN); rk->len = min(len, MAX_RK_LEN - 1); memcpy(rk->name, str, rk->len); } static inline int fq_rk_cmp(const fq_rk * const a, const fq_rk * const b) { if(a->len < b->len) return -1; if(a->len > b->len) return 1; return memcmp(a->name, b->name, a->len); } #define FQ_BIND_ILLEGAL (uint32_t)0xffffffff typedef struct { fq_rk exchange; uint32_t flags; char *program; uint32_t out__route_id; } fq_bind_req; typedef struct { fq_rk exchange; uint32_t route_id; uint32_t out__success; } fq_unbind_req; typedef struct fq_msgid { union { struct { uint32_t p1; /* user(sender) */ uint32_t p2; /* user(sender) */ uint32_t p3; /* reserved */ uint32_t p4; /* reserved */ } u32; unsigned char d[16]; } id; } fq_msgid; typedef struct msg_free_stacks_handle_t msg_free_stacks_handle_t; #define MAX_HOPS 32 typedef struct fq_msg { uint32_t hops[MAX_HOPS]; fq_rk route; fq_rk sender; fq_rk exchange; fq_msgid sender_msgid; uint32_t refcnt; uint32_t payload_len; uint64_t arrival_time; ck_stack_entry_t cleanup_stack_entry; msg_free_stacks_handle_t *cleanup_handle; /* define a free function as an alternative to `free()` */ void (*free_fn)(struct fq_msg *m); unsigned char payload[]; /* over allocated */ } fq_msg; extern void fq_clear_message_cleanup_stack(void); extern fq_msg *fq_msg_alloc(const void *payload, size_t payload_size); extern fq_msg *fq_msg_alloc_BLANK(size_t payload_size); extern void fq_msg_ref(fq_msg *); extern void fq_msg_deref(fq_msg *); #define fq_msg_free(a) fq_msg_deref(a) extern void fq_msg_exchange(fq_msg *, const void *key, int klen); extern void fq_msg_route(fq_msg *, const void *key, int klen); extern void fq_msg_id(fq_msg *, fq_msgid *id); extern int fq_find_in_hops(uint32_t, fq_msg *); typedef struct buffered_msg_reader buffered_msg_reader; extern buffered_msg_reader * fq_buffered_msg_reader_alloc(int fd, uint32_t peermode); extern void fq_buffered_msg_reader_free(buffered_msg_reader *f); extern int fq_buffered_msg_read(buffered_msg_reader *f, void (*f_msg_handler)(void *, fq_msg *), void *); /* frame */ /* * 1 x uint8_t hops * hops x uint32_t node * 1 x exchange * 1 x fq_rk sender * 1 x fq_rk route * 1 x uint32_t payload_len * 1 x data */ typedef struct fq_conn_s *fq_client; #define FQ_HOOKS_V1 1 #define FQ_HOOKS_V2 2 #define FQ_HOOKS_V3 3 #define FQ_HOOKS_V4 4 typedef struct fq_hooks { int version; /* V1 */ void (*auth)(fq_client, int); void (*bind)(fq_client, fq_bind_req *); /* V2 */ void (*unbind)(fq_client, fq_unbind_req *); /* V3 */ int sync; /* V4 */ bool (*message)(fq_client, fq_msg *m); void (*cleanup)(fq_client); void (*disconnect)(fq_client); } fq_hooks; extern int fq_client_hooks(fq_client conn, fq_hooks *hooks); extern void fq_client_set_userdata(fq_client, void *); extern void * fq_client_get_userdata(fq_client); extern int fq_client_init(fq_client *, uint32_t peermode, void (*)(fq_client, const char *)); extern int fq_client_creds(fq_client, const char *host, unsigned short port, const char *source, const char *pass); extern void fq_client_status(fq_client conn, void (*f)(char *, uint32_t, void *), void *c); extern void fq_client_heartbeat(fq_client conn, unsigned short ms); extern void fq_client_bind(fq_client conn, fq_bind_req *req); extern void fq_client_unbind(fq_client conn, fq_unbind_req *req); extern void fq_client_set_backlog(fq_client conn, uint32_t len, uint32_t stall); extern void fq_client_set_nonblock(fq_client conn, bool nonblock); extern int fq_client_connect(fq_client conn); extern int fq_client_publish(fq_client, fq_msg *msg); extern fq_msg * fq_client_receive(fq_client conn); extern void fq_client_destroy(fq_client conn); extern int fq_client_data_backlog(fq_client conn); extern int fq_rk_to_hex(char *buf, int len, fq_rk *k); extern int fq_read_status(int fd, void (*f)(char *, uint32_t, void *), void *); extern int fq_read_uint16(int fd, unsigned short *v); extern int fq_write_uint16(int fd, unsigned short hs); extern int fq_read_uint32(int fd, uint32_t *v); extern int fq_write_uint32(int fd, uint32_t hs); extern int fq_read_short_cmd(int fd, unsigned short buflen, void *buf); extern int fq_write_short_cmd(int fd, unsigned short buflen, const void *buf); extern int fq_read_long_cmd(int fd, int *len, void **buf); /* This function returns 0 on success, -1 on failure or a positive * integer indicating that a partial write as happened. * The initial call should be made with off = 0, if a positive * value is returned, a subsequent call should be made with * off = (off + return value). * The caller must be able to keep track of an accumulated offset * in the event that several invocations are required to send the * message. */ extern int fq_client_write_msg(int fd, uint32_t peermode, fq_msg *m, size_t off, size_t *written); typedef enum { FQ_POLICY_DROP = 0, FQ_POLICY_BLOCK = 1, } queue_policy_t; typedef enum { FQ_DEBUG_MEM = 0x00000001, FQ_DEBUG_MSG = 0x00000002, FQ_DEBUG_ROUTE = 0x00000004, FQ_DEBUG_IO = 0x00000008, FQ_DEBUG_CONN = 0x00000010, FQ_DEBUG_CONFIG = 0x00000020, FQ_DEBUG = 0x00000040, FQ_DEBUG_PEER = 0x00000080, FQ_DEBUG_HTTP = 0x00000100, FQ_DEBUG_PANIC = 0x40000000 } fq_debug_bits_t; void fq_keepalive_fd(int fd, int cnt, int idle, int invtl); extern uint32_t fq_debug_bits; void fq_debug_set_bits(uint32_t bits); /* string can be integer, hex or comma separated string (e.g. "mem,io") */ void fq_debug_set_string(const char *s); extern int fq_debug_fl(const char *file, int line, fq_debug_bits_t, const char *fmt, ...) __attribute__((format(printf, 4, 5))); #define fq_debug(type, ...) do { \ if(0 != (type & fq_debug_bits)) { \ fq_debug_fl(__FILE__, __LINE__, type, __VA_ARGS__); \ } \ } while(0) #define fq_stacktrace(b,t,s,e) do { \ if(0 != (b & fq_debug_bits)) { \ fq_debug_stacktrace(b,t,s,e); \ } \ } while(0) /* programming: * * PROGRAM: :string RULES* * RULE: (RULE) * RULE: (RULE && RULE) * RULE: (RULE || RULE) * RULE: EXPR * EXPR: function(args) * args: arg * args: arg, args * arg: "string" * arg: true|false * arg: [0-9][0-9]*(?:.[0-9]*) * * functions are dynamically loadable with type signature * strings: s, booleans: b, integers: d * function: substr_eq(9.3,10,"tailorings",true) * C symbol: fqd_route_prog__substr_eq__ddsb(int nargs, valnode_t *args); * fallback symbol: fqd_route_prog_substr_eq__VA(int nargs, valnode_t *args); */ typedef struct valnode { enum { RP_VALUE_STRING = 1, RP_VALUE_BOOLEAN = 2, RP_VALUE_DOUBLE = 3 } value_type; union { char *s; bool b; double d; } value; } valnode_t; extern void fq_debug_stacktrace(fq_debug_bits_t b, const char *tag, int start, int end); #if defined(__MACH__) typedef uint64_t hrtime_t; #elif defined(BSD) || defined(__FreeBSD__) typedef uint64_t hrtime_t; #elif defined(linux) || defined(__linux) || defined(__linux__) typedef uint64_t hrtime_t; #endif extern hrtime_t fq_gethrtime(void); /* !lua stop */ /* DTrace helpers */ typedef struct { char *route; char *sender; char *exchange; char *payload; uint32_t payload_len; uint64_t latency; } fq_dtrace_msg_t; #define DTRACE_PACK_MSG(dmsg, msg) do { \ (dmsg)->route = (char *)(msg)->route.name; \ (dmsg)->sender = (char *)(msg)->sender.name; \ (dmsg)->exchange = (char *)(msg)->exchange.name; \ (dmsg)->payload_len = (uint32_t)(msg)->payload_len; \ (dmsg)->payload = (char *)(msg)->payload; \ (dmsg)->latency = fq_gethrtime() - (msg)->arrival_time; \ } while(0) #define fq_assert(A) do { \ if(!(A)) { \ fq_debug_stacktrace(FQ_DEBUG_PANIC, "assert", 1, 1000); \ (void)fprintf (stderr, "%s:%s:%u: failed assertion `%s'\n", __func__, __FILE__, __LINE__, #A); \ abort(); \ } \ } while(0) #endif ================================================ FILE: fq_bench.c ================================================ /* * Copyright (c) 2016 Circonus * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include "fq.h" void logger(fq_client c, const char *s) { (void)c; fprintf(stderr, "fq_logger: %s\n", s); } static void debug_status(char *key, uint32_t value, void *unused) { (void)unused; fq_debug(FQ_DEBUG_CONN, " ---> %s : %u\n", key, value); } static void print_rate(fq_client c, hrtime_t s, hrtime_t f, uint64_t cnt, uint64_t icnt) { double d; fq_client_status(c, debug_status, NULL); if(cnt) { d = (double)cnt * 1000000000; d /= (double)(f-s); printf("[%d backlog] output %0.2f msg/sec\n", fq_client_data_backlog(c), d); } if(icnt) { d = (double)icnt * 1000000000; d /= (double)(f-s); printf("[%d backlog] input %0.2f msg/sec\n", fq_client_data_backlog(c), d); } } static void usage(const char *prog) { printf("%s:\n", prog); printf("\t-H\t\tthis help message\n"); printf("\t-h\t\tthe fq host to connect to\n"); printf("\t-p \tspecify connecting port (default: 8765)\n"); printf("\t-u \tuser name\n"); printf("\t-P \tpassword\n"); printf("\t-t \t'disk' or 'mem' for type of queue to test\n"); printf("\t-q \tname of queue to test (default: benchmark_queue)\n"); printf("\t-c \tnumber of messages to bench with\n"); printf("\t-s \tsize of each message\n"); } static char *host; static int port = 8765; static char *user; static char *exchange; static char *pass; static char *type; static char *queue_name; static int count = 100000; static int size = 100; static void parse_cli(int argc, char **argv) { int c; const char *debug = getenv("FQ_DEBUG"); while((c = getopt(argc, argv, "Hh:p:u:P:t:q:e:c:s:")) != EOF) { switch(c) { case 'H': usage(argv[0]); exit(0); case 'h': free(host); host = strdup(optarg); break; case 'p': port = atoi(optarg); break; case 'u': free(user); user = strdup(optarg); break; case 'P': free(pass); pass = strdup(optarg); break; case 't': free(type); type = strdup(optarg); break; case 'q': free(queue_name); queue_name = strdup(optarg); break; case 'e': free(exchange); exchange = strdup(optarg); break; case 'c': count = atoi(optarg); break; case 's': size = atoi(optarg); break; default: usage(argv[0]); exit(-1); } } if(debug) fq_debug_set_string(debug); if(!host) host = strdup("localhost"); if(!user) user = strdup("user"); if(!pass) pass = strdup("pass"); if(!queue_name) queue_name = strdup("benchmark_queue"); if(!exchange) exchange = strdup("maryland"); if(!type) type = strdup("mem"); } /** * Benchmark a single thread against a type of queue at some host. */ int main(int argc, char **argv) { char queue[256] = {0}; hrtime_t s0, s, f, f0; uint64_t cnt = 0, icnt = 0; int i = 0; fq_client c; fq_msg *m; char *fq_debug = getenv("FQ_DEBUG"); if(fq_debug) fq_debug_set_bits(atoi(fq_debug)); signal(SIGPIPE, SIG_IGN); fq_client_init(&c, 0, logger); parse_cli(argc, argv); sprintf(queue, "%s/%s/%s:public,drop,backlog=10000,permanent", user, queue_name, type); printf("using queue -> %s\n", queue); fq_client_creds(c, host, port, queue, pass); fq_client_heartbeat(c, 1000); fq_client_set_backlog(c, 10000, 100); fq_client_connect(c); printf("payload size -> %d\n", size); printf("message count -> %d\n", count); s0 = s = fq_gethrtime(); f = s; m = fq_msg_alloc_BLANK(size); memset(m->payload, 'X', size); fq_msg_exchange(m, exchange, strlen(exchange)); fq_msg_route(m, "test.bench.foo", 14); while(i < count || fq_client_data_backlog(c) > 0) { if(i < count) { m->arrival_time = fq_gethrtime(); fq_msg_id(m, NULL); fq_client_publish(c, m); cnt++; i++; } else usleep(10); if (i % 1000 == 0) { f = fq_gethrtime(); } if(f-s > 1000000000) { print_rate(c, s, f, cnt, icnt); icnt = 0; cnt = 0; s = f; } } f0 = fq_gethrtime(); print_rate(c, s0, f0, i, 0); free(host); free(user); free(pass); free(exchange); free(queue_name); free(type); return 0; } ================================================ FILE: fq_client.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fq.h" #include "fqd.h" #include "fqd_private.h" static const long MAX_RESOLVE_CACHE = 1000000000; /* ns */ static __thread char thread_local_name[16]; void fq_thread_setname(const char *format, ...) { va_list arg; va_start(arg, format); char thrname[16] = "\0"; if(!format) format = "terminated"; vsnprintf(thrname, sizeof(thrname), format, arg); #if defined(linux) || defined(__linux) || defined(__linux__) pthread_setname_np(pthread_self(), thrname); #endif strlcpy(thread_local_name, thrname, sizeof(thread_local_name)); va_end(arg); } #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #define CONNERR_S(c) do { \ if(c->errorlog) c->errorlog(c, c->error); \ } while(0) #define CONNERR(c, s) do { \ strlcpy(c->error, s, sizeof(c->error)); \ if(c->errorlog) c->errorlog(c, c->error); \ } while(0) static inline int fq_client_wfrw_internal(int fd, int needs_read, int needs_write, uint64_t ms, int *mask) { int rv; struct pollfd pfd; pfd.fd = fd; pfd.events = 0; if(needs_read) pfd.events |= POLLIN | POLLERR | POLLHUP; if(needs_write) pfd.events |= POLLOUT | POLLERR | POLLHUP; while(-1 == (rv = poll(&pfd, 1, ms)) && errno == EINTR); if(mask) *mask = pfd.revents; return rv; } struct fq_conn_s { struct sockaddr_in remote; char *host; short port; hrtime_t last_resolve; char error[128]; char *user; char *pass; char *queue; char *queue_type; fq_rk key; int cmd_fd; int cmd_hb_needed; unsigned short cmd_hb_ms; hrtime_t cmd_hb_last; uint32_t peermode; int data_fd; pthread_t worker; pthread_t data_worker; int stop; ck_fifo_spsc_t cmdq; ck_fifo_spsc_t q; ck_fifo_spsc_t backq; uint32_t qlen; uint32_t qmaxlen; uint32_t q_stall_time; bool non_blocking; int connected; int data_ready; fq_msg *tosend; int tosend_offset; int sync_hooks; /* should they run in the calling thread */ void (*auth_hook)(fq_client, int); void (*bind_hook)(fq_client, fq_bind_req *); void (*unbind_hook)(fq_client, fq_unbind_req *); bool (*message_hook)(fq_client, fq_msg *); void (*cleanup_hook)(fq_client); void (*disconnect_hook)(fq_client); void (*errorlog)(fq_client, const char *); ck_fifo_spsc_entry_t *cmdqhead; ck_fifo_spsc_entry_t *qhead; ck_fifo_spsc_entry_t *backqhead; uint32_t thrcnt; void *userdata; }; typedef struct fq_conn_s fq_conn_s; typedef struct { unsigned short cmd; union { struct { uint16_t ms; } heartbeat; struct { void (*callback)(char *, uint32_t, void *); void *closure; } status; fq_bind_req *bind; fq_unbind_req *unbind; int return_value; } data; } cmd_instr; static void mark_safe_free(void *pptr); static void fq_conn_free(fq_conn_s *conn_s) { if(conn_s->user) free(conn_s->user); if(conn_s->pass) free(conn_s->pass); if(conn_s->queue) free(conn_s->queue); if(conn_s->queue_type) free(conn_s->queue_type); if(conn_s->tosend) fq_msg_free(conn_s->tosend); #define SWEEP_CONN_Q(TYPE, FREE, QNAME, ENTRYNAME) do { \ TYPE tofree; \ ck_fifo_spsc_dequeue_lock(&conn_s->QNAME); \ while(ck_fifo_spsc_dequeue(&conn_s->QNAME, &tofree) == true) { \ FREE(tofree); \ } \ ck_fifo_spsc_dequeue_unlock(&conn_s->QNAME); \ ck_fifo_spsc_entry_t *garbage = NULL; \ ck_fifo_spsc_deinit(&conn_s->QNAME, &garbage); \ while (garbage != NULL) { \ ck_fifo_spsc_entry_t *gn = garbage->next; \ free(garbage); \ garbage = gn; \ } \ } while(0) SWEEP_CONN_Q(fq_msg *, fq_msg_free, q, qhead); SWEEP_CONN_Q(void *, mark_safe_free, backq, backqhead); SWEEP_CONN_Q(cmd_instr *, free, cmdq, cmdqhead); #undef SWEEP_CONN_Q if(conn_s->cleanup_hook) conn_s->cleanup_hook(conn_s); free(conn_s); } typedef enum { AUTH_HOOK_TYPE, CMD_HOOK_TYPE } hook_req_type_t; typedef struct { hook_req_type_t type; cmd_instr *entry; } hook_req_t; static void fq_client_signal(fq_client conn, cmd_instr *e) { fq_conn_s *conn_s = conn; ck_fifo_spsc_enqueue_lock(&conn_s->cmdq); ck_fifo_spsc_entry_t *fifo_entry = ck_fifo_spsc_recycle(&conn_s->cmdq); if (fifo_entry == NULL) { fifo_entry = malloc(sizeof(ck_fifo_spsc_entry_t)); } ck_fifo_spsc_enqueue(&conn_s->cmdq, fifo_entry, e); ck_fifo_spsc_enqueue_unlock(&conn_s->cmdq); } static int fq_resolve_endpoint(fq_conn_s *conn_s) { conn_s->remote.sin_family = AF_INET; conn_s->remote.sin_port = htons(conn_s->port); if(inet_pton(AF_INET, conn_s->host, &conn_s->remote.sin_addr) != 1) { #ifdef HAVE_GETHOSTBYNAME_R struct hostent hostbuf, *hp; struct in_addr **addr_list; int buflen = 1024, herr; char *buf; if((buf = malloc(buflen)) == NULL) { CONNERR(conn_s, "out of memory"); return -1; } while((hp = gethostbyname_r(conn_s->host, &hostbuf, buf, buflen, &herr)) == NULL && errno == ERANGE) { buflen *= 2; if((buf = realloc(buf, buflen)) == NULL) { CONNERR(conn_s, "out of memory"); free(buf); return -1; } } if(!hp) { CONNERR(conn_s, "host lookup failed"); free(buf); return -1; } addr_list = (struct in_addr **)hp->h_addr_list; if(*addr_list == 0) { CONNERR(conn_s, "no address for host"); free(buf); return -1; } memcpy(&conn_s->remote.sin_addr, *addr_list, sizeof(struct in_addr)); free(buf); #elif defined _POSIX_C_SOURCE struct addrinfo *result; if (getaddrinfo(conn_s->host, NULL, NULL, &result)) { CONNERR(conn_s, "host lookup failed"); return -1; } if (result) { for (struct addrinfo *rp = result; rp; rp = rp->ai_next) { if (rp->ai_addr) { conn_s->remote.sin_addr = ((struct sockaddr_in*)rp->ai_addr)->sin_addr; break; } } freeaddrinfo(result); } else { CONNERR(conn_s, "no address for host"); return -1; } #else static pthread_mutex_t guard = PTHREAD_MUTEX_INITIALIZER; struct hostent *hp; struct in_addr **addr_list; pthread_mutex_lock(&guard); hp = gethostbyname(conn_s->host); if(!hp) { CONNERR(conn_s, "host lookup failed"); pthread_mutex_unlock(&guard); return -1; } addr_list = (struct in_addr **)hp->h_addr_list; if(*addr_list == 0) { CONNERR(conn_s, "no address for host"); pthread_mutex_unlock(&guard); return -1; } memcpy(&conn_s->remote.sin_addr, *addr_list, sizeof(struct in_addr)); pthread_mutex_unlock(&guard); #endif } conn_s->last_resolve = fq_gethrtime(); return 0; } static int fq_socket_connect(fq_conn_s *conn_s) { int fd, rv, on = 1; /* re-resolve if we've not done so quite recently */ if(conn_s->last_resolve == 0 || (fq_gethrtime() - conn_s->last_resolve) > MAX_RESOLVE_CACHE) { if(fq_resolve_endpoint(conn_s) < 0) { return -1; } } fd = socket(AF_INET, SOCK_STREAM, 0); if(fd == -1) return -1; rv = connect(fd, (struct sockaddr *)&conn_s->remote, sizeof(conn_s->remote)); if(rv == -1) { snprintf(conn_s->error, sizeof(conn_s->error), "socket: %s", strerror(errno)); CONNERR_S(conn_s); close(fd); return -1; } /* If this fails, we ignore it */ (void)setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); fq_keepalive_fd(fd, 10, 5, 2); return fd; } static void fq_client_disconnect_internal(fq_conn_s *conn_s) { if(conn_s->cmd_fd >= 0) { int toclose = conn_s->cmd_fd; conn_s->cmd_fd = -1; fq_stacktrace(FQ_DEBUG_CONN, "close(cmd_fd)\n",1,2); close(toclose); if(conn_s->disconnect_hook) conn_s->disconnect_hook(conn_s); } if(conn_s->data_fd >= 0) { int toclose = conn_s->data_fd; conn_s->data_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); close(toclose); } conn_s->data_ready = 0; if(conn_s->cmd_hb_ms) { unsigned short hb; hb = conn_s->cmd_hb_ms; conn_s->cmd_hb_ms = 0; fq_client_heartbeat(conn_s, hb); conn_s->cmd_hb_last = 0; } } static int fq_client_do_auth(fq_conn_s *conn_s) { int len, qlen, qtlen; uint16_t cmd; char error[1024]; char queue_composed[MAX_RK_LEN*2 + 1]; if(fq_write_uint16(conn_s->cmd_fd, FQ_PROTO_AUTH_CMD)) return -1; if(fq_write_uint16(conn_s->cmd_fd, FQ_PROTO_AUTH_PLAIN)) return -2; if(fq_write_short_cmd(conn_s->cmd_fd, strlen(conn_s->user), conn_s->user) < 0) return -3; qlen = strlen(conn_s->queue); if(qlen > MAX_RK_LEN) qlen = MAX_RK_LEN; qtlen = strlen(conn_s->queue_type); if(qtlen > MAX_RK_LEN) qtlen = MAX_RK_LEN; len = qlen + qtlen + 1; memcpy(queue_composed, conn_s->queue, qlen); queue_composed[qlen] = '\0'; memcpy(queue_composed + qlen + 1, conn_s->queue_type, qtlen); if(fq_write_short_cmd(conn_s->cmd_fd, len, queue_composed) < 0) return -4; if(fq_write_short_cmd(conn_s->cmd_fd, strlen(conn_s->pass), conn_s->pass) < 0) return -5; if(fq_read_uint16(conn_s->cmd_fd, &cmd)) return -6; switch(cmd) { case FQ_PROTO_ERROR: len = fq_read_short_cmd(conn_s->cmd_fd, sizeof(error)-1, error); if(conn_s->errorlog) { if(len > (int)sizeof(error)-1) len = sizeof(error)-1; if(len < 0) conn_s->errorlog(conn_s, "error reading error"); else conn_s->errorlog(conn_s,error); } return -7; case FQ_PROTO_AUTH_RESP: len = fq_read_short_cmd(conn_s->cmd_fd, sizeof(conn_s->key.name), conn_s->key.name); if(len < 0 || len > (int)sizeof(conn_s->key.name)) return -8; conn_s->key.len = len; #ifdef DEBUG { char hex[260]; if(fq_rk_to_hex(hex, sizeof(hex), &conn_s->key) >= 0) fq_debug(FQ_DEBUG_CONN, "client keyed:\n%s\n", hex); } #endif conn_s->data_ready = 1; break; default: if(conn_s->errorlog) { snprintf(error, sizeof(error), "server auth response 0x%04x unknown\n", cmd); conn_s->errorlog(conn_s, error); } return -9; } return 0; } static int fq_client_data_connect_internal(fq_conn_s *conn_s) { int flags; uint32_t cmd = htonl(conn_s->peermode ? FQ_PROTO_PEER_MODE : FQ_PROTO_DATA_MODE); /* We don't support data connections when the cmd connection is down */ if(conn_s->cmd_fd < 0) return -1; if(conn_s->data_fd >= 0) { int toclose = conn_s->data_fd; conn_s->data_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); close(toclose); } conn_s->data_fd = fq_socket_connect(conn_s); if(conn_s->data_fd < 0) goto shutdown; fq_debug(FQ_DEBUG_CONN, "connect(data_fd) -> %d\n", conn_s->data_fd); if(write(conn_s->data_fd, &cmd, sizeof(cmd)) != sizeof(cmd)) goto shutdown; if(conn_s->peermode) { if(write(conn_s->data_fd, &conn_s->peermode, sizeof(conn_s->peermode)) != sizeof(conn_s->peermode)) goto shutdown; } #ifdef DEBUG { char hex[260]; if(fq_rk_to_hex(hex, sizeof(hex), &conn_s->key) >= 0) fq_debug(FQ_DEBUG_CONN, "client keying:\n%s\n", hex); } #endif if(fq_write_short_cmd(conn_s->data_fd, conn_s->key.len, conn_s->key.name) < 0) { goto shutdown; } conn_s->tosend_offset = 0; if(((flags = fcntl(conn_s->data_fd, F_GETFL, 0)) == -1) || (fcntl(conn_s->data_fd, F_SETFL, flags | O_NONBLOCK) == -1)) goto shutdown; return 0; shutdown: if(conn_s->data_fd >= 0) { int toclose = conn_s->data_fd; conn_s->data_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); close(toclose); } return -1; } /* This is dastardly... we know the ptr has to be aligned, * when we see that it isn't on dequeue, we know it is a hook_req_t */ #define MARKED_HOOK_REQ_PTR(p) ((void *)(((uintptr_t)p)|1)) #define CHECK_HOOK_REQ_PTR(p) (((uintptr_t)p)&1) #define UNMARKED_HOOK_REQ_PTR(p) ((void *)(((uintptr_t)p)&~1)) static void mark_safe_free(void *pptr) { if(CHECK_HOOK_REQ_PTR(pptr)) { void *ptr = UNMARKED_HOOK_REQ_PTR(pptr); free(ptr); } else fq_msg_free(pptr); } static void enqueue_auth_hook_req(fq_conn_s *conn_s, int rv) { ck_fifo_spsc_enqueue_lock(&conn_s->backq); ck_fifo_spsc_entry_t *fifo_entry = ck_fifo_spsc_recycle(&conn_s->backq); if (unlikely(fifo_entry == NULL)) { fifo_entry = malloc(sizeof(ck_fifo_spsc_entry_t)); } hook_req_t *hreq = calloc(1,sizeof(*hreq)); hreq->type = AUTH_HOOK_TYPE; hreq->entry = calloc(1, sizeof(*hreq->entry)); hreq->entry->data.return_value = 0; ck_fifo_spsc_enqueue(&conn_s->backq, fifo_entry, MARKED_HOOK_REQ_PTR(hreq)); ck_fifo_spsc_enqueue_unlock(&conn_s->backq); } static void enqueue_cmd_hook_req(fq_conn_s *conn_s, cmd_instr *e) { ck_fifo_spsc_enqueue_lock(&conn_s->backq); ck_fifo_spsc_entry_t *fifo_entry = ck_fifo_spsc_recycle(&conn_s->backq); if (unlikely(fifo_entry == NULL)) { fifo_entry = malloc(sizeof(ck_fifo_spsc_entry_t)); } hook_req_t *hreq = calloc(1,sizeof(*hreq)); hreq->type = CMD_HOOK_TYPE; hreq->entry = e; ck_fifo_spsc_enqueue(&conn_s->backq, fifo_entry, MARKED_HOOK_REQ_PTR(hreq)); ck_fifo_spsc_enqueue_unlock(&conn_s->backq); } static int fq_client_connect_internal(fq_conn_s *conn_s) { int rv = -1; uint32_t cmd = htonl(FQ_PROTO_CMD_MODE); fq_client_disconnect_internal(conn_s); conn_s->cmd_fd = fq_socket_connect(conn_s); if(conn_s->cmd_fd < 0) goto shutdown; fq_debug(FQ_DEBUG_CONN, "connect(cmd_fd) -> %d\n", conn_s->cmd_fd); if(write(conn_s->cmd_fd, &cmd, sizeof(cmd)) != sizeof(cmd)) goto shutdown; if((rv = fq_client_do_auth(conn_s)) < 0) { fq_debug(FQ_DEBUG_CONN, "fq_client_do_auth -> %d\n", rv); goto shutdown; } if(conn_s->auth_hook) { if(conn_s->sync_hooks) enqueue_auth_hook_req(conn_s, 0); else conn_s->auth_hook((fq_client)conn_s, 0); } return 0; shutdown: if(conn_s->cmd_fd >= 0) { int toclose = conn_s->cmd_fd; conn_s->cmd_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(cmd_fd) (in auth)\n"); close(toclose); if(conn_s->disconnect_hook) conn_s->disconnect_hook(conn_s); } if(conn_s->auth_hook) { if(conn_s->sync_hooks) enqueue_auth_hook_req(conn_s, rv); else conn_s->auth_hook((fq_client)conn_s, rv); } return -1; } static void fq_client_read_complete(void *closure, fq_msg *msg) { fq_conn_s *conn_s = (fq_conn_s *)closure; if(conn_s->message_hook && conn_s->message_hook(conn_s, msg)) { fq_msg_deref(msg); } else { ck_fifo_spsc_enqueue_lock(&conn_s->backq); ck_fifo_spsc_entry_t *fifo_entry = ck_fifo_spsc_recycle(&conn_s->backq); if (unlikely(fifo_entry == NULL)) { fifo_entry = malloc(sizeof(ck_fifo_spsc_entry_t)); } ck_fifo_spsc_enqueue(&conn_s->backq, fifo_entry, msg); ck_fifo_spsc_enqueue_unlock(&conn_s->backq); } } static void fq_data_worker_loop(fq_conn_s *conn_s) { buffered_msg_reader *ctx = NULL; ctx = fq_buffered_msg_reader_alloc(conn_s->data_fd, 1); fq_thread_setname("fq:w:%d/%d", conn_s->cmd_fd, conn_s->data_fd); while(conn_s->cmd_fd >= 0 && conn_s->data_fd >= 0 && conn_s->stop == 0) { int rv; int wait_ms = 500, needs_write = 0, mask, write_rv; if(conn_s->tosend) goto the_thick_of_it; ck_fifo_spsc_dequeue_lock(&conn_s->q); while(ck_fifo_spsc_dequeue(&conn_s->q, &conn_s->tosend) == true) { conn_s->tosend_offset = 0; ck_pr_dec_uint(&conn_s->qlen); the_thick_of_it: #ifdef DEBUG fq_debug(FQ_DEBUG_MSG, "dequeue message to submit to server\n"); #endif write_rv = fq_client_write_msg(conn_s->data_fd, conn_s->peermode, conn_s->tosend, conn_s->tosend_offset, NULL); if(write_rv > 0) { conn_s->tosend_offset += write_rv; wait_ms = 0; break; } if(write_rv < 0) { if(errno == EAGAIN) { needs_write = 1; break; } if(conn_s->errorlog) { char errbuf[128]; snprintf(errbuf, sizeof(errbuf), "data write error: %s\n", strerror(errno)); conn_s->errorlog(conn_s, errbuf); } ck_fifo_spsc_dequeue_unlock(&conn_s->q); goto finish; } fq_msg_deref(conn_s->tosend); conn_s->tosend = NULL; conn_s->tosend_offset = 0; wait_ms = 0; } ck_fifo_spsc_dequeue_unlock(&conn_s->q); rv = fq_client_wfrw_internal(conn_s->data_fd, 1, needs_write, wait_ms, &mask); fq_debug(FQ_DEBUG_CONN, "fq_client_wfrw_internal(data:%d) -> %d\n", conn_s->data_fd, rv); if(rv < 0) { if(conn_s->errorlog) { char errbuf[128]; snprintf(errbuf, sizeof(errbuf), "data read error: %s\n", strerror(errno)); conn_s->errorlog(conn_s, errbuf); } goto finish; } if(rv > 0 && (mask & POLLIN)) { if(fq_buffered_msg_read(ctx, fq_client_read_complete, conn_s) < 0) { if(conn_s->errorlog) conn_s->errorlog(conn_s, "data read: end-of-line\n"); goto finish; } } } finish: conn_s->data_ready = 0; fq_clear_message_cleanup_stack(); if(ctx) fq_buffered_msg_reader_free(ctx); #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "cmd_fd -> %d, stop -> %d\n", conn_s->cmd_fd, conn_s->stop); #endif } static void * fq_data_worker(void *u) { int backoff = 0; bool zero; fq_conn_s *conn_s = (fq_conn_s *)u; ck_pr_inc_uint(&conn_s->thrcnt); while(conn_s->stop == 0) { if(conn_s->data_ready) { if(fq_client_data_connect_internal(conn_s) == 0) { backoff = 0; /* we're good, restart our backoff */ } fq_debug(FQ_DEBUG_IO, "[data] connected\n"); fq_data_worker_loop(conn_s); fq_debug(FQ_DEBUG_IO, "[data] connection failed: %s\n", conn_s->error); } if(backoff) usleep(backoff + (4096 - (lrand48()%8192))); /* +/- 4ms */ else backoff = 16384; if(backoff < 1000000) backoff += (backoff >> 4); } if(conn_s->data_fd >= 0) { int toclose = conn_s->data_fd; conn_s->data_fd = -1; fq_debug(FQ_DEBUG_CONN, "close(data_fd)\n"); close(toclose); } ck_pr_dec_uint_zero(&conn_s->thrcnt, &zero); if(zero) fq_conn_free(conn_s); return (void *)NULL; } #define MAX_PENDING 128 static void * fq_conn_worker(void *u) { int backoff = 0, i; bool zero; fq_conn_s *conn_s = (fq_conn_s *)u; cmd_instr *last_entries[MAX_PENDING] = { NULL }; int last_entry_idx = 0; int next_entry_idx = 0; #define SAVE_ENTRY(e) do { \ if(last_entries[next_entry_idx] != NULL) { \ CONNERR(conn_s, "exceed max cmd pipeline"); \ goto restart; \ } \ last_entries[next_entry_idx++] = e; \ next_entry_idx = next_entry_idx % MAX_PENDING; \ } while(0) #define last_entry last_entries[last_entry_idx] #define PROCESS_ENTRY(e, should_free) do { \ fq_assert(last_entries[last_entry_idx] == e); \ if(should_free) free(last_entries[last_entry_idx]); \ last_entries[last_entry_idx++] = NULL; \ last_entry_idx = last_entry_idx % MAX_PENDING; \ } while(0) cmd_instr *entry; fq_thread_setname("fq:c"); ck_pr_inc_uint(&conn_s->thrcnt); while(conn_s->stop == 0) { int wait_ms = 50; if(fq_client_connect_internal(conn_s) == 0) { fq_thread_setname("fq:c:%d", conn_s->cmd_fd); backoff = 0; /* we're good, restart our backoff */ } else { fq_thread_setname("fq:c"); } while(conn_s->data_ready && conn_s->stop == 0) { hrtime_t t; unsigned long long hb_us; struct timeval tv; int rv; ck_fifo_spsc_dequeue_lock(&conn_s->cmdq); while(ck_fifo_spsc_dequeue(&conn_s->cmdq, &entry) == true) { #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "client acting on user req 0x%04x\n", entry->cmd); #endif switch(entry->cmd) { case FQ_PROTO_STATUSREQ: fq_debug(FQ_DEBUG_CONN, "sending status request\n"); if(fq_write_uint16(conn_s->cmd_fd, entry->cmd)) { free(entry); CONNERR(conn_s, "write failed (statusreq)"); goto restart; } SAVE_ENTRY(entry); break; case FQ_PROTO_HBREQ: fq_debug(FQ_DEBUG_CONN, "sending heartbeat request\n"); if(fq_write_uint16(conn_s->cmd_fd, entry->cmd) || fq_write_uint16(conn_s->cmd_fd, entry->data.heartbeat.ms)) { free(entry); CONNERR(conn_s, "write failed (hbreq)"); goto restart; } conn_s->cmd_hb_ms = entry->data.heartbeat.ms; tv.tv_sec = (unsigned long)entry->data.heartbeat.ms / 1000; tv.tv_usec = 1000UL * (entry->data.heartbeat.ms % 1000); if(setsockopt(conn_s->cmd_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) CONNERR(conn_s, strerror(errno)); tv.tv_sec = (unsigned long)entry->data.heartbeat.ms / 1000; tv.tv_usec = 1000UL * (entry->data.heartbeat.ms % 1000); if(setsockopt(conn_s->cmd_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv))) CONNERR(conn_s, strerror(errno)); conn_s->cmd_hb_last = fq_gethrtime(); free(entry); break; case FQ_PROTO_BINDREQ: { unsigned short flags = entry->data.bind->flags; if(fq_write_uint16(conn_s->cmd_fd, entry->cmd) || fq_write_uint16(conn_s->cmd_fd, flags) || fq_write_short_cmd(conn_s->cmd_fd, entry->data.bind->exchange.len, entry->data.bind->exchange.name) < 0 || fq_write_short_cmd(conn_s->cmd_fd, strlen(entry->data.bind->program), entry->data.bind->program) < 0) { CONNERR(conn_s, "write failed (bindreq)"); goto restart; } SAVE_ENTRY(entry); } break; case FQ_PROTO_UNBINDREQ: { if(fq_write_uint16(conn_s->cmd_fd, entry->cmd) || fq_write_uint32(conn_s->cmd_fd, entry->data.unbind->route_id) || fq_write_short_cmd(conn_s->cmd_fd, entry->data.unbind->exchange.len, entry->data.unbind->exchange.name) < 0) { CONNERR(conn_s, "write failed (unbindreq)"); goto restart; } SAVE_ENTRY(entry); } break; default: CONNERR(conn_s, "unknown user-side cmd"); free(entry); } } ck_fifo_spsc_dequeue_unlock(&conn_s->cmdq); if(conn_s->cmd_hb_needed) { #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "-> heartbeat\n"); #endif if(fq_write_uint16(conn_s->cmd_fd, FQ_PROTO_HB)) { CONNERR(conn_s, "write failed (hb)"); break; } conn_s->cmd_hb_needed = 0; } rv = fq_client_wfrw_internal(conn_s->cmd_fd, 1, 0, wait_ms, NULL); if(rv == 0) { wait_ms = (wait_ms >> 2) + wait_ms; if(conn_s->cmd_hb_ms && wait_ms > conn_s->cmd_hb_ms) wait_ms = conn_s->cmd_hb_ms; if(wait_ms > 1000) wait_ms = 1000; } else wait_ms = 50; fq_debug(FQ_DEBUG_CONN, "fq_client_wfrw_internal(cmd:%d) -> %d\n", conn_s->cmd_fd, rv); t = fq_gethrtime(); hb_us = (unsigned long long)conn_s->cmd_hb_ms * 3 * 1000000ULL; if(conn_s->cmd_hb_last && hb_us && conn_s->cmd_hb_last < (unsigned int) (t - hb_us)) { char errbuf[256]; snprintf(errbuf, sizeof(errbuf), "heartbeat failed [%llu - %llu = %llu]", (unsigned long long)t, (unsigned long long)conn_s->cmd_hb_last, (unsigned long long)(t - conn_s->cmd_hb_last)); CONNERR(conn_s, errbuf); break; } if(rv < 0) { CONNERR(conn_s, strerror(errno)); break; } if(rv > 0) { bool handled = false; uint16_t hb; if(fq_read_uint16(conn_s->cmd_fd, &hb)) break; switch(hb) { case FQ_PROTO_HB: #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "<- heartbeat\n"); #endif conn_s->cmd_hb_last = fq_gethrtime(); conn_s->cmd_hb_needed = 1; break; case FQ_PROTO_STATUS: if(!last_entry || last_entry->cmd != FQ_PROTO_STATUSREQ) { CONNERR(conn_s, "protocol violation (status unexpected)"); goto restart; } if(fq_read_status(conn_s->cmd_fd, last_entry->data.status.callback, last_entry->data.status.closure)) goto restart; PROCESS_ENTRY(last_entry, 1); break; case FQ_PROTO_BIND: if(!last_entry || last_entry->cmd != FQ_PROTO_BINDREQ) { CONNERR(conn_s, "protocol violation (bind unexpected)"); goto restart; } if(fq_read_uint32(conn_s->cmd_fd, &last_entry->data.bind->out__route_id)) { if(conn_s->bind_hook) { if(conn_s->sync_hooks) { enqueue_cmd_hook_req(conn_s, last_entry); PROCESS_ENTRY(last_entry, 0); handled = true; } else conn_s->bind_hook((fq_client)conn_s, last_entry->data.bind); } CONNERR(conn_s, "read failed (bind)"); goto restart; } if(conn_s->bind_hook) { if(conn_s->sync_hooks) { enqueue_cmd_hook_req(conn_s, last_entry); PROCESS_ENTRY(last_entry, 0); handled = true; } else conn_s->bind_hook((fq_client)conn_s, last_entry->data.bind); } if(!handled) PROCESS_ENTRY(last_entry, 1); break; case FQ_PROTO_UNBIND: if(!last_entry || last_entry->cmd != FQ_PROTO_UNBINDREQ) { CONNERR(conn_s, "protocol violation (unbind unexpected)"); goto restart; } if(fq_read_uint32(conn_s->cmd_fd, &last_entry->data.unbind->out__success)) { if(conn_s->unbind_hook) { if(conn_s->sync_hooks) { enqueue_cmd_hook_req(conn_s, last_entry); PROCESS_ENTRY(last_entry, 0); handled = true; } conn_s->unbind_hook((fq_client)conn_s, last_entry->data.unbind); } CONNERR(conn_s, "read failed (unbind)"); goto restart; } if(conn_s->unbind_hook) { if(conn_s->sync_hooks) { enqueue_cmd_hook_req(conn_s, last_entry); PROCESS_ENTRY(last_entry, 0); handled = true; last_entry = NULL; } conn_s->unbind_hook((fq_client)conn_s, last_entry->data.unbind); } if(!handled) PROCESS_ENTRY(last_entry,1); break; default: CONNERR(conn_s, "protocol violation"); goto restart; break; } } } fq_debug(FQ_DEBUG_CONN, "[cmd] connection failed: %s\n", conn_s->error); if(backoff) usleep(backoff + (4096 - (lrand48()%8192))); /* +/- 4ms */ else backoff = 16384; if(backoff < 1000000) backoff += (backoff >> 4); restart: /* drain the queue.. we're going to make a new connection */ #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "[cmd] draining cmds\n"); #endif last_entry = NULL; for(i=0;icmdq, &entry) == true) { free(entry); } } fq_client_disconnect_internal(conn_s); ck_pr_dec_uint_zero(&conn_s->thrcnt, &zero); if(zero) fq_conn_free(conn_s); return (void *)NULL; } int fq_client_init(fq_client *conn_ptr, uint32_t peermode, void (*logger)(fq_client, const char *)) { fq_conn_s *conn_s; conn_s = *conn_ptr = calloc(1, sizeof(*conn_s)); if(!conn_s) return -1; /* make the sockets as disconnected */ conn_s->cmd_fd = conn_s->data_fd = -1; conn_s->peermode = peermode; conn_s->errorlog = logger; conn_s->thrcnt = 1; return 0; } void fq_client_set_userdata(fq_client conn, void *d) { fq_conn_s *conn_s = (fq_conn_s *)conn; conn_s->userdata = d; } void * fq_client_get_userdata(fq_client conn) { fq_conn_s *conn_s = (fq_conn_s *)conn; return conn_s->userdata; } int fq_client_hooks(fq_client conn, fq_hooks *hooks) { fq_conn_s *conn_s = (fq_conn_s *)conn; switch(hooks->version) { case FQ_HOOKS_V4: conn_s->message_hook = hooks->message; conn_s->cleanup_hook = hooks->cleanup; conn_s->disconnect_hook = hooks->disconnect; case FQ_HOOKS_V3: conn_s->sync_hooks = hooks->sync; case FQ_HOOKS_V2: conn_s->unbind_hook = hooks->unbind; case FQ_HOOKS_V1: conn_s->auth_hook = hooks->auth; conn_s->bind_hook = hooks->bind; break; default: return -1; } return 0; } int fq_client_creds(fq_client conn, const char *host, unsigned short port, const char *sender, const char *pass) { char qname[39]; fq_conn_s *conn_s; conn_s = conn; if(conn_s->user) { CONNERR(conn_s, "fq_client_creds already called"); return -1; } /* mark the sockets as disconnected */ conn_s->cmd_fd = conn_s->data_fd = -1; /* parse the user info */ conn_s->user = strdup(sender); conn_s->queue = strchr(conn_s->user, '/'); if(conn_s->queue) { *conn_s->queue++ = '\0'; conn_s->queue_type = strchr(conn_s->queue, '/'); if(conn_s->queue_type) { *conn_s->queue_type++ = '\0'; } } if(!conn_s->queue || conn_s->queue[0] == '\0') { char *cp; uuid_t out; uuid_generate(out); qname[0] = 'q'; qname[1] = '-'; uuid_unparse(out, qname+2); for(cp=qname;*cp;cp++) *cp = tolower(*cp); conn_s->queue = qname; } conn_s->queue_type = strdup(conn_s->queue_type ? conn_s->queue_type : FQ_DEFAULT_QUEUE_TYPE); conn_s->queue = strdup(conn_s->queue); conn_s->pass = strdup(pass); conn_s->cmdqhead = malloc(sizeof(ck_fifo_spsc_entry_t)); ck_fifo_spsc_init(&conn_s->cmdq, conn_s->cmdqhead); conn_s->qhead = malloc(sizeof(ck_fifo_spsc_entry_t)); ck_fifo_spsc_init(&conn_s->q, conn_s->qhead); conn_s->backqhead = malloc(sizeof(ck_fifo_spsc_entry_t)); ck_fifo_spsc_init(&conn_s->backq, conn_s->backqhead); conn_s->host = strdup(host); conn_s->port = port; return 0; } void fq_client_status(fq_client conn, void (*f)(char *, uint32_t, void *), void *c) { fq_conn_s *conn_s = conn; cmd_instr *e; if(conn_s == 0 || conn_s->cmd_fd < 0) return; e = malloc(sizeof(*e)); e->cmd = FQ_PROTO_STATUSREQ; e->data.status.callback = f; e->data.status.closure = c; fq_client_signal(conn, e); } void fq_client_heartbeat(fq_client conn, unsigned short heartbeat_ms) { fq_conn_s *conn_s = conn; cmd_instr *e; if(conn_s->cmd_fd < 0) return; e = malloc(sizeof(*e)); e->cmd = FQ_PROTO_HBREQ; e->data.heartbeat.ms = heartbeat_ms; fq_client_signal(conn, e); } void fq_client_bind(fq_client conn, fq_bind_req *req) { fq_conn_s *conn_s = conn; cmd_instr *e; if(conn_s->cmd_fd < 0) return; e = malloc(sizeof(*e)); e->cmd = FQ_PROTO_BINDREQ; e->data.bind = req; fq_client_signal(conn, e); } void fq_client_unbind(fq_client conn, fq_unbind_req *req) { fq_conn_s *conn_s = conn; cmd_instr *e; if(conn_s->cmd_fd < 0) return; e = malloc(sizeof(*e)); e->cmd = FQ_PROTO_UNBINDREQ; e->data.unbind = req; fq_client_signal(conn, e); } void fq_client_set_backlog(fq_client conn, uint32_t len, uint32_t stall) { fq_conn_s *conn_s = conn; conn_s->qmaxlen = len; conn_s->q_stall_time = stall; } void fq_client_set_nonblock(fq_client conn, bool nonblock) { fq_conn_s *conn_s = conn; conn_s->non_blocking = nonblock; } int fq_client_connect(fq_client conn) { pthread_attr_t attr; fq_conn_s *conn_s = conn; if(conn_s->connected != 0) return -1; conn_s->connected = 1; if(pthread_attr_init(&attr) != 0) { CONNERR(conn_s, "pthread_attr_init failed"); return -1; } if(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) { CONNERR(conn_s, "pthread_attr_setdetachstate failed"); return -1; } if(pthread_create(&conn_s->worker, NULL, fq_conn_worker, conn_s) != 0) { CONNERR(conn_s, "could not start command thread"); conn_s->stop = 1; return -1; } if(pthread_create(&conn_s->data_worker, NULL, fq_data_worker, conn_s) != 0) { CONNERR(conn_s, "could not start data thread"); conn_s->stop = 1; return -1; } return 0; } void fq_client_destroy(fq_client conn) { bool zero; fq_conn_s *conn_s = conn; conn_s->stop = 1; ck_pr_dec_uint_zero(&conn_s->thrcnt, &zero); if(zero) fq_conn_free(conn_s); } int fq_client_data_backlog(fq_client conn) { fq_conn_s *conn_s = conn; return ck_pr_load_uint(&conn_s->qlen); } int fq_client_publish(fq_client conn, fq_msg *msg) { fq_conn_s *conn_s = conn; ck_fifo_spsc_enqueue_lock(&conn_s->q); if(conn_s->non_blocking && conn_s->qlen >= conn_s->qmaxlen) { ck_fifo_spsc_enqueue_unlock(&conn_s->q); return -1; } ck_fifo_spsc_entry_t *fifo_entry = ck_fifo_spsc_recycle(&conn_s->q); if (unlikely(fifo_entry == NULL)) { fifo_entry = malloc(sizeof(ck_fifo_spsc_entry_t)); } while(conn_s->qlen >= conn_s->qmaxlen) { if(conn_s->q_stall_time > 0) usleep(conn_s->q_stall_time); else ck_pr_stall(); } fq_msg_ref(msg); ck_fifo_spsc_enqueue(&conn_s->q, fifo_entry, msg); ck_fifo_spsc_enqueue_unlock(&conn_s->q); ck_pr_inc_uint(&conn_s->qlen); return 1; } fq_msg *fq_client_receive(fq_client conn) { bool success; fq_conn_s *conn_s = conn; fq_msg *m = NULL; ck_fifo_spsc_dequeue_lock(&conn_s->backq); success = ck_fifo_spsc_dequeue(&conn_s->backq, &m); ck_fifo_spsc_dequeue_unlock(&conn_s->backq); if(success && m && CHECK_HOOK_REQ_PTR(m)) { hook_req_t *hreq = UNMARKED_HOOK_REQ_PTR(m); m = NULL; cmd_instr *entry = hreq->entry; switch(hreq->type) { case AUTH_HOOK_TYPE: if(conn_s->sync_hooks && conn_s->auth_hook) conn_s->auth_hook(conn_s, entry->data.return_value); break; case CMD_HOOK_TYPE: switch(entry->cmd) { case FQ_PROTO_BINDREQ: if(conn_s->sync_hooks && conn_s->bind_hook) conn_s->bind_hook(conn_s, entry->data.bind); break; case FQ_PROTO_UNBINDREQ: if(conn_s->sync_hooks && conn_s->unbind_hook) conn_s->unbind_hook(conn_s, entry->data.unbind); break; default: snprintf(conn_s->error, sizeof(conn_s->error), "sync cmd feedback unknown: %x\n", entry->cmd); if(conn_s->errorlog) conn_s->errorlog(conn_s, conn_s->error); } break; } free(entry); free(hreq); } return m; } ================================================ FILE: fq_dtrace.blank.h ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #if 0 #else #define FQ_QUEUE_ENQUEUE(arg0, arg1) \ do { \ } while (0) #define FQ_QUEUE_ENQUEUE_ENABLED() (0) #define FQ_CLIENT_AUTH(arg0) \ do { \ } while (0) #define FQ_CLIENT_AUTH_ENABLED() (0) #define FQ_CLIENT_AUTH_DATA(arg0) \ do { \ } while (0) #define FQ_CLIENT_AUTH_DATA_ENABLED() (0) #define FQ_CLIENT_CONNECT(arg0, arg1) \ do { \ } while (0) #define FQ_CLIENT_CONNECT_ENABLED() (0) #define FQ_CLIENT_DISCONNECT(arg0, arg1) \ do { \ } while (0) #define FQ_CLIENT_DISCONNECT_ENABLED() (0) #define FQ_CONFIG_ROTATE(arg0) \ do { \ } while (0) #define FQ_CONFIG_ROTATE_ENABLED() (0) #define FQ_MESSAGE_DELIVER(arg0, arg1, arg2) \ do { \ } while (0) #define FQ_MESSAGE_DELIVER_ENABLED() (0) #define FQ_MESSAGE_RECEIVE(arg0, arg1, arg2) \ do { \ } while (0) #define FQ_MESSAGE_RECEIVE_ENABLED() (0) #define FQ_QUEUE_BLOCK(arg0, arg1) \ do { \ } while (0) #define FQ_QUEUE_BLOCK_ENABLED() (0) #define FQ_QUEUE_CREATE_FAILURE(arg0, arg1, arg2) \ do { \ } while (0) #define FQ_QUEUE_CREATE_FAILURE_ENABLED() (0) #define FQ_QUEUE_CREATE_SUCCESS(arg0, arg1, arg2, arg3, arg4, arg5) \ do { \ } while (0) #define FQ_QUEUE_CREATE_SUCCESS_ENABLED() (0) #define FQ_QUEUE_DESTROY(arg0, arg1) \ do { \ } while (0) #define FQ_QUEUE_DESTROY_ENABLED() (0) #define FQ_QUEUE_DROP(arg0, arg1) \ do { \ } while (0) #define FQ_QUEUE_DROP_ENABLED() (0) #define FQ_ROUTE_PROGRAM_ENTRY(arg0, arg1) \ do { \ } while (0) #define FQ_ROUTE_PROGRAM_ENTRY_ENABLED() (0) #define FQ_ROUTE_PROGRAM_RETURN(arg0, arg1, arg2) \ do { \ } while (0) #define FQ_ROUTE_PROGRAM_RETURN_ENABLED() (0) #endif /* !defined(DTRACE_PROBES_DISABLED) || !DTRACE_PROBES_DISABLED */ ================================================ FILE: fq_dtrace.d ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ typedef struct { int dummy; } fq_dtrace_msg_t; typedef struct { int dummy; } fq_msg_t; typedef struct { int dummy; } fq_dtrace_queue_t; typedef struct { int dummy; } fq_queue_t; typedef struct { int dummy; } fq_dtrace_remote_client_t; typedef struct { int dummy; } fq_remote_client_t; typedef struct { int dummy; } fq_dtrace_remote_anon_client_t; typedef struct { int dummy; } fq_remote_anon_client_t; typedef struct { int dummy; } fq_dtrace_remote_data_client_t; typedef struct { int dummy; } fq_remote_data_client_t; provider fq { probe client__connect(fq_dtrace_remote_anon_client_t *c, int m) : (fq_remote_anon_client_t *c, int m); probe client__disconnect(fq_dtrace_remote_anon_client_t *c, int m) : (fq_remote_anon_client_t *c, int m); probe client__auth(fq_dtrace_remote_client_t *c) : (fq_remote_client_t *c); probe client__auth__data(fq_dtrace_remote_data_client_t *c) : (fq_remote_data_client_t *c); probe queue__create__success(int, char *, int, char *, int, int); probe queue__create__failure(int, char *, char *); probe queue__destroy(int, char *); probe queue__drop(fq_dtrace_queue_t *q, fq_dtrace_msg_t *m) : (fq_queue_t *q, fq_msg_t *m); probe queue__block(fq_dtrace_queue_t *q, fq_dtrace_msg_t *m) : (fq_queue_t *q, fq_msg_t *m); probe queue__enqueue(fq_dtrace_queue_t *q, fq_dtrace_msg_t *m) : (fq_queue_t *q, fq_msg_t *m); probe config__rotate(int); probe message__receive(fq_dtrace_remote_client_t *c, fq_dtrace_remote_data_client_t *d, fq_dtrace_msg_t *m) : (fq_remote_client_t *c, fq_remote_data_client_t *d, fq_msg_t *m); probe message__deliver(fq_dtrace_remote_client_t *c, fq_dtrace_remote_data_client_t *d, fq_dtrace_msg_t *m) : (fq_remote_client_t *c, fq_remote_data_client_t *d, fq_msg_t *m); probe route__program__entry(char *p, fq_dtrace_msg_t *m) : (char *p, fq_msg_t *m); probe route__program__return(char *p, fq_dtrace_msg_t *m, int32_t u) : (char *p, fq_msg_t *m, int32_t u); }; #pragma D attributes Evolving/Evolving/ISA provider fq provider #pragma D attributes Private/Private/Unknown provider fq module #pragma D attributes Private/Private/Unknown provider fq function #pragma D attributes Private/Private/ISA provider fq name #pragma D attributes Evolving/Evolving/ISA provider fq args ================================================ FILE: fq_msg.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include "fq.h" #include "ck_pr.h" #include "ck_malloc.h" #include "ck_hs.h" #include "ck_fifo.h" #define MSG_ALIGN sizeof(void *) #define MAX_FREE_LIST_SIZE 1000000 #define unlikely(x) __builtin_expect(!!(x), 0) static fq_msgid local_msgid = { .id = { .u32 = { .p1 = 0, .p2 = 0, .p3 = 0, .p4 = 0 } } }; static void pull_next_local_msgid(fq_msgid *msgid) { uint32_t last; fq_msgid g; again: memcpy(&g, &local_msgid, sizeof(fq_msgid)); memcpy(msgid, &g, sizeof(fq_msgid)); last = ck_pr_faa_32(&local_msgid.id.u32.p1, 1); msgid->id.u32.p1 = last + 1; if(last == 0xffffffffUL) { last = ck_pr_faa_32(&local_msgid.id.u32.p2, 1); msgid->id.u32.p2 = last + 1; if(last == 0xffffffffUL) { last = ck_pr_faa_32(&local_msgid.id.u32.p3, 1); msgid->id.u32.p3 = last + 1; if(last == 0xffffffffUL) { last = ck_pr_faa_32(&local_msgid.id.u32.p4, 1); msgid->id.u32.p4 = last + 1; } } } if(msgid->id.u32.p4 < g.id.u32.p4) goto again; if(msgid->id.u32.p4 > g.id.u32.p4) return; if(msgid->id.u32.p3 < g.id.u32.p3) goto again; if(msgid->id.u32.p3 > g.id.u32.p3) return; if(msgid->id.u32.p2 < g.id.u32.p2) goto again; if(msgid->id.u32.p2 > g.id.u32.p2) return; if(msgid->id.u32.p1 > g.id.u32.p1) return; goto again; } static inline fq_msg* msg_allocate(const size_t s) { fq_msg *m = calloc(1, sizeof(fq_msg) + s); if(!m) return NULL; m->payload_len = s; return m; } static inline void msg_free(fq_msg *m) { if (m->free_fn != NULL) { m->free_fn(m); } else { free(m); } } fq_msg * fq_msg_alloc(const void *data, size_t s) { fq_msg *m = msg_allocate(s); if (unlikely(m == NULL)) { return NULL; } if(s) memcpy(m->payload, data, s); #ifdef DEBUG fq_debug(FQ_DEBUG_MSG, "msg(%p) -> alloc\n", (void *)m); #endif m->arrival_time = fq_gethrtime(); m->refcnt = 1; return m; } fq_msg * fq_msg_alloc_BLANK(size_t s) { fq_msg *m = msg_allocate(s); if (unlikely(m == NULL)) { return NULL; } #ifdef DEBUG fq_debug(FQ_DEBUG_MSG, "msg(%p) -> alloc\n", (void *)m); #endif m->arrival_time = fq_gethrtime(); m->refcnt = 1; return m; } void fq_msg_ref(fq_msg *msg) { ck_pr_inc_uint(&msg->refcnt); #ifdef DEBUG fq_debug(FQ_DEBUG_MSG, "msg(%p) -> ref: %d\n", (void *)msg, msg->refcnt); #endif } void fq_msg_deref(fq_msg *msg) { bool zero; ck_pr_dec_uint_zero(&msg->refcnt, &zero); if(zero) { #ifdef DEBUG fq_debug(FQ_DEBUG_MSG, "msg(%p) -> free\n", (void *)msg); #endif msg_free(msg); } #ifdef DEBUG else { fq_debug(FQ_DEBUG_MSG, "msg(%p) -> deref: %d\n", (void *)msg, msg->refcnt); } #endif } void fq_msg_exchange(fq_msg *msg, const void *exchange, int rlen) { if(rlen <= 0) { msg->exchange.len = 0; return; } if(rlen > MAX_RK_LEN) rlen = MAX_RK_LEN; msg->exchange.len = rlen; memcpy(msg->exchange.name, exchange, rlen); } void fq_msg_route(fq_msg *msg, const void *route, int rlen) { if(rlen <= 0) { msg->route.len = 0; return; } if(rlen > MAX_RK_LEN) rlen = MAX_RK_LEN; msg->route.len = rlen; memcpy(msg->route.name, route, rlen); } void fq_msg_id(fq_msg *msg, fq_msgid *id) { if(!id) pull_next_local_msgid(&msg->sender_msgid); else memcpy(&msg->sender_msgid, id, sizeof(*id)); } ================================================ FILE: fq_rcvr.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include "fq.h" char *exchange = "maryland"; char *program = "prefix:\"\""; int output = 1; void logger(fq_client, const char *); void logger(fq_client c, const char *s) { (void)c; fprintf(stderr, "fq_logger: %s\n", s); } static void print_rate(fq_client c, hrtime_t s, hrtime_t f, uint64_t cnt, uint64_t icnt) { double d; if(cnt) { d = (double)cnt * 1000000000; d /= (double)(f-s); printf("[%d backlog] output %0.2f msg/sec\n", fq_client_data_backlog(c), d); } if(icnt) { d = (double)icnt * 1000000000; d /= (double)(f-s); printf("[%d backlog] input %0.2f msg/sec\n", fq_client_data_backlog(c), d); } } static void my_auth_handler(fq_client c, int error) { fq_bind_req *breq; if(error) return; printf("attempting bind\n"); breq = malloc(sizeof(*breq)); memset(breq, 0, sizeof(*breq)); int exchange_len = strlen(exchange); memcpy(breq->exchange.name, exchange, exchange_len); breq->exchange.len = exchange_len; breq->flags = FQ_BIND_TRANS; breq->program = program; fq_client_bind(c, breq); } static void my_bind_handler(fq_client c, fq_bind_req *breq) { (void)c; printf("route set -> %u\n", breq->out__route_id); if(breq->out__route_id == FQ_BIND_ILLEGAL) { fprintf(stderr, "Failure to bind...\n"); exit(-1); } } fq_hooks hooks = { .version = FQ_HOOKS_V1, .auth = my_auth_handler, .bind = my_bind_handler }; int main(int argc, char **argv) { hrtime_t s, f; uint64_t cnt = 0, icnt = 0, icnt_total = 0; int rcvd = 0; fq_client c; fq_msg *m; char *fq_debug = getenv("FQ_DEBUG"); if(fq_debug) fq_debug_set_bits(atoi(fq_debug)); signal(SIGPIPE, SIG_IGN); fq_client_init(&c, 0, logger); if(fq_client_hooks(c, &hooks)) { fprintf(stderr, "Can't register hooks\n"); exit(-1); } char *host = "localhost"; int port = 8765; char *user = "guest"; char *pass = "guest"; int o; while(-1 != (o = getopt(argc, argv, "h:p:u:P:e:b:s"))) { switch(o) { case 'h': host = strdup(optarg); break; case 'p': port = atoi(optarg); break; case 'u': user = strdup(optarg); break; case 'P': pass = strdup(optarg); break; case 'e': exchange = strdup(optarg); break; case 'b': program = strdup(optarg); break; case 's': output = 2; break; default: fprintf(stderr, "%s [-h host] [-p port] [-u user] [-P pass] [-e exchange] [-b program] [-s]\n", argv[0]); exit(-1); break; } } fq_client_hooks(c, &hooks); fq_client_creds(c, host, port, user, pass); fq_client_heartbeat(c, 1000); fq_client_set_backlog(c, 10000, 100); fq_client_connect(c); s = fq_gethrtime(); while(1) { f = fq_gethrtime(); while(NULL != (m = fq_client_receive(c))) { icnt++; icnt_total++; rcvd++; if(output == 1) { int ending = m->payload[m->payload_len-1] == '\n' ? 1 : 0; printf("[%.*s] %.*s\n", m->route.len, m->route.name, m->payload_len - ending, m->payload); } fq_msg_deref(m); } usleep(1000); if(f-s > 1000000000) { if(output == 2) { print_rate(c, s, f, cnt, icnt); printf("total: %llu\n", (unsigned long long)icnt_total); } icnt = 0; cnt = 0; s = f; } } (void) argc; return 0; } ================================================ FILE: fq_sndr.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include "fq.h" #define SEND_COUNT 1000 int send_count = SEND_COUNT; void logger(fq_client c, const char *); void logger(fq_client c, const char *s) { (void)c; fprintf(stderr, "fq_logger: %s\n", s); } static void debug_status(char *key, uint32_t value, void *unused) { (void)unused; fq_debug(FQ_DEBUG_CONN, " ---> %s : %u\n", key, value); } static void print_rate(fq_client c, hrtime_t s, hrtime_t f, uint64_t cnt, uint64_t icnt) { double d; fq_client_status(c, debug_status, NULL); if(cnt) { d = (double)cnt * 1000000000; d /= (double)(f-s); printf("[%d backlog] output %0.2f msg/sec\n", fq_client_data_backlog(c), d); } if(icnt) { d = (double)icnt * 1000000000; d /= (double)(f-s); printf("[%d backlog] input %0.2f msg/sec\n", fq_client_data_backlog(c), d); } } int main(int argc, char **argv) { hrtime_t s0, s, f, f0; uint64_t cnt = 0, icnt = 0; int psize = 0, i = 0; fq_client c; fq_msg *m; char *fq_debug = getenv("FQ_DEBUG"); if(fq_debug) fq_debug_set_bits(atoi(fq_debug)); signal(SIGPIPE, SIG_IGN); fq_client_init(&c, 0, logger); if(argc < 5) { fprintf(stderr, "%s [size [count]]\n", argv[0]); exit(-1); } fq_client_creds(c, argv[1], atoi(argv[2]), argv[3], argv[4]); fq_client_heartbeat(c, 1000); fq_client_set_backlog(c, 10000, 100); fq_client_connect(c); if(argc > 5) { psize = atoi(argv[5]); } printf("payload size -> %d\n", psize); if(argc > 6) { send_count = atoi(argv[6]); } printf("message count -> %d\n", send_count); s0 = s = fq_gethrtime(); while(i < send_count || fq_client_data_backlog(c) > 0) { if(i < send_count) { m = fq_msg_alloc_BLANK(psize); memset(m->payload, 0, psize); fq_msg_exchange(m, "maryland", 8); fq_msg_route(m, "test.prefix.boo", 15); fq_msg_id(m, NULL); fq_client_publish(c, m); cnt++; i++; fq_msg_free(m); } else usleep(100); f = fq_gethrtime(); if(f-s > 1000000000) { print_rate(c, s, f, cnt, icnt); icnt = 0; cnt = 0; s = f; } } f0 = fq_gethrtime(); print_rate(c, s0, f0, i, 0); (void) argc; return 0; } ================================================ FILE: fq_utils.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "fq.h" #include "fqd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline int msg_free_stack_select(ssize_t in); typedef struct free_message_stack { ck_stack_t stack; uint32_t size; uint32_t max_size; size_t alloc_size; } free_message_stack; /* We support separate stacks for separate msg sizes... * containers are from 2^10 (1k) to 2^16 (65k). * Messages are allocated from the smallest stack that can contain them. * Otherwise, they are traditionally allocated. */ #define MSG_FREE_BASE 10 #define MSG_FREE_CEILING 16 #define MSG_FREE_STACKS (MSG_FREE_CEILING-MSG_FREE_BASE+1) /* the handles are TLS, they on heap and allocated TLS, and freed to a pool. * once a handle is allocated it never actually freed. */ struct msg_free_stacks_handle_t { free_message_stack *stacks[MSG_FREE_STACKS]; bool valid; }; struct handle_free_list { msg_free_stacks_handle_t *handle; struct handle_free_list *next; }; /* this is actually in on illumos but flagged off for some reason */ #ifndef container_of #define container_of(m, s, name) \ (void *)((uintptr_t)(m) - (uintptr_t)offsetof(s, name)) #endif uint32_t fq_debug_bits = FQ_DEBUG_PANIC; void fq_debug_set_bits(uint32_t bits) { fq_debug_bits = bits | FQ_DEBUG_PANIC; } static void fq_init_free_message_stack(free_message_stack *stack, const size_t max_free_count, const size_t alloc_size) { ck_stack_init(&stack->stack); stack->size = 0; stack->max_size = max_free_count; stack->alloc_size = alloc_size; } static inline fq_msg * fq_pop_free_message_stack(struct free_message_stack *stack) { fq_msg *rv = NULL; if (stack == NULL) { return rv; } ck_stack_entry_t *ce = ck_stack_pop_mpmc(&stack->stack); if (ce != NULL) { ck_pr_dec_32(&stack->size); rv = container_of(ce, fq_msg, cleanup_stack_entry); } return rv; } static inline void fq_push_free_message_stack(struct free_message_stack *stack, fq_msg *m) { if (stack == NULL) { return; } while(ck_pr_load_32(&stack->size) > stack->max_size) { ck_stack_entry_t *ce = ck_stack_pop_mpmc(&stack->stack); if (ce != NULL) { fq_msg *m = container_of(ce, fq_msg, cleanup_stack_entry); free(m); ck_pr_dec_32(&stack->size); } else break; } uint32_t c = ck_pr_load_32(&stack->size); if (c >= stack->max_size) { free(m); return; } ck_pr_inc_32(&stack->size); ck_stack_push_mpmc(&stack->stack, &m->cleanup_stack_entry); } static void fq_free_msg_fn(fq_msg *m) { if (m->cleanup_handle && m->cleanup_handle->valid) { int idx = msg_free_stack_select(m->payload_len); if(idx >= 0) { fq_push_free_message_stack(m->cleanup_handle->stacks[idx], m); return; } } free(m); } void fq_debug_set_string(const char *s) { char *lastsep, *tok = NULL; char copy[128]; unsigned long nv; int slen; if(!s) return; /* then comma separated named */ slen = strlen(s); if(slen < 0 || slen > sizeof(copy) - 1) return; /* copy including null terminator */ memcpy(copy,s,slen+1); /* First try decimal */ nv = strtoul(copy,&lastsep,10); if(*lastsep == '\0') { fq_debug_set_bits(nv); return; } /* Then try hex */ nv = strtoul(copy,&lastsep,16); if(*lastsep == '\0') { fq_debug_set_bits(nv); return; } for (tok = strtok_r(copy, ",", &lastsep); tok; tok = strtok_r(NULL, ",", &lastsep)) { #define SETBIT(tok, A) do { \ if(!strcasecmp(tok, #A + 9)) fq_debug_bits |= A; \ } while(0) SETBIT(tok, FQ_DEBUG_MEM); SETBIT(tok, FQ_DEBUG_MSG); SETBIT(tok, FQ_DEBUG_ROUTE); SETBIT(tok, FQ_DEBUG_IO); SETBIT(tok, FQ_DEBUG_CONN); SETBIT(tok, FQ_DEBUG_CONFIG); SETBIT(tok, FQ_DEBUG_PEER); SETBIT(tok, FQ_DEBUG_HTTP); if(lastsep == NULL) break; } } #define IN_READ_BUFFER_SIZE 1024*128 #define FREE_MSG_LIST_SIZE 100000000 /* in bytes */ #define CAPPED(x) (((x)<(MAX_MESSAGE_SIZE))?(x):(MAX_MESSAGE_SIZE)) struct buffered_msg_reader { unsigned char scratch[IN_READ_BUFFER_SIZE]; int fd; int off; uint32_t peermode; ssize_t nread; ssize_t into_body; fq_msg *copy; fq_msg *msg; }; static struct handle_free_list *free_message_handle_list = NULL; static pthread_mutex_t free_message_handle_list_lock = PTHREAD_MUTEX_INITIALIZER; static inline msg_free_stacks_handle_t *free_message_handle_acquire(void) { msg_free_stacks_handle_t *a = NULL; pthread_mutex_lock(&free_message_handle_list_lock); if(free_message_handle_list) { struct handle_free_list *tofree = free_message_handle_list; free_message_handle_list = tofree->next; a = tofree->handle; free(tofree); } pthread_mutex_unlock(&free_message_handle_list_lock); if(!a) a = calloc(1, sizeof(*a)); a->valid = true; return a; } static inline int msg_free_stack_select(ssize_t in) { int i; if(in <= (1 << MSG_FREE_BASE)) return 0; in--; in >>= MSG_FREE_BASE+1; for(i = 1; i < MSG_FREE_STACKS && in; i++, in >>= 1); if(i < MSG_FREE_STACKS) return i; return -1; } static __thread msg_free_stacks_handle_t *tls_free_message_handle = NULL; buffered_msg_reader *fq_buffered_msg_reader_alloc(int fd, uint32_t peermode) { buffered_msg_reader *br; br = calloc(1, sizeof(*br)); br->fd = fd; br->peermode = peermode; br->msg = fq_msg_alloc_BLANK(0); return br; } void fq_buffered_msg_reader_free(buffered_msg_reader *f) { assert(f->msg->refcnt == 1); fq_msg_deref(f->msg); if(f->copy) fq_msg_deref(f->copy); free(f); } static int parse_message_headers(int peermode, unsigned char *d, int dlen, fq_msg *msg) { int ioff = 0; unsigned char exchange_len, route_len, sender_len, nhops; #define BAIL_UNLESS_LEFT(d) do { \ if((dlen-ioff) < (int)(d)) return 0; \ } while(0) BAIL_UNLESS_LEFT(sizeof(exchange_len)); memcpy(&exchange_len, d+ioff, sizeof(exchange_len)); ioff += sizeof(exchange_len); if(exchange_len > sizeof(msg->exchange.name)) return -1; msg->exchange.len = exchange_len; BAIL_UNLESS_LEFT(exchange_len); memcpy(msg->exchange.name, d+ioff, exchange_len); ioff += exchange_len; BAIL_UNLESS_LEFT(sizeof(route_len)); memcpy(&route_len, d+ioff, sizeof(route_len)); ioff += sizeof(route_len); if(route_len > sizeof(msg->route.name)) return -2; msg->route.len = route_len; BAIL_UNLESS_LEFT(route_len); memcpy(msg->route.name, d+ioff, route_len); ioff += route_len; BAIL_UNLESS_LEFT(sizeof(msg->sender_msgid)); memcpy(&msg->sender_msgid, d+ioff, sizeof(msg->sender_msgid)); ioff += sizeof(msg->sender_msgid); if(peermode) { /* Peer mode includes the sender and the hops */ BAIL_UNLESS_LEFT(sizeof(sender_len)); memcpy(&sender_len, d+ioff, sizeof(sender_len)); ioff += sizeof(sender_len); if(sender_len > sizeof(msg->sender.name)) return -3; msg->sender.len = sender_len; BAIL_UNLESS_LEFT(sender_len); memcpy(msg->sender.name, d+ioff, sender_len); ioff += sender_len; BAIL_UNLESS_LEFT(sizeof(nhops)); memcpy(&nhops, d+ioff, sizeof(nhops)); ioff += sizeof(nhops); if(nhops > MAX_HOPS) return -4; if(nhops > 0) { BAIL_UNLESS_LEFT(sizeof(uint32_t) * nhops); memcpy(msg->hops, d+ioff, sizeof(uint32_t) * nhops); ioff += sizeof(uint32_t) * nhops; } } BAIL_UNLESS_LEFT(sizeof(msg->payload_len)); memcpy(&msg->payload_len, d+ioff, sizeof(msg->payload_len)); msg->payload_len = ntohl(msg->payload_len); ioff += sizeof(msg->payload_len); return ioff; } void fq_clear_message_cleanup_stack() { int i; if(tls_free_message_handle == NULL) return; tls_free_message_handle->valid = false; for(i=0; istacks[i]) { tls_free_message_handle->stacks[i]->size = 0; ck_stack_entry_t *ce = ck_stack_batch_pop_mpmc(&tls_free_message_handle->stacks[i]->stack); while (ce != NULL) { fq_msg *m = container_of(ce, fq_msg, cleanup_stack_entry); ce = ce->next; free(m); } } } struct handle_free_list *node = calloc(1, sizeof(*node)); pthread_mutex_lock(&free_message_handle_list_lock); node->handle = tls_free_message_handle; node->next = free_message_handle_list; free_message_handle_list = node; pthread_mutex_unlock(&free_message_handle_list_lock); } /* * return 0: keep going (to write path) * return -1: busted * * Read into one of N buffers so the processing thread * can do the work separate from the read */ int fq_buffered_msg_read(buffered_msg_reader *f, void (*f_msg_handler)(void *, fq_msg *), void *closure) { int rv; static char scratch_buf[IN_READ_BUFFER_SIZE]; while(f->into_body < f->msg->payload_len) { fq_assert(f->copy); /* we need to be reading a largish payload */ if(f->into_body >= MAX_MESSAGE_SIZE) { /* read into a scratch buffer */ size_t readsize = f->copy->payload_len - f->into_body; if(readsize > sizeof(scratch_buf)) readsize = sizeof(scratch_buf); while((rv = read(f->fd, scratch_buf, readsize)) == -1 && errno == EINTR); } else { while((rv = read(f->fd, f->copy->payload + f->into_body, CAPPED(f->copy->payload_len) - f->into_body)) == -1 && errno == EINTR); } if(rv < 0 && errno == EAGAIN) return 0; if(rv <= 0) { fq_debug(FQ_DEBUG_IO, "read error: %s\n", rv < 0 ? strerror(errno) : "end-of-line"); return -1; } fq_debug(FQ_DEBUG_MSG, "%p <-- %d bytes for payload\n", (void *)f, rv); f->into_body += rv; if(f->into_body == f->copy->payload_len) { f->into_body = 0; goto message_done; } } while((rv = read(f->fd, f->scratch+f->nread, sizeof(f->scratch)-f->nread)) == -1 && errno == EINTR); fq_debug(FQ_DEBUG_IO, "%p <-- %d bytes @ %d (%d)\n", (void *)f, rv, (int)f->nread, (int)f->nread + ((rv > 0) ? rv : 0)); if(rv == -1 && errno == EAGAIN) return 0; if(rv <= 0) return -1; f->nread += rv; while(f->nread>0) { uint32_t body_available; int body_start; body_start = parse_message_headers(f->peermode, f->scratch+f->off, f->nread-f->off, f->msg); f->into_body = 0; fq_debug(FQ_DEBUG_MSG, "%d = parse(+%d, %d) -> %d\n", body_start, f->off, (int)f->nread-f->off, body_start ? (int)f->msg->payload_len : 0); if(body_start < 0) return -1; if(!body_start) { fq_debug(FQ_DEBUG_MSG, "incomplete message header...\n"); memmove(f->scratch, f->scratch + f->off, f->nread - f->off); f->nread -= f->off; f->off = 0; return 0; } free_message_stack *tls_free_message_stack = NULL; int msg_stack_idx = msg_free_stack_select(f->msg->payload_len); if(msg_stack_idx >= 0) { if(tls_free_message_handle == NULL) tls_free_message_handle = free_message_handle_acquire(); if(tls_free_message_handle->stacks[msg_stack_idx] == NULL) { /* lazy create/init the cleanup stack */ tls_free_message_handle->stacks[msg_stack_idx] = malloc(sizeof(free_message_stack)); fq_init_free_message_stack(tls_free_message_handle->stacks[msg_stack_idx], FREE_MSG_LIST_SIZE/(1 << (msg_stack_idx + MSG_FREE_BASE)), (1 << (msg_stack_idx + MSG_FREE_BASE))); } tls_free_message_stack = tls_free_message_handle->stacks[msg_stack_idx]; } if(tls_free_message_stack) { /* We have a message... or the formal beginnings of one */ f->copy = fq_pop_free_message_stack(tls_free_message_stack); if (f->copy == NULL) { /* ran out of entries in free list */ f->copy = fq_msg_alloc_BLANK(tls_free_message_stack->alloc_size); if (f->copy == NULL) { /* this is bad, we can't alloc */ fq_debug(FQ_DEBUG_MSG, "unable to malloc, OOM?\n"); return -1; } } /* always 1 as this msg only lives until it's copied by a worker thread */ memcpy(f->copy, f->msg, sizeof(fq_msg)); f->copy->refcnt = 1; f->copy->free_fn = fq_free_msg_fn; } else { f->copy = fq_msg_alloc_BLANK(CAPPED(f->msg->payload_len)); if (f->copy == NULL) { /* this is bad, we can't alloc */ fq_debug(FQ_DEBUG_MSG, "unable to malloc, OOM?\n"); return -1; } memcpy(f->copy, f->msg, sizeof(fq_msg)); f->copy->refcnt = 1; f->copy->free_fn = NULL; } /* assign the cleanup stack for this message */ f->copy->cleanup_handle = tls_free_message_stack ? tls_free_message_handle : NULL; memset(&f->copy->cleanup_stack_entry, 0, sizeof(ck_stack_entry_t)); f->off += body_start; body_available = f->nread - f->off; if(f->copy->payload_len < body_available) body_available = f->copy->payload_len; memcpy(f->copy->payload, f->scratch+f->off, CAPPED(body_available)); if(body_available == f->copy->payload_len) { f->off += body_available; message_done: f->copy->refcnt = 1; f->copy->payload_len = CAPPED(f->copy->payload_len); fq_debug(FQ_DEBUG_MSG, "message read... injecting\n"); f->copy->arrival_time = fq_gethrtime(); f_msg_handler(closure, f->copy); f->copy = NULL; memset(f->msg, 0, sizeof(fq_msg)); /* It is still allocated and we are the sole owner, refcnt must be 1 */ f->msg->refcnt = 1; } else { f->nread = 0; f->off = 0; f->into_body = body_available; fq_debug(FQ_DEBUG_MSG, "incomplete message... (%d needed)\n", (int)f->msg->payload_len - (int)f->into_body); return 0; } } return 0; } #if defined(BSD) || defined(__FreeBSD__) #include #define NANOSEC 1000000000 hrtime_t fq_gethrtime() { struct timespec ts; clock_gettime(CLOCK_UPTIME,&ts); return (((u_int64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec); } #elif defined(linux) || defined(__linux) || defined(__linux__) #include hrtime_t fq_gethrtime() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); return ((ts.tv_sec * 1000000000) + ts.tv_nsec); } #elif defined(__MACH__) #include #include static int initialized = 0; static mach_timebase_info_data_t sTimebaseInfo; hrtime_t fq_gethrtime() { uint64_t t; if(!initialized) { if(sTimebaseInfo.denom == 0) (void) mach_timebase_info(&sTimebaseInfo); } t = mach_absolute_time(); return t * sTimebaseInfo.numer / sTimebaseInfo.denom; } #elif defined(__sun) inline hrtime_t fq_gethrtime() { return gethrtime(); } #else #error "Unknown platform for clock implementation" #endif int fq_rk_to_hex(char *buf, int len, fq_rk *k) { int i; unsigned char *bout = (unsigned char *)buf; if(k->len * 2 + 4 > len) return -1; *bout++ = '0'; *bout++ = 'x'; for (i=0; ilen; i++) { snprintf((char *)bout, 3, "%02x", k->name[i]); bout+=2; } *bout = '\0'; return (bout - (unsigned char *)buf); } int fq_read_uint16(int fd, unsigned short *v) { unsigned short nlen; int rv; while((rv = read(fd, &nlen, sizeof(nlen))) == -1 && errno == EINTR); if(rv != sizeof(nlen)) return -1; *v = ntohs(nlen); return 0; } int fq_write_uint16(int fd, unsigned short v) { uint16_t nv; int rv; nv = htons(v); while((rv = write(fd, &nv, sizeof(nv))) == -1 && errno == EINTR); return (rv == sizeof(nv)) ? 0 : -1; } int fq_read_uint32(int fd, uint32_t *v) { uint32_t nlen; int rv; while((rv = read(fd, &nlen, sizeof(nlen))) == -1 && errno == EINTR); if(rv != sizeof(nlen)) return -1; *v = ntohl(nlen); return 0; } int fq_write_uint32(int fd, uint32_t v) { uint32_t nv; int rv; nv = htonl(v); while((rv = write(fd, &nv, sizeof(nv))) == -1 && errno == EINTR); return (rv == sizeof(nv)) ? 0 : -1; } int fq_read_short_cmd(int fd, unsigned short buflen, void *buf) { void *tgt = buf; unsigned char scratch[0xffff]; unsigned short nlen, len; int rv; while((rv = read(fd, &nlen, sizeof(nlen))) == -1 && errno == EINTR); if(rv < 0 || rv != sizeof(nlen)) return -1; len = ntohs(nlen); if(len == 0) return 0; if(len > buflen) tgt = scratch; while((rv = read(fd, tgt, len)) == -1 && errno == EINTR); if(rv != len) { return -1; } if(tgt != buf) memcpy(buf, tgt, buflen); /* truncated */ return rv; } int fq_read_status(int fd, void (*f)(char *, uint32_t, void *), void *closure) { while(1) { char key[0x10000]; int len; uint32_t value; len = fq_read_short_cmd(fd, 0xffff, key); if(len < 0) return -1; if(len == 0) break; key[len] = '\0'; if(fq_read_uint32(fd, &value) < 0) return -1; f(key, value, closure); } return 0; } int fq_write_short_cmd(int fd, unsigned short buflen, const void *buf) { unsigned short nlen; int rv; nlen = htons(buflen); while((rv = write(fd, &nlen, sizeof(nlen))) == -1 && errno == EINTR); if(rv != sizeof(nlen)) return -1; if(buflen == 0) return 0; while((rv = write(fd, buf, buflen)) == -1 && errno == EINTR); if(rv != buflen) return -1; return rv; } int fq_read_long_cmd(int fd, int *rlen, void **rbuf) { unsigned int nlen; int rv, len; while((rv = read(fd, &nlen, sizeof(nlen))) == -1 && errno == EINTR); if(rv < 0 || rv != sizeof(nlen)) return -1; len = ntohl(nlen); *rlen = 0; *rbuf = NULL; if(len < 0) { return -1; } else if(len > 0) { *rbuf = malloc(len); while((rv = read(fd, *rbuf, len)) == -1 && errno == EINTR); if(rv != len) { free(*rbuf); *rlen = 0; *rbuf = NULL; return -1; } *rlen = rv; } return *rlen; } int fq_debug_fl(const char *file, int line, fq_debug_bits_t b, const char *fmt, ...) { int rv; va_list argp; static hrtime_t epoch = 0; hrtime_t now; char fmtstring[1024]; uint64_t p = (uint64_t)pthread_self(); uint32_t ps = p & 0xffffffff; (void)b; now = fq_gethrtime(); if(!epoch) epoch = now; snprintf(fmtstring, sizeof(fmtstring), "[%" PRIu64 "] [%08x] %s", (uint64_t)((now-epoch)/1000), ps, fmt); va_start(argp, fmt); rv = vfprintf(stderr, fmtstring, argp); va_end(argp); (void)file; (void)line; return rv; } void fq_debug_stacktrace(fq_debug_bits_t b, const char *tag, int start, int end) { #define STACK_DEPTH 16 int i, cnt; void *bti[STACK_DEPTH + 1], **bt = bti+1; char **btname; cnt = backtrace(bti, STACK_DEPTH + 1); if(cnt < 1) { fq_debug(b, "track trace failed\n"); return; } btname = backtrace_symbols(bt, cnt); if(start > cnt) start = cnt; if(end > cnt) end = cnt; for(i=start;i!=end;i += (start > end) ? -1 : 1) { if(btname && btname[i]) fq_debug(b, "[%2d] %s %s\n", i, tag, btname[i]); else fq_debug(b, "[%2d] %s %p\n", i, tag, bt[i]); } if(btname) free(btname); } int fq_serialize(struct iovec **vecs, int *vec_count, uint32_t peermode, size_t off, fq_msg *m) { int i, writev_start = 0, idx = 0; size_t expect = 0; uint32_t data_len = htonl(m->payload_len); uint8_t nhops = 0; uint8_t sender_len = m->sender.len; uint8_t exchange_len = m->exchange.len; uint8_t route_len = m->route.len; if (vecs == NULL) { return -1; } *vec_count = 7 + (peermode ? 4 : 0); /* 7 for normal + 4 for peer */ *vecs = calloc(*vec_count, sizeof(struct iovec)); struct iovec *pv = *vecs; expect = 1 + m->exchange.len + 1 + m->route.len + sizeof(m->sender_msgid) + sizeof(data_len) + m->payload_len; if(peermode) { for(i = 0; i < MAX_HOPS; i++) { if(m->hops[i] == 0) break; nhops++; } expect += 1 + m->sender.len + 1 + (nhops * sizeof(uint32_t)); } fq_assert(off < expect); expect -= off; pv[idx ].iov_len = 1; pv[idx++].iov_base = &exchange_len; pv[idx ].iov_len = m->exchange.len; pv[idx++].iov_base = m->exchange.name; pv[idx ].iov_len = 1; pv[idx++].iov_base = &route_len; pv[idx ].iov_len = m->route.len; pv[idx++].iov_base = m->route.name; pv[idx ].iov_len = sizeof(m->sender_msgid); pv[idx++].iov_base = &m->sender_msgid; if(peermode) { pv[idx ].iov_len = 1; pv[idx++].iov_base = &sender_len; pv[idx ].iov_len = m->sender.len; pv[idx++].iov_base = m->sender.name; pv[idx ].iov_len = 1; pv[idx++].iov_base = &nhops; pv[idx ].iov_len = nhops * sizeof(uint32_t); pv[idx++].iov_base = m->hops; } pv[idx ].iov_len = sizeof(data_len); pv[idx++].iov_base = &data_len; pv[idx ].iov_len = m->payload_len; pv[idx++].iov_base = m->payload; if(off > 0) { for(i = 0; i < idx; i++) { if(off >= pv[i].iov_len) { off -= pv[i].iov_len; writev_start++; } else { pv[i].iov_len -= off; pv[i].iov_base = ((unsigned char *)pv[i].iov_base) + off; off = 0; break; } } } return 0; } int fq_client_write_msg(int fd, uint32_t peermode, fq_msg *m, size_t off, size_t *written) { struct iovec pv[11]; /* 7 for normal + 4 for peer */ int rv, i, writev_start = 0, idx = 0; size_t expect; unsigned char nhops = 0; unsigned char sender_len = m->sender.len; unsigned char exchange_len = m->exchange.len; unsigned char route_len = m->route.len; uint32_t data_len = htonl(m->payload_len); expect = 1 + m->exchange.len + 1 + m->route.len + sizeof(m->sender_msgid) + sizeof(data_len) + m->payload_len; if(peermode) { for(i=0;ihops[i] == 0) break; nhops++; } expect += 1 + m->sender.len + 1 + (nhops * sizeof(uint32_t)); } fq_assert(off < expect); expect -= off; pv[idx ].iov_len = 1; pv[idx++].iov_base = &exchange_len; pv[idx ].iov_len = m->exchange.len; pv[idx++].iov_base = m->exchange.name; pv[idx ].iov_len = 1; pv[idx++].iov_base = &route_len; pv[idx ].iov_len = m->route.len; pv[idx++].iov_base = m->route.name; pv[idx ].iov_len = sizeof(m->sender_msgid); pv[idx++].iov_base = &m->sender_msgid; if(peermode) { pv[idx ].iov_len = 1; pv[idx++].iov_base = &sender_len; pv[idx ].iov_len = m->sender.len; pv[idx++].iov_base = m->sender.name; pv[idx ].iov_len = 1; pv[idx++].iov_base = &nhops; pv[idx ].iov_len = nhops * sizeof(uint32_t); pv[idx++].iov_base = m->hops; } pv[idx ].iov_len = sizeof(data_len); pv[idx++].iov_base = &data_len; pv[idx ].iov_len = m->payload_len; pv[idx++].iov_base = m->payload; if(off > 0) { for(i=0;i= pv[i].iov_len) { off -= pv[i].iov_len; writev_start++; } else { pv[i].iov_len -= off; pv[i].iov_base = ((unsigned char *)pv[i].iov_base) + off; off = 0; break; } } } rv = writev(fd, pv+writev_start, idx-writev_start); fq_debug(FQ_DEBUG_IO, "writev(%d bytes [%d data]) -> %d\n", (int)expect, (int)m->payload_len, rv); if(rv > 0 && written) *written = rv; if(rv != (int)expect) { return rv; } if(rv == 0) return -1; return 0; } int fq_find_in_hops(uint32_t needle, fq_msg *m) { int i; for(i=0; ihops[i] == needle) return i; } return -1; } void fq_keepalive_fd(int fd, int cnt, int idle, int invtl) { int optval = 1; socklen_t optlen = sizeof(optval); if(setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { fq_debug(FQ_DEBUG_CONN, "client(%d) keepalive failed: %s\n", fd, strerror(errno)); } #if defined(SOL_TCP) #if defined(TCP_KEEPCNT) optval = cnt; if(setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &optval, optlen) < 0) { fq_debug(FQ_DEBUG_CONN, "client(%d) keepcnt failed : %s\n", fd, strerror(errno)); } #endif #if defined(TCP_KEEPIDLE) optval = idle; if(setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &optval, optlen) < 0) { fq_debug(FQ_DEBUG_CONN, "client(%d) keepidle failed : %s\n", fd, strerror(errno)); } #endif #if defined(TCP_KEEPINTVL) optval = intvl; if(setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &optval, optlen) < 0) { fq_debug(FQ_DEBUG_CONN, "client(%d) keepidle failed : %s\n", fd, strerror(errno)); } #endif #endif } ================================================ FILE: fqc.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include "fq.h" #define SEND_COUNT 1000 int send_count = SEND_COUNT; void logger(fq_client, const char *); void logger(fq_client c, const char *s) { (void)c; fprintf(stderr, "fq_logger: %s\n", s); } static void print_rate(fq_client c, hrtime_t s, hrtime_t f, uint64_t cnt, uint64_t icnt) { double d; if(cnt) { d = (double)cnt * 1000000000; d /= (double)(f-s); printf("[%d backlog] output %0.2f msg/sec\n", fq_client_data_backlog(c), d); } if(icnt) { d = (double)icnt * 1000000000; d /= (double)(f-s); printf("[%d backlog] input %0.2f msg/sec\n", fq_client_data_backlog(c), d); } } int main(int argc, char **argv) { hrtime_t s0, s, f, f0; uint64_t cnt = 0, icnt = 0; int psize = 0, i = 0, rcvd = 0; fq_client c; fq_bind_req breq; fq_msg *m; signal(SIGPIPE, SIG_IGN); fq_client_init(&c, 0, logger); if(argc < 5) { fprintf(stderr, "%s [size [count]]\n", argv[0]); exit(-1); } fq_client_creds(c, argv[1], atoi(argv[2]), argv[3], argv[4]); fq_client_heartbeat(c, 1000); fq_client_set_backlog(c, 10000, 100); fq_client_connect(c); memset(&breq, 0, sizeof(breq)); memcpy(breq.exchange.name, "maryland", 8); breq.exchange.len = 8; breq.flags = 0; breq.program = (char *)"prefix:\"test.prefix.\""; fq_client_bind(c, &breq); while(ck_pr_load_32(&breq.out__route_id) == 0) usleep(100); printf("route set -> %u\n", breq.out__route_id); if(breq.out__route_id == FQ_BIND_ILLEGAL) { fprintf(stderr, "Failure to bind...\n"); exit(-1); } if(argc > 5) { psize = atoi(argv[5]); if(psize <= 0 || psize > 100000000) { fprintf(stderr, "invalid size must be > 0 and < 100000000\n"); exit(-1); } } printf("payload size -> %d\n", psize); if(argc > 6) { send_count = atoi(argv[6]); if(send_count <= 0 || send_count > 10000000) { fprintf(stderr, "invalid send count must be > 0 and < 10000000\n"); exit(-1); } } printf("message count -> %d\n", send_count); s0 = s = fq_gethrtime(); while(i < send_count || fq_client_data_backlog(c) > 0) { if(i < send_count) { m = fq_msg_alloc_BLANK(psize); memset(m->payload, 0, psize); fq_msg_exchange(m, "maryland", 8); fq_msg_route(m, "test.prefix.foo", 15); fq_msg_id(m, NULL); fq_client_publish(c, m); cnt++; i++; fq_msg_free(m); } else usleep(100); f = fq_gethrtime(); while(NULL != (m = fq_client_receive(c))) { icnt++; rcvd++; fq_msg_deref(m); } if(f-s > 1000000000) { print_rate(c, s, f, cnt, icnt); icnt = 0; cnt = 0; s = f; } } f0 = fq_gethrtime(); print_rate(c, s0, f0, i, 0); do { icnt=0; while(NULL != (m = fq_client_receive(c))) { icnt++; rcvd++; fq_msg_deref(m); } } while(rcvd < send_count); f0 = fq_gethrtime(); print_rate(c, s0, f0, 0, rcvd); printf("Total received during test: %d\n", rcvd); (void) argc; return 0; } ================================================ FILE: fqd.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "getopt.h" #include "fqd.h" #include "fqd_private.h" #ifndef NO_BCD #include static void bcd_signal_handler(int s, siginfo_t *si, void *unused) { (void)si; (void)unused; bcd_fatal("This is a fatal crash"); signal(s, SIG_DFL); return; } static void bcd_setup_sigaction(void) { struct sigaction sa; int signals[] = { SIGSEGV, SIGFPE, SIGABRT, SIGBUS, SIGILL, SIGFPE }; sa.sa_sigaction = bcd_signal_handler; sa.sa_flags = SA_SIGINFO | SA_ONSTACK; for (size_t i = 0; i < sizeof(signals) / sizeof(*signals); i++) { if (sigaction(signals[i], &sa, NULL) == -1) { fprintf(stderr, "warning: failed to set signal " "handler %d\n", signals[i]); } } return; } #else typedef void *bcd_t; #endif static uint32_t nodeid = 0; static unsigned short port = 8765; static int foreground = 0; static bool usebcd = false; static int worker_threads = 1; static char *config_path = NULL; static char *queue_path = NULL; static char *libexecdir = NULL; static bcd_t global_bcd = { 0 }; #define die(str) do { \ fprintf(stderr, "%s: %s\n", str, strerror(errno)); \ exit(-1); \ } while(0) void fqd_bcd_attach(void) { if(!usebcd) return; #ifndef NO_BCD bcd_error_t error; if (bcd_attach(&global_bcd, &error) == -1) { fprintf(stderr, "error: %s\n", bcd_error_message(&error)); exit(-1); } #endif } static void *listener_thread(void *unused) { (void)unused; fqd_start_worker_threads(worker_threads); fprintf(stderr, "Listening on port: %d\n", port); fqd_listener(NULL, port); fqd_stop_worker_threads(); return NULL; } static void usage(const char *prog) { printf("%s:\n", prog); printf("\t-h\t\tthis help message\n"); printf("\t-D\t\trun in the foreground\n"); printf("\t-B\t\tenable BCD backtrace reporting\n"); printf("\t-t \tnumber of worker threads to use (default 1)\n"); printf("\t-n \t\tnode self identifier (IPv4)\n"); printf("\t-p \tspecify listening port (default: 8765)\n"); printf("\t-c \tlocation of the configdb\n"); printf("\t-q \twhere persistent queues are stored\n"); printf("\t-w \twhere files for web services are available\n"); printf("\t-v \tprint additional debugging information, by overriding FQ_DEBUG (cf. fq.h)\n"); printf("\t-l \tuse this dir for relative module loads\n"); printf("\t-m \tmodule to load\n"); } static void parse_cli(int argc, char **argv) { int c; char *debug = NULL; if(getenv("FQ_DEBUG")) { debug = strdup(getenv("FQ_DEBUG")); } libexecdir = strdup(LIBEXECDIR); while((c = getopt(argc, argv, "Bbl:m:hDt:n:p:q:c:w:v:")) != EOF) { switch(c) { case 'B': usebcd = true; break; case 'b': usebcd = false; break; case 'l': free(libexecdir); libexecdir = strdup(optarg); break; case 'm': fqd_route_load_module(libexecdir, optarg, ".so"); break; case 'q': free(queue_path); queue_path = strdup(optarg); break; case 'w': fqd_http_set_root(optarg); break; case 'c': free(config_path); config_path = strdup(optarg); break; case 'D': foreground = 1; break; case 't': worker_threads = atoi(optarg); break; case 'h': usage(argv[0]); exit(0); case 'n': if(inet_pton(AF_INET, optarg, &nodeid) != 1) { fprintf(stderr, "Bad argument to -n, must be an IPv4 address.\n"); exit(-1); } if(nodeid == 0 || nodeid == htonl(0x7f000001)) { fprintf(stderr, "nodeid cannot be INADDR_ANY or loopback\n"); exit(-1); } break; case 'p': port = atoi(optarg); break; case 'v': free(debug); debug = strdup(optarg); break; default: usage(argv[0]); exit(-1); } } if(debug) fq_debug_set_string(debug); free(debug); } static uint32_t get_my_ip(void) { uint32_t ip; struct hostent *h; char buff[128]; gethostname(buff, sizeof(buff)); h = gethostbyname(buff); if(h && h->h_addrtype == AF_INET && h->h_length == 4) { memcpy(&ip, h->h_addr_list[0], h->h_length); if(ip == htonl(0x7f000001)) return 0; return ip; } return 0; } int main(int argc, char **argv) { nodeid = get_my_ip(); parse_cli(argc,argv); global_functions_init(libexecdir); if(nodeid == 0) { fprintf(stderr, "Could not determine host address, use -n \n"); exit(-1); } signal(SIGPIPE, SIG_IGN); if(!foreground) { int pid, fd; /* Handle stdin/stdout/stderr */ fd = open("/dev/null", O_RDONLY); if(fd < 0 || dup2(fd, STDIN_FILENO) < 0) die("Failed to setup stdin"); close(fd); fd = open("/dev/null", O_WRONLY); if(fd < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0) die("Failed to setup std{out,err}"); close(fd); /* daemonize */ pid = fork(); if(pid < 0) die("Failed to fork"); if(pid > 0) exit(0); setsid(); pid = fork(); if(pid < 0) die("Failed to fork"); if(pid > 0) exit(0); /* run */ } #ifndef NO_BCD bcd_error_t error; if(usebcd) { struct bcd_config config; /* Initialize BCD configuration. See bcd.h for options */ if (bcd_config_init(&config, &error) == -1) goto fatal; /* Initialize the library. */ if (bcd_init(&config, &error) == -1) goto fatal; /* Initialize a handle to BCD. This should be called by every thread interacting with BCD. */ if (bcd_attach(&global_bcd, &error) == -1) goto fatal; if (bcd_kv(&global_bcd, "application", "fqd", &error) == -1) goto fatal; if (bcd_kv(&global_bcd, "version", FQ_VERSION, &error) == -1) goto fatal; bcd_setup_sigaction(); } #endif fqd_config_init(nodeid, config_path, queue_path); listener_thread(NULL); fprintf(stderr, "Listener thread could not start. Exiting.\n"); exit(0); return 0; #ifndef NO_BCD fatal: fprintf(stderr, "error: %s\n", bcd_error_message(&error)); exit(-1); #endif } ================================================ FILE: fqd.h.in ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef FQD_H #define FQD_H #ifndef _REENTRANT #error "You must compile with -D_REENTRANT" #endif #include #include #include #include #include #define FQ_VERSION_MAJOR @@FQ_MAJOR@@ #define FQ_VERSION_MINOR @@FQ_MINOR@@ #define FQ_VERSION_PATCH @@FQ_MICRO@@ #define FQ_VERSION "@@FQ_MAJOR@@.@@FQ_MINOR@@.@@FQ_MICRO@@" #include "fq.h" #ifndef VARLIBFQDIR #define VARLIBFQDIR "/var/lib/fq" #endif #define MAX_MESSAGE_SIZE (1 << 28) /* 256MB */ typedef void * fqd_queue_impl_data; typedef struct fqd_queue_impl { const char *name; fqd_queue_impl_data (*setup)(fq_rk *, uint32_t *count); void (*enqueue)(fqd_queue_impl_data, fq_msg *); fq_msg *(*dequeue)(fqd_queue_impl_data); void (*dispose)(fq_rk *, fqd_queue_impl_data); int (*add_checkpoint)(fqd_queue_impl_data, const char *name, const fq_msgid *id); int (*remove_checkpoint)(fqd_queue_impl_data, const char *name); int (*reset_checkpoint)(fqd_queue_impl_data, const char *name); } fqd_queue_impl; /* implememted in fqd_queue_mem.c */ extern fqd_queue_impl fqd_queue_mem_impl; /* implememted in fqd_queue_jlog.c */ extern fqd_queue_impl fqd_queue_jlog_impl; typedef struct fqd_queue fqd_queue; typedef struct fqd_route_rules fqd_route_rules; typedef struct fqd_route_rule fqd_route_rule; typedef struct fqd_config fqd_config; typedef struct fqd_exchange_stats { uint64_t n_messages; uint64_t n_bytes; uint64_t n_routed; uint64_t n_no_route; uint64_t n_dropped; uint64_t n_size_dropped; uint64_t n_no_exchange; uint64_t n_loops; } fqd_exchange_stats_t; typedef struct fqd_exchange { fq_rk exchange; fqd_exchange_stats_t *stats; fqd_route_rules *set; } fqd_exchange; extern int fqd_queue_write_json(int fd, fqd_queue *q); extern int fqd_queue_sprint(char *buf, int len, fqd_queue *q); extern void fqd_queue_ref(fqd_queue *); extern bool fqd_queue_deref(fqd_queue *); extern int fqd_queue_cmp(const fqd_queue *, const fqd_queue *); extern int fqd_config_make_perm_queue(fqd_queue *q); extern int fqd_config_make_trans_queue(fqd_queue *q); extern int fqd_config_make_perm_binding(fq_rk *exchange, fqd_queue *q, int peermode, const char *program); extern int fqd_config_make_trans_binding(fq_rk *exchange, fqd_queue *q, int peermode, const char *program); #define CLIENT_SHARED \ uint32_t refcnt; \ int fd; \ struct timeval connect_time; \ struct sockaddr_in remote; \ hrtime_t last_activity; \ hrtime_t last_heartbeat; \ char pretty[80]; typedef struct { CLIENT_SHARED } remote_anon_client; typedef struct { CLIENT_SHARED uint32_t mode; uint32_t peer_id; uint32_t no_exchange; uint32_t no_route; uint32_t routed; uint32_t dropped; uint32_t size_dropped; uint32_t msgs_in; uint32_t msgs_out; uint32_t octets_in; uint32_t octets_out; } remote_data_client; typedef struct remote_client { CLIENT_SHARED fq_rk user; fq_rk key; fqd_queue *queue; remote_data_client *data; unsigned short heartbeat_ms; } remote_client; /* You can read around in this... but can't modify it */ extern void fqd_config_init(uint32_t, const char *config_path, const char *queue_path); extern int fqd_config_construct_queue_path(char *, size_t, fq_rk *); extern uint32_t fqd_config_get_nodeid(void); extern fqd_config *fqd_config_get(void); extern void fqd_config_release(fqd_config *); extern int fqd_config_register_client(remote_client *, uint64_t *gen); extern int fqd_config_deregister_client(remote_client *, uint64_t *gen); extern fqd_queue *fqd_config_register_queue(fqd_queue *, uint64_t *gen); extern int fqd_config_deregister_queue(fqd_queue *, uint64_t *gen); extern fqd_queue *fqd_config_get_registered_queue(fqd_config *, fq_rk *); extern remote_client *fqd_config_get_registered_client(fqd_config *, fq_rk *key); extern fqd_exchange *fqd_config_get_exchange(fqd_config *c, fq_rk *exchange); extern void fqd_size_dropped(uint64_t); extern void fqd_exchange_messages(fqd_exchange *, uint64_t); extern void fqd_exchange_message_octets(fqd_exchange *, uint64_t); extern void fqd_exchange_no_route(fqd_exchange *, uint64_t); extern void fqd_exchange_routed(fqd_exchange *, uint64_t); extern void fqd_exchange_dropped(fqd_exchange *, uint64_t); extern void fqd_exchange_no_exchange(fqd_exchange *, uint64_t); extern uint32_t fqd_config_bind(fq_rk *exchange, uint16_t flags, const char *program, fqd_queue *q, uint64_t *gen); extern int fqd_config_unbind(fq_rk *exchange, uint32_t route_id, fqd_queue *q, uint64_t *gen); extern void fqd_config_wait(uint64_t gen, int us); extern void fqd_config_http_stats(remote_client *client); extern void fqd_command_and_control_server(remote_client *); extern void fqd_data_subscription_server(remote_data_client *); extern int fqd_listener(const char *ip, unsigned short port); extern void fqd_remote_client_ref(remote_client *); extern bool fqd_remote_client_deref(remote_client *); extern fq_rk *fqd_queue_name(fqd_queue *q); extern fqd_queue *fqd_queue_get(fq_rk *, const char *, const char *, int, char *); extern uint32_t fqd_queue_get_backlog_limit(fqd_queue *); extern void fqd_queue_set_backlog_limit(fqd_queue *, uint32_t); extern queue_policy_t fqd_queue_get_policy(fqd_queue *); extern void fqd_queue_set_policy(fqd_queue *, queue_policy_t); extern void fqd_queue_enqueue(fqd_queue *q, fq_msg *m, int *dropped); extern fq_msg *fqd_queue_dequeue(fqd_queue *q); extern int fqd_queue_register_client(fqd_queue *q, remote_client *c); extern bool fqd_queue_deregister_client(fqd_queue *q, remote_client *c); extern void fqd_inject_message(remote_data_client *c, fq_msg *m); extern struct fqd_route_rule * fqd_routemgr_compile(const char *program, int peermode, fqd_queue *q); extern void fqd_routemgr_rule_free(fqd_route_rule *rule); extern fqd_route_rules *fqd_routemgr_ruleset_alloc(void); extern uint32_t fqd_routemgr_ruleset_add_rule(fqd_route_rules *set, fqd_route_rule *r, int *isnew); extern int fqd_routemgr_drop_rules_by_route_id(fqd_route_rules *set, fqd_queue *q, uint32_t route_id); extern int fqd_routemgr_perm_route_id(fqd_route_rules *set, uint32_t route_id); extern int fqd_routemgr_trans_route_id(fqd_route_rules *set, uint32_t route_id); extern void fqd_routemgr_drop_rules_by_queue(fqd_route_rules *set, fqd_queue *q); extern fqd_route_rules *fqd_routemgr_ruleset_copy(fqd_route_rules *set); extern void fqd_routemgr_ruleset_free(fqd_route_rules *set); extern int fqd_add_peer(uint64_t gen, const char *host, int port, const char *user, const char *pass, fq_rk *exchange, const char *prog, bool perm); extern int fqd_remove_peers(uint64_t current_gen); extern int fqd_remove_peer(const char *host, int port, const char *user, const char *pass, fq_rk *exchange, const char *prog); #define ERRTOFD(fd, error) do { \ (void)fq_write_uint16(fd, htons(FQ_PROTO_ERROR)); \ (void)fq_write_short_cmd(fd, strlen(error), error); \ } while(0) /* programming: * * PROGRAM: :string RULES* * RULE: (RULE) * RULE: (RULE && RULE) * RULE: (RULE || RULE) * RULE: EXPR * EXPR: function(args) * args: arg * args: arg, args * arg: "string" * arg: true|false * arg: [0-9][0-9]*(?:.[0-9]*) * * functions are dynamically loadable with type signature * strings: s, booleans: b, integers: d * function: substr_eq(9.3,10,"tailorings",true) * C symbol: fqd_route_prog__substr_eq__ddsb(int nargs, valnode_t *args); * fallback symbol: fqd_route_prog_substr_eq__VA(int nargs, valnode_t *args); */ #define MAX_VALNODE_ARGS 16 typedef struct exprnode { bool (*match)(fq_msg *m, int nargs, valnode_t *args); int nargs; valnode_t *args; } exprnode_t; typedef struct rulenode { uint32_t refcnt; char oper; struct rulenode *left; struct rulenode *right; struct exprnode *expr; } rulenode_t; /* DTrace helpers */ typedef struct { int fd; char *pretty; } fq_dtrace_remote_anon_client_t; typedef struct { int fd; char *pretty; char *user; } fq_dtrace_remote_client_t; typedef struct { int fd; char *pretty; uint32_t mode; uint32_t no_exchange; uint32_t no_route; uint32_t routed; uint32_t dropped; uint32_t size_dropped; uint32_t msgs_in; uint32_t msgs_out; } fq_dtrace_remote_data_client_t; #define DTRACE_PACK_ANON_CLIENT(dc, c) do { \ (dc)->fd = (int32_t)(c)->fd; \ (dc)->pretty = (c)->pretty; \ } while(0) #define DTRACE_PACK_CLIENT(dc, c) do { \ (dc)->fd = (int32_t)(c)->fd; \ (dc)->pretty = (c)->pretty; \ (dc)->user = (char *)(c)->user.name; \ } while(0) #define DTRACE_PACK_DATA_CLIENT(dc, c) do { \ (dc)->fd = (int32_t)(c)->fd; \ (dc)->pretty = (c)->pretty; \ (dc)->mode = (c)->mode; \ (dc)->no_exchange = (c)->no_exchange; \ (dc)->no_route = (c)->no_route; \ (dc)->routed = (c)->routed; \ (dc)->dropped = (c)->dropped; \ (dc)->size_dropped = (c)->size_dropped; \ (dc)->msgs_in = (c)->msgs_in; \ (dc)->msgs_out = (c)->msgs_out; \ } while(0) typedef struct { char *name; int32_t private; int32_t policy; char *type; } fq_dtrace_queue_t; void fqd_queue_dtrace_pack(fq_dtrace_queue_t *, fqd_queue *); void fqd_http_loop(remote_client *c, uint32_t bytes_four); void fqd_http_set_root(const char *newpath); #define DTRACE_PACK_QUEUE(dq, c) fqd_queue_dtrace_pack(dq, c) #endif ================================================ FILE: fqd_ccs.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "fqd.h" #include "fqd_private.h" #include "fq_dtrace.h" #include #include #include #include #include #include #include #include #include #ifdef __MACH__ static int mkkey(void *ptr, int len) { static FILE *_random; if(_random == NULL) _random = fopen("/dev/random", "r"); if(_random == NULL) return -1; unsigned char *ucp = ptr; for(int i=0; i static int mkkey(void *ptr, int len) { if(RAND_bytes(ptr, len) != 1) { #if OPENSSL_VERSION_NUMBER < 0x1010100fL if(RAND_pseudo_bytes(ptr, len) != 1) { return -1; } #else return -1; #endif } return 0; } #endif static int fqd_ccs_auth(remote_client *client) { uint16_t cmd, method; fq_rk queue_name; if(fq_read_uint16(client->fd, &cmd) || ntohs(cmd) != FQ_PROTO_AUTH_CMD) { ERRTOFD(client->fd, "auth command expected"); return -1; } if(fq_read_uint16(client->fd, &method)) { ERRTOFD(client->fd, "auth method read failed"); return -2; } method = ntohs(method); if(method == 0) { char buf[128]; unsigned char pass[10240]; char queue_detail[1024], *end_of_qd; char *qtype = NULL, *qparams = NULL; char *replace_params = NULL; int len; len = fq_read_short_cmd(client->fd, sizeof(client->user.name), client->user.name); if(len < 0 || len > (int)sizeof(client->user.name)) { ERRTOFD(client->fd, "user name is too long"); return -3; } client->user.len = len & 0xff; len = fq_read_short_cmd(client->fd, sizeof(queue_detail)-1, queue_detail); if(len < 0) return -4; if(len >= sizeof(queue_detail)) { ERRTOFD(client->fd, "queue detail is too long"); return -4; } queue_detail[len] = '\0'; end_of_qd = memchr(queue_detail, '\0', len); if(!end_of_qd) { if(len < 0 || len > (int)sizeof(queue_name.name)) { ERRTOFD(client->fd, "queue name is too long"); return -4; } queue_name.len = len & 0xff; memcpy(queue_name.name, queue_detail, queue_name.len); if(queue_name.len < sizeof(queue_name.name)) memset(queue_name.name + queue_name.len, 0, sizeof(queue_name.name) - queue_name.len); } else if(end_of_qd - queue_detail <= 0xff) { queue_name.len = end_of_qd - queue_detail; memcpy(queue_name.name, queue_detail, queue_name.len); if(queue_name.len < sizeof(queue_name.name)) memset(queue_name.name + queue_name.len, 0, sizeof(queue_name.name) - queue_name.len); qtype = end_of_qd + 1; if(*qtype) qparams = strchr(qtype, ':'); else qtype = NULL; if(qparams) *qparams++ = '\0'; } else { ERRTOFD(client->fd, "pass field is too long"); return -4; } if(queue_name.len == 0) { uuid_t autogen; static const char *DYNAMIC_QUEUE_FORCE_OPTIONS = "transient,private"; int rlen = strlen(DYNAMIC_QUEUE_FORCE_OPTIONS)+1; if(!qparams || *qparams == '\0') { replace_params = malloc(rlen); memcpy(replace_params, DYNAMIC_QUEUE_FORCE_OPTIONS, rlen); } else { rlen += strlen(qparams)+1; replace_params = malloc(rlen); snprintf(replace_params, rlen, "%s,%s", qparams, DYNAMIC_QUEUE_FORCE_OPTIONS); } qparams = replace_params; uuid_generate(autogen); memcpy(queue_name.name, "auto-", 5); uuid_unparse_lower(autogen, (void *)(queue_name.name+5)); queue_name.len = 5 + 36; /* 5 + 36 uuid, no trailing \0 */ } len = fq_read_short_cmd(client->fd, sizeof(pass), pass); if(len < 0 || len > (int)sizeof(pass)) { ERRTOFD(client->fd, "queue name is too long"); free(replace_params); return -4; } client->queue = fqd_queue_get(&queue_name, qtype, qparams, sizeof(buf), buf); if(client->queue == NULL) { ERRTOFD(client->fd, buf); free(replace_params); return -6; } /* do AUTH */ buf[0] = '\0'; inet_ntop(AF_INET, &client->remote.sin_addr, buf, sizeof(buf)); snprintf(client->pretty, sizeof(client->pretty), "%.*s/%.*s@%s:%d", client->user.len, client->user.name, queue_name.len, queue_name.name, buf, ntohs(client->remote.sin_port)); if(FQ_CLIENT_AUTH_ENABLED()) { fq_dtrace_remote_client_t dclient; DTRACE_PACK_CLIENT(&dclient, client); FQ_CLIENT_AUTH(&dclient); } free(replace_params); return 0; } ERRTOFD(client->fd, "unsupported auth method"); return -1; } static int fqd_ccs_key_client(remote_client *client) { int fd = client->fd; client->key.len = sizeof(client->key.name); if(mkkey(client->key.name, client->key.len) != 0) { ERRTOFD(fd, "can't generate random key"); return -1; } if(fqd_queue_register_client(client->queue, client)) { ERRTOFD(fd, "can't add you to queue"); return -1; } if(fq_write_uint16(client->fd, FQ_PROTO_AUTH_RESP) || fq_write_short_cmd(client->fd, client->key.len, client->key.name) < 0) { return -2; } #ifdef DEBUG { char hex[260]; if(fq_rk_to_hex(hex, sizeof(hex), &client->key) >= 0) fq_debug(FQ_DEBUG_CONN, "client keyed:\n%s\n", hex); } #endif return 0; } static int fqd_ccs_heartbeat(remote_client *client) { #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "heartbeat -> %s\n", client->pretty); #endif return fq_write_uint16(client->fd, FQ_PROTO_HB); } static int fqd_css_status(remote_client *client) { remote_data_client *data = client->data; #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "status -> %s\n", client->pretty); #endif if(fq_write_uint16(client->fd, FQ_PROTO_STATUS) < 0) return -1; #define write_uintkey(name, v) do { \ if(fq_write_short_cmd(client->fd, strlen(name), name) < 0) return -1; \ if(fq_write_uint32(client->fd, v) < 0) return -1; \ } while(0) if(client->queue) write_uintkey("dropped_in", client->queue->dropped_to); if(data) { write_uintkey("no_exchange", data->no_exchange); write_uintkey("no_route", data->no_route); write_uintkey("routed", data->routed); write_uintkey("dropped", data->dropped); write_uintkey("size_dropped", data->size_dropped); write_uintkey("msgs_in", data->msgs_in); write_uintkey("msgs_out", data->msgs_out); write_uintkey("octets_in", data->octets_in); write_uintkey("octets_out", data->octets_out); } if(fq_write_uint16(client->fd, 0) < 0) return -1; return 0; } static int fqd_ccs_loop(remote_client *client) { int poll_timeout = 10; while(1) { int rv; struct pollfd pfd; uint16_t cmd; hrtime_t t; pfd.fd = client->fd; pfd.events = POLLIN|POLLHUP; pfd.revents = 0; rv = poll(&pfd, 1, poll_timeout); if(rv < 0) { #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "poll() failed on %s: %s\n", client->pretty, strerror(errno)); #endif break; } if(rv > 0) poll_timeout = 10; else poll_timeout *= 2; if(poll_timeout > 4000) poll_timeout = 4000; /* we must wake up often enough to emit our heartbeat */ if(client->heartbeat_ms && poll_timeout > client->heartbeat_ms) poll_timeout = client->heartbeat_ms; t = fq_gethrtime(); unsigned long long hb_ns = ((unsigned long long)client->heartbeat_ms) * 1000000ULL; long long hb_age_ns = t - client->last_heartbeat; if(client->heartbeat_ms && hb_age_ns > hb_ns) { if(fqd_ccs_heartbeat(client)) break; client->last_heartbeat = t; } if(hb_ns && client->last_activity < (t - hb_ns * 3)) { ERRTOFD(client->fd, "client heartbeat failed"); #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "heartbeat [%dms] failed from %s [%lld]\n", client->heartbeat_ms, client->pretty, hb_age_ns); #endif break; } if(rv > 0) { if(fq_read_uint16(client->fd, &cmd) != 0) break; client->last_activity = fq_gethrtime(); switch(cmd) { case FQ_PROTO_HB: #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "heartbeat <- %s\n", client->pretty); #endif break; case FQ_PROTO_HBREQ: { uint16_t ms; if(fq_read_uint16(client->fd, &ms) < 0) return -1; #ifdef DEBUG fq_debug(FQ_DEBUG_CONN, "setting client(%p) heartbeat to %d\n", (void *)client, ms); #endif client->heartbeat_ms = ms; break; } case FQ_PROTO_STATUSREQ: if(fqd_css_status(client)) return -1; break; case FQ_PROTO_BINDREQ: { int len; uint16_t flags; uint32_t route_id; uint64_t cgen; char program[0xffff]; fq_rk exchange; if(fq_read_uint16(client->fd, &flags)) return -1; len = fq_read_short_cmd(client->fd, sizeof(exchange.name), exchange.name); if(len < 0 || len > (int)sizeof(exchange.name)) return -3; exchange.len = len & 0xff; len = fq_read_short_cmd(client->fd, sizeof(program)-1, program); if(len < 0 || len > (int)sizeof(program)-1) return -1; program[len] = '\0'; route_id = fqd_config_bind(&exchange, flags, program, client->queue, &cgen); if(route_id != FQ_BIND_ILLEGAL) fqd_config_wait(cgen, 100); if(fq_write_uint16(client->fd, FQ_PROTO_BIND) != 0) return -1; if(fq_write_uint32(client->fd, route_id) != 0) return -1; break; } case FQ_PROTO_UNBINDREQ: { uint32_t route_id; fq_rk exchange; int success, len; if(fq_read_uint32(client->fd, &route_id)) return -1; len = fq_read_short_cmd(client->fd, sizeof(exchange.name), exchange.name); if(len < 0 || len > (int)sizeof(exchange.name)) return -1; exchange.len = len & 0xff; success = fqd_config_unbind(&exchange, route_id, client->queue, NULL); if(fq_write_uint16(client->fd, FQ_PROTO_UNBIND) != 0) return -1; if(fq_write_uint32(client->fd, success ? route_id : FQ_BIND_ILLEGAL)) return -1; break; } default: return -1; } } } return -1; } extern void fqd_command_and_control_server(remote_client *client) { /* auth */ int rv, registered = 0; uint64_t cgen; fq_debug(FQ_DEBUG_CONN, "--> ccs thread\n"); if((rv = fqd_ccs_auth(client)) != 0) { fq_debug(FQ_DEBUG_CONN, "client auth failed: %d\n", rv); (void)rv; goto out; } if(fqd_config_register_client(client, &cgen)) { fq_debug(FQ_DEBUG_CONN, "client registration failed\n"); goto out; } fq_thread_setname("fqd:ccs:%s", client->pretty); registered = 1; fqd_config_wait(cgen, 100); if(fqd_ccs_key_client(client) != 0) { fq_debug(FQ_DEBUG_CONN, "client keying failed: %d\n", rv); goto out; } fqd_ccs_heartbeat(client); fqd_ccs_loop(client); out: if(registered) fqd_config_deregister_client(client, NULL); fq_debug(FQ_DEBUG_CONN, "<-- ccs thread\n"); } ================================================ FILE: fqd_config.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "fq.h" #include "fqd.h" #include "fqd_private.h" #include "fq_dtrace.h" #define CONFIG_RING_SIZE 3 #define CONFIG_ROTATE_NS (100*1000*1000) /*100ms*/ #define DEFAULT_CLIENT_CNT 128 const char *fq_version_string = FQ_VERSION; const char *fqd_config_path = VARLIBFQDIR "/fqd.sqlite"; const char *fqd_queue_path = VARLIBFQDIR "/queues"; /* A ring of three configs * * [cycleout] [currentread] [currentwrite] * */ fqd_exchange_stats_t global_counters = { 0, 0, 0, 0, 0, 0 }; struct fqd_config { uint64_t gen; int n_clients; remote_client **clients; int n_queues; fqd_queue **queues; int n_exchanges; fqd_exchange **exchanges; }; static sqlite3 *configdb = NULL; static uint64_t global_gen = 0; static uint32_t global_nodeid = 0; uint32_t fqd_config_get_nodeid() { return global_nodeid; } typedef struct fqd_config_ref { fqd_config config; uint32_t readers; uint32_t dirty; } fqd_config_ref; static struct { fqd_config_ref configs[CONFIG_RING_SIZE]; /* protected by writelock */ pthread_mutex_t writelock; uint32_t current_config; /* end writelock protected things */ } global_config; #define FQGC(i) global_config.configs[i] static void *config_rotation(void *); static void setup_config(void); static void setup_initial_config(void); void fqd_config_init(uint32_t nodeid, const char *config_path, const char *qpath) { int i; pthread_t t; pthread_attr_t attr; global_nodeid = nodeid; if(config_path) fqd_config_path = config_path; if(qpath) fqd_queue_path = qpath; memset(&global_config, 0, sizeof(global_config)); pthread_mutex_init(&global_config.writelock, NULL); for(i=0;ireaders); } int fqd_config_construct_queue_path(char *path, size_t pathlen, fq_rk *qname) { int i; char *qout, qhex[MAX_RK_LEN * 2 + 1]; qout = qhex; for(i=0; ilen; i++) { snprintf(qout, 3, "%02x", (int)qname->name[i]); qout += 2; } *qout = '\0'; return snprintf(path, pathlen, "%s/%s", fqd_queue_path, qhex); } fqd_queue * fqd_config_get_registered_queue(fqd_config *c, fq_rk *qname) { int i; fqd_queue *q = NULL; for(i=0;in_queues;i++) { if(c->queues[i] && fq_rk_cmp(qname, fqd_queue_name(c->queues[i])) == 0) { q = c->queues[i]; break; } } fq_debug(FQ_DEBUG_CONFIG, "referencing queue -> (%p)\n", (void *)q); return q; } remote_client * fqd_config_get_registered_client(fqd_config *c, fq_rk *key) { int i; remote_client *client = NULL; for(i=0;in_clients;i++) { if(c->clients[i] && fq_rk_cmp(key, &c->clients[i]->key) == 0) { client = c->clients[i]; break; } } return client; } fqd_exchange * fqd_config_get_exchange(fqd_config *c, fq_rk *exchange) { int i; for(i=0;in_exchanges;i++) if(c->exchanges[i] && fq_rk_cmp(exchange, &c->exchanges[i]->exchange) == 0) return c->exchanges[i]; return NULL; } /* This is static b/c no one but us should be calling it * we we need to hold a lock whilst calling it. */ static fqd_exchange * fqd_config_add_exchange(fqd_config *c, fq_rk *exchange) { int i; for(i=0;in_exchanges;i++) { if(c->exchanges[i] == NULL) break; if(fq_rk_cmp(exchange, &c->exchanges[i]->exchange) == 0) return c->exchanges[i]; } if(i == c->n_exchanges) { fqd_exchange **nlist; int ncnt = c->n_exchanges * 2; if(ncnt == 0) ncnt = 16; nlist = calloc(ncnt, sizeof(*c->exchanges)); if(c->n_exchanges) { memcpy(nlist, c->exchanges, c->n_exchanges * sizeof(*c->exchanges)); free(c->exchanges); } c->n_exchanges = ncnt; c->exchanges = nlist; } c->exchanges[i] = calloc(1, sizeof(*c->exchanges[i])); memcpy(&c->exchanges[i]->exchange, exchange, sizeof(*exchange)); c->exchanges[i]->stats = calloc(1, sizeof(*c->exchanges[i]->stats)); c->exchanges[i]->set = fqd_routemgr_ruleset_alloc(); fq_debug(FQ_DEBUG_CONFIG, "Adding new exchange[%.*s] -> %d\n", exchange->len, exchange->name, i); return c->exchanges[i]; } void fqd_config_wait(uint64_t gen, int us) { while(1) { int which; which = ck_pr_load_uint(&global_config.current_config); if(FQGC(which).config.gen >= gen) return; if(us>0) usleep(us); } } /* config modification */ #define BEGIN_CONFIG_MODIFY(conf) \ fqd_config_ref *conf ## _ref; \ fqd_config *conf; \ pthread_mutex_lock(&global_config.writelock); \ conf ## _ref = &FQGC((global_config.current_config + 1) % CONFIG_RING_SIZE); \ conf = &conf ## _ref->config #define MARK_CONFIG(conf) do { conf ## _ref->dirty = 1; } while(0) #define END_CONFIG_MODIFY() pthread_mutex_unlock(&global_config.writelock) extern uint32_t fqd_config_bind(fq_rk *exchange, uint16_t flags, const char *program, fqd_queue *q, uint64_t *gen) { uint32_t route_id; fqd_exchange *x; fqd_route_rule *rule; int peermode = ((flags & FQ_BIND_PEER) == FQ_BIND_PEER); int isnew = 0; rule = fqd_routemgr_compile(program, peermode, q); if(!rule) return FQ_BIND_ILLEGAL; BEGIN_CONFIG_MODIFY(config); x = fqd_config_get_exchange(config, exchange); if(!x) x = fqd_config_add_exchange(config, exchange); route_id = fqd_routemgr_ruleset_add_rule(x->set, rule, &isnew); if(flags & FQ_BIND_PERM) { if((flags & FQ_BIND_PERM) == FQ_BIND_PERM) { fqd_routemgr_perm_route_id(x->set, route_id); } else if((flags & FQ_BIND_PERM) == FQ_BIND_TRANS) { fqd_routemgr_trans_route_id(x->set, route_id); } } fq_debug(FQ_DEBUG_CONFIG, "rule %u \"%s\" for exchange \"%.*s\" -> Q[%p]\n", route_id, program, exchange->len, exchange->name, (void *)q); if(gen) *gen = config->gen; MARK_CONFIG(config); END_CONFIG_MODIFY(); /* if these bits are set, we have configdb work to do */ if(flags & FQ_BIND_PERM) { if((flags & FQ_BIND_PERM) == FQ_BIND_PERM) { fqd_config_make_perm_binding(exchange, q, peermode, program); } else if((flags & FQ_BIND_PERM) == FQ_BIND_TRANS) { fqd_config_make_trans_binding(exchange, q, peermode, program); } } return route_id; } extern int fqd_config_unbind(fq_rk *exchange, uint32_t route_id, fqd_queue *c, uint64_t *gen) { int i, dropped = 0; BEGIN_CONFIG_MODIFY(config); for(i=0;in_exchanges;i++) { if(config->exchanges[i] != NULL && fq_rk_cmp(exchange, &config->exchanges[i]->exchange) == 0) { dropped = fqd_routemgr_drop_rules_by_route_id(config->exchanges[i]->set, c, route_id); if(gen) *gen = config->gen; break; } } if(dropped) MARK_CONFIG(config); END_CONFIG_MODIFY(); fq_debug(FQ_DEBUG_CONFIG, "unbind rule %u %s for exchange \"%.*s\" -> Q[%p]\n", route_id, dropped ? "successful" : "failed", exchange->len, exchange->name, (void *)c); return dropped; } extern int fqd_config_register_client(remote_client *c, uint64_t *gen) { int i, rv = 0, available_slot = -1; BEGIN_CONFIG_MODIFY(config); for(i=0; in_clients; i++) { fq_assert(c != config->clients[i]); if(available_slot == -1 && config->clients[i] == NULL) available_slot = i; } if(available_slot < 0) { remote_client **f; f = calloc(sizeof(*f), config->n_clients + 128); if(f == NULL) goto oom; if(config->n_clients) memcpy(f, config->clients, sizeof(*f) * config->n_clients); available_slot = config->n_clients; config->n_clients += 128; free(config->clients); config->clients = f; } config->clients[available_slot] = c; fq_debug(FQ_DEBUG_CONFIG, "registering client -> (%p:%s)\n", (void *)c, c->pretty); fqd_remote_client_ref(c); if(gen) *gen = config->gen; MARK_CONFIG(config); rv = 0; oom: END_CONFIG_MODIFY(); return rv; } extern int fqd_config_deregister_client(remote_client *c, uint64_t *gen) { int i; remote_client *toderef = NULL; BEGIN_CONFIG_MODIFY(config); for(i=0; in_clients; i++) { if(c == config->clients[i]) { config->clients[i] = NULL; toderef = c; fq_debug(FQ_DEBUG_CONFIG, "deregistering client -> (%p:%s)\n", (void *)c, c->pretty); break; } } if(i == config->n_clients) fq_debug(FQ_DEBUG_CONFIG, "FAILED deregistering client -> (%p:%s)\n", (void *)c, c->pretty); fq_assert(i != config->n_clients); MARK_CONFIG(config); if(gen) *gen = config->gen; END_CONFIG_MODIFY(); if(toderef) { /* Do this work without holding the lock */ if(toderef->queue) { if(fqd_queue_deregister_client(toderef->queue, c)) { fqd_config_deregister_queue(toderef->queue, NULL); } } toderef->queue = NULL; fqd_remote_client_deref(toderef); } return 0; } extern fqd_queue * fqd_config_register_queue(fqd_queue *c, uint64_t *gen) { int i, available_slot = -1; BEGIN_CONFIG_MODIFY(config); for(i=0; in_queues; i++) { if(config->queues[i] && fqd_queue_cmp(c, config->queues[i]) == 0) { if(gen) *gen = config->gen; c = config->queues[i]; goto out; } if(available_slot == -1 && config->queues[i] == NULL) available_slot = i; } if(available_slot < 0) { fqd_queue **f; f = calloc(sizeof(*f), config->n_queues + 128); if(f == NULL) goto out; if(config->n_queues) memcpy(f, config->queues, sizeof(*f) * config->n_queues); available_slot = config->n_queues; config->n_queues += 128; free(config->queues); config->queues = f; } config->queues[available_slot] = c; fqd_queue_ref(c); if(gen) *gen = config->gen; MARK_CONFIG(config); out: END_CONFIG_MODIFY(); fq_debug(FQ_DEBUG_CONFIG, "registering queue (%s) -> (%p:%.*s)\n", (available_slot == -1) ? "old" : "new", (void *)c, c->name.len, c->name.name); return c; } extern int fqd_config_deregister_queue(fqd_queue *c, uint64_t *gen) { int i; fqd_queue *toderef = NULL; BEGIN_CONFIG_MODIFY(config); for(i=0; in_queues; i++) { if(config->queues[i] && fqd_queue_cmp(c, config->queues[i]) == 0) { config->queues[i] = NULL; toderef = c; fq_debug(FQ_DEBUG_CONFIG, "deregistering queue -> (%p:%.*s)\n", (void *)c, c->name.len, c->name.name); break; } } if(toderef) { for(i=0;in_exchanges;i++) { if(config->exchanges[i] != NULL) { fqd_routemgr_drop_rules_by_queue(config->exchanges[i]->set, toderef); } } MARK_CONFIG(config); } else { fq_debug(FQ_DEBUG_CONFIG, "FAILED deregistering queue -> (%p:%.*s)\n", (void *)c, c->name.len, c->name.name); } if(gen) *gen = config->gen; END_CONFIG_MODIFY(); if(toderef) fqd_queue_deref(toderef); return 0; } /* This section deals with managing the rings */ static void fqd_internal_copy_config(fqd_config_ref *src, fqd_config_ref *tgt) { int i; /* First clients */ if(tgt->config.clients) { for(i=0;iconfig.n_clients;i++) if(tgt->config.clients[i]) fqd_remote_client_deref(tgt->config.clients[i]); free(tgt->config.clients); tgt->config.clients = NULL; } if(src->config.clients) { tgt->config.n_clients = src->config.n_clients; tgt->config.clients = malloc(sizeof(*tgt->config.clients) * tgt->config.n_clients); fq_assert(tgt->config.clients); memcpy(tgt->config.clients, src->config.clients, sizeof(*tgt->config.clients) * tgt->config.n_clients); for(i=0;iconfig.n_clients;i++) if(tgt->config.clients[i]) fqd_remote_client_ref(tgt->config.clients[i]); } /* Now the same thing of queues */ if(tgt->config.queues) { for(i=0;iconfig.n_queues;i++) if(tgt->config.queues[i]) fqd_queue_deref(tgt->config.queues[i]); free(tgt->config.queues); tgt->config.queues = NULL; } if(src->config.queues) { tgt->config.n_queues = src->config.n_queues; tgt->config.queues = malloc(sizeof(*tgt->config.queues) * tgt->config.n_queues); fq_assert(tgt->config.queues); memcpy(tgt->config.queues, src->config.queues, sizeof(*tgt->config.queues) * tgt->config.n_queues); for(i=0;iconfig.n_queues;i++) if(tgt->config.queues[i]) fqd_queue_ref(tgt->config.queues[i]); } /* next the exchang/routemaps */ if(tgt->config.exchanges) { for(i=0;iconfig.n_exchanges;i++) { if(tgt->config.exchanges[i] && tgt->config.exchanges[i]->set) { fqd_routemgr_ruleset_free(tgt->config.exchanges[i]->set); free(tgt->config.exchanges[i]); } } free(tgt->config.exchanges); tgt->config.exchanges = NULL; } if(src->config.exchanges) { tgt->config.n_exchanges = src->config.n_exchanges; tgt->config.exchanges = malloc(sizeof(*tgt->config.exchanges) * tgt->config.n_exchanges); fq_assert(tgt->config.exchanges); for(i=0;iconfig.n_exchanges;i++) { if(src->config.exchanges[i]) { tgt->config.exchanges[i] = malloc(sizeof(*tgt->config.exchanges[i])); memcpy(tgt->config.exchanges[i], src->config.exchanges[i], sizeof(*tgt->config.exchanges[i])); tgt->config.exchanges[i]->set = fqd_routemgr_ruleset_copy(src->config.exchanges[i]->set); } else tgt->config.exchanges[i] = NULL; } } } static void fixup_config_write_context(void) { uint32_t current, next, nextnext; current = global_config.current_config; next = (current + 1) % CONFIG_RING_SIZE; nextnext = (current + 2) % CONFIG_RING_SIZE; FQ_CONFIG_ROTATE(FQGC(next).dirty); if(!FQGC(next).dirty) return; fq_debug(FQ_DEBUG_CONFIG, "Swapping to next running config\n"); pthread_mutex_lock(&global_config.writelock); /* We've locked writing... let the world use the new config */ global_config.current_config = next; /* Wait until the next(next) has no readers so we can copy into it */ while(ck_pr_load_uint(&FQGC(nextnext).readers) != 0) ck_pr_stall(); /* Safe to do the copy */ fqd_internal_copy_config(&FQGC(next), &FQGC(nextnext)); /* Mark that new write target as clean */ FQGC(nextnext).config.gen = ++global_gen; FQGC(nextnext).dirty = 0; pthread_mutex_unlock(&global_config.writelock); fq_debug(FQ_DEBUG_CONFIG, "Swapped to next running config\n"); } static void *config_rotation(void *unused) { fq_thread_setname("fqd:config"); fqd_bcd_attach(); while(1) { fixup_config_write_context(); usleep(CONFIG_ROTATE_NS / 1000); } (void)unused; return NULL; } #define cprintf(client, fmt, ...) do { \ char scratch[1024]; \ int len; \ len = snprintf(scratch, sizeof(scratch), fmt, __VA_ARGS__); \ while(write(client->fd, scratch, len) == -1 && errno == EINTR); \ } while(0) #define cwrite(client, str) write(client->fd, str, strlen(str)) int fqd_config_http_routes(struct fqd_route_rule *r, int rv, void *closure) { remote_client *client = closure; char *program_encoded, *cp, *tcp; int len; len = strlen(r->program)*2+1; program_encoded = malloc(len); for(cp = r->program, tcp = program_encoded; *cp; cp++) { switch(*cp) { case '\n': *tcp++ = '\\'; *tcp++ = 'n'; break; case '\r': *tcp++ = '\\'; *tcp++ = 'r'; break; case '\t': *tcp++ = '\\'; *tcp++ = 't'; break; case '\\': case '\"': *tcp++ = '\\'; *tcp++ = *cp; break; default: *tcp++ = *cp; break; } } *tcp = '\0'; cprintf(client, " %s\"%u\": {\n", rv ? "," : " ", r->route_id); cprintf(client, " \"route_id\": %u,\n", r->route_id); cprintf(client, " \"prefix\": \"%.*s\",\n", r->prefix.len, r->prefix.name); cprintf(client, " \"queue\": \"%.*s\",\n", r->queue->name.len, r->queue->name.name); cprintf(client, " \"permanent\": %s,\n", r->permanent ? "true" : "false"); cprintf(client, " \"invocations\": %llu,\n", (unsigned long long)r->stats->invocations); cprintf(client, " \"avg_ns\": %u,\n", r->stats->avg_ns); cprintf(client, " \"program\": \"%s\"\n", program_encoded); cwrite(client, " }\n"); free(program_encoded); return 1; } void fqd_config_http_stats(remote_client *client) { int i; const char *headers = "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n"; fqd_config *config; while(write(client->fd, headers, strlen(headers)) == -1 && errno == EINTR); config = fqd_config_get(); cwrite(client, "{\n"); cprintf(client, " \"version\": \"%s\",\n", fq_version_string); cwrite(client, " \"exchanges\": {\n"); for(i=0;in_exchanges;i++) { if(config->exchanges[i]) { fqd_exchange *e = config->exchanges[i]; cprintf(client, " \"%.*s\": {\n", e->exchange.len, e->exchange.name); cprintf(client, " \"messages\": %llu,\n", (long long unsigned int) e->stats->n_messages); cprintf(client, " \"octets\": %llu,\n", (long long unsigned int) e->stats->n_bytes); cprintf(client, " \"no_route\": %llu,\n", (long long unsigned int) e->stats->n_no_route); cprintf(client, " \"routed\": %llu,\n", (long long unsigned int) e->stats->n_routed); cprintf(client, " \"dropped\": %llu,\n", (long long unsigned int) e->stats->n_dropped); cwrite(client, " \"routes\": {\n"); for_each_route_rule_do(e->set, fqd_config_http_routes, client); cwrite(client, " }\n"); cwrite(client, " },\n"); } } cwrite(client, " \"_aggregate\": {\n"); cprintf(client, " \"no_exchange\": %llu,\n", (long long unsigned int) global_counters.n_no_exchange); cprintf(client, " \"messages\": %llu,\n", (long long unsigned int) global_counters.n_messages); cprintf(client, " \"octets\": %llu,\n", (long long unsigned int) global_counters.n_bytes); cprintf(client, " \"no_route\": %llu,\n", (long long unsigned int) global_counters.n_no_route); cprintf(client, " \"routed\": %llu,\n", (long long unsigned int) global_counters.n_routed); cprintf(client, " \"dropped\": %llu,\n", (long long unsigned int) global_counters.n_dropped); cprintf(client, " \"size_dropped\": %llu\n", (long long unsigned int) global_counters.n_size_dropped); cwrite(client, " }\n"); cwrite(client, " },\n"); cwrite(client, " \"queues\": {\n"); int seen = 0; for(i=0;in_queues;i++) { if(config->queues[i]) { fqd_queue *q = config->queues[i]; fq_rk *qname = fqd_queue_name(q); if(seen++) cwrite(client, ",\n"); cprintf(client, " \"%.*s\": \n", qname->len, qname->name); fqd_queue_write_json(client->fd, q); } } cwrite(client, " }\n"); cwrite(client, "}\n"); fqd_config_release(config); } void fqd_size_dropped(uint64_t n) { ck_pr_add_64(&global_counters.n_size_dropped, n); } void fqd_exchange_messages(fqd_exchange *e, uint64_t n) { if(e) ck_pr_add_64(&e->stats->n_messages, n); ck_pr_add_64(&global_counters.n_messages, n); } void fqd_exchange_message_octets(fqd_exchange *e, uint64_t n) { if(e) ck_pr_add_64(&e->stats->n_bytes, n); ck_pr_add_64(&global_counters.n_bytes, n); } void fqd_exchange_no_route(fqd_exchange *e, uint64_t n) { if(e) ck_pr_add_64(&e->stats->n_no_route, n); ck_pr_add_64(&global_counters.n_no_route, n); } void fqd_exchange_routed(fqd_exchange *e, uint64_t n) { fq_assert(e); ck_pr_add_64(&e->stats->n_routed, n); ck_pr_add_64(&global_counters.n_routed, n); } void fqd_exchange_dropped(fqd_exchange *e, uint64_t n) { if(e) ck_pr_add_64(&e->stats->n_dropped, n); ck_pr_add_64(&global_counters.n_dropped, n); } void fqd_exchange_no_exchange(fqd_exchange *e, uint64_t n) { fq_assert(!e); ck_pr_add_64(&global_counters.n_no_exchange, n); } #define bail(...) do {fprintf(stderr, __VA_ARGS__); exit(-2);} while(0) static void setup_initial_config() { char *SQL, *errmsg = NULL; int rv; int flags = SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_EXCLUSIVE; if((rv = sqlite3_open_v2(fqd_config_path, &configdb, flags, NULL)) != 0) bail("... failed to open %s: %s\n", fqd_config_path, sqlite3_errmsg(configdb)); sqlite3_exec(configdb, "PRAGMA foreign_keys = ON", 0, 0, &errmsg); if(errmsg) bail("sqlite error: %s\n", sqlite3_errmsg(configdb)); SQL = sqlite3_mprintf( "CREATE TABLE queue (name TEXT NOT NULL PRIMARY KEY," " type TEXT NOT NULL DEFAULT \"mem\", attributes TEXT)" ); sqlite3_exec(configdb, SQL, 0, 0, &errmsg); sqlite3_free(SQL); if(errmsg && strcmp(errmsg, "table queue already exists")) bail("sqlite error: %s\n", sqlite3_errmsg(configdb)); if(errmsg) sqlite3_free(errmsg); SQL = sqlite3_mprintf( "CREATE TABLE binding ( " " exchange TEXT NOT NULL, " " queue TEXT NOT NULL, " " peermode BOOLEAN NOT NULL DEFAULT FALSE, program TEXT, " " UNIQUE(exchange, queue, peermode, program), " " FOREIGN KEY(queue) REFERENCES queue(name) " ")" ); sqlite3_exec(configdb, SQL, 0, 0, &errmsg); sqlite3_free(SQL); if(errmsg && strcmp(errmsg, "table binding already exists")) bail("sqlite error: %s\n", sqlite3_errmsg(configdb)); if(errmsg) sqlite3_free(errmsg); SQL = sqlite3_mprintf( "CREATE TABLE upstream ( " " host TEXT NOT NULL, " " port INTEGER NOT NULL DEFAULT 8765, " " source TEXT NOT NULL, " " password TEXT NOT NULL, " " exchange TEXT NOT NULL, " " program TEXT NOT NULL DEFAULT '', " " permanent_binding BOOLEAN NOT NULL DEFAULT FALSE, " " UNIQUE(host, port, source, password, exchange, program, permanent_binding) " ")" ); sqlite3_exec(configdb, SQL, 0, 0, &errmsg); sqlite3_free(SQL); if(errmsg && strcmp(errmsg, "table upstream already exists")) bail("sqlite error: %s\n", sqlite3_errmsg(configdb)); if(errmsg) sqlite3_free(errmsg); } int fqd_config_make_perm_queue(fqd_queue *q) { sqlite3_stmt *stmt; fq_rk *qname; const char *insertSQL; char qtype[1024], *attrs; fqd_queue_sprint(qtype, sizeof(qtype), q); attrs = strchr(qtype, ':'); if(attrs == NULL) return -1; *attrs++ = '\0'; insertSQL = "INSERT INTO queue VALUES(?,?,?)"; qname = fqd_queue_name(q); sqlite3_prepare_v2(configdb, insertSQL, strlen(insertSQL), &stmt, NULL); sqlite3_bind_text(stmt, 1, (char *)qname->name, qname->len, NULL); sqlite3_bind_text(stmt, 2, qtype, strlen(qtype), NULL); sqlite3_bind_text(stmt, 3, attrs, strlen(attrs), NULL); switch(sqlite3_step(stmt)) { case SQLITE_DONE: if(sqlite3_changes(configdb) > 0) { fq_debug(FQ_DEBUG_CONFIG, "Queue %.*s made permanent\n", qname->len, qname->name); fqd_queue_ref(q); } break; default: fq_debug(FQ_DEBUG_CONFIG, "Queue %.*s not made permanent: %s\n", qname->len, qname->name, sqlite3_errmsg(configdb)); break; } sqlite3_finalize(stmt); return 0; } int fqd_config_make_trans_queue(fqd_queue *q) { sqlite3_stmt *stmt; fq_rk *qname; const char *insertSQL; char qtype[1024], *attrs; fqd_queue_sprint(qtype, sizeof(qtype), q); attrs = strchr(qtype, ':'); if(attrs == NULL) return -1; *attrs++ = '\0'; insertSQL = "DELETE FROM queue WHERE name = ?"; qname = fqd_queue_name(q); sqlite3_prepare_v2(configdb, insertSQL, strlen(insertSQL), &stmt, NULL); sqlite3_bind_text(stmt, 1, (char *)qname->name, qname->len, NULL); switch(sqlite3_step(stmt)) { case SQLITE_DONE: if(sqlite3_changes(configdb) > 0) { fq_debug(FQ_DEBUG_CONFIG, "Queue %.*s made transient\n", qname->len, qname->name); fqd_queue_deref(q); break; } fq_debug(FQ_DEBUG_CONFIG, "Queue %.*s not made transient: not found\n", qname->len, qname->name); break; default: fq_debug(FQ_DEBUG_CONFIG, "Queue %.*s not made transient: %s\n", qname->len, qname->name, sqlite3_errmsg(configdb)); break; } sqlite3_finalize(stmt); return 0; } static int sql_make_queues(void *c, int n, char **row, char **col) { fqd_queue *queue; char err[1024]; fq_rk q; fq_assert(n == 3); (void)c; (void)col; q.len = strlen(row[0]); if(q.len != strlen(row[0])) return 0; memcpy(q.name, row[0], q.len); queue = fqd_queue_get(&q, row[1], row[2], sizeof(err), err); if(!queue) { fprintf(stderr, "queue(%s) -> %s\n", row[0], err); return 0; } fqd_queue_ref(queue); return 0; } static uint64_t peer_generation = 0; static int sql_make_peers(void *c, int n, char **row, char **col) { fq_rk exchange; char *host = row[0], *source = row[2], *pass = row[3], *prog = row[5]; int port = atoi(row[1]); bool perm = !strcmp(row[6],"true"); exchange.len = strlen(row[4]); if(exchange.len != strlen(row[4])) return 0; memcpy(exchange.name, row[4], exchange.len); fqd_add_peer(peer_generation, host, port, source, pass, &exchange, prog, perm); return 0; } int fqd_config_make_perm_binding(fq_rk *exchange, fqd_queue *q, int peermode, const char *program) { sqlite3_stmt *stmt; fq_rk *qname; const char *insertSQL; const char *pmstr = peermode ? "true" : "false"; char qtype[1024], *attrs; fqd_queue_sprint(qtype, sizeof(qtype), q); attrs = strchr(qtype, ':'); if(attrs == NULL) return -1; *attrs++ = '\0'; insertSQL = "INSERT INTO binding (exchange,queue,peermode,program) " "VALUES(?,?,?,?)"; qname = fqd_queue_name(q); sqlite3_prepare_v2(configdb, insertSQL, strlen(insertSQL), &stmt, NULL); sqlite3_bind_text(stmt, 1, (char *)exchange->name, exchange->len, NULL); sqlite3_bind_text(stmt, 2, (char *)qname->name, qname->len, NULL); sqlite3_bind_text(stmt, 3, pmstr, strlen(pmstr), NULL); sqlite3_bind_text(stmt, 4, program, strlen(program), NULL); switch(sqlite3_step(stmt)) { case SQLITE_DONE: if(sqlite3_changes(configdb) > 0) { fq_debug(FQ_DEBUG_CONFIG, "Binding %.*s made permanent\n", qname->len, qname->name); fqd_queue_ref(q); } break; default: fq_debug(FQ_DEBUG_CONFIG, "Binding %.*s not made permanent: %s\n", qname->len, qname->name, sqlite3_errmsg(configdb)); break; } sqlite3_finalize(stmt); return 0; } int fqd_config_make_trans_binding(fq_rk *exchange, fqd_queue *q, int peermode, const char *program) { sqlite3_stmt *stmt; fq_rk *qname; const char *delSQL; const char *pmstr = peermode ? "true" : "false"; char qtype[1024], *attrs; fqd_queue_sprint(qtype, sizeof(qtype), q); attrs = strchr(qtype, ':'); if(attrs != NULL) *attrs++ = '\0'; delSQL = "DELETE FROM binding WHERE exchange=? AND queue=? " " AND peermode=? AND program=?"; qname = fqd_queue_name(q); sqlite3_prepare_v2(configdb, delSQL, strlen(delSQL), &stmt, NULL); sqlite3_bind_text(stmt, 1, (char *)exchange->name, exchange->len, NULL); sqlite3_bind_text(stmt, 2, (char *)qname->name, qname->len, NULL); sqlite3_bind_text(stmt, 3, pmstr, strlen(pmstr), NULL); sqlite3_bind_text(stmt, 4, program, strlen(program), NULL); switch(sqlite3_step(stmt)) { case SQLITE_DONE: if(sqlite3_changes(configdb) > 0) { fq_debug(FQ_DEBUG_CONFIG, "Binding %.*s made transient\n", qname->len, qname->name); fqd_queue_ref(q); break; } fq_debug(FQ_DEBUG_CONFIG, "Binding %.*s not made transient: not found\n", qname->len, qname->name); break; default: fq_debug(FQ_DEBUG_CONFIG, "Binding %.*s not made transient: %s\n", qname->len, qname->name, sqlite3_errmsg(configdb)); break; } sqlite3_finalize(stmt); return 0; } static int sql_make_bindings(void *c, int n, char **row, char **col) { int *nbindings = (int *)c; fqd_queue *queue; fq_rk q, x; uint16_t flags; fq_assert(n == 4); (void)c; (void)col; x.len = strlen(row[0]); if(x.len != strlen(row[0])) return 0; memcpy(x.name, row[0], x.len); q.len = strlen(row[1]); if(q.len != strlen(row[1])) return 0; memcpy(q.name, row[1], q.len); BEGIN_CONFIG_MODIFY(config); queue = fqd_config_get_registered_queue(config, &q); MARK_CONFIG(config); END_CONFIG_MODIFY(); if(queue == NULL) return 1; flags = !strcmp(row[2],"true") ? FQ_BIND_PEER : 0; flags |= FQ_BIND_PERM; fqd_config_bind(&x, flags, row[3], queue, NULL); (*nbindings)++; return 0; } static void fqd_refresh_peers(bool fatal) { char *errmsg = NULL; sqlite3_exec(configdb, "SELECT host, port, source, password, exchange, program, permanent_binding FROM upstream", sql_make_peers, NULL, &errmsg ); if(errmsg) { if(fatal) bail("sqlite error: %s\n", sqlite3_errmsg(configdb)); fq_debug(FQ_DEBUG_PEER, "sqlite error: %s\n", sqlite3_errmsg(configdb)); } } static void * fqd_peer_config_maintenance(void *c) { fq_thread_setname("fqd:peer_config"); fqd_bcd_attach(); while(1) { peer_generation++; fqd_refresh_peers(0); fqd_remove_peers(peer_generation); sleep(1); } return NULL; } static void setup_config() { pthread_t tid; pthread_attr_t attr; fqd_config *config; int i, nexchanges = 0, nqueues = 0, nbindings = 0; char *errmsg = NULL; int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_EXCLUSIVE; if(sqlite3_config(SQLITE_CONFIG_SERIALIZED) != SQLITE_OK) { bail("... failed to set sqlite3 threadsafety\n"); } fprintf(stderr, "Opening configdb %s\n", fqd_config_path); if(sqlite3_open_v2(fqd_config_path, &configdb, flags, NULL)) { flags = SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_EXCLUSIVE; if(sqlite3_open_v2(fqd_config_path, &configdb, flags, NULL)) bail("... failed to open %s: %s\n", fqd_config_path, sqlite3_errmsg(configdb)); } setup_initial_config(); sqlite3_exec(configdb, "SELECT name, type, attributes FROM queue", sql_make_queues, NULL, &errmsg ); if(errmsg) bail("sqlite error: %s\n", sqlite3_errmsg(configdb)); sqlite3_exec(configdb, "SELECT exchange, queue, peermode, program FROM binding", sql_make_bindings, &nbindings, &errmsg ); if(errmsg) bail("sqlite error: %s\n", sqlite3_errmsg(configdb)); /* Summarize */ { BEGIN_CONFIG_MODIFY(tc); (void)tc; MARK_CONFIG(tc); END_CONFIG_MODIFY(); } fqd_config_wait(global_gen-1, 1000); config = fqd_config_get(); for(i=0;in_exchanges;i++) if(config->exchanges[i]) nexchanges++; for(i=0;in_queues;i++) if(config->queues[i]) nqueues++; fprintf(stderr, "Established %d exchanges, %d queues, %d bindings\n", nexchanges, nqueues, nbindings); fqd_config_release(config); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&tid, &attr, fqd_peer_config_maintenance, NULL); } ================================================ FILE: fqd_dss.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "fq.h" #include "fqd.h" #include "fqd_private.h" #include "fq_dtrace.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef linux #include #include #include static int futex(int *uaddr, int futex_op, int val, const struct timespec *timeout, int *uaddr2, int val3) { return (int)syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); } #endif #define IN_READ_BUFFER_SIZE 1024*256 #define MAX_STALL_ATTEMPTS 10000 static ck_fifo_spsc_t *work_queues; static uint32_t *work_queue_backlogs; #ifdef linux static int *work_queue_waiting; static int *work_queue_futexes; #endif static int *worker_thread_ids; static int worker_thread_count = 0; static pthread_t *worker_threads; static bool worker_thread_shutdown = false; struct incoming_message { remote_data_client *client; fq_msg *msg; }; void * fqd_worker_thread(void *arg) { struct incoming_message *m; int tindex = *(int*) arg; uint64_t empty_retries = 0; ck_fifo_spsc_t *q = &work_queues[tindex]; uint32_t *backlog = &work_queue_backlogs[tindex]; fq_thread_setname("fqd:worker/%d", tindex); fqd_bcd_attach(); while (!worker_thread_shutdown) { if (!CK_FIFO_SPSC_ISEMPTY(q)) { bool success; ck_fifo_spsc_dequeue_lock(q); success = ck_fifo_spsc_dequeue(q, &m); ck_fifo_spsc_dequeue_unlock(q); if (success) { empty_retries = 0; if (m != NULL) { ck_pr_dec_32(backlog); fq_msg *copy = fq_msg_alloc_BLANK(m->msg->payload_len); if (copy == NULL) { continue; } memcpy(copy, m->msg, sizeof(fq_msg) + m->msg->payload_len); /* reset the refcnt on the copy since the memcpy above will have overwritten it */ copy->refcnt = 1; /* the copy will be freed as normal so eliminate cleanup_stack pointer */ copy->free_fn = NULL; copy->cleanup_handle = NULL; /* we are done with the incoming message, deref */ fq_msg_deref(m->msg); fqd_inject_message(m->client, copy); fqd_remote_client_deref((remote_client *)m->client); free(m); } } } else { empty_retries++; if(empty_retries > MAX_STALL_ATTEMPTS) { #ifdef linux ck_pr_store_int(&work_queue_waiting[tindex], 1); int cv = ck_pr_load_int(&work_queue_futexes[tindex]); struct timespec timeout = { .tv_sec = 1, .tv_nsec = 0 }; int rv = futex(&work_queue_futexes[tindex], FUTEX_WAIT_PRIVATE, cv, &timeout, NULL, 0); fq_debug(FQ_DEBUG_MSG, "retries: %" PRId64 ", futex -> %d/%s\n", empty_retries, rv, strerror(errno)); #else /* slow-stall 100ms */ const struct timespec timeout = { .tv_sec = 0, .tv_nsec = 100 * 1000 * 1000 }; nanosleep(&timeout, NULL); #endif } else { /* fast-stall 1us */ const struct timespec timeout = { .tv_sec = 0, .tv_nsec = 1000 }; nanosleep(&timeout, NULL); } } } /* drain the queue before exiting */ if (!CK_FIFO_SPSC_ISEMPTY(q)) { bool success; ck_fifo_spsc_dequeue_lock(q); success = ck_fifo_spsc_dequeue(q, &m); ck_fifo_spsc_dequeue_unlock(q); if (success) { if (m != NULL) { ck_pr_dec_32(backlog); fq_msg *copy = fq_msg_alloc_BLANK(m->msg->payload_len); memcpy(copy, m->msg, sizeof(fq_msg) + m->msg->payload_len); /* the copy will be freed as normal so eliminate cleanup_stack pointer */ copy->cleanup_handle = NULL; /* we are done with the incoming message, drop it on it's cleanup stack */ fqd_inject_message(m->client, copy); fq_msg_deref(m->msg); free(m); } } } usleep(10); return NULL; } void fqd_start_worker_threads(int thread_count) { int i = 0; worker_thread_count = thread_count; work_queues = calloc(thread_count, sizeof(ck_fifo_spsc_t)); #ifdef linux work_queue_futexes = calloc(thread_count, sizeof(int)); work_queue_waiting = calloc(thread_count, sizeof(int)); #endif work_queue_backlogs = calloc(thread_count, sizeof(uint32_t)); worker_threads = calloc(thread_count, sizeof(pthread_t)); worker_thread_ids = calloc(thread_count, sizeof(int)); for ( ; i < thread_count; i++) { worker_thread_ids[i] = i; ck_fifo_spsc_init(&work_queues[i], malloc(sizeof(ck_fifo_spsc_entry_t))); pthread_create(&worker_threads[i], NULL, fqd_worker_thread, &worker_thread_ids[i]); } } void fqd_stop_worker_threads() { int i = 0; worker_thread_shutdown = true; for ( ; i < worker_thread_count; i++) { pthread_join(worker_threads[i], NULL); } } static void fqd_queue_message_process(remote_data_client *me, fq_msg *msg) { ck_fifo_spsc_t *work_queue = NULL; ck_fifo_spsc_entry_t *entry = NULL, *tofree = NULL; int tindex = 0; struct incoming_message *m = malloc(sizeof(struct incoming_message)); m->client = me; m->msg = msg; /* while we live in this queue, we ref the client so it can't be destroyed until the queue is cleared of it */ fqd_remote_client_ref((remote_client *) m->client); tindex = me->fd % worker_thread_count; ck_pr_inc_32(&work_queue_backlogs[tindex]); work_queue = &work_queues[tindex]; ck_fifo_spsc_enqueue_lock(work_queue); entry = ck_fifo_spsc_recycle(work_queue); if (entry == NULL) { ck_fifo_spsc_enqueue_unlock(work_queue); tofree = malloc(sizeof(ck_fifo_spsc_entry_t)); ck_fifo_spsc_enqueue_lock(work_queue); entry = ck_fifo_spsc_recycle(work_queue); if(entry == NULL) { entry = tofree; tofree = NULL; } } ck_fifo_spsc_enqueue(work_queue, entry, m); ck_fifo_spsc_enqueue_unlock(work_queue); #ifdef linux ck_pr_inc_int(&work_queue_futexes[tindex]); if(ck_pr_load_int(&work_queue_waiting[tindex]) == 1) { ck_pr_store_int(&work_queue_waiting[tindex], 0); futex(&work_queue_futexes[tindex], FUTEX_WAKE_PRIVATE, INT_MAX, NULL, NULL, 0); } #endif free(tofree); } static void fqd_dss_read_complete(void *closure, fq_msg *msg) { int i; remote_client *parent = closure; remote_data_client *me = parent->data; msg->hops[0] = fqd_config_get_nodeid(); if(me->mode == FQ_PROTO_DATA_MODE) { memcpy(&msg->sender, &parent->user, sizeof(parent->user)); memcpy(&msg->hops[1], &me->remote.sin_addr, sizeof(uint32_t)); } if(msg->payload_len > MAX_MESSAGE_SIZE) { me->size_dropped++; fqd_size_dropped(1); fq_msg_deref(msg); return; } me->msgs_in++; me->octets_in += (1 + msg->exchange.len) + (1 + msg->route.len) + sizeof(fq_msgid) + (4 + msg->payload_len); if(me->mode == FQ_PROTO_PEER_MODE) { me->octets_in += 1 + msg->sender.len; me->octets_in += 1; /* nhops */ for(i=0;ihops[i] != 0) { me->octets_in += 4; } } } if(FQ_MESSAGE_RECEIVE_ENABLED()) { fq_dtrace_msg_t dmsg; fq_dtrace_remote_client_t dpc; fq_dtrace_remote_data_client_t dme; DTRACE_PACK_MSG(&dmsg, msg); DTRACE_PACK_CLIENT(&dpc, parent); DTRACE_PACK_DATA_CLIENT(&dme, me); FQ_MESSAGE_RECEIVE(&dpc, &dme, &dmsg); } /* don't do any work here, just drop the message on one of N processing queues */ fqd_queue_message_process(me, msg); } static void fqd_data_driver(remote_client *parent) { remote_data_client *me = parent->data; fq_msg *inflight = NULL; size_t inflight_sofar = 0; buffered_msg_reader *ctx = NULL; int had_msgs = 1, flags, needs_write = 1; if(((flags = fcntl(me->fd, F_GETFL, 0)) == -1) || (fcntl(me->fd, F_SETFL, flags | O_NONBLOCK) == -1)) return; ctx = fq_buffered_msg_reader_alloc(me->fd, (me->mode == FQ_PROTO_PEER_MODE)); while(1) { uint32_t msgs_in = me->msgs_in, msgs_out = me->msgs_out; int rv, timeout_ms = 1000; struct pollfd pfd; pfd.fd = me->fd; pfd.events = POLLIN|POLLHUP; if(needs_write) pfd.events |= POLLOUT; pfd.revents = 0; if(parent->heartbeat_ms && parent->heartbeat_ms < timeout_ms) timeout_ms = parent->heartbeat_ms; /* if we had msgs, but aren't waiting for write, * then we set a very short timeout */ if(had_msgs && !needs_write) timeout_ms = 1; rv = poll(&pfd, 1, timeout_ms); if(rv < 0) break; had_msgs = 0; if(rv > 0 && (pfd.revents & POLLIN)) { me->last_heartbeat = me->last_activity = fq_gethrtime(); if(fq_buffered_msg_read(ctx, fqd_dss_read_complete, parent) < 0) { fq_debug(FQ_DEBUG_IO, "client read error\n"); break; } had_msgs = 1; } if(!needs_write || (rv > 0 && (pfd.revents & POLLOUT))) { fq_msg *m; needs_write = 0; haveanother: m = inflight ? inflight : parent->queue ? fqd_queue_dequeue(parent->queue) : NULL; /* If we're in peer mode, we can just toss messages the remote has seen */ if(m && me->mode == FQ_PROTO_PEER_MODE && fq_find_in_hops(me->peer_id, m) >= 0) { fq_msg_deref(m); goto haveanother; } inflight = NULL; while(m) { int written; size_t octets_out = 0; had_msgs = 1; written = fq_client_write_msg(me->fd, 1, m, inflight_sofar, &octets_out); if(written > 0) inflight_sofar += written; if(octets_out) me->octets_out += octets_out; if(written > 0 || (written < 0 && errno == EAGAIN)) { inflight = m; needs_write = 1; break; } else if(written < 0) { fq_debug(FQ_DEBUG_IO, "client write error\n"); goto broken; } if(FQ_MESSAGE_DELIVER_ENABLED()) { fq_dtrace_msg_t dmsg; fq_dtrace_remote_client_t dpc; fq_dtrace_remote_data_client_t dme; DTRACE_PACK_MSG(&dmsg, m); DTRACE_PACK_CLIENT(&dpc, parent); DTRACE_PACK_DATA_CLIENT(&dme, me); FQ_MESSAGE_DELIVER(&dpc, &dme, &dmsg); } fq_msg_deref(m); me->msgs_out++; inflight_sofar = 0; m = parent->queue ? fqd_queue_dequeue(parent->queue) : NULL; } } if(me->msgs_in != msgs_in || me->msgs_out != msgs_out) fq_debug(FQ_DEBUG_MSG, "Round.... %d in, %d out\n", me->msgs_in, me->msgs_out); } broken: if(inflight) { /* We're screwed here... we might have delivered it. so just toss it */ fq_msg_deref(inflight); } if(ctx) fq_buffered_msg_reader_free(ctx); parent->data = NULL; fq_debug(FQ_DEBUG_IO, "data path from client ended: %s\n", parent->pretty); } void fqd_data_subscription_server(remote_data_client *client) { int len; char buf[260]; fqd_config *config; remote_client *parent; fq_rk key; fq_debug(FQ_DEBUG_CONN, "--> dss thread [%s]\n", client->mode == FQ_PROTO_DATA_MODE ? "client" : "peer"); if((len = fq_read_short_cmd(client->fd, sizeof(key.name), key.name)) < 0) return; if(len > (int)sizeof(key.name)) return; key.len = len; fq_rk_to_hex(buf, sizeof(buf), &key); fq_debug(FQ_DEBUG_CONN, "data conn w/ key:\n%s\n", buf); config = fqd_config_get(); parent = fqd_config_get_registered_client(config, &key); fqd_config_release(config); if(!parent) return; if(parent->data) return; fq_thread_setname("fqd:dss:%s", parent->pretty); ck_pr_cas_ptr(&parent->data, NULL, client); if(parent->data != client) { fq_debug(FQ_DEBUG_CONN, "%s dss double gang rejected\n", parent->pretty); return; } if(FQ_CLIENT_AUTH_DATA_ENABLED()) { fq_dtrace_remote_data_client_t dclient; DTRACE_PACK_DATA_CLIENT(&dclient, client); FQ_CLIENT_AUTH_DATA(&dclient); } fqd_remote_client_ref(parent); fqd_data_driver(parent); fq_clear_message_cleanup_stack(); fqd_remote_client_deref(parent); fq_debug(FQ_DEBUG_CONN, "<-- dss thread\n"); } ================================================ FILE: fqd_dyn_sample.c ================================================ /* * Copyright (c) 2014 Circonus, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "fqd.h" #include bool fqd_route_prog__sample__d(fq_msg *, int, valnode_t *); bool fqd_route_prog__sample__d(fq_msg *m, int nargs, valnode_t *args) { (void)m; assert(nargs == 1); assert(args[0].value_type == RP_VALUE_DOUBLE); if(drand48() < args[0].value.d) return true; return false; } ================================================ FILE: fqd_http.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "fqd.h" #include "fqd_private.h" #include "http_parser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include const char *fqd_web_path = VARLIBFQDIR "/web"; const char *index_file = "index.html"; static remote_data_client web_data_client = { .refcnt = 1, .fd = -1, .pretty = "_web_data", .mode = FQ_PROTO_DATA_MODE }; void fqd_http_set_root(const char *newpath) { char path[PATH_MAX]; if(realpath(newpath, path) != NULL) fqd_web_path = strdup(path); } static inline int ends_with(const char *str, const char *end) { int elen = strlen(end); int slen = strlen(str); if(slen < elen) return 0; return !strcasecmp(str + slen - elen, end); } static const char * fqd_http_mime_type(const char *url) { if(ends_with(url, ".js")) return "text/javascript"; if(ends_with(url, ".json")) return "application/json"; if(ends_with(url, ".css")) return "text/css"; if(ends_with(url, ".jpg") || ends_with(url, ".jpeg")) return "image/jpeg"; if(ends_with(url, ".gif")) return "image/gif"; if(ends_with(url, ".png")) return "image/png"; if(ends_with(url, "/") || ends_with(url, ".html") || ends_with(url, ".htm")) return "text/html"; return "application/octet-stream"; } struct http_req { remote_client *client; enum http_method method; char *url; char *qs; char *status; char *fldname; char *error; ck_ht_t headers; ck_ht_t query_params; size_t body_len; size_t body_read; fq_msg *msg; enum { HTTP_EXPECT_NONE = 0, HTTP_EXPECT_CONTINUE, HTTP_EXPECT_SENT } expect_continue; int close; }; static int fqd_http_submit_msg(struct http_req *req); static int fqd_http_add_checkpoint(struct http_req *req); static int fqd_http_remove_checkpoint(struct http_req *req); static int fqd_http_reset_to_checkpoint(struct http_req *req); static void * ht_malloc(size_t r) { return malloc(r); } static void ht_free(void *p, size_t b, bool r) { (void)b; (void)r; free(p); return; } static struct ck_malloc my_alloc = { .malloc = ht_malloc, .free = ht_free }; static void http_req_clean(struct http_req *req) { ck_ht_entry_t *cursor; ck_ht_iterator_t iterator = CK_HT_ITERATOR_INITIALIZER; while(ck_ht_next(&req->headers, &iterator, &cursor)) { ck_ht_hash_t hv; char *key = ck_ht_entry_key(cursor); char *value = ck_ht_entry_value(cursor); ck_ht_hash(&hv, &req->headers, key, strlen(key)); ck_ht_remove_spmc(&req->headers, hv, cursor); free(key); free(value); } ck_ht_iterator_init(&iterator); while(ck_ht_next(&req->query_params, &iterator, &cursor)) { ck_ht_hash_t hv; char *key = ck_ht_entry_key(cursor); char *value = ck_ht_entry_value(cursor); ck_ht_hash(&hv, &req->headers, key, strlen(key)); ck_ht_remove_spmc(&req->headers, hv, cursor); free(key); free(value); } if(req->url) free(req->url); /* req->qs isn't allocated */ if(req->status) free(req->status); if(req->fldname) free(req->fldname); if(req->error) free(req->error); if(req->msg) fq_msg_deref(req->msg); req->url = NULL; req->qs = NULL; req->status = NULL; req->fldname = NULL; req->error = NULL; req->body_len = 0; req->body_read = 0; req->msg = NULL; req->expect_continue = HTTP_EXPECT_NONE; } /* split incoming string by '=' and store the left as key and right as value in the table */ static void store_kv(ck_ht_t *table, char *kv_string) { ck_ht_entry_t entry; ck_ht_hash_t hv; const char *key = kv_string; char *eq = strchr(kv_string, '='); if(eq) eq[0] = '\0'; const char *val = eq ? eq + 1 : ""; ck_ht_hash(&hv, table, key, strlen(key)); ck_ht_entry_set(&entry, hv, strdup(key), strlen(key), strdup(val)); if(ck_ht_set_spmc(table, hv, &entry)) { fq_debug(FQ_DEBUG_HTTP, ".store_kv -> added (%s, %s)\n", key, val); } /* be non-destructive */ if(eq) eq[0] = '='; } static const char * get_ht_value(ck_ht_t *table, const char *key) { ck_ht_entry_t entry; ck_ht_hash_t hv; ck_ht_hash(&hv, table, key, strlen(key)); ck_ht_entry_key_set(&entry, key, strlen(key)); if (ck_ht_get_spmc(table, hv, &entry)) { return ck_ht_entry_value(&entry); } return NULL; } static int fqd_http_message_url(http_parser *p, const char *at, size_t len) { struct http_req *req = p->data; req->url = malloc(len+1); strlcpy(req->url, at, len+1); req->qs = strchr(req->url, '?'); if(req->qs) *(req->qs++) = '\0'; if (req->qs != NULL) { char *trailing = req->qs; for (uint32_t i = 0; i < strlen(req->qs); i++) { if (req->qs[i] == '&') { req->qs[i] = '\0'; store_kv(&req->query_params, trailing); req->qs[i] = '&'; trailing = &req->qs[i+1]; } } store_kv(&req->query_params, trailing); } fq_debug(FQ_DEBUG_HTTP, ".on_url -> '%s'\n", req->url); fq_debug(FQ_DEBUG_HTTP, ".on_url query_string -> '%s'\n", req->qs); return 0; } static int fqd_http_message_status(http_parser *p, const char *at, size_t len) { struct http_req *req = p->data; req->status = malloc(len+1); strlcpy(req->status, at, len+1); fq_debug(FQ_DEBUG_HTTP, ".on_status -> '%s'\n", req->status); return 0; } static int fqd_http_message_body(http_parser *p, const char *at, size_t len) { struct http_req *req = p->data; fq_debug(FQ_DEBUG_HTTP, ".on_data -> %zu\n", len); if(req->msg) { if(req->body_read + len > req->body_len) { req->error = strdup("excessive data received"); return 1; } memcpy(req->msg->payload + req->body_read, at, len); req->body_read += len; } return 0; } static int fqd_http_message_header_field(http_parser *p, const char *at, size_t len) { char *cp; struct http_req *req = p->data; req->fldname = malloc(len+1); strlcpy(req->fldname, at, len+1); for(cp=req->fldname;*cp;cp++) *cp = tolower(*cp); fq_debug(FQ_DEBUG_HTTP, ".on_header_field -> '%s'\n", req->fldname); return 0; } static const char * fqd_http_header(struct http_req *req, const char *hdr) { ck_ht_entry_t entry; ck_ht_hash_t hv; int hdrlen = strlen(hdr); ck_ht_hash(&hv, &req->headers, hdr, hdrlen); ck_ht_entry_set(&entry, hv, hdr, hdrlen, NULL); if(ck_ht_get_spmc(&req->headers, hv, &entry)) { return ck_ht_entry_value(&entry); } return NULL; } static int fqd_http_message_header_value(http_parser *p, const char *at, size_t len) { struct http_req *req = p->data; ck_ht_entry_t entry; ck_ht_hash_t hv; char *val; if(!req->fldname) return -1; val = malloc(len+1); strlcpy(val, at, len+1); fq_debug(FQ_DEBUG_HTTP, ".on_header_value -> '%s'\n", val); ck_ht_hash(&hv, &req->headers, req->fldname, strlen(req->fldname)); ck_ht_entry_set(&entry, hv, req->fldname, strlen(req->fldname), val); if(ck_ht_set_spmc(&req->headers, hv, &entry) && !ck_ht_entry_empty(&entry)) { char *key = ck_ht_entry_key(&entry); char *value = ck_ht_entry_value(&entry); if(key && key != req->fldname) free(key); if(value && value != val) free(value); } if(!strcmp(req->fldname, "expect") && !strcasecmp(val,"100-continue")) req->expect_continue = HTTP_EXPECT_CONTINUE; req->fldname = NULL; return 0; } static int fqd_http_message_headers_complete(http_parser *p) { #define EXPECT_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n" const char *clen; static char *expect_continue = EXPECT_CONTINUE; static int expect_continue_len = sizeof(EXPECT_CONTINUE)-1; struct http_req *req = p->data; if(req->expect_continue == HTTP_EXPECT_CONTINUE) { while(write(req->client->fd, expect_continue, expect_continue_len) == -1 && errno == EINTR); req->expect_continue = HTTP_EXPECT_SENT; } clen = fqd_http_header(req, "content-length"); if(!strcmp(req->url, "/submit") && clen) { req->body_len = atoi(clen); req->msg = fq_msg_alloc_BLANK(req->body_len); } return 0; } #define cwrite(client, str) write(client->fd, str, strlen(str)) static void fqd_http_jsend(remote_client *client, const char *status, const char *fmt, ...) { char error[1024]; char scratch[1124]; const char *headers = "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n"; va_list argp; error[0] = 0; scratch[0] = 0; va_start(argp, fmt); vsnprintf(error, sizeof(error), fmt, argp); va_end(argp); while(write(client->fd, headers, strlen(headers)) == -1 && errno == EINTR); cwrite(client, "{\n"); cwrite(client, " \"status\": \""); cwrite(client, status); cwrite(client, "\",\n"); snprintf(scratch, sizeof(scratch), " \"message\": \"%s\"\n", error); cwrite(client, scratch); cwrite(client, "}\n"); } #define fqd_http_error_json_f(client, fmt, ...) fqd_http_jsend(client, "error", fmt, __VA_ARGS__) #define fqd_http_success_json_f(client, fmt, ...) fqd_http_jsend(client, "success", fmt, __VA_ARGS__) #define fqd_http_error_json(client, fmt) fqd_http_error_json_f(client, "%s", fmt) #define fqd_http_success_json(client, fmt) fqd_http_success_json_f(client, "%s", fmt) static int fqd_http_message_complete(http_parser *p) { char file[PATH_MAX], rfile[PATH_MAX]; struct http_req *req = p->data; fq_debug(FQ_DEBUG_HTTP, ".on_complete ->\n"); /* programmatic endpoints */ if(!strcmp(req->url, "/stats.json")) { fqd_config_http_stats(req->client); req->close = 1; return 0; } if(!strcmp(req->url, "/submit")) { fqd_http_submit_msg(req); return 0; } if(!strcmp(req->url, "/shutdown")) { const char *allowed = getenv("HTTP_SHUTDOWN"); if(allowed && !strcmp(allowed, "1")) exit(0); } if (!strcmp(req->url, "/add_checkpoint")) { fqd_http_add_checkpoint(req); return 0; } if (!strcmp(req->url, "/remove_checkpoint")) { fqd_http_remove_checkpoint(req); return 0; } if (!strcmp(req->url, "/reset_to_checkpoint")) { fqd_http_reset_to_checkpoint(req); return 0; } /* Files */ if(strlen(fqd_web_path)) { int fd, rv; int drlen = strlen(fqd_web_path); char http_header[1024]; void *contents; struct stat st; strlcpy(file, fqd_web_path, sizeof(file)); if(file[drlen-1] != '/') strlcat(file, "/", sizeof(file)); strlcat(file, req->url, sizeof(file)); if(file[strlen(file) - 1] == '/') strlcat(file, index_file, sizeof(file)); if(realpath(file, rfile) == NULL) goto not_found; if(strncmp(rfile, fqd_web_path, drlen)) goto not_found; if(rfile[drlen] != '/' && rfile[drlen + 1] != '/') goto not_found; fd = open(rfile, O_RDONLY); if(fd < 0) goto not_found; while((rv = fstat(fd, &st)) < 0 && errno == EINTR); if(rv < 0) { close(fd); goto not_found; } contents = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); close(fd); snprintf(http_header, sizeof(http_header), "HTTP/1.0 200 OK\r\nContent-Length: %lu\r\n" "Content-Type: %s\r\n\r\n", (long int)st.st_size, fqd_http_mime_type(req->url)); drlen = strlen(http_header); while(write(req->client->fd, http_header, drlen) == -1 && errno == EINTR); while(write(req->client->fd, contents, st.st_size) == -1 && errno == EINTR); munmap(contents, st.st_size); return 0; } /* 404 */ not_found: { const char *headers = "HTTP/1.0 404 OK\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n"; while(write(req->client->fd, headers, strlen(headers)) == -1 && errno == EINTR); req->close = 1; } return 0; } static int fqd_http_add_checkpoint(struct http_req *req) { fqd_config *config = fqd_config_get(); const char *cpname = get_ht_value(&req->query_params, "cpname"); const char *qname = get_ht_value(&req->query_params, "qname"); const char *chkptid = get_ht_value(&req->query_params, "chkptid"); fq_rk qn; fq_rk_from_str(&qn, qname); fqd_queue *queue = fqd_config_get_registered_queue(config, &qn); if (strcmp(cpname, "fq") == 0) { fqd_http_error_json(req->client, "'fq' is a reserved name, cannot be used for a checkpoint name"); req->close = 1; return 0; } if (queue == NULL) { fqd_http_error_json_f(req->client, "Cannot find registered queue '%s'", qname); req->close = 1; return 0; } /* do we really want this restriction? */ if (queue->permanent == false) { fqd_http_error_json(req->client, "Checkpoints on ephemeral queues not supported"); req->close = 1; return 0; } /* check points only supported on disk queue */ if (strcmp(queue->impl->name, "disk") != 0) { fqd_http_error_json(req->client, "Checkpoints on memory queues not supported"); req->close = 1; return 0; } /* validate chkptid format */ char ckid[48] = {0}; strncpy(ckid, chkptid, sizeof(ckid)-1); const char *log_string = ckid; char *sep = strchr(ckid, ':'); if (sep == NULL) { fqd_http_error_json(req->client, "'chkptid' must be of format: [0-9]*:[0-9]*"); req->close = 1; return 0; } sep[0] = '\0'; const char *marker_string = sep + 1; uint32_t log = atoi(log_string); uint32_t marker = atoi(marker_string); fq_msgid id = { .id.u32.p1 = log, .id.u32.p2 = marker }; int rv = queue->impl->add_checkpoint(queue->impl_data, cpname, &id); if (rv == -1) { fqd_http_error_json(req->client, "'chkptid' is out of range of the queue"); req->close = 1; return 0; } if (rv == -2) { fqd_http_error_json(req->client, "Failed to set checkpoint"); req->close = 1; return 0; } if (rv < 0) { fqd_http_error_json(req->client, "Unknown error"); req->close = 1; return 0; } fqd_http_success_json(req->client, "Checkpoint added"); req->close = 1; fq_debug(FQ_DEBUG_HTTP, ".on_complete -> add_checkpoint on [%s] for queue [%s] and id [%s]\n", cpname, qname, chkptid); return 0; } static int fqd_http_remove_checkpoint(struct http_req *req) { fqd_config *config = fqd_config_get(); const char *cpname = get_ht_value(&req->query_params, "cpname"); const char *qname = get_ht_value(&req->query_params, "qname"); fq_rk qn; fq_rk_from_str(&qn, qname); fqd_queue *queue = fqd_config_get_registered_queue(config, &qn); if (strcmp(cpname, "fq") == 0) { fqd_http_error_json(req->client, "'fq' is a reserved name, cannot be used for a checkpoint name"); req->close = 1; return 0; } if (queue == NULL) { fqd_http_error_json_f(req->client, "Cannot find registered queue '%s'", qname); req->close = 1; return 0; } int rv = queue->impl->remove_checkpoint(queue->impl_data, cpname); if (rv == -1) { fqd_http_error_json(req->client, "Checkpoint does not exist"); req->close = 1; return 0; } if (rv < 0) { fqd_http_error_json(req->client, "Unknown error"); req->close = 1; return 0; } fqd_http_success_json(req->client, "Checkpoint removed"); req->close = 1; fq_debug(FQ_DEBUG_HTTP, ".on_complete -> remove_checkpoint on [%s] for queue [%s]\n", cpname, qname); return 0; } static int fqd_http_reset_to_checkpoint(struct http_req *req) { fqd_config *config = fqd_config_get(); const char *cpname = get_ht_value(&req->query_params, "cpname"); const char *qname = get_ht_value(&req->query_params, "qname"); fq_rk qn; fq_rk_from_str(&qn, qname); fqd_queue *queue = fqd_config_get_registered_queue(config, &qn); if (strcmp(cpname, "fq") == 0) { fqd_http_error_json(req->client, "'fq' is a reserved name, cannot be used for a checkpoint name"); req->close = 1; return 0; } if (queue == NULL) { fqd_http_error_json_f(req->client, "Cannot find registered queue '%s'", qname); req->close = 1; return 0; } int rv = queue->impl->reset_checkpoint(queue->impl_data, cpname); if (rv == -1) { fqd_http_error_json(req->client, "Checkpoint does not exist"); req->close = 1; return 0; } if (rv < 0) { fqd_http_error_json(req->client, "Unknown error"); req->close = 1; return 0; } fqd_http_success_json_f(req->client, "'%s' reset to checkpoint '%s'", qname, cpname); req->close = 1; fq_debug(FQ_DEBUG_HTTP, ".on_complete -> reset_to_checkpoint on [%s] for queue [%s]\n", cpname, qname); return 0; } static int fqd_http_submit_msg(struct http_req *req) { remote_data_client tmp_data_client = { .refcnt = 1, .fd = -1, .pretty = "_web_data", .mode = FQ_PROTO_DATA_MODE }; const char *hdrval; int len, slen; char http_header[1024]; char scratch[1024]; const char *status = "200 OK"; #define SUBERR(a) do { req->error = strdup(a); goto error; } while(0) if(!req->msg) SUBERR("no message"); if(req->msg->payload_len != req->body_len) SUBERR("short message"); hdrval = fqd_http_header(req, "x-fq-sender"); if(!hdrval) hdrval = "_web"; if(strlen(hdrval) > MAX_RK_LEN) SUBERR("sender too long"); req->msg->sender.len = strlen(hdrval); memcpy(req->msg->sender.name, hdrval, req->msg->sender.len); hdrval = fqd_http_header(req, "x-fq-route"); if(!hdrval) SUBERR("missing route"); if(strlen(hdrval) > MAX_RK_LEN) SUBERR("route too long"); req->msg->route.len = strlen(hdrval); memcpy(req->msg->route.name, hdrval, req->msg->route.len); hdrval = fqd_http_header(req, "x-fq-exchange"); if(!hdrval) SUBERR("missing exchange"); if(strlen(hdrval) > MAX_RK_LEN) SUBERR("exchange too long"); req->msg->exchange.len = strlen(hdrval); memcpy(req->msg->exchange.name, hdrval, req->msg->exchange.len); if(req->error) goto error; fq_msg_id(req->msg, NULL); fqd_inject_message(&tmp_data_client, req->msg); req->msg = NULL; /* not my problem anymore */ snprintf(scratch, sizeof(scratch), "{\"routed\":%u,\"dropped\":%u," "\"no_route\":%u,\"no_exchange\":%u}\n", tmp_data_client.routed, tmp_data_client.dropped, tmp_data_client.no_route, tmp_data_client.no_exchange); #define BUMP(a) ck_pr_add_32(&web_data_client.a, tmp_data_client.a) BUMP(msgs_in); BUMP(octets_in); BUMP(msgs_out); BUMP(octets_out); BUMP(routed); BUMP(dropped); BUMP(no_route); BUMP(no_exchange); goto out; error: status = "500 ERROR"; snprintf(scratch, sizeof(scratch), "{ \"error\": \"%s\" }\n", req->error); out: slen = strlen(scratch); snprintf(http_header, sizeof(http_header), "HTTP/1.0 %s\r\nContent-Length: %lu\r\n" "Content-Type: application/json\r\n\r\n", status, (long int)slen); len = strlen(http_header); while(write(req->client->fd, http_header, len) == -1 && errno == EINTR); while(write(req->client->fd, scratch, slen) == -1 && errno == EINTR); return 0; } void fqd_http_loop(remote_client *client, uint32_t bytes) { ssize_t rv, len = 4; char inbuff[4096 * 16]; struct http_req req = { .client = client }; http_parser parser; http_parser_settings settings; fq_thread_setname("fqd:http:%s", client->pretty); memset(&parser, 0, sizeof(parser)); memset(&settings, 0, sizeof(settings)); fq_assert(ck_ht_init(&req.headers, CK_HT_MODE_BYTESTRING, NULL, &my_alloc, 8, lrand48())); fq_assert(ck_ht_init(&req.query_params, CK_HT_MODE_BYTESTRING, NULL, &my_alloc, 8, lrand48())); http_parser_init(&parser, HTTP_REQUEST); http_parser_settings_init(&settings); parser.data = &req; settings.on_url = fqd_http_message_url; settings.on_status = fqd_http_message_status; settings.on_body = fqd_http_message_body; settings.on_header_field = fqd_http_message_header_field; settings.on_header_value = fqd_http_message_header_value; settings.on_headers_complete = fqd_http_message_headers_complete; settings.on_message_complete = fqd_http_message_complete; memcpy(inbuff, &bytes, 4); while((rv = http_parser_execute(&parser, &settings, inbuff, len)) == len && !req.close) { struct pollfd pfd; pfd.fd = client->fd; pfd.events = POLLIN|POLLHUP; /* We actually don't care why we woke up, so ignore return */ (void)poll(&pfd, 1, 0); len = recv(client->fd, inbuff, sizeof(inbuff), 0); fq_debug(FQ_DEBUG_HTTP, "recv() -> %d\n", (int)len); if(len <= 0) break; } http_req_clean(&req); ck_ht_destroy(&req.headers); ck_ht_destroy(&req.query_params); (void)bytes; } ================================================ FILE: fqd_listener.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fq.h" #include "fqd.h" #include "fqd_private.h" #include "fq_dtrace.h" void fqd_remote_client_ref(remote_client *r) { ck_pr_inc_uint(&r->refcnt); } bool fqd_remote_client_deref(remote_client *r) { bool zero; ck_pr_dec_uint_zero(&r->refcnt, &zero); fq_debug(FQ_DEBUG_CONN, "deref client -> %u%s\n", r->refcnt, zero ? " dropping" : ""); if(zero) { close(r->fd); free(r); return true; } return false; } static void service_connection(remote_anon_client *client) { uint32_t cmd; uint32_t peer_id = 0; int rv, on = 1; char buf[40]; buf[0] = '\0'; inet_ntop(AF_INET, &client->remote.sin_addr, buf, sizeof(buf)); fq_thread_setname("fqd:c:%s", client->pretty); snprintf(client->pretty, sizeof(client->pretty), "(pre-auth)@%s:%d", buf, ntohs(client->remote.sin_port)); gettimeofday(&client->connect_time, NULL); fq_debug(FQ_DEBUG_CONN, "client(%s) connected\n", client->pretty); /* We do nothing if this fails. */ (void)setsockopt(client->fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); while((rv = read(client->fd, &cmd, sizeof(cmd))) == -1 && errno == EINTR); if(rv != 4) goto disconnect; if(FQ_CLIENT_DISCONNECT_ENABLED()) { fq_dtrace_remote_anon_client_t dc; DTRACE_PACK_ANON_CLIENT(&dc, client); FQ_CLIENT_CONNECT(&dc, ntohl(cmd)); } fq_debug(FQ_DEBUG_CONN, "read(%d) cmd -> %08x\n", client->fd, ntohl(cmd)); switch(ntohl(cmd)) { case FQ_PROTO_CMD_MODE: { remote_client *newc = calloc(1, sizeof(*newc)); memcpy(newc, client, sizeof(*client)); newc->refcnt = 1; client->fd=-1; fqd_command_and_control_server(newc); (void)fqd_remote_client_deref((remote_client *)newc); } break; case FQ_PROTO_PEER_MODE: while((rv = read(client->fd, &peer_id, sizeof(peer_id))) == -1 && errno == EINTR); if(rv != 4) goto disconnect; /* FALLTHROUGH */ case FQ_PROTO_OLD_PEER_MODE: cmd = FQ_PROTO_PEER_MODE; /* FALLTHROUGH */ case FQ_PROTO_DATA_MODE: { remote_data_client *newc = calloc(1, sizeof(*newc)); memcpy(newc, client, sizeof(*client)); newc->mode = ntohl(cmd); newc->peer_id = peer_id; newc->refcnt=1; client->fd=-1; fqd_data_subscription_server(newc); (void)fqd_remote_client_deref((remote_client *)newc); } break; case FQ_PROTO_HTTP_GET: case FQ_PROTO_HTTP_HEAD: case FQ_PROTO_HTTP_POST: case FQ_PROTO_HTTP_PUT: { remote_client *newc = calloc(1, sizeof(*newc)); memcpy(newc, client, sizeof(*client)); newc->refcnt = 1; client->fd=-1; fqd_http_loop(newc, cmd); (void)fqd_remote_client_deref((remote_client *)newc); } break; default: fq_debug(FQ_DEBUG_CONN, "client protocol violation in initial cmd\n"); close(client->fd); client->fd = -1; break; } disconnect: if(FQ_CLIENT_DISCONNECT_ENABLED()) { fq_dtrace_remote_anon_client_t dc; DTRACE_PACK_ANON_CLIENT(&dc, client); FQ_CLIENT_DISCONNECT(&dc, ntohl(cmd)); } close(client->fd); free(client); } static void * conn_handler(void *vc) { fqd_bcd_attach(); while(1) { fq_thread_setname("fqd:c:idle"); remote_anon_client *client = fqd_ccs_dequeue_work(); service_connection(client); } return NULL; } typedef struct fqd_ccs_work_queue { remote_anon_client *client; struct fqd_ccs_work_queue *next; } fqd_ccs_work_queue_t; static pthread_mutex_t fqd_ccs_work_queue_lock; static pthread_cond_t fqd_ccs_work_queue_cv; static fqd_ccs_work_queue_t *fqd_ccs_work_queue; static int fqd_ccs_idle_threads = 0; static void fqd_ccs_enqueue_work(remote_anon_client *client) { fqd_ccs_work_queue_t *node = calloc(sizeof(*client), 1); int err; node->client = client; err = pthread_mutex_lock(&fqd_ccs_work_queue_lock); if(err != 0) { fprintf(stderr, "pthread_mutex_lock: %s\n", strerror(err)); exit(2); } if(fqd_ccs_idle_threads < 1) { pthread_t client_task; assert(pthread_create(&client_task, NULL, conn_handler, NULL) == 0); } node->next = (fqd_ccs_work_queue_t *)fqd_ccs_work_queue; fqd_ccs_work_queue = node; err = pthread_mutex_unlock(&fqd_ccs_work_queue_lock); if(err != 0) { fprintf(stderr, "pthread_mutex_unlock: %s\n", strerror(err)); exit(2); } err = pthread_cond_signal(&fqd_ccs_work_queue_cv); if(err != 0) { fprintf(stderr, "pthread_cond_signal: %s\n", strerror(err)); exit(2); } } remote_anon_client * fqd_ccs_dequeue_work(void) { remote_anon_client *client = NULL; int err; err = pthread_mutex_lock(&fqd_ccs_work_queue_lock); if(err != 0) { fprintf(stderr, "pthread_mutex_lock: %s\n", strerror(err)); exit(2); } fqd_ccs_idle_threads++; while(fqd_ccs_work_queue == NULL) { err = pthread_cond_wait(&fqd_ccs_work_queue_cv, &fqd_ccs_work_queue_lock); if(err != 0) { fprintf(stderr, "pthread_cond_wait: %s\n", strerror(err)); exit(2); } } client = fqd_ccs_work_queue->client; fqd_ccs_work_queue_t *tofree = (fqd_ccs_work_queue_t *)fqd_ccs_work_queue; fqd_ccs_work_queue = fqd_ccs_work_queue->next; fqd_ccs_idle_threads--; err = pthread_mutex_unlock(&fqd_ccs_work_queue_lock); if(err != 0) { fprintf(stderr, "pthread_mutex_unlock: %s\n", strerror(err)); exit(2); } free(tofree); return client; } int fqd_listener(const char *host, unsigned short port) { int fd; remote_anon_client *client = NULL; unsigned int on = 1; struct sockaddr_in laddr; pthread_mutex_init(&fqd_ccs_work_queue_lock, NULL); pthread_cond_init(&fqd_ccs_work_queue_cv, NULL); memset(&laddr, 0, sizeof(laddr)); laddr.sin_family = AF_INET; laddr.sin_addr.s_addr = INADDR_ANY; if(host && inet_pton(AF_INET, host, &laddr.sin_addr) != 0) { return -1; } laddr.sin_port = htons(port); fd = socket(AF_INET, SOCK_STREAM #ifdef SOCK_CLOEXEC |SOCK_CLOEXEC #endif , 0); if(fd < 0) return -1; if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0 || #ifdef SO_REUSEPORT setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) != 0 || #endif bind(fd, (struct sockaddr *)&laddr, sizeof(laddr)) < 0 || listen(fd, 16) < 0) { close(fd); return -1; } while(1) { struct sockaddr_in raddr; socklen_t raddr_len; if(client == NULL) client = calloc(1, sizeof(*client)); raddr_len = sizeof(raddr); client->fd = accept(fd, (struct sockaddr *)&client->remote, &raddr_len); if(client->fd < 0) continue; fq_keepalive_fd(client->fd, 10, 5, 2); client->refcnt = 1; fqd_ccs_enqueue_work(client); client = NULL; } return -1; } ================================================ FILE: fqd_peer.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "fq.h" #include "fqd.h" /* Peer connections are currently only permanent, in memory * bounded, public, drop-semantic queues. */ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; /* These are infrequent calls, big ol' lock is fine */ typedef struct { uint64_t gen; uint32_t route_id; fq_rk exchange; char *prog; bool perm; bool disabled; bool disable_requested; } peer_binding_info; typedef struct peer_connection { fq_client client; char *host; int port; char *user; char *pass; bool online_and_bound; int n_bindings; peer_binding_info **bindings; remote_data_client *stats_holder; struct peer_connection *next; } fqd_peer_connection; static fqd_peer_connection list_head; static int peercmp(fqd_peer_connection *a, fqd_peer_connection *b) { int rv; if(a->host == NULL || b->host == NULL) return -1; if(a->user == NULL || b->user == NULL) return -1; if(a->pass == NULL || b->pass == NULL) return -1; if(0 != (rv = strcmp(a->host, b->host))) return rv; if(0 != (rv = strcmp(a->user, b->user))) return rv; if(0 != (rv = strcmp(a->pass, b->pass))) return rv; if(a->port == b->port) return 0; if(a->port < b->port) return -1; return 1; } static void fqd_peer_auth_hook(fq_client conn, int authed) { int i; fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); fq_debug(FQ_DEBUG_PEER, "authed(%s:%d) -> %d\n", peer->host, peer->port, authed); if(authed || !peer) return; peer->online_and_bound = true; pthread_mutex_lock(&lock); for(i=0;in_bindings;i++) { peer_binding_info *bi = peer->bindings[i]; fq_bind_req *breq; breq = calloc(1, sizeof(*breq)); memcpy(&breq->exchange, &bi->exchange, sizeof(bi->exchange)); breq->flags = FQ_BIND_PEER | (bi->perm ? FQ_BIND_PERM : 0); breq->program = strdup(bi->prog); fq_client_bind(conn, breq); fq_debug(FQ_DEBUG_PEER, "bindreq(%s:%d) %.*s/%s\n", peer->host, peer->port, bi->exchange.len, bi->exchange.name, bi->prog); } pthread_mutex_unlock(&lock); } static void fqd_peer_bind_hook(fq_client conn, fq_bind_req *breq) { int i; fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); if(!peer) return; fq_debug(FQ_DEBUG_PEER, "bindresp(%u) %.*s/%s\n", breq->out__route_id, breq->exchange.len, breq->exchange.name, breq->program); if(breq->out__route_id == FQ_BIND_ILLEGAL) { return; } pthread_mutex_lock(&lock); for(i=0;in_bindings;i++) { peer_binding_info *bi = peer->bindings[i]; if(!fq_rk_cmp(&bi->exchange, &breq->exchange) && !strcmp(bi->prog, breq->program)) { bi->route_id = breq->out__route_id; break; } } pthread_mutex_unlock(&lock); } static void fqd_peer_unbind_hook(fq_client conn, fq_unbind_req *breq) { /* We *could* find the route and unset the route_id, but * I see no point in doing that work. */ } static void fqd_peer_cleanup_hook(fq_client conn) { int i; fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); for(i=0;in_bindings;i++) { peer_binding_info *bi = peer->bindings[i]; free(bi->prog); } if(peer->bindings) free(peer->bindings); if(peer->host) free(peer->host); if(peer->user) free(peer->user); if(peer->pass) free(peer->pass); if(peer->stats_holder) free(peer->stats_holder); free(peer); } static void fqd_peer_disconnect_hook(fq_client conn) { fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); if(peer) { fq_debug(FQ_DEBUG_PEER, "disconnect from peer(%s:%d)\n", peer->host, peer->port); peer->online_and_bound = false; } } static bool fqd_peer_message_hook(fq_client conn, fq_msg *m) { int i; uint32_t me = fqd_config_get_nodeid(); fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); for(i=MAX_HOPS-1;i>0;i--) { if(m->hops[i] == me) { fq_debug(FQ_DEBUG_PEER, "recieved looped message"); return true; } m->hops[i] = m->hops[i-1]; } m->hops[0] = fqd_config_get_nodeid(); /* route this */ fq_msg_ref(m); fqd_inject_message(peer ? peer->stats_holder : NULL, m); return true; } static fq_hooks fqd_peer_hooks = { .version = FQ_HOOKS_V4, .auth = fqd_peer_auth_hook, .bind = fqd_peer_bind_hook, .unbind = fqd_peer_unbind_hook, .sync = 0, .message = fqd_peer_message_hook, .cleanup = fqd_peer_cleanup_hook, .disconnect = fqd_peer_disconnect_hook }; static void peerlog(fq_client conn, const char *str) { fqd_peer_connection *peer; peer = fq_client_get_userdata(conn); fq_debug(FQ_DEBUG_PEER, "error %s:%d -> %s\n", peer->host, peer->port, str); } static void fqd_peer_start(fqd_peer_connection *peer) { fq_debug(FQ_DEBUG_PEER, "starting peer(%s:%d)\n", peer->host, peer->port); fq_client_init(&peer->client, fqd_config_get_nodeid(), peerlog); fq_client_set_userdata(peer->client, peer); fq_client_creds(peer->client, peer->host, peer->port, peer->user, peer->pass); fq_client_hooks(peer->client, &fqd_peer_hooks); fq_client_connect(peer->client); } static void fqd_peer_stop(fqd_peer_connection *peer) { fq_debug(FQ_DEBUG_PEER, "stopping peer(%s:%d)\n", peer->host, peer->port); fq_client_destroy(peer->client); } static void fqd_peer_online_bind(fqd_peer_connection *peer, peer_binding_info *bi) { if(peer->online_and_bound == true) { fq_bind_req *breq; fq_debug(FQ_DEBUG_PEER, "binding peer(%s:%d) exchange:\"%.*s\"\n", peer->host, peer->port, bi->exchange.len, bi->exchange.name); breq = calloc(1, sizeof(*breq)); memcpy(&breq->exchange, &bi->exchange, sizeof(bi->exchange)); breq->flags = FQ_BIND_PEER | FQ_BIND_PERM; breq->program = strdup(bi->prog); fq_client_bind(peer->client, breq); } } static void fqd_peer_online_unbind(fqd_peer_connection *peer, peer_binding_info *bi) { fq_unbind_req *ureq; if(!bi->disabled || bi->disable_requested || !peer->online_and_bound || bi->route_id == FQ_BIND_ILLEGAL) return; fq_debug(FQ_DEBUG_PEER, "unbinding peer(%s:%d) exchange:\"%.*s\"\n", peer->host, peer->port, bi->exchange.len, bi->exchange.name); ureq = calloc(1, sizeof(*ureq)); memcpy(&ureq->exchange, &bi->exchange, sizeof(bi->exchange)); ureq->route_id = bi->route_id; fq_client_unbind(peer->client, ureq); } int fqd_add_peer(uint64_t gen, const char *host, int port, const char *user, const char *pass, fq_rk *exchange, const char *prog, bool perm) { bool added_peer = false, added_binding = false; fqd_peer_connection *peer, speer; peer_binding_info *bi = NULL; int i; memset(&speer, 0, sizeof(speer)); speer.host = (char *)host; speer.port = port; speer.user = (char *)user; speer.pass = (char *)pass; pthread_mutex_lock(&lock); /* Get a peer */ for(peer = list_head.next; peer; peer = peer->next) { if(!peercmp(peer, &speer)) break; } if(!peer) { peer = calloc(1, sizeof(*peer)); peer->host = strdup(speer.host); peer->port = speer.port; peer->user = strdup(speer.user); peer->pass = strdup(speer.pass); peer->next = list_head.next; list_head.next = peer; added_peer = true; } /* Get a binding */ for(i=0; in_bindings; i++) { bi = peer->bindings[i]; if(!fq_rk_cmp(exchange, &bi->exchange) && !strcmp(prog, bi->prog)) break; } if(i == peer->n_bindings) { bi = calloc(1, sizeof(*bi)); memcpy(&bi->exchange, exchange, sizeof(*exchange)); bi->route_id = FQ_BIND_ILLEGAL; bi->prog = strdup(prog); peer->n_bindings++; peer->bindings = realloc(peer->bindings, peer->n_bindings * sizeof(bi)); peer->bindings[peer->n_bindings - 1] = bi; added_binding = true; } /* This will force a rebind */ if(perm != bi->perm) added_binding = true; bi->perm = perm; bi->gen = gen; if(added_peer) fqd_peer_start(peer); else if(added_binding) fqd_peer_online_bind(peer, bi); pthread_mutex_unlock(&lock); return added_binding ? 0 : -1; } /* Remove all peers older than the specified generation */ int fqd_remove_peers(uint64_t current_gen) { fqd_peer_connection *prev; int turndown = 0; pthread_mutex_lock(&lock); for(prev = &list_head; prev && prev->next; prev = prev->next) { fqd_peer_connection *peer = prev->next; bool useful = false; int i; for(i=0; in_bindings; i++) { if(peer->bindings[i]->gen >= current_gen) useful = true; else if(!peer->bindings[i]->disabled) { peer->bindings[i]->disabled = true; turndown++; } } if(!useful) { prev->next = peer->next; peer->next = NULL; fqd_peer_stop(peer); } else { for(i=0; in_bindings; i++) { fqd_peer_online_unbind(peer, peer->bindings[i]); } } } pthread_mutex_unlock(&lock); return turndown; } int fqd_remove_peer(const char *host, int port, const char *user, const char *pass, fq_rk *exchange, const char *prog) { fqd_peer_connection *peer, speer; peer_binding_info *bi = NULL; int i; memset(&speer, 0, sizeof(speer)); speer.host = (char *)host; speer.port = port; speer.user = (char *)user; speer.pass = (char *)pass; pthread_mutex_lock(&lock); /* Get a peer */ for(peer = list_head.next; peer; peer = peer->next) { if(!peercmp(peer, &speer)) { for(i=0; in_bindings; i++) { bi = peer->bindings[i]; if(bi->disabled == false && fq_rk_cmp(exchange, &bi->exchange) && !strcmp(prog, bi->prog)) { bi->disabled = true; break; } } break; } } if(bi && peer->n_bindings == 1) { /* Special case, we must remove this so no one else can see it */ fqd_peer_connection *prev = &list_head; for(prev = &list_head; prev->next; prev = prev->next) { if(!peercmp(prev->next, peer)) { prev->next = peer->next; peer->next = NULL; break; } } } if(bi) { /* We found something and it is our job to turn it down */ if(peer->n_bindings == 1) fqd_peer_stop(peer); else fqd_peer_online_unbind(peer, bi); } pthread_mutex_unlock(&lock); return bi ? 0 : -1; } ================================================ FILE: fqd_private.h ================================================ /* * Copyright (c) 2013 Circonus, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef FQD_PRIVATE_H #define FQD_PRIVATE_H #include "fq.h" #define MAX_QUEUE_CLIENTS 16 struct fqd_route_stats { /* These are just estimates */ uint64_t invocations; uint32_t avg_ns; uint32_t refcnt; }; struct fqd_route_rule { fq_rk prefix; int match_maxlen; char *program; rulenode_t *compiled_program; uint32_t route_id; bool permanent; int peermode; fqd_queue *queue; struct fqd_route_stats *stats; struct fqd_route_rule *next; }; struct prefix_jumptable { enum { JUMPTABLE, RULETABLE } tabletype; struct fqd_route_rule *rules; struct { uint64_t pattern; uint64_t checkbits; struct prefix_jumptable *jt; } *pats; int pat_len; }; struct fqd_route_rules { struct prefix_jumptable master; }; struct fqd_queue { fq_rk name; bool permanent; bool private; remote_client *downstream[MAX_QUEUE_CLIENTS]; /* referenced by: routes and connections */ queue_policy_t policy; uint32_t backlog_limit; uint32_t backlog; uint32_t dropped_to; /* These are only use for FQ_POLICY_BLOCK */ pthread_cond_t cv; pthread_mutex_t lock; uint32_t refcnt; fqd_queue_impl *impl; fqd_queue_impl_data *impl_data; }; extern int for_each_route_rule_do(struct fqd_route_rules *set, int (*f)(struct fqd_route_rule *, int, void *), void *closure); void fqd_bcd_attach(void); void fqd_start_worker_threads(int thread_count); void fqd_stop_worker_threads(void); void fqd_routemgr_add_handle(void *); void global_function_register(const char *name, void (*f)(void)); void global_functions_init(const char *dir); void fqd_route_load_module(const char *libexecdir, const char *file, const char *ext); void fq_thread_setname(const char *format, ...); remote_anon_client *fqd_ccs_dequeue_work(void); #if defined(linux) || defined(__linux) || defined(__linux__) #if ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 38)) static inline size_t strlcpy(char *dst, const char *src, size_t size) { if(size > 0) { strncpy(dst, src, size-1); dst[size-1] = '\0'; return size; } dst[0] = '\0'; return 0; } static inline size_t strlcat(char *dst, const char *src, size_t size) { int dl = strlen(dst); int sz = size-dl-1; if(sz >= 0) { strncat(dst, src, sz); dst[dl+sz] = '\0'; } return dl+strlen(src); } #endif /* glibc */ #endif /* linux */ #endif ================================================ FILE: fqd_prog.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #define _GNU_SOURCE #include #include #include #include #include #include #include "fqd.h" #include "fqd_private.h" bool fqd_route_prog__true__(fq_msg *, int, valnode_t *); bool fqd_route_prog__route_contains__s(fq_msg *, int, valnode_t *); bool fqd_route_prog__payload_prefix__s(fq_msg *, int, valnode_t *); void fqd_route_load_module(const char *libexecdir, const char *file, const char *ext) { char path[PATH_MAX]; if(*file != '/') { snprintf(path, sizeof(path), "%s/%s%s", libexecdir, file, ext ? ext : ""); file = path; } void *handle = dlopen(file, RTLD_NOW|RTLD_GLOBAL); if(handle == NULL) { fprintf(stderr, "Failed to load %s: %s\n", file, dlerror()); } fqd_routemgr_add_handle(handle); } void global_functions_init(const char *libexecdir) { #define GFR(a) global_function_register(#a, (void (*)(void))a) GFR(fqd_route_prog__true__); GFR(fqd_route_prog__route_contains__s); GFR(fqd_route_prog__payload_prefix__s); #undef GFR DIR *dir = opendir(libexecdir); if(!dir) return; struct dirent *de; while(NULL != (de = readdir(dir))) { char path[PATH_MAX]; struct stat sb; int namelen = strlen(de->d_name); if(namelen < 4 || memcmp(de->d_name + namelen - 3, ".so", 3)) continue; snprintf(path, sizeof(path), "%s/%s", libexecdir, de->d_name); if(stat(path, &sb) == -1) continue; if((sb.st_mode & S_IFMT) == S_IFREG || (sb.st_mode & S_IFMT) == S_IFLNK) { fqd_route_load_module(libexecdir, de->d_name, NULL); } } closedir(dir); } bool fqd_route_prog__true__(fq_msg *m, int nargs, valnode_t *args) { fq_assert(nargs == 0); (void)m; (void)nargs; (void)args; return true; } bool fqd_route_prog__route_contains__s(fq_msg *m, int nargs, valnode_t *args) { int flen; fq_assert(nargs == 1); fq_assert(args[0].value_type == RP_VALUE_STRING); flen = strlen(args[0].value.s); if(flen > m->route.len) return false; return memmem(m->route.name, m->route.len, args[0].value.s, flen) != NULL; } bool fqd_route_prog__payload_prefix__s(fq_msg *m, int nargs, valnode_t *args) { uint32_t flen; fq_assert(nargs == 1); fq_assert(args[0].value_type == RP_VALUE_STRING); flen = strlen(args[0].value.s); if(flen > m->payload_len) return false; if(memcmp(args[0].value.s, m->payload, flen) == 0) return true; return false; } bool fqd_route_prog__payload_contains__s(fq_msg *m, int nargs, valnode_t *args) { int flen; fq_assert(nargs == 1); fq_assert(args[0].value_type == RP_VALUE_STRING); flen = strlen(args[0].value.s); if(flen > m->payload_len) return false; return memmem(m->payload, m->payload_len, args[0].value.s, flen) != NULL; } ================================================ FILE: fqd_queue.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "fqd.h" #include "fqd_private.h" #include "ck_pr.h" #include "fq_dtrace.h" #define DEFAULT_QUEUE_LIMIT 16384 #define cprintf(fd, fmt, ...) do { \ char scratch[1024]; \ int len; \ len = snprintf(scratch, sizeof(scratch), fmt, __VA_ARGS__); \ write(fd, scratch, len); \ } while(0) #define cwrite(fd, str) write(fd, str, strlen(str)) int fqd_queue_write_json(int fd, fqd_queue *q) { int i, seen = 0; cwrite(fd, "{\n"); cprintf(fd, " \"private\": %s,\n", q->private ? "true" : "false"); cprintf(fd, " \"type\": \"%s\",\n", q->impl->name); cprintf(fd, " \"policy\": \"%s\",\n", (q->policy == FQ_POLICY_DROP) ? "drop" : "block"); cprintf(fd, " \"backlog_limit\": %d,\n", q->backlog_limit); cprintf(fd, " \"backlog\": %d,\n", q->backlog); cprintf(fd, " \"dropped_to\": %d,\n", q->dropped_to); cprintf(fd, " \"refcnt\": %d,\n", q->refcnt); cwrite(fd, " \"clients\": ["); for(i=0;idownstream[i]; if(c) { char buf[INET6_ADDRSTRLEN+1]; buf[0] = '\0'; inet_ntop(AF_INET, &c->remote.sin_addr, buf, sizeof(buf)); if(seen++) cwrite(fd, " ,{\n"); else cwrite(fd, " {\n"); cprintf(fd, " \"user\": \"%.*s\"\n", c->user.len, c->user.name); cprintf(fd, " ,\"remote_addr\": \"%s\"\n", buf); cprintf(fd, " ,\"remote_port\": \"%d\"\n", ntohs(c->remote.sin_port)); if(c->data) { cprintf(fd, " ,\"mode\": \"%s\"\n", (c->data->mode == FQ_PROTO_DATA_MODE) ? "client" : "peer"); cprintf(fd, " ,\"no_exchange\": \"%u\"\n", c->data->no_exchange); cprintf(fd, " ,\"no_route\": \"%u\"\n", c->data->no_route); cprintf(fd, " ,\"routed\": \"%u\"\n", c->data->routed); cprintf(fd, " ,\"dropped\": \"%u\"\n", c->data->dropped); cprintf(fd, " ,\"size_dropped\": \"%u\"\n", c->data->size_dropped); cprintf(fd, " ,\"msgs_in\": \"%u\"\n", c->data->msgs_in); cprintf(fd, " ,\"msgs_out\": \"%u\"\n", c->data->msgs_out); cprintf(fd, " ,\"octets_in\": \"%u\"\n", c->data->octets_in); cprintf(fd, " ,\"octets_out\": \"%u\"\n", c->data->octets_out); } cwrite(fd, " }\n"); } } cwrite(fd, "]\n"); cwrite(fd, "}"); return 0; } int fqd_queue_sprint(char *buf, int len, fqd_queue *q) { return snprintf(buf, len, "%s:%s,%s,backlog=%d", q->impl->name, q->private ? "private" : "public", (q->policy == FQ_POLICY_DROP) ? "drop" : "block", q->backlog_limit); } void fqd_queue_dtrace_pack(fq_dtrace_queue_t *d, fqd_queue *s) { d->name = (char *)s->name.name; d->private = s->private; d->policy = s->policy; d->type = (char *)s->impl->name; } static void fqd_queue_free(fqd_queue *q); fq_rk * fqd_queue_name(fqd_queue *q) { return &q->name; } void fqd_queue_enqueue(fqd_queue *q, fq_msg *m, int *dropped) { while(1) { uint32_t backlog; if(q->backlog_limit) { backlog = ck_pr_load_uint(&q->backlog); if(backlog < q->backlog_limit) break; } if(q->policy == FQ_POLICY_DROP) { if(dropped) (*dropped)++; ck_pr_inc_32(&q->dropped_to); if(FQ_QUEUE_DROP_ENABLED()) { fq_dtrace_msg_t dm; fq_dtrace_queue_t dq; DTRACE_PACK_MSG(&dm, m); DTRACE_PACK_QUEUE(&dq, q); FQ_QUEUE_DROP(&dq, &dm); } return; } else { pthread_mutex_lock(&q->lock); again: if(q->backlog_limit) { backlog = ck_pr_load_uint(&q->backlog); if(backlog < q->backlog_limit) { pthread_mutex_unlock(&q->lock); break; } } if(FQ_QUEUE_BLOCK_ENABLED()) { fq_dtrace_msg_t dm; fq_dtrace_queue_t dq; DTRACE_PACK_MSG(&dm, m); DTRACE_PACK_QUEUE(&dq, q); FQ_QUEUE_BLOCK(&dq, &dm); } pthread_cond_wait(&q->cv, &q->lock); goto again; } } ck_pr_inc_32(&q->backlog); if(FQ_QUEUE_ENQUEUE_ENABLED()) { fq_dtrace_msg_t dm; fq_dtrace_queue_t dq; DTRACE_PACK_MSG(&dm, m); DTRACE_PACK_QUEUE(&dq, q); FQ_QUEUE_ENQUEUE(&dq, &dm); } q->impl->enqueue(q->impl_data, m); } fq_msg * fqd_queue_dequeue(fqd_queue *q) { fq_msg *msg = q->impl->dequeue(q->impl_data); if(msg) { ck_pr_dec_32(&q->backlog); if(q->policy == FQ_POLICY_BLOCK) pthread_cond_signal(&q->cv); } return msg; } int fqd_queue_register_client(fqd_queue *q, remote_client *c) { int i; int max_clients = q->private ? 1 : MAX_QUEUE_CLIENTS; fqd_queue_ref(q); fqd_remote_client_ref(c); for(i=0;idownstream[i] == NULL) { if(ck_pr_cas_ptr(&q->downstream[i], NULL, c) == true) { #ifdef DEBUG fq_debug(FQ_DEBUG_CONFIG, "%.*s adding %s\n", q->name.len, q->name.name, c->pretty); #endif return 0; } } } if(fqd_remote_client_deref(c)) abort(); if(fqd_queue_deref(q)) abort(); return -1; } bool fqd_queue_deregister_client(fqd_queue *q, remote_client *c) { int i; bool found = false; int max_clients = q->private ? 1 : MAX_QUEUE_CLIENTS; for(i=0;idownstream[i] == c) { q->downstream[i] = NULL; fq_debug(FQ_DEBUG_CONFIG, "%.*s dropping %s\n", q->name.len, q->name.name, c->pretty); if(fqd_remote_client_deref(c)) abort(); if(fqd_queue_deref(q)) abort(); if(found) abort(); found = true; } } if(q->permanent) return false; for(i=0;idownstream[i]) return false; return true; } int fqd_queue_cmp(const fqd_queue *a, const fqd_queue *b) { return fq_rk_cmp(&a->name, &b->name); } void fqd_queue_ref(fqd_queue *q) { fq_stacktrace(FQ_DEBUG_MEM,"fqd_queue_ref",1,2); ck_pr_inc_uint(&q->refcnt); fq_debug(FQ_DEBUG_MEM, "Q[%.*s] -> refcnt:%u\n", q->name.len, q->name.name, q->refcnt); } bool fqd_queue_deref(fqd_queue *q) { bool zero; fq_stacktrace(FQ_DEBUG_MEM,"fqd_queue_deref",1,2); ck_pr_dec_uint_zero(&q->refcnt, &zero); fq_debug(FQ_DEBUG_MEM, "Q[%.*s] -> refcnt:%u\n", q->name.len, q->name.name, q->refcnt); if(zero) { FQ_QUEUE_DESTROY(q->name.len, (char *)q->name.name); fq_debug(FQ_DEBUG_CONFIG, "dropping queue(%p) %.*s\n", (void *)q, q->name.len, q->name.name); fqd_queue_free(q); return true; } return false; } uint32_t fqd_queue_get_backlog_limit(fqd_queue *q) { return q->backlog_limit; } void fqd_queue_set_backlog_limit(fqd_queue *q, uint32_t l) { q->backlog_limit = l; } queue_policy_t fqd_queue_get_policy(fqd_queue *q) { return q->policy; } void fqd_queue_set_policy(fqd_queue *q, queue_policy_t p) { q->policy = p; } static void fqd_queue_free(fqd_queue *q) { pthread_mutex_destroy(&q->lock); pthread_cond_destroy(&q->cv); q->impl->dispose(&q->name, q->impl_data); free(q); } fqd_queue * fqd_queue_get(fq_rk *qname, const char *type, const char *params, int errlen, char *err) { bool error = false, created = false; fqd_queue *q = NULL; fqd_queue *nq = NULL; fqd_config *config; char *params_copy, *lastsep = NULL, *tok; int permanent = -1; /* unset */ bool private = true; queue_policy_t policy = FQ_POLICY_DROP; uint32_t backlog_limit = DEFAULT_QUEUE_LIMIT; fqd_queue_impl *queue_impl = &fqd_queue_mem_impl; if(!type) type = FQ_DEFAULT_QUEUE_TYPE; if(!strcmp(type, "disk")) { queue_impl = &fqd_queue_jlog_impl; } else if(strcmp(type, "mem")) { snprintf(err, errlen, "invalid queue type: %s", type); return NULL; } params_copy = strdup(params ? params : ""); if(!params_copy) { snprintf(err, errlen, "memory exhaustion"); return NULL; } for (tok = strtok_r(params_copy, ",", &lastsep); tok; tok = strtok_r(NULL, ",", &lastsep)) { if(!strcmp(tok, "private")) private = true; else if(!strcmp(tok, "public")) private = false; else if(!strcmp(tok, "drop")) policy = FQ_POLICY_DROP; else if(!strcmp(tok, "block")) policy = FQ_POLICY_BLOCK; else if(!strncmp(tok, "backlog=", 8)) { backlog_limit = atoi(tok + 8); } else if(!strcmp(tok, "permanent")) permanent = 1; else if(!strcmp(tok, "transient")) permanent = 0; else { error = true; snprintf(err, errlen, "invalid queue param: %s", tok); break; } if(lastsep == NULL) break; } free(params_copy); if(error) return NULL; config = fqd_config_get(); nq = q = fqd_config_get_registered_queue(config, qname); if(q) { if(q->private) { int i; for(i=0; idownstream[i]) { snprintf(err, errlen, "requested queue is private and in use\n"); fqd_config_release(config); return NULL; } } } } else { nq = calloc(1, sizeof(*nq)); nq->refcnt = 0; nq->private = private; nq->policy = policy; nq->backlog_limit = backlog_limit; if(permanent == 1) nq->permanent = true; pthread_mutex_init(&nq->lock, NULL); pthread_cond_init(&nq->cv, NULL); memcpy(&nq->name, qname, sizeof(*qname)); nq->impl = queue_impl; nq->impl_data = nq->impl->setup(qname, &nq->backlog); if(nq->impl_data == NULL) { snprintf(err, errlen, "initialization of %s queue failed", nq->impl->name); fqd_queue_free(nq); nq = q = NULL; } } fqd_config_release(config); if(nq != NULL) { q = fqd_config_register_queue(nq, NULL); if(nq != q) { fqd_queue_free(nq); } else { created = true; } } if(q && q->impl != queue_impl) { snprintf(err, errlen, "requested type %s, queue is %s", type, q->impl->name); q = NULL; } else if(q && q->private != private) { snprintf(err, errlen, "requested %s, queue is %s", private ? "private" : "public", q->private ? "private" : "public"); q = NULL; } else if(q && q->policy != policy) { snprintf(err, errlen, "request %s, queue is %s", (policy == FQ_POLICY_DROP) ? "drop" : "block", (q->policy == FQ_POLICY_DROP) ? "drop" : "block"); q = NULL; } /* We don't actually enforce a backlog difference */ if(q && permanent >= 0) { if(!permanent) { fqd_config_make_trans_queue(q); q->permanent = false; } else { fqd_config_make_perm_queue(q); q->permanent = true; } } if(q) { (void)created; FQ_QUEUE_CREATE_SUCCESS(qname->len, (char *)qname->name, created, (char *)q->impl->name, q->private, q->policy); } else { (void)err; FQ_QUEUE_CREATE_FAILURE(qname->len, (char *)qname->name, err); } return q; } ================================================ FILE: fqd_queue_jlog.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "fqd.h" #include #include #include #include #include #include #include #include #include #include "ck_pr.h" struct queue_jlog { bool auto_chkpt; uint32_t nenqueued; uint32_t last_seen_nenqueued; char *qpath; jlog_ctx *writer; jlog_ctx *reader; jlog_id start; jlog_id finish; jlog_id last_dequeued; int count; uuid_t uuid; /* * If we create a transient queue, diconnect and reconnect with the same * transient queue then we expose a condition where the original queue * has not been reaped (as it exists in older config version, but the * new queue has the same name. * * For in-memory queues, this is no issue. Because the jlog implementation * stores the queue on disk (and must find it later on restart) there is a * chance that the disposal of an old queue would wipe the new queue's * on-disk structure rendering it completely busted. * * When a jlog queue is initially setup here, we generate a uuid and store * that in the path/.sig file. If we have a race such as above, then * the disposal will come along and notice that the .sig does not match * its uuid. This indicates to the disposal that another queue owns the * on-disk structure and it should skip the unlink/rmdir removal. */ uint32_t errors; }; static void queue_jlog_enqueue(fqd_queue_impl_data f, fq_msg *m) { struct queue_jlog *d = (struct queue_jlog *)f; size_t wlen; wlen = offsetof(fq_msg, payload) + m->payload_len; if(jlog_ctx_write(d->writer, m, wlen) != 0) { ck_pr_inc_uint(&d->errors); } ck_pr_inc_uint(&d->nenqueued); } static fq_msg *queue_jlog_dequeue(fqd_queue_impl_data f) { struct queue_jlog *d = (struct queue_jlog *)f; jlog_message msg; fq_msg *m; if(d->count == 0 && d->last_seen_nenqueued == d->nenqueued) return NULL; retry: if(d->count <= 0) { d->count = jlog_ctx_read_interval(d->reader, &d->start, &d->finish); fq_debug(FQ_DEBUG_IO, "jlog read batch count -> %d\n", d->count); if(d->count < 0) { char idxfile[PATH_MAX]; fq_debug(FQ_DEBUG_IO, "jlog_ctx_read_interval: %s\n", jlog_ctx_err_string(d->reader)); switch (jlog_ctx_err(d->reader)) { case JLOG_ERR_FILE_CORRUPT: case JLOG_ERR_IDX_CORRUPT: jlog_repair_datafile(d->reader, d->start.log); jlog_repair_datafile(d->reader, d->start.log + 1); fq_debug(FQ_DEBUG_IO, "jlog reconstructed, deleting corresponding index.\n"); STRSETDATAFILE(d->reader, idxfile, d->start.log); strncpy(idxfile + strlen(idxfile), INDEX_EXT, sizeof(idxfile) - strlen(idxfile)); unlink(idxfile); STRSETDATAFILE(d->reader, idxfile, d->start.log + 1); strncpy(idxfile + strlen(idxfile), INDEX_EXT, sizeof(idxfile) - strlen(idxfile)); unlink(idxfile); break; default: break; } } if(d->count <= 0) return NULL; } if(jlog_ctx_read_message(d->reader, &d->start, &msg) == -1) { d->count = 0; return NULL; } if(d->last_dequeued.log > d->start.log || (d->last_dequeued.log == d->start.log && d->last_dequeued.marker > d->start.marker)) { d->count--; JLOG_ID_ADVANCE(&d->start); goto retry; } if(msg.mess_len < sizeof(fq_msg)-1) m = NULL; else { off_t expected_len; uint32_t payload_len; m = (fq_msg *)msg.mess; memcpy(&payload_len, &m->payload_len, sizeof(m->payload_len)); expected_len = offsetof(fq_msg, payload) + payload_len; if(expected_len != msg.mess_len) m = NULL; else { m = malloc(expected_len); memcpy(m, msg.mess, expected_len); m->sender_msgid.id.u32.p3 = d->start.log; m->sender_msgid.id.u32.p4 = d->start.marker; } } d->count--; fq_debug(FQ_DEBUG_IO, "jlog batch count -> %d\n", d->count); if(d->count == 0) { if(d->auto_chkpt) { jlog_ctx_read_checkpoint(d->reader, &d->start); } } d->last_dequeued = d->start; JLOG_ID_ADVANCE(&d->start); ck_pr_inc_uint(&d->last_seen_nenqueued); return m; } /* * returns -1 when the incoming checkpoint is out of range of the log * returns -2 if there was an error actually setting the checkpoint */ static int queue_log_add_checkpoint(fqd_queue_impl_data data, const char *name, const fq_msgid *id) { struct queue_jlog *d = (struct queue_jlog *)data; jlog_id jid = { .log = id->id.u32.p1, .marker = id->id.u32.p2 }; /* ensure the checkpoint makes sense */ jlog_id first = { .log = 0, .marker = 0 }; jlog_id last = { .log = 0, .marker = 0 }; jlog_ctx_first_log_id(d->reader, &first); jlog_ctx_last_log_id(d->reader, &last); if (! (jid.log >= first.log && jid.log <= last.log && jid.marker >= first.marker && jid.marker <= last.marker)) { return -1; } char **subs; int sub_count = jlog_ctx_list_subscribers(d->reader, &subs); int have_it = 0; for (int i = 0; i < sub_count; i++) { have_it += strcmp(subs[i], name) == 0 ? 1 : 0; } if (have_it == 0) { jlog_ctx_add_subscriber(d->reader, name, JLOG_BEGIN); } if (jlog_ctx_read_checkpoint(d->reader, &jid) == -1) { /* If we failed to checkpoint we are in a situation where the 'add_subscriber' call above put them at the beginning of the log so we have to remove the subscriber if we just added them However, if they already existed and had a previous good checkpoint, leave it alone */ if (have_it == 0) { jlog_ctx_remove_subscriber(d->reader, name); } return -2; } return 0; } /* * return -1 if the subscriber doesn't exist * return 0 on success */ static int queue_log_remove_checkpoint(fqd_queue_impl_data data, const char *name) { struct queue_jlog *d = (struct queue_jlog *)data; if (jlog_ctx_remove_subscriber(d->reader, name) == 0) { return -1; } return 0; } /* * return -1 if the subscriber doesn't exist * return -2 if we can't reset the checkpoint * return 0 on success */ static int queue_log_reset_to_checkpoint(fqd_queue_impl_data data, const char *name) { struct queue_jlog *d = (struct queue_jlog *)data; char **subs; int sub_count = jlog_ctx_list_subscribers(d->reader, &subs); int have_it = 0; for (int i = 0; i < sub_count; i++) { have_it += strcmp(subs[i], name) == 0 ? 1 : 0; } if (have_it == 0) { return -1; } jlog_id checkpoint; if (jlog_get_checkpoint(d->reader, name, &checkpoint) == -1) { return -2; } if (jlog_ctx_read_checkpoint(d->reader, &checkpoint) == -1) { return -2; } return 0; } static int write_sig(struct queue_jlog *d) { char sigfile[PATH_MAX]; int fd; snprintf(sigfile, sizeof(sigfile), "%s/.sig", d->qpath); fd = open(sigfile, O_CREAT|O_TRUNC|O_WRONLY, 0640); if(fd < 0) return -1; write(fd, d->uuid, 16); close(fd); return 0; } static int read_sig(struct queue_jlog *d, uuid_t out) { char sigfile[PATH_MAX]; int fd, rv; snprintf(sigfile, sizeof(sigfile), "%s/.sig", d->qpath); fd = open(sigfile, O_RDONLY); if(fd < 0) return -1; rv = read(fd, out, 16); close(fd); return (rv == 16) ? 0 : -1; } static fqd_queue_impl_data queue_jlog_setup(fq_rk *qname, uint32_t *count) { char qpath[PATH_MAX]; jlog_id chkpt; struct queue_jlog *d; d = calloc(1, sizeof(*d)); d->auto_chkpt = true; fqd_config_construct_queue_path(qpath, sizeof(qpath), qname); d->qpath = strdup(qpath); d->writer = jlog_new(d->qpath); jlog_ctx_set_pre_commit_buffer_size(d->writer, 1024 * 1024); jlog_ctx_set_multi_process(d->writer, 0); jlog_ctx_set_use_compression(d->writer, 1); if(jlog_ctx_open_writer(d->writer) != 0) { jlog_ctx_close(d->writer); d->writer = jlog_new(d->qpath); jlog_ctx_set_pre_commit_buffer_size(d->writer, 1024 * 1024); jlog_ctx_set_multi_process(d->writer, 0); jlog_ctx_set_use_compression(d->writer, 1); if(jlog_ctx_init(d->writer) != 0) { fq_debug(FQ_DEBUG_IO, "jlog init: %s\n", jlog_ctx_err_string(d->writer)); goto bail; } jlog_ctx_close(d->writer); d->writer = jlog_new(d->qpath); jlog_ctx_set_pre_commit_buffer_size(d->writer, 1024 * 1024); jlog_ctx_set_multi_process(d->writer, 0); jlog_ctx_set_use_compression(d->writer, 1); if(jlog_ctx_open_writer(d->writer) != 0) { fq_debug(FQ_DEBUG_IO, "jlog writer: %s\n", jlog_ctx_err_string(d->writer)); goto bail; } } /* 128MB journal chunks */ jlog_ctx_alter_journal_size(d->writer, 128 * 1024 * 1024); d->reader = jlog_new(d->qpath); if(jlog_get_checkpoint(d->reader, "fq", &chkpt) != 0) { if(jlog_ctx_add_subscriber(d->reader, "fq", JLOG_BEGIN) != 0) { fq_debug(FQ_DEBUG_IO, "jlog add sub: %s\n", jlog_ctx_err_string(d->reader)); goto bail; } } if(jlog_ctx_open_reader(d->reader, "fq") != 0) { fq_debug(FQ_DEBUG_IO, "jlog: %s\n", jlog_ctx_err_string(d->reader)); goto bail; } uuid_generate(d->uuid); write_sig(d); *count = d->count = jlog_ctx_read_interval(d->reader, &d->start, &d->finish); (void)qname; return d; bail: if(d->writer) jlog_ctx_close(d->writer); if(d->reader) jlog_ctx_close(d->reader); free(d->qpath); free(d); return NULL; } static int multi_unlink(const char *path, const struct stat *sb, int d, struct FTW *f) { (void)sb; (void)f; if(d == FTW_D) rmdir(path); else unlink(path); return 0; } static void queue_jlog_dispose(fq_rk *qname, fqd_queue_impl_data f) { struct queue_jlog *d = (struct queue_jlog *)f; uuid_t exist; (void)qname; if (d == NULL) { /* there was likely a total failure to init this queue type due to file system permissions, bail */ return; } uuid_clear(exist); read_sig(d, exist); if(uuid_compare(d->uuid, exist) == 0) { /* This is my jlog queue ... I can delete it */ fq_debug(FQ_DEBUG_IO, "jlog: removing %s\n", d->qpath); nftw(d->qpath, multi_unlink, 2, FTW_DEPTH); rmdir(d->qpath); } free(d); } fqd_queue_impl fqd_queue_jlog_impl = { .name = "disk", .setup = queue_jlog_setup, .enqueue = queue_jlog_enqueue, .dequeue = queue_jlog_dequeue, .dispose = queue_jlog_dispose, .add_checkpoint = queue_log_add_checkpoint, .remove_checkpoint = queue_log_remove_checkpoint, .reset_checkpoint = queue_log_reset_to_checkpoint }; ================================================ FILE: fqd_queue_mem.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "fqd.h" #include #include struct queue_mem { uint32_t qlen; ck_fifo_spsc_t q; ck_fifo_spsc_entry_t *qhead; }; static void queue_mem_enqueue(fqd_queue_impl_data f, fq_msg *m) { struct queue_mem *d = (struct queue_mem *)f; ck_fifo_spsc_enqueue_lock(&d->q); ck_fifo_spsc_entry_t *fifo_entry = ck_fifo_spsc_recycle(&d->q); if (fifo_entry == NULL) { fifo_entry = malloc(sizeof(ck_fifo_spsc_entry_t)); } fq_msg_ref(m); ck_fifo_spsc_enqueue(&d->q, fifo_entry, m); ck_fifo_spsc_enqueue_unlock(&d->q); ck_pr_inc_uint(&d->qlen); } static fq_msg *queue_mem_dequeue(fqd_queue_impl_data f) { struct queue_mem *d = (struct queue_mem *)f; fq_msg *m = NULL; ck_fifo_spsc_dequeue_lock(&d->q); if(ck_fifo_spsc_dequeue(&d->q, &m) == true) { ck_fifo_spsc_dequeue_unlock(&d->q); ck_pr_dec_uint(&d->qlen); return m; } ck_fifo_spsc_dequeue_unlock(&d->q); return NULL; } static fqd_queue_impl_data queue_mem_setup(fq_rk *qname, uint32_t *count) { struct queue_mem *d; d = calloc(1, sizeof(*d)); d->qhead = malloc(sizeof(ck_fifo_spsc_entry_t)); *count = 0; ck_fifo_spsc_init(&d->q, d->qhead); (void)qname; return d; } static void queue_mem_dispose(fq_rk *qname, fqd_queue_impl_data f) { struct queue_mem *d = (struct queue_mem *)f; fq_msg *m; (void)qname; while(NULL != (m = queue_mem_dequeue(d))) { fq_msg_deref(m); } ck_fifo_spsc_entry_t *garbage = NULL; ck_fifo_spsc_deinit(&d->q, &garbage); while (garbage != NULL) { ck_fifo_spsc_entry_t *n = garbage->next; free(garbage); garbage = n; } free(d); } /* not supported for now */ static int queue_mem_add_checkpoint(fqd_queue_impl_data data, const char *name, const fq_msgid *id) { return -1; } /* not supported for now */ static int queue_mem_remove_checkpoint(fqd_queue_impl_data data, const char *name) { return -1; } /* not supported for now */ static int queue_mem_reset_to_checkpoint(fqd_queue_impl_data data, const char *name) { return -1; } fqd_queue_impl fqd_queue_mem_impl = { .name = "mem", .setup = queue_mem_setup, .enqueue = queue_mem_enqueue, .dequeue = queue_mem_dequeue, .dispose = queue_mem_dispose, .add_checkpoint = queue_mem_add_checkpoint, .remove_checkpoint = queue_mem_remove_checkpoint, .reset_checkpoint = queue_mem_reset_to_checkpoint }; ================================================ FILE: fqd_routemgr.c ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include "fqd.h" #include "fqd_private.h" #include "fq_dtrace.h" #include uint32_t global_route_id = 1; #define RR_SET_SIZE 32 #define MAX_QUEUE_TARGETS 30 struct dl_handles { void *handle; struct dl_handles *next; } *global_handles; void fqd_routemgr_add_handle(void *handle) { struct dl_handles *nh = calloc(1, sizeof(*nh)); nh->handle = handle; nh->next = global_handles; global_handles = nh; } struct function_registry_entry { char *name; void (*handle)(void); }; static void *gen_malloc(size_t r) { return malloc(r); } static void gen_free(void *p, size_t b, bool r) { (void)b; (void)r; free(p); } static struct ck_malloc malloc_ck_hs = { .malloc = gen_malloc, .free = gen_free }; static unsigned long __ck_hash_name(const void *v, unsigned long seed) { unsigned long hash = seed; const struct function_registry_entry *e = v; int len = strlen(e->name); for(int i=0; iname[i]); return hash; } static bool __ck_compare_name(const void *a, const void *b) { const struct function_registry_entry *ae = a, *be = b; return 0 == strcmp(ae->name, be->name); } static ck_hs_t *global_functions = NULL; void global_function_register(const char *name, void (*f)(void)) { if(!global_functions) { ck_hs_t *map = calloc(1, sizeof(*map)); ck_hs_init(map, CK_HS_MODE_OBJECT | CK_HS_MODE_SPMC, __ck_hash_name, __ck_compare_name, &malloc_ck_hs, 100, 0); global_functions = map; } struct function_registry_entry *old = NULL, *entry = calloc(1, sizeof(*entry)); entry->name = strdup(name); entry->handle = f; unsigned long hash = CK_HS_HASH(global_functions, __ck_hash_name, entry); ck_hs_set(global_functions, hash, (void *)entry, (void **)&old); if(old) { free(old->name); free(old); } } static void (*global_function_lookup(const char *name))(void) { struct function_registry_entry *entry = NULL, stub = { .name = (char *)name }; unsigned long hash = CK_HS_HASH(global_functions, __ck_hash_name, &stub); entry = ck_hs_get(global_functions, hash, &stub); if(!entry) return NULL; return entry->handle; } static void prog_free(rulenode_t *); static void expr_free(exprnode_t *); static rulenode_t *prog_compile(const char *program, int errlen, char *err); static bool apply_compiled_program_node(rulenode_t *, fq_msg *); static bool apply_compiled_program_node(rulenode_t *p, fq_msg *m) { bool lval = false, rval = false; if(p->left) lval = apply_compiled_program_node(p->left, m); if(p->right) rval = apply_compiled_program_node(p->right, m); if(p->oper == '|') return lval || rval; if(p->oper == '&') return lval && rval; if(p->expr) { return p->expr->match(m, p->expr->nargs, p->expr->args); } fq_assert("Bad program" == NULL); return false; } static bool apply_compiled_program(struct fqd_route_rule *r, fq_msg *m) { bool rv; hrtime_t start, delta, rpl; ck_pr_add_64(&r->stats->invocations, 1); start = fq_gethrtime(); rv = apply_compiled_program_node(r->compiled_program, m); delta = fq_gethrtime() - start; /* simple exp smoothing (alpha = 127/128) */ rpl = ((r->stats->avg_ns * 127)>>7) + (delta>>7); r->stats->avg_ns = rpl; return rv; } struct queue_target { fqd_queue *tgts[MAX_QUEUE_TARGETS]; int cnt; int allocd; struct queue_target *next; }; static bool already_queue_target(struct queue_target **d, fqd_queue *q) { int i; struct queue_target *nd; /* Simple O(n) scan of target queues to avoid dup delivery */ for(nd = *d; nd; nd = nd->next) for(i=0;icnt;i++) if(q == nd->tgts[i]) return true; return false; } static void add_queue_target(struct queue_target **d, fqd_queue *q) { struct queue_target *nd; if(!(*d) || (*d)->cnt >= MAX_QUEUE_TARGETS) { nd = malloc(sizeof(*nd)); nd->next = *d; nd->cnt = 1; nd->allocd = 1; fqd_queue_ref(q); nd->tgts[0] = q; *d = nd; } else { fqd_queue_ref(q); (*d)->tgts[(*d)->cnt++] = q; } } static int internal_jt_do(struct prefix_jumptable *jt, int rv, int (*f)(struct fqd_route_rule *, int, void *), void *closure) { int i; if(jt->tabletype == RULETABLE) { struct fqd_route_rule *r; for(r=jt->rules;r;r=r->next) rv += f(r, rv, closure); } else if(jt->tabletype == JUMPTABLE) { for(i=0;ipat_len;i++) { rv += internal_jt_do(jt->pats[i].jt, rv, f, closure); } } return rv; } int for_each_route_rule_do(struct fqd_route_rules *set, int (*f)(struct fqd_route_rule *, int, void *), void *closure) { return internal_jt_do(&set->master, 0, f, closure); } static void walk_jump_table(struct prefix_jumptable *jt, fq_msg *m, int offset, struct queue_target **d) { if(jt->tabletype == RULETABLE) { struct fqd_route_rule *r; for(r=jt->rules;r;r=r->next) { if(m->route.len >= r->prefix.len && m->route.len <= r->match_maxlen && !already_queue_target(d, r->queue)) { bool matched = false; if(FQ_ROUTE_PROGRAM_ENTRY_ENABLED()) { fq_dtrace_msg_t dmsg; DTRACE_PACK_MSG(&dmsg, m); FQ_ROUTE_PROGRAM_ENTRY(r->program, &dmsg); } if(apply_compiled_program(r, m)) { fq_rk *rk = (fq_rk *)r->queue; fq_debug(FQ_DEBUG_ROUTE, "M[%p] -> Q[%.*s]\n", (void *)m, rk->len, rk->name); add_queue_target(d, r->queue); matched = true; } if(FQ_ROUTE_PROGRAM_RETURN_ENABLED()) { fq_dtrace_msg_t dmsg; DTRACE_PACK_MSG(&dmsg, m); (void)matched; FQ_ROUTE_PROGRAM_RETURN(r->program, &dmsg, matched); } } } } else if(jt->tabletype == JUMPTABLE) { int i; uint64_t inbits; uint8_t *in = m->route.name + offset; memcpy(&inbits, in, sizeof(inbits)); for(i=0;ipat_len;i++) { if(jt->pats[i].pattern == (jt->pats[i].checkbits & inbits)) { walk_jump_table(jt->pats[i].jt, m, offset + sizeof(inbits), d); } } } } void fqd_inject_message(remote_data_client *c, fq_msg *m) { fqd_exchange *e; fqd_config *config; struct queue_target stub, *headptr = &stub; stub.next = NULL; stub.cnt = 0; stub.allocd = 0; config = fqd_config_get(); e = fqd_config_get_exchange(config, &m->exchange); fqd_exchange_messages(e, 1); fqd_exchange_message_octets(e, m->payload_len); if(e) { walk_jump_table(&e->set->master, m, 0, &headptr); } else { fq_debug(FQ_DEBUG_ROUTE, "No exchange \"%.*s\"\n", m->exchange.len, m->exchange.name); fqd_exchange_no_exchange(NULL, 1); if(c) c->no_exchange++; } fqd_config_release(config); if(headptr->cnt == 0) { fqd_exchange_no_route(e, 1); if(c) c->no_route++; } while(headptr) { int i; struct queue_target *tofree = headptr; for(i=0; icnt; i++) { int dropped = 0; fqd_queue *q = headptr->tgts[i]; fqd_queue_enqueue(q, m, &dropped); fqd_queue_deref(q); if(dropped) { fqd_exchange_dropped(e, dropped); if(c) c->dropped += dropped; } fqd_exchange_routed(e, 1); if(c) c->routed += 1; } headptr = headptr->next; if(tofree->allocd) free(tofree); } fq_msg_deref(m); } #define is_hex(a) (((a) > '0' || (a) < '9') || \ ((a) > 'a' || (a) < 'f') || \ ((a) > 'A' || (a) < 'F')) #define to_hex_c(a) (((a) > '0' || (a) < 9) ? ((a) - '0') : \ ((a) > 'a' || (a) < 'f') ? ((a) - 'a' + 10) : \ ((a) > 'A' || (a) < 'F') ? ((a) - 'A' + 10) : 0) #define to_hex(cp) ((to_hex_c(cp[0]) << 4) | to_hex_c(cp[1])) static inline int is_term_char(char a, const char *ts, int tslen) { int i; for(i=0;i (int)sizeof(r->prefix.name)) return NULL; if(strncmp(program, "prefix:", 7) && strncmp(program, "exact:", 6)) { return NULL; } cp = strchr(program, ':'); if(!cp) return NULL; cp++; r = calloc(1, sizeof(*r)); alen = sizeof(r->prefix.name); cp = parse_prog_string(cp, (char *)r->prefix.name, &alen); r->prefix.len = (uint8_t)alen; if(cp == NULL) { fq_debug(FQ_DEBUG_ROUTE, "Failed to parse: %s\n", r->prefix.name); free(r); return NULL; } r->compiled_program = prog_compile(cp, sizeof(err), err); if(r->compiled_program == NULL) { fq_debug(FQ_DEBUG_ROUTE, "Failed to compile[%s]: %s\n", cp, err); free(r); return NULL; } r->match_maxlen = sizeof(r->prefix.name); if(!strncmp(program, "exact:", 6)) r->match_maxlen = r->prefix.len; r->program = strdup(program); r->queue = q; fqd_queue_ref(r->queue); r->peermode = peermode; r->stats = calloc(1, sizeof(*r->stats)); r->stats->refcnt = 1; fq_debug(FQ_DEBUG_ROUTE, "creating rule \"%s\"\n", r->program); fq_debug(FQ_DEBUG_MEM, "alloc rule [%p/%p] -> Q[%p]\n", (void *)r, (void *)r->compiled_program, (void *)r->queue); return r; } void fqd_routemgr_rule_free(struct fqd_route_rule *rule) { fq_debug(FQ_DEBUG_ROUTE, "dropping rule \"%s\"\n", rule->program); fq_debug(FQ_DEBUG_MEM, "free rule [%p] -> Q[%p]\n", (void *)rule, (void *)rule->queue); free(rule->program); prog_free(rule->compiled_program); if(rule->queue) fqd_queue_deref(rule->queue); if(rule->stats) { bool zero; ck_pr_dec_uint_zero(&rule->stats->refcnt, &zero); if(zero) { free(rule->stats); } } free(rule); } struct fqd_route_rules * fqd_routemgr_ruleset_alloc() { return calloc(1, sizeof(struct fqd_route_rules)); } static int walk_jump_table_setp_by_route_id(struct prefix_jumptable *jt, uint32_t route_id, bool nv) { if(jt->tabletype == RULETABLE) { struct fqd_route_rule *r = jt->rules; while(r) { if(r->route_id == route_id) { r->permanent = nv; return 1; } else r = r->next; } } else if(jt->tabletype == JUMPTABLE) { int i; for(i=0;ipat_len;i++) { if(walk_jump_table_setp_by_route_id(jt->pats[i].jt, route_id, nv)) { return 1; } } } return 0; } static int walk_jump_table_drop_rules_by_route_id(struct prefix_jumptable *jt, fqd_queue *q, uint32_t route_id) { if(jt->tabletype == RULETABLE) { struct fqd_route_rule *prev = NULL, *r = jt->rules; while(r) { if(r->route_id == route_id && (q == NULL || r->queue == q)) { struct fqd_route_rule *tofree = r; if(prev) r = prev->next = r->next; else r = jt->rules= r->next; fqd_routemgr_rule_free(tofree); return 1; } else { prev = r; r = r->next; } } } else if(jt->tabletype == JUMPTABLE) { int i; for(i=0;ipat_len;i++) { if(walk_jump_table_drop_rules_by_route_id(jt->pats[i].jt, q, route_id)) { return 1; } } } return 0; } static void walk_jump_table_drop_rules_by_queue(struct prefix_jumptable *jt, fqd_queue *q) { if(jt->tabletype == RULETABLE) { struct fqd_route_rule *prev = NULL, *r = jt->rules; while(r) { if(r->queue == q) { struct fqd_route_rule *tofree = r; if(prev) r = prev->next = r->next; else r = jt->rules= r->next; fqd_routemgr_rule_free(tofree); } else { prev = r; r = r->next; } } } else if(jt->tabletype == JUMPTABLE) { int i; for(i=0;ipat_len;i++) walk_jump_table_drop_rules_by_queue(jt->pats[i].jt, q); } } static int fqd_routemgr_set_permanence_by_route_id(fqd_route_rules *set, uint32_t route_id, bool nv) { return walk_jump_table_setp_by_route_id(&set->master, route_id, nv); } int fqd_routemgr_perm_route_id(fqd_route_rules *set, uint32_t route_id) { return fqd_routemgr_set_permanence_by_route_id(set, route_id, true); } int fqd_routemgr_trans_route_id(fqd_route_rules *set, uint32_t route_id) { return fqd_routemgr_set_permanence_by_route_id(set, route_id, false); } int fqd_routemgr_drop_rules_by_route_id(fqd_route_rules *set, fqd_queue *q, uint32_t route_id) { fq_debug(FQ_DEBUG_ROUTE, "fqd_routemgr_drop_rules_by_route_id(%p, %p, %u)\n", (void *)set, (void *)q, route_id); return walk_jump_table_drop_rules_by_route_id(&set->master, q, route_id); } void fqd_routemgr_drop_rules_by_queue(fqd_route_rules *set, fqd_queue *q) { fq_debug(FQ_DEBUG_ROUTE, "fqd_routemgr_drop_rules_by_queue(%p, %p)\n", (void *)set, (void *)q); walk_jump_table_drop_rules_by_queue(&set->master, q); } static struct prefix_jumptable * get_ruletable(struct prefix_jumptable *parent, fqd_route_rule *newrule, int offset) { uint64_t inbits; uint64_t incb = 0; int i; struct prefix_jumptable *child; if(newrule->prefix.len >= (offset+sizeof(inbits))) /* use all the bits */ incb = ~incb; else if(newrule->prefix.len % sizeof(inbits) != 0) /* use partial bits */ incb = ~incb << ((sizeof(inbits) - (newrule->prefix.len % sizeof(inbits))) * sizeof(inbits)); #if !defined(BYTE_ORDER) || !defined(BIG_ENDIAN) #error "BYTE_ORDER and BIG_ENDIAN must be defined!" #endif #if BYTE_ORDER != BIG_ENDIAN incb = (((uint64_t)ntohl(incb & 0xffffffffLLU)) << 32) | (ntohl((incb &0xffffffff00000000LLU) >> 32)); #endif memcpy(&inbits, newrule->prefix.name + offset, sizeof(inbits)); /* We need to (possibly) insert a rule table */ for(i=0;ipat_len;i++) { if(parent->pats[i].checkbits == incb && parent->pats[i].pattern == (parent->pats[i].checkbits & inbits)) { if(parent->pats[i].jt->tabletype == RULETABLE) { return parent->pats[i].jt; } else { return get_ruletable(parent->pats[i].jt, newrule, offset + sizeof(inbits)); } } } child = calloc(1, sizeof(*child)); if(!child) return NULL; child->tabletype = (~incb == 0) ? JUMPTABLE : RULETABLE; /* Here we use child->pats as a tmp variable to grow parent->pats */ child->pats = malloc(sizeof(*child->pats) * (parent->pat_len + 1)); if(!child->pats) { free(child); return NULL; } if(parent->pat_len) { memcpy(child->pats, parent->pats, sizeof(*child->pats) * parent->pat_len); free(parent->pats); } parent->pats = child->pats; child->pats = NULL; parent->pat_len++; parent->pats[i].pattern = (inbits & incb); parent->pats[i].checkbits = incb; parent->pats[i].jt = child; if(child->tabletype == RULETABLE) { return child; } return get_ruletable(child, newrule, offset + sizeof(inbits)); } uint32_t fqd_routemgr_ruleset_add_rule(fqd_route_rules *set, fqd_route_rule *newrule, int *isnew) { fqd_route_rule *r; struct prefix_jumptable *jt; jt = get_ruletable(&set->master, newrule, 0); for(r=jt->rules;r;r=r->next) { if(r->queue == newrule->queue && !strcmp(r->program, newrule->program)) { fqd_routemgr_rule_free(newrule); if(isnew) *isnew = 0; return r->route_id; } } do { newrule->route_id = ck_pr_faa_32(&global_route_id, 1); } while(newrule->route_id == FQ_BIND_ILLEGAL); newrule->next = jt->rules; jt->rules = newrule; fq_debug(FQ_DEBUG_ROUTE, "rule[%u] -> %p\n", newrule->route_id, (void *)newrule); if(isnew) *isnew = 1; return newrule->route_id; } static rulenode_t * copy_compiled_program(rulenode_t *in) { fq_debug(FQ_DEBUG_MEM, "copy compiled program: %p\n", (void *)in); ck_pr_inc_uint(&in->refcnt); return in; } static fqd_route_rule * copy_rule(fqd_route_rule *in) { fqd_route_rule *out; fq_debug(FQ_DEBUG_MEM, "copy from [%p] -> Q[%p]\n", (void *)in, (void *)in->queue); out = calloc(1, sizeof(*out)); memcpy(out, in, sizeof(*out)); fq_assert(out->queue); out->program = strdup(in->program); out->compiled_program = copy_compiled_program(in->compiled_program); fqd_queue_ref(out->queue); ck_pr_inc_uint(&out->stats->refcnt); out->next = NULL; fq_debug(FQ_DEBUG_MEM, "copy to [%p] -> Q[%p]\n", (void *)out, (void *)out->queue); return out; } static void copy_jt(struct prefix_jumptable *tgt, struct prefix_jumptable *src) { memcpy(tgt, src, sizeof(*src)); if(src->tabletype == RULETABLE) { fqd_route_rule *r, *nhead, *nr, *tmp; nhead = nr = NULL; for(r=src->rules;r;r=r->next) { tmp = copy_rule(r); if(!nhead) nhead = tmp; if(nr) { nr->next = tmp; } nr = tmp; } tgt->rules = nhead; } else if(src->tabletype == JUMPTABLE) { int i; tgt->pats = malloc(src->pat_len * sizeof(*tgt->pats)); if(src->pat_len) { memcpy(tgt->pats, src->pats, src->pat_len * sizeof(*tgt->pats)); for(i=0; ipat_len; i++) { tgt->pats[i].jt = malloc(sizeof(*tgt->pats[i].jt)); if(tgt->pats[i].jt) copy_jt(tgt->pats[i].jt, src->pats[i].jt); } } } } fqd_route_rules * fqd_routemgr_ruleset_copy(fqd_route_rules *set) { fqd_route_rules *nset; nset = fqd_routemgr_ruleset_alloc(); copy_jt(&nset->master, &set->master); /* TODO: compress set */ return nset; } static void free_jt(struct prefix_jumptable *jt) { if(jt->tabletype == RULETABLE) { while(jt->rules) { fqd_route_rule *r = jt->rules->next; fqd_routemgr_rule_free(jt->rules); jt->rules = r; } } else if(jt->tabletype == JUMPTABLE) { int i; for(i=0;ipat_len;i++) { if(jt->pats[i].jt) { free_jt(jt->pats[i].jt); free(jt->pats[i].jt); } } free(jt->pats); } } void fqd_routemgr_ruleset_free(fqd_route_rules *set) { free_jt(&set->master); free(set); } static void prog_free(rulenode_t *p) { bool zero; if(!p) return; ck_pr_dec_uint_zero(&p->refcnt, &zero); if(!zero) return; if(p->left) prog_free(p->left); if(p->right) prog_free(p->right); if(p->expr) expr_free(p->expr); free(p); } static void expr_free(exprnode_t *e) { if(!e) return; if(e->args) { int i; for(i=0;inargs;i++) { if(e->args[i].value_type == RP_VALUE_STRING) free(e->args[i].value.s); } free(e->args); } free(e); } #define EAT_SPACE(p) while(*p != '\0' && isspace(*p)) (p)++ static int is_valid_term_char(char ch, bool first) { if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch == '_')) return 1; if(first) return 0; if(ch >= '0' && ch <= '9') return 1; return 0; } static int rule_getterm(const char **cp, char *term, int len) { int idx = 0; while(idx < (len-1) && is_valid_term_char((*cp)[idx], idx == 0)) { term[idx] = (*cp)[idx]; idx++; } term[idx] = '\0'; (*cp) += idx; fq_debug(FQ_DEBUG_ROUTE, "term[%s]\n", term); if(idx > 0) return 0; return 1; } static int rule_getstring(const char **cp, valnode_t *arg) { const char *begin; if(**cp != '\"') return 0; (*cp)++; begin = *cp; while(**cp != '\0') { if(**cp == '\\' && (*cp)[1] != '\0') (*cp)++; (*cp)++; if(**cp == '\"') { char *dst; int i, len = (*cp) - begin; (*cp)++; arg->value_type = RP_VALUE_STRING; dst = arg->value.s = malloc(len + 1); for(i=0;inext) { u.symbol = dlsym(node->handle, symbol_name); if(u.symbol != NULL) break; } } if(!u.symbol) { u.dummy = global_function_lookup(symbol_name); } if(!u.symbol) { snprintf(err, errlen, "cannot find symbol: %s\n", symbol_name); fq_debug(FQ_DEBUG_ROUTE, "cannot find symbol: %s\n", symbol_name); snprintf(symbol_name, sizeof(symbol_name), "fqd_route_prog__%s__VA", fname); if(!u.symbol) { for(struct dl_handles *node = global_handles; node; node = node->next) { u.symbol = dlsym(node->handle, symbol_name); if(u.symbol != NULL) break; } } if(!u.symbol) { u.dummy = global_function_lookup(symbol_name); } if(!u.symbol) { fq_debug(FQ_DEBUG_ROUTE, "cannot find symbol: %s\n", symbol_name); return NULL; } } expr = calloc(1, sizeof(*expr)); expr->match = (bool (*)(fq_msg *, int, valnode_t *)) u.match; if(nargs > 0) { expr->nargs = nargs; expr->args = calloc(nargs, sizeof(*expr->args)); for(i=0; iargs[i], &args[i], sizeof(*args)); /* don't need to double the alloc */ } } return expr; } rulenode_t *rule_parse(const char **cp, int errlen, char *err); #define rule_parse_busted(fmt, ...) do { \ snprintf(err, errlen, fmt, __VAR_ARGS__); \ goto busted; \ } while(0) rulenode_t * rule_parse(const char **cp, int errlen, char *err) { rulenode_t *nr = NULL; EAT_SPACE(*cp); if(**cp == '\0') goto busted; fq_debug(FQ_DEBUG_ROUTE, "parse->(%s)\n", *cp); if(**cp == '(') { (*cp)++; EAT_SPACE(*cp); if(**cp == '\0') goto busted; nr = calloc(1, sizeof(*nr)); nr->refcnt = 1; nr->left = rule_parse(cp, errlen, err); if(nr->left == NULL) goto busted; EAT_SPACE(*cp); if(**cp == '\0') goto busted; if(**cp == ')') return nr; if((*cp)[0] == '&' && (*cp)[1] == '&') nr->oper = '&'; else if((*cp)[0] == '|' || (*cp)[1] == '|') nr->oper = '|'; else goto busted; (*cp) += 2; nr->right = rule_parse(cp, errlen, err); if(nr->right == NULL) goto busted; EAT_SPACE(*cp); if(**cp == '\0') goto busted; if(**cp != ')') goto busted; (*cp)++; return nr; } else { char term[128]; int nargs = 0; valnode_t args[MAX_VALNODE_ARGS]; fq_debug(FQ_DEBUG_ROUTE, "parsing function: %s\n", *cp); if(rule_getterm(cp, term, sizeof(term))) goto busted; EAT_SPACE(*cp); if(**cp == '\0') goto busted; if(**cp != '(') goto busted; (*cp)++; while(**cp != '\0' && **cp != ')') { EAT_SPACE(*cp); if(**cp == '\0') goto busted; if(nargs > 0) { if(**cp != ',') goto busted; (*cp)++; EAT_SPACE(*cp); if(**cp == '\0') goto busted; } if(**cp == '\"') { if(rule_getstring(cp, &args[nargs])) goto busted; nargs++; } else if(!strcmp(*cp, "true")) { args[nargs].value_type = RP_VALUE_BOOLEAN; args[nargs].value.b = true; nargs++; (*cp) += strlen("true"); } else if(!strcmp(*cp, "false")) { args[nargs].value_type = RP_VALUE_BOOLEAN; args[nargs].value.b = false; nargs++; (*cp) += strlen("false"); } else { char *endptr; /* parse a double */ args[nargs].value_type = RP_VALUE_DOUBLE; args[nargs].value.d = strtod(*cp, &endptr); if(endptr == *cp) goto busted; nargs++; *cp = endptr; } EAT_SPACE(*cp); if(**cp == '\0') goto busted; } if(**cp != ')') goto busted; (*cp)++; nr = calloc(1, sizeof(*nr)); nr->refcnt = 1; nr->expr = rule_compose_expression(term, nargs, args, errlen, err); if(!nr->expr) { int i; for(i=0;iexpr) { if(nr->expr->nargs) { int i; for(i=0;iexpr->nargs;i++) if(nr->expr->args[i].value_type == RP_VALUE_STRING) free(nr->expr->args[i].value.s); free(nr->expr->args); } free(nr->expr); } free(nr); } return NULL; } static rulenode_t * prog_compile(const char *program, int errlen, char *err) { rulenode_t *nr; EAT_SPACE(program); if(*program == '\0') { return prog_compile("true()", errlen, err); } if(errlen>0) err[0] = '\0'; nr = rule_parse(&program, errlen, err); EAT_SPACE(program); if(*program) { if(err && err[0] == '\0') snprintf(err, errlen, "trailing trash: %s", program); prog_free(nr); return NULL; } return nr; } ================================================ FILE: fqs.c ================================================ /* * fqs * * fqs reads messages from stdin, one pre line, and sends it to the specified fq exchange. * */ #define _GNU_SOURCE #include #include #include #include #include #include "fq.h" void logger(fq_client c, const char *s) { fprintf(stderr, "fq_logger: %s\n", s); } static void usage(const char *prog) { printf("%s:\n", prog); printf("\t-h \tshow this help message\n"); printf("\t-a :\tspecify the address to connect to (default: 127.0.0.1:8765)\n"); printf("\t-x \tspecify the exchange to relay messages on (required)\n"); printf("\t-r \tspecify the message route (required)\n"); printf("\t-u \tspecify the user (default: user)\n"); printf("\t-p \tspecify the password (default: pass)\n"); } int main(int argc, char **argv) { char *host = NULL, *exchange = NULL, *route = NULL; char *address = strdup("127.0.0.1:8765"); char *user = "user"; char *pass = "pass"; int port = 0; int c = 0; if (argc == 1) { usage(argv[0]); exit(-1); } while((c = getopt(argc, argv, "ha:x:r:u:p:")) != EOF) { switch(c) { case 'h': usage(argv[0]); exit(0); break; case 'a': address = strdup(optarg); break; case 'x': exchange = strdup(optarg); break; case 'r': route = strdup(optarg); break; case 'u': user = strdup(optarg); break; case 'p': pass = strdup(optarg); break; default: usage(argv[0]); exit(-1); } } // Separate host and port in address string // We have strdup'ed address so we can mutate it host = address; for (char *p = address; *p != 0; p++) { if (*p == ':') { *p = 0; port = strtol(p+1, NULL, 10); break; } } if (!port) { printf("Illegal port specification\n"); exit(-1); } if (!exchange) { printf("Exchange argument required"); exit(-1); } if (!route) { printf("Route argument required"); exit(-1); } fq_client cli; fq_msg *m = NULL; size_t exchange_len = strlen(exchange); size_t route_len = strlen(route); signal(SIGPIPE, SIG_IGN); // ignore SIGPIPE fq_client_init(&cli, 0, logger); fq_client_creds(cli, host, port, user, pass); fq_client_heartbeat(cli, 1000); fq_client_set_backlog(cli, 10000, 100); fq_client_connect(cli); while(true) { char *line = NULL; size_t line_cap = 0; int line_len; line_len = getline(&line, &line_cap, stdin); if(line_len < 0) { // wait for queues to drain while(fq_client_data_backlog(cli) > 0) { usleep(100); } exit(0); } m = fq_msg_alloc(line, line_len); fq_msg_exchange(m, exchange, exchange_len); fq_msg_route(m, route, route_len); fq_msg_id(m, NULL); fq_client_publish(cli, m); fq_msg_free(m); } return 0; } ================================================ FILE: fqtool.c ================================================ /* * Copyright (c) 2014 Circonus, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "getopt.h" #include "fq.h" int usage(const char *); void logger(fq_client, const char *); void logger(fq_client c, const char *s) { (void)c; fprintf(stderr, "fq_logger: %s\n", s); } int permanent = 1; int binding_trans = 1; char *binding_prog = NULL; char *exchange = NULL; char *output = NULL; char *format = "payload"; enum { M_ROUTE = 0, M_HOST, M_PAYLOAD, M_NONE } o_fmt[M_NONE]; #define M_COUNT M_NONE int output_fd = -1; bool inject_json = true; static void my_auth_handler(fq_client c, int error) { fq_bind_req *breq; if(error) { fprintf(stderr, "Error encoutered.\n"); exit(-1); } if(!output) fprintf(stderr, "Queue made %s.\n", permanent ? "permanent" : "transient"); if(!binding_prog && !output) exit(0); breq = malloc(sizeof(*breq)); memset(breq, 0, sizeof(*breq)); assert(strlen(exchange) < sizeof(breq->exchange.name)); memcpy(breq->exchange.name, exchange, strlen(exchange)); breq->exchange.len = strlen(exchange); breq->flags = binding_trans ? FQ_BIND_TRANS : FQ_BIND_PERM; breq->program = binding_prog; fq_client_bind(c, breq); } static void my_bind_handler(fq_client c, fq_bind_req *breq) { (void)c; if(breq->out__route_id == FQ_BIND_ILLEGAL) { fprintf(stderr, "Failure to bind...\n"); exit(-1); } if(!output) { fprintf(stderr, "Binding made %s.\n", binding_trans ? "transient" : "permanent"); exit(0); } } static bool my_message_handler(fq_client c, fq_msg *m) { int i; char *payload = (char *)m->payload; long payload_len = m->payload_len; char host[32] = ""; bool lineend = true; bool started = false; if(payload_len > 0 && payload[payload_len-1] == '\n') { payload_len--; } struct in_addr addr; for(i=0;ihops[i+1] == 0 || /* IN ADDR ANY */ m->hops[i+1] == 0xffffffff || /* IN ADDR ANY */ (ntohl(m->hops[i+1]) & 0xff000000) == 0x7f000000) /* LOCALHOST */ break; memcpy(&addr, m->hops+i, sizeof(unsigned int)); snprintf(host, sizeof(host), "%s", inet_ntoa(addr)); #define OUTSTART() do { \ if(started) write(output_fd, " ", 1); started = true; \ } while(0) for(i=0;iroute.name, m->route.len); break; case M_PAYLOAD: OUTSTART(); if(inject_json && payload[0] == '{' && payload[payload_len-1] == '}') { struct iovec iov[8]; write(output_fd, payload, payload_len-1); iov[0].iov_base = "\"_host\":\""; iov[0].iov_len = 9; if(payload_len > 2) { iov[0].iov_base = ",\"_host\":\""; iov[0].iov_len = 10; } iov[1].iov_base = host; iov[1].iov_len = strlen(host); iov[2].iov_base = "\""; iov[2].iov_len = 1; writev(output_fd, iov, 3); iov[0].iov_base = ",\"_route\":\""; iov[0].iov_len = 11; iov[1].iov_base = m->route.name; iov[1].iov_len = m->route.len; iov[2].iov_base = "\""; iov[2].iov_len = 1; writev(output_fd, iov, 3); write(output_fd, "}", 1); } else { write(output_fd, payload, payload_len); } break; } } if(started && lineend) write(output_fd, "\n", 1); return true; } fq_hooks hooks = { .version = FQ_HOOKS_V4, .auth = my_auth_handler, .bind = my_bind_handler, .message = my_message_handler }; int usage(const char *prog) { printf("%s [-h host] [-P port] [-u user] [-p pass]\n", prog); printf("\t<-e name> <-q name> [-t ] [-D]\n"); printf("\t[-(b|B) 'program'] <-d backlog>\n"); printf("\n"); printf("\t-h \t\tdefault: localhost\n"); printf("\t-e \t\texchange name\n"); printf("\t-P \t\tdefault: 8765\n"); printf("\t-u \t\tdefault: nobody\n"); printf("\t-p \t\tdefault: nopass\n"); printf("\t-q \t\tqueue name\n"); printf("\t-t \t\tqueue type\n"); printf("\t-d \t\t\tdefine queue depth (backlog)\n"); printf("\t-D\t\t\tdelete queue (make transient)\n"); printf("\t-B \t\tinstall binding\n"); printf("\t-b \t\tuninstall binding\n"); printf("\t-o \t\toutput file (- for stdout)\n"); return 0; } int main(int argc, char **argv) { int opt; char *user = (char *)"nobody"; char *pass = (char *)"nopass"; char *host = (char *)"localhost"; int port = 8765; char *queuename = NULL; char *qtype = (char *)"mem"; int backlog = -1; char connstr[256]; fq_client c; while((opt = getopt(argc, argv, "Jo:f:Dh:e:P:u:p:q:t:B:b:d:")) != EOF) { switch(opt) { case 'o': output = strdup(optarg); break; case 'f': format = strdup(optarg); break; case 'e': exchange = strdup(optarg); break; case 'h': host = strdup(optarg); break; case 'P': port = atoi(optarg); break; case 'u': user = strdup(optarg); break; case 'p': pass = strdup(optarg); break; case 'q': queuename = strdup(optarg); break; case 't': qtype = strdup(optarg); break; case 'D': permanent = 0; break; case 'B': binding_prog = strdup(optarg); binding_trans = 0; break; case 'b': binding_prog = strdup(optarg); binding_trans = 1; break; case 'd': backlog = atoi(optarg); break; case 'J': inject_json = false; break; default: exit(usage(argv[0])); } } if(format) { char *word, *brkt; int o_i = 0; for (word = strtok_r(format, ",", &brkt); word && o_i < M_COUNT; word = strtok_r(NULL, ",", &brkt)) { if(!strcmp(word, "host")) o_fmt[o_i++] = M_HOST; else if(!strcmp(word, "route")) o_fmt[o_i++] = M_ROUTE; else if(!strcmp(word, "payload")) o_fmt[o_i++] = M_PAYLOAD; else { fprintf(stderr, "Error: unknown format '%s'\n", word); exit(-2); } } for(;o_i #include #include #include #include #include #ifndef ULLONG_MAX # define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ #endif #ifndef MIN # define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef ARRAY_SIZE # define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #endif #ifndef BIT_AT # define BIT_AT(a, i) \ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ (1 << ((unsigned int) (i) & 7)))) #endif #ifndef ELEM_AT # define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) #endif #define SET_ERRNO(e) \ do { \ parser->http_errno = (e); \ } while(0) #define CURRENT_STATE() p_state #define UPDATE_STATE(V) p_state = (enum state) (V); #define RETURN(V) \ do { \ parser->state = CURRENT_STATE(); \ return (V); \ } while (0); #define REEXECUTE() \ --p; \ break; #ifdef __GNUC__ # define LIKELY(X) __builtin_expect(!!(X), 1) # define UNLIKELY(X) __builtin_expect(!!(X), 0) #else # define LIKELY(X) (X) # define UNLIKELY(X) (X) #endif /* Run the notify callback FOR, returning ER if it fails */ #define CALLBACK_NOTIFY_(FOR, ER) \ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ if (LIKELY(settings->on_##FOR)) { \ parser->state = CURRENT_STATE(); \ if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ } while (0) /* Run the notify callback FOR and consume the current byte */ #define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) /* Run the notify callback FOR and don't consume the current byte */ #define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) /* Run data callback FOR with LEN bytes, returning ER if it fails */ #define CALLBACK_DATA_(FOR, LEN, ER) \ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ if (FOR##_mark) { \ if (LIKELY(settings->on_##FOR)) { \ parser->state = CURRENT_STATE(); \ if (UNLIKELY(0 != \ settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ FOR##_mark = NULL; \ } \ } while (0) /* Run the data callback FOR and consume the current byte */ #define CALLBACK_DATA(FOR) \ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) /* Run the data callback FOR and don't consume the current byte */ #define CALLBACK_DATA_NOADVANCE(FOR) \ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) /* Set the mark FOR; non-destructive if mark is already set */ #define MARK(FOR) \ do { \ if (!FOR##_mark) { \ FOR##_mark = p; \ } \ } while (0) /* Don't allow the total size of the HTTP headers (including the status * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect * embedders against denial-of-service attacks where the attacker feeds * us a never-ending header that the embedder keeps buffering. * * This check is arguably the responsibility of embedders but we're doing * it on the embedder's behalf because most won't bother and this way we * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger * than any reasonable request or response so this should never affect * day-to-day operation. */ #define COUNT_HEADER_SIZE(V) \ do { \ parser->nread += (V); \ if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ SET_ERRNO(HPE_HEADER_OVERFLOW); \ goto error; \ } \ } while (0) #define PROXY_CONNECTION "proxy-connection" #define CONNECTION "connection" #define CONTENT_LENGTH "content-length" #define TRANSFER_ENCODING "transfer-encoding" #define UPGRADE "upgrade" #define CHUNKED "chunked" #define KEEP_ALIVE "keep-alive" #define CLOSE "close" static const char *method_strings[] = { #define XX(num, name, string) #string, HTTP_METHOD_MAP(XX) #undef XX }; /* Tokens as defined by rfc 2616. Also lowercases them. * token = 1* * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT */ static const char tokens[256] = { /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 0, 0, 0, 0, 0, 0, 0, 0, /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 0, 0, 0, 0, 0, 0, 0, 0, /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 0, 0, 0, 0, 0, 0, 0, 0, /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 0, 0, 0, 0, 0, 0, 0, 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 0, '!', 0, '#', '$', '%', '&', '\'', /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 0, 0, '*', '+', 0, '-', '.', 0, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ '0', '1', '2', '3', '4', '5', '6', '7', /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ '8', '9', 0, 0, 0, 0, 0, 0, /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 'x', 'y', 'z', 0, 0, 0, '^', '_', /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 'x', 'y', 'z', 0, '|', 0, '~', 0 }; static const int8_t unhex[256] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }; #if HTTP_PARSER_STRICT # define T(v) 0 #else # define T(v) v #endif static const uint8_t normal_url_char[32] = { /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; #undef T enum state { s_dead = 1 /* important that this is > 0 */ , s_start_req_or_res , s_res_or_resp_H , s_start_res , s_res_H , s_res_HT , s_res_HTT , s_res_HTTP , s_res_first_http_major , s_res_http_major , s_res_first_http_minor , s_res_http_minor , s_res_first_status_code , s_res_status_code , s_res_status_start , s_res_status , s_res_line_almost_done , s_start_req , s_req_method , s_req_spaces_before_url , s_req_schema , s_req_schema_slash , s_req_schema_slash_slash , s_req_server_start , s_req_server , s_req_server_with_at , s_req_path , s_req_query_string_start , s_req_query_string , s_req_fragment_start , s_req_fragment , s_req_http_start , s_req_http_H , s_req_http_HT , s_req_http_HTT , s_req_http_HTTP , s_req_first_http_major , s_req_http_major , s_req_first_http_minor , s_req_http_minor , s_req_line_almost_done , s_header_field_start , s_header_field , s_header_value_discard_ws , s_header_value_discard_ws_almost_done , s_header_value_discard_lws , s_header_value_start , s_header_value , s_header_value_lws , s_header_almost_done , s_chunk_size_start , s_chunk_size , s_chunk_parameters , s_chunk_size_almost_done , s_headers_almost_done , s_headers_done /* Important: 's_headers_done' must be the last 'header' state. All * states beyond this must be 'body' states. It is used for overflow * checking. See the PARSING_HEADER() macro. */ , s_chunk_data , s_chunk_data_almost_done , s_chunk_data_done , s_body_identity , s_body_identity_eof , s_message_done }; #define PARSING_HEADER(state) (state <= s_headers_done) enum header_states { h_general = 0 , h_C , h_CO , h_CON , h_matching_connection , h_matching_proxy_connection , h_matching_content_length , h_matching_transfer_encoding , h_matching_upgrade , h_connection , h_content_length , h_transfer_encoding , h_upgrade , h_matching_transfer_encoding_chunked , h_matching_connection_token_start , h_matching_connection_keep_alive , h_matching_connection_close , h_matching_connection_upgrade , h_matching_connection_token , h_transfer_encoding_chunked , h_connection_keep_alive , h_connection_close , h_connection_upgrade }; enum http_host_state { s_http_host_dead = 1 , s_http_userinfo_start , s_http_userinfo , s_http_host_start , s_http_host_v6_start , s_http_host , s_http_host_v6 , s_http_host_v6_end , s_http_host_port_start , s_http_host_port }; /* Macros for character classes; depends on strict-mode */ #define CR '\r' #define LF '\n' #define LOWER(c) (unsigned char)(c | 0x20) #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') #define IS_NUM(c) ((c) >= '0' && (c) <= '9') #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) #define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ (c) == ')') #define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ (c) == '$' || (c) == ',') #define STRICT_TOKEN(c) (tokens[(unsigned char)c]) #if HTTP_PARSER_STRICT #define TOKEN(c) (tokens[(unsigned char)c]) #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') #else #define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) #define IS_URL_CHAR(c) \ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) #define IS_HOST_CHAR(c) \ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') #endif #define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) #if HTTP_PARSER_STRICT # define STRICT_CHECK(cond) \ do { \ if (cond) { \ SET_ERRNO(HPE_STRICT); \ goto error; \ } \ } while (0) # define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) #else # define STRICT_CHECK(cond) # define NEW_MESSAGE() start_state #endif /* Map errno values to strings for human-readable output */ #define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, static struct { const char *name; const char *description; } http_strerror_tab[] = { HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) }; #undef HTTP_STRERROR_GEN int http_message_needs_eof(const http_parser *parser); /* Our URL parser. * * This is designed to be shared by http_parser_execute() for URL validation, * hence it has a state transition + byte-for-byte interface. In addition, it * is meant to be embedded in http_parser_parse_url(), which does the dirty * work of turning state transitions URL components for its API. * * This function should only be invoked with non-space characters. It is * assumed that the caller cares about (and can detect) the transition between * URL and non-URL states by looking for these. */ static enum state parse_url_char(enum state s, const char ch) { if (ch == ' ' || ch == '\r' || ch == '\n') { return s_dead; } #if HTTP_PARSER_STRICT if (ch == '\t' || ch == '\f') { return s_dead; } #endif switch (s) { case s_req_spaces_before_url: /* Proxied requests are followed by scheme of an absolute URI (alpha). * All methods except CONNECT are followed by '/' or '*'. */ if (ch == '/' || ch == '*') { return s_req_path; } if (IS_ALPHA(ch)) { return s_req_schema; } break; case s_req_schema: if (IS_ALPHA(ch)) { return s; } if (ch == ':') { return s_req_schema_slash; } break; case s_req_schema_slash: if (ch == '/') { return s_req_schema_slash_slash; } break; case s_req_schema_slash_slash: if (ch == '/') { return s_req_server_start; } break; case s_req_server_with_at: if (ch == '@') { return s_dead; } /* FALLTHROUGH */ case s_req_server_start: case s_req_server: if (ch == '/') { return s_req_path; } if (ch == '?') { return s_req_query_string_start; } if (ch == '@') { return s_req_server_with_at; } if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { return s_req_server; } break; case s_req_path: if (IS_URL_CHAR(ch)) { return s; } switch (ch) { case '?': return s_req_query_string_start; case '#': return s_req_fragment_start; } break; case s_req_query_string_start: case s_req_query_string: if (IS_URL_CHAR(ch)) { return s_req_query_string; } switch (ch) { case '?': /* allow extra '?' in query string */ return s_req_query_string; case '#': return s_req_fragment_start; } break; case s_req_fragment_start: if (IS_URL_CHAR(ch)) { return s_req_fragment; } switch (ch) { case '?': return s_req_fragment; case '#': return s; } break; case s_req_fragment: if (IS_URL_CHAR(ch)) { return s; } switch (ch) { case '?': case '#': return s; } break; default: break; } /* We should never fall out of the switch above unless there's an error */ return s_dead; } size_t http_parser_execute (http_parser *parser, const http_parser_settings *settings, const char *data, size_t len) { char c, ch; int8_t unhex_val; const char *p = data; const char *header_field_mark = 0; const char *header_value_mark = 0; const char *url_mark = 0; const char *body_mark = 0; const char *status_mark = 0; enum state p_state = (enum state) parser->state; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { return 0; } if (len == 0) { switch (CURRENT_STATE()) { case s_body_identity_eof: /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if * we got paused. */ CALLBACK_NOTIFY_NOADVANCE(message_complete); return 0; case s_dead: case s_start_req_or_res: case s_start_res: case s_start_req: return 0; default: SET_ERRNO(HPE_INVALID_EOF_STATE); return 1; } } if (CURRENT_STATE() == s_header_field) header_field_mark = data; if (CURRENT_STATE() == s_header_value) header_value_mark = data; switch (CURRENT_STATE()) { case s_req_path: case s_req_schema: case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: case s_req_server: case s_req_server_with_at: case s_req_query_string_start: case s_req_query_string: case s_req_fragment_start: case s_req_fragment: url_mark = data; break; case s_res_status: status_mark = data; break; default: break; } for (p=data; p != data + len; p++) { ch = *p; if (PARSING_HEADER(CURRENT_STATE())) COUNT_HEADER_SIZE(1); switch (CURRENT_STATE()) { case s_dead: /* this state is used after a 'Connection: close' message * the parser will error out if it reads another message */ if (LIKELY(ch == CR || ch == LF)) break; SET_ERRNO(HPE_CLOSED_CONNECTION); goto error; case s_start_req_or_res: { if (ch == CR || ch == LF) break; parser->flags = 0; parser->content_length = ULLONG_MAX; if (ch == 'H') { UPDATE_STATE(s_res_or_resp_H); CALLBACK_NOTIFY(message_begin); } else { parser->type = HTTP_REQUEST; UPDATE_STATE(s_start_req); REEXECUTE(); } break; } case s_res_or_resp_H: if (ch == 'T') { parser->type = HTTP_RESPONSE; UPDATE_STATE(s_res_HT); } else { if (UNLIKELY(ch != 'E')) { SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } parser->type = HTTP_REQUEST; parser->method = HTTP_HEAD; parser->index = 2; UPDATE_STATE(s_req_method); } break; case s_start_res: { parser->flags = 0; parser->content_length = ULLONG_MAX; switch (ch) { case 'H': UPDATE_STATE(s_res_H); break; case CR: case LF: break; default: SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } CALLBACK_NOTIFY(message_begin); break; } case s_res_H: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_res_HT); break; case s_res_HT: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_res_HTT); break; case s_res_HTT: STRICT_CHECK(ch != 'P'); UPDATE_STATE(s_res_HTTP); break; case s_res_HTTP: STRICT_CHECK(ch != '/'); UPDATE_STATE(s_res_first_http_major); break; case s_res_first_http_major: if (UNLIKELY(ch < '0' || ch > '9')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; UPDATE_STATE(s_res_http_major); break; /* major HTTP version or dot */ case s_res_http_major: { if (ch == '.') { UPDATE_STATE(s_res_first_http_minor); break; } if (!IS_NUM(ch)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major *= 10; parser->http_major += ch - '0'; if (UNLIKELY(parser->http_major > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } break; } /* first digit of minor HTTP version */ case s_res_first_http_minor: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; UPDATE_STATE(s_res_http_minor); break; /* minor HTTP version or end of request line */ case s_res_http_minor: { if (ch == ' ') { UPDATE_STATE(s_res_first_status_code); break; } if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor *= 10; parser->http_minor += ch - '0'; if (UNLIKELY(parser->http_minor > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } break; } case s_res_first_status_code: { if (!IS_NUM(ch)) { if (ch == ' ') { break; } SET_ERRNO(HPE_INVALID_STATUS); goto error; } parser->status_code = ch - '0'; UPDATE_STATE(s_res_status_code); break; } case s_res_status_code: { if (!IS_NUM(ch)) { switch (ch) { case ' ': UPDATE_STATE(s_res_status_start); break; case CR: UPDATE_STATE(s_res_line_almost_done); break; case LF: UPDATE_STATE(s_header_field_start); break; default: SET_ERRNO(HPE_INVALID_STATUS); goto error; } break; } parser->status_code *= 10; parser->status_code += ch - '0'; if (UNLIKELY(parser->status_code > 999)) { SET_ERRNO(HPE_INVALID_STATUS); goto error; } break; } case s_res_status_start: { if (ch == CR) { UPDATE_STATE(s_res_line_almost_done); break; } if (ch == LF) { UPDATE_STATE(s_header_field_start); break; } MARK(status); UPDATE_STATE(s_res_status); parser->index = 0; break; } case s_res_status: if (ch == CR) { UPDATE_STATE(s_res_line_almost_done); CALLBACK_DATA(status); break; } if (ch == LF) { UPDATE_STATE(s_header_field_start); CALLBACK_DATA(status); break; } break; case s_res_line_almost_done: STRICT_CHECK(ch != LF); UPDATE_STATE(s_header_field_start); break; case s_start_req: { if (ch == CR || ch == LF) break; parser->flags = 0; parser->content_length = ULLONG_MAX; if (UNLIKELY(!IS_ALPHA(ch))) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } parser->method = (enum http_method) 0; parser->index = 1; switch (ch) { case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; case 'D': parser->method = HTTP_DELETE; break; case 'G': parser->method = HTTP_GET; break; case 'H': parser->method = HTTP_HEAD; break; case 'L': parser->method = HTTP_LOCK; break; case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; case 'N': parser->method = HTTP_NOTIFY; break; case 'O': parser->method = HTTP_OPTIONS; break; case 'P': parser->method = HTTP_POST; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break; case 'R': parser->method = HTTP_REPORT; break; case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; case 'T': parser->method = HTTP_TRACE; break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; default: SET_ERRNO(HPE_INVALID_METHOD); goto error; } UPDATE_STATE(s_req_method); CALLBACK_NOTIFY(message_begin); break; } case s_req_method: { const char *matcher; if (UNLIKELY(ch == '\0')) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } matcher = method_strings[parser->method]; if (ch == ' ' && matcher[parser->index] == '\0') { UPDATE_STATE(s_req_spaces_before_url); } else if (ch == matcher[parser->index]) { ; /* nada */ } else if (parser->method == HTTP_CONNECT) { if (parser->index == 1 && ch == 'H') { parser->method = HTTP_CHECKOUT; } else if (parser->index == 2 && ch == 'P') { parser->method = HTTP_COPY; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->method == HTTP_MKCOL) { if (parser->index == 1 && ch == 'O') { parser->method = HTTP_MOVE; } else if (parser->index == 1 && ch == 'E') { parser->method = HTTP_MERGE; } else if (parser->index == 1 && ch == '-') { parser->method = HTTP_MSEARCH; } else if (parser->index == 2 && ch == 'A') { parser->method = HTTP_MKACTIVITY; } else if (parser->index == 3 && ch == 'A') { parser->method = HTTP_MKCALENDAR; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->method == HTTP_SUBSCRIBE) { if (parser->index == 1 && ch == 'E') { parser->method = HTTP_SEARCH; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->index == 1 && parser->method == HTTP_POST) { if (ch == 'R') { parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ } else if (ch == 'U') { parser->method = HTTP_PUT; /* or HTTP_PURGE */ } else if (ch == 'A') { parser->method = HTTP_PATCH; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->index == 2) { if (parser->method == HTTP_PUT) { if (ch == 'R') { parser->method = HTTP_PURGE; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->method == HTTP_UNLOCK) { if (ch == 'S') { parser->method = HTTP_UNSUBSCRIBE; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { parser->method = HTTP_PROPPATCH; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } ++parser->index; break; } case s_req_spaces_before_url: { if (ch == ' ') break; MARK(url); if (parser->method == HTTP_CONNECT) { UPDATE_STATE(s_req_server_start); } UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } break; } case s_req_schema: case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: { switch (ch) { /* No whitespace allowed here */ case ' ': case CR: case LF: SET_ERRNO(HPE_INVALID_URL); goto error; default: UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } } break; } case s_req_server: case s_req_server_with_at: case s_req_path: case s_req_query_string_start: case s_req_query_string: case s_req_fragment_start: case s_req_fragment: { switch (ch) { case ' ': UPDATE_STATE(s_req_http_start); CALLBACK_DATA(url); break; case CR: case LF: parser->http_major = 0; parser->http_minor = 9; UPDATE_STATE((ch == CR) ? s_req_line_almost_done : s_header_field_start); CALLBACK_DATA(url); break; default: UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } } break; } case s_req_http_start: switch (ch) { case 'H': UPDATE_STATE(s_req_http_H); break; case ' ': break; default: SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } break; case s_req_http_H: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_req_http_HT); break; case s_req_http_HT: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_req_http_HTT); break; case s_req_http_HTT: STRICT_CHECK(ch != 'P'); UPDATE_STATE(s_req_http_HTTP); break; case s_req_http_HTTP: STRICT_CHECK(ch != '/'); UPDATE_STATE(s_req_first_http_major); break; /* first digit of major HTTP version */ case s_req_first_http_major: if (UNLIKELY(ch < '1' || ch > '9')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; UPDATE_STATE(s_req_http_major); break; /* major HTTP version or dot */ case s_req_http_major: { if (ch == '.') { UPDATE_STATE(s_req_first_http_minor); break; } if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major *= 10; parser->http_major += ch - '0'; if (UNLIKELY(parser->http_major > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } break; } /* first digit of minor HTTP version */ case s_req_first_http_minor: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; UPDATE_STATE(s_req_http_minor); break; /* minor HTTP version or end of request line */ case s_req_http_minor: { if (ch == CR) { UPDATE_STATE(s_req_line_almost_done); break; } if (ch == LF) { UPDATE_STATE(s_header_field_start); break; } /* XXX allow spaces after digit? */ if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor *= 10; parser->http_minor += ch - '0'; if (UNLIKELY(parser->http_minor > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } break; } /* end of request line */ case s_req_line_almost_done: { if (UNLIKELY(ch != LF)) { SET_ERRNO(HPE_LF_EXPECTED); goto error; } UPDATE_STATE(s_header_field_start); break; } case s_header_field_start: { if (ch == CR) { UPDATE_STATE(s_headers_almost_done); break; } if (ch == LF) { /* they might be just sending \n instead of \r\n so this would be * the second \n to denote the end of headers*/ UPDATE_STATE(s_headers_almost_done); REEXECUTE(); } c = TOKEN(ch); if (UNLIKELY(!c)) { SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } MARK(header_field); parser->index = 0; UPDATE_STATE(s_header_field); switch (c) { case 'c': parser->header_state = h_C; break; case 'p': parser->header_state = h_matching_proxy_connection; break; case 't': parser->header_state = h_matching_transfer_encoding; break; case 'u': parser->header_state = h_matching_upgrade; break; default: parser->header_state = h_general; break; } break; } case s_header_field: { const char* start = p; for (; p != data + len; p++) { ch = *p; c = TOKEN(ch); if (!c) break; switch (parser->header_state) { case h_general: break; case h_C: parser->index++; parser->header_state = (c == 'o' ? h_CO : h_general); break; case h_CO: parser->index++; parser->header_state = (c == 'n' ? h_CON : h_general); break; case h_CON: parser->index++; switch (c) { case 'n': parser->header_state = h_matching_connection; break; case 't': parser->header_state = h_matching_content_length; break; default: parser->header_state = h_general; break; } break; /* connection */ case h_matching_connection: parser->index++; if (parser->index > sizeof(CONNECTION)-1 || c != CONNECTION[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(CONNECTION)-2) { parser->header_state = h_connection; } break; /* proxy-connection */ case h_matching_proxy_connection: parser->index++; if (parser->index > sizeof(PROXY_CONNECTION)-1 || c != PROXY_CONNECTION[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { parser->header_state = h_connection; } break; /* content-length */ case h_matching_content_length: parser->index++; if (parser->index > sizeof(CONTENT_LENGTH)-1 || c != CONTENT_LENGTH[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { parser->header_state = h_content_length; } break; /* transfer-encoding */ case h_matching_transfer_encoding: parser->index++; if (parser->index > sizeof(TRANSFER_ENCODING)-1 || c != TRANSFER_ENCODING[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { parser->header_state = h_transfer_encoding; } break; /* upgrade */ case h_matching_upgrade: parser->index++; if (parser->index > sizeof(UPGRADE)-1 || c != UPGRADE[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(UPGRADE)-2) { parser->header_state = h_upgrade; } break; case h_connection: case h_content_length: case h_transfer_encoding: case h_upgrade: if (ch != ' ') parser->header_state = h_general; break; default: assert(0 && "Unknown header_state"); break; } } COUNT_HEADER_SIZE(p - start); if (p == data + len) { --p; break; } if (ch == ':') { UPDATE_STATE(s_header_value_discard_ws); CALLBACK_DATA(header_field); break; } SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } case s_header_value_discard_ws: if (ch == ' ' || ch == '\t') break; if (ch == CR) { UPDATE_STATE(s_header_value_discard_ws_almost_done); break; } if (ch == LF) { UPDATE_STATE(s_header_value_discard_lws); break; } /* FALLTHROUGH */ case s_header_value_start: { MARK(header_value); UPDATE_STATE(s_header_value); parser->index = 0; c = LOWER(ch); switch (parser->header_state) { case h_upgrade: parser->flags |= F_UPGRADE; parser->header_state = h_general; break; case h_transfer_encoding: /* looking for 'Transfer-Encoding: chunked' */ if ('c' == c) { parser->header_state = h_matching_transfer_encoding_chunked; } else { parser->header_state = h_general; } break; case h_content_length: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } parser->content_length = ch - '0'; break; case h_connection: /* looking for 'Connection: keep-alive' */ if (c == 'k') { parser->header_state = h_matching_connection_keep_alive; /* looking for 'Connection: close' */ } else if (c == 'c') { parser->header_state = h_matching_connection_close; } else if (c == 'u') { parser->header_state = h_matching_connection_upgrade; } else { parser->header_state = h_matching_connection_token; } break; /* Multi-value `Connection` header */ case h_matching_connection_token_start: break; default: parser->header_state = h_general; break; } break; } case s_header_value: { const char* start = p; enum header_states h_state = (enum header_states) parser->header_state; for (; p != data + len; p++) { ch = *p; if (ch == CR) { UPDATE_STATE(s_header_almost_done); parser->header_state = h_state; CALLBACK_DATA(header_value); break; } if (ch == LF) { UPDATE_STATE(s_header_almost_done); COUNT_HEADER_SIZE(p - start); parser->header_state = h_state; CALLBACK_DATA_NOADVANCE(header_value); REEXECUTE(); } c = LOWER(ch); switch (h_state) { case h_general: { const char* p_cr; const char* p_lf; size_t limit = data + len - p; limit = MIN(limit, HTTP_MAX_HEADER_SIZE); p_cr = (const char*) memchr(p, CR, limit); p_lf = (const char*) memchr(p, LF, limit); if (p_cr != NULL) { if (p_lf != NULL && p_cr >= p_lf) p = p_lf; else p = p_cr; } else if (UNLIKELY(p_lf != NULL)) { p = p_lf; } else { p = data + len; } --p; break; } case h_connection: case h_transfer_encoding: assert(0 && "Shouldn't get here."); break; case h_content_length: { uint64_t t; if (ch == ' ') break; if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); parser->header_state = h_state; goto error; } t = parser->content_length; t *= 10; t += ch - '0'; /* Overflow? Test against a conservative limit for simplicity. */ if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); parser->header_state = h_state; goto error; } parser->content_length = t; break; } /* Transfer-Encoding: chunked */ case h_matching_transfer_encoding_chunked: parser->index++; if (parser->index > sizeof(CHUNKED)-1 || c != CHUNKED[parser->index]) { h_state = h_general; } else if (parser->index == sizeof(CHUNKED)-2) { h_state = h_transfer_encoding_chunked; } break; case h_matching_connection_token_start: /* looking for 'Connection: keep-alive' */ if (c == 'k') { h_state = h_matching_connection_keep_alive; /* looking for 'Connection: close' */ } else if (c == 'c') { h_state = h_matching_connection_close; } else if (c == 'u') { h_state = h_matching_connection_upgrade; } else if (STRICT_TOKEN(c)) { h_state = h_matching_connection_token; } else if (c == ' ' || c == '\t') { /* Skip lws */ } else { h_state = h_general; } break; /* looking for 'Connection: keep-alive' */ case h_matching_connection_keep_alive: parser->index++; if (parser->index > sizeof(KEEP_ALIVE)-1 || c != KEEP_ALIVE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(KEEP_ALIVE)-2) { h_state = h_connection_keep_alive; } break; /* looking for 'Connection: close' */ case h_matching_connection_close: parser->index++; if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(CLOSE)-2) { h_state = h_connection_close; } break; /* looking for 'Connection: upgrade' */ case h_matching_connection_upgrade: parser->index++; if (parser->index > sizeof(UPGRADE) - 1 || c != UPGRADE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(UPGRADE)-2) { h_state = h_connection_upgrade; } break; case h_matching_connection_token: if (ch == ',') { h_state = h_matching_connection_token_start; parser->index = 0; } break; case h_transfer_encoding_chunked: if (ch != ' ') h_state = h_general; break; case h_connection_keep_alive: case h_connection_close: case h_connection_upgrade: if (ch == ',') { if (h_state == h_connection_keep_alive) { parser->flags |= F_CONNECTION_KEEP_ALIVE; } else if (h_state == h_connection_close) { parser->flags |= F_CONNECTION_CLOSE; } else if (h_state == h_connection_upgrade) { parser->flags |= F_CONNECTION_UPGRADE; } h_state = h_matching_connection_token_start; parser->index = 0; } else if (ch != ' ') { h_state = h_matching_connection_token; } break; default: UPDATE_STATE(s_header_value); h_state = h_general; break; } } parser->header_state = h_state; COUNT_HEADER_SIZE(p - start); if (p == data + len) --p; break; } case s_header_almost_done: { STRICT_CHECK(ch != LF); UPDATE_STATE(s_header_value_lws); break; } case s_header_value_lws: { if (ch == ' ' || ch == '\t') { UPDATE_STATE(s_header_value_start); REEXECUTE(); } /* finished the header */ switch (parser->header_state) { case h_connection_keep_alive: parser->flags |= F_CONNECTION_KEEP_ALIVE; break; case h_connection_close: parser->flags |= F_CONNECTION_CLOSE; break; case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; case h_connection_upgrade: parser->flags |= F_CONNECTION_UPGRADE; break; default: break; } UPDATE_STATE(s_header_field_start); REEXECUTE(); } case s_header_value_discard_ws_almost_done: { STRICT_CHECK(ch != LF); UPDATE_STATE(s_header_value_discard_lws); break; } case s_header_value_discard_lws: { if (ch == ' ' || ch == '\t') { UPDATE_STATE(s_header_value_discard_ws); break; } else { switch (parser->header_state) { case h_connection_keep_alive: parser->flags |= F_CONNECTION_KEEP_ALIVE; break; case h_connection_close: parser->flags |= F_CONNECTION_CLOSE; break; case h_connection_upgrade: parser->flags |= F_CONNECTION_UPGRADE; break; case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; default: break; } /* header value was empty */ MARK(header_value); UPDATE_STATE(s_header_field_start); CALLBACK_DATA_NOADVANCE(header_value); REEXECUTE(); } } case s_headers_almost_done: { STRICT_CHECK(ch != LF); if (parser->flags & F_TRAILING) { /* End of a chunked request */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); break; } UPDATE_STATE(s_headers_done); /* Set this here so that on_headers_complete() callbacks can see it */ parser->upgrade = ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == (F_UPGRADE | F_CONNECTION_UPGRADE) || parser->method == HTTP_CONNECT); /* Here we call the headers_complete callback. This is somewhat * different than other callbacks because if the user returns 1, we * will interpret that as saying that this message has no body. This * is needed for the annoying case of recieving a response to a HEAD * request. * * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so * we have to simulate it by handling a change in errno below. */ if (settings->on_headers_complete) { switch (settings->on_headers_complete(parser)) { case 0: break; case 1: parser->flags |= F_SKIPBODY; break; default: SET_ERRNO(HPE_CB_headers_complete); RETURN(p - data); /* Error */ } } if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { RETURN(p - data); } REEXECUTE(); } case s_headers_done: { STRICT_CHECK(ch != LF); parser->nread = 0; /* Exit, the rest of the connect is in a different protocol. */ if (parser->upgrade) { UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); RETURN((p - data) + 1); } if (parser->flags & F_SKIPBODY) { UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header */ UPDATE_STATE(s_chunk_size_start); } else { if (parser->content_length == 0) { /* Content-Length header given but zero: Content-Length: 0\r\n */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->content_length != ULLONG_MAX) { /* Content-Length header given and non-zero */ UPDATE_STATE(s_body_identity); } else { if (parser->type == HTTP_REQUEST || !http_message_needs_eof(parser)) { /* Assume content-length 0 - read the next */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else { /* Read body until EOF */ UPDATE_STATE(s_body_identity_eof); } } } break; } case s_body_identity: { uint64_t to_read = MIN(parser->content_length, (uint64_t) ((data + len) - p)); assert(parser->content_length != 0 && parser->content_length != ULLONG_MAX); /* The difference between advancing content_length and p is because * the latter will automaticaly advance on the next loop iteration. * Further, if content_length ends up at 0, we want to see the last * byte again for our message complete callback. */ MARK(body); parser->content_length -= to_read; p += to_read - 1; if (parser->content_length == 0) { UPDATE_STATE(s_message_done); /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. * * The alternative to doing this is to wait for the next byte to * trigger the data callback, just as in every other case. The * problem with this is that this makes it difficult for the test * harness to distinguish between complete-on-EOF and * complete-on-length. It's not clear that this distinction is * important for applications, but let's keep it for now. */ CALLBACK_DATA_(body, p - body_mark + 1, p - data); REEXECUTE(); } break; } /* read until EOF */ case s_body_identity_eof: MARK(body); p = data + len - 1; break; case s_message_done: UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); break; case s_chunk_size_start: { assert(parser->nread == 1); assert(parser->flags & F_CHUNKED); unhex_val = unhex[(unsigned char)ch]; if (UNLIKELY(unhex_val == -1)) { SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } parser->content_length = unhex_val; UPDATE_STATE(s_chunk_size); break; } case s_chunk_size: { uint64_t t; assert(parser->flags & F_CHUNKED); if (ch == CR) { UPDATE_STATE(s_chunk_size_almost_done); break; } unhex_val = unhex[(unsigned char)ch]; if (unhex_val == -1) { if (ch == ';' || ch == ' ') { UPDATE_STATE(s_chunk_parameters); break; } SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } t = parser->content_length; t *= 16; t += unhex_val; /* Overflow? Test against a conservative limit for simplicity. */ if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } parser->content_length = t; break; } case s_chunk_parameters: { assert(parser->flags & F_CHUNKED); /* just ignore this shit. TODO check for overflow */ if (ch == CR) { UPDATE_STATE(s_chunk_size_almost_done); break; } break; } case s_chunk_size_almost_done: { assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; if (parser->content_length == 0) { parser->flags |= F_TRAILING; UPDATE_STATE(s_header_field_start); } else { UPDATE_STATE(s_chunk_data); } break; } case s_chunk_data: { uint64_t to_read = MIN(parser->content_length, (uint64_t) ((data + len) - p)); assert(parser->flags & F_CHUNKED); assert(parser->content_length != 0 && parser->content_length != ULLONG_MAX); /* See the explanation in s_body_identity for why the content * length and data pointers are managed this way. */ MARK(body); parser->content_length -= to_read; p += to_read - 1; if (parser->content_length == 0) { UPDATE_STATE(s_chunk_data_almost_done); } break; } case s_chunk_data_almost_done: assert(parser->flags & F_CHUNKED); assert(parser->content_length == 0); STRICT_CHECK(ch != CR); UPDATE_STATE(s_chunk_data_done); CALLBACK_DATA(body); break; case s_chunk_data_done: assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; UPDATE_STATE(s_chunk_size_start); break; default: assert(0 && "unhandled state"); SET_ERRNO(HPE_INVALID_INTERNAL_STATE); goto error; } } /* Run callbacks for any marks that we have leftover after we ran our of * bytes. There should be at most one of these set, so it's OK to invoke * them in series (unset marks will not result in callbacks). * * We use the NOADVANCE() variety of callbacks here because 'p' has already * overflowed 'data' and this allows us to correct for the off-by-one that * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' * value that's in-bounds). */ assert(((header_field_mark ? 1 : 0) + (header_value_mark ? 1 : 0) + (url_mark ? 1 : 0) + (body_mark ? 1 : 0) + (status_mark ? 1 : 0)) <= 1); CALLBACK_DATA_NOADVANCE(header_field); CALLBACK_DATA_NOADVANCE(header_value); CALLBACK_DATA_NOADVANCE(url); CALLBACK_DATA_NOADVANCE(body); CALLBACK_DATA_NOADVANCE(status); RETURN(len); error: if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { SET_ERRNO(HPE_UNKNOWN); } RETURN(p - data); } /* Does the parser need to see an EOF to find the end of the message? */ int http_message_needs_eof (const http_parser *parser) { if (parser->type == HTTP_REQUEST) { return 0; } /* See RFC 2616 section 4.4 */ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ parser->status_code == 204 || /* No Content */ parser->status_code == 304 || /* Not Modified */ parser->flags & F_SKIPBODY) { /* response to a HEAD request */ return 0; } if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { return 0; } return 1; } int http_should_keep_alive (const http_parser *parser) { if (parser->http_major > 0 && parser->http_minor > 0) { /* HTTP/1.1 */ if (parser->flags & F_CONNECTION_CLOSE) { return 0; } } else { /* HTTP/1.0 or earlier */ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { return 0; } } return !http_message_needs_eof(parser); } const char * http_method_str (enum http_method m) { return ELEM_AT(method_strings, m, ""); } void http_parser_init (http_parser *parser, enum http_parser_type t) { void *data = parser->data; /* preserve application data */ memset(parser, 0, sizeof(*parser)); parser->data = data; parser->type = t; parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); parser->http_errno = HPE_OK; } void http_parser_settings_init(http_parser_settings *settings) { memset(settings, 0, sizeof(*settings)); } const char * http_errno_name(enum http_errno err) { assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); return http_strerror_tab[err].name; } const char * http_errno_description(enum http_errno err) { assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); return http_strerror_tab[err].description; } static enum http_host_state http_parse_host_char(enum http_host_state s, const char ch) { switch(s) { case s_http_userinfo: case s_http_userinfo_start: if (ch == '@') { return s_http_host_start; } if (IS_USERINFO_CHAR(ch)) { return s_http_userinfo; } break; case s_http_host_start: if (ch == '[') { return s_http_host_v6_start; } if (IS_HOST_CHAR(ch)) { return s_http_host; } break; case s_http_host: if (IS_HOST_CHAR(ch)) { return s_http_host; } /* FALLTHROUGH */ case s_http_host_v6_end: if (ch == ':') { return s_http_host_port_start; } break; case s_http_host_v6: if (ch == ']') { return s_http_host_v6_end; } /* FALLTHROUGH */ case s_http_host_v6_start: if (IS_HEX(ch) || ch == ':' || ch == '.') { return s_http_host_v6; } break; case s_http_host_port: case s_http_host_port_start: if (IS_NUM(ch)) { return s_http_host_port; } break; default: break; } return s_http_host_dead; } static int http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { enum http_host_state s; const char *p; size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; u->field_data[UF_HOST].len = 0; s = found_at ? s_http_userinfo_start : s_http_host_start; for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { enum http_host_state new_s = http_parse_host_char(s, *p); if (new_s == s_http_host_dead) { return 1; } switch(new_s) { case s_http_host: if (s != s_http_host) { u->field_data[UF_HOST].off = p - buf; } u->field_data[UF_HOST].len++; break; case s_http_host_v6: if (s != s_http_host_v6) { u->field_data[UF_HOST].off = p - buf; } u->field_data[UF_HOST].len++; break; case s_http_host_port: if (s != s_http_host_port) { u->field_data[UF_PORT].off = p - buf; u->field_data[UF_PORT].len = 0; u->field_set |= (1 << UF_PORT); } u->field_data[UF_PORT].len++; break; case s_http_userinfo: if (s != s_http_userinfo) { u->field_data[UF_USERINFO].off = p - buf ; u->field_data[UF_USERINFO].len = 0; u->field_set |= (1 << UF_USERINFO); } u->field_data[UF_USERINFO].len++; break; default: break; } s = new_s; } /* Make sure we don't end somewhere unexpected */ switch (s) { case s_http_host_start: case s_http_host_v6_start: case s_http_host_v6: case s_http_host_port_start: case s_http_userinfo: case s_http_userinfo_start: return 1; default: break; } return 0; } int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, struct http_parser_url *u) { enum state s; const char *p; enum http_parser_url_fields uf, old_uf; int found_at = 0; u->port = u->field_set = 0; s = is_connect ? s_req_server_start : s_req_spaces_before_url; old_uf = UF_MAX; for (p = buf; p < buf + buflen; p++) { s = parse_url_char(s, *p); /* Figure out the next field that we're operating on */ switch (s) { case s_dead: return 1; /* Skip delimeters */ case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: case s_req_query_string_start: case s_req_fragment_start: continue; case s_req_schema: uf = UF_SCHEMA; break; case s_req_server_with_at: found_at = 1; /* FALLTROUGH */ case s_req_server: uf = UF_HOST; break; case s_req_path: uf = UF_PATH; break; case s_req_query_string: uf = UF_QUERY; break; case s_req_fragment: uf = UF_FRAGMENT; break; default: assert(!"Unexpected state"); return 1; } /* Nothing's changed; soldier on */ if (uf == old_uf) { u->field_data[uf].len++; continue; } u->field_data[uf].off = p - buf; u->field_data[uf].len = 1; u->field_set |= (1 << uf); old_uf = uf; } /* host must be present if there is a schema */ /* parsing http:///toto will fail */ if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { if (http_parse_host(buf, u, found_at) != 0) { return 1; } } /* CONNECT requests can only contain "hostname:port" */ if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { return 1; } if (u->field_set & (1 << UF_PORT)) { /* Don't bother with endp; we've already validated the string */ unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); /* Ports have a max value of 2^16 */ if (v > 0xffff) { return 1; } u->port = (uint16_t) v; } return 0; } void http_parser_pause(http_parser *parser, int paused) { /* Users should only be pausing/unpausing a parser that is not in an error * state. In non-debug builds, there's not much that we can do about this * other than ignore it. */ if (HTTP_PARSER_ERRNO(parser) == HPE_OK || HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); } else { assert(0 && "Attempting to pause parser in error state"); } } int http_body_is_final(const struct http_parser *parser) { return parser->state == s_message_done; } unsigned long http_parser_version(void) { return HTTP_PARSER_VERSION_MAJOR * 0x10000 | HTTP_PARSER_VERSION_MINOR * 0x00100 | HTTP_PARSER_VERSION_PATCH * 0x00001; } ================================================ FILE: http_parser.h ================================================ /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef http_parser_h #define http_parser_h #ifdef __cplusplus extern "C" { #endif /* Also update SONAME in the Makefile whenever you change these. */ #define HTTP_PARSER_VERSION_MAJOR 2 #define HTTP_PARSER_VERSION_MINOR 4 #define HTTP_PARSER_VERSION_PATCH 2 #include #if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) #include #include typedef __int8 int8_t; typedef unsigned __int8 uint8_t; typedef __int16 int16_t; typedef unsigned __int16 uint16_t; typedef __int32 int32_t; typedef unsigned __int32 uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else #include #endif /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run * faster */ #ifndef HTTP_PARSER_STRICT # define HTTP_PARSER_STRICT 1 #endif /* Maximium header size allowed. If the macro is not defined * before including this header then the default is used. To * change the maximum header size, define the macro in the build * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove * the effective limit on the size of the header, define the macro * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) */ #ifndef HTTP_MAX_HEADER_SIZE # define HTTP_MAX_HEADER_SIZE (80*1024) #endif typedef struct http_parser http_parser; typedef struct http_parser_settings http_parser_settings; /* Callbacks should return non-zero to indicate an error. The parser will * then halt execution. * * The one exception is on_headers_complete. In a HTTP_RESPONSE parser * returning '1' from on_headers_complete will tell the parser that it * should not expect a body. This is used when receiving a response to a * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: * chunked' headers that indicate the presence of a body. * * http_data_cb does not return data chunks. It will be called arbitrarily * many times for each string. E.G. you might get 10 callbacks for "on_url" * each providing just a few characters more data. */ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); typedef int (*http_cb) (http_parser*); /* Request Methods */ #define HTTP_METHOD_MAP(XX) \ XX(0, DELETE, DELETE) \ XX(1, GET, GET) \ XX(2, HEAD, HEAD) \ XX(3, POST, POST) \ XX(4, PUT, PUT) \ /* pathological */ \ XX(5, CONNECT, CONNECT) \ XX(6, OPTIONS, OPTIONS) \ XX(7, TRACE, TRACE) \ /* webdav */ \ XX(8, COPY, COPY) \ XX(9, LOCK, LOCK) \ XX(10, MKCOL, MKCOL) \ XX(11, MOVE, MOVE) \ XX(12, PROPFIND, PROPFIND) \ XX(13, PROPPATCH, PROPPATCH) \ XX(14, SEARCH, SEARCH) \ XX(15, UNLOCK, UNLOCK) \ /* subversion */ \ XX(16, REPORT, REPORT) \ XX(17, MKACTIVITY, MKACTIVITY) \ XX(18, CHECKOUT, CHECKOUT) \ XX(19, MERGE, MERGE) \ /* upnp */ \ XX(20, MSEARCH, M-SEARCH) \ XX(21, NOTIFY, NOTIFY) \ XX(22, SUBSCRIBE, SUBSCRIBE) \ XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ /* RFC-5789 */ \ XX(24, PATCH, PATCH) \ XX(25, PURGE, PURGE) \ /* CalDAV */ \ XX(26, MKCALENDAR, MKCALENDAR) \ enum http_method { #define XX(num, name, string) HTTP_##name = num, HTTP_METHOD_MAP(XX) #undef XX }; enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; /* Flag values for http_parser.flags field */ enum flags { F_CHUNKED = 1 << 0 , F_CONNECTION_KEEP_ALIVE = 1 << 1 , F_CONNECTION_CLOSE = 1 << 2 , F_CONNECTION_UPGRADE = 1 << 3 , F_TRAILING = 1 << 4 , F_UPGRADE = 1 << 5 , F_SKIPBODY = 1 << 6 }; /* Map for errno-related constants * * The provided argument should be a macro that takes 2 arguments. */ #define HTTP_ERRNO_MAP(XX) \ /* No error */ \ XX(OK, "success") \ \ /* Callback-related errors */ \ XX(CB_message_begin, "the on_message_begin callback failed") \ XX(CB_url, "the on_url callback failed") \ XX(CB_header_field, "the on_header_field callback failed") \ XX(CB_header_value, "the on_header_value callback failed") \ XX(CB_headers_complete, "the on_headers_complete callback failed") \ XX(CB_body, "the on_body callback failed") \ XX(CB_message_complete, "the on_message_complete callback failed") \ XX(CB_status, "the on_status callback failed") \ \ /* Parsing-related errors */ \ XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ XX(HEADER_OVERFLOW, \ "too many header bytes seen; overflow detected") \ XX(CLOSED_CONNECTION, \ "data received after completed connection: close message") \ XX(INVALID_VERSION, "invalid HTTP version") \ XX(INVALID_STATUS, "invalid HTTP status code") \ XX(INVALID_METHOD, "invalid HTTP method") \ XX(INVALID_URL, "invalid URL") \ XX(INVALID_HOST, "invalid host") \ XX(INVALID_PORT, "invalid port") \ XX(INVALID_PATH, "invalid path") \ XX(INVALID_QUERY_STRING, "invalid query string") \ XX(INVALID_FRAGMENT, "invalid fragment") \ XX(LF_EXPECTED, "LF character expected") \ XX(INVALID_HEADER_TOKEN, "invalid character in header") \ XX(INVALID_CONTENT_LENGTH, \ "invalid character in content-length header") \ XX(INVALID_CHUNK_SIZE, \ "invalid character in chunk size header") \ XX(INVALID_CONSTANT, "invalid constant string") \ XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ XX(STRICT, "strict mode assertion failed") \ XX(PAUSED, "parser is paused") \ XX(UNKNOWN, "an unknown error occurred") /* Define HPE_* values for each errno value above */ #define HTTP_ERRNO_GEN(n, s) HPE_##n, enum http_errno { HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) }; #undef HTTP_ERRNO_GEN /* Get an http_errno value from an http_parser */ #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) struct http_parser { /** PRIVATE **/ unsigned int type : 2; /* enum http_parser_type */ unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */ unsigned int state : 8; /* enum state from http_parser.c */ unsigned int header_state : 8; /* enum header_state from http_parser.c */ unsigned int index : 8; /* index into current matcher */ uint32_t nread; /* # bytes read in various scenarios */ uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ /** READ-ONLY **/ unsigned short http_major; unsigned short http_minor; unsigned int status_code : 16; /* responses only */ unsigned int method : 8; /* requests only */ unsigned int http_errno : 7; /* 1 = Upgrade header was present and the parser has exited because of that. * 0 = No upgrade header present. * Should be checked when http_parser_execute() returns in addition to * error checking. */ unsigned int upgrade : 1; /** PUBLIC **/ void *data; /* A pointer to get hook to the "connection" or "socket" object */ }; struct http_parser_settings { http_cb on_message_begin; http_data_cb on_url; http_data_cb on_status; http_data_cb on_header_field; http_data_cb on_header_value; http_cb on_headers_complete; http_data_cb on_body; http_cb on_message_complete; }; enum http_parser_url_fields { UF_SCHEMA = 0 , UF_HOST = 1 , UF_PORT = 2 , UF_PATH = 3 , UF_QUERY = 4 , UF_FRAGMENT = 5 , UF_USERINFO = 6 , UF_MAX = 7 }; /* Result structure for http_parser_parse_url(). * * Callers should index into field_data[] with UF_* values iff field_set * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and * because we probably have padding left over), we convert any port to * a uint16_t. */ struct http_parser_url { uint16_t field_set; /* Bitmask of (1 << UF_*) values */ uint16_t port; /* Converted UF_PORT string */ struct { uint16_t off; /* Offset into buffer in which field starts */ uint16_t len; /* Length of run in buffer */ } field_data[UF_MAX]; }; /* Returns the library version. Bits 16-23 contain the major version number, * bits 8-15 the minor version number and bits 0-7 the patch level. * Usage example: * * unsigned long version = http_parser_version(); * unsigned major = (version >> 16) & 255; * unsigned minor = (version >> 8) & 255; * unsigned patch = version & 255; * printf("http_parser v%u.%u.%u\n", major, minor, patch); */ unsigned long http_parser_version(void); void http_parser_init(http_parser *parser, enum http_parser_type type); /* Initialize http_parser_settings members to 0 */ void http_parser_settings_init(http_parser_settings *settings); /* Executes the parser. Returns number of parsed bytes. Sets * `parser->http_errno` on error. */ size_t http_parser_execute(http_parser *parser, const http_parser_settings *settings, const char *data, size_t len); /* If http_should_keep_alive() in the on_headers_complete or * on_message_complete callback returns 0, then this should be * the last message on the connection. * If you are the server, respond with the "Connection: close" header. * If you are the client, close the connection. */ int http_should_keep_alive(const http_parser *parser); /* Returns a string version of the HTTP method. */ const char *http_method_str(enum http_method m); /* Return a string name of the given error */ const char *http_errno_name(enum http_errno err); /* Return a string description of the given error */ const char *http_errno_description(enum http_errno err); /* Parse a URL; return nonzero on failure */ int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, struct http_parser_url *u); /* Pause or un-pause the parser; a nonzero value pauses */ void http_parser_pause(http_parser *parser, int paused); /* Checks if this is the final chunk of the body. */ int http_body_is_final(const http_parser *parser); #ifdef __cplusplus } #endif #endif ================================================ FILE: java/Makefile ================================================ JAVAC=javac JAR=jar JAVAFILES=\ com/omniti/labs/FqClient.java \ com/omniti/labs/FqClientImplDebug.java \ com/omniti/labs/FqClientImplInterface.java \ com/omniti/labs/FqClientImplNoop.java \ com/omniti/labs/FqCommand.java \ com/omniti/labs/FqCommandProtocolError.java \ com/omniti/labs/FqDataProtocolError.java \ com/omniti/labs/FqHeartbeatException.java \ com/omniti/labs/FqMessage.java all: fqclient.jar fqclient.jar: $(JAVAFILES:%=src/%) mkdir -p classes (cd src && $(JAVAC) -d ../classes $(JAVAFILES)) $(JAR) cf $@ -C classes . fq_rcvr.class: fq_rcvr.java fqclient.jar $(JAVAC) -cp fqclient.jar fq_rcvr.java clean: rm -f fqclient.jar rm -f *.class rm -rf classes ================================================ FILE: java/fq_rcvr.java ================================================ import com.circonus.FqClient; import com.circonus.FqClientImplDebug; import com.circonus.FqClientImplNoop; import com.circonus.FqClientImplInterface; import com.circonus.FqCommand; import com.circonus.FqMessage; public class fq_rcvr { private static class FqTest extends FqClientImplDebug { private FqClient client; private long count = 0; private long incr = 0; private long s = 0; public void setClient(FqClient c) { client = c; } public void dispatch(FqMessage m) { count++; incr++; client.send(m); } public void dispatchAuth(FqCommand.Auth a) { if(a.success()) { client.setHeartbeat(500); FqCommand.BindRequest breq = new FqCommand.BindRequest( "maryland", "prefix:\"test.prefix.\" sample(1)", false ); client.send(breq); } } public void showRate() { long now = System.nanoTime(); if(s != 0) { System.err.println(count + " [" + 1000000000.0 * (double)incr/((double)now-s) + " m/s]"); incr = 0; } s = now; } } public static void main(String args[]) { if(args.length != 4) { System.err.println(": "); System.exit(-1); } System.err.println(args[0]); FqClient client = null; FqTest impl = new FqTest(); try { client = new FqClient(impl); client.creds(args[0], new Integer(args[1]), args[2], args[3]); client.connect(); } catch(Exception e) { throw new RuntimeException(e); } while(true) { client.send(new FqCommand.StatusRequest()); try { Thread.sleep(1000); } catch(InterruptedException ignore) { } } //if(client != null) client.shutdown(); } } ================================================ FILE: java/pom.xml ================================================ 4.0.0 com.circonus fqclient 0.0.1 jar Fq Java Client Fq Client for Java https://github.com/circonus-labs/fq BSD https://github.com/circonus-labs/fq/blob/master/LICENSE repo https://github.com/circonus-labs/fq postwait Theo Schlossnagle theo.schlossnagle (at) circonus.com org.apache.maven.plugins maven-compiler-plugin 2.3.1 1.5 1.5 org.apache.maven.plugins maven-deploy-plugin 2.7 org.apache.maven.plugins maven-source-plugin 2.3 attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin 2.10.1 attach-javadocs jar -Xdoclint:none org.sonatype.plugins nexus-staging-maven-plugin 1.6.3 true ossrh https://oss.sonatype.org/ false org.apache.maven.plugins maven-gpg-plugin 1.5 sign-artifacts verify sign org.apache.maven.plugins maven-release-plugin 2.5 true false release deploy ================================================ FILE: java/src/com/omniti/labs/FqClient.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.Socket; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.nio.channels.Selector; import java.nio.channels.spi.AbstractSelector; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.ArrayDeque; import java.util.UUID; import com.omniti.labs.FqClientImplInterface; import com.omniti.labs.FqCommand; import com.omniti.labs.FqMessage; public class FqClient { public final static int FQ_PROTO_CMD_MODE = 0xcc50cafe; public final static int FQ_PROTO_DATA_MODE = 0xcc50face; public final static int FQ_PROTO_PEER_MODE = 0xcc50fade; private int mode; private String host; private SocketAddress hostaddr; private int port; private String user; private String queue; private String queue_type; private String pass; /* private int q_stall_time; private int qmaxlen; */ private final FqMessage endpost = new FqMessage(); private volatile boolean stop = false; private volatile boolean shutting_down = false; private boolean data_ready = false; private short cmd_hb_ms = 0; private short last_cmd_hb_ms = 0; private long cmd_hb_last; private long cmd_hb_last_sent = 0; private FqClientImplInterface impl; private AbstractSelector cmd_selector; private SocketChannel cmd_socket; private ByteBuffer cmd_in_buff; private ByteBuffer data_in_buff; private AbstractSelector data_selector; private SelectionKey data_skey; private SocketChannel data_socket; private Object keylock = new Object(); private boolean connected = false; private byte client_key[]; private Thread worker; private Thread data_worker; private Thread back_worker; private Thread sender_worker; private final Object sender_worker_lock = new Object(); private AtomicInteger qlen; private ConcurrentLinkedQueue cmdq; private LinkedBlockingQueue q; private LinkedBlockingQueue backq; private FqCommand.Heartbeat reusable_hb; private void initialize(FqClientImplInterface _impl, int _mode, int bsize) throws FqClientImplInterface.InUseException { impl = _impl; impl.setClient(this); mode = _mode; qlen = new AtomicInteger(0); cmdq = new ConcurrentLinkedQueue(); q = new LinkedBlockingQueue(); backq = new LinkedBlockingQueue(); reusable_hb = new FqCommand.Heartbeat(); cmd_in_buff = ByteBuffer.allocate(65536); data_in_buff = ByteBuffer.allocate(bsize); data_in_buff.mark(); } public FqClient(FqClientImplInterface _impl) throws FqClientImplInterface.InUseException { initialize(_impl, FQ_PROTO_DATA_MODE, 4194304); } public FqClient(FqClientImplInterface _impl, int _mode) throws FqClientImplInterface.InUseException { initialize(_impl, _mode, 4194304); } public FqClient(FqClientImplInterface _impl, int _mode, int _bsize) throws FqClientImplInterface.InUseException { initialize(_impl, _mode, _bsize); } public boolean isPeermode() { return (mode == FQ_PROTO_PEER_MODE); } public FqClientImplInterface getImpl() { return impl; } public void setHeartbeat(short ms) { if(ms != cmd_hb_ms) { cmd_hb_ms = ms; send(new FqCommand.HeartbeatRequest(cmd_hb_ms)); } } public void setHeartbeat(int ms) { setHeartbeat((short)ms); } public void set_backlog(int len, int stall) { /* qmaxlen = len; q_stall_time = stall; */ } public void send(FqCommand cmd) { cmdq.offer(cmd); } public void send(FqMessage m) { q.offer(m); } public void creds(int _port, String _source, String _pass) throws java.net.UnknownHostException { creds(null, _port, _source, _pass); } public void creds(String _host, String _source, String _pass) throws java.net.UnknownHostException { creds(_host, 0, _source, _pass); } public void creds(String _host, int _port, String _source, String _pass) throws java.net.UnknownHostException { int cidx; if(_host != null) host = _host; if(host == null) host = "127.0.0.1"; if(_port != 0) port = _port % 0xffff; if(port == 0) port = 8765; user = _source; if((cidx = user.indexOf("/")) >= 0) { queue = user.substring(cidx + 1); user = user.substring(0, cidx); if((cidx = queue.indexOf("/")) >= 0) { queue_type = queue.substring(cidx + 1); queue = queue.substring(0, cidx); } } if(queue == null || queue.length() == 0) queue = UUID.randomUUID().toString(); if(queue_type == null || queue_type.length() == 0) queue_type = "mem"; pass = _pass; hostaddr = new InetSocketAddress(host, port); } private boolean client_do_auth() throws IOException, FqCommandProtocolError { FqCommand.PlainAuth auth = new FqCommand.PlainAuth(user,pass,queue,queue_type); auth.send(this); auth.process(this); client_key = auth.getKey(); return (client_key != null); } public byte[] cmd_read_short_bytearray() throws IOException { cmd_in_buff.clear(); cmd_in_buff.limit(2); if(cmd_socket.read(cmd_in_buff) == -1) return null; cmd_in_buff.flip(); Short strlen = cmd_in_buff.getShort(); cmd_in_buff.clear(); cmd_in_buff.limit(strlen); if(cmd_socket.read(cmd_in_buff) == -1) return null; byte a[] = new byte[strlen]; cmd_in_buff.flip(); cmd_in_buff.get(a); return a; } public String cmd_read_short_string() throws IOException { byte a[] = cmd_read_short_bytearray(); if(a == null) return null; return new String(a, StandardCharsets.UTF_8); } public ByteBuffer cmd_read(int len) throws IOException { if(len > cmd_in_buff.capacity()) { ByteBuffer bb = ByteBuffer.allocate(len); if(cmd_socket.read(bb) == -1) return null; return bb; } cmd_in_buff.clear(); cmd_in_buff.limit(len); if(cmd_socket.read(cmd_in_buff) == -1) return null; return cmd_in_buff; } public long data_write(ByteBuffer bb) throws IOException { return data_socket.write(bb); } public long data_write(ByteBuffer[] bb) throws IOException { return data_socket.write(bb); } public int cmd_write(ByteBuffer bb) throws IOException { return cmd_socket.write(bb); } private boolean client_data_connect_internal() { boolean success = false; try { data_selector = (AbstractSelector)Selector.open(); data_socket = data_selector.provider().openSocketChannel(); data_socket.connect(hostaddr); data_socket.socket().setTcpNoDelay(true); ByteBuffer bb = ByteBuffer.allocate(4 + 2 + client_key.length); bb.order(ByteOrder.BIG_ENDIAN); bb.putInt(mode); bb.putShort((short)client_key.length); bb.put(client_key); bb.flip(); data_write(bb); data_socket.configureBlocking(false); data_skey = data_socket.register(data_selector, SelectionKey.OP_READ); } catch(Exception e) { impl.connectError(e); return success; } return success; } private void reset() { try { cmd_socket.close(); } catch (Exception ce) { ce.printStackTrace(); } try { data_socket.close(); } catch (Exception ce) { ce.printStackTrace(); } data_ready = false; cmd_hb_last_sent = 0; cmd_hb_last = 0; } private boolean client_connect_internal() { boolean success = false; // Force close reset(); try { cmd_selector = (AbstractSelector)Selector.open(); cmd_socket = cmd_selector.provider().openSocketChannel(); cmd_socket.connect(hostaddr); cmd_socket.socket().setTcpNoDelay(true); cmd_socket.socket().setSoTimeout(5000); setHeartbeat((cmd_hb_ms != 0) ? cmd_hb_ms : (short)10000); ByteBuffer bb = ByteBuffer.allocate(4); bb.order(ByteOrder.BIG_ENDIAN); bb.putInt(FQ_PROTO_CMD_MODE); bb.flip(); cmd_write(bb); success = client_do_auth(); } catch(Exception e) { impl.connectError(e); return success; } return success; } public void connect() { if(connected) return; worker = new Thread() { public void run() { worker(); } }; worker.setName("Fq-cmd(" + host + ")"); worker.start(); data_worker = new Thread() { public void run() { data_worker(); } }; data_worker.setName("Fq-data-in(" + host + ")"); data_worker.start(); back_worker = new Thread() { public void run() { back_worker(); } }; back_worker.setName("Fq-back(" + host + ")"); back_worker.start(); } public void recvHeartbeat() { cmd_hb_last = System.nanoTime(); } private void sendHeartbeat() throws IOException, FqHeartbeatException { long t = System.nanoTime(); if((t - cmd_hb_last_sent) > ((long)cmd_hb_ms * 1000000)) { reusable_hb.send(this); cmd_hb_last_sent = t; } if(cmd_hb_ms != last_cmd_hb_ms) { cmd_socket.socket().setSoTimeout(cmd_hb_ms * 2); last_cmd_hb_ms = cmd_hb_ms; } long hb_ns = (long)cmd_hb_ms * (long)3 * (long)1000000; if(cmd_hb_last != 0 && hb_ns != 0 & cmd_hb_last < (t - hb_ns)) { throw new FqHeartbeatException(); } } public int data_backlog() { return qlen.get(); } private void worker() { final ArrayDeque responses = new ArrayDeque<>(); int backoff = 0; while(!stop) { responses.clear(); try { if(client_connect_internal()) { data_ready = true; backoff = 0; } while(!stop && data_ready) { FqCommand entry; while(null != (entry = cmdq.poll())) { entry.send(this); if(entry.hasInBandResponse()) { responses.addLast(entry); } } sendHeartbeat(); entry = responses.pollFirst(); if(entry == null) entry = reusable_hb; entry.process(this); } } catch(Exception e) { impl.commandError(e); } try { Thread.sleep(backoff / 1000, (backoff % 1000) * 1000); } catch(InterruptedException ignore) {} backoff += 10000; } } private void data_worker_sender() { FqMessage m; while(!stop && cmd_socket.socket().isConnected()) { m = null; try { m = q.take(); } catch(InterruptedException ignore) { } if(m == endpost) break; if(m == null) continue; try { while(!m.send(this) && !stop && cmd_socket.socket().isConnected()) { synchronized(keylock) { data_skey.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE); try { keylock.wait(); } catch(InterruptedException ignore) { } } } } catch(IOException e) { impl.dataError(e); return; } catch(FqDataProtocolError e) { impl.dataError(e); return; } } } private void waitForData(long ms) throws IOException { data_selector.select(ms); if(data_skey.isWritable()) { synchronized(keylock) { data_skey.interestOps(SelectionKey.OP_READ); keylock.notify(); } } } public int blockingRead(byte dst[], int offset, int len) throws IOException { ByteBuffer rest = ByteBuffer.wrap(dst, offset, len); int nread = 0; // A quick non-blocking attempt if((nread = data_socket.read(rest)) == len) { return len; // done. } if (nread < 0) throw new IOException(); while(rest.position() < (offset+len)) { int readlen; waitForData(1000); while((readlen = data_socket.read(rest)) > 0) { nread += readlen; } if(readlen < 0) throw new IOException(); } return nread; } public ByteBuffer fill_data_buffer(boolean force) throws IOException { if(force || data_in_buff.position() == 0) { waitForData(1000); int rsize = data_socket.read(data_in_buff); if(rsize < 0) throw new IOException("bad read"); } return data_in_buff; } private void data_worker_receiver() { while(!stop && cmd_socket.socket().isConnected()) { FqMessage m; m = new FqMessage(); try { while(!stop && !m.read(this)) fill_data_buffer(true); } catch (IOException e) { impl.dataError(e); reset(); return; } catch (FqDataProtocolError e) { impl.dataError(e); reset(); return; } if(m.isComplete()) { do { try { backq.put(m); m = null; } catch(InterruptedException ignore) { } } while(m != null); } } } private void data_worker() { int backoff = 0; Error boom = null; while(!stop) { if(data_ready) { try { if(client_data_connect_internal()) { backoff = 0; } sender_worker = new Thread() { public void run() { data_worker_sender(); } }; sender_worker.setName("Fq-data-out(" + host + ")"); sender_worker.start(); try { data_worker_receiver(); } catch (Error e) { impl.dataError(e); q.offer(endpost); reset(); boom = e; } try { sender_worker.interrupt(); } catch (Exception ignore) { ignore.printStackTrace(); } synchronized(sender_worker_lock) { sender_worker.join(); } } catch(Exception e) { impl.dataError(e); } } if(backoff < 1000000) backoff += 10000; try { Thread.sleep(backoff / 1000, (backoff % 1000) * 1000); } catch(InterruptedException ignore) {} } shutting_down = true; backq.offer(endpost); if(boom != null) throw(boom); } private void back_worker() { while(!shutting_down) { try { FqMessage m = backq.take(); if(m == endpost) break; impl.dispatch(m); } catch(InterruptedException ignore) { } } } public void shutdown() { stop = true; q.offer(endpost); try { synchronized(sender_worker_lock) { sender_worker.join(); } data_worker.join(); worker.join(); } catch(InterruptedException ignore) { } } } ================================================ FILE: java/src/com/omniti/labs/FqClientImplDebug.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; import java.util.Date; import java.util.Map; public class FqClientImplDebug implements FqClientImplInterface { protected FqClient client = null; public void setClient(FqClient c) throws InUseException { if(client != null) throw new InUseException(); client = c; } protected void genericError(Throwable e) { e.printStackTrace(); } public void connectError(Throwable e) { genericError(e); } public void commandError(Throwable e) { genericError(e); } public void dataError(Throwable e) { genericError(e); } public void dispatch(FqMessage m) { byte b[] = m.getPayload(); int len = (b == null) ? 0 : b.length; System.err.println("m[" + len + "] via " + m.getRoute() + " over " + m.getExchange() + " from " + m.getSender()); } public void dispatch(FqCommand cmd) { System.err.println(cmd); } public void dispatchAuth(FqCommand.Auth cmd) { dispatch(cmd); } public void dispatchHeartbeatRequest(FqCommand.HeartbeatRequest cmd) { dispatch(cmd); } public void dispatchHeartbeat(FqCommand.Heartbeat cmd) { dispatch(cmd); } public void dispatchBindRequest(FqCommand.BindRequest cmd) { System.err.println(cmd.toString() + cmd.getBinding()); } public void dispatchUnbindRequest(FqCommand.UnbindRequest cmd) { System.err.println(cmd.toString() + cmd.getBinding() + " " + cmd.getSuccess()); } public void dispatchStatusRequest(FqCommand.StatusRequest cmd) { Date d = cmd.getDate(); Map m = cmd.getMap(); System.err.println("Status: " + d); for(Map.Entry entry : m.entrySet()) { System.err.println(" " + entry.getKey() + " : " + entry.getValue()); } } } ================================================ FILE: java/src/com/omniti/labs/FqClientImplInterface.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; import com.omniti.labs.FqCommand; public interface FqClientImplInterface { public class InUseException extends Exception { } public void setClient(FqClient c) throws InUseException; public void connectError(Throwable e); public void commandError(Throwable e); public void dataError(Throwable e); public void dispatch(FqMessage m); public void dispatch(FqCommand cmd); public void dispatchAuth(FqCommand.Auth cmd); public void dispatchHeartbeat(FqCommand.Heartbeat cmd); public void dispatchHeartbeatRequest(FqCommand.HeartbeatRequest cmd); public void dispatchBindRequest(FqCommand.BindRequest cmd); public void dispatchUnbindRequest(FqCommand.UnbindRequest cmd); public void dispatchStatusRequest(FqCommand.StatusRequest cmd); } ================================================ FILE: java/src/com/omniti/labs/FqClientImplNoop.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; public class FqClientImplNoop implements FqClientImplInterface { protected FqClient client; public void setClient(FqClient c) throws InUseException { if(client != null) throw new InUseException(); client = c; } protected void genericError(Throwable e) { } public void connectError(Throwable e) { genericError(e); } public void commandError(Throwable e) { genericError(e); } public void dataError(Throwable e) { genericError(e); } public void dispatch(FqMessage m) { } public void dispatch(FqCommand cmd) { } public void dispatchAuth(FqCommand.Auth cmd) { dispatch(cmd); } public void dispatchHeartbeatRequest(FqCommand.HeartbeatRequest cmd) { dispatch(cmd); } public void dispatchHeartbeat(FqCommand.Heartbeat cmd) { dispatch(cmd); } public void dispatchBindRequest(FqCommand.BindRequest cmd) { dispatch(cmd); }; public void dispatchUnbindRequest(FqCommand.UnbindRequest cmd) { dispatch(cmd); }; public void dispatchStatusRequest(FqCommand.StatusRequest cmd) { dispatch(cmd); }; } ================================================ FILE: java/src/com/omniti/labs/FqCommand.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.HashMap; import java.util.Map; import com.omniti.labs.FqClient; public abstract class FqCommand { public final static short FQ_PROTO_ERROR = (short)0xeeee; public final static short FQ_PROTO_HB = (short)0xbea7; public final static short FQ_PROTO_AUTH_CMD = (short)0xaaaa; public final static short FQ_PROTO_AUTH_PLAIN = (short)0; public final static short FQ_PROTO_AUTH_RESP = (short)0xaa00; public final static short FQ_PROTO_HBREQ = (short)0x4848; public final static short FQ_PROTO_BIND = (short)0xb171; public final static short FQ_PROTO_BINDREQ = (short)0xb170; public final static short FQ_PROTO_UNBIND = (short)0x171b; public final static short FQ_PROTO_UNBINDREQ = (short)0x071b; public final static short FQ_PROTO_STATUS = (short)0x57a7; public final static short FQ_PROTO_STATUSREQ = (short)0xc7a7; protected ByteBuffer bb; private boolean composed = false; public abstract short cmd(); public short response_cmd() { return cmd(); } public void compose() { } public void send(FqClient c) throws IOException { if(composed) { bb.position(0); } else { bb.putShort(cmd()); compose(); bb.flip(); composed = true; } c.cmd_write(bb); } public abstract boolean hasInBandResponse(); private static Heartbeat hb = new Heartbeat(); public Short getShortCmd(FqClient c) throws IOException, FqCommandProtocolError { Short cmd; do { ByteBuffer bb = c.cmd_read(2); if(bb == null) return null; bb.flip(); cmd = bb.getShort(); if(cmd == FQ_PROTO_HB) { c.recvHeartbeat(); } if(cmd == FQ_PROTO_ERROR) { throw new FqCommandProtocolError(c.cmd_read_short_string()); } } while(cmd() != FQ_PROTO_HB && cmd == FQ_PROTO_HB); return cmd; } public void process(FqClient c) throws IOException, FqCommandProtocolError { Short cmd = getShortCmd(c); // the hearbeat happens magically in getShortCmd if(cmd == null) { throw new FqCommandProtocolError("null cmd"); } else if(cmd != response_cmd()) { throw new FqCommandProtocolError(response_cmd(), cmd); } } protected void alloc(int size) { bb = ByteBuffer.allocate(size); bb.order(ByteOrder.BIG_ENDIAN); } public FqCommand(int size) { alloc(size + 2); } public FqCommand() { } public static class Heartbeat extends FqCommand { public Heartbeat() { super(0); } public boolean hasInBandResponse() { return false; } public short cmd() { return FQ_PROTO_HB; } } public static class HeartbeatRequest extends FqCommand { short ms; public HeartbeatRequest(int _ms) { super(2); ms = (short)(_ms & 0xffff); } public boolean hasInBandResponse() { return false; } public short cmd() { return FQ_PROTO_HBREQ; } public void compose() { bb.putShort(ms); } } public static abstract class Auth extends FqCommand { protected byte[] key = null; public boolean success() { return (key != null); } public byte[] getKey() { return key; } } public static class PlainAuth extends Auth { private byte b_user[]; private byte b_pass[]; private byte b_queue[]; private byte b_queue_type[]; public PlainAuth(String user, String pass, String queue, String queue_type) { b_user = user.getBytes(StandardCharsets.UTF_8); b_queue = queue.getBytes(StandardCharsets.UTF_8); b_queue_type = queue_type.getBytes(StandardCharsets.UTF_8); b_pass = pass.getBytes(StandardCharsets.UTF_8); int extra_space = 2 + /* plain */ 2 + b_user.length + /* user */ 2 + b_queue.length + 1 + b_queue_type.length + /* queue */ 2 + b_pass.length; alloc(2+extra_space); } public short cmd() { return FQ_PROTO_AUTH_CMD; } public boolean hasInBandResponse() { return true; } public void compose() { bb.putShort(FQ_PROTO_AUTH_PLAIN); bb.putShort((short)b_user.length); bb.put(b_user); bb.putShort((short)(b_queue.length + 1 + b_queue_type.length)); bb.put(b_queue); bb.put((byte) 0); bb.put(b_queue_type); bb.putShort((short)b_pass.length); bb.put(b_pass); } public void process(FqClient c) throws IOException, FqCommandProtocolError { Short cmd, len; bb = c.cmd_read(2); if(bb == null) return; bb.flip(); cmd = bb.getShort(); switch(cmd) { case FQ_PROTO_AUTH_RESP: key = c.cmd_read_short_bytearray(); if(key == null || key.length > 127) throw new FqCommandProtocolError("bad key"); break; case FQ_PROTO_ERROR: String error = c.cmd_read_short_string(); if(error != null) throw new FqCommandProtocolError(error); /* fall through */ default: throw new FqCommandProtocolError(cmd); } c.getImpl().dispatchAuth(this); } } public static class BindRequest extends FqCommand { public static final short FQ_BIND_PEER = 0x0001; public static final short FQ_BIND_PERM = 0x0110; public static final short FQ_BIND_TRANS = 0x0100; private Integer binding; private byte exchange[]; private byte program[]; private short flags; public BindRequest(byte _exchange[], String _program, short _flags) { program = _program.getBytes(StandardCharsets.UTF_8); exchange = _exchange; flags = _flags; int extra_space = 2 + /* flags */ 2 + exchange.length + /* user */ 2 + program.length; alloc(2+extra_space); } public BindRequest(byte _exchange[], String _program, boolean _peermode) { this(_exchange, _program, _peermode ? FQ_BIND_PEER : 0); } public BindRequest(String exchange, String p, boolean m) { this(exchange.getBytes(StandardCharsets.UTF_8), p, m); } public short cmd() { return FQ_PROTO_BINDREQ; } public short response_cmd() { return FQ_PROTO_BIND; } public boolean hasInBandResponse() { return true; } public void compose() { bb.putShort(flags); bb.putShort((short)exchange.length); bb.put(exchange); bb.putShort((short)program.length); bb.put(program); } public void process(FqClient c) throws IOException, FqCommandProtocolError { super.process(c); Integer cmd; bb = c.cmd_read(4); if(bb == null) return; bb.flip(); binding = bb.getInt(); c.getImpl().dispatchBindRequest(this); } public Integer getBinding() { return binding; } public byte[] getExchange() { return exchange; } } public static class UnbindRequest extends FqCommand { private BindRequest bind; private Integer success; public UnbindRequest(BindRequest b) { bind = b; int extra_space = 2 + /* peermode */ 4 + /* route_id */ 2 + bind.getExchange().length; alloc(2+extra_space); } public short cmd() { return FQ_PROTO_UNBINDREQ; } public short response_cmd() { return FQ_PROTO_UNBIND; } public boolean hasInBandResponse() { return true; } public void compose() { bb.putInt(bind.getBinding()); bb.putShort((short)bind.getExchange().length); bb.put(bind.getExchange()); } public void process(FqClient c) throws IOException, FqCommandProtocolError { super.process(c); Integer cmd; bb = c.cmd_read(4); if(bb == null) return; bb.flip(); success = bb.getInt(); c.getImpl().dispatchUnbindRequest(this); } public Integer getBinding() { return bind.getBinding(); } public boolean getSuccess() { return (success != null && success.equals(bind.getBinding())); } } public static class StatusRequest extends FqCommand { protected Date last_update; protected HashMap status = new HashMap(); public StatusRequest() { super(0); } public short cmd() { return FQ_PROTO_STATUSREQ; } public short response_cmd() { return FQ_PROTO_STATUS; } public boolean hasInBandResponse() { return true; } public void process(FqClient c) throws IOException, FqCommandProtocolError { super.process(c); last_update = new Date(); while(true) { String key = c.cmd_read_short_string(); if(key == null || key.length() == 0) break; Integer ivalue; bb = c.cmd_read(4); if(bb == null) throw new FqCommandProtocolError("status read failure"); bb.flip(); ivalue = bb.getInt(); Long value = ivalue & (long)0xffffffff; status.put(key,value); } c.getImpl().dispatchStatusRequest(this); } public Date getDate() { return last_update; } public Map getMap() { return status; } } } ================================================ FILE: java/src/com/omniti/labs/FqCommandProtocolError.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; public class FqCommandProtocolError extends Exception { private short expected; private short recvd; private String msg; public String toString() { if(msg != null) return msg; return "Expected " + String.format("0x%04x", expected) + ", but received " + String.format("0x%04x", recvd); } public FqCommandProtocolError(short _expected, short _recvd) { expected = _expected; recvd = _recvd; } public FqCommandProtocolError(short _expected) { expected = _expected; recvd = 0; } public FqCommandProtocolError(String _msg) { msg = _msg; recvd = 0; } } ================================================ FILE: java/src/com/omniti/labs/FqDataProtocolError.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; public class FqDataProtocolError extends Exception { public FqDataProtocolError(String a) { super(a); } } ================================================ FILE: java/src/com/omniti/labs/FqHeartbeatException.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; public class FqHeartbeatException extends Exception { public FqHeartbeatException() { super(); } } ================================================ FILE: java/src/com/omniti/labs/FqMessage.java ================================================ /* * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.omniti.labs; import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.UUID; import com.omniti.labs.FqDataProtocolError; public class FqMessage { public static class MsgId { protected byte d[]; public MsgId(byte v[]) { d = new byte[16]; System.arraycopy(v,0,d,0,16); } } private boolean _complete = false; private int nhops = -1; private InetAddress hops[]; private int route_len = -1; private byte route[]; private int sender_len = -1; private byte sender[]; private int exchange_len = -1; private byte exchange[]; private MsgId sender_msgid; private int payload_len = -1; private byte payload[]; private ByteBuffer[] iovec; public void setRoute(byte[] _r) { route = _r; route_len = _r.length; } public void setSender(byte[] _r) { sender = _r; sender_len = _r.length; } public void setExchange(byte[] _r) { exchange = _r; exchange_len = _r.length; } public void setMsgId() { UUID uuid = UUID.randomUUID(); ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuid.getMostSignificantBits()); bb.putLong(uuid.getLeastSignificantBits()); sender_msgid = new MsgId(bb.array()); } public void setPayload(byte[] _r) { payload = _r; payload_len = _r.length; } public String getRoute() { return new String(route, StandardCharsets.UTF_8); } public String getExchange() { return new String(exchange, StandardCharsets.UTF_8); } public String getSender() { return new String(sender, StandardCharsets.UTF_8); } public MsgId getMsgId() { return sender_msgid; } public byte[] getPayload() { return payload; } public InetAddress[] getPath() { return hops; } public boolean isComplete(boolean peermode) { if(peermode) { if(nhops < 0 || hops == null || sender_len < 0 || sender == null) return false; } if(route_len <= 0 || route == null || exchange_len <= 0 || exchange == null || payload_len < 0 || payload == null || sender_msgid == null) return false; return true; } public boolean isComplete() { return _complete; } public boolean read(FqClient c) throws IOException, FqDataProtocolError { boolean success; int limit, position; if(isComplete()) return true; ByteBuffer bb = c.fill_data_buffer(false); // Save fill location position = bb.position(); limit = bb.limit(); // Set read location bb.reset(); bb.limit(position); success = readInternal(c, bb); if(!success) { // compact while reading bb.compact(); // after compaction, position is a fill position position = bb.position(); // mark at zero (as we've compacted) bb.position(0); bb.mark(); // restore the fill position bb.position(position); } else { // restore fill position bb.limit(limit); bb.position(position); } return success; } private boolean readInternal(FqClient c, ByteBuffer bb) throws IOException, FqDataProtocolError { if(isComplete()) return true; if(exchange_len == -1) { if(bb.remaining() < 1) return false; byte len = bb.get(); exchange_len = len; bb.mark(); if(exchange_len <= 0 || exchange_len > 127) throw new FqDataProtocolError("invalid exchange_len: " + exchange_len); } if(exchange == null) { if(bb.remaining() < exchange_len) return false; exchange = new byte[exchange_len]; bb.get(exchange); bb.mark(); } if(route_len == -1) { if(bb.remaining() < 1) return false; route_len = (int)bb.get(); bb.mark(); if(route_len < 0 || route_len > 127) throw new FqDataProtocolError("invalid route_len: " + route_len); } if(route == null) { if(bb.remaining() < route_len) return false; route = new byte[route_len]; bb.get(route); bb.mark(); } if(sender_msgid == null) { if(bb.remaining() < 16) return false; byte[] m = new byte[16]; bb.get(m); bb.mark(); sender_msgid = new MsgId(m); } if(sender_len == -1) { if(bb.remaining() < 1) return false; sender_len = (int)bb.get(); bb.mark(); if(sender_len < 0 || sender_len > 127) throw new FqDataProtocolError("invalid sender_len: " + sender_len); } if(sender == null) { if(bb.remaining() < sender_len) return false; sender = new byte[sender_len]; bb.get(sender); bb.mark(); } if(nhops == -1) { if(bb.remaining() < 1) return false; nhops = (int)bb.get(); if(nhops < 0 || nhops > 32) throw new FqDataProtocolError("invalid nhops: " + nhops); bb.mark(); } if(hops == null) { if(bb.remaining() < nhops * 4) return false; hops = new InetAddress[nhops]; byte ip[] = new byte[4]; for(int i=0;i 0) { payload = new byte[payload_len]; if(bb.remaining() >= payload_len) { bb.get(payload); } else { int havenow = bb.remaining(); bb.get(payload, 0, havenow); int nread = c.blockingRead(payload, havenow, payload_len - havenow); if((nread+havenow) != payload_len) throw new FqDataProtocolError("payload read failure: " + nread + "+" + havenow + " != " + payload_len); } bb.mark(); } _complete = true; return true; } public boolean send(FqClient c) throws IOException, FqDataProtocolError { if(!isComplete(c.isPeermode())) throw new FqDataProtocolError("incomplete message"); if(iovec == null) { int i = 0; iovec = new ByteBuffer[c.isPeermode() ? 11 : 7]; iovec[i ] = ByteBuffer.allocate(1).put((byte)exchange_len); iovec[i++].flip(); iovec[i++] = ByteBuffer.wrap(exchange); iovec[i ] = ByteBuffer.allocate(1).put((byte)route_len); iovec[i++].flip(); iovec[i++] = ByteBuffer.wrap(route); iovec[i++] = ByteBuffer.wrap(sender_msgid.d); if(c.isPeermode()) { iovec[i ] = ByteBuffer.allocate(1).put((byte)sender_len); iovec[i++].flip(); iovec[i++] = ByteBuffer.wrap(sender); iovec[i ] = ByteBuffer.allocate(1).put((byte)nhops); iovec[i++].flip(); iovec[i ] = ByteBuffer.allocate(nhops * 4); for(int j=0; j cmdq; private LinkedBlockingQueue q; private LinkedBlockingQueue backq; private FqCommand.Heartbeat reusable_hb; private void initialize(FqClientImplInterface _impl, int _mode, int bsize) throws FqClientImplInterface.InUseException { impl = _impl; impl.setClient(this); mode = _mode; qlen = new AtomicInteger(0); cmdq = new ConcurrentLinkedQueue(); q = new LinkedBlockingQueue(); backq = new LinkedBlockingQueue(); reusable_hb = new FqCommand.Heartbeat(); cmd_in_buff = ByteBuffer.allocate(65536); data_in_buff = ByteBuffer.allocate(bsize); data_in_buff.mark(); } public FqClient(FqClientImplInterface _impl) throws FqClientImplInterface.InUseException { initialize(_impl, FQ_PROTO_DATA_MODE, 4194304); } public FqClient(FqClientImplInterface _impl, int _mode) throws FqClientImplInterface.InUseException { initialize(_impl, _mode, 4194304); } public FqClient(FqClientImplInterface _impl, int _mode, int _bsize) throws FqClientImplInterface.InUseException { initialize(_impl, _mode, _bsize); } public boolean isPeermode() { return (mode == FQ_PROTO_PEER_MODE); } public FqClientImplInterface getImpl() { return impl; } public void setHeartbeat(short ms) { if(ms != cmd_hb_ms) { cmd_hb_ms = ms; send(new FqCommand.HeartbeatRequest(cmd_hb_ms)); } } public void setHeartbeat(int ms) { setHeartbeat((short)ms); } public void set_backlog(int len, int stall) { /* qmaxlen = len; q_stall_time = stall; */ } public void send(FqCommand cmd) { cmdq.offer(cmd); } public void send(FqMessage m) { q.offer(m); } public void creds(int _port, String _source, String _pass) throws java.net.UnknownHostException { creds(null, _port, _source, _pass); } public void creds(String _host, String _source, String _pass) throws java.net.UnknownHostException { creds(_host, 0, _source, _pass); } public void creds(String _host, int _port, String _source, String _pass) throws java.net.UnknownHostException { int cidx; if(_host != null) host = _host; if(host == null) host = "127.0.0.1"; if(_port != 0) port = _port % 0xffff; if(port == 0) port = 8765; user = _source; if((cidx = user.indexOf("/")) >= 0) { queue = user.substring(cidx + 1); user = user.substring(0, cidx); if((cidx = queue.indexOf("/")) >= 0) { queue_type = queue.substring(cidx + 1); queue = queue.substring(0, cidx); } } if(queue == null || queue.length() == 0) queue = UUID.randomUUID().toString(); if(queue_type == null || queue_type.length() == 0) queue_type = "mem"; pass = _pass; hostaddr = new InetSocketAddress(host, port); } private boolean client_do_auth() throws IOException, FqCommandProtocolError { FqCommand.PlainAuth auth = new FqCommand.PlainAuth(user,pass,queue,queue_type); auth.send(this); auth.process(this); client_key = auth.getKey(); return (client_key != null); } public byte[] cmd_read_short_bytearray() throws IOException { cmd_in_buff.clear(); cmd_in_buff.limit(2); if(cmd_socket.read(cmd_in_buff) == -1) return null; cmd_in_buff.flip(); Short strlen = cmd_in_buff.getShort(); cmd_in_buff.clear(); cmd_in_buff.limit(strlen); if(cmd_socket.read(cmd_in_buff) == -1) return null; byte a[] = new byte[strlen]; cmd_in_buff.flip(); cmd_in_buff.get(a); return a; } public String cmd_read_short_string() throws IOException { byte a[] = cmd_read_short_bytearray(); if(a == null) return null; return new String(a, StandardCharsets.UTF_8); } public ByteBuffer cmd_read(int len) throws IOException { if(len > cmd_in_buff.capacity()) { ByteBuffer bb = ByteBuffer.allocate(len); if(cmd_socket.read(bb) == -1) return null; return bb; } cmd_in_buff.clear(); cmd_in_buff.limit(len); if(cmd_socket.read(cmd_in_buff) == -1) return null; return cmd_in_buff; } public long data_write(ByteBuffer bb) throws IOException { return data_socket.write(bb); } public long data_write(ByteBuffer[] bb) throws IOException { return data_socket.write(bb); } public int cmd_write(ByteBuffer bb) throws IOException { return cmd_socket.write(bb); } private boolean client_data_connect_internal() { boolean success = false; try { data_selector = (AbstractSelector)Selector.open(); data_socket = data_selector.provider().openSocketChannel(); data_socket.connect(hostaddr); data_socket.socket().setTcpNoDelay(true); ByteBuffer bb = ByteBuffer.allocate(4 + 2 + client_key.length); bb.order(ByteOrder.BIG_ENDIAN); bb.putInt(mode); bb.putShort((short)client_key.length); bb.put(client_key); bb.flip(); data_write(bb); data_socket.configureBlocking(false); data_skey = data_socket.register(data_selector, SelectionKey.OP_READ); } catch(Exception e) { impl.connectError(e); return success; } return success; } private void reset() { try { cmd_socket.close(); } catch (Exception ce) { ce.printStackTrace(); } try { data_socket.close(); } catch (Exception ce) { ce.printStackTrace(); } data_ready = false; cmd_hb_last_sent = 0; cmd_hb_last = 0; } private boolean client_connect_internal() { boolean success = false; // Force close reset(); try { cmd_selector = (AbstractSelector)Selector.open(); cmd_socket = cmd_selector.provider().openSocketChannel(); cmd_socket.connect(hostaddr); cmd_socket.socket().setTcpNoDelay(true); cmd_socket.socket().setSoTimeout(5000); setHeartbeat((cmd_hb_ms != 0) ? cmd_hb_ms : (short)10000); ByteBuffer bb = ByteBuffer.allocate(4); bb.order(ByteOrder.BIG_ENDIAN); bb.putInt(FQ_PROTO_CMD_MODE); bb.flip(); cmd_write(bb); success = client_do_auth(); } catch(Exception e) { impl.connectError(e); return success; } return success; } public void connect() { if(connected) return; worker = new Thread() { public void run() { worker(); } }; worker.setName("Fq-cmd(" + host + ")"); worker.start(); data_worker = new Thread() { public void run() { data_worker(); } }; data_worker.setName("Fq-data-in(" + host + ")"); data_worker.start(); back_worker = new Thread() { public void run() { back_worker(); } }; back_worker.setName("Fq-back(" + host + ")"); back_worker.start(); } public void recvHeartbeat() { cmd_hb_last = System.nanoTime(); } private void sendHeartbeat() throws IOException, FqHeartbeatException { long t = System.nanoTime(); if((t - cmd_hb_last_sent) > ((long)cmd_hb_ms * 1000000)) { reusable_hb.send(this); cmd_hb_last_sent = t; } if(cmd_hb_ms != last_cmd_hb_ms) { cmd_socket.socket().setSoTimeout(cmd_hb_ms * 2); last_cmd_hb_ms = cmd_hb_ms; } long hb_ns = (long)cmd_hb_ms * (long)3 * (long)1000000; if(cmd_hb_last != 0 && hb_ns != 0 & cmd_hb_last < (t - hb_ns)) { throw new FqHeartbeatException(); } } public int data_backlog() { return qlen.get(); } private void worker() { final ArrayDeque responses = new ArrayDeque<>(); int backoff = 0; while(!stop) { responses.clear(); try { if(client_connect_internal()) { data_ready = true; backoff = 0; } while(!stop && data_ready) { FqCommand entry; while(null != (entry = cmdq.poll())) { entry.send(this); if(entry.hasInBandResponse()) { responses.addLast(entry); } } sendHeartbeat(); entry = responses.pollFirst(); if(entry == null) entry = reusable_hb; entry.process(this); } } catch(Exception e) { impl.commandError(e); } try { Thread.sleep(backoff / 1000, (backoff % 1000) * 1000); } catch(InterruptedException ignore) {} backoff += 10000; } } private void data_worker_sender() { FqMessage m; while(!stop && cmd_socket.socket().isConnected()) { m = null; try { m = q.take(); } catch(InterruptedException ignore) { } if(m == endpost) break; if(m == null) continue; try { while(!m.send(this) && !stop && cmd_socket.socket().isConnected()) { synchronized(keylock) { data_skey.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE); try { keylock.wait(); } catch(InterruptedException ignore) { } } } } catch(IOException e) { impl.dataError(e); return; } catch(FqDataProtocolError e) { impl.dataError(e); return; } } } private void waitForData(long ms) throws IOException { data_selector.select(ms); if(data_skey.isWritable()) { synchronized(keylock) { data_skey.interestOps(SelectionKey.OP_READ); keylock.notify(); } } } public int blockingRead(byte dst[], int offset, int len) throws IOException { ByteBuffer rest = ByteBuffer.wrap(dst, offset, len); int nread = 0; // A quick non-blocking attempt if((nread = data_socket.read(rest)) == len) { return len; // done. } if (nread < 0) throw new IOException(); while(rest.position() < (offset+len)) { int readlen; waitForData(1000); while((readlen = data_socket.read(rest)) > 0) { nread += readlen; } if(readlen < 0) throw new IOException(); } return nread; } public ByteBuffer fill_data_buffer(boolean force) throws IOException { if(force || data_in_buff.position() == 0) { waitForData(1000); int rsize = data_socket.read(data_in_buff); if(rsize < -1) throw new IOException("bad read"); } return data_in_buff; } private void data_worker_receiver() { while(!stop && cmd_socket.socket().isConnected()) { FqMessage m; m = new FqMessage(); try { while(!stop && !m.read(this)) fill_data_buffer(true); } catch (IOException e) { impl.dataError(e); reset(); return; } catch (FqDataProtocolError e) { impl.dataError(e); reset(); return; } if(m.isComplete()) { do { try { backq.put(m); m = null; } catch(InterruptedException ignore) { } } while(m != null); } } } private void data_worker() { int backoff = 0; Error boom = null; while(!stop) { if(data_ready) { try { if(client_data_connect_internal()) { backoff = 0; } sender_worker = new Thread() { public void run() { data_worker_sender(); } }; sender_worker.setName("Fq-data-out(" + host + ")"); sender_worker.start(); try { data_worker_receiver(); } catch (Error e) { impl.dataError(e); q.offer(endpost); reset(); boom = e; } try { sender_worker.interrupt(); } catch (Exception ignore) { ignore.printStackTrace(); } synchronized(sender_worker_lock) { sender_worker.join(); } } catch(Exception e) { impl.dataError(e); } } if(backoff < 1000000) backoff += 10000; try { Thread.sleep(backoff / 1000, (backoff % 1000) * 1000); } catch(InterruptedException ignore) {} } shutting_down = true; backq.offer(endpost); if(boom != null) throw(boom); } private void back_worker() { while(!shutting_down) { try { FqMessage m = backq.take(); if(m == endpost) break; impl.dispatch(m); } catch(InterruptedException ignore) { } } } public void shutdown() { stop = true; q.offer(endpost); try { synchronized(sender_worker_lock) { sender_worker.join(); } data_worker.join(); worker.join(); } catch(InterruptedException ignore) { } } } ================================================ FILE: java/src/main/java/com/circonus/FqClientImplDebug.java ================================================ /* * Copyright (c) 2016 Circonus, Inc. * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.circonus; import java.util.Date; import java.util.Map; public class FqClientImplDebug implements FqClientImplInterface { protected FqClient client = null; public void setClient(FqClient c) throws InUseException { if(client != null) throw new InUseException(); client = c; } protected void genericError(Throwable e) { e.printStackTrace(); } public void connectError(Throwable e) { genericError(e); } public void commandError(Throwable e) { genericError(e); } public void dataError(Throwable e) { genericError(e); } public void dispatch(FqMessage m) { byte b[] = m.getPayload(); int len = (b == null) ? 0 : b.length; System.err.println("m[" + len + "] via " + m.getRoute() + " over " + m.getExchange() + " from " + m.getSender()); } public void dispatch(FqCommand cmd) { System.err.println(cmd); } public void dispatchAuth(FqCommand.Auth cmd) { dispatch(cmd); } public void dispatchHeartbeatRequest(FqCommand.HeartbeatRequest cmd) { dispatch(cmd); } public void dispatchHeartbeat(FqCommand.Heartbeat cmd) { dispatch(cmd); } public void dispatchBindRequest(FqCommand.BindRequest cmd) { System.err.println(cmd.toString() + cmd.getBinding()); } public void dispatchUnbindRequest(FqCommand.UnbindRequest cmd) { System.err.println(cmd.toString() + cmd.getBinding() + " " + cmd.getSuccess()); } public void dispatchStatusRequest(FqCommand.StatusRequest cmd) { Date d = cmd.getDate(); Map m = cmd.getMap(); System.err.println("Status: " + d); for(Map.Entry entry : m.entrySet()) { System.err.println(" " + entry.getKey() + " : " + entry.getValue()); } } } ================================================ FILE: java/src/main/java/com/circonus/FqClientImplInterface.java ================================================ /* * Copyright (c) 2016 Circonus, Inc. * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.circonus; import com.circonus.FqCommand; public interface FqClientImplInterface { public class InUseException extends Exception { } public void setClient(FqClient c) throws InUseException; public void connectError(Throwable e); public void commandError(Throwable e); public void dataError(Throwable e); public void dispatch(FqMessage m); public void dispatch(FqCommand cmd); public void dispatchAuth(FqCommand.Auth cmd); public void dispatchHeartbeat(FqCommand.Heartbeat cmd); public void dispatchHeartbeatRequest(FqCommand.HeartbeatRequest cmd); public void dispatchBindRequest(FqCommand.BindRequest cmd); public void dispatchUnbindRequest(FqCommand.UnbindRequest cmd); public void dispatchStatusRequest(FqCommand.StatusRequest cmd); } ================================================ FILE: java/src/main/java/com/circonus/FqClientImplNoop.java ================================================ /* * Copyright (c) 2016 Circonus, Inc. * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.circonus; public class FqClientImplNoop implements FqClientImplInterface { protected FqClient client; public void setClient(FqClient c) throws InUseException { if(client != null) throw new InUseException(); client = c; } protected void genericError(Throwable e) { } public void connectError(Throwable e) { genericError(e); } public void commandError(Throwable e) { genericError(e); } public void dataError(Throwable e) { genericError(e); } public void dispatch(FqMessage m) { } public void dispatch(FqCommand cmd) { } public void dispatchAuth(FqCommand.Auth cmd) { dispatch(cmd); } public void dispatchHeartbeatRequest(FqCommand.HeartbeatRequest cmd) { dispatch(cmd); } public void dispatchHeartbeat(FqCommand.Heartbeat cmd) { dispatch(cmd); } public void dispatchBindRequest(FqCommand.BindRequest cmd) { dispatch(cmd); }; public void dispatchUnbindRequest(FqCommand.UnbindRequest cmd) { dispatch(cmd); }; public void dispatchStatusRequest(FqCommand.StatusRequest cmd) { dispatch(cmd); }; } ================================================ FILE: java/src/main/java/com/circonus/FqCommand.java ================================================ /* * Copyright (c) 2016 Circonus, Inc. * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.circonus; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.HashMap; import java.util.Map; import com.circonus.FqClient; public abstract class FqCommand { public final static short FQ_PROTO_ERROR = (short)0xeeee; public final static short FQ_PROTO_HB = (short)0xbea7; public final static short FQ_PROTO_AUTH_CMD = (short)0xaaaa; public final static short FQ_PROTO_AUTH_PLAIN = (short)0; public final static short FQ_PROTO_AUTH_RESP = (short)0xaa00; public final static short FQ_PROTO_HBREQ = (short)0x4848; public final static short FQ_PROTO_BIND = (short)0xb171; public final static short FQ_PROTO_BINDREQ = (short)0xb170; public final static short FQ_PROTO_UNBIND = (short)0x171b; public final static short FQ_PROTO_UNBINDREQ = (short)0x071b; public final static short FQ_PROTO_STATUS = (short)0x57a7; public final static short FQ_PROTO_STATUSREQ = (short)0xc7a7; protected ByteBuffer bb; private boolean composed = false; public abstract short cmd(); public short response_cmd() { return cmd(); } public void compose() { } public void send(FqClient c) throws IOException { if(composed) { bb.position(0); } else { bb.putShort(cmd()); compose(); bb.flip(); composed = true; } c.cmd_write(bb); } public abstract boolean hasInBandResponse(); private static Heartbeat hb = new Heartbeat(); public Short getShortCmd(FqClient c) throws IOException, FqCommandProtocolError { Short cmd; do { ByteBuffer bb = c.cmd_read(2); if(bb == null) return null; bb.flip(); cmd = bb.getShort(); if(cmd == FQ_PROTO_HB) { c.recvHeartbeat(); } if(cmd == FQ_PROTO_ERROR) { throw new FqCommandProtocolError(c.cmd_read_short_string()); } } while(cmd() != FQ_PROTO_HB && cmd == FQ_PROTO_HB); return cmd; } public void process(FqClient c) throws IOException, FqCommandProtocolError { Short cmd = getShortCmd(c); // the hearbeat happens magically in getShortCmd if(cmd == null) { throw new FqCommandProtocolError("null cmd"); } else if(cmd != response_cmd()) { throw new FqCommandProtocolError(response_cmd(), cmd); } } protected void alloc(int size) { bb = ByteBuffer.allocate(size); bb.order(ByteOrder.BIG_ENDIAN); } public FqCommand(int size) { alloc(size + 2); } public FqCommand() { } public static class Heartbeat extends FqCommand { public Heartbeat() { super(0); } public boolean hasInBandResponse() { return false; } public short cmd() { return FQ_PROTO_HB; } } public static class HeartbeatRequest extends FqCommand { short ms; public HeartbeatRequest(int _ms) { super(2); ms = (short)(_ms & 0xffff); } public boolean hasInBandResponse() { return false; } public short cmd() { return FQ_PROTO_HBREQ; } public void compose() { bb.putShort(ms); } } public static abstract class Auth extends FqCommand { protected byte[] key = null; public boolean success() { return (key != null); } public byte[] getKey() { return key; } } public static class PlainAuth extends Auth { private byte b_user[]; private byte b_pass[]; private byte b_queue[]; private byte b_queue_type[]; public PlainAuth(String user, String pass, String queue, String queue_type) { b_user = user.getBytes(StandardCharsets.UTF_8); b_queue = queue.getBytes(StandardCharsets.UTF_8); b_queue_type = queue_type.getBytes(StandardCharsets.UTF_8); b_pass = pass.getBytes(StandardCharsets.UTF_8); int extra_space = 2 + /* plain */ 2 + b_user.length + /* user */ 2 + b_queue.length + 1 + b_queue_type.length + /* queue */ 2 + b_pass.length; alloc(2+extra_space); } public short cmd() { return FQ_PROTO_AUTH_CMD; } public boolean hasInBandResponse() { return true; } public void compose() { bb.putShort(FQ_PROTO_AUTH_PLAIN); bb.putShort((short)b_user.length); bb.put(b_user); bb.putShort((short)(b_queue.length + 1 + b_queue_type.length)); bb.put(b_queue); bb.put((byte) 0); bb.put(b_queue_type); bb.putShort((short)b_pass.length); bb.put(b_pass); } public void process(FqClient c) throws IOException, FqCommandProtocolError { Short cmd, len; bb = c.cmd_read(2); if(bb == null) return; bb.flip(); cmd = bb.getShort(); switch(cmd) { case FQ_PROTO_AUTH_RESP: key = c.cmd_read_short_bytearray(); if(key == null || key.length > 127) throw new FqCommandProtocolError("bad key"); break; case FQ_PROTO_ERROR: String error = c.cmd_read_short_string(); if(error != null) throw new FqCommandProtocolError(error); /* fall through */ default: throw new FqCommandProtocolError(cmd); } c.getImpl().dispatchAuth(this); } } public static class BindRequest extends FqCommand { public static final short FQ_BIND_PEER = 0x0001; public static final short FQ_BIND_PERM = 0x0110; public static final short FQ_BIND_TRANS = 0x0100; private Integer binding; private byte exchange[]; private byte program[]; private short flags; public BindRequest(byte _exchange[], String _program, short _flags) { program = _program.getBytes(StandardCharsets.UTF_8); exchange = _exchange; flags = _flags; int extra_space = 2 + /* flags */ 2 + exchange.length + /* user */ 2 + program.length; alloc(2+extra_space); } public BindRequest(byte _exchange[], String _program, boolean _peermode) { this(_exchange, _program, _peermode ? FQ_BIND_PEER : 0); } public BindRequest(String exchange, String p, boolean m) { this(exchange.getBytes(StandardCharsets.UTF_8), p, m); } public short cmd() { return FQ_PROTO_BINDREQ; } public short response_cmd() { return FQ_PROTO_BIND; } public boolean hasInBandResponse() { return true; } public void compose() { bb.putShort(flags); bb.putShort((short)exchange.length); bb.put(exchange); bb.putShort((short)program.length); bb.put(program); } public void process(FqClient c) throws IOException, FqCommandProtocolError { super.process(c); Integer cmd; bb = c.cmd_read(4); if(bb == null) return; bb.flip(); binding = bb.getInt(); c.getImpl().dispatchBindRequest(this); } public Integer getBinding() { return binding; } public byte[] getExchange() { return exchange; } } public static class UnbindRequest extends FqCommand { private BindRequest bind; private Integer success; public UnbindRequest(BindRequest b) { bind = b; int extra_space = 2 + /* peermode */ 4 + /* route_id */ 2 + bind.getExchange().length; alloc(2+extra_space); } public short cmd() { return FQ_PROTO_UNBINDREQ; } public short response_cmd() { return FQ_PROTO_UNBIND; } public boolean hasInBandResponse() { return true; } public void compose() { bb.putInt(bind.getBinding()); bb.putShort((short)bind.getExchange().length); bb.put(bind.getExchange()); } public void process(FqClient c) throws IOException, FqCommandProtocolError { super.process(c); Integer cmd; bb = c.cmd_read(4); if(bb == null) return; bb.flip(); success = bb.getInt(); c.getImpl().dispatchUnbindRequest(this); } public Integer getBinding() { return bind.getBinding(); } public boolean getSuccess() { return (success != null && success.equals(bind.getBinding())); } } public static class StatusRequest extends FqCommand { protected Date last_update; protected HashMap status = new HashMap(); public StatusRequest() { super(0); } public short cmd() { return FQ_PROTO_STATUSREQ; } public short response_cmd() { return FQ_PROTO_STATUS; } public boolean hasInBandResponse() { return true; } public void process(FqClient c) throws IOException, FqCommandProtocolError { super.process(c); last_update = new Date(); while(true) { String key = c.cmd_read_short_string(); if(key == null || key.length() == 0) break; Integer ivalue; bb = c.cmd_read(4); if(bb == null) throw new FqCommandProtocolError("status read failure"); bb.flip(); ivalue = bb.getInt(); Long value = ivalue & (long)0xffffffff; status.put(key,value); } c.getImpl().dispatchStatusRequest(this); } public Date getDate() { return last_update; } public Map getMap() { return status; } } } ================================================ FILE: java/src/main/java/com/circonus/FqCommandProtocolError.java ================================================ /* * Copyright (c) 2016 Circonus, Inc. * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.circonus; public class FqCommandProtocolError extends Exception { private short expected; private short recvd; private String msg; public String toString() { if(msg != null) return msg; return "Expected " + String.format("0x%04x", expected) + ", but received " + String.format("0x%04x", recvd); } public FqCommandProtocolError(short _expected, short _recvd) { expected = _expected; recvd = _recvd; } public FqCommandProtocolError(short _expected) { expected = _expected; recvd = 0; } public FqCommandProtocolError(String _msg) { msg = _msg; recvd = 0; } } ================================================ FILE: java/src/main/java/com/circonus/FqDataProtocolError.java ================================================ /* * Copyright (c) 2016 Circonus, Inc. * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.circonus; public class FqDataProtocolError extends Exception { public FqDataProtocolError(String a) { super(a); } } ================================================ FILE: java/src/main/java/com/circonus/FqHeartbeatException.java ================================================ /* * Copyright (c) 2016 Circonus, Inc. * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.circonus; public class FqHeartbeatException extends Exception { public FqHeartbeatException() { super(); } } ================================================ FILE: java/src/main/java/com/circonus/FqMessage.java ================================================ /* * Copyright (c) 2016 Circonus, Inc. * Copyright (c) 2013 OmniTI Computer Consulting, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ package com.circonus; import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.UUID; import com.circonus.FqDataProtocolError; public class FqMessage { public static class MsgId { protected byte d[]; public MsgId(byte v[]) { d = new byte[16]; System.arraycopy(v,0,d,0,16); } } private boolean _complete = false; private int nhops = -1; private InetAddress hops[]; private int route_len = -1; private byte route[]; private int sender_len = -1; private byte sender[]; private int exchange_len = -1; private byte exchange[]; private MsgId sender_msgid; private int payload_len = -1; private byte payload[]; private ByteBuffer[] iovec; public void setRoute(byte[] _r) { route = _r; route_len = _r.length; } public void setSender(byte[] _r) { sender = _r; sender_len = _r.length; } public void setExchange(byte[] _r) { exchange = _r; exchange_len = _r.length; } public void setMsgId() { UUID uuid = UUID.randomUUID(); ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuid.getMostSignificantBits()); bb.putLong(uuid.getLeastSignificantBits()); sender_msgid = new MsgId(bb.array()); } public void setPayload(byte[] _r) { payload = _r; payload_len = _r.length; } public String getRoute() { return new String(route, StandardCharsets.UTF_8); } public String getExchange() { return new String(exchange, StandardCharsets.UTF_8); } public String getSender() { return new String(sender, StandardCharsets.UTF_8); } public MsgId getMsgId() { return sender_msgid; } public byte[] getPayload() { return payload; } public InetAddress[] getPath() { return hops; } public boolean isComplete(boolean peermode) { if(peermode) { if(nhops < 0 || hops == null || sender_len < 0 || sender == null) return false; } if(route_len <= 0 || route == null || exchange_len <= 0 || exchange == null || payload_len < 0 || payload == null || sender_msgid == null) return false; return true; } public boolean isComplete() { return _complete; } public boolean read(FqClient c) throws IOException, FqDataProtocolError { boolean success; int limit, position; if(isComplete()) return true; ByteBuffer bb = c.fill_data_buffer(false); // Save fill location position = bb.position(); limit = bb.limit(); // Set read location bb.reset(); bb.limit(position); success = readInternal(c, bb); if(!success) { // compact while reading bb.compact(); // after compaction, position is a fill position position = bb.position(); // mark at zero (as we've compacted) bb.position(0); bb.mark(); // restore the fill position bb.position(position); } else { // restore fill position bb.limit(limit); bb.position(position); } return success; } private boolean readInternal(FqClient c, ByteBuffer bb) throws IOException, FqDataProtocolError { if(isComplete()) return true; if(exchange_len == -1) { if(bb.remaining() < 1) return false; byte len = bb.get(); exchange_len = len; bb.mark(); if(exchange_len <= 0 || exchange_len > 127) throw new FqDataProtocolError("invalid exchange_len: " + exchange_len); } if(exchange == null) { if(bb.remaining() < exchange_len) return false; exchange = new byte[exchange_len]; bb.get(exchange); bb.mark(); } if(route_len == -1) { if(bb.remaining() < 1) return false; route_len = (int)bb.get(); bb.mark(); if(route_len < 0 || route_len > 127) throw new FqDataProtocolError("invalid route_len: " + route_len); } if(route == null) { if(bb.remaining() < route_len) return false; route = new byte[route_len]; bb.get(route); bb.mark(); } if(sender_msgid == null) { if(bb.remaining() < 16) return false; byte[] m = new byte[16]; bb.get(m); bb.mark(); sender_msgid = new MsgId(m); } if(sender_len == -1) { if(bb.remaining() < 1) return false; sender_len = (int)bb.get(); bb.mark(); if(sender_len < 0 || sender_len > 127) throw new FqDataProtocolError("invalid sender_len: " + sender_len); } if(sender == null) { if(bb.remaining() < sender_len) return false; sender = new byte[sender_len]; bb.get(sender); bb.mark(); } if(nhops == -1) { if(bb.remaining() < 1) return false; nhops = (int)bb.get(); if(nhops < 0 || nhops > 32) throw new FqDataProtocolError("invalid nhops: " + nhops); bb.mark(); } if(hops == null) { if(bb.remaining() < nhops * 4) return false; hops = new InetAddress[nhops]; byte ip[] = new byte[4]; for(int i=0;i 0) { payload = new byte[payload_len]; if(bb.remaining() >= payload_len) { bb.get(payload); } else { int havenow = bb.remaining(); bb.get(payload, 0, havenow); int nread = c.blockingRead(payload, havenow, payload_len - havenow); if((nread+havenow) != payload_len) throw new FqDataProtocolError("payload read failure: " + nread + "+" + havenow + " != " + payload_len); } bb.mark(); } _complete = true; return true; } public boolean send(FqClient c) throws IOException, FqDataProtocolError { if(!isComplete(c.isPeermode())) throw new FqDataProtocolError("incomplete message"); if(iovec == null) { int i = 0; iovec = new ByteBuffer[c.isPeermode() ? 11 : 7]; iovec[i ] = ByteBuffer.allocate(1).put((byte)exchange_len); iovec[i++].flip(); iovec[i++] = ByteBuffer.wrap(exchange); iovec[i ] = ByteBuffer.allocate(1).put((byte)route_len); iovec[i++].flip(); iovec[i++] = ByteBuffer.wrap(route); iovec[i++] = ByteBuffer.wrap(sender_msgid.d); if(c.isPeermode()) { iovec[i ] = ByteBuffer.allocate(1).put((byte)sender_len); iovec[i++].flip(); iovec[i++] = ByteBuffer.wrap(sender); iovec[i ] = ByteBuffer.allocate(1).put((byte)nhops); iovec[i++].flip(); iovec[i ] = ByteBuffer.allocate(nhops * 4); for(int j=0; j 0 do local key = shift() if key == "--src_host" then src_host = shift() elseif key == "--src_port" then src_port = shift() elseif key == "--src_user" then src_user = shift() elseif key == "--src_pass" then src_pass = shift() elseif key == "--src_exchange" then src_exchange = shift() elseif key == "--src_program" then src_program = shift() elseif key == "--dst_host" then dst_host = shift() elseif key == "--dst_port" then dst_port = shift() elseif key == "--dst_user" then dst_user = shift() elseif key == "--dst_pass" then dst_pass = shift() elseif key == "--dst_exchange" then dst_exchange = shift() elseif key == "--dst_route" then dst_route = shift() elseif key == "--filter" then filter = shift() elseif key == "--print" then do_print = true else die("Unknown argument " .. key) end end if not src_exchange then die("src_exchange not set") end if not dst_exchange then die("dst_exchange not set") end function main() local src_fq_client = fqclient.new(src_host, src_port, src_user, src_pass) src_fq_client:bind(src_exchange, src_program) src_fq_client:connect() local dst_fq_client = fqclient.new(dst_host, dst_port, dst_user, dst_pass) dst_fq_client:connect() if filter then print(string.format("Filtering messages by '%s'", filter)) end src_fq_client:listen(function(msg) if (not filter) or string.match(msg, filter) then if do_print then print(msg) end dst_fq_client:send(msg, dst_exchange, dst_route) end end) end main() ================================================ FILE: lua/fq-receiver ================================================ #!/usr/bin/env luajit local fqclient = require 'fqclient' local usage = [[ Arguments: --host (default localhost) --port (default 8765) --user (default user) --pass (default pass) --exchange (string) --program (default 'prefix:') --time add timestamp prefix --timef (default '%.6f') add fromatted timestamp -v,--verbose print metadata -h,--help print this message ]] local function die(msg) print(msg) print(usage) os.exit(1) end local host = "localhost" local port = 8765 local program = 'prefix:' local exchange local user = "user" local pass = "pass" local verbose = false local separator = "\n" local time = false local time_format = "[%.3f]\t" local function shift() return table.remove(arg, 1) end while #arg > 0 do local key = shift() if key == "--host" then host = shift() elseif key == "--port" then port = shift() elseif key == "--user" then user = shift() elseif key == "--pass" then pass = shift() elseif key == "--program" then program = shift() elseif key == "--exchange" then exchange = shift() elseif key == "--separator" then separator = shift() elseif key == "--time" then time = true elseif key == "--timef" then time = true time_format = shift() elseif key == "-v" or key == "--verbose" then verbose = true elseif key == "-h" or key == "--help" then print(usage) os.exit(0) else die("Unknown argument " .. key) end end if not exchange then die("No exchange provided") end local fq_client = fqclient.new(host, port, user, pass) fq_client:bind(exchange, program) fq_client:connect() fq_client:listen_table(function(mtab) if verbose then io.write(string.format("--%s\n", time and string.format(time_format, fqclient.time()) or "")) io.write(string.format("arrival_time: %d\n", mtab.arrival_time)) io.write(string.format("sender : %s\n", mtab.sender)) io.write(string.format("route : %s\n", mtab.route)) io.write(string.format("payload : %s\n", mtab.payload)) else if time then io.write(string.format(time_format, fqclient.time())) end io.write(mtab.payload) io.write(separator) end end) ================================================ FILE: lua/fq-sender ================================================ #!/usr/bin/env luajit local fqclient = require 'fqclient' local ffi = require 'ffi'; local USAGE = [[ Arguments: --host (default localhost) fq host to connect --port (default 8765) port to connect --user (default user) --pass (default pass) --exchange (string) --route (default "") -v verbosity ]] local function die(msg) print(msg) print(USAGE) os.exit(1) end local host = "localhost" local port = 8765 local user = "user" local pass = "pass" local exchange local route = "" local verbose = false local function shift() return table.remove(arg, 1) end while #arg > 0 do local key = shift() if key == "--host" then host = shift() elseif key == "--port" then port = shift() elseif key == "--user" then user = shift() elseif key == "--pass" then pass = shift() elseif key == "--exchange" then exchange = shift() elseif key == "--route" then route = shift() elseif key == "-v" or key == "--verbose" then verbose = true elseif key == "-h" or key == "--help" then print(USAGE) os.exit(0) else die("Unknown argument " .. key) end end if not exchange then die("Exchange not provided") end if not route then die("Route not provided") end local log if verbose then log = function(...) return io.stderr:write(string.format(...) .. "\n") end else log = function() end end local function exit() end log("Connecting to %s %d %s %s\n", host, port, user, pass) local fq_client = fqclient.new(host, port, user, pass) fq_client:connect() while true do local msg = io.read() if not msg then fqclient:close() os.exit(0) end fq_client:send(msg, exchange, route) log('sent\t{ "exchange":"%s", route:"%s", "message":"%s" }', exchange, route, msg) end ================================================ FILE: lua/fqclient.lua.tail ================================================ local gettimeofday_struct = ffi.new("timeval") local function gettimeofday() ffi.C.gettimeofday(gettimeofday_struct, nil) return tonumber(gettimeofday_struct.tv_sec) + tonumber(gettimeofday_struct.tv_usec) / 1000000 end local function charstar(str) local len = string.len(str) local buf = ffi.new("char[?]", len+1, 0) ffi.copy(buf, str, len) return buf end local function m2tab(m) return { route = ffi.string(m.route.name, m.route.len), sender = ffi.string(m.sender.name, m.sender.len), exchange = ffi.string(m.exchange.name, m.exchange.len), arrival_time = tonumber(m.arrival_time), payload = ffi.string(m.payload, m.payload_len), } end local function new(host, port, user, pass) local binds = {} local bind_programs = {} -- refs the ffi C strings inside binds local conn = ffi.new("fq_client[?]", 1); local object = {} local hooks = ffi.new("fq_hooks[?]", 1); hooks[0].version = ffi.C.FQ_HOOKS_V3; hooks[0].sync = 1 hooks[0].unbind = nil hooks[0].auth = function (c, err) if object.auth_cb ~= nil then object:auth_cb(err) end -- perform binds after auth has completed for i,v in ipairs(binds) do fq.fq_client_bind(c, v[0]) end end hooks[0].bind = function (c, breq) if object.bind_cb ~= nil then object:bind_cb(breq) end end rv = fq.fq_client_init(conn, 0, nil) fq.fq_client_hooks(conn[0], hooks) fq.fq_client_creds(conn[0], host, port, user, pass) fq.fq_client_heartbeat(conn[0], 1000); fq.fq_client_set_backlog(conn[0], 10000, 100); fq.fq_client_set_nonblock(conn[0], false); object.conn = conn[0] object.bind = function(object, exchange, program, flags) local breq = ffi.new("fq_bind_req[?]", 1) ffi.copy(breq[0].exchange.name, exchange) breq[0].exchange.len = exchange:len() breq[0].flags = flags or fq.FQ_BIND_TRANS local bind_program = charstar(program) table.insert(bind_programs, bind_program) breq[0].program = bind_program table.insert(binds, breq) return object end object.connect = function(object) local rc = fq.fq_client_connect(object.conn); if (rc == -1) then error("Connection failed") end end object.close = function(object) while fq.fq_client_data_backlog(object.conn) > 0 do ffi.C.usleep(100) end fq.fq_client_destroy(object.conn) end --- Recevie message if one is available, otherwise nil object.recv = function(object) jit.off() local m = fq.fq_client_receive(object.conn) if m ~= nil then -- need ~= nil check here. local arrival_time = tonumber(m.arrival_time) local payload = ffi.string(m.payload, m.payload_len) local route = ffi.string(m.route.name, m.route.len) local sender = ffi.string(m.sender.name, m.sender.len) local exchange = ffi.string(m.exchange.name, m.exchange.len) fq.fq_msg_deref(m) return arrival_time, payload, route, sender, exchange end jit.on() end object.listen_raw = function(object, callback) -- poll on socket and execute callback when message is found local sleep_micros = 1 local sleep_micros_min = 2 local sleep_micros_max = 10E3 while true do jit.off() local m = fq.fq_client_receive(object.conn) if m ~= nil then callback(m) fq.fq_msg_deref(m) sleep_micros = 1 elseif sleep_micros < sleep_micros_max then sleep_micros = sleep_micros * 2 end if sleep_micros > sleep_micros_min then ffi.C.usleep(sleep_micros) end jit.on() end end object.listen = function(object, callback) object:listen_raw(function(m) callback(ffi.string(m.payload)) end) end object.listen_table = function(object, callback) object:listen_raw(function(m) callback(m2tab(m)) end) end object.send = function(object, message, exchange, route) local cmsg = charstar(message) local cexchange = charstar(exchange) local croute = charstar(route) local msg = fq.fq_msg_alloc(cmsg, string.len(message)) fq.fq_msg_exchange(msg, cexchange, string.len(exchange)) fq.fq_msg_route(msg, croute, string.len(route)) -- fq is set to be blocking so fq_client_publish will block fq.fq_client_publish(object.conn, msg) fq.fq_msg_deref(msg) end return object end return { new = new, usleep = ffi.C.usleep, time = gettimeofday, } ================================================ FILE: lua/generatelua.sh ================================================ #!/usr/bin/env bash set -o errexit # Exit script on first error. set -o nounset # Treat references to unset variables as errors. set -o pipefail AWK=' BEGIN { out=0 } /!lua start/ { out=1; next } /!lua stop/ { out=0; next } /^$/ {next} # skip whitespace /^#/ { while (/\\$/) { getline } next } (out == 1) { print } ' FFI_HEAD=' local ffi = require("ffi") local fq = ffi.load("fq") ffi.cdef [[ extern void usleep(int); typedef long time_t; typedef struct timeval { time_t tv_sec; time_t tv_usec; } timeval; int gettimeofday(struct timeval* t, void* tzp); struct ck_stack_entry { struct ck_stack_entry *next; }; typedef struct ck_stack_entry ck_stack_entry_t; // Those are defined in fqh as macros static const uint32_t FQ_PROTO_CMD_MODE = 0xcc50cafe; static const uint32_t FQ_PROTO_DATA_MODE = 0xcc50face; static const uint32_t FQ_PROTO_PEER_MODE = 0xcc50fade; static const uint32_t FQ_PROTO_READ_STAT = 0x47455420; static const uint32_t FQ_PROTO_HTTP_GET = 0x47455420; static const uint32_t FQ_PROTO_HTTP_PUT = 0x50555420; static const uint32_t FQ_PROTO_HTTP_POST = 0x504f5354; static const uint32_t FQ_PROTO_HTTP_HEAD = 0x48454144; static const uint32_t FQ_BIND_PEER = 0x00000001; static const uint32_t FQ_BIND_PERM = 0x00000110; static const uint32_t FQ_BIND_TRANS = 0x00000100; static const uint32_t FQ_PROTO_ERROR = 0xeeee; static const uint32_t FQ_PROTO_AUTH_CMD = 0xaaaa; static const uint32_t FQ_PROTO_AUTH_PLAIN = 0; static const uint32_t FQ_PROTO_AUTH_RESP = 0xaa00; static const uint32_t FQ_PROTO_HBREQ = 0x4848; static const uint32_t FQ_PROTO_HB = 0xbea7; static const uint32_t FQ_PROTO_BINDREQ = 0xb170; static const uint32_t FQ_PROTO_BIND = 0xb171; static const uint32_t FQ_PROTO_UNBINDREQ = 0x071b; static const uint32_t FQ_PROTO_UNBIND = 0x171b; static const uint32_t FQ_PROTO_STATUS = 0x57a7; static const uint32_t FQ_PROTO_STATUSREQ = 0xc7a7; static const uint32_t FQ_BIND_ILLEGAL = 0xffffffff; static const int MAX_RK_LEN = 127; static const int MAX_HOPS = 32; static const int FQ_HOOKS_V1 = 1; static const int FQ_HOOKS_V2 = 2; static const int FQ_HOOKS_V3 = 3; ' FFI_TAIL=']]' printf '%s\n' "$FFI_HEAD" > fqclient.lua cat ../fq.h | awk "$AWK" | sed 's/MAX_RK_LEN/127/' >> fqclient.lua printf '%s\n' "$FFI_TAIL" >> fqclient.lua cat fqclient.lua.tail >> fqclient.lua ================================================ FILE: service-configs/50-circonus-fq.preset ================================================ # Ship disabled by default disable circonus-fq.service ================================================ FILE: service-configs/circonus-fq.service ================================================ [Unit] Description=FQ After=network.target [Service] User=fq WorkingDirectory=/opt/circonus/var/lib/fq EnvironmentFile=-/opt/circonus/var/lib/fq/daemon_options ExecStart=/opt/circonus/sbin/fqd -D ${DAEMON_OPTS} # Note that leaving the service as forking breaks restarts Restart=always [Install] WantedBy=multi-user.target ================================================ FILE: service-configs/daemon_options ================================================ # Additional fqd commandline arguments DAEMON_OPTS="" ================================================ FILE: test/lua-support/init.lua ================================================ ================================================ FILE: test/run-tests.sh ================================================ cd "$(dirname $0)" LD_LIBRARY_PATH="../" export LD_LIBRARY_PATH /opt/circonus/bin/mtevbusted $@ ================================================ FILE: test/test_spec.lua ================================================ local fqclient = require("../lua/fqclient.lua") local function mkreader(exchange, program) local key_auth = mtev.uuid() local key_bind = mtev.uuid() local key_read = mtev.uuid() local fqc_read = fqclient.new("127.0.0.1", 18765, "busted-user-1", "busted-pw") fqc_read.auth_cb = function() mtev.notify(key_auth, true) end fqc_read.bind_cb = function() mtev.notify(key_bind, true) end fqc_read:bind(exchange, program) -- need to bind before connect fqc_read:connect() -- We have to call fq_read:recv() in a loop in order for call backs to execute. mtev.coroutine_spawn(function() while true do local m = { fqc_read:recv() } if #m > 0 then mtev.log("debug", "RECV: %s\n", mtev.tojson(m):tostring()) mtev.notify(key_read, m[2]) -- just forward payload else mtev.sleep(.005) end end end) assert.truthy(mtev.waitfor(key_auth, 5)) assert.truthy(mtev.waitfor(key_bind, 5)) local reader = function(timeout) local _, m = mtev.waitfor(key_read, timeout or 5) return m end return reader end describe("fq", function() local fq, api setup(function() -- Setup fq process wrapper fq = mtev.Proc:new { path = "../fqd", argv = { "fqd", "-D", '-n', '10.254.254.1', '-c', './fqd.sqlite', '-p', '18765', '-v', 'conn,route,msg,io', }, boot_match = "Listening on port", } -- write stderr output to out.log fq:logwrite("out.log") -- Optional: Forward fqd output to error log -- fq:loglog("error") api = mtev.Api:http("127.0.0.1", '18765') end) teardown(function() fq:kill() end) it("should start", function() fq:start() assert.truthy(fq:ready()) end) it("should allow HTTP requests", function() assert.truthy(api:get("/stats.json"):check()) end) local fqc_send local exchange = "test-exchange" local program = "prefix:" local route = "test-route" local reader it("should accept connections", function() fqc_send = fqclient.new("127.0.0.1", 18765, "busted-user-2", "busted-pw") fqc_send:connect() reader = mkreader(exchange, "prefix:") end) it("should send/recv hello messages", function() local msg = "Hello!" local N = 10 for i=1,N do fqc_send:send(msg, exchange, route) end for i=1,N do assert.equal(msg, reader()) end end) it("should send messages via HTTP", function() -- Submit message via HTTP -- $ curl -X POST -H "X-Fq-User: web" -H 'X-Fq-Route: user-route' \ -- -H 'X-Fq-Exchange: busted-exchange' 192.168.33.10:8765/submit \ -- --data 'Hello world!' -- {"routed":1,"dropped":0,"no_route":0,"no_exchange":0} local payload = "Some HTTP payload" r = api:post("/submit", payload, { ["X-Fq-User"] = "web", ["X-Fq-Route"] = "web-route", ["X-Fq-Exchange"] = exchange, }):check() assert.equals(r:json().routed, 1) assert.equals(payload, reader()) end) it("should send messages via fqs", function() -- quick and dirty way to spin up fqs mtev.sh(string.format( [[printf 'hello fqs' | LD_LIBRARY_PATH=../:/opt/circonus/lib ../fqs -a 127.0.0.1:18765 -x "%s" -r "%s"]], exchange, route)) assert.equals('hello fqs', reader()) end) it("should allow multiple readers", function() local reader2 = mkreader(exchange, "prefix:") local msg = "hello reader 2!" fqc_send:send(msg, exchange, route) assert.equals(msg, reader()) assert.equals(msg, reader2()) end) it("should filter prefixes", function() local reader_x = mkreader(exchange, "prefix:x") fqc_send:send("abc", exchange, "abc") fqc_send:send("xxx", exchange, "xxx") assert.equals("abc", reader()) assert.equals("xxx", reader()) assert.equals("xxx", reader_x()) end) end) ================================================ FILE: web/css/bootstrap-theme.css ================================================ .btn-default, .btn-primary, .btn-success, .btn-info, .btn-warning, .btn-danger { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); } .btn-default:active, .btn-primary:active, .btn-success:active, .btn-info:active, .btn-warning:active, .btn-danger:active, .btn-default.active, .btn-primary.active, .btn-success.active, .btn-info.active, .btn-warning.active, .btn-danger.active { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn:active, .btn.active { background-image: none; } .btn-default { text-shadow: 0 1px 0 #fff; background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%); background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%); background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%); background-repeat: repeat-x; border-color: #e0e0e0; border-color: #ccc; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); } .btn-default:active, .btn-default.active { background-color: #e6e6e6; border-color: #e0e0e0; } .btn-primary { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); background-repeat: repeat-x; border-color: #2d6ca2; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); } .btn-primary:active, .btn-primary.active { background-color: #3071a9; border-color: #2d6ca2; } .btn-success { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); background-repeat: repeat-x; border-color: #419641; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); } .btn-success:active, .btn-success.active { background-color: #449d44; border-color: #419641; } .btn-warning { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); background-repeat: repeat-x; border-color: #eb9316; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); } .btn-warning:active, .btn-warning.active { background-color: #ec971f; border-color: #eb9316; } .btn-danger { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); background-repeat: repeat-x; border-color: #c12e2a; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); } .btn-danger:active, .btn-danger.active { background-color: #c9302c; border-color: #c12e2a; } .btn-info { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); background-repeat: repeat-x; border-color: #2aabd2; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); } .btn-info:active, .btn-info.active { background-color: #31b0d5; border-color: #2aabd2; } .thumbnail, .img-thumbnail { -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); } .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus, .dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus { background-color: #357ebd; background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); } .navbar { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%); background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); background-repeat: repeat-x; border-radius: 4px; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); } .navbar .navbar-nav > .active > a { background-color: #f8f8f8; } .navbar-brand, .navbar-nav > li > a { text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); } .navbar-inverse { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%); background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); } .navbar-inverse .navbar-nav > .active > a { background-color: #222222; } .navbar-inverse .navbar-brand, .navbar-inverse .navbar-nav > li > a { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } .navbar-static-top, .navbar-fixed-top, .navbar-fixed-bottom { border-radius: 0; } .alert { text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); } .alert-success { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%); background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); background-repeat: repeat-x; border-color: #b2dba1; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); } .alert-info { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%); background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); background-repeat: repeat-x; border-color: #9acfea; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); } .alert-warning { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%); background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); background-repeat: repeat-x; border-color: #f5e79e; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); } .alert-danger { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%); background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); background-repeat: repeat-x; border-color: #dca7a7; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); } .progress { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%); background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); } .progress-bar { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); } .progress-bar-success { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); } .progress-bar-info { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); } .progress-bar-warning { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); } .progress-bar-danger { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); } .list-group { border-radius: 4px; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); } .list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { text-shadow: 0 -1px 0 #3071a9; background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%); background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); background-repeat: repeat-x; border-color: #3278b3; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); } .panel { -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); } .panel-default > .panel-heading { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%); background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); } .panel-primary > .panel-heading { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); } .panel-success > .panel-heading { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%); background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); } .panel-info > .panel-heading { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%); background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); } .panel-warning > .panel-heading { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%); background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); } .panel-danger > .panel-heading { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%); background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); } .well { background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%); background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); background-repeat: repeat-x; border-color: #dcdcdc; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); } ================================================ FILE: web/css/bootstrap.css ================================================ /*! * Bootstrap v3.0.0 * * Copyright 2013 Twitter, Inc * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0 * * Designed and built with all the love in the world by @mdo and @fat. */ /*! normalize.css v2.1.0 | MIT License | git.io/normalize */ article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } audio, canvas, video { display: inline-block; } audio:not([controls]) { display: none; height: 0; } [hidden] { display: none; } html { font-family: sans-serif; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body { margin: 0; } a:focus { outline: thin dotted; } a:active, a:hover { outline: 0; } h1 { margin: 0.67em 0; font-size: 2em; } abbr[title] { border-bottom: 1px dotted; } b, strong { font-weight: bold; } dfn { font-style: italic; } hr { height: 0; -moz-box-sizing: content-box; box-sizing: content-box; } mark { color: #000; background: #ff0; } code, kbd, pre, samp { font-family: monospace, serif; font-size: 1em; } pre { white-space: pre-wrap; } q { quotes: "\201C" "\201D" "\2018" "\2019"; } small { font-size: 80%; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } img { border: 0; } svg:not(:root) { overflow: hidden; } figure { margin: 0; } fieldset { padding: 0.35em 0.625em 0.75em; margin: 0 2px; border: 1px solid #c0c0c0; } legend { padding: 0; border: 0; } button, input, select, textarea { margin: 0; font-family: inherit; font-size: 100%; } button, input { line-height: normal; } button, select { text-transform: none; } button, html input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; } button[disabled], html input[disabled] { cursor: default; } input[type="checkbox"], input[type="radio"] { padding: 0; box-sizing: border-box; } input[type="search"] { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; -webkit-appearance: textfield; } input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } button::-moz-focus-inner, input::-moz-focus-inner { padding: 0; border: 0; } textarea { overflow: auto; vertical-align: top; } table { border-collapse: collapse; border-spacing: 0; } @media print { * { color: #000 !important; text-shadow: none !important; background: transparent !important; box-shadow: none !important; } a, a:visited { text-decoration: underline; } a[href]:after { content: " (" attr(href) ")"; } abbr[title]:after { content: " (" attr(title) ")"; } .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } @page { margin: 2cm .5cm; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } .navbar { display: none; } .table td, .table th { background-color: #fff !important; } .btn > .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px solid #000; } .table { border-collapse: collapse !important; } .table-bordered th, .table-bordered td { border: 1px solid #ddd !important; } } *, *:before, *:after { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } html { font-size: 62.5%; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.428571429; color: #333333; background-color: #ffffff; } input, button, select, textarea { font-family: inherit; font-size: inherit; line-height: inherit; } button, input, select[multiple], textarea { background-image: none; } a { color: #428bca; text-decoration: none; } a:hover, a:focus { color: #2a6496; text-decoration: underline; } a:focus { outline: thin dotted #333; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } img { vertical-align: middle; } .img-responsive { display: block; height: auto; max-width: 100%; } .img-rounded { border-radius: 6px; } .img-thumbnail { display: inline-block; height: auto; max-width: 100%; padding: 4px; line-height: 1.428571429; background-color: #ffffff; border: 1px solid #dddddd; border-radius: 4px; -webkit-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } .img-circle { border-radius: 50%; } hr { margin-top: 20px; margin-bottom: 20px; border: 0; border-top: 1px solid #eeeeee; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); border: 0; } p { margin: 0 0 10px; } .lead { margin-bottom: 20px; font-size: 16.099999999999998px; font-weight: 200; line-height: 1.4; } @media (min-width: 768px) { .lead { font-size: 21px; } } small { font-size: 85%; } cite { font-style: normal; } .text-muted { color: #999999; } .text-primary { color: #428bca; } .text-warning { color: #c09853; } .text-danger { color: #b94a48; } .text-success { color: #468847; } .text-info { color: #3a87ad; } .text-left { text-align: left; } .text-right { text-align: right; } .text-center { text-align: center; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 500; line-height: 1.1; } h1 small, h2 small, h3 small, h4 small, h5 small, h6 small, .h1 small, .h2 small, .h3 small, .h4 small, .h5 small, .h6 small { font-weight: normal; line-height: 1; color: #999999; } h1, h2, h3 { margin-top: 20px; margin-bottom: 10px; } h4, h5, h6 { margin-top: 10px; margin-bottom: 10px; } h1, .h1 { font-size: 36px; } h2, .h2 { font-size: 30px; } h3, .h3 { font-size: 24px; } h4, .h4 { font-size: 18px; } h5, .h5 { font-size: 14px; } h6, .h6 { font-size: 12px; } h1 small, .h1 small { font-size: 24px; } h2 small, .h2 small { font-size: 18px; } h3 small, .h3 small, h4 small, .h4 small { font-size: 14px; } .page-header { padding-bottom: 9px; margin: 40px 0 20px; border-bottom: 1px solid #eeeeee; } ul, ol { margin-top: 0; margin-bottom: 10px; } ul ul, ol ul, ul ol, ol ol { margin-bottom: 0; } .list-unstyled { padding-left: 0; list-style: none; } .list-inline { padding-left: 0; list-style: none; } .list-inline > li { display: inline-block; padding-right: 5px; padding-left: 5px; } dl { margin-bottom: 20px; } dt, dd { line-height: 1.428571429; } dt { font-weight: bold; } dd { margin-left: 0; } @media (min-width: 768px) { .dl-horizontal dt { float: left; width: 160px; overflow: hidden; clear: left; text-align: right; text-overflow: ellipsis; white-space: nowrap; } .dl-horizontal dd { margin-left: 180px; } .dl-horizontal dd:before, .dl-horizontal dd:after { display: table; content: " "; } .dl-horizontal dd:after { clear: both; } .dl-horizontal dd:before, .dl-horizontal dd:after { display: table; content: " "; } .dl-horizontal dd:after { clear: both; } } abbr[title], abbr[data-original-title] { cursor: help; border-bottom: 1px dotted #999999; } abbr.initialism { font-size: 90%; text-transform: uppercase; } blockquote { padding: 10px 20px; margin: 0 0 20px; border-left: 5px solid #eeeeee; } blockquote p { font-size: 17.5px; font-weight: 300; line-height: 1.25; } blockquote p:last-child { margin-bottom: 0; } blockquote small { display: block; line-height: 1.428571429; color: #999999; } blockquote small:before { content: '\2014 \00A0'; } blockquote.pull-right { padding-right: 15px; padding-left: 0; border-right: 5px solid #eeeeee; border-left: 0; } blockquote.pull-right p, blockquote.pull-right small { text-align: right; } blockquote.pull-right small:before { content: ''; } blockquote.pull-right small:after { content: '\00A0 \2014'; } q:before, q:after, blockquote:before, blockquote:after { content: ""; } address { display: block; margin-bottom: 20px; font-style: normal; line-height: 1.428571429; } code, pre { font-family: Monaco, Menlo, Consolas, "Courier New", monospace; } code { padding: 2px 4px; font-size: 90%; color: #c7254e; white-space: nowrap; background-color: #f9f2f4; border-radius: 4px; } pre { display: block; padding: 9.5px; margin: 0 0 10px; font-size: 13px; line-height: 1.428571429; color: #333333; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #cccccc; border-radius: 4px; } pre.prettyprint { margin-bottom: 20px; } pre code { padding: 0; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border: 0; } .pre-scrollable { max-height: 340px; overflow-y: scroll; } .container { padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } .container:before, .container:after { display: table; content: " "; } .container:after { clear: both; } .container:before, .container:after { display: table; content: " "; } .container:after { clear: both; } .row { margin-right: -15px; margin-left: -15px; } .row:before, .row:after { display: table; content: " "; } .row:after { clear: both; } .row:before, .row:after { display: table; content: " "; } .row:after { clear: both; } .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { position: relative; min-height: 1px; padding-right: 15px; padding-left: 15px; } .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11 { float: left; } .col-xs-1 { width: 8.333333333333332%; } .col-xs-2 { width: 16.666666666666664%; } .col-xs-3 { width: 25%; } .col-xs-4 { width: 33.33333333333333%; } .col-xs-5 { width: 41.66666666666667%; } .col-xs-6 { width: 50%; } .col-xs-7 { width: 58.333333333333336%; } .col-xs-8 { width: 66.66666666666666%; } .col-xs-9 { width: 75%; } .col-xs-10 { width: 83.33333333333334%; } .col-xs-11 { width: 91.66666666666666%; } .col-xs-12 { width: 100%; } @media (min-width: 768px) { .container { max-width: 750px; } .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11 { float: left; } .col-sm-1 { width: 8.333333333333332%; } .col-sm-2 { width: 16.666666666666664%; } .col-sm-3 { width: 25%; } .col-sm-4 { width: 33.33333333333333%; } .col-sm-5 { width: 41.66666666666667%; } .col-sm-6 { width: 50%; } .col-sm-7 { width: 58.333333333333336%; } .col-sm-8 { width: 66.66666666666666%; } .col-sm-9 { width: 75%; } .col-sm-10 { width: 83.33333333333334%; } .col-sm-11 { width: 91.66666666666666%; } .col-sm-12 { width: 100%; } .col-sm-push-1 { left: 8.333333333333332%; } .col-sm-push-2 { left: 16.666666666666664%; } .col-sm-push-3 { left: 25%; } .col-sm-push-4 { left: 33.33333333333333%; } .col-sm-push-5 { left: 41.66666666666667%; } .col-sm-push-6 { left: 50%; } .col-sm-push-7 { left: 58.333333333333336%; } .col-sm-push-8 { left: 66.66666666666666%; } .col-sm-push-9 { left: 75%; } .col-sm-push-10 { left: 83.33333333333334%; } .col-sm-push-11 { left: 91.66666666666666%; } .col-sm-pull-1 { right: 8.333333333333332%; } .col-sm-pull-2 { right: 16.666666666666664%; } .col-sm-pull-3 { right: 25%; } .col-sm-pull-4 { right: 33.33333333333333%; } .col-sm-pull-5 { right: 41.66666666666667%; } .col-sm-pull-6 { right: 50%; } .col-sm-pull-7 { right: 58.333333333333336%; } .col-sm-pull-8 { right: 66.66666666666666%; } .col-sm-pull-9 { right: 75%; } .col-sm-pull-10 { right: 83.33333333333334%; } .col-sm-pull-11 { right: 91.66666666666666%; } .col-sm-offset-1 { margin-left: 8.333333333333332%; } .col-sm-offset-2 { margin-left: 16.666666666666664%; } .col-sm-offset-3 { margin-left: 25%; } .col-sm-offset-4 { margin-left: 33.33333333333333%; } .col-sm-offset-5 { margin-left: 41.66666666666667%; } .col-sm-offset-6 { margin-left: 50%; } .col-sm-offset-7 { margin-left: 58.333333333333336%; } .col-sm-offset-8 { margin-left: 66.66666666666666%; } .col-sm-offset-9 { margin-left: 75%; } .col-sm-offset-10 { margin-left: 83.33333333333334%; } .col-sm-offset-11 { margin-left: 91.66666666666666%; } } @media (min-width: 992px) { .container { max-width: 970px; } .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11 { float: left; } .col-md-1 { width: 8.333333333333332%; } .col-md-2 { width: 16.666666666666664%; } .col-md-3 { width: 25%; } .col-md-4 { width: 33.33333333333333%; } .col-md-5 { width: 41.66666666666667%; } .col-md-6 { width: 50%; } .col-md-7 { width: 58.333333333333336%; } .col-md-8 { width: 66.66666666666666%; } .col-md-9 { width: 75%; } .col-md-10 { width: 83.33333333333334%; } .col-md-11 { width: 91.66666666666666%; } .col-md-12 { width: 100%; } .col-md-push-0 { left: auto; } .col-md-push-1 { left: 8.333333333333332%; } .col-md-push-2 { left: 16.666666666666664%; } .col-md-push-3 { left: 25%; } .col-md-push-4 { left: 33.33333333333333%; } .col-md-push-5 { left: 41.66666666666667%; } .col-md-push-6 { left: 50%; } .col-md-push-7 { left: 58.333333333333336%; } .col-md-push-8 { left: 66.66666666666666%; } .col-md-push-9 { left: 75%; } .col-md-push-10 { left: 83.33333333333334%; } .col-md-push-11 { left: 91.66666666666666%; } .col-md-pull-0 { right: auto; } .col-md-pull-1 { right: 8.333333333333332%; } .col-md-pull-2 { right: 16.666666666666664%; } .col-md-pull-3 { right: 25%; } .col-md-pull-4 { right: 33.33333333333333%; } .col-md-pull-5 { right: 41.66666666666667%; } .col-md-pull-6 { right: 50%; } .col-md-pull-7 { right: 58.333333333333336%; } .col-md-pull-8 { right: 66.66666666666666%; } .col-md-pull-9 { right: 75%; } .col-md-pull-10 { right: 83.33333333333334%; } .col-md-pull-11 { right: 91.66666666666666%; } .col-md-offset-0 { margin-left: 0; } .col-md-offset-1 { margin-left: 8.333333333333332%; } .col-md-offset-2 { margin-left: 16.666666666666664%; } .col-md-offset-3 { margin-left: 25%; } .col-md-offset-4 { margin-left: 33.33333333333333%; } .col-md-offset-5 { margin-left: 41.66666666666667%; } .col-md-offset-6 { margin-left: 50%; } .col-md-offset-7 { margin-left: 58.333333333333336%; } .col-md-offset-8 { margin-left: 66.66666666666666%; } .col-md-offset-9 { margin-left: 75%; } .col-md-offset-10 { margin-left: 83.33333333333334%; } .col-md-offset-11 { margin-left: 91.66666666666666%; } } @media (min-width: 1200px) { .container { max-width: 1170px; } .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11 { float: left; } .col-lg-1 { width: 8.333333333333332%; } .col-lg-2 { width: 16.666666666666664%; } .col-lg-3 { width: 25%; } .col-lg-4 { width: 33.33333333333333%; } .col-lg-5 { width: 41.66666666666667%; } .col-lg-6 { width: 50%; } .col-lg-7 { width: 58.333333333333336%; } .col-lg-8 { width: 66.66666666666666%; } .col-lg-9 { width: 75%; } .col-lg-10 { width: 83.33333333333334%; } .col-lg-11 { width: 91.66666666666666%; } .col-lg-12 { width: 100%; } .col-lg-push-0 { left: auto; } .col-lg-push-1 { left: 8.333333333333332%; } .col-lg-push-2 { left: 16.666666666666664%; } .col-lg-push-3 { left: 25%; } .col-lg-push-4 { left: 33.33333333333333%; } .col-lg-push-5 { left: 41.66666666666667%; } .col-lg-push-6 { left: 50%; } .col-lg-push-7 { left: 58.333333333333336%; } .col-lg-push-8 { left: 66.66666666666666%; } .col-lg-push-9 { left: 75%; } .col-lg-push-10 { left: 83.33333333333334%; } .col-lg-push-11 { left: 91.66666666666666%; } .col-lg-pull-0 { right: auto; } .col-lg-pull-1 { right: 8.333333333333332%; } .col-lg-pull-2 { right: 16.666666666666664%; } .col-lg-pull-3 { right: 25%; } .col-lg-pull-4 { right: 33.33333333333333%; } .col-lg-pull-5 { right: 41.66666666666667%; } .col-lg-pull-6 { right: 50%; } .col-lg-pull-7 { right: 58.333333333333336%; } .col-lg-pull-8 { right: 66.66666666666666%; } .col-lg-pull-9 { right: 75%; } .col-lg-pull-10 { right: 83.33333333333334%; } .col-lg-pull-11 { right: 91.66666666666666%; } .col-lg-offset-0 { margin-left: 0; } .col-lg-offset-1 { margin-left: 8.333333333333332%; } .col-lg-offset-2 { margin-left: 16.666666666666664%; } .col-lg-offset-3 { margin-left: 25%; } .col-lg-offset-4 { margin-left: 33.33333333333333%; } .col-lg-offset-5 { margin-left: 41.66666666666667%; } .col-lg-offset-6 { margin-left: 50%; } .col-lg-offset-7 { margin-left: 58.333333333333336%; } .col-lg-offset-8 { margin-left: 66.66666666666666%; } .col-lg-offset-9 { margin-left: 75%; } .col-lg-offset-10 { margin-left: 83.33333333333334%; } .col-lg-offset-11 { margin-left: 91.66666666666666%; } } table { max-width: 100%; background-color: transparent; } th { text-align: left; } .table { width: 100%; margin-bottom: 20px; } .table thead > tr > th, .table tbody > tr > th, .table tfoot > tr > th, .table thead > tr > td, .table tbody > tr > td, .table tfoot > tr > td { padding: 8px; line-height: 1.428571429; vertical-align: top; border-top: 1px solid #dddddd; } .table thead > tr > th { vertical-align: bottom; border-bottom: 2px solid #dddddd; } .table caption + thead tr:first-child th, .table colgroup + thead tr:first-child th, .table thead:first-child tr:first-child th, .table caption + thead tr:first-child td, .table colgroup + thead tr:first-child td, .table thead:first-child tr:first-child td { border-top: 0; } .table tbody + tbody { border-top: 2px solid #dddddd; } .table .table { background-color: #ffffff; } .table-condensed thead > tr > th, .table-condensed tbody > tr > th, .table-condensed tfoot > tr > th, .table-condensed thead > tr > td, .table-condensed tbody > tr > td, .table-condensed tfoot > tr > td { padding: 5px; } .table-bordered { border: 1px solid #dddddd; } .table-bordered > thead > tr > th, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > tbody > tr > td, .table-bordered > tfoot > tr > td { border: 1px solid #dddddd; } .table-bordered > thead > tr > th, .table-bordered > thead > tr > td { border-bottom-width: 2px; } .table-striped > tbody > tr:nth-child(odd) > td, .table-striped > tbody > tr:nth-child(odd) > th { background-color: #f9f9f9; } .table-hover > tbody > tr:hover > td, .table-hover > tbody > tr:hover > th { background-color: #f5f5f5; } table col[class*="col-"] { display: table-column; float: none; } table td[class*="col-"], table th[class*="col-"] { display: table-cell; float: none; } .table > thead > tr > td.active, .table > tbody > tr > td.active, .table > tfoot > tr > td.active, .table > thead > tr > th.active, .table > tbody > tr > th.active, .table > tfoot > tr > th.active, .table > thead > tr.active > td, .table > tbody > tr.active > td, .table > tfoot > tr.active > td, .table > thead > tr.active > th, .table > tbody > tr.active > th, .table > tfoot > tr.active > th { background-color: #f5f5f5; } .table > thead > tr > td.success, .table > tbody > tr > td.success, .table > tfoot > tr > td.success, .table > thead > tr > th.success, .table > tbody > tr > th.success, .table > tfoot > tr > th.success, .table > thead > tr.success > td, .table > tbody > tr.success > td, .table > tfoot > tr.success > td, .table > thead > tr.success > th, .table > tbody > tr.success > th, .table > tfoot > tr.success > th { background-color: #dff0d8; border-color: #d6e9c6; } .table-hover > tbody > tr > td.success:hover, .table-hover > tbody > tr > th.success:hover, .table-hover > tbody > tr.success:hover > td { background-color: #d0e9c6; border-color: #c9e2b3; } .table > thead > tr > td.danger, .table > tbody > tr > td.danger, .table > tfoot > tr > td.danger, .table > thead > tr > th.danger, .table > tbody > tr > th.danger, .table > tfoot > tr > th.danger, .table > thead > tr.danger > td, .table > tbody > tr.danger > td, .table > tfoot > tr.danger > td, .table > thead > tr.danger > th, .table > tbody > tr.danger > th, .table > tfoot > tr.danger > th { background-color: #f2dede; border-color: #eed3d7; } .table-hover > tbody > tr > td.danger:hover, .table-hover > tbody > tr > th.danger:hover, .table-hover > tbody > tr.danger:hover > td { background-color: #ebcccc; border-color: #e6c1c7; } .table > thead > tr > td.warning, .table > tbody > tr > td.warning, .table > tfoot > tr > td.warning, .table > thead > tr > th.warning, .table > tbody > tr > th.warning, .table > tfoot > tr > th.warning, .table > thead > tr.warning > td, .table > tbody > tr.warning > td, .table > tfoot > tr.warning > td, .table > thead > tr.warning > th, .table > tbody > tr.warning > th, .table > tfoot > tr.warning > th { background-color: #fcf8e3; border-color: #fbeed5; } .table-hover > tbody > tr > td.warning:hover, .table-hover > tbody > tr > th.warning:hover, .table-hover > tbody > tr.warning:hover > td { background-color: #faf2cc; border-color: #f8e5be; } @media (max-width: 768px) { .table-responsive { width: 100%; margin-bottom: 15px; overflow-x: scroll; overflow-y: hidden; border: 1px solid #dddddd; } .table-responsive > .table { margin-bottom: 0; background-color: #fff; } .table-responsive > .table > thead > tr > th, .table-responsive > .table > tbody > tr > th, .table-responsive > .table > tfoot > tr > th, .table-responsive > .table > thead > tr > td, .table-responsive > .table > tbody > tr > td, .table-responsive > .table > tfoot > tr > td { white-space: nowrap; } .table-responsive > .table-bordered { border: 0; } .table-responsive > .table-bordered > thead > tr > th:first-child, .table-responsive > .table-bordered > tbody > tr > th:first-child, .table-responsive > .table-bordered > tfoot > tr > th:first-child, .table-responsive > .table-bordered > thead > tr > td:first-child, .table-responsive > .table-bordered > tbody > tr > td:first-child, .table-responsive > .table-bordered > tfoot > tr > td:first-child { border-left: 0; } .table-responsive > .table-bordered > thead > tr > th:last-child, .table-responsive > .table-bordered > tbody > tr > th:last-child, .table-responsive > .table-bordered > tfoot > tr > th:last-child, .table-responsive > .table-bordered > thead > tr > td:last-child, .table-responsive > .table-bordered > tbody > tr > td:last-child, .table-responsive > .table-bordered > tfoot > tr > td:last-child { border-right: 0; } .table-responsive > .table-bordered > thead > tr:last-child > th, .table-responsive > .table-bordered > tbody > tr:last-child > th, .table-responsive > .table-bordered > tfoot > tr:last-child > th, .table-responsive > .table-bordered > thead > tr:last-child > td, .table-responsive > .table-bordered > tbody > tr:last-child > td, .table-responsive > .table-bordered > tfoot > tr:last-child > td { border-bottom: 0; } } fieldset { padding: 0; margin: 0; border: 0; } legend { display: block; width: 100%; padding: 0; margin-bottom: 20px; font-size: 21px; line-height: inherit; color: #333333; border: 0; border-bottom: 1px solid #e5e5e5; } label { display: inline-block; margin-bottom: 5px; font-weight: bold; } input[type="search"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } input[type="radio"], input[type="checkbox"] { margin: 4px 0 0; margin-top: 1px \9; /* IE8-9 */ line-height: normal; } input[type="file"] { display: block; } select[multiple], select[size] { height: auto; } select optgroup { font-family: inherit; font-size: inherit; font-style: inherit; } input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { outline: thin dotted #333; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button { height: auto; } .form-control:-moz-placeholder { color: #999999; } .form-control::-moz-placeholder { color: #999999; } .form-control:-ms-input-placeholder { color: #999999; } .form-control::-webkit-input-placeholder { color: #999999; } .form-control { display: block; width: 100%; height: 34px; padding: 6px 12px; font-size: 14px; line-height: 1.428571429; color: #555555; vertical-align: middle; background-color: #ffffff; border: 1px solid #cccccc; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; } .form-control:focus { border-color: #66afe9; outline: 0; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); } .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { cursor: not-allowed; background-color: #eeeeee; } textarea.form-control { height: auto; } .form-group { margin-bottom: 15px; } .radio, .checkbox { display: block; min-height: 20px; padding-left: 20px; margin-top: 10px; margin-bottom: 10px; vertical-align: middle; } .radio label, .checkbox label { display: inline; margin-bottom: 0; font-weight: normal; cursor: pointer; } .radio input[type="radio"], .radio-inline input[type="radio"], .checkbox input[type="checkbox"], .checkbox-inline input[type="checkbox"] { float: left; margin-left: -20px; } .radio + .radio, .checkbox + .checkbox { margin-top: -5px; } .radio-inline, .checkbox-inline { display: inline-block; padding-left: 20px; margin-bottom: 0; font-weight: normal; vertical-align: middle; cursor: pointer; } .radio-inline + .radio-inline, .checkbox-inline + .checkbox-inline { margin-top: 0; margin-left: 10px; } input[type="radio"][disabled], input[type="checkbox"][disabled], .radio[disabled], .radio-inline[disabled], .checkbox[disabled], .checkbox-inline[disabled], fieldset[disabled] input[type="radio"], fieldset[disabled] input[type="checkbox"], fieldset[disabled] .radio, fieldset[disabled] .radio-inline, fieldset[disabled] .checkbox, fieldset[disabled] .checkbox-inline { cursor: not-allowed; } .input-sm { height: 30px; padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } select.input-sm { height: 30px; line-height: 30px; } textarea.input-sm { height: auto; } .input-lg { height: 45px; padding: 10px 16px; font-size: 18px; line-height: 1.33; border-radius: 6px; } select.input-lg { height: 45px; line-height: 45px; } textarea.input-lg { height: auto; } .has-warning .help-block, .has-warning .control-label { color: #c09853; } .has-warning .form-control { border-color: #c09853; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } .has-warning .form-control:focus { border-color: #a47e3c; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; } .has-warning .input-group-addon { color: #c09853; background-color: #fcf8e3; border-color: #c09853; } .has-error .help-block, .has-error .control-label { color: #b94a48; } .has-error .form-control { border-color: #b94a48; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } .has-error .form-control:focus { border-color: #953b39; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; } .has-error .input-group-addon { color: #b94a48; background-color: #f2dede; border-color: #b94a48; } .has-success .help-block, .has-success .control-label { color: #468847; } .has-success .form-control { border-color: #468847; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } .has-success .form-control:focus { border-color: #356635; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; } .has-success .input-group-addon { color: #468847; background-color: #dff0d8; border-color: #468847; } .form-control-static { padding-top: 7px; margin-bottom: 0; } .help-block { display: block; margin-top: 5px; margin-bottom: 10px; color: #737373; } @media (min-width: 768px) { .form-inline .form-group { display: inline-block; margin-bottom: 0; vertical-align: middle; } .form-inline .form-control { display: inline-block; } .form-inline .radio, .form-inline .checkbox { display: inline-block; padding-left: 0; margin-top: 0; margin-bottom: 0; } .form-inline .radio input[type="radio"], .form-inline .checkbox input[type="checkbox"] { float: none; margin-left: 0; } } .form-horizontal .control-label, .form-horizontal .radio, .form-horizontal .checkbox, .form-horizontal .radio-inline, .form-horizontal .checkbox-inline { padding-top: 7px; margin-top: 0; margin-bottom: 0; } .form-horizontal .form-group { margin-right: -15px; margin-left: -15px; } .form-horizontal .form-group:before, .form-horizontal .form-group:after { display: table; content: " "; } .form-horizontal .form-group:after { clear: both; } .form-horizontal .form-group:before, .form-horizontal .form-group:after { display: table; content: " "; } .form-horizontal .form-group:after { clear: both; } @media (min-width: 768px) { .form-horizontal .control-label { text-align: right; } } .btn { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: normal; line-height: 1.428571429; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; border: 1px solid transparent; border-radius: 4px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; user-select: none; } .btn:focus { outline: thin dotted #333; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } .btn:hover, .btn:focus { color: #333333; text-decoration: none; } .btn:active, .btn.active { background-image: none; outline: 0; -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn.disabled, .btn[disabled], fieldset[disabled] .btn { pointer-events: none; cursor: not-allowed; opacity: 0.65; filter: alpha(opacity=65); -webkit-box-shadow: none; box-shadow: none; } .btn-default { color: #333333; background-color: #ffffff; border-color: #cccccc; } .btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default { color: #333333; background-color: #ebebeb; border-color: #adadad; } .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default { background-image: none; } .btn-default.disabled, .btn-default[disabled], fieldset[disabled] .btn-default, .btn-default.disabled:hover, .btn-default[disabled]:hover, fieldset[disabled] .btn-default:hover, .btn-default.disabled:focus, .btn-default[disabled]:focus, fieldset[disabled] .btn-default:focus, .btn-default.disabled:active, .btn-default[disabled]:active, fieldset[disabled] .btn-default:active, .btn-default.disabled.active, .btn-default[disabled].active, fieldset[disabled] .btn-default.active { background-color: #ffffff; border-color: #cccccc; } .btn-primary { color: #ffffff; background-color: #428bca; border-color: #357ebd; } .btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary { color: #ffffff; background-color: #3276b1; border-color: #285e8e; } .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary { background-image: none; } .btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #428bca; border-color: #357ebd; } .btn-warning { color: #ffffff; background-color: #f0ad4e; border-color: #eea236; } .btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .open .dropdown-toggle.btn-warning { color: #ffffff; background-color: #ed9c28; border-color: #d58512; } .btn-warning:active, .btn-warning.active, .open .dropdown-toggle.btn-warning { background-image: none; } .btn-warning.disabled, .btn-warning[disabled], fieldset[disabled] .btn-warning, .btn-warning.disabled:hover, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning:hover, .btn-warning.disabled:focus, .btn-warning[disabled]:focus, fieldset[disabled] .btn-warning:focus, .btn-warning.disabled:active, .btn-warning[disabled]:active, fieldset[disabled] .btn-warning:active, .btn-warning.disabled.active, .btn-warning[disabled].active, fieldset[disabled] .btn-warning.active { background-color: #f0ad4e; border-color: #eea236; } .btn-danger { color: #ffffff; background-color: #d9534f; border-color: #d43f3a; } .btn-danger:hover, .btn-danger:focus, .btn-danger:active, .btn-danger.active, .open .dropdown-toggle.btn-danger { color: #ffffff; background-color: #d2322d; border-color: #ac2925; } .btn-danger:active, .btn-danger.active, .open .dropdown-toggle.btn-danger { background-image: none; } .btn-danger.disabled, .btn-danger[disabled], fieldset[disabled] .btn-danger, .btn-danger.disabled:hover, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger:hover, .btn-danger.disabled:focus, .btn-danger[disabled]:focus, fieldset[disabled] .btn-danger:focus, .btn-danger.disabled:active, .btn-danger[disabled]:active, fieldset[disabled] .btn-danger:active, .btn-danger.disabled.active, .btn-danger[disabled].active, fieldset[disabled] .btn-danger.active { background-color: #d9534f; border-color: #d43f3a; } .btn-success { color: #ffffff; background-color: #5cb85c; border-color: #4cae4c; } .btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active, .open .dropdown-toggle.btn-success { color: #ffffff; background-color: #47a447; border-color: #398439; } .btn-success:active, .btn-success.active, .open .dropdown-toggle.btn-success { background-image: none; } .btn-success.disabled, .btn-success[disabled], fieldset[disabled] .btn-success, .btn-success.disabled:hover, .btn-success[disabled]:hover, fieldset[disabled] .btn-success:hover, .btn-success.disabled:focus, .btn-success[disabled]:focus, fieldset[disabled] .btn-success:focus, .btn-success.disabled:active, .btn-success[disabled]:active, fieldset[disabled] .btn-success:active, .btn-success.disabled.active, .btn-success[disabled].active, fieldset[disabled] .btn-success.active { background-color: #5cb85c; border-color: #4cae4c; } .btn-info { color: #ffffff; background-color: #5bc0de; border-color: #46b8da; } .btn-info:hover, .btn-info:focus, .btn-info:active, .btn-info.active, .open .dropdown-toggle.btn-info { color: #ffffff; background-color: #39b3d7; border-color: #269abc; } .btn-info:active, .btn-info.active, .open .dropdown-toggle.btn-info { background-image: none; } .btn-info.disabled, .btn-info[disabled], fieldset[disabled] .btn-info, .btn-info.disabled:hover, .btn-info[disabled]:hover, fieldset[disabled] .btn-info:hover, .btn-info.disabled:focus, .btn-info[disabled]:focus, fieldset[disabled] .btn-info:focus, .btn-info.disabled:active, .btn-info[disabled]:active, fieldset[disabled] .btn-info:active, .btn-info.disabled.active, .btn-info[disabled].active, fieldset[disabled] .btn-info.active { background-color: #5bc0de; border-color: #46b8da; } .btn-link { font-weight: normal; color: #428bca; cursor: pointer; border-radius: 0; } .btn-link, .btn-link:active, .btn-link[disabled], fieldset[disabled] .btn-link { background-color: transparent; -webkit-box-shadow: none; box-shadow: none; } .btn-link, .btn-link:hover, .btn-link:focus, .btn-link:active { border-color: transparent; } .btn-link:hover, .btn-link:focus { color: #2a6496; text-decoration: underline; background-color: transparent; } .btn-link[disabled]:hover, fieldset[disabled] .btn-link:hover, .btn-link[disabled]:focus, fieldset[disabled] .btn-link:focus { color: #999999; text-decoration: none; } .btn-lg { padding: 10px 16px; font-size: 18px; line-height: 1.33; border-radius: 6px; } .btn-sm, .btn-xs { padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .btn-xs { padding: 1px 5px; } .btn-block { display: block; width: 100%; padding-right: 0; padding-left: 0; } .btn-block + .btn-block { margin-top: 5px; } input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="button"].btn-block { width: 100%; } .fade { opacity: 0; -webkit-transition: opacity 0.15s linear; transition: opacity 0.15s linear; } .fade.in { opacity: 1; } .collapse { display: none; } .collapse.in { display: block; } .collapsing { position: relative; height: 0; overflow: hidden; -webkit-transition: height 0.35s ease; transition: height 0.35s ease; } @font-face { font-family: 'Glyphicons Halflings'; src: url('../fonts/glyphicons-halflings-regular.eot'); src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg'); } .glyphicon { position: relative; top: 1px; display: inline-block; font-family: 'Glyphicons Halflings'; -webkit-font-smoothing: antialiased; font-style: normal; font-weight: normal; line-height: 1; } .glyphicon-asterisk:before { content: "\2a"; } .glyphicon-plus:before { content: "\2b"; } .glyphicon-euro:before { content: "\20ac"; } .glyphicon-minus:before { content: "\2212"; } .glyphicon-cloud:before { content: "\2601"; } .glyphicon-envelope:before { content: "\2709"; } .glyphicon-pencil:before { content: "\270f"; } .glyphicon-glass:before { content: "\e001"; } .glyphicon-music:before { content: "\e002"; } .glyphicon-search:before { content: "\e003"; } .glyphicon-heart:before { content: "\e005"; } .glyphicon-star:before { content: "\e006"; } .glyphicon-star-empty:before { content: "\e007"; } .glyphicon-user:before { content: "\e008"; } .glyphicon-film:before { content: "\e009"; } .glyphicon-th-large:before { content: "\e010"; } .glyphicon-th:before { content: "\e011"; } .glyphicon-th-list:before { content: "\e012"; } .glyphicon-ok:before { content: "\e013"; } .glyphicon-remove:before { content: "\e014"; } .glyphicon-zoom-in:before { content: "\e015"; } .glyphicon-zoom-out:before { content: "\e016"; } .glyphicon-off:before { content: "\e017"; } .glyphicon-signal:before { content: "\e018"; } .glyphicon-cog:before { content: "\e019"; } .glyphicon-trash:before { content: "\e020"; } .glyphicon-home:before { content: "\e021"; } .glyphicon-file:before { content: "\e022"; } .glyphicon-time:before { content: "\e023"; } .glyphicon-road:before { content: "\e024"; } .glyphicon-download-alt:before { content: "\e025"; } .glyphicon-download:before { content: "\e026"; } .glyphicon-upload:before { content: "\e027"; } .glyphicon-inbox:before { content: "\e028"; } .glyphicon-play-circle:before { content: "\e029"; } .glyphicon-repeat:before { content: "\e030"; } .glyphicon-refresh:before { content: "\e031"; } .glyphicon-list-alt:before { content: "\e032"; } .glyphicon-flag:before { content: "\e034"; } .glyphicon-headphones:before { content: "\e035"; } .glyphicon-volume-off:before { content: "\e036"; } .glyphicon-volume-down:before { content: "\e037"; } .glyphicon-volume-up:before { content: "\e038"; } .glyphicon-qrcode:before { content: "\e039"; } .glyphicon-barcode:before { content: "\e040"; } .glyphicon-tag:before { content: "\e041"; } .glyphicon-tags:before { content: "\e042"; } .glyphicon-book:before { content: "\e043"; } .glyphicon-print:before { content: "\e045"; } .glyphicon-font:before { content: "\e047"; } .glyphicon-bold:before { content: "\e048"; } .glyphicon-italic:before { content: "\e049"; } .glyphicon-text-height:before { content: "\e050"; } .glyphicon-text-width:before { content: "\e051"; } .glyphicon-align-left:before { content: "\e052"; } .glyphicon-align-center:before { content: "\e053"; } .glyphicon-align-right:before { content: "\e054"; } .glyphicon-align-justify:before { content: "\e055"; } .glyphicon-list:before { content: "\e056"; } .glyphicon-indent-left:before { content: "\e057"; } .glyphicon-indent-right:before { content: "\e058"; } .glyphicon-facetime-video:before { content: "\e059"; } .glyphicon-picture:before { content: "\e060"; } .glyphicon-map-marker:before { content: "\e062"; } .glyphicon-adjust:before { content: "\e063"; } .glyphicon-tint:before { content: "\e064"; } .glyphicon-edit:before { content: "\e065"; } .glyphicon-share:before { content: "\e066"; } .glyphicon-check:before { content: "\e067"; } .glyphicon-move:before { content: "\e068"; } .glyphicon-step-backward:before { content: "\e069"; } .glyphicon-fast-backward:before { content: "\e070"; } .glyphicon-backward:before { content: "\e071"; } .glyphicon-play:before { content: "\e072"; } .glyphicon-pause:before { content: "\e073"; } .glyphicon-stop:before { content: "\e074"; } .glyphicon-forward:before { content: "\e075"; } .glyphicon-fast-forward:before { content: "\e076"; } .glyphicon-step-forward:before { content: "\e077"; } .glyphicon-eject:before { content: "\e078"; } .glyphicon-chevron-left:before { content: "\e079"; } .glyphicon-chevron-right:before { content: "\e080"; } .glyphicon-plus-sign:before { content: "\e081"; } .glyphicon-minus-sign:before { content: "\e082"; } .glyphicon-remove-sign:before { content: "\e083"; } .glyphicon-ok-sign:before { content: "\e084"; } .glyphicon-question-sign:before { content: "\e085"; } .glyphicon-info-sign:before { content: "\e086"; } .glyphicon-screenshot:before { content: "\e087"; } .glyphicon-remove-circle:before { content: "\e088"; } .glyphicon-ok-circle:before { content: "\e089"; } .glyphicon-ban-circle:before { content: "\e090"; } .glyphicon-arrow-left:before { content: "\e091"; } .glyphicon-arrow-right:before { content: "\e092"; } .glyphicon-arrow-up:before { content: "\e093"; } .glyphicon-arrow-down:before { content: "\e094"; } .glyphicon-share-alt:before { content: "\e095"; } .glyphicon-resize-full:before { content: "\e096"; } .glyphicon-resize-small:before { content: "\e097"; } .glyphicon-exclamation-sign:before { content: "\e101"; } .glyphicon-gift:before { content: "\e102"; } .glyphicon-leaf:before { content: "\e103"; } .glyphicon-eye-open:before { content: "\e105"; } .glyphicon-eye-close:before { content: "\e106"; } .glyphicon-warning-sign:before { content: "\e107"; } .glyphicon-plane:before { content: "\e108"; } .glyphicon-random:before { content: "\e110"; } .glyphicon-comment:before { content: "\e111"; } .glyphicon-magnet:before { content: "\e112"; } .glyphicon-chevron-up:before { content: "\e113"; } .glyphicon-chevron-down:before { content: "\e114"; } .glyphicon-retweet:before { content: "\e115"; } .glyphicon-shopping-cart:before { content: "\e116"; } .glyphicon-folder-close:before { content: "\e117"; } .glyphicon-folder-open:before { content: "\e118"; } .glyphicon-resize-vertical:before { content: "\e119"; } .glyphicon-resize-horizontal:before { content: "\e120"; } .glyphicon-hdd:before { content: "\e121"; } .glyphicon-bullhorn:before { content: "\e122"; } .glyphicon-certificate:before { content: "\e124"; } .glyphicon-thumbs-up:before { content: "\e125"; } .glyphicon-thumbs-down:before { content: "\e126"; } .glyphicon-hand-right:before { content: "\e127"; } .glyphicon-hand-left:before { content: "\e128"; } .glyphicon-hand-up:before { content: "\e129"; } .glyphicon-hand-down:before { content: "\e130"; } .glyphicon-circle-arrow-right:before { content: "\e131"; } .glyphicon-circle-arrow-left:before { content: "\e132"; } .glyphicon-circle-arrow-up:before { content: "\e133"; } .glyphicon-circle-arrow-down:before { content: "\e134"; } .glyphicon-globe:before { content: "\e135"; } .glyphicon-tasks:before { content: "\e137"; } .glyphicon-filter:before { content: "\e138"; } .glyphicon-fullscreen:before { content: "\e140"; } .glyphicon-dashboard:before { content: "\e141"; } .glyphicon-heart-empty:before { content: "\e143"; } .glyphicon-link:before { content: "\e144"; } .glyphicon-phone:before { content: "\e145"; } .glyphicon-usd:before { content: "\e148"; } .glyphicon-gbp:before { content: "\e149"; } .glyphicon-sort:before { content: "\e150"; } .glyphicon-sort-by-alphabet:before { content: "\e151"; } .glyphicon-sort-by-alphabet-alt:before { content: "\e152"; } .glyphicon-sort-by-order:before { content: "\e153"; } .glyphicon-sort-by-order-alt:before { content: "\e154"; } .glyphicon-sort-by-attributes:before { content: "\e155"; } .glyphicon-sort-by-attributes-alt:before { content: "\e156"; } .glyphicon-unchecked:before { content: "\e157"; } .glyphicon-expand:before { content: "\e158"; } .glyphicon-collapse-down:before { content: "\e159"; } .glyphicon-collapse-up:before { content: "\e160"; } .glyphicon-log-in:before { content: "\e161"; } .glyphicon-flash:before { content: "\e162"; } .glyphicon-log-out:before { content: "\e163"; } .glyphicon-new-window:before { content: "\e164"; } .glyphicon-record:before { content: "\e165"; } .glyphicon-save:before { content: "\e166"; } .glyphicon-open:before { content: "\e167"; } .glyphicon-saved:before { content: "\e168"; } .glyphicon-import:before { content: "\e169"; } .glyphicon-export:before { content: "\e170"; } .glyphicon-send:before { content: "\e171"; } .glyphicon-floppy-disk:before { content: "\e172"; } .glyphicon-floppy-saved:before { content: "\e173"; } .glyphicon-floppy-remove:before { content: "\e174"; } .glyphicon-floppy-save:before { content: "\e175"; } .glyphicon-floppy-open:before { content: "\e176"; } .glyphicon-credit-card:before { content: "\e177"; } .glyphicon-transfer:before { content: "\e178"; } .glyphicon-cutlery:before { content: "\e179"; } .glyphicon-header:before { content: "\e180"; } .glyphicon-compressed:before { content: "\e181"; } .glyphicon-earphone:before { content: "\e182"; } .glyphicon-phone-alt:before { content: "\e183"; } .glyphicon-tower:before { content: "\e184"; } .glyphicon-stats:before { content: "\e185"; } .glyphicon-sd-video:before { content: "\e186"; } .glyphicon-hd-video:before { content: "\e187"; } .glyphicon-subtitles:before { content: "\e188"; } .glyphicon-sound-stereo:before { content: "\e189"; } .glyphicon-sound-dolby:before { content: "\e190"; } .glyphicon-sound-5-1:before { content: "\e191"; } .glyphicon-sound-6-1:before { content: "\e192"; } .glyphicon-sound-7-1:before { content: "\e193"; } .glyphicon-copyright-mark:before { content: "\e194"; } .glyphicon-registration-mark:before { content: "\e195"; } .glyphicon-cloud-download:before { content: "\e197"; } .glyphicon-cloud-upload:before { content: "\e198"; } .glyphicon-tree-conifer:before { content: "\e199"; } .glyphicon-tree-deciduous:before { content: "\e200"; } .glyphicon-briefcase:before { content: "\1f4bc"; } .glyphicon-calendar:before { content: "\1f4c5"; } .glyphicon-pushpin:before { content: "\1f4cc"; } .glyphicon-paperclip:before { content: "\1f4ce"; } .glyphicon-camera:before { content: "\1f4f7"; } .glyphicon-lock:before { content: "\1f512"; } .glyphicon-bell:before { content: "\1f514"; } .glyphicon-bookmark:before { content: "\1f516"; } .glyphicon-fire:before { content: "\1f525"; } .glyphicon-wrench:before { content: "\1f527"; } .caret { display: inline-block; width: 0; height: 0; margin-left: 2px; vertical-align: middle; border-top: 4px solid #000000; border-right: 4px solid transparent; border-bottom: 0 dotted; border-left: 4px solid transparent; content: ""; } .dropdown { position: relative; } .dropdown-toggle:focus { outline: 0; } .dropdown-menu { position: absolute; top: 100%; left: 0; z-index: 1000; display: none; float: left; min-width: 160px; padding: 5px 0; margin: 2px 0 0; font-size: 14px; list-style: none; background-color: #ffffff; border: 1px solid #cccccc; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 4px; -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); background-clip: padding-box; } .dropdown-menu.pull-right { right: 0; left: auto; } .dropdown-menu .divider { height: 1px; margin: 9px 0; overflow: hidden; background-color: #e5e5e5; } .dropdown-menu > li > a { display: block; padding: 3px 20px; clear: both; font-weight: normal; line-height: 1.428571429; color: #333333; white-space: nowrap; } .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { color: #ffffff; text-decoration: none; background-color: #428bca; } .dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus { color: #ffffff; text-decoration: none; background-color: #428bca; outline: 0; } .dropdown-menu > .disabled > a, .dropdown-menu > .disabled > a:hover, .dropdown-menu > .disabled > a:focus { color: #999999; } .dropdown-menu > .disabled > a:hover, .dropdown-menu > .disabled > a:focus { text-decoration: none; cursor: not-allowed; background-color: transparent; background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); } .open > .dropdown-menu { display: block; } .open > a { outline: 0; } .dropdown-header { display: block; padding: 3px 20px; font-size: 12px; line-height: 1.428571429; color: #999999; } .dropdown-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 990; } .pull-right > .dropdown-menu { right: 0; left: auto; } .dropup .caret, .navbar-fixed-bottom .dropdown .caret { border-top: 0 dotted; border-bottom: 4px solid #000000; content: ""; } .dropup .dropdown-menu, .navbar-fixed-bottom .dropdown .dropdown-menu { top: auto; bottom: 100%; margin-bottom: 1px; } @media (min-width: 768px) { .navbar-right .dropdown-menu { right: 0; left: auto; } } .btn-default .caret { border-top-color: #333333; } .btn-primary .caret, .btn-success .caret, .btn-warning .caret, .btn-danger .caret, .btn-info .caret { border-top-color: #fff; } .dropup .btn-default .caret { border-bottom-color: #333333; } .dropup .btn-primary .caret, .dropup .btn-success .caret, .dropup .btn-warning .caret, .dropup .btn-danger .caret, .dropup .btn-info .caret { border-bottom-color: #fff; } .btn-group, .btn-group-vertical { position: relative; display: inline-block; vertical-align: middle; } .btn-group > .btn, .btn-group-vertical > .btn { position: relative; float: left; } .btn-group > .btn:hover, .btn-group-vertical > .btn:hover, .btn-group > .btn:focus, .btn-group-vertical > .btn:focus, .btn-group > .btn:active, .btn-group-vertical > .btn:active, .btn-group > .btn.active, .btn-group-vertical > .btn.active { z-index: 2; } .btn-group > .btn:focus, .btn-group-vertical > .btn:focus { outline: none; } .btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group { margin-left: -1px; } .btn-toolbar:before, .btn-toolbar:after { display: table; content: " "; } .btn-toolbar:after { clear: both; } .btn-toolbar:before, .btn-toolbar:after { display: table; content: " "; } .btn-toolbar:after { clear: both; } .btn-toolbar .btn-group { float: left; } .btn-toolbar > .btn + .btn, .btn-toolbar > .btn-group + .btn, .btn-toolbar > .btn + .btn-group, .btn-toolbar > .btn-group + .btn-group { margin-left: 5px; } .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { border-radius: 0; } .btn-group > .btn:first-child { margin-left: 0; } .btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) { border-bottom-left-radius: 0; border-top-left-radius: 0; } .btn-group > .btn-group { float: left; } .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; } .btn-group > .btn-group:first-child > .btn:last-child, .btn-group > .btn-group:first-child > .dropdown-toggle { border-top-right-radius: 0; border-bottom-right-radius: 0; } .btn-group > .btn-group:last-child > .btn:first-child { border-bottom-left-radius: 0; border-top-left-radius: 0; } .btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { outline: 0; } .btn-group-xs > .btn { padding: 5px 10px; padding: 1px 5px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .btn-group-sm > .btn { padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .btn-group-lg > .btn { padding: 10px 16px; font-size: 18px; line-height: 1.33; border-radius: 6px; } .btn-group > .btn + .dropdown-toggle { padding-right: 8px; padding-left: 8px; } .btn-group > .btn-lg + .dropdown-toggle { padding-right: 12px; padding-left: 12px; } .btn-group.open .dropdown-toggle { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn .caret { margin-left: 0; } .btn-lg .caret { border-width: 5px 5px 0; border-bottom-width: 0; } .dropup .btn-lg .caret { border-width: 0 5px 5px; } .btn-group-vertical > .btn, .btn-group-vertical > .btn-group { display: block; float: none; width: 100%; max-width: 100%; } .btn-group-vertical > .btn-group:before, .btn-group-vertical > .btn-group:after { display: table; content: " "; } .btn-group-vertical > .btn-group:after { clear: both; } .btn-group-vertical > .btn-group:before, .btn-group-vertical > .btn-group:after { display: table; content: " "; } .btn-group-vertical > .btn-group:after { clear: both; } .btn-group-vertical > .btn-group > .btn { float: none; } .btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group { margin-top: -1px; margin-left: 0; } .btn-group-vertical > .btn:not(:first-child):not(:last-child) { border-radius: 0; } .btn-group-vertical > .btn:first-child:not(:last-child) { border-top-right-radius: 4px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn:last-child:not(:first-child) { border-top-right-radius: 0; border-bottom-left-radius: 4px; border-top-left-radius: 0; } .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; } .btn-group-vertical > .btn-group:first-child > .btn:last-child, .btn-group-vertical > .btn-group:first-child > .dropdown-toggle { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn-group:last-child > .btn:first-child { border-top-right-radius: 0; border-top-left-radius: 0; } .btn-group-justified { display: table; width: 100%; border-collapse: separate; table-layout: fixed; } .btn-group-justified .btn { display: table-cell; float: none; width: 1%; } [data-toggle="buttons"] > .btn > input[type="radio"], [data-toggle="buttons"] > .btn > input[type="checkbox"] { display: none; } .input-group { position: relative; display: table; border-collapse: separate; } .input-group.col { float: none; padding-right: 0; padding-left: 0; } .input-group .form-control { width: 100%; margin-bottom: 0; } .input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn { height: 45px; padding: 10px 16px; font-size: 18px; line-height: 1.33; border-radius: 6px; } select.input-group-lg > .form-control, select.input-group-lg > .input-group-addon, select.input-group-lg > .input-group-btn > .btn { height: 45px; line-height: 45px; } textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-addon, textarea.input-group-lg > .input-group-btn > .btn { height: auto; } .input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn { height: 30px; padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } select.input-group-sm > .form-control, select.input-group-sm > .input-group-addon, select.input-group-sm > .input-group-btn > .btn { height: 30px; line-height: 30px; } textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-addon, textarea.input-group-sm > .input-group-btn > .btn { height: auto; } .input-group-addon, .input-group-btn, .input-group .form-control { display: table-cell; } .input-group-addon:not(:first-child):not(:last-child), .input-group-btn:not(:first-child):not(:last-child), .input-group .form-control:not(:first-child):not(:last-child) { border-radius: 0; } .input-group-addon, .input-group-btn { width: 1%; white-space: nowrap; vertical-align: middle; } .input-group-addon { padding: 6px 12px; font-size: 14px; font-weight: normal; line-height: 1; text-align: center; background-color: #eeeeee; border: 1px solid #cccccc; border-radius: 4px; } .input-group-addon.input-sm { padding: 5px 10px; font-size: 12px; border-radius: 3px; } .input-group-addon.input-lg { padding: 10px 16px; font-size: 18px; border-radius: 6px; } .input-group-addon input[type="radio"], .input-group-addon input[type="checkbox"] { margin-top: 0; } .input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group-addon:first-child { border-right: 0; } .input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:last-child > .btn, .input-group-btn:last-child > .dropdown-toggle, .input-group-btn:first-child > .btn:not(:first-child) { border-bottom-left-radius: 0; border-top-left-radius: 0; } .input-group-addon:last-child { border-left: 0; } .input-group-btn { position: relative; white-space: nowrap; } .input-group-btn > .btn { position: relative; } .input-group-btn > .btn + .btn { margin-left: -4px; } .input-group-btn > .btn:hover, .input-group-btn > .btn:active { z-index: 2; } .nav { padding-left: 0; margin-bottom: 0; list-style: none; } .nav:before, .nav:after { display: table; content: " "; } .nav:after { clear: both; } .nav:before, .nav:after { display: table; content: " "; } .nav:after { clear: both; } .nav > li { position: relative; display: block; } .nav > li > a { position: relative; display: block; padding: 10px 15px; } .nav > li > a:hover, .nav > li > a:focus { text-decoration: none; background-color: #eeeeee; } .nav > li.disabled > a { color: #999999; } .nav > li.disabled > a:hover, .nav > li.disabled > a:focus { color: #999999; text-decoration: none; cursor: not-allowed; background-color: transparent; } .nav .open > a, .nav .open > a:hover, .nav .open > a:focus { background-color: #eeeeee; border-color: #428bca; } .nav .nav-divider { height: 1px; margin: 9px 0; overflow: hidden; background-color: #e5e5e5; } .nav > li > a > img { max-width: none; } .nav-tabs { border-bottom: 1px solid #dddddd; } .nav-tabs > li { float: left; margin-bottom: -1px; } .nav-tabs > li > a { margin-right: 2px; line-height: 1.428571429; border: 1px solid transparent; border-radius: 4px 4px 0 0; } .nav-tabs > li > a:hover { border-color: #eeeeee #eeeeee #dddddd; } .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { color: #555555; cursor: default; background-color: #ffffff; border: 1px solid #dddddd; border-bottom-color: transparent; } .nav-tabs.nav-justified { width: 100%; border-bottom: 0; } .nav-tabs.nav-justified > li { float: none; } .nav-tabs.nav-justified > li > a { text-align: center; } @media (min-width: 768px) { .nav-tabs.nav-justified > li { display: table-cell; width: 1%; } } .nav-tabs.nav-justified > li > a { margin-right: 0; border-bottom: 1px solid #dddddd; } .nav-tabs.nav-justified > .active > a { border-bottom-color: #ffffff; } .nav-pills > li { float: left; } .nav-pills > li > a { border-radius: 5px; } .nav-pills > li + li { margin-left: 2px; } .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { color: #ffffff; background-color: #428bca; } .nav-stacked > li { float: none; } .nav-stacked > li + li { margin-top: 2px; margin-left: 0; } .nav-justified { width: 100%; } .nav-justified > li { float: none; } .nav-justified > li > a { text-align: center; } @media (min-width: 768px) { .nav-justified > li { display: table-cell; width: 1%; } } .nav-tabs-justified { border-bottom: 0; } .nav-tabs-justified > li > a { margin-right: 0; border-bottom: 1px solid #dddddd; } .nav-tabs-justified > .active > a { border-bottom-color: #ffffff; } .tabbable:before, .tabbable:after { display: table; content: " "; } .tabbable:after { clear: both; } .tabbable:before, .tabbable:after { display: table; content: " "; } .tabbable:after { clear: both; } .tab-content > .tab-pane, .pill-content > .pill-pane { display: none; } .tab-content > .active, .pill-content > .active { display: block; } .nav .caret { border-top-color: #428bca; border-bottom-color: #428bca; } .nav a:hover .caret { border-top-color: #2a6496; border-bottom-color: #2a6496; } .nav-tabs .dropdown-menu { margin-top: -1px; border-top-right-radius: 0; border-top-left-radius: 0; } .navbar { position: relative; z-index: 1000; min-height: 50px; margin-bottom: 20px; border: 1px solid transparent; } .navbar:before, .navbar:after { display: table; content: " "; } .navbar:after { clear: both; } .navbar:before, .navbar:after { display: table; content: " "; } .navbar:after { clear: both; } @media (min-width: 768px) { .navbar { border-radius: 4px; } } .navbar-header:before, .navbar-header:after { display: table; content: " "; } .navbar-header:after { clear: both; } .navbar-header:before, .navbar-header:after { display: table; content: " "; } .navbar-header:after { clear: both; } @media (min-width: 768px) { .navbar-header { float: left; } } .navbar-collapse { max-height: 340px; padding-right: 15px; padding-left: 15px; overflow-x: visible; border-top: 1px solid transparent; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); -webkit-overflow-scrolling: touch; } .navbar-collapse:before, .navbar-collapse:after { display: table; content: " "; } .navbar-collapse:after { clear: both; } .navbar-collapse:before, .navbar-collapse:after { display: table; content: " "; } .navbar-collapse:after { clear: both; } .navbar-collapse.in { overflow-y: auto; } @media (min-width: 768px) { .navbar-collapse { width: auto; border-top: 0; box-shadow: none; } .navbar-collapse.collapse { display: block !important; height: auto !important; padding-bottom: 0; overflow: visible !important; } .navbar-collapse.in { overflow-y: visible; } .navbar-collapse .navbar-nav.navbar-left:first-child { margin-left: -15px; } .navbar-collapse .navbar-nav.navbar-right:last-child { margin-right: -15px; } .navbar-collapse .navbar-text:last-child { margin-right: 0; } } .container > .navbar-header, .container > .navbar-collapse { margin-right: -15px; margin-left: -15px; } @media (min-width: 768px) { .container > .navbar-header, .container > .navbar-collapse { margin-right: 0; margin-left: 0; } } .navbar-static-top { border-width: 0 0 1px; } @media (min-width: 768px) { .navbar-static-top { border-radius: 0; } } .navbar-fixed-top, .navbar-fixed-bottom { position: fixed; right: 0; left: 0; border-width: 0 0 1px; } @media (min-width: 768px) { .navbar-fixed-top, .navbar-fixed-bottom { border-radius: 0; } } .navbar-fixed-top { top: 0; z-index: 1030; } .navbar-fixed-bottom { bottom: 0; margin-bottom: 0; } .navbar-brand { float: left; padding: 15px 15px; font-size: 18px; line-height: 20px; } .navbar-brand:hover, .navbar-brand:focus { text-decoration: none; } @media (min-width: 768px) { .navbar > .container .navbar-brand { margin-left: -15px; } } .navbar-toggle { position: relative; float: right; padding: 9px 10px; margin-top: 8px; margin-right: 15px; margin-bottom: 8px; background-color: transparent; border: 1px solid transparent; border-radius: 4px; } .navbar-toggle .icon-bar { display: block; width: 22px; height: 2px; border-radius: 1px; } .navbar-toggle .icon-bar + .icon-bar { margin-top: 4px; } @media (min-width: 768px) { .navbar-toggle { display: none; } } .navbar-nav { margin: 7.5px -15px; } .navbar-nav > li > a { padding-top: 10px; padding-bottom: 10px; line-height: 20px; } @media (max-width: 767px) { .navbar-nav .open .dropdown-menu { position: static; float: none; width: auto; margin-top: 0; background-color: transparent; border: 0; box-shadow: none; } .navbar-nav .open .dropdown-menu > li > a, .navbar-nav .open .dropdown-menu .dropdown-header { padding: 5px 15px 5px 25px; } .navbar-nav .open .dropdown-menu > li > a { line-height: 20px; } .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-nav .open .dropdown-menu > li > a:focus { background-image: none; } } @media (min-width: 768px) { .navbar-nav { float: left; margin: 0; } .navbar-nav > li { float: left; } .navbar-nav > li > a { padding-top: 15px; padding-bottom: 15px; } } @media (min-width: 768px) { .navbar-left { float: left !important; } .navbar-right { float: right !important; } } .navbar-form { padding: 10px 15px; margin-top: 8px; margin-right: -15px; margin-bottom: 8px; margin-left: -15px; border-top: 1px solid transparent; border-bottom: 1px solid transparent; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); } @media (min-width: 768px) { .navbar-form .form-group { display: inline-block; margin-bottom: 0; vertical-align: middle; } .navbar-form .form-control { display: inline-block; } .navbar-form .radio, .navbar-form .checkbox { display: inline-block; padding-left: 0; margin-top: 0; margin-bottom: 0; } .navbar-form .radio input[type="radio"], .navbar-form .checkbox input[type="checkbox"] { float: none; margin-left: 0; } } @media (max-width: 767px) { .navbar-form .form-group { margin-bottom: 5px; } } @media (min-width: 768px) { .navbar-form { width: auto; padding-top: 0; padding-bottom: 0; margin-right: 0; margin-left: 0; border: 0; -webkit-box-shadow: none; box-shadow: none; } } .navbar-nav > li > .dropdown-menu { margin-top: 0; border-top-right-radius: 0; border-top-left-radius: 0; } .navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .navbar-nav.pull-right > li > .dropdown-menu, .navbar-nav > li > .dropdown-menu.pull-right { right: 0; left: auto; } .navbar-btn { margin-top: 8px; margin-bottom: 8px; } .navbar-text { float: left; margin-top: 15px; margin-bottom: 15px; } @media (min-width: 768px) { .navbar-text { margin-right: 15px; margin-left: 15px; } } .navbar-default { background-color: #f8f8f8; border-color: #e7e7e7; } .navbar-default .navbar-brand { color: #777777; } .navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus { color: #5e5e5e; background-color: transparent; } .navbar-default .navbar-text { color: #777777; } .navbar-default .navbar-nav > li > a { color: #777777; } .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { color: #333333; background-color: transparent; } .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { color: #555555; background-color: #e7e7e7; } .navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:hover, .navbar-default .navbar-nav > .disabled > a:focus { color: #cccccc; background-color: transparent; } .navbar-default .navbar-toggle { border-color: #dddddd; } .navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { background-color: #dddddd; } .navbar-default .navbar-toggle .icon-bar { background-color: #cccccc; } .navbar-default .navbar-collapse, .navbar-default .navbar-form { border-color: #e6e6e6; } .navbar-default .navbar-nav > .dropdown > a:hover .caret, .navbar-default .navbar-nav > .dropdown > a:focus .caret { border-top-color: #333333; border-bottom-color: #333333; } .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { color: #555555; background-color: #e7e7e7; } .navbar-default .navbar-nav > .open > a .caret, .navbar-default .navbar-nav > .open > a:hover .caret, .navbar-default .navbar-nav > .open > a:focus .caret { border-top-color: #555555; border-bottom-color: #555555; } .navbar-default .navbar-nav > .dropdown > a .caret { border-top-color: #777777; border-bottom-color: #777777; } @media (max-width: 767px) { .navbar-default .navbar-nav .open .dropdown-menu > li > a { color: #777777; } .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { color: #333333; background-color: transparent; } .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { color: #555555; background-color: #e7e7e7; } .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { color: #cccccc; background-color: transparent; } } .navbar-default .navbar-link { color: #777777; } .navbar-default .navbar-link:hover { color: #333333; } .navbar-inverse { background-color: #222222; border-color: #080808; } .navbar-inverse .navbar-brand { color: #999999; } .navbar-inverse .navbar-brand:hover, .navbar-inverse .navbar-brand:focus { color: #ffffff; background-color: transparent; } .navbar-inverse .navbar-text { color: #999999; } .navbar-inverse .navbar-nav > li > a { color: #999999; } .navbar-inverse .navbar-nav > li > a:hover, .navbar-inverse .navbar-nav > li > a:focus { color: #ffffff; background-color: transparent; } .navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { color: #ffffff; background-color: #080808; } .navbar-inverse .navbar-nav > .disabled > a, .navbar-inverse .navbar-nav > .disabled > a:hover, .navbar-inverse .navbar-nav > .disabled > a:focus { color: #444444; background-color: transparent; } .navbar-inverse .navbar-toggle { border-color: #333333; } .navbar-inverse .navbar-toggle:hover, .navbar-inverse .navbar-toggle:focus { background-color: #333333; } .navbar-inverse .navbar-toggle .icon-bar { background-color: #ffffff; } .navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { border-color: #101010; } .navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { color: #ffffff; background-color: #080808; } .navbar-inverse .navbar-nav > .dropdown > a:hover .caret { border-top-color: #ffffff; border-bottom-color: #ffffff; } .navbar-inverse .navbar-nav > .dropdown > a .caret { border-top-color: #999999; border-bottom-color: #999999; } .navbar-inverse .navbar-nav > .open > a .caret, .navbar-inverse .navbar-nav > .open > a:hover .caret, .navbar-inverse .navbar-nav > .open > a:focus .caret { border-top-color: #ffffff; border-bottom-color: #ffffff; } @media (max-width: 767px) { .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { border-color: #080808; } .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { color: #999999; } .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { color: #ffffff; background-color: transparent; } .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { color: #ffffff; background-color: #080808; } .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { color: #444444; background-color: transparent; } } .navbar-inverse .navbar-link { color: #999999; } .navbar-inverse .navbar-link:hover { color: #ffffff; } .breadcrumb { padding: 8px 15px; margin-bottom: 20px; list-style: none; background-color: #f5f5f5; border-radius: 4px; } .breadcrumb > li { display: inline-block; } .breadcrumb > li + li:before { padding: 0 5px; color: #cccccc; content: "/\00a0"; } .breadcrumb > .active { color: #999999; } .pagination { display: inline-block; padding-left: 0; margin: 20px 0; border-radius: 4px; } .pagination > li { display: inline; } .pagination > li > a, .pagination > li > span { position: relative; float: left; padding: 6px 12px; margin-left: -1px; line-height: 1.428571429; text-decoration: none; background-color: #ffffff; border: 1px solid #dddddd; } .pagination > li:first-child > a, .pagination > li:first-child > span { margin-left: 0; border-bottom-left-radius: 4px; border-top-left-radius: 4px; } .pagination > li:last-child > a, .pagination > li:last-child > span { border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .pagination > li > a:hover, .pagination > li > span:hover, .pagination > li > a:focus, .pagination > li > span:focus { background-color: #eeeeee; } .pagination > .active > a, .pagination > .active > span, .pagination > .active > a:hover, .pagination > .active > span:hover, .pagination > .active > a:focus, .pagination > .active > span:focus { z-index: 2; color: #ffffff; cursor: default; background-color: #428bca; border-color: #428bca; } .pagination > .disabled > span, .pagination > .disabled > a, .pagination > .disabled > a:hover, .pagination > .disabled > a:focus { color: #999999; cursor: not-allowed; background-color: #ffffff; border-color: #dddddd; } .pagination-lg > li > a, .pagination-lg > li > span { padding: 10px 16px; font-size: 18px; } .pagination-lg > li:first-child > a, .pagination-lg > li:first-child > span { border-bottom-left-radius: 6px; border-top-left-radius: 6px; } .pagination-lg > li:last-child > a, .pagination-lg > li:last-child > span { border-top-right-radius: 6px; border-bottom-right-radius: 6px; } .pagination-sm > li > a, .pagination-sm > li > span { padding: 5px 10px; font-size: 12px; } .pagination-sm > li:first-child > a, .pagination-sm > li:first-child > span { border-bottom-left-radius: 3px; border-top-left-radius: 3px; } .pagination-sm > li:last-child > a, .pagination-sm > li:last-child > span { border-top-right-radius: 3px; border-bottom-right-radius: 3px; } .pager { padding-left: 0; margin: 20px 0; text-align: center; list-style: none; } .pager:before, .pager:after { display: table; content: " "; } .pager:after { clear: both; } .pager:before, .pager:after { display: table; content: " "; } .pager:after { clear: both; } .pager li { display: inline; } .pager li > a, .pager li > span { display: inline-block; padding: 5px 14px; background-color: #ffffff; border: 1px solid #dddddd; border-radius: 15px; } .pager li > a:hover, .pager li > a:focus { text-decoration: none; background-color: #eeeeee; } .pager .next > a, .pager .next > span { float: right; } .pager .previous > a, .pager .previous > span { float: left; } .pager .disabled > a, .pager .disabled > a:hover, .pager .disabled > a:focus, .pager .disabled > span { color: #999999; cursor: not-allowed; background-color: #ffffff; } .label { display: inline; padding: .2em .6em .3em; font-size: 75%; font-weight: bold; line-height: 1; color: #ffffff; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; } .label[href]:hover, .label[href]:focus { color: #ffffff; text-decoration: none; cursor: pointer; } .label:empty { display: none; } .label-default { background-color: #999999; } .label-default[href]:hover, .label-default[href]:focus { background-color: #808080; } .label-primary { background-color: #428bca; } .label-primary[href]:hover, .label-primary[href]:focus { background-color: #3071a9; } .label-success { background-color: #5cb85c; } .label-success[href]:hover, .label-success[href]:focus { background-color: #449d44; } .label-info { background-color: #5bc0de; } .label-info[href]:hover, .label-info[href]:focus { background-color: #31b0d5; } .label-warning { background-color: #f0ad4e; } .label-warning[href]:hover, .label-warning[href]:focus { background-color: #ec971f; } .label-danger { background-color: #d9534f; } .label-danger[href]:hover, .label-danger[href]:focus { background-color: #c9302c; } .badge { display: inline-block; min-width: 10px; padding: 3px 7px; font-size: 12px; font-weight: bold; line-height: 1; color: #ffffff; text-align: center; white-space: nowrap; vertical-align: baseline; background-color: #999999; border-radius: 10px; } .badge:empty { display: none; } a.badge:hover, a.badge:focus { color: #ffffff; text-decoration: none; cursor: pointer; } .btn .badge { position: relative; top: -1px; } a.list-group-item.active > .badge, .nav-pills > .active > a > .badge { color: #428bca; background-color: #ffffff; } .nav-pills > li > a > .badge { margin-left: 3px; } .jumbotron { padding: 30px; margin-bottom: 30px; font-size: 21px; font-weight: 200; line-height: 2.1428571435; color: inherit; background-color: #eeeeee; } .jumbotron h1 { line-height: 1; color: inherit; } .jumbotron p { line-height: 1.4; } .container .jumbotron { border-radius: 6px; } @media screen and (min-width: 768px) { .jumbotron { padding-top: 48px; padding-bottom: 48px; } .container .jumbotron { padding-right: 60px; padding-left: 60px; } .jumbotron h1 { font-size: 63px; } } .thumbnail { display: inline-block; display: block; height: auto; max-width: 100%; padding: 4px; line-height: 1.428571429; background-color: #ffffff; border: 1px solid #dddddd; border-radius: 4px; -webkit-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } .thumbnail > img { display: block; height: auto; max-width: 100%; } a.thumbnail:hover, a.thumbnail:focus { border-color: #428bca; } .thumbnail > img { margin-right: auto; margin-left: auto; } .thumbnail .caption { padding: 9px; color: #333333; } .alert { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; } .alert h4 { margin-top: 0; color: inherit; } .alert .alert-link { font-weight: bold; } .alert > p, .alert > ul { margin-bottom: 0; } .alert > p + p { margin-top: 5px; } .alert-dismissable { padding-right: 35px; } .alert-dismissable .close { position: relative; top: -2px; right: -21px; color: inherit; } .alert-success { color: #468847; background-color: #dff0d8; border-color: #d6e9c6; } .alert-success hr { border-top-color: #c9e2b3; } .alert-success .alert-link { color: #356635; } .alert-info { color: #3a87ad; background-color: #d9edf7; border-color: #bce8f1; } .alert-info hr { border-top-color: #a6e1ec; } .alert-info .alert-link { color: #2d6987; } .alert-warning { color: #c09853; background-color: #fcf8e3; border-color: #fbeed5; } .alert-warning hr { border-top-color: #f8e5be; } .alert-warning .alert-link { color: #a47e3c; } .alert-danger { color: #b94a48; background-color: #f2dede; border-color: #eed3d7; } .alert-danger hr { border-top-color: #e6c1c7; } .alert-danger .alert-link { color: #953b39; } @-webkit-keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } @-moz-keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } @-o-keyframes progress-bar-stripes { from { background-position: 0 0; } to { background-position: 40px 0; } } @keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } .progress { height: 20px; margin-bottom: 20px; overflow: hidden; background-color: #f5f5f5; border-radius: 4px; -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); } .progress-bar { float: left; width: 0; height: 100%; font-size: 12px; color: #ffffff; text-align: center; background-color: #428bca; -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); -webkit-transition: width 0.6s ease; transition: width 0.6s ease; } .progress-striped .progress-bar { background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-size: 40px 40px; } .progress.active .progress-bar { -webkit-animation: progress-bar-stripes 2s linear infinite; -moz-animation: progress-bar-stripes 2s linear infinite; -ms-animation: progress-bar-stripes 2s linear infinite; -o-animation: progress-bar-stripes 2s linear infinite; animation: progress-bar-stripes 2s linear infinite; } .progress-bar-success { background-color: #5cb85c; } .progress-striped .progress-bar-success { background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-info { background-color: #5bc0de; } .progress-striped .progress-bar-info { background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-warning { background-color: #f0ad4e; } .progress-striped .progress-bar-warning { background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-danger { background-color: #d9534f; } .progress-striped .progress-bar-danger { background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .media, .media-body { overflow: hidden; zoom: 1; } .media, .media .media { margin-top: 15px; } .media:first-child { margin-top: 0; } .media-object { display: block; } .media-heading { margin: 0 0 5px; } .media > .pull-left { margin-right: 10px; } .media > .pull-right { margin-left: 10px; } .media-list { padding-left: 0; list-style: none; } .list-group { padding-left: 0; margin-bottom: 20px; } .list-group-item { position: relative; display: block; padding: 10px 15px; margin-bottom: -1px; background-color: #ffffff; border: 1px solid #dddddd; } .list-group-item:first-child { border-top-right-radius: 4px; border-top-left-radius: 4px; } .list-group-item:last-child { margin-bottom: 0; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } .list-group-item > .badge { float: right; } .list-group-item > .badge + .badge { margin-right: 5px; } a.list-group-item { color: #555555; } a.list-group-item .list-group-item-heading { color: #333333; } a.list-group-item:hover, a.list-group-item:focus { text-decoration: none; background-color: #f5f5f5; } .list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { z-index: 2; color: #ffffff; background-color: #428bca; border-color: #428bca; } .list-group-item.active .list-group-item-heading, .list-group-item.active:hover .list-group-item-heading, .list-group-item.active:focus .list-group-item-heading { color: inherit; } .list-group-item.active .list-group-item-text, .list-group-item.active:hover .list-group-item-text, .list-group-item.active:focus .list-group-item-text { color: #e1edf7; } .list-group-item-heading { margin-top: 0; margin-bottom: 5px; } .list-group-item-text { margin-bottom: 0; line-height: 1.3; } .panel { margin-bottom: 20px; background-color: #ffffff; border: 1px solid transparent; border-radius: 4px; -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); } .panel-body { padding: 15px; } .panel-body:before, .panel-body:after { display: table; content: " "; } .panel-body:after { clear: both; } .panel-body:before, .panel-body:after { display: table; content: " "; } .panel-body:after { clear: both; } .panel > .list-group { margin-bottom: 0; } .panel > .list-group .list-group-item { border-width: 1px 0; } .panel > .list-group .list-group-item:first-child { border-top-right-radius: 0; border-top-left-radius: 0; } .panel > .list-group .list-group-item:last-child { border-bottom: 0; } .panel-heading + .list-group .list-group-item:first-child { border-top-width: 0; } .panel > .table { margin-bottom: 0; } .panel > .panel-body + .table { border-top: 1px solid #dddddd; } .panel-heading { padding: 10px 15px; border-bottom: 1px solid transparent; border-top-right-radius: 3px; border-top-left-radius: 3px; } .panel-title { margin-top: 0; margin-bottom: 0; font-size: 16px; } .panel-title > a { color: inherit; } .panel-footer { padding: 10px 15px; background-color: #f5f5f5; border-top: 1px solid #dddddd; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } .panel-group .panel { margin-bottom: 0; overflow: hidden; border-radius: 4px; } .panel-group .panel + .panel { margin-top: 5px; } .panel-group .panel-heading { border-bottom: 0; } .panel-group .panel-heading + .panel-collapse .panel-body { border-top: 1px solid #dddddd; } .panel-group .panel-footer { border-top: 0; } .panel-group .panel-footer + .panel-collapse .panel-body { border-bottom: 1px solid #dddddd; } .panel-default { border-color: #dddddd; } .panel-default > .panel-heading { color: #333333; background-color: #f5f5f5; border-color: #dddddd; } .panel-default > .panel-heading + .panel-collapse .panel-body { border-top-color: #dddddd; } .panel-default > .panel-footer + .panel-collapse .panel-body { border-bottom-color: #dddddd; } .panel-primary { border-color: #428bca; } .panel-primary > .panel-heading { color: #ffffff; background-color: #428bca; border-color: #428bca; } .panel-primary > .panel-heading + .panel-collapse .panel-body { border-top-color: #428bca; } .panel-primary > .panel-footer + .panel-collapse .panel-body { border-bottom-color: #428bca; } .panel-success { border-color: #d6e9c6; } .panel-success > .panel-heading { color: #468847; background-color: #dff0d8; border-color: #d6e9c6; } .panel-success > .panel-heading + .panel-collapse .panel-body { border-top-color: #d6e9c6; } .panel-success > .panel-footer + .panel-collapse .panel-body { border-bottom-color: #d6e9c6; } .panel-warning { border-color: #fbeed5; } .panel-warning > .panel-heading { color: #c09853; background-color: #fcf8e3; border-color: #fbeed5; } .panel-warning > .panel-heading + .panel-collapse .panel-body { border-top-color: #fbeed5; } .panel-warning > .panel-footer + .panel-collapse .panel-body { border-bottom-color: #fbeed5; } .panel-danger { border-color: #eed3d7; } .panel-danger > .panel-heading { color: #b94a48; background-color: #f2dede; border-color: #eed3d7; } .panel-danger > .panel-heading + .panel-collapse .panel-body { border-top-color: #eed3d7; } .panel-danger > .panel-footer + .panel-collapse .panel-body { border-bottom-color: #eed3d7; } .panel-info { border-color: #bce8f1; } .panel-info > .panel-heading { color: #3a87ad; background-color: #d9edf7; border-color: #bce8f1; } .panel-info > .panel-heading + .panel-collapse .panel-body { border-top-color: #bce8f1; } .panel-info > .panel-footer + .panel-collapse .panel-body { border-bottom-color: #bce8f1; } .well { min-height: 20px; padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); } .well blockquote { border-color: #ddd; border-color: rgba(0, 0, 0, 0.15); } .well-lg { padding: 24px; border-radius: 6px; } .well-sm { padding: 9px; border-radius: 3px; } .close { float: right; font-size: 21px; font-weight: bold; line-height: 1; color: #000000; text-shadow: 0 1px 0 #ffffff; opacity: 0.2; filter: alpha(opacity=20); } .close:hover, .close:focus { color: #000000; text-decoration: none; cursor: pointer; opacity: 0.5; filter: alpha(opacity=50); } button.close { padding: 0; cursor: pointer; background: transparent; border: 0; -webkit-appearance: none; } .modal-open { overflow: hidden; } body.modal-open, .modal-open .navbar-fixed-top, .modal-open .navbar-fixed-bottom { margin-right: 15px; } .modal { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1040; display: none; overflow: auto; overflow-y: scroll; } .modal.fade .modal-dialog { -webkit-transform: translate(0, -25%); -ms-transform: translate(0, -25%); transform: translate(0, -25%); -webkit-transition: -webkit-transform 0.3s ease-out; -moz-transition: -moz-transform 0.3s ease-out; -o-transition: -o-transform 0.3s ease-out; transition: transform 0.3s ease-out; } .modal.in .modal-dialog { -webkit-transform: translate(0, 0); -ms-transform: translate(0, 0); transform: translate(0, 0); } .modal-dialog { z-index: 1050; width: auto; padding: 10px; margin-right: auto; margin-left: auto; } .modal-content { position: relative; background-color: #ffffff; border: 1px solid #999999; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 6px; outline: none; -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); background-clip: padding-box; } .modal-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1030; background-color: #000000; } .modal-backdrop.fade { opacity: 0; filter: alpha(opacity=0); } .modal-backdrop.in { opacity: 0.5; filter: alpha(opacity=50); } .modal-header { min-height: 16.428571429px; padding: 15px; border-bottom: 1px solid #e5e5e5; } .modal-header .close { margin-top: -2px; } .modal-title { margin: 0; line-height: 1.428571429; } .modal-body { position: relative; padding: 20px; } .modal-footer { padding: 19px 20px 20px; margin-top: 15px; text-align: right; border-top: 1px solid #e5e5e5; } .modal-footer:before, .modal-footer:after { display: table; content: " "; } .modal-footer:after { clear: both; } .modal-footer:before, .modal-footer:after { display: table; content: " "; } .modal-footer:after { clear: both; } .modal-footer .btn + .btn { margin-bottom: 0; margin-left: 5px; } .modal-footer .btn-group .btn + .btn { margin-left: -1px; } .modal-footer .btn-block + .btn-block { margin-left: 0; } @media screen and (min-width: 768px) { .modal-dialog { right: auto; left: 50%; width: 600px; padding-top: 30px; padding-bottom: 30px; } .modal-content { -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); } } .tooltip { position: absolute; z-index: 1030; display: block; font-size: 12px; line-height: 1.4; opacity: 0; filter: alpha(opacity=0); visibility: visible; } .tooltip.in { opacity: 0.9; filter: alpha(opacity=90); } .tooltip.top { padding: 5px 0; margin-top: -3px; } .tooltip.right { padding: 0 5px; margin-left: 3px; } .tooltip.bottom { padding: 5px 0; margin-top: 3px; } .tooltip.left { padding: 0 5px; margin-left: -3px; } .tooltip-inner { max-width: 200px; padding: 3px 8px; color: #ffffff; text-align: center; text-decoration: none; background-color: #000000; border-radius: 4px; } .tooltip-arrow { position: absolute; width: 0; height: 0; border-color: transparent; border-style: solid; } .tooltip.top .tooltip-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-color: #000000; border-width: 5px 5px 0; } .tooltip.top-left .tooltip-arrow { bottom: 0; left: 5px; border-top-color: #000000; border-width: 5px 5px 0; } .tooltip.top-right .tooltip-arrow { right: 5px; bottom: 0; border-top-color: #000000; border-width: 5px 5px 0; } .tooltip.right .tooltip-arrow { top: 50%; left: 0; margin-top: -5px; border-right-color: #000000; border-width: 5px 5px 5px 0; } .tooltip.left .tooltip-arrow { top: 50%; right: 0; margin-top: -5px; border-left-color: #000000; border-width: 5px 0 5px 5px; } .tooltip.bottom .tooltip-arrow { top: 0; left: 50%; margin-left: -5px; border-bottom-color: #000000; border-width: 0 5px 5px; } .tooltip.bottom-left .tooltip-arrow { top: 0; left: 5px; border-bottom-color: #000000; border-width: 0 5px 5px; } .tooltip.bottom-right .tooltip-arrow { top: 0; right: 5px; border-bottom-color: #000000; border-width: 0 5px 5px; } .popover { position: absolute; top: 0; left: 0; z-index: 1010; display: none; max-width: 276px; padding: 1px; text-align: left; white-space: normal; background-color: #ffffff; border: 1px solid #cccccc; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 6px; -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); background-clip: padding-box; } .popover.top { margin-top: -10px; } .popover.right { margin-left: 10px; } .popover.bottom { margin-top: 10px; } .popover.left { margin-left: -10px; } .popover-title { padding: 8px 14px; margin: 0; font-size: 14px; font-weight: normal; line-height: 18px; background-color: #f7f7f7; border-bottom: 1px solid #ebebeb; border-radius: 5px 5px 0 0; } .popover-content { padding: 9px 14px; } .popover .arrow, .popover .arrow:after { position: absolute; display: block; width: 0; height: 0; border-color: transparent; border-style: solid; } .popover .arrow { border-width: 11px; } .popover .arrow:after { border-width: 10px; content: ""; } .popover.top .arrow { bottom: -11px; left: 50%; margin-left: -11px; border-top-color: #999999; border-top-color: rgba(0, 0, 0, 0.25); border-bottom-width: 0; } .popover.top .arrow:after { bottom: 1px; margin-left: -10px; border-top-color: #ffffff; border-bottom-width: 0; content: " "; } .popover.right .arrow { top: 50%; left: -11px; margin-top: -11px; border-right-color: #999999; border-right-color: rgba(0, 0, 0, 0.25); border-left-width: 0; } .popover.right .arrow:after { bottom: -10px; left: 1px; border-right-color: #ffffff; border-left-width: 0; content: " "; } .popover.bottom .arrow { top: -11px; left: 50%; margin-left: -11px; border-bottom-color: #999999; border-bottom-color: rgba(0, 0, 0, 0.25); border-top-width: 0; } .popover.bottom .arrow:after { top: 1px; margin-left: -10px; border-bottom-color: #ffffff; border-top-width: 0; content: " "; } .popover.left .arrow { top: 50%; right: -11px; margin-top: -11px; border-left-color: #999999; border-left-color: rgba(0, 0, 0, 0.25); border-right-width: 0; } .popover.left .arrow:after { right: 1px; bottom: -10px; border-left-color: #ffffff; border-right-width: 0; content: " "; } .carousel { position: relative; } .carousel-inner { position: relative; width: 100%; overflow: hidden; } .carousel-inner > .item { position: relative; display: none; -webkit-transition: 0.6s ease-in-out left; transition: 0.6s ease-in-out left; } .carousel-inner > .item > img, .carousel-inner > .item > a > img { display: block; height: auto; max-width: 100%; line-height: 1; } .carousel-inner > .active, .carousel-inner > .next, .carousel-inner > .prev { display: block; } .carousel-inner > .active { left: 0; } .carousel-inner > .next, .carousel-inner > .prev { position: absolute; top: 0; width: 100%; } .carousel-inner > .next { left: 100%; } .carousel-inner > .prev { left: -100%; } .carousel-inner > .next.left, .carousel-inner > .prev.right { left: 0; } .carousel-inner > .active.left { left: -100%; } .carousel-inner > .active.right { left: 100%; } .carousel-control { position: absolute; top: 0; bottom: 0; left: 0; width: 15%; font-size: 20px; color: #ffffff; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); opacity: 0.5; filter: alpha(opacity=50); } .carousel-control.left { background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%)); background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); } .carousel-control.right { right: 0; left: auto; background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%)); background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); } .carousel-control:hover, .carousel-control:focus { color: #ffffff; text-decoration: none; opacity: 0.9; filter: alpha(opacity=90); } .carousel-control .icon-prev, .carousel-control .icon-next, .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right { position: absolute; top: 50%; left: 50%; z-index: 5; display: inline-block; } .carousel-control .icon-prev, .carousel-control .icon-next { width: 20px; height: 20px; margin-top: -10px; margin-left: -10px; font-family: serif; } .carousel-control .icon-prev:before { content: '\2039'; } .carousel-control .icon-next:before { content: '\203a'; } .carousel-indicators { position: absolute; bottom: 10px; left: 50%; z-index: 15; width: 60%; padding-left: 0; margin-left: -30%; text-align: center; list-style: none; } .carousel-indicators li { display: inline-block; width: 10px; height: 10px; margin: 1px; text-indent: -999px; cursor: pointer; border: 1px solid #ffffff; border-radius: 10px; } .carousel-indicators .active { width: 12px; height: 12px; margin: 0; background-color: #ffffff; } .carousel-caption { position: absolute; right: 15%; bottom: 20px; left: 15%; z-index: 10; padding-top: 20px; padding-bottom: 20px; color: #ffffff; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); } .carousel-caption .btn { text-shadow: none; } @media screen and (min-width: 768px) { .carousel-control .icon-prev, .carousel-control .icon-next { width: 30px; height: 30px; margin-top: -15px; margin-left: -15px; font-size: 30px; } .carousel-caption { right: 20%; left: 20%; padding-bottom: 30px; } .carousel-indicators { bottom: 20px; } } .clearfix:before, .clearfix:after { display: table; content: " "; } .clearfix:after { clear: both; } .pull-right { float: right !important; } .pull-left { float: left !important; } .hide { display: none !important; } .show { display: block !important; } .invisible { visibility: hidden; } .text-hide { font: 0/0 a; color: transparent; text-shadow: none; background-color: transparent; border: 0; } .affix { position: fixed; } @-ms-viewport { width: device-width; } @media screen and (max-width: 400px) { @-ms-viewport { width: 320px; } } .hidden { display: none !important; visibility: hidden !important; } .visible-xs { display: none !important; } tr.visible-xs { display: none !important; } th.visible-xs, td.visible-xs { display: none !important; } @media (max-width: 767px) { .visible-xs { display: block !important; } tr.visible-xs { display: table-row !important; } th.visible-xs, td.visible-xs { display: table-cell !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-xs.visible-sm { display: block !important; } tr.visible-xs.visible-sm { display: table-row !important; } th.visible-xs.visible-sm, td.visible-xs.visible-sm { display: table-cell !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-xs.visible-md { display: block !important; } tr.visible-xs.visible-md { display: table-row !important; } th.visible-xs.visible-md, td.visible-xs.visible-md { display: table-cell !important; } } @media (min-width: 1200px) { .visible-xs.visible-lg { display: block !important; } tr.visible-xs.visible-lg { display: table-row !important; } th.visible-xs.visible-lg, td.visible-xs.visible-lg { display: table-cell !important; } } .visible-sm { display: none !important; } tr.visible-sm { display: none !important; } th.visible-sm, td.visible-sm { display: none !important; } @media (max-width: 767px) { .visible-sm.visible-xs { display: block !important; } tr.visible-sm.visible-xs { display: table-row !important; } th.visible-sm.visible-xs, td.visible-sm.visible-xs { display: table-cell !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm { display: block !important; } tr.visible-sm { display: table-row !important; } th.visible-sm, td.visible-sm { display: table-cell !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-sm.visible-md { display: block !important; } tr.visible-sm.visible-md { display: table-row !important; } th.visible-sm.visible-md, td.visible-sm.visible-md { display: table-cell !important; } } @media (min-width: 1200px) { .visible-sm.visible-lg { display: block !important; } tr.visible-sm.visible-lg { display: table-row !important; } th.visible-sm.visible-lg, td.visible-sm.visible-lg { display: table-cell !important; } } .visible-md { display: none !important; } tr.visible-md { display: none !important; } th.visible-md, td.visible-md { display: none !important; } @media (max-width: 767px) { .visible-md.visible-xs { display: block !important; } tr.visible-md.visible-xs { display: table-row !important; } th.visible-md.visible-xs, td.visible-md.visible-xs { display: table-cell !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-md.visible-sm { display: block !important; } tr.visible-md.visible-sm { display: table-row !important; } th.visible-md.visible-sm, td.visible-md.visible-sm { display: table-cell !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md { display: block !important; } tr.visible-md { display: table-row !important; } th.visible-md, td.visible-md { display: table-cell !important; } } @media (min-width: 1200px) { .visible-md.visible-lg { display: block !important; } tr.visible-md.visible-lg { display: table-row !important; } th.visible-md.visible-lg, td.visible-md.visible-lg { display: table-cell !important; } } .visible-lg { display: none !important; } tr.visible-lg { display: none !important; } th.visible-lg, td.visible-lg { display: none !important; } @media (max-width: 767px) { .visible-lg.visible-xs { display: block !important; } tr.visible-lg.visible-xs { display: table-row !important; } th.visible-lg.visible-xs, td.visible-lg.visible-xs { display: table-cell !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-lg.visible-sm { display: block !important; } tr.visible-lg.visible-sm { display: table-row !important; } th.visible-lg.visible-sm, td.visible-lg.visible-sm { display: table-cell !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-lg.visible-md { display: block !important; } tr.visible-lg.visible-md { display: table-row !important; } th.visible-lg.visible-md, td.visible-lg.visible-md { display: table-cell !important; } } @media (min-width: 1200px) { .visible-lg { display: block !important; } tr.visible-lg { display: table-row !important; } th.visible-lg, td.visible-lg { display: table-cell !important; } } .hidden-xs { display: block !important; } tr.hidden-xs { display: table-row !important; } th.hidden-xs, td.hidden-xs { display: table-cell !important; } @media (max-width: 767px) { .hidden-xs { display: none !important; } tr.hidden-xs { display: none !important; } th.hidden-xs, td.hidden-xs { display: none !important; } } @media (min-width: 768px) and (max-width: 991px) { .hidden-xs.hidden-sm { display: none !important; } tr.hidden-xs.hidden-sm { display: none !important; } th.hidden-xs.hidden-sm, td.hidden-xs.hidden-sm { display: none !important; } } @media (min-width: 992px) and (max-width: 1199px) { .hidden-xs.hidden-md { display: none !important; } tr.hidden-xs.hidden-md { display: none !important; } th.hidden-xs.hidden-md, td.hidden-xs.hidden-md { display: none !important; } } @media (min-width: 1200px) { .hidden-xs.hidden-lg { display: none !important; } tr.hidden-xs.hidden-lg { display: none !important; } th.hidden-xs.hidden-lg, td.hidden-xs.hidden-lg { display: none !important; } } .hidden-sm { display: block !important; } tr.hidden-sm { display: table-row !important; } th.hidden-sm, td.hidden-sm { display: table-cell !important; } @media (max-width: 767px) { .hidden-sm.hidden-xs { display: none !important; } tr.hidden-sm.hidden-xs { display: none !important; } th.hidden-sm.hidden-xs, td.hidden-sm.hidden-xs { display: none !important; } } @media (min-width: 768px) and (max-width: 991px) { .hidden-sm { display: none !important; } tr.hidden-sm { display: none !important; } th.hidden-sm, td.hidden-sm { display: none !important; } } @media (min-width: 992px) and (max-width: 1199px) { .hidden-sm.hidden-md { display: none !important; } tr.hidden-sm.hidden-md { display: none !important; } th.hidden-sm.hidden-md, td.hidden-sm.hidden-md { display: none !important; } } @media (min-width: 1200px) { .hidden-sm.hidden-lg { display: none !important; } tr.hidden-sm.hidden-lg { display: none !important; } th.hidden-sm.hidden-lg, td.hidden-sm.hidden-lg { display: none !important; } } .hidden-md { display: block !important; } tr.hidden-md { display: table-row !important; } th.hidden-md, td.hidden-md { display: table-cell !important; } @media (max-width: 767px) { .hidden-md.hidden-xs { display: none !important; } tr.hidden-md.hidden-xs { display: none !important; } th.hidden-md.hidden-xs, td.hidden-md.hidden-xs { display: none !important; } } @media (min-width: 768px) and (max-width: 991px) { .hidden-md.hidden-sm { display: none !important; } tr.hidden-md.hidden-sm { display: none !important; } th.hidden-md.hidden-sm, td.hidden-md.hidden-sm { display: none !important; } } @media (min-width: 992px) and (max-width: 1199px) { .hidden-md { display: none !important; } tr.hidden-md { display: none !important; } th.hidden-md, td.hidden-md { display: none !important; } } @media (min-width: 1200px) { .hidden-md.hidden-lg { display: none !important; } tr.hidden-md.hidden-lg { display: none !important; } th.hidden-md.hidden-lg, td.hidden-md.hidden-lg { display: none !important; } } .hidden-lg { display: block !important; } tr.hidden-lg { display: table-row !important; } th.hidden-lg, td.hidden-lg { display: table-cell !important; } @media (max-width: 767px) { .hidden-lg.hidden-xs { display: none !important; } tr.hidden-lg.hidden-xs { display: none !important; } th.hidden-lg.hidden-xs, td.hidden-lg.hidden-xs { display: none !important; } } @media (min-width: 768px) and (max-width: 991px) { .hidden-lg.hidden-sm { display: none !important; } tr.hidden-lg.hidden-sm { display: none !important; } th.hidden-lg.hidden-sm, td.hidden-lg.hidden-sm { display: none !important; } } @media (min-width: 992px) and (max-width: 1199px) { .hidden-lg.hidden-md { display: none !important; } tr.hidden-lg.hidden-md { display: none !important; } th.hidden-lg.hidden-md, td.hidden-lg.hidden-md { display: none !important; } } @media (min-width: 1200px) { .hidden-lg { display: none !important; } tr.hidden-lg { display: none !important; } th.hidden-lg, td.hidden-lg { display: none !important; } } .visible-print { display: none !important; } tr.visible-print { display: none !important; } th.visible-print, td.visible-print { display: none !important; } @media print { .visible-print { display: block !important; } tr.visible-print { display: table-row !important; } th.visible-print, td.visible-print { display: table-cell !important; } .hidden-print { display: none !important; } tr.hidden-print { display: none !important; } th.hidden-print, td.hidden-print { display: none !important; } } ================================================ FILE: web/css/colorbrewer.css ================================================ /* This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/). */ .YlGn .q0-3{fill:rgb(247,252,185)} .YlGn .q1-3{fill:rgb(173,221,142)} .YlGn .q2-3{fill:rgb(49,163,84)} .YlGn .q0-4{fill:rgb(255,255,204)} .YlGn .q1-4{fill:rgb(194,230,153)} .YlGn .q2-4{fill:rgb(120,198,121)} .YlGn .q3-4{fill:rgb(35,132,67)} .YlGn .q0-5{fill:rgb(255,255,204)} .YlGn .q1-5{fill:rgb(194,230,153)} .YlGn .q2-5{fill:rgb(120,198,121)} .YlGn .q3-5{fill:rgb(49,163,84)} .YlGn .q4-5{fill:rgb(0,104,55)} .YlGn .q0-6{fill:rgb(255,255,204)} .YlGn .q1-6{fill:rgb(217,240,163)} .YlGn .q2-6{fill:rgb(173,221,142)} .YlGn .q3-6{fill:rgb(120,198,121)} .YlGn .q4-6{fill:rgb(49,163,84)} .YlGn .q5-6{fill:rgb(0,104,55)} .YlGn .q0-7{fill:rgb(255,255,204)} .YlGn .q1-7{fill:rgb(217,240,163)} .YlGn .q2-7{fill:rgb(173,221,142)} .YlGn .q3-7{fill:rgb(120,198,121)} .YlGn .q4-7{fill:rgb(65,171,93)} .YlGn .q5-7{fill:rgb(35,132,67)} .YlGn .q6-7{fill:rgb(0,90,50)} .YlGn .q0-8{fill:rgb(255,255,229)} .YlGn .q1-8{fill:rgb(247,252,185)} .YlGn .q2-8{fill:rgb(217,240,163)} .YlGn .q3-8{fill:rgb(173,221,142)} .YlGn .q4-8{fill:rgb(120,198,121)} .YlGn .q5-8{fill:rgb(65,171,93)} .YlGn .q6-8{fill:rgb(35,132,67)} .YlGn .q7-8{fill:rgb(0,90,50)} .YlGn .q0-9{fill:rgb(255,255,229)} .YlGn .q1-9{fill:rgb(247,252,185)} .YlGn .q2-9{fill:rgb(217,240,163)} .YlGn .q3-9{fill:rgb(173,221,142)} .YlGn .q4-9{fill:rgb(120,198,121)} .YlGn .q5-9{fill:rgb(65,171,93)} .YlGn .q6-9{fill:rgb(35,132,67)} .YlGn .q7-9{fill:rgb(0,104,55)} .YlGn .q8-9{fill:rgb(0,69,41)} .YlGnBu .q0-3{fill:rgb(237,248,177)} .YlGnBu .q1-3{fill:rgb(127,205,187)} .YlGnBu .q2-3{fill:rgb(44,127,184)} .YlGnBu .q0-4{fill:rgb(255,255,204)} .YlGnBu .q1-4{fill:rgb(161,218,180)} .YlGnBu .q2-4{fill:rgb(65,182,196)} .YlGnBu .q3-4{fill:rgb(34,94,168)} .YlGnBu .q0-5{fill:rgb(255,255,204)} .YlGnBu .q1-5{fill:rgb(161,218,180)} .YlGnBu .q2-5{fill:rgb(65,182,196)} .YlGnBu .q3-5{fill:rgb(44,127,184)} .YlGnBu .q4-5{fill:rgb(37,52,148)} .YlGnBu .q0-6{fill:rgb(255,255,204)} .YlGnBu .q1-6{fill:rgb(199,233,180)} .YlGnBu .q2-6{fill:rgb(127,205,187)} .YlGnBu .q3-6{fill:rgb(65,182,196)} .YlGnBu .q4-6{fill:rgb(44,127,184)} .YlGnBu .q5-6{fill:rgb(37,52,148)} .YlGnBu .q0-7{fill:rgb(255,255,204)} .YlGnBu .q1-7{fill:rgb(199,233,180)} .YlGnBu .q2-7{fill:rgb(127,205,187)} .YlGnBu .q3-7{fill:rgb(65,182,196)} .YlGnBu .q4-7{fill:rgb(29,145,192)} .YlGnBu .q5-7{fill:rgb(34,94,168)} .YlGnBu .q6-7{fill:rgb(12,44,132)} .YlGnBu .q0-8{fill:rgb(255,255,217)} .YlGnBu .q1-8{fill:rgb(237,248,177)} .YlGnBu .q2-8{fill:rgb(199,233,180)} .YlGnBu .q3-8{fill:rgb(127,205,187)} .YlGnBu .q4-8{fill:rgb(65,182,196)} .YlGnBu .q5-8{fill:rgb(29,145,192)} .YlGnBu .q6-8{fill:rgb(34,94,168)} .YlGnBu .q7-8{fill:rgb(12,44,132)} .YlGnBu .q0-9{fill:rgb(255,255,217)} .YlGnBu .q1-9{fill:rgb(237,248,177)} .YlGnBu .q2-9{fill:rgb(199,233,180)} .YlGnBu .q3-9{fill:rgb(127,205,187)} .YlGnBu .q4-9{fill:rgb(65,182,196)} .YlGnBu .q5-9{fill:rgb(29,145,192)} .YlGnBu .q6-9{fill:rgb(34,94,168)} .YlGnBu .q7-9{fill:rgb(37,52,148)} .YlGnBu .q8-9{fill:rgb(8,29,88)} .GnBu .q0-3{fill:rgb(224,243,219)} .GnBu .q1-3{fill:rgb(168,221,181)} .GnBu .q2-3{fill:rgb(67,162,202)} .GnBu .q0-4{fill:rgb(240,249,232)} .GnBu .q1-4{fill:rgb(186,228,188)} .GnBu .q2-4{fill:rgb(123,204,196)} .GnBu .q3-4{fill:rgb(43,140,190)} .GnBu .q0-5{fill:rgb(240,249,232)} .GnBu .q1-5{fill:rgb(186,228,188)} .GnBu .q2-5{fill:rgb(123,204,196)} .GnBu .q3-5{fill:rgb(67,162,202)} .GnBu .q4-5{fill:rgb(8,104,172)} .GnBu .q0-6{fill:rgb(240,249,232)} .GnBu .q1-6{fill:rgb(204,235,197)} .GnBu .q2-6{fill:rgb(168,221,181)} .GnBu .q3-6{fill:rgb(123,204,196)} .GnBu .q4-6{fill:rgb(67,162,202)} .GnBu .q5-6{fill:rgb(8,104,172)} .GnBu .q0-7{fill:rgb(240,249,232)} .GnBu .q1-7{fill:rgb(204,235,197)} .GnBu .q2-7{fill:rgb(168,221,181)} .GnBu .q3-7{fill:rgb(123,204,196)} .GnBu .q4-7{fill:rgb(78,179,211)} .GnBu .q5-7{fill:rgb(43,140,190)} .GnBu .q6-7{fill:rgb(8,88,158)} .GnBu .q0-8{fill:rgb(247,252,240)} .GnBu .q1-8{fill:rgb(224,243,219)} .GnBu .q2-8{fill:rgb(204,235,197)} .GnBu .q3-8{fill:rgb(168,221,181)} .GnBu .q4-8{fill:rgb(123,204,196)} .GnBu .q5-8{fill:rgb(78,179,211)} .GnBu .q6-8{fill:rgb(43,140,190)} .GnBu .q7-8{fill:rgb(8,88,158)} .GnBu .q0-9{fill:rgb(247,252,240)} .GnBu .q1-9{fill:rgb(224,243,219)} .GnBu .q2-9{fill:rgb(204,235,197)} .GnBu .q3-9{fill:rgb(168,221,181)} .GnBu .q4-9{fill:rgb(123,204,196)} .GnBu .q5-9{fill:rgb(78,179,211)} .GnBu .q6-9{fill:rgb(43,140,190)} .GnBu .q7-9{fill:rgb(8,104,172)} .GnBu .q8-9{fill:rgb(8,64,129)} .BuGn .q0-3{fill:rgb(229,245,249)} .BuGn .q1-3{fill:rgb(153,216,201)} .BuGn .q2-3{fill:rgb(44,162,95)} .BuGn .q0-4{fill:rgb(237,248,251)} .BuGn .q1-4{fill:rgb(178,226,226)} .BuGn .q2-4{fill:rgb(102,194,164)} .BuGn .q3-4{fill:rgb(35,139,69)} .BuGn .q0-5{fill:rgb(237,248,251)} .BuGn .q1-5{fill:rgb(178,226,226)} .BuGn .q2-5{fill:rgb(102,194,164)} .BuGn .q3-5{fill:rgb(44,162,95)} .BuGn .q4-5{fill:rgb(0,109,44)} .BuGn .q0-6{fill:rgb(237,248,251)} .BuGn .q1-6{fill:rgb(204,236,230)} .BuGn .q2-6{fill:rgb(153,216,201)} .BuGn .q3-6{fill:rgb(102,194,164)} .BuGn .q4-6{fill:rgb(44,162,95)} .BuGn .q5-6{fill:rgb(0,109,44)} .BuGn .q0-7{fill:rgb(237,248,251)} .BuGn .q1-7{fill:rgb(204,236,230)} .BuGn .q2-7{fill:rgb(153,216,201)} .BuGn .q3-7{fill:rgb(102,194,164)} .BuGn .q4-7{fill:rgb(65,174,118)} .BuGn .q5-7{fill:rgb(35,139,69)} .BuGn .q6-7{fill:rgb(0,88,36)} .BuGn .q0-8{fill:rgb(247,252,253)} .BuGn .q1-8{fill:rgb(229,245,249)} .BuGn .q2-8{fill:rgb(204,236,230)} .BuGn .q3-8{fill:rgb(153,216,201)} .BuGn .q4-8{fill:rgb(102,194,164)} .BuGn .q5-8{fill:rgb(65,174,118)} .BuGn .q6-8{fill:rgb(35,139,69)} .BuGn .q7-8{fill:rgb(0,88,36)} .BuGn .q0-9{fill:rgb(247,252,253)} .BuGn .q1-9{fill:rgb(229,245,249)} .BuGn .q2-9{fill:rgb(204,236,230)} .BuGn .q3-9{fill:rgb(153,216,201)} .BuGn .q4-9{fill:rgb(102,194,164)} .BuGn .q5-9{fill:rgb(65,174,118)} .BuGn .q6-9{fill:rgb(35,139,69)} .BuGn .q7-9{fill:rgb(0,109,44)} .BuGn .q8-9{fill:rgb(0,68,27)} .PuBuGn .q0-3{fill:rgb(236,226,240)} .PuBuGn .q1-3{fill:rgb(166,189,219)} .PuBuGn .q2-3{fill:rgb(28,144,153)} .PuBuGn .q0-4{fill:rgb(246,239,247)} .PuBuGn .q1-4{fill:rgb(189,201,225)} .PuBuGn .q2-4{fill:rgb(103,169,207)} .PuBuGn .q3-4{fill:rgb(2,129,138)} .PuBuGn .q0-5{fill:rgb(246,239,247)} .PuBuGn .q1-5{fill:rgb(189,201,225)} .PuBuGn .q2-5{fill:rgb(103,169,207)} .PuBuGn .q3-5{fill:rgb(28,144,153)} .PuBuGn .q4-5{fill:rgb(1,108,89)} .PuBuGn .q0-6{fill:rgb(246,239,247)} .PuBuGn .q1-6{fill:rgb(208,209,230)} .PuBuGn .q2-6{fill:rgb(166,189,219)} .PuBuGn .q3-6{fill:rgb(103,169,207)} .PuBuGn .q4-6{fill:rgb(28,144,153)} .PuBuGn .q5-6{fill:rgb(1,108,89)} .PuBuGn .q0-7{fill:rgb(246,239,247)} .PuBuGn .q1-7{fill:rgb(208,209,230)} .PuBuGn .q2-7{fill:rgb(166,189,219)} .PuBuGn .q3-7{fill:rgb(103,169,207)} .PuBuGn .q4-7{fill:rgb(54,144,192)} .PuBuGn .q5-7{fill:rgb(2,129,138)} .PuBuGn .q6-7{fill:rgb(1,100,80)} .PuBuGn .q0-8{fill:rgb(255,247,251)} .PuBuGn .q1-8{fill:rgb(236,226,240)} .PuBuGn .q2-8{fill:rgb(208,209,230)} .PuBuGn .q3-8{fill:rgb(166,189,219)} .PuBuGn .q4-8{fill:rgb(103,169,207)} .PuBuGn .q5-8{fill:rgb(54,144,192)} .PuBuGn .q6-8{fill:rgb(2,129,138)} .PuBuGn .q7-8{fill:rgb(1,100,80)} .PuBuGn .q0-9{fill:rgb(255,247,251)} .PuBuGn .q1-9{fill:rgb(236,226,240)} .PuBuGn .q2-9{fill:rgb(208,209,230)} .PuBuGn .q3-9{fill:rgb(166,189,219)} .PuBuGn .q4-9{fill:rgb(103,169,207)} .PuBuGn .q5-9{fill:rgb(54,144,192)} .PuBuGn .q6-9{fill:rgb(2,129,138)} .PuBuGn .q7-9{fill:rgb(1,108,89)} .PuBuGn .q8-9{fill:rgb(1,70,54)} .PuBu .q0-3{fill:rgb(236,231,242)} .PuBu .q1-3{fill:rgb(166,189,219)} .PuBu .q2-3{fill:rgb(43,140,190)} .PuBu .q0-4{fill:rgb(241,238,246)} .PuBu .q1-4{fill:rgb(189,201,225)} .PuBu .q2-4{fill:rgb(116,169,207)} .PuBu .q3-4{fill:rgb(5,112,176)} .PuBu .q0-5{fill:rgb(241,238,246)} .PuBu .q1-5{fill:rgb(189,201,225)} .PuBu .q2-5{fill:rgb(116,169,207)} .PuBu .q3-5{fill:rgb(43,140,190)} .PuBu .q4-5{fill:rgb(4,90,141)} .PuBu .q0-6{fill:rgb(241,238,246)} .PuBu .q1-6{fill:rgb(208,209,230)} .PuBu .q2-6{fill:rgb(166,189,219)} .PuBu .q3-6{fill:rgb(116,169,207)} .PuBu .q4-6{fill:rgb(43,140,190)} .PuBu .q5-6{fill:rgb(4,90,141)} .PuBu .q0-7{fill:rgb(241,238,246)} .PuBu .q1-7{fill:rgb(208,209,230)} .PuBu .q2-7{fill:rgb(166,189,219)} .PuBu .q3-7{fill:rgb(116,169,207)} .PuBu .q4-7{fill:rgb(54,144,192)} .PuBu .q5-7{fill:rgb(5,112,176)} .PuBu .q6-7{fill:rgb(3,78,123)} .PuBu .q0-8{fill:rgb(255,247,251)} .PuBu .q1-8{fill:rgb(236,231,242)} .PuBu .q2-8{fill:rgb(208,209,230)} .PuBu .q3-8{fill:rgb(166,189,219)} .PuBu .q4-8{fill:rgb(116,169,207)} .PuBu .q5-8{fill:rgb(54,144,192)} .PuBu .q6-8{fill:rgb(5,112,176)} .PuBu .q7-8{fill:rgb(3,78,123)} .PuBu .q0-9{fill:rgb(255,247,251)} .PuBu .q1-9{fill:rgb(236,231,242)} .PuBu .q2-9{fill:rgb(208,209,230)} .PuBu .q3-9{fill:rgb(166,189,219)} .PuBu .q4-9{fill:rgb(116,169,207)} .PuBu .q5-9{fill:rgb(54,144,192)} .PuBu .q6-9{fill:rgb(5,112,176)} .PuBu .q7-9{fill:rgb(4,90,141)} .PuBu .q8-9{fill:rgb(2,56,88)} .BuPu .q0-3{fill:rgb(224,236,244)} .BuPu .q1-3{fill:rgb(158,188,218)} .BuPu .q2-3{fill:rgb(136,86,167)} .BuPu .q0-4{fill:rgb(237,248,251)} .BuPu .q1-4{fill:rgb(179,205,227)} .BuPu .q2-4{fill:rgb(140,150,198)} .BuPu .q3-4{fill:rgb(136,65,157)} .BuPu .q0-5{fill:rgb(237,248,251)} .BuPu .q1-5{fill:rgb(179,205,227)} .BuPu .q2-5{fill:rgb(140,150,198)} .BuPu .q3-5{fill:rgb(136,86,167)} .BuPu .q4-5{fill:rgb(129,15,124)} .BuPu .q0-6{fill:rgb(237,248,251)} .BuPu .q1-6{fill:rgb(191,211,230)} .BuPu .q2-6{fill:rgb(158,188,218)} .BuPu .q3-6{fill:rgb(140,150,198)} .BuPu .q4-6{fill:rgb(136,86,167)} .BuPu .q5-6{fill:rgb(129,15,124)} .BuPu .q0-7{fill:rgb(237,248,251)} .BuPu .q1-7{fill:rgb(191,211,230)} .BuPu .q2-7{fill:rgb(158,188,218)} .BuPu .q3-7{fill:rgb(140,150,198)} .BuPu .q4-7{fill:rgb(140,107,177)} .BuPu .q5-7{fill:rgb(136,65,157)} .BuPu .q6-7{fill:rgb(110,1,107)} .BuPu .q0-8{fill:rgb(247,252,253)} .BuPu .q1-8{fill:rgb(224,236,244)} .BuPu .q2-8{fill:rgb(191,211,230)} .BuPu .q3-8{fill:rgb(158,188,218)} .BuPu .q4-8{fill:rgb(140,150,198)} .BuPu .q5-8{fill:rgb(140,107,177)} .BuPu .q6-8{fill:rgb(136,65,157)} .BuPu .q7-8{fill:rgb(110,1,107)} .BuPu .q0-9{fill:rgb(247,252,253)} .BuPu .q1-9{fill:rgb(224,236,244)} .BuPu .q2-9{fill:rgb(191,211,230)} .BuPu .q3-9{fill:rgb(158,188,218)} .BuPu .q4-9{fill:rgb(140,150,198)} .BuPu .q5-9{fill:rgb(140,107,177)} .BuPu .q6-9{fill:rgb(136,65,157)} .BuPu .q7-9{fill:rgb(129,15,124)} .BuPu .q8-9{fill:rgb(77,0,75)} .RdPu .q0-3{fill:rgb(253,224,221)} .RdPu .q1-3{fill:rgb(250,159,181)} .RdPu .q2-3{fill:rgb(197,27,138)} .RdPu .q0-4{fill:rgb(254,235,226)} .RdPu .q1-4{fill:rgb(251,180,185)} .RdPu .q2-4{fill:rgb(247,104,161)} .RdPu .q3-4{fill:rgb(174,1,126)} .RdPu .q0-5{fill:rgb(254,235,226)} .RdPu .q1-5{fill:rgb(251,180,185)} .RdPu .q2-5{fill:rgb(247,104,161)} .RdPu .q3-5{fill:rgb(197,27,138)} .RdPu .q4-5{fill:rgb(122,1,119)} .RdPu .q0-6{fill:rgb(254,235,226)} .RdPu .q1-6{fill:rgb(252,197,192)} .RdPu .q2-6{fill:rgb(250,159,181)} .RdPu .q3-6{fill:rgb(247,104,161)} .RdPu .q4-6{fill:rgb(197,27,138)} .RdPu .q5-6{fill:rgb(122,1,119)} .RdPu .q0-7{fill:rgb(254,235,226)} .RdPu .q1-7{fill:rgb(252,197,192)} .RdPu .q2-7{fill:rgb(250,159,181)} .RdPu .q3-7{fill:rgb(247,104,161)} .RdPu .q4-7{fill:rgb(221,52,151)} .RdPu .q5-7{fill:rgb(174,1,126)} .RdPu .q6-7{fill:rgb(122,1,119)} .RdPu .q0-8{fill:rgb(255,247,243)} .RdPu .q1-8{fill:rgb(253,224,221)} .RdPu .q2-8{fill:rgb(252,197,192)} .RdPu .q3-8{fill:rgb(250,159,181)} .RdPu .q4-8{fill:rgb(247,104,161)} .RdPu .q5-8{fill:rgb(221,52,151)} .RdPu .q6-8{fill:rgb(174,1,126)} .RdPu .q7-8{fill:rgb(122,1,119)} .RdPu .q0-9{fill:rgb(255,247,243)} .RdPu .q1-9{fill:rgb(253,224,221)} .RdPu .q2-9{fill:rgb(252,197,192)} .RdPu .q3-9{fill:rgb(250,159,181)} .RdPu .q4-9{fill:rgb(247,104,161)} .RdPu .q5-9{fill:rgb(221,52,151)} .RdPu .q6-9{fill:rgb(174,1,126)} .RdPu .q7-9{fill:rgb(122,1,119)} .RdPu .q8-9{fill:rgb(73,0,106)} .PuRd .q0-3{fill:rgb(231,225,239)} .PuRd .q1-3{fill:rgb(201,148,199)} .PuRd .q2-3{fill:rgb(221,28,119)} .PuRd .q0-4{fill:rgb(241,238,246)} .PuRd .q1-4{fill:rgb(215,181,216)} .PuRd .q2-4{fill:rgb(223,101,176)} .PuRd .q3-4{fill:rgb(206,18,86)} .PuRd .q0-5{fill:rgb(241,238,246)} .PuRd .q1-5{fill:rgb(215,181,216)} .PuRd .q2-5{fill:rgb(223,101,176)} .PuRd .q3-5{fill:rgb(221,28,119)} .PuRd .q4-5{fill:rgb(152,0,67)} .PuRd .q0-6{fill:rgb(241,238,246)} .PuRd .q1-6{fill:rgb(212,185,218)} .PuRd .q2-6{fill:rgb(201,148,199)} .PuRd .q3-6{fill:rgb(223,101,176)} .PuRd .q4-6{fill:rgb(221,28,119)} .PuRd .q5-6{fill:rgb(152,0,67)} .PuRd .q0-7{fill:rgb(241,238,246)} .PuRd .q1-7{fill:rgb(212,185,218)} .PuRd .q2-7{fill:rgb(201,148,199)} .PuRd .q3-7{fill:rgb(223,101,176)} .PuRd .q4-7{fill:rgb(231,41,138)} .PuRd .q5-7{fill:rgb(206,18,86)} .PuRd .q6-7{fill:rgb(145,0,63)} .PuRd .q0-8{fill:rgb(247,244,249)} .PuRd .q1-8{fill:rgb(231,225,239)} .PuRd .q2-8{fill:rgb(212,185,218)} .PuRd .q3-8{fill:rgb(201,148,199)} .PuRd .q4-8{fill:rgb(223,101,176)} .PuRd .q5-8{fill:rgb(231,41,138)} .PuRd .q6-8{fill:rgb(206,18,86)} .PuRd .q7-8{fill:rgb(145,0,63)} .PuRd .q0-9{fill:rgb(247,244,249)} .PuRd .q1-9{fill:rgb(231,225,239)} .PuRd .q2-9{fill:rgb(212,185,218)} .PuRd .q3-9{fill:rgb(201,148,199)} .PuRd .q4-9{fill:rgb(223,101,176)} .PuRd .q5-9{fill:rgb(231,41,138)} .PuRd .q6-9{fill:rgb(206,18,86)} .PuRd .q7-9{fill:rgb(152,0,67)} .PuRd .q8-9{fill:rgb(103,0,31)} .OrRd .q0-3{fill:rgb(254,232,200)} .OrRd .q1-3{fill:rgb(253,187,132)} .OrRd .q2-3{fill:rgb(227,74,51)} .OrRd .q0-4{fill:rgb(254,240,217)} .OrRd .q1-4{fill:rgb(253,204,138)} .OrRd .q2-4{fill:rgb(252,141,89)} .OrRd .q3-4{fill:rgb(215,48,31)} .OrRd .q0-5{fill:rgb(254,240,217)} .OrRd .q1-5{fill:rgb(253,204,138)} .OrRd .q2-5{fill:rgb(252,141,89)} .OrRd .q3-5{fill:rgb(227,74,51)} .OrRd .q4-5{fill:rgb(179,0,0)} .OrRd .q0-6{fill:rgb(254,240,217)} .OrRd .q1-6{fill:rgb(253,212,158)} .OrRd .q2-6{fill:rgb(253,187,132)} .OrRd .q3-6{fill:rgb(252,141,89)} .OrRd .q4-6{fill:rgb(227,74,51)} .OrRd .q5-6{fill:rgb(179,0,0)} .OrRd .q0-7{fill:rgb(254,240,217)} .OrRd .q1-7{fill:rgb(253,212,158)} .OrRd .q2-7{fill:rgb(253,187,132)} .OrRd .q3-7{fill:rgb(252,141,89)} .OrRd .q4-7{fill:rgb(239,101,72)} .OrRd .q5-7{fill:rgb(215,48,31)} .OrRd .q6-7{fill:rgb(153,0,0)} .OrRd .q0-8{fill:rgb(255,247,236)} .OrRd .q1-8{fill:rgb(254,232,200)} .OrRd .q2-8{fill:rgb(253,212,158)} .OrRd .q3-8{fill:rgb(253,187,132)} .OrRd .q4-8{fill:rgb(252,141,89)} .OrRd .q5-8{fill:rgb(239,101,72)} .OrRd .q6-8{fill:rgb(215,48,31)} .OrRd .q7-8{fill:rgb(153,0,0)} .OrRd .q0-9{fill:rgb(255,247,236)} .OrRd .q1-9{fill:rgb(254,232,200)} .OrRd .q2-9{fill:rgb(253,212,158)} .OrRd .q3-9{fill:rgb(253,187,132)} .OrRd .q4-9{fill:rgb(252,141,89)} .OrRd .q5-9{fill:rgb(239,101,72)} .OrRd .q6-9{fill:rgb(215,48,31)} .OrRd .q7-9{fill:rgb(179,0,0)} .OrRd .q8-9{fill:rgb(127,0,0)} .YlOrRd .q0-3{fill:rgb(255,237,160)} .YlOrRd .q1-3{fill:rgb(254,178,76)} .YlOrRd .q2-3{fill:rgb(240,59,32)} .YlOrRd .q0-4{fill:rgb(255,255,178)} .YlOrRd .q1-4{fill:rgb(254,204,92)} .YlOrRd .q2-4{fill:rgb(253,141,60)} .YlOrRd .q3-4{fill:rgb(227,26,28)} .YlOrRd .q0-5{fill:rgb(255,255,178)} .YlOrRd .q1-5{fill:rgb(254,204,92)} .YlOrRd .q2-5{fill:rgb(253,141,60)} .YlOrRd .q3-5{fill:rgb(240,59,32)} .YlOrRd .q4-5{fill:rgb(189,0,38)} .YlOrRd .q0-6{fill:rgb(255,255,178)} .YlOrRd .q1-6{fill:rgb(254,217,118)} .YlOrRd .q2-6{fill:rgb(254,178,76)} .YlOrRd .q3-6{fill:rgb(253,141,60)} .YlOrRd .q4-6{fill:rgb(240,59,32)} .YlOrRd .q5-6{fill:rgb(189,0,38)} .YlOrRd .q0-7{fill:rgb(255,255,178)} .YlOrRd .q1-7{fill:rgb(254,217,118)} .YlOrRd .q2-7{fill:rgb(254,178,76)} .YlOrRd .q3-7{fill:rgb(253,141,60)} .YlOrRd .q4-7{fill:rgb(252,78,42)} .YlOrRd .q5-7{fill:rgb(227,26,28)} .YlOrRd .q6-7{fill:rgb(177,0,38)} .YlOrRd .q0-8{fill:rgb(255,255,204)} .YlOrRd .q1-8{fill:rgb(255,237,160)} .YlOrRd .q2-8{fill:rgb(254,217,118)} .YlOrRd .q3-8{fill:rgb(254,178,76)} .YlOrRd .q4-8{fill:rgb(253,141,60)} .YlOrRd .q5-8{fill:rgb(252,78,42)} .YlOrRd .q6-8{fill:rgb(227,26,28)} .YlOrRd .q7-8{fill:rgb(177,0,38)} .YlOrRd .q0-9{fill:rgb(255,255,204)} .YlOrRd .q1-9{fill:rgb(255,237,160)} .YlOrRd .q2-9{fill:rgb(254,217,118)} .YlOrRd .q3-9{fill:rgb(254,178,76)} .YlOrRd .q4-9{fill:rgb(253,141,60)} .YlOrRd .q5-9{fill:rgb(252,78,42)} .YlOrRd .q6-9{fill:rgb(227,26,28)} .YlOrRd .q7-9{fill:rgb(189,0,38)} .YlOrRd .q8-9{fill:rgb(128,0,38)} .YlOrBr .q0-3{fill:rgb(255,247,188)} .YlOrBr .q1-3{fill:rgb(254,196,79)} .YlOrBr .q2-3{fill:rgb(217,95,14)} .YlOrBr .q0-4{fill:rgb(255,255,212)} .YlOrBr .q1-4{fill:rgb(254,217,142)} .YlOrBr .q2-4{fill:rgb(254,153,41)} .YlOrBr .q3-4{fill:rgb(204,76,2)} .YlOrBr .q0-5{fill:rgb(255,255,212)} .YlOrBr .q1-5{fill:rgb(254,217,142)} .YlOrBr .q2-5{fill:rgb(254,153,41)} .YlOrBr .q3-5{fill:rgb(217,95,14)} .YlOrBr .q4-5{fill:rgb(153,52,4)} .YlOrBr .q0-6{fill:rgb(255,255,212)} .YlOrBr .q1-6{fill:rgb(254,227,145)} .YlOrBr .q2-6{fill:rgb(254,196,79)} .YlOrBr .q3-6{fill:rgb(254,153,41)} .YlOrBr .q4-6{fill:rgb(217,95,14)} .YlOrBr .q5-6{fill:rgb(153,52,4)} .YlOrBr .q0-7{fill:rgb(255,255,212)} .YlOrBr .q1-7{fill:rgb(254,227,145)} .YlOrBr .q2-7{fill:rgb(254,196,79)} .YlOrBr .q3-7{fill:rgb(254,153,41)} .YlOrBr .q4-7{fill:rgb(236,112,20)} .YlOrBr .q5-7{fill:rgb(204,76,2)} .YlOrBr .q6-7{fill:rgb(140,45,4)} .YlOrBr .q0-8{fill:rgb(255,255,229)} .YlOrBr .q1-8{fill:rgb(255,247,188)} .YlOrBr .q2-8{fill:rgb(254,227,145)} .YlOrBr .q3-8{fill:rgb(254,196,79)} .YlOrBr .q4-8{fill:rgb(254,153,41)} .YlOrBr .q5-8{fill:rgb(236,112,20)} .YlOrBr .q6-8{fill:rgb(204,76,2)} .YlOrBr .q7-8{fill:rgb(140,45,4)} .YlOrBr .q0-9{fill:rgb(255,255,229)} .YlOrBr .q1-9{fill:rgb(255,247,188)} .YlOrBr .q2-9{fill:rgb(254,227,145)} .YlOrBr .q3-9{fill:rgb(254,196,79)} .YlOrBr .q4-9{fill:rgb(254,153,41)} .YlOrBr .q5-9{fill:rgb(236,112,20)} .YlOrBr .q6-9{fill:rgb(204,76,2)} .YlOrBr .q7-9{fill:rgb(153,52,4)} .YlOrBr .q8-9{fill:rgb(102,37,6)} .Purples .q0-3{fill:rgb(239,237,245)} .Purples .q1-3{fill:rgb(188,189,220)} .Purples .q2-3{fill:rgb(117,107,177)} .Purples .q0-4{fill:rgb(242,240,247)} .Purples .q1-4{fill:rgb(203,201,226)} .Purples .q2-4{fill:rgb(158,154,200)} .Purples .q3-4{fill:rgb(106,81,163)} .Purples .q0-5{fill:rgb(242,240,247)} .Purples .q1-5{fill:rgb(203,201,226)} .Purples .q2-5{fill:rgb(158,154,200)} .Purples .q3-5{fill:rgb(117,107,177)} .Purples .q4-5{fill:rgb(84,39,143)} .Purples .q0-6{fill:rgb(242,240,247)} .Purples .q1-6{fill:rgb(218,218,235)} .Purples .q2-6{fill:rgb(188,189,220)} .Purples .q3-6{fill:rgb(158,154,200)} .Purples .q4-6{fill:rgb(117,107,177)} .Purples .q5-6{fill:rgb(84,39,143)} .Purples .q0-7{fill:rgb(242,240,247)} .Purples .q1-7{fill:rgb(218,218,235)} .Purples .q2-7{fill:rgb(188,189,220)} .Purples .q3-7{fill:rgb(158,154,200)} .Purples .q4-7{fill:rgb(128,125,186)} .Purples .q5-7{fill:rgb(106,81,163)} .Purples .q6-7{fill:rgb(74,20,134)} .Purples .q0-8{fill:rgb(252,251,253)} .Purples .q1-8{fill:rgb(239,237,245)} .Purples .q2-8{fill:rgb(218,218,235)} .Purples .q3-8{fill:rgb(188,189,220)} .Purples .q4-8{fill:rgb(158,154,200)} .Purples .q5-8{fill:rgb(128,125,186)} .Purples .q6-8{fill:rgb(106,81,163)} .Purples .q7-8{fill:rgb(74,20,134)} .Purples .q0-9{fill:rgb(252,251,253)} .Purples .q1-9{fill:rgb(239,237,245)} .Purples .q2-9{fill:rgb(218,218,235)} .Purples .q3-9{fill:rgb(188,189,220)} .Purples .q4-9{fill:rgb(158,154,200)} .Purples .q5-9{fill:rgb(128,125,186)} .Purples .q6-9{fill:rgb(106,81,163)} .Purples .q7-9{fill:rgb(84,39,143)} .Purples .q8-9{fill:rgb(63,0,125)} .Blues .q0-3{fill:rgb(222,235,247)} .Blues .q1-3{fill:rgb(158,202,225)} .Blues .q2-3{fill:rgb(49,130,189)} .Blues .q0-4{fill:rgb(239,243,255)} .Blues .q1-4{fill:rgb(189,215,231)} .Blues .q2-4{fill:rgb(107,174,214)} .Blues .q3-4{fill:rgb(33,113,181)} .Blues .q0-5{fill:rgb(239,243,255)} .Blues .q1-5{fill:rgb(189,215,231)} .Blues .q2-5{fill:rgb(107,174,214)} .Blues .q3-5{fill:rgb(49,130,189)} .Blues .q4-5{fill:rgb(8,81,156)} .Blues .q0-6{fill:rgb(239,243,255)} .Blues .q1-6{fill:rgb(198,219,239)} .Blues .q2-6{fill:rgb(158,202,225)} .Blues .q3-6{fill:rgb(107,174,214)} .Blues .q4-6{fill:rgb(49,130,189)} .Blues .q5-6{fill:rgb(8,81,156)} .Blues .q0-7{fill:rgb(239,243,255)} .Blues .q1-7{fill:rgb(198,219,239)} .Blues .q2-7{fill:rgb(158,202,225)} .Blues .q3-7{fill:rgb(107,174,214)} .Blues .q4-7{fill:rgb(66,146,198)} .Blues .q5-7{fill:rgb(33,113,181)} .Blues .q6-7{fill:rgb(8,69,148)} .Blues .q0-8{fill:rgb(247,251,255)} .Blues .q1-8{fill:rgb(222,235,247)} .Blues .q2-8{fill:rgb(198,219,239)} .Blues .q3-8{fill:rgb(158,202,225)} .Blues .q4-8{fill:rgb(107,174,214)} .Blues .q5-8{fill:rgb(66,146,198)} .Blues .q6-8{fill:rgb(33,113,181)} .Blues .q7-8{fill:rgb(8,69,148)} .Blues .q0-9{fill:rgb(247,251,255)} .Blues .q1-9{fill:rgb(222,235,247)} .Blues .q2-9{fill:rgb(198,219,239)} .Blues .q3-9{fill:rgb(158,202,225)} .Blues .q4-9{fill:rgb(107,174,214)} .Blues .q5-9{fill:rgb(66,146,198)} .Blues .q6-9{fill:rgb(33,113,181)} .Blues .q7-9{fill:rgb(8,81,156)} .Blues .q8-9{fill:rgb(8,48,107)} .Greens .q0-3{fill:rgb(229,245,224)} .Greens .q1-3{fill:rgb(161,217,155)} .Greens .q2-3{fill:rgb(49,163,84)} .Greens .q0-4{fill:rgb(237,248,233)} .Greens .q1-4{fill:rgb(186,228,179)} .Greens .q2-4{fill:rgb(116,196,118)} .Greens .q3-4{fill:rgb(35,139,69)} .Greens .q0-5{fill:rgb(237,248,233)} .Greens .q1-5{fill:rgb(186,228,179)} .Greens .q2-5{fill:rgb(116,196,118)} .Greens .q3-5{fill:rgb(49,163,84)} .Greens .q4-5{fill:rgb(0,109,44)} .Greens .q0-6{fill:rgb(237,248,233)} .Greens .q1-6{fill:rgb(199,233,192)} .Greens .q2-6{fill:rgb(161,217,155)} .Greens .q3-6{fill:rgb(116,196,118)} .Greens .q4-6{fill:rgb(49,163,84)} .Greens .q5-6{fill:rgb(0,109,44)} .Greens .q0-7{fill:rgb(237,248,233)} .Greens .q1-7{fill:rgb(199,233,192)} .Greens .q2-7{fill:rgb(161,217,155)} .Greens .q3-7{fill:rgb(116,196,118)} .Greens .q4-7{fill:rgb(65,171,93)} .Greens .q5-7{fill:rgb(35,139,69)} .Greens .q6-7{fill:rgb(0,90,50)} .Greens .q0-8{fill:rgb(247,252,245)} .Greens .q1-8{fill:rgb(229,245,224)} .Greens .q2-8{fill:rgb(199,233,192)} .Greens .q3-8{fill:rgb(161,217,155)} .Greens .q4-8{fill:rgb(116,196,118)} .Greens .q5-8{fill:rgb(65,171,93)} .Greens .q6-8{fill:rgb(35,139,69)} .Greens .q7-8{fill:rgb(0,90,50)} .Greens .q0-9{fill:rgb(247,252,245)} .Greens .q1-9{fill:rgb(229,245,224)} .Greens .q2-9{fill:rgb(199,233,192)} .Greens .q3-9{fill:rgb(161,217,155)} .Greens .q4-9{fill:rgb(116,196,118)} .Greens .q5-9{fill:rgb(65,171,93)} .Greens .q6-9{fill:rgb(35,139,69)} .Greens .q7-9{fill:rgb(0,109,44)} .Greens .q8-9{fill:rgb(0,68,27)} .Oranges .q0-3{fill:rgb(254,230,206)} .Oranges .q1-3{fill:rgb(253,174,107)} .Oranges .q2-3{fill:rgb(230,85,13)} .Oranges .q0-4{fill:rgb(254,237,222)} .Oranges .q1-4{fill:rgb(253,190,133)} .Oranges .q2-4{fill:rgb(253,141,60)} .Oranges .q3-4{fill:rgb(217,71,1)} .Oranges .q0-5{fill:rgb(254,237,222)} .Oranges .q1-5{fill:rgb(253,190,133)} .Oranges .q2-5{fill:rgb(253,141,60)} .Oranges .q3-5{fill:rgb(230,85,13)} .Oranges .q4-5{fill:rgb(166,54,3)} .Oranges .q0-6{fill:rgb(254,237,222)} .Oranges .q1-6{fill:rgb(253,208,162)} .Oranges .q2-6{fill:rgb(253,174,107)} .Oranges .q3-6{fill:rgb(253,141,60)} .Oranges .q4-6{fill:rgb(230,85,13)} .Oranges .q5-6{fill:rgb(166,54,3)} .Oranges .q0-7{fill:rgb(254,237,222)} .Oranges .q1-7{fill:rgb(253,208,162)} .Oranges .q2-7{fill:rgb(253,174,107)} .Oranges .q3-7{fill:rgb(253,141,60)} .Oranges .q4-7{fill:rgb(241,105,19)} .Oranges .q5-7{fill:rgb(217,72,1)} .Oranges .q6-7{fill:rgb(140,45,4)} .Oranges .q0-8{fill:rgb(255,245,235)} .Oranges .q1-8{fill:rgb(254,230,206)} .Oranges .q2-8{fill:rgb(253,208,162)} .Oranges .q3-8{fill:rgb(253,174,107)} .Oranges .q4-8{fill:rgb(253,141,60)} .Oranges .q5-8{fill:rgb(241,105,19)} .Oranges .q6-8{fill:rgb(217,72,1)} .Oranges .q7-8{fill:rgb(140,45,4)} .Oranges .q0-9{fill:rgb(255,245,235)} .Oranges .q1-9{fill:rgb(254,230,206)} .Oranges .q2-9{fill:rgb(253,208,162)} .Oranges .q3-9{fill:rgb(253,174,107)} .Oranges .q4-9{fill:rgb(253,141,60)} .Oranges .q5-9{fill:rgb(241,105,19)} .Oranges .q6-9{fill:rgb(217,72,1)} .Oranges .q7-9{fill:rgb(166,54,3)} .Oranges .q8-9{fill:rgb(127,39,4)} .Reds .q0-3{fill:rgb(254,224,210)} .Reds .q1-3{fill:rgb(252,146,114)} .Reds .q2-3{fill:rgb(222,45,38)} .Reds .q0-4{fill:rgb(254,229,217)} .Reds .q1-4{fill:rgb(252,174,145)} .Reds .q2-4{fill:rgb(251,106,74)} .Reds .q3-4{fill:rgb(203,24,29)} .Reds .q0-5{fill:rgb(254,229,217)} .Reds .q1-5{fill:rgb(252,174,145)} .Reds .q2-5{fill:rgb(251,106,74)} .Reds .q3-5{fill:rgb(222,45,38)} .Reds .q4-5{fill:rgb(165,15,21)} .Reds .q0-6{fill:rgb(254,229,217)} .Reds .q1-6{fill:rgb(252,187,161)} .Reds .q2-6{fill:rgb(252,146,114)} .Reds .q3-6{fill:rgb(251,106,74)} .Reds .q4-6{fill:rgb(222,45,38)} .Reds .q5-6{fill:rgb(165,15,21)} .Reds .q0-7{fill:rgb(254,229,217)} .Reds .q1-7{fill:rgb(252,187,161)} .Reds .q2-7{fill:rgb(252,146,114)} .Reds .q3-7{fill:rgb(251,106,74)} .Reds .q4-7{fill:rgb(239,59,44)} .Reds .q5-7{fill:rgb(203,24,29)} .Reds .q6-7{fill:rgb(153,0,13)} .Reds .q0-8{fill:rgb(255,245,240)} .Reds .q1-8{fill:rgb(254,224,210)} .Reds .q2-8{fill:rgb(252,187,161)} .Reds .q3-8{fill:rgb(252,146,114)} .Reds .q4-8{fill:rgb(251,106,74)} .Reds .q5-8{fill:rgb(239,59,44)} .Reds .q6-8{fill:rgb(203,24,29)} .Reds .q7-8{fill:rgb(153,0,13)} .Reds .q0-9{fill:rgb(255,245,240)} .Reds .q1-9{fill:rgb(254,224,210)} .Reds .q2-9{fill:rgb(252,187,161)} .Reds .q3-9{fill:rgb(252,146,114)} .Reds .q4-9{fill:rgb(251,106,74)} .Reds .q5-9{fill:rgb(239,59,44)} .Reds .q6-9{fill:rgb(203,24,29)} .Reds .q7-9{fill:rgb(165,15,21)} .Reds .q8-9{fill:rgb(103,0,13)} .Greys .q0-3{fill:rgb(240,240,240)} .Greys .q1-3{fill:rgb(189,189,189)} .Greys .q2-3{fill:rgb(99,99,99)} .Greys .q0-4{fill:rgb(247,247,247)} .Greys .q1-4{fill:rgb(204,204,204)} .Greys .q2-4{fill:rgb(150,150,150)} .Greys .q3-4{fill:rgb(82,82,82)} .Greys .q0-5{fill:rgb(247,247,247)} .Greys .q1-5{fill:rgb(204,204,204)} .Greys .q2-5{fill:rgb(150,150,150)} .Greys .q3-5{fill:rgb(99,99,99)} .Greys .q4-5{fill:rgb(37,37,37)} .Greys .q0-6{fill:rgb(247,247,247)} .Greys .q1-6{fill:rgb(217,217,217)} .Greys .q2-6{fill:rgb(189,189,189)} .Greys .q3-6{fill:rgb(150,150,150)} .Greys .q4-6{fill:rgb(99,99,99)} .Greys .q5-6{fill:rgb(37,37,37)} .Greys .q0-7{fill:rgb(247,247,247)} .Greys .q1-7{fill:rgb(217,217,217)} .Greys .q2-7{fill:rgb(189,189,189)} .Greys .q3-7{fill:rgb(150,150,150)} .Greys .q4-7{fill:rgb(115,115,115)} .Greys .q5-7{fill:rgb(82,82,82)} .Greys .q6-7{fill:rgb(37,37,37)} .Greys .q0-8{fill:rgb(255,255,255)} .Greys .q1-8{fill:rgb(240,240,240)} .Greys .q2-8{fill:rgb(217,217,217)} .Greys .q3-8{fill:rgb(189,189,189)} .Greys .q4-8{fill:rgb(150,150,150)} .Greys .q5-8{fill:rgb(115,115,115)} .Greys .q6-8{fill:rgb(82,82,82)} .Greys .q7-8{fill:rgb(37,37,37)} .Greys .q0-9{fill:rgb(255,255,255)} .Greys .q1-9{fill:rgb(240,240,240)} .Greys .q2-9{fill:rgb(217,217,217)} .Greys .q3-9{fill:rgb(189,189,189)} .Greys .q4-9{fill:rgb(150,150,150)} .Greys .q5-9{fill:rgb(115,115,115)} .Greys .q6-9{fill:rgb(82,82,82)} .Greys .q7-9{fill:rgb(37,37,37)} .Greys .q8-9{fill:rgb(0,0,0)} .PuOr .q0-3{fill:rgb(241,163,64)} .PuOr .q1-3{fill:rgb(247,247,247)} .PuOr .q2-3{fill:rgb(153,142,195)} .PuOr .q0-4{fill:rgb(230,97,1)} .PuOr .q1-4{fill:rgb(253,184,99)} .PuOr .q2-4{fill:rgb(178,171,210)} .PuOr .q3-4{fill:rgb(94,60,153)} .PuOr .q0-5{fill:rgb(230,97,1)} .PuOr .q1-5{fill:rgb(253,184,99)} .PuOr .q2-5{fill:rgb(247,247,247)} .PuOr .q3-5{fill:rgb(178,171,210)} .PuOr .q4-5{fill:rgb(94,60,153)} .PuOr .q0-6{fill:rgb(179,88,6)} .PuOr .q1-6{fill:rgb(241,163,64)} .PuOr .q2-6{fill:rgb(254,224,182)} .PuOr .q3-6{fill:rgb(216,218,235)} .PuOr .q4-6{fill:rgb(153,142,195)} .PuOr .q5-6{fill:rgb(84,39,136)} .PuOr .q0-7{fill:rgb(179,88,6)} .PuOr .q1-7{fill:rgb(241,163,64)} .PuOr .q2-7{fill:rgb(254,224,182)} .PuOr .q3-7{fill:rgb(247,247,247)} .PuOr .q4-7{fill:rgb(216,218,235)} .PuOr .q5-7{fill:rgb(153,142,195)} .PuOr .q6-7{fill:rgb(84,39,136)} .PuOr .q0-8{fill:rgb(179,88,6)} .PuOr .q1-8{fill:rgb(224,130,20)} .PuOr .q2-8{fill:rgb(253,184,99)} .PuOr .q3-8{fill:rgb(254,224,182)} .PuOr .q4-8{fill:rgb(216,218,235)} .PuOr .q5-8{fill:rgb(178,171,210)} .PuOr .q6-8{fill:rgb(128,115,172)} .PuOr .q7-8{fill:rgb(84,39,136)} .PuOr .q0-9{fill:rgb(179,88,6)} .PuOr .q1-9{fill:rgb(224,130,20)} .PuOr .q2-9{fill:rgb(253,184,99)} .PuOr .q3-9{fill:rgb(254,224,182)} .PuOr .q4-9{fill:rgb(247,247,247)} .PuOr .q5-9{fill:rgb(216,218,235)} .PuOr .q6-9{fill:rgb(178,171,210)} .PuOr .q7-9{fill:rgb(128,115,172)} .PuOr .q8-9{fill:rgb(84,39,136)} .PuOr .q0-10{fill:rgb(127,59,8)} .PuOr .q1-10{fill:rgb(179,88,6)} .PuOr .q2-10{fill:rgb(224,130,20)} .PuOr .q3-10{fill:rgb(253,184,99)} .PuOr .q4-10{fill:rgb(254,224,182)} .PuOr .q5-10{fill:rgb(216,218,235)} .PuOr .q6-10{fill:rgb(178,171,210)} .PuOr .q7-10{fill:rgb(128,115,172)} .PuOr .q8-10{fill:rgb(84,39,136)} .PuOr .q9-10{fill:rgb(45,0,75)} .PuOr .q0-11{fill:rgb(127,59,8)} .PuOr .q1-11{fill:rgb(179,88,6)} .PuOr .q2-11{fill:rgb(224,130,20)} .PuOr .q3-11{fill:rgb(253,184,99)} .PuOr .q4-11{fill:rgb(254,224,182)} .PuOr .q5-11{fill:rgb(247,247,247)} .PuOr .q6-11{fill:rgb(216,218,235)} .PuOr .q7-11{fill:rgb(178,171,210)} .PuOr .q8-11{fill:rgb(128,115,172)} .PuOr .q9-11{fill:rgb(84,39,136)} .PuOr .q10-11{fill:rgb(45,0,75)} .BrBG .q0-3{fill:rgb(216,179,101)} .BrBG .q1-3{fill:rgb(245,245,245)} .BrBG .q2-3{fill:rgb(90,180,172)} .BrBG .q0-4{fill:rgb(166,97,26)} .BrBG .q1-4{fill:rgb(223,194,125)} .BrBG .q2-4{fill:rgb(128,205,193)} .BrBG .q3-4{fill:rgb(1,133,113)} .BrBG .q0-5{fill:rgb(166,97,26)} .BrBG .q1-5{fill:rgb(223,194,125)} .BrBG .q2-5{fill:rgb(245,245,245)} .BrBG .q3-5{fill:rgb(128,205,193)} .BrBG .q4-5{fill:rgb(1,133,113)} .BrBG .q0-6{fill:rgb(140,81,10)} .BrBG .q1-6{fill:rgb(216,179,101)} .BrBG .q2-6{fill:rgb(246,232,195)} .BrBG .q3-6{fill:rgb(199,234,229)} .BrBG .q4-6{fill:rgb(90,180,172)} .BrBG .q5-6{fill:rgb(1,102,94)} .BrBG .q0-7{fill:rgb(140,81,10)} .BrBG .q1-7{fill:rgb(216,179,101)} .BrBG .q2-7{fill:rgb(246,232,195)} .BrBG .q3-7{fill:rgb(245,245,245)} .BrBG .q4-7{fill:rgb(199,234,229)} .BrBG .q5-7{fill:rgb(90,180,172)} .BrBG .q6-7{fill:rgb(1,102,94)} .BrBG .q0-8{fill:rgb(140,81,10)} .BrBG .q1-8{fill:rgb(191,129,45)} .BrBG .q2-8{fill:rgb(223,194,125)} .BrBG .q3-8{fill:rgb(246,232,195)} .BrBG .q4-8{fill:rgb(199,234,229)} .BrBG .q5-8{fill:rgb(128,205,193)} .BrBG .q6-8{fill:rgb(53,151,143)} .BrBG .q7-8{fill:rgb(1,102,94)} .BrBG .q0-9{fill:rgb(140,81,10)} .BrBG .q1-9{fill:rgb(191,129,45)} .BrBG .q2-9{fill:rgb(223,194,125)} .BrBG .q3-9{fill:rgb(246,232,195)} .BrBG .q4-9{fill:rgb(245,245,245)} .BrBG .q5-9{fill:rgb(199,234,229)} .BrBG .q6-9{fill:rgb(128,205,193)} .BrBG .q7-9{fill:rgb(53,151,143)} .BrBG .q8-9{fill:rgb(1,102,94)} .BrBG .q0-10{fill:rgb(84,48,5)} .BrBG .q1-10{fill:rgb(140,81,10)} .BrBG .q2-10{fill:rgb(191,129,45)} .BrBG .q3-10{fill:rgb(223,194,125)} .BrBG .q4-10{fill:rgb(246,232,195)} .BrBG .q5-10{fill:rgb(199,234,229)} .BrBG .q6-10{fill:rgb(128,205,193)} .BrBG .q7-10{fill:rgb(53,151,143)} .BrBG .q8-10{fill:rgb(1,102,94)} .BrBG .q9-10{fill:rgb(0,60,48)} .BrBG .q0-11{fill:rgb(84,48,5)} .BrBG .q1-11{fill:rgb(140,81,10)} .BrBG .q2-11{fill:rgb(191,129,45)} .BrBG .q3-11{fill:rgb(223,194,125)} .BrBG .q4-11{fill:rgb(246,232,195)} .BrBG .q5-11{fill:rgb(245,245,245)} .BrBG .q6-11{fill:rgb(199,234,229)} .BrBG .q7-11{fill:rgb(128,205,193)} .BrBG .q8-11{fill:rgb(53,151,143)} .BrBG .q9-11{fill:rgb(1,102,94)} .BrBG .q10-11{fill:rgb(0,60,48)} .PRGn .q0-3{fill:rgb(175,141,195)} .PRGn .q1-3{fill:rgb(247,247,247)} .PRGn .q2-3{fill:rgb(127,191,123)} .PRGn .q0-4{fill:rgb(123,50,148)} .PRGn .q1-4{fill:rgb(194,165,207)} .PRGn .q2-4{fill:rgb(166,219,160)} .PRGn .q3-4{fill:rgb(0,136,55)} .PRGn .q0-5{fill:rgb(123,50,148)} .PRGn .q1-5{fill:rgb(194,165,207)} .PRGn .q2-5{fill:rgb(247,247,247)} .PRGn .q3-5{fill:rgb(166,219,160)} .PRGn .q4-5{fill:rgb(0,136,55)} .PRGn .q0-6{fill:rgb(118,42,131)} .PRGn .q1-6{fill:rgb(175,141,195)} .PRGn .q2-6{fill:rgb(231,212,232)} .PRGn .q3-6{fill:rgb(217,240,211)} .PRGn .q4-6{fill:rgb(127,191,123)} .PRGn .q5-6{fill:rgb(27,120,55)} .PRGn .q0-7{fill:rgb(118,42,131)} .PRGn .q1-7{fill:rgb(175,141,195)} .PRGn .q2-7{fill:rgb(231,212,232)} .PRGn .q3-7{fill:rgb(247,247,247)} .PRGn .q4-7{fill:rgb(217,240,211)} .PRGn .q5-7{fill:rgb(127,191,123)} .PRGn .q6-7{fill:rgb(27,120,55)} .PRGn .q0-8{fill:rgb(118,42,131)} .PRGn .q1-8{fill:rgb(153,112,171)} .PRGn .q2-8{fill:rgb(194,165,207)} .PRGn .q3-8{fill:rgb(231,212,232)} .PRGn .q4-8{fill:rgb(217,240,211)} .PRGn .q5-8{fill:rgb(166,219,160)} .PRGn .q6-8{fill:rgb(90,174,97)} .PRGn .q7-8{fill:rgb(27,120,55)} .PRGn .q0-9{fill:rgb(118,42,131)} .PRGn .q1-9{fill:rgb(153,112,171)} .PRGn .q2-9{fill:rgb(194,165,207)} .PRGn .q3-9{fill:rgb(231,212,232)} .PRGn .q4-9{fill:rgb(247,247,247)} .PRGn .q5-9{fill:rgb(217,240,211)} .PRGn .q6-9{fill:rgb(166,219,160)} .PRGn .q7-9{fill:rgb(90,174,97)} .PRGn .q8-9{fill:rgb(27,120,55)} .PRGn .q0-10{fill:rgb(64,0,75)} .PRGn .q1-10{fill:rgb(118,42,131)} .PRGn .q2-10{fill:rgb(153,112,171)} .PRGn .q3-10{fill:rgb(194,165,207)} .PRGn .q4-10{fill:rgb(231,212,232)} .PRGn .q5-10{fill:rgb(217,240,211)} .PRGn .q6-10{fill:rgb(166,219,160)} .PRGn .q7-10{fill:rgb(90,174,97)} .PRGn .q8-10{fill:rgb(27,120,55)} .PRGn .q9-10{fill:rgb(0,68,27)} .PRGn .q0-11{fill:rgb(64,0,75)} .PRGn .q1-11{fill:rgb(118,42,131)} .PRGn .q2-11{fill:rgb(153,112,171)} .PRGn .q3-11{fill:rgb(194,165,207)} .PRGn .q4-11{fill:rgb(231,212,232)} .PRGn .q5-11{fill:rgb(247,247,247)} .PRGn .q6-11{fill:rgb(217,240,211)} .PRGn .q7-11{fill:rgb(166,219,160)} .PRGn .q8-11{fill:rgb(90,174,97)} .PRGn .q9-11{fill:rgb(27,120,55)} .PRGn .q10-11{fill:rgb(0,68,27)} .PiYG .q0-3{fill:rgb(233,163,201)} .PiYG .q1-3{fill:rgb(247,247,247)} .PiYG .q2-3{fill:rgb(161,215,106)} .PiYG .q0-4{fill:rgb(208,28,139)} .PiYG .q1-4{fill:rgb(241,182,218)} .PiYG .q2-4{fill:rgb(184,225,134)} .PiYG .q3-4{fill:rgb(77,172,38)} .PiYG .q0-5{fill:rgb(208,28,139)} .PiYG .q1-5{fill:rgb(241,182,218)} .PiYG .q2-5{fill:rgb(247,247,247)} .PiYG .q3-5{fill:rgb(184,225,134)} .PiYG .q4-5{fill:rgb(77,172,38)} .PiYG .q0-6{fill:rgb(197,27,125)} .PiYG .q1-6{fill:rgb(233,163,201)} .PiYG .q2-6{fill:rgb(253,224,239)} .PiYG .q3-6{fill:rgb(230,245,208)} .PiYG .q4-6{fill:rgb(161,215,106)} .PiYG .q5-6{fill:rgb(77,146,33)} .PiYG .q0-7{fill:rgb(197,27,125)} .PiYG .q1-7{fill:rgb(233,163,201)} .PiYG .q2-7{fill:rgb(253,224,239)} .PiYG .q3-7{fill:rgb(247,247,247)} .PiYG .q4-7{fill:rgb(230,245,208)} .PiYG .q5-7{fill:rgb(161,215,106)} .PiYG .q6-7{fill:rgb(77,146,33)} .PiYG .q0-8{fill:rgb(197,27,125)} .PiYG .q1-8{fill:rgb(222,119,174)} .PiYG .q2-8{fill:rgb(241,182,218)} .PiYG .q3-8{fill:rgb(253,224,239)} .PiYG .q4-8{fill:rgb(230,245,208)} .PiYG .q5-8{fill:rgb(184,225,134)} .PiYG .q6-8{fill:rgb(127,188,65)} .PiYG .q7-8{fill:rgb(77,146,33)} .PiYG .q0-9{fill:rgb(197,27,125)} .PiYG .q1-9{fill:rgb(222,119,174)} .PiYG .q2-9{fill:rgb(241,182,218)} .PiYG .q3-9{fill:rgb(253,224,239)} .PiYG .q4-9{fill:rgb(247,247,247)} .PiYG .q5-9{fill:rgb(230,245,208)} .PiYG .q6-9{fill:rgb(184,225,134)} .PiYG .q7-9{fill:rgb(127,188,65)} .PiYG .q8-9{fill:rgb(77,146,33)} .PiYG .q0-10{fill:rgb(142,1,82)} .PiYG .q1-10{fill:rgb(197,27,125)} .PiYG .q2-10{fill:rgb(222,119,174)} .PiYG .q3-10{fill:rgb(241,182,218)} .PiYG .q4-10{fill:rgb(253,224,239)} .PiYG .q5-10{fill:rgb(230,245,208)} .PiYG .q6-10{fill:rgb(184,225,134)} .PiYG .q7-10{fill:rgb(127,188,65)} .PiYG .q8-10{fill:rgb(77,146,33)} .PiYG .q9-10{fill:rgb(39,100,25)} .PiYG .q0-11{fill:rgb(142,1,82)} .PiYG .q1-11{fill:rgb(197,27,125)} .PiYG .q2-11{fill:rgb(222,119,174)} .PiYG .q3-11{fill:rgb(241,182,218)} .PiYG .q4-11{fill:rgb(253,224,239)} .PiYG .q5-11{fill:rgb(247,247,247)} .PiYG .q6-11{fill:rgb(230,245,208)} .PiYG .q7-11{fill:rgb(184,225,134)} .PiYG .q8-11{fill:rgb(127,188,65)} .PiYG .q9-11{fill:rgb(77,146,33)} .PiYG .q10-11{fill:rgb(39,100,25)} .RdBu .q0-3{fill:rgb(239,138,98)} .RdBu .q1-3{fill:rgb(247,247,247)} .RdBu .q2-3{fill:rgb(103,169,207)} .RdBu .q0-4{fill:rgb(202,0,32)} .RdBu .q1-4{fill:rgb(244,165,130)} .RdBu .q2-4{fill:rgb(146,197,222)} .RdBu .q3-4{fill:rgb(5,113,176)} .RdBu .q0-5{fill:rgb(202,0,32)} .RdBu .q1-5{fill:rgb(244,165,130)} .RdBu .q2-5{fill:rgb(247,247,247)} .RdBu .q3-5{fill:rgb(146,197,222)} .RdBu .q4-5{fill:rgb(5,113,176)} .RdBu .q0-6{fill:rgb(178,24,43)} .RdBu .q1-6{fill:rgb(239,138,98)} .RdBu .q2-6{fill:rgb(253,219,199)} .RdBu .q3-6{fill:rgb(209,229,240)} .RdBu .q4-6{fill:rgb(103,169,207)} .RdBu .q5-6{fill:rgb(33,102,172)} .RdBu .q0-7{fill:rgb(178,24,43)} .RdBu .q1-7{fill:rgb(239,138,98)} .RdBu .q2-7{fill:rgb(253,219,199)} .RdBu .q3-7{fill:rgb(247,247,247)} .RdBu .q4-7{fill:rgb(209,229,240)} .RdBu .q5-7{fill:rgb(103,169,207)} .RdBu .q6-7{fill:rgb(33,102,172)} .RdBu .q0-8{fill:rgb(178,24,43)} .RdBu .q1-8{fill:rgb(214,96,77)} .RdBu .q2-8{fill:rgb(244,165,130)} .RdBu .q3-8{fill:rgb(253,219,199)} .RdBu .q4-8{fill:rgb(209,229,240)} .RdBu .q5-8{fill:rgb(146,197,222)} .RdBu .q6-8{fill:rgb(67,147,195)} .RdBu .q7-8{fill:rgb(33,102,172)} .RdBu .q0-9{fill:rgb(178,24,43)} .RdBu .q1-9{fill:rgb(214,96,77)} .RdBu .q2-9{fill:rgb(244,165,130)} .RdBu .q3-9{fill:rgb(253,219,199)} .RdBu .q4-9{fill:rgb(247,247,247)} .RdBu .q5-9{fill:rgb(209,229,240)} .RdBu .q6-9{fill:rgb(146,197,222)} .RdBu .q7-9{fill:rgb(67,147,195)} .RdBu .q8-9{fill:rgb(33,102,172)} .RdBu .q0-10{fill:rgb(103,0,31)} .RdBu .q1-10{fill:rgb(178,24,43)} .RdBu .q2-10{fill:rgb(214,96,77)} .RdBu .q3-10{fill:rgb(244,165,130)} .RdBu .q4-10{fill:rgb(253,219,199)} .RdBu .q5-10{fill:rgb(209,229,240)} .RdBu .q6-10{fill:rgb(146,197,222)} .RdBu .q7-10{fill:rgb(67,147,195)} .RdBu .q8-10{fill:rgb(33,102,172)} .RdBu .q9-10{fill:rgb(5,48,97)} .RdBu .q0-11{fill:rgb(103,0,31)} .RdBu .q1-11{fill:rgb(178,24,43)} .RdBu .q2-11{fill:rgb(214,96,77)} .RdBu .q3-11{fill:rgb(244,165,130)} .RdBu .q4-11{fill:rgb(253,219,199)} .RdBu .q5-11{fill:rgb(247,247,247)} .RdBu .q6-11{fill:rgb(209,229,240)} .RdBu .q7-11{fill:rgb(146,197,222)} .RdBu .q8-11{fill:rgb(67,147,195)} .RdBu .q9-11{fill:rgb(33,102,172)} .RdBu .q10-11{fill:rgb(5,48,97)} .RdGy .q0-3{fill:rgb(239,138,98)} .RdGy .q1-3{fill:rgb(255,255,255)} .RdGy .q2-3{fill:rgb(153,153,153)} .RdGy .q0-4{fill:rgb(202,0,32)} .RdGy .q1-4{fill:rgb(244,165,130)} .RdGy .q2-4{fill:rgb(186,186,186)} .RdGy .q3-4{fill:rgb(64,64,64)} .RdGy .q0-5{fill:rgb(202,0,32)} .RdGy .q1-5{fill:rgb(244,165,130)} .RdGy .q2-5{fill:rgb(255,255,255)} .RdGy .q3-5{fill:rgb(186,186,186)} .RdGy .q4-5{fill:rgb(64,64,64)} .RdGy .q0-6{fill:rgb(178,24,43)} .RdGy .q1-6{fill:rgb(239,138,98)} .RdGy .q2-6{fill:rgb(253,219,199)} .RdGy .q3-6{fill:rgb(224,224,224)} .RdGy .q4-6{fill:rgb(153,153,153)} .RdGy .q5-6{fill:rgb(77,77,77)} .RdGy .q0-7{fill:rgb(178,24,43)} .RdGy .q1-7{fill:rgb(239,138,98)} .RdGy .q2-7{fill:rgb(253,219,199)} .RdGy .q3-7{fill:rgb(255,255,255)} .RdGy .q4-7{fill:rgb(224,224,224)} .RdGy .q5-7{fill:rgb(153,153,153)} .RdGy .q6-7{fill:rgb(77,77,77)} .RdGy .q0-8{fill:rgb(178,24,43)} .RdGy .q1-8{fill:rgb(214,96,77)} .RdGy .q2-8{fill:rgb(244,165,130)} .RdGy .q3-8{fill:rgb(253,219,199)} .RdGy .q4-8{fill:rgb(224,224,224)} .RdGy .q5-8{fill:rgb(186,186,186)} .RdGy .q6-8{fill:rgb(135,135,135)} .RdGy .q7-8{fill:rgb(77,77,77)} .RdGy .q0-9{fill:rgb(178,24,43)} .RdGy .q1-9{fill:rgb(214,96,77)} .RdGy .q2-9{fill:rgb(244,165,130)} .RdGy .q3-9{fill:rgb(253,219,199)} .RdGy .q4-9{fill:rgb(255,255,255)} .RdGy .q5-9{fill:rgb(224,224,224)} .RdGy .q6-9{fill:rgb(186,186,186)} .RdGy .q7-9{fill:rgb(135,135,135)} .RdGy .q8-9{fill:rgb(77,77,77)} .RdGy .q0-10{fill:rgb(103,0,31)} .RdGy .q1-10{fill:rgb(178,24,43)} .RdGy .q2-10{fill:rgb(214,96,77)} .RdGy .q3-10{fill:rgb(244,165,130)} .RdGy .q4-10{fill:rgb(253,219,199)} .RdGy .q5-10{fill:rgb(224,224,224)} .RdGy .q6-10{fill:rgb(186,186,186)} .RdGy .q7-10{fill:rgb(135,135,135)} .RdGy .q8-10{fill:rgb(77,77,77)} .RdGy .q9-10{fill:rgb(26,26,26)} .RdGy .q0-11{fill:rgb(103,0,31)} .RdGy .q1-11{fill:rgb(178,24,43)} .RdGy .q2-11{fill:rgb(214,96,77)} .RdGy .q3-11{fill:rgb(244,165,130)} .RdGy .q4-11{fill:rgb(253,219,199)} .RdGy .q5-11{fill:rgb(255,255,255)} .RdGy .q6-11{fill:rgb(224,224,224)} .RdGy .q7-11{fill:rgb(186,186,186)} .RdGy .q8-11{fill:rgb(135,135,135)} .RdGy .q9-11{fill:rgb(77,77,77)} .RdGy .q10-11{fill:rgb(26,26,26)} .RdYlBu .q0-3{fill:rgb(252,141,89)} .RdYlBu .q1-3{fill:rgb(255,255,191)} .RdYlBu .q2-3{fill:rgb(145,191,219)} .RdYlBu .q0-4{fill:rgb(215,25,28)} .RdYlBu .q1-4{fill:rgb(253,174,97)} .RdYlBu .q2-4{fill:rgb(171,217,233)} .RdYlBu .q3-4{fill:rgb(44,123,182)} .RdYlBu .q0-5{fill:rgb(215,25,28)} .RdYlBu .q1-5{fill:rgb(253,174,97)} .RdYlBu .q2-5{fill:rgb(255,255,191)} .RdYlBu .q3-5{fill:rgb(171,217,233)} .RdYlBu .q4-5{fill:rgb(44,123,182)} .RdYlBu .q0-6{fill:rgb(215,48,39)} .RdYlBu .q1-6{fill:rgb(252,141,89)} .RdYlBu .q2-6{fill:rgb(254,224,144)} .RdYlBu .q3-6{fill:rgb(224,243,248)} .RdYlBu .q4-6{fill:rgb(145,191,219)} .RdYlBu .q5-6{fill:rgb(69,117,180)} .RdYlBu .q0-7{fill:rgb(215,48,39)} .RdYlBu .q1-7{fill:rgb(252,141,89)} .RdYlBu .q2-7{fill:rgb(254,224,144)} .RdYlBu .q3-7{fill:rgb(255,255,191)} .RdYlBu .q4-7{fill:rgb(224,243,248)} .RdYlBu .q5-7{fill:rgb(145,191,219)} .RdYlBu .q6-7{fill:rgb(69,117,180)} .RdYlBu .q0-8{fill:rgb(215,48,39)} .RdYlBu .q1-8{fill:rgb(244,109,67)} .RdYlBu .q2-8{fill:rgb(253,174,97)} .RdYlBu .q3-8{fill:rgb(254,224,144)} .RdYlBu .q4-8{fill:rgb(224,243,248)} .RdYlBu .q5-8{fill:rgb(171,217,233)} .RdYlBu .q6-8{fill:rgb(116,173,209)} .RdYlBu .q7-8{fill:rgb(69,117,180)} .RdYlBu .q0-9{fill:rgb(215,48,39)} .RdYlBu .q1-9{fill:rgb(244,109,67)} .RdYlBu .q2-9{fill:rgb(253,174,97)} .RdYlBu .q3-9{fill:rgb(254,224,144)} .RdYlBu .q4-9{fill:rgb(255,255,191)} .RdYlBu .q5-9{fill:rgb(224,243,248)} .RdYlBu .q6-9{fill:rgb(171,217,233)} .RdYlBu .q7-9{fill:rgb(116,173,209)} .RdYlBu .q8-9{fill:rgb(69,117,180)} .RdYlBu .q0-10{fill:rgb(165,0,38)} .RdYlBu .q1-10{fill:rgb(215,48,39)} .RdYlBu .q2-10{fill:rgb(244,109,67)} .RdYlBu .q3-10{fill:rgb(253,174,97)} .RdYlBu .q4-10{fill:rgb(254,224,144)} .RdYlBu .q5-10{fill:rgb(224,243,248)} .RdYlBu .q6-10{fill:rgb(171,217,233)} .RdYlBu .q7-10{fill:rgb(116,173,209)} .RdYlBu .q8-10{fill:rgb(69,117,180)} .RdYlBu .q9-10{fill:rgb(49,54,149)} .RdYlBu .q0-11{fill:rgb(165,0,38)} .RdYlBu .q1-11{fill:rgb(215,48,39)} .RdYlBu .q2-11{fill:rgb(244,109,67)} .RdYlBu .q3-11{fill:rgb(253,174,97)} .RdYlBu .q4-11{fill:rgb(254,224,144)} .RdYlBu .q5-11{fill:rgb(255,255,191)} .RdYlBu .q6-11{fill:rgb(224,243,248)} .RdYlBu .q7-11{fill:rgb(171,217,233)} .RdYlBu .q8-11{fill:rgb(116,173,209)} .RdYlBu .q9-11{fill:rgb(69,117,180)} .RdYlBu .q10-11{fill:rgb(49,54,149)} .Spectral .q0-3{fill:rgb(252,141,89)} .Spectral .q1-3{fill:rgb(255,255,191)} .Spectral .q2-3{fill:rgb(153,213,148)} .Spectral .q0-4{fill:rgb(215,25,28)} .Spectral .q1-4{fill:rgb(253,174,97)} .Spectral .q2-4{fill:rgb(171,221,164)} .Spectral .q3-4{fill:rgb(43,131,186)} .Spectral .q0-5{fill:rgb(215,25,28)} .Spectral .q1-5{fill:rgb(253,174,97)} .Spectral .q2-5{fill:rgb(255,255,191)} .Spectral .q3-5{fill:rgb(171,221,164)} .Spectral .q4-5{fill:rgb(43,131,186)} .Spectral .q0-6{fill:rgb(213,62,79)} .Spectral .q1-6{fill:rgb(252,141,89)} .Spectral .q2-6{fill:rgb(254,224,139)} .Spectral .q3-6{fill:rgb(230,245,152)} .Spectral .q4-6{fill:rgb(153,213,148)} .Spectral .q5-6{fill:rgb(50,136,189)} .Spectral .q0-7{fill:rgb(213,62,79)} .Spectral .q1-7{fill:rgb(252,141,89)} .Spectral .q2-7{fill:rgb(254,224,139)} .Spectral .q3-7{fill:rgb(255,255,191)} .Spectral .q4-7{fill:rgb(230,245,152)} .Spectral .q5-7{fill:rgb(153,213,148)} .Spectral .q6-7{fill:rgb(50,136,189)} .Spectral .q0-8{fill:rgb(213,62,79)} .Spectral .q1-8{fill:rgb(244,109,67)} .Spectral .q2-8{fill:rgb(253,174,97)} .Spectral .q3-8{fill:rgb(254,224,139)} .Spectral .q4-8{fill:rgb(230,245,152)} .Spectral .q5-8{fill:rgb(171,221,164)} .Spectral .q6-8{fill:rgb(102,194,165)} .Spectral .q7-8{fill:rgb(50,136,189)} .Spectral .q0-9{fill:rgb(213,62,79)} .Spectral .q1-9{fill:rgb(244,109,67)} .Spectral .q2-9{fill:rgb(253,174,97)} .Spectral .q3-9{fill:rgb(254,224,139)} .Spectral .q4-9{fill:rgb(255,255,191)} .Spectral .q5-9{fill:rgb(230,245,152)} .Spectral .q6-9{fill:rgb(171,221,164)} .Spectral .q7-9{fill:rgb(102,194,165)} .Spectral .q8-9{fill:rgb(50,136,189)} .Spectral .q0-10{fill:rgb(158,1,66)} .Spectral .q1-10{fill:rgb(213,62,79)} .Spectral .q2-10{fill:rgb(244,109,67)} .Spectral .q3-10{fill:rgb(253,174,97)} .Spectral .q4-10{fill:rgb(254,224,139)} .Spectral .q5-10{fill:rgb(230,245,152)} .Spectral .q6-10{fill:rgb(171,221,164)} .Spectral .q7-10{fill:rgb(102,194,165)} .Spectral .q8-10{fill:rgb(50,136,189)} .Spectral .q9-10{fill:rgb(94,79,162)} .Spectral .q0-11{fill:rgb(158,1,66)} .Spectral .q1-11{fill:rgb(213,62,79)} .Spectral .q2-11{fill:rgb(244,109,67)} .Spectral .q3-11{fill:rgb(253,174,97)} .Spectral .q4-11{fill:rgb(254,224,139)} .Spectral .q5-11{fill:rgb(255,255,191)} .Spectral .q6-11{fill:rgb(230,245,152)} .Spectral .q7-11{fill:rgb(171,221,164)} .Spectral .q8-11{fill:rgb(102,194,165)} .Spectral .q9-11{fill:rgb(50,136,189)} .Spectral .q10-11{fill:rgb(94,79,162)} .RdYlGn .q0-3{fill:rgb(252,141,89)} .RdYlGn .q1-3{fill:rgb(255,255,191)} .RdYlGn .q2-3{fill:rgb(145,207,96)} .RdYlGn .q0-4{fill:rgb(215,25,28)} .RdYlGn .q1-4{fill:rgb(253,174,97)} .RdYlGn .q2-4{fill:rgb(166,217,106)} .RdYlGn .q3-4{fill:rgb(26,150,65)} .RdYlGn .q0-5{fill:rgb(215,25,28)} .RdYlGn .q1-5{fill:rgb(253,174,97)} .RdYlGn .q2-5{fill:rgb(255,255,191)} .RdYlGn .q3-5{fill:rgb(166,217,106)} .RdYlGn .q4-5{fill:rgb(26,150,65)} .RdYlGn .q0-6{fill:rgb(215,48,39)} .RdYlGn .q1-6{fill:rgb(252,141,89)} .RdYlGn .q2-6{fill:rgb(254,224,139)} .RdYlGn .q3-6{fill:rgb(217,239,139)} .RdYlGn .q4-6{fill:rgb(145,207,96)} .RdYlGn .q5-6{fill:rgb(26,152,80)} .RdYlGn .q0-7{fill:rgb(215,48,39)} .RdYlGn .q1-7{fill:rgb(252,141,89)} .RdYlGn .q2-7{fill:rgb(254,224,139)} .RdYlGn .q3-7{fill:rgb(255,255,191)} .RdYlGn .q4-7{fill:rgb(217,239,139)} .RdYlGn .q5-7{fill:rgb(145,207,96)} .RdYlGn .q6-7{fill:rgb(26,152,80)} .RdYlGn .q0-8{fill:rgb(215,48,39)} .RdYlGn .q1-8{fill:rgb(244,109,67)} .RdYlGn .q2-8{fill:rgb(253,174,97)} .RdYlGn .q3-8{fill:rgb(254,224,139)} .RdYlGn .q4-8{fill:rgb(217,239,139)} .RdYlGn .q5-8{fill:rgb(166,217,106)} .RdYlGn .q6-8{fill:rgb(102,189,99)} .RdYlGn .q7-8{fill:rgb(26,152,80)} .RdYlGn .q0-9{fill:rgb(215,48,39)} .RdYlGn .q1-9{fill:rgb(244,109,67)} .RdYlGn .q2-9{fill:rgb(253,174,97)} .RdYlGn .q3-9{fill:rgb(254,224,139)} .RdYlGn .q4-9{fill:rgb(255,255,191)} .RdYlGn .q5-9{fill:rgb(217,239,139)} .RdYlGn .q6-9{fill:rgb(166,217,106)} .RdYlGn .q7-9{fill:rgb(102,189,99)} .RdYlGn .q8-9{fill:rgb(26,152,80)} .RdYlGn .q0-10{fill:rgb(165,0,38)} .RdYlGn .q1-10{fill:rgb(215,48,39)} .RdYlGn .q2-10{fill:rgb(244,109,67)} .RdYlGn .q3-10{fill:rgb(253,174,97)} .RdYlGn .q4-10{fill:rgb(254,224,139)} .RdYlGn .q5-10{fill:rgb(217,239,139)} .RdYlGn .q6-10{fill:rgb(166,217,106)} .RdYlGn .q7-10{fill:rgb(102,189,99)} .RdYlGn .q8-10{fill:rgb(26,152,80)} .RdYlGn .q9-10{fill:rgb(0,104,55)} .RdYlGn .q0-11{fill:rgb(165,0,38)} .RdYlGn .q1-11{fill:rgb(215,48,39)} .RdYlGn .q2-11{fill:rgb(244,109,67)} .RdYlGn .q3-11{fill:rgb(253,174,97)} .RdYlGn .q4-11{fill:rgb(254,224,139)} .RdYlGn .q5-11{fill:rgb(255,255,191)} .RdYlGn .q6-11{fill:rgb(217,239,139)} .RdYlGn .q7-11{fill:rgb(166,217,106)} .RdYlGn .q8-11{fill:rgb(102,189,99)} .RdYlGn .q9-11{fill:rgb(26,152,80)} .RdYlGn .q10-11{fill:rgb(0,104,55)} .Accent .q0-3{fill:rgb(127,201,127)} .Accent .q1-3{fill:rgb(190,174,212)} .Accent .q2-3{fill:rgb(253,192,134)} .Accent .q0-4{fill:rgb(127,201,127)} .Accent .q1-4{fill:rgb(190,174,212)} .Accent .q2-4{fill:rgb(253,192,134)} .Accent .q3-4{fill:rgb(255,255,153)} .Accent .q0-5{fill:rgb(127,201,127)} .Accent .q1-5{fill:rgb(190,174,212)} .Accent .q2-5{fill:rgb(253,192,134)} .Accent .q3-5{fill:rgb(255,255,153)} .Accent .q4-5{fill:rgb(56,108,176)} .Accent .q0-6{fill:rgb(127,201,127)} .Accent .q1-6{fill:rgb(190,174,212)} .Accent .q2-6{fill:rgb(253,192,134)} .Accent .q3-6{fill:rgb(255,255,153)} .Accent .q4-6{fill:rgb(56,108,176)} .Accent .q5-6{fill:rgb(240,2,127)} .Accent .q0-7{fill:rgb(127,201,127)} .Accent .q1-7{fill:rgb(190,174,212)} .Accent .q2-7{fill:rgb(253,192,134)} .Accent .q3-7{fill:rgb(255,255,153)} .Accent .q4-7{fill:rgb(56,108,176)} .Accent .q5-7{fill:rgb(240,2,127)} .Accent .q6-7{fill:rgb(191,91,23)} .Accent .q0-8{fill:rgb(127,201,127)} .Accent .q1-8{fill:rgb(190,174,212)} .Accent .q2-8{fill:rgb(253,192,134)} .Accent .q3-8{fill:rgb(255,255,153)} .Accent .q4-8{fill:rgb(56,108,176)} .Accent .q5-8{fill:rgb(240,2,127)} .Accent .q6-8{fill:rgb(191,91,23)} .Accent .q7-8{fill:rgb(102,102,102)} .Dark2 .q0-3{fill:rgb(27,158,119)} .Dark2 .q1-3{fill:rgb(217,95,2)} .Dark2 .q2-3{fill:rgb(117,112,179)} .Dark2 .q0-4{fill:rgb(27,158,119)} .Dark2 .q1-4{fill:rgb(217,95,2)} .Dark2 .q2-4{fill:rgb(117,112,179)} .Dark2 .q3-4{fill:rgb(231,41,138)} .Dark2 .q0-5{fill:rgb(27,158,119)} .Dark2 .q1-5{fill:rgb(217,95,2)} .Dark2 .q2-5{fill:rgb(117,112,179)} .Dark2 .q3-5{fill:rgb(231,41,138)} .Dark2 .q4-5{fill:rgb(102,166,30)} .Dark2 .q0-6{fill:rgb(27,158,119)} .Dark2 .q1-6{fill:rgb(217,95,2)} .Dark2 .q2-6{fill:rgb(117,112,179)} .Dark2 .q3-6{fill:rgb(231,41,138)} .Dark2 .q4-6{fill:rgb(102,166,30)} .Dark2 .q5-6{fill:rgb(230,171,2)} .Dark2 .q0-7{fill:rgb(27,158,119)} .Dark2 .q1-7{fill:rgb(217,95,2)} .Dark2 .q2-7{fill:rgb(117,112,179)} .Dark2 .q3-7{fill:rgb(231,41,138)} .Dark2 .q4-7{fill:rgb(102,166,30)} .Dark2 .q5-7{fill:rgb(230,171,2)} .Dark2 .q6-7{fill:rgb(166,118,29)} .Dark2 .q0-8{fill:rgb(27,158,119)} .Dark2 .q1-8{fill:rgb(217,95,2)} .Dark2 .q2-8{fill:rgb(117,112,179)} .Dark2 .q3-8{fill:rgb(231,41,138)} .Dark2 .q4-8{fill:rgb(102,166,30)} .Dark2 .q5-8{fill:rgb(230,171,2)} .Dark2 .q6-8{fill:rgb(166,118,29)} .Dark2 .q7-8{fill:rgb(102,102,102)} .Paired .q0-3{fill:rgb(166,206,227)} .Paired .q1-3{fill:rgb(31,120,180)} .Paired .q2-3{fill:rgb(178,223,138)} .Paired .q0-4{fill:rgb(166,206,227)} .Paired .q1-4{fill:rgb(31,120,180)} .Paired .q2-4{fill:rgb(178,223,138)} .Paired .q3-4{fill:rgb(51,160,44)} .Paired .q0-5{fill:rgb(166,206,227)} .Paired .q1-5{fill:rgb(31,120,180)} .Paired .q2-5{fill:rgb(178,223,138)} .Paired .q3-5{fill:rgb(51,160,44)} .Paired .q4-5{fill:rgb(251,154,153)} .Paired .q0-6{fill:rgb(166,206,227)} .Paired .q1-6{fill:rgb(31,120,180)} .Paired .q2-6{fill:rgb(178,223,138)} .Paired .q3-6{fill:rgb(51,160,44)} .Paired .q4-6{fill:rgb(251,154,153)} .Paired .q5-6{fill:rgb(227,26,28)} .Paired .q0-7{fill:rgb(166,206,227)} .Paired .q1-7{fill:rgb(31,120,180)} .Paired .q2-7{fill:rgb(178,223,138)} .Paired .q3-7{fill:rgb(51,160,44)} .Paired .q4-7{fill:rgb(251,154,153)} .Paired .q5-7{fill:rgb(227,26,28)} .Paired .q6-7{fill:rgb(253,191,111)} .Paired .q0-8{fill:rgb(166,206,227)} .Paired .q1-8{fill:rgb(31,120,180)} .Paired .q2-8{fill:rgb(178,223,138)} .Paired .q3-8{fill:rgb(51,160,44)} .Paired .q4-8{fill:rgb(251,154,153)} .Paired .q5-8{fill:rgb(227,26,28)} .Paired .q6-8{fill:rgb(253,191,111)} .Paired .q7-8{fill:rgb(255,127,0)} .Paired .q0-9{fill:rgb(166,206,227)} .Paired .q1-9{fill:rgb(31,120,180)} .Paired .q2-9{fill:rgb(178,223,138)} .Paired .q3-9{fill:rgb(51,160,44)} .Paired .q4-9{fill:rgb(251,154,153)} .Paired .q5-9{fill:rgb(227,26,28)} .Paired .q6-9{fill:rgb(253,191,111)} .Paired .q7-9{fill:rgb(255,127,0)} .Paired .q8-9{fill:rgb(202,178,214)} .Paired .q0-10{fill:rgb(166,206,227)} .Paired .q1-10{fill:rgb(31,120,180)} .Paired .q2-10{fill:rgb(178,223,138)} .Paired .q3-10{fill:rgb(51,160,44)} .Paired .q4-10{fill:rgb(251,154,153)} .Paired .q5-10{fill:rgb(227,26,28)} .Paired .q6-10{fill:rgb(253,191,111)} .Paired .q7-10{fill:rgb(255,127,0)} .Paired .q8-10{fill:rgb(202,178,214)} .Paired .q9-10{fill:rgb(106,61,154)} .Paired .q0-11{fill:rgb(166,206,227)} .Paired .q1-11{fill:rgb(31,120,180)} .Paired .q2-11{fill:rgb(178,223,138)} .Paired .q3-11{fill:rgb(51,160,44)} .Paired .q4-11{fill:rgb(251,154,153)} .Paired .q5-11{fill:rgb(227,26,28)} .Paired .q6-11{fill:rgb(253,191,111)} .Paired .q7-11{fill:rgb(255,127,0)} .Paired .q8-11{fill:rgb(202,178,214)} .Paired .q9-11{fill:rgb(106,61,154)} .Paired .q10-11{fill:rgb(255,255,153)} .Paired .q0-12{fill:rgb(166,206,227)} .Paired .q1-12{fill:rgb(31,120,180)} .Paired .q2-12{fill:rgb(178,223,138)} .Paired .q3-12{fill:rgb(51,160,44)} .Paired .q4-12{fill:rgb(251,154,153)} .Paired .q5-12{fill:rgb(227,26,28)} .Paired .q6-12{fill:rgb(253,191,111)} .Paired .q7-12{fill:rgb(255,127,0)} .Paired .q8-12{fill:rgb(202,178,214)} .Paired .q9-12{fill:rgb(106,61,154)} .Paired .q10-12{fill:rgb(255,255,153)} .Paired .q11-12{fill:rgb(177,89,40)} .Pastel1 .q0-3{fill:rgb(251,180,174)} .Pastel1 .q1-3{fill:rgb(179,205,227)} .Pastel1 .q2-3{fill:rgb(204,235,197)} .Pastel1 .q0-4{fill:rgb(251,180,174)} .Pastel1 .q1-4{fill:rgb(179,205,227)} .Pastel1 .q2-4{fill:rgb(204,235,197)} .Pastel1 .q3-4{fill:rgb(222,203,228)} .Pastel1 .q0-5{fill:rgb(251,180,174)} .Pastel1 .q1-5{fill:rgb(179,205,227)} .Pastel1 .q2-5{fill:rgb(204,235,197)} .Pastel1 .q3-5{fill:rgb(222,203,228)} .Pastel1 .q4-5{fill:rgb(254,217,166)} .Pastel1 .q0-6{fill:rgb(251,180,174)} .Pastel1 .q1-6{fill:rgb(179,205,227)} .Pastel1 .q2-6{fill:rgb(204,235,197)} .Pastel1 .q3-6{fill:rgb(222,203,228)} .Pastel1 .q4-6{fill:rgb(254,217,166)} .Pastel1 .q5-6{fill:rgb(255,255,204)} .Pastel1 .q0-7{fill:rgb(251,180,174)} .Pastel1 .q1-7{fill:rgb(179,205,227)} .Pastel1 .q2-7{fill:rgb(204,235,197)} .Pastel1 .q3-7{fill:rgb(222,203,228)} .Pastel1 .q4-7{fill:rgb(254,217,166)} .Pastel1 .q5-7{fill:rgb(255,255,204)} .Pastel1 .q6-7{fill:rgb(229,216,189)} .Pastel1 .q0-8{fill:rgb(251,180,174)} .Pastel1 .q1-8{fill:rgb(179,205,227)} .Pastel1 .q2-8{fill:rgb(204,235,197)} .Pastel1 .q3-8{fill:rgb(222,203,228)} .Pastel1 .q4-8{fill:rgb(254,217,166)} .Pastel1 .q5-8{fill:rgb(255,255,204)} .Pastel1 .q6-8{fill:rgb(229,216,189)} .Pastel1 .q7-8{fill:rgb(253,218,236)} .Pastel1 .q0-9{fill:rgb(251,180,174)} .Pastel1 .q1-9{fill:rgb(179,205,227)} .Pastel1 .q2-9{fill:rgb(204,235,197)} .Pastel1 .q3-9{fill:rgb(222,203,228)} .Pastel1 .q4-9{fill:rgb(254,217,166)} .Pastel1 .q5-9{fill:rgb(255,255,204)} .Pastel1 .q6-9{fill:rgb(229,216,189)} .Pastel1 .q7-9{fill:rgb(253,218,236)} .Pastel1 .q8-9{fill:rgb(242,242,242)} .Pastel2 .q0-3{fill:rgb(179,226,205)} .Pastel2 .q1-3{fill:rgb(253,205,172)} .Pastel2 .q2-3{fill:rgb(203,213,232)} .Pastel2 .q0-4{fill:rgb(179,226,205)} .Pastel2 .q1-4{fill:rgb(253,205,172)} .Pastel2 .q2-4{fill:rgb(203,213,232)} .Pastel2 .q3-4{fill:rgb(244,202,228)} .Pastel2 .q0-5{fill:rgb(179,226,205)} .Pastel2 .q1-5{fill:rgb(253,205,172)} .Pastel2 .q2-5{fill:rgb(203,213,232)} .Pastel2 .q3-5{fill:rgb(244,202,228)} .Pastel2 .q4-5{fill:rgb(230,245,201)} .Pastel2 .q0-6{fill:rgb(179,226,205)} .Pastel2 .q1-6{fill:rgb(253,205,172)} .Pastel2 .q2-6{fill:rgb(203,213,232)} .Pastel2 .q3-6{fill:rgb(244,202,228)} .Pastel2 .q4-6{fill:rgb(230,245,201)} .Pastel2 .q5-6{fill:rgb(255,242,174)} .Pastel2 .q0-7{fill:rgb(179,226,205)} .Pastel2 .q1-7{fill:rgb(253,205,172)} .Pastel2 .q2-7{fill:rgb(203,213,232)} .Pastel2 .q3-7{fill:rgb(244,202,228)} .Pastel2 .q4-7{fill:rgb(230,245,201)} .Pastel2 .q5-7{fill:rgb(255,242,174)} .Pastel2 .q6-7{fill:rgb(241,226,204)} .Pastel2 .q0-8{fill:rgb(179,226,205)} .Pastel2 .q1-8{fill:rgb(253,205,172)} .Pastel2 .q2-8{fill:rgb(203,213,232)} .Pastel2 .q3-8{fill:rgb(244,202,228)} .Pastel2 .q4-8{fill:rgb(230,245,201)} .Pastel2 .q5-8{fill:rgb(255,242,174)} .Pastel2 .q6-8{fill:rgb(241,226,204)} .Pastel2 .q7-8{fill:rgb(204,204,204)} .Set1 .q0-3{fill:rgb(228,26,28)} .Set1 .q1-3{fill:rgb(55,126,184)} .Set1 .q2-3{fill:rgb(77,175,74)} .Set1 .q0-4{fill:rgb(228,26,28)} .Set1 .q1-4{fill:rgb(55,126,184)} .Set1 .q2-4{fill:rgb(77,175,74)} .Set1 .q3-4{fill:rgb(152,78,163)} .Set1 .q0-5{fill:rgb(228,26,28)} .Set1 .q1-5{fill:rgb(55,126,184)} .Set1 .q2-5{fill:rgb(77,175,74)} .Set1 .q3-5{fill:rgb(152,78,163)} .Set1 .q4-5{fill:rgb(255,127,0)} .Set1 .q0-6{fill:rgb(228,26,28)} .Set1 .q1-6{fill:rgb(55,126,184)} .Set1 .q2-6{fill:rgb(77,175,74)} .Set1 .q3-6{fill:rgb(152,78,163)} .Set1 .q4-6{fill:rgb(255,127,0)} .Set1 .q5-6{fill:rgb(255,255,51)} .Set1 .q0-7{fill:rgb(228,26,28)} .Set1 .q1-7{fill:rgb(55,126,184)} .Set1 .q2-7{fill:rgb(77,175,74)} .Set1 .q3-7{fill:rgb(152,78,163)} .Set1 .q4-7{fill:rgb(255,127,0)} .Set1 .q5-7{fill:rgb(255,255,51)} .Set1 .q6-7{fill:rgb(166,86,40)} .Set1 .q0-8{fill:rgb(228,26,28)} .Set1 .q1-8{fill:rgb(55,126,184)} .Set1 .q2-8{fill:rgb(77,175,74)} .Set1 .q3-8{fill:rgb(152,78,163)} .Set1 .q4-8{fill:rgb(255,127,0)} .Set1 .q5-8{fill:rgb(255,255,51)} .Set1 .q6-8{fill:rgb(166,86,40)} .Set1 .q7-8{fill:rgb(247,129,191)} .Set1 .q0-9{fill:rgb(228,26,28)} .Set1 .q1-9{fill:rgb(55,126,184)} .Set1 .q2-9{fill:rgb(77,175,74)} .Set1 .q3-9{fill:rgb(152,78,163)} .Set1 .q4-9{fill:rgb(255,127,0)} .Set1 .q5-9{fill:rgb(255,255,51)} .Set1 .q6-9{fill:rgb(166,86,40)} .Set1 .q7-9{fill:rgb(247,129,191)} .Set1 .q8-9{fill:rgb(153,153,153)} .Set2 .q0-3{fill:rgb(102,194,165)} .Set2 .q1-3{fill:rgb(252,141,98)} .Set2 .q2-3{fill:rgb(141,160,203)} .Set2 .q0-4{fill:rgb(102,194,165)} .Set2 .q1-4{fill:rgb(252,141,98)} .Set2 .q2-4{fill:rgb(141,160,203)} .Set2 .q3-4{fill:rgb(231,138,195)} .Set2 .q0-5{fill:rgb(102,194,165)} .Set2 .q1-5{fill:rgb(252,141,98)} .Set2 .q2-5{fill:rgb(141,160,203)} .Set2 .q3-5{fill:rgb(231,138,195)} .Set2 .q4-5{fill:rgb(166,216,84)} .Set2 .q0-6{fill:rgb(102,194,165)} .Set2 .q1-6{fill:rgb(252,141,98)} .Set2 .q2-6{fill:rgb(141,160,203)} .Set2 .q3-6{fill:rgb(231,138,195)} .Set2 .q4-6{fill:rgb(166,216,84)} .Set2 .q5-6{fill:rgb(255,217,47)} .Set2 .q0-7{fill:rgb(102,194,165)} .Set2 .q1-7{fill:rgb(252,141,98)} .Set2 .q2-7{fill:rgb(141,160,203)} .Set2 .q3-7{fill:rgb(231,138,195)} .Set2 .q4-7{fill:rgb(166,216,84)} .Set2 .q5-7{fill:rgb(255,217,47)} .Set2 .q6-7{fill:rgb(229,196,148)} .Set2 .q0-8{fill:rgb(102,194,165)} .Set2 .q1-8{fill:rgb(252,141,98)} .Set2 .q2-8{fill:rgb(141,160,203)} .Set2 .q3-8{fill:rgb(231,138,195)} .Set2 .q4-8{fill:rgb(166,216,84)} .Set2 .q5-8{fill:rgb(255,217,47)} .Set2 .q6-8{fill:rgb(229,196,148)} .Set2 .q7-8{fill:rgb(179,179,179)} .Set3 .q0-3{fill:rgb(141,211,199)} .Set3 .q1-3{fill:rgb(255,255,179)} .Set3 .q2-3{fill:rgb(190,186,218)} .Set3 .q0-4{fill:rgb(141,211,199)} .Set3 .q1-4{fill:rgb(255,255,179)} .Set3 .q2-4{fill:rgb(190,186,218)} .Set3 .q3-4{fill:rgb(251,128,114)} .Set3 .q0-5{fill:rgb(141,211,199)} .Set3 .q1-5{fill:rgb(255,255,179)} .Set3 .q2-5{fill:rgb(190,186,218)} .Set3 .q3-5{fill:rgb(251,128,114)} .Set3 .q4-5{fill:rgb(128,177,211)} .Set3 .q0-6{fill:rgb(141,211,199)} .Set3 .q1-6{fill:rgb(255,255,179)} .Set3 .q2-6{fill:rgb(190,186,218)} .Set3 .q3-6{fill:rgb(251,128,114)} .Set3 .q4-6{fill:rgb(128,177,211)} .Set3 .q5-6{fill:rgb(253,180,98)} .Set3 .q0-7{fill:rgb(141,211,199)} .Set3 .q1-7{fill:rgb(255,255,179)} .Set3 .q2-7{fill:rgb(190,186,218)} .Set3 .q3-7{fill:rgb(251,128,114)} .Set3 .q4-7{fill:rgb(128,177,211)} .Set3 .q5-7{fill:rgb(253,180,98)} .Set3 .q6-7{fill:rgb(179,222,105)} .Set3 .q0-8{fill:rgb(141,211,199)} .Set3 .q1-8{fill:rgb(255,255,179)} .Set3 .q2-8{fill:rgb(190,186,218)} .Set3 .q3-8{fill:rgb(251,128,114)} .Set3 .q4-8{fill:rgb(128,177,211)} .Set3 .q5-8{fill:rgb(253,180,98)} .Set3 .q6-8{fill:rgb(179,222,105)} .Set3 .q7-8{fill:rgb(252,205,229)} .Set3 .q0-9{fill:rgb(141,211,199)} .Set3 .q1-9{fill:rgb(255,255,179)} .Set3 .q2-9{fill:rgb(190,186,218)} .Set3 .q3-9{fill:rgb(251,128,114)} .Set3 .q4-9{fill:rgb(128,177,211)} .Set3 .q5-9{fill:rgb(253,180,98)} .Set3 .q6-9{fill:rgb(179,222,105)} .Set3 .q7-9{fill:rgb(252,205,229)} .Set3 .q8-9{fill:rgb(217,217,217)} .Set3 .q0-10{fill:rgb(141,211,199)} .Set3 .q1-10{fill:rgb(255,255,179)} .Set3 .q2-10{fill:rgb(190,186,218)} .Set3 .q3-10{fill:rgb(251,128,114)} .Set3 .q4-10{fill:rgb(128,177,211)} .Set3 .q5-10{fill:rgb(253,180,98)} .Set3 .q6-10{fill:rgb(179,222,105)} .Set3 .q7-10{fill:rgb(252,205,229)} .Set3 .q8-10{fill:rgb(217,217,217)} .Set3 .q9-10{fill:rgb(188,128,189)} .Set3 .q0-11{fill:rgb(141,211,199)} .Set3 .q1-11{fill:rgb(255,255,179)} .Set3 .q2-11{fill:rgb(190,186,218)} .Set3 .q3-11{fill:rgb(251,128,114)} .Set3 .q4-11{fill:rgb(128,177,211)} .Set3 .q5-11{fill:rgb(253,180,98)} .Set3 .q6-11{fill:rgb(179,222,105)} .Set3 .q7-11{fill:rgb(252,205,229)} .Set3 .q8-11{fill:rgb(217,217,217)} .Set3 .q9-11{fill:rgb(188,128,189)} .Set3 .q10-11{fill:rgb(204,235,197)} .Set3 .q0-12{fill:rgb(141,211,199)} .Set3 .q1-12{fill:rgb(255,255,179)} .Set3 .q2-12{fill:rgb(190,186,218)} .Set3 .q3-12{fill:rgb(251,128,114)} .Set3 .q4-12{fill:rgb(128,177,211)} .Set3 .q5-12{fill:rgb(253,180,98)} .Set3 .q6-12{fill:rgb(179,222,105)} .Set3 .q7-12{fill:rgb(252,205,229)} .Set3 .q8-12{fill:rgb(217,217,217)} .Set3 .q9-12{fill:rgb(188,128,189)} .Set3 .q10-12{fill:rgb(204,235,197)} .Set3 .q11-12{fill:rgb(255,237,111)} ================================================ FILE: web/css/theme.css ================================================ body { padding-top: 75px; padding-bottom: 30px; } .container > div.navbar-header { background: transparent url(../i/product-logo-rev2.png) no-repeat scroll left top; background-size: 50px 50px; background-position: 0 2px; padding-left: 55px; } .theme-dropdown .dropdown-menu { display: block; position: static; margin-bottom: 20px; } .theme-showcase > p > .btn { margin: 5px 0; } .label-none { background-color: #fff; } span.exchange-rate { text-align:right; display: inline-block; width: 8em; } span.exchange-value { text-align:right; float: right; display: inline-block; width: auto; } span.exchange-value:before, span.client-value:before, span.queue-value:before { content:"("; } span.exchange-value:after, span.client-value:after, span.queue-value:after { content:")"; } span.exchange-rate:after, span.client-rate:after, span.queue-rate:after { content:"/s"; } .aggregate-only { display:none; } div#exchange-5f616767726567617465 .aggregate-only { display:block; } div#queues div.panel-heading { padding-bottom: 0; } div.queue-name-title a { margin-left: 1em; } div.backlog-container { width:80px; } div.queue-name-title a:hover { text-decoration:none; } table.clients td { font-size: 12px; } table.clients th, table.clients td { width: 8%; text-align:right; } table.clients th.gap { width:1em; } table.clients th.top-span { width:auto; border-bottom:1; border-color:#999; } table.clients th.client-user, table.clients td.client-user { text-align:left; } table.clients th.client-address, table.clients td.client-address { width: 18%; text-align:center; } @media (min-width: 768px) { dl.dl-horizontal { margin-bottom:0; } .dl-horizontal dt { width:100px; } .dl-horizontal dd { margin-left: 6em; } } ================================================ FILE: web/index.html ================================================ Circonus Fq Operational Dashboard

Exchanges

_aggregate

Messages In
Octets In
Routed
Dropped
Size dropped
No Route
No Exchange

Queues

Routes

User Address Tx (to Client) Rx (from Client)
MsgsOctets MsgsOctets Routed Dropped Size Dropped No Route No Exchange









Prefix
Program
Invocations
Approximate Latency
================================================ FILE: web/js/bootstrap.js ================================================ /** * bootstrap.js v3.0.0 by @fat and @mdo * Copyright 2013 Twitter Inc. * http://www.apache.org/licenses/LICENSE-2.0 */ if (!jQuery) { throw new Error("Bootstrap requires jQuery") } /* ======================================================================== * Bootstrap: transition.js v3.0.0 * http://twbs.github.com/bootstrap/javascript.html#transitions * ======================================================================== * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ======================================================================== */ +function ($) { "use strict"; // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) // ============================================================ function transitionEnd() { var el = document.createElement('bootstrap') var transEndEventNames = { 'WebkitTransition' : 'webkitTransitionEnd' , 'MozTransition' : 'transitionend' , 'OTransition' : 'oTransitionEnd otransitionend' , 'transition' : 'transitionend' } for (var name in transEndEventNames) { if (el.style[name] !== undefined) { return { end: transEndEventNames[name] } } } } // http://blog.alexmaccaw.com/css-transitions $.fn.emulateTransitionEnd = function (duration) { var called = false, $el = this $(this).one($.support.transition.end, function () { called = true }) var callback = function () { if (!called) $($el).trigger($.support.transition.end) } setTimeout(callback, duration) return this } $(function () { $.support.transition = transitionEnd() }) }(window.jQuery); /* ======================================================================== * Bootstrap: alert.js v3.0.0 * http://twbs.github.com/bootstrap/javascript.html#alerts * ======================================================================== * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ======================================================================== */ +function ($) { "use strict"; // ALERT CLASS DEFINITION // ====================== var dismiss = '[data-dismiss="alert"]' var Alert = function (el) { $(el).on('click', dismiss, this.close) } Alert.prototype.close = function (e) { var $this = $(this) var selector = $this.attr('data-target') if (!selector) { selector = $this.attr('href') selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 } var $parent = $(selector) if (e) e.preventDefault() if (!$parent.length) { $parent = $this.hasClass('alert') ? $this : $this.parent() } $parent.trigger(e = $.Event('close.bs.alert')) if (e.isDefaultPrevented()) return $parent.removeClass('in') function removeElement() { $parent.trigger('closed.bs.alert').remove() } $.support.transition && $parent.hasClass('fade') ? $parent .one($.support.transition.end, removeElement) .emulateTransitionEnd(150) : removeElement() } // ALERT PLUGIN DEFINITION // ======================= var old = $.fn.alert $.fn.alert = function (option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.alert') if (!data) $this.data('bs.alert', (data = new Alert(this))) if (typeof option == 'string') data[option].call($this) }) } $.fn.alert.Constructor = Alert // ALERT NO CONFLICT // ================= $.fn.alert.noConflict = function () { $.fn.alert = old return this } // ALERT DATA-API // ============== $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) }(window.jQuery); /* ======================================================================== * Bootstrap: button.js v3.0.0 * http://twbs.github.com/bootstrap/javascript.html#buttons * ======================================================================== * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ======================================================================== */ +function ($) { "use strict"; // BUTTON PUBLIC CLASS DEFINITION // ============================== var Button = function (element, options) { this.$element = $(element) this.options = $.extend({}, Button.DEFAULTS, options) } Button.DEFAULTS = { loadingText: 'loading...' } Button.prototype.setState = function (state) { var d = 'disabled' var $el = this.$element var val = $el.is('input') ? 'val' : 'html' var data = $el.data() state = state + 'Text' if (!data.resetText) $el.data('resetText', $el[val]()) $el[val](data[state] || this.options[state]) // push to event loop to allow forms to submit setTimeout(function () { state == 'loadingText' ? $el.addClass(d).attr(d, d) : $el.removeClass(d).removeAttr(d); }, 0) } Button.prototype.toggle = function () { var $parent = this.$element.closest('[data-toggle="buttons"]') if ($parent.length) { var $input = this.$element.find('input') .prop('checked', !this.$element.hasClass('active')) .trigger('change') if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active') } this.$element.toggleClass('active') } // BUTTON PLUGIN DEFINITION // ======================== var old = $.fn.button $.fn.button = function (option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.button') var options = typeof option == 'object' && option if (!data) $this.data('bs.button', (data = new Button(this, options))) if (option == 'toggle') data.toggle() else if (option) data.setState(option) }) } $.fn.button.Constructor = Button // BUTTON NO CONFLICT // ================== $.fn.button.noConflict = function () { $.fn.button = old return this } // BUTTON DATA-API // =============== $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) { var $btn = $(e.target) if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') $btn.button('toggle') e.preventDefault() }) }(window.jQuery); /* ======================================================================== * Bootstrap: carousel.js v3.0.0 * http://twbs.github.com/bootstrap/javascript.html#carousel * ======================================================================== * Copyright 2012 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ======================================================================== */ +function ($) { "use strict"; // CAROUSEL CLASS DEFINITION // ========================= var Carousel = function (element, options) { this.$element = $(element) this.$indicators = this.$element.find('.carousel-indicators') this.options = options this.paused = this.sliding = this.interval = this.$active = this.$items = null this.options.pause == 'hover' && this.$element .on('mouseenter', $.proxy(this.pause, this)) .on('mouseleave', $.proxy(this.cycle, this)) } Carousel.DEFAULTS = { interval: 5000 , pause: 'hover' , wrap: true } Carousel.prototype.cycle = function (e) { e || (this.paused = false) this.interval && clearInterval(this.interval) this.options.interval && !this.paused && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) return this } Carousel.prototype.getActiveIndex = function () { this.$active = this.$element.find('.item.active') this.$items = this.$active.parent().children() return this.$items.index(this.$active) } Carousel.prototype.to = function (pos) { var that = this var activeIndex = this.getActiveIndex() if (pos > (this.$items.length - 1) || pos < 0) return if (this.sliding) return this.$element.one('slid', function () { that.to(pos) }) if (activeIndex == pos) return this.pause().cycle() return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) } Carousel.prototype.pause = function (e) { e || (this.paused = true) if (this.$element.find('.next, .prev').length && $.support.transition.end) { this.$element.trigger($.support.transition.end) this.cycle(true) } this.interval = clearInterval(this.interval) return this } Carousel.prototype.next = function () { if (this.sliding) return return this.slide('next') } Carousel.prototype.prev = function () { if (this.sliding) return return this.slide('prev') } Carousel.prototype.slide = function (type, next) { var $active = this.$element.find('.item.active') var $next = next || $active[type]() var isCycling = this.interval var direction = type == 'next' ? 'left' : 'right' var fallback = type == 'next' ? 'first' : 'last' var that = this if (!$next.length) { if (!this.options.wrap) return $next = this.$element.find('.item')[fallback]() } this.sliding = true isCycling && this.pause() var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction }) if ($next.hasClass('active')) return if (this.$indicators.length) { this.$indicators.find('.active').removeClass('active') this.$element.one('slid', function () { var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) $nextIndicator && $nextIndicator.addClass('active') }) } if ($.support.transition && this.$element.hasClass('slide')) { this.$element.trigger(e) if (e.isDefaultPrevented()) return $next.addClass(type) $next[0].offsetWidth // force reflow $active.addClass(direction) $next.addClass(direction) $active .one($.support.transition.end, function () { $next.removeClass([type, direction].join(' ')).addClass('active') $active.removeClass(['active', direction].join(' ')) that.sliding = false setTimeout(function () { that.$element.trigger('slid') }, 0) }) .emulateTransitionEnd(600) } else { this.$element.trigger(e) if (e.isDefaultPrevented()) return $active.removeClass('active') $next.addClass('active') this.sliding = false this.$element.trigger('slid') } isCycling && this.cycle() return this } // CAROUSEL PLUGIN DEFINITION // ========================== var old = $.fn.carousel $.fn.carousel = function (option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.carousel') var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) var action = typeof option == 'string' ? option : options.slide if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) if (typeof option == 'number') data.to(option) else if (action) data[action]() else if (options.interval) data.pause().cycle() }) } $.fn.carousel.Constructor = Carousel // CAROUSEL NO CONFLICT // ==================== $.fn.carousel.noConflict = function () { $.fn.carousel = old return this } // CAROUSEL DATA-API // ================= $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { var $this = $(this), href var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 var options = $.extend({}, $target.data(), $this.data()) var slideIndex = $this.attr('data-slide-to') if (slideIndex) options.interval = false $target.carousel(options) if (slideIndex = $this.attr('data-slide-to')) { $target.data('bs.carousel').to(slideIndex) } e.preventDefault() }) $(window).on('load', function () { $('[data-ride="carousel"]').each(function () { var $carousel = $(this) $carousel.carousel($carousel.data()) }) }) }(window.jQuery); /* ======================================================================== * Bootstrap: collapse.js v3.0.0 * http://twbs.github.com/bootstrap/javascript.html#collapse * ======================================================================== * Copyright 2012 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ======================================================================== */ +function ($) { "use strict"; // COLLAPSE PUBLIC CLASS DEFINITION // ================================ var Collapse = function (element, options) { this.$element = $(element) this.options = $.extend({}, Collapse.DEFAULTS, options) this.transitioning = null if (this.options.parent) this.$parent = $(this.options.parent) if (this.options.toggle) this.toggle() } Collapse.DEFAULTS = { toggle: true } Collapse.prototype.dimension = function () { var hasWidth = this.$element.hasClass('width') return hasWidth ? 'width' : 'height' } Collapse.prototype.show = function () { if (this.transitioning || this.$element.hasClass('in')) return var startEvent = $.Event('show.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return var actives = this.$parent && this.$parent.find('> .panel > .in') if (actives && actives.length) { var hasData = actives.data('bs.collapse') if (hasData && hasData.transitioning) return actives.collapse('hide') hasData || actives.data('bs.collapse', null) } var dimension = this.dimension() this.$element .removeClass('collapse') .addClass('collapsing') [dimension](0) this.transitioning = 1 var complete = function () { this.$element .removeClass('collapsing') .addClass('in') [dimension]('auto') this.transitioning = 0 this.$element.trigger('shown.bs.collapse') } if (!$.support.transition) return complete.call(this) var scrollSize = $.camelCase(['scroll', dimension].join('-')) this.$element .one($.support.transition.end, $.proxy(complete, this)) .emulateTransitionEnd(350) [dimension](this.$element[0][scrollSize]) } Collapse.prototype.hide = function () { if (this.transitioning || !this.$element.hasClass('in')) return var startEvent = $.Event('hide.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return var dimension = this.dimension() this.$element [dimension](this.$element[dimension]()) [0].offsetHeight this.$element .addClass('collapsing') .removeClass('collapse') .removeClass('in') this.transitioning = 1 var complete = function () { this.transitioning = 0 this.$element .trigger('hidden.bs.collapse') .removeClass('collapsing') .addClass('collapse') } if (!$.support.transition) return complete.call(this) this.$element [dimension](0) .one($.support.transition.end, $.proxy(complete, this)) .emulateTransitionEnd(350) } Collapse.prototype.toggle = function () { this[this.$element.hasClass('in') ? 'hide' : 'show']() } // COLLAPSE PLUGIN DEFINITION // ========================== var old = $.fn.collapse $.fn.collapse = function (option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.collapse') var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) if (typeof option == 'string') data[option]() }) } $.fn.collapse.Constructor = Collapse // COLLAPSE NO CONFLICT // ==================== $.fn.collapse.noConflict = function () { $.fn.collapse = old return this } // COLLAPSE DATA-API // ================= $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) { var $this = $(this), href var target = $this.attr('data-target') || e.preventDefault() || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 var $target = $(target) var data = $target.data('bs.collapse') var option = data ? 'toggle' : $this.data() var parent = $this.attr('data-parent') var $parent = parent && $(parent) if (!data || !data.transitioning) { if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed') $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') } $target.collapse(option) }) }(window.jQuery); /* ======================================================================== * Bootstrap: dropdown.js v3.0.0 * http://twbs.github.com/bootstrap/javascript.html#dropdowns * ======================================================================== * Copyright 2012 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ======================================================================== */ +function ($) { "use strict"; // DROPDOWN CLASS DEFINITION // ========================= var backdrop = '.dropdown-backdrop' var toggle = '[data-toggle=dropdown]' var Dropdown = function (element) { var $el = $(element).on('click.bs.dropdown', this.toggle) } Dropdown.prototype.toggle = function (e) { var $this = $(this) if ($this.is('.disabled, :disabled')) return var $parent = getParent($this) var isActive = $parent.hasClass('open') clearMenus() if (!isActive) { if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { // if mobile we we use a backdrop because click events don't delegate $('