/** * @file BReactor_badvpn.h * @author Ambroz Bizjak * * @section LICENSE * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @section DESCRIPTION * * Event loop that supports file desciptor (Linux) or HANDLE (Windows) events * and timers. */ #ifndef BADVPN_SYSTEM_BREACTOR_H #define BADVPN_SYSTEM_BREACTOR_H #if (defined(BADVPN_USE_WINAPI) + defined(BADVPN_USE_EPOLL) + defined(BADVPN_USE_KEVENT) + defined(BADVPN_USE_POLL)) != 1 #error Unknown event backend or too many event backends #endif #ifdef BADVPN_USE_WINAPI #include #endif #ifdef BADVPN_USE_EPOLL #include #endif #ifdef BADVPN_USE_KEVENT #include #include #include #endif #ifdef BADVPN_USE_POLL #include #endif #include #include #include #include #include #include #include #include struct BSmallTimer_t; typedef struct BSmallTimer_t *BReactor_timerstree_link; #include "BReactor_badvpn_timerstree.h" #include #define BTIMER_SET_ABSOLUTE 1 #define BTIMER_SET_RELATIVE 2 /** * Handler function invoked when the timer expires. * The timer was in running state. * The timer enters not running state before this function is invoked. * This function is being called from within the timer's previosly * associated reactor. * * @param timer pointer to the timer. Use the {@link UPPER_OBJECT} macro * to obtain the pointer to the containing structure. */ typedef void (*BSmallTimer_handler) (struct BSmallTimer_t *timer); /** * Handler function invoked when the timer expires. * The timer was in running state. * The timer enters not running state before this function is invoked. * This function is being called from within the timer's previosly * associated reactor. * * @param user value passed to {@link BTimer_Init} */ typedef void (*BTimer_handler) (void *user); /** * Timer object used with {@link BReactor}. */ typedef struct BSmallTimer_t { union { BSmallTimer_handler smalll; // MSVC doesn't like "small" BTimer_handler heavy; } handler; union { LinkedList1Node list_node; struct BSmallTimer_t *tree_child[2]; } u; struct BSmallTimer_t *tree_parent; btime_t absTime; int8_t tree_balance; uint8_t state; uint8_t is_small; } BSmallTimer; /** * Initializes the timer object. * The timer object is initialized in not running state. * * @param bt the object * @param handler handler function invoked when the timer expires */ void BSmallTimer_Init (BSmallTimer *bt, BSmallTimer_handler handler); /** * Checks if the timer is running. * * @param bt the object * @return 1 if running, 0 if not running */ int BSmallTimer_IsRunning (BSmallTimer *bt); /** * Timer object used with {@link BReactor}. This is a legacy wrapper * around {@link BSmallTimer} with an extra field for the default time. */ typedef struct { BSmallTimer base; void *user; btime_t msTime; } BTimer; /** * Initializes the timer object. * The timer object is initialized in not running state. * * @param bt the object * @param msTime default timeout in milliseconds * @param handler handler function invoked when the timer expires * @param user value to pass to the handler function */ void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *user); /** * Checks if the timer is running. * * @param bt the object * @return 1 if running, 0 if not running */ int BTimer_IsRunning (BTimer *bt); #ifndef BADVPN_USE_WINAPI struct BFileDescriptor_t; #define BREACTOR_READ (1 << 0) #define BREACTOR_WRITE (1 << 1) #define BREACTOR_ERROR (1 << 2) #define BREACTOR_HUP (1 << 3) /** * Handler function invoked by the reactor when one or more events are detected. * The events argument will contain a subset of the monitored events (BREACTOR_READ, BREACTOR_WRITE), * plus possibly the error event (BREACTOR_ERROR). * The file descriptor object is in active state, being called from within * the associated reactor. * * @param user value passed to {@link BFileDescriptor_Init} * @param events bitmask composed of a subset of monitored events (BREACTOR_READ, BREACTOR_WRITE), * and possibly the error event BREACTOR_ERROR and the hang-up event BREACTOR_HUP. * Will be nonzero. */ typedef void (*BFileDescriptor_handler) (void *user, int events); /** * File descriptor object used with {@link BReactor}. */ typedef struct BFileDescriptor_t { int fd; BFileDescriptor_handler handler; void *user; int active; int waitEvents; #ifdef BADVPN_USE_EPOLL struct BFileDescriptor_t **epoll_returned_ptr; #endif #ifdef BADVPN_USE_KEVENT int kevent_tag; int **kevent_returned_ptr; #endif #ifdef BADVPN_USE_POLL LinkedList1Node poll_enabled_fds_list_node; int poll_returned_index; #endif } BFileDescriptor; /** * Intializes the file descriptor object. * The object is initialized in not active state. * * @param bs file descriptor object to initialize * @param fb file descriptor to represent * @param handler handler function invoked by the reactor when a monitored event is detected * @param user value passed to the handler functuon */ void BFileDescriptor_Init (BFileDescriptor *bs, int fd, BFileDescriptor_handler handler, void *user); #endif // BReactor #define BSYSTEM_MAX_RESULTS 64 #define BSYSTEM_MAX_HANDLES 64 #define BSYSTEM_MAX_POLL_FDS 4096 /** * Event loop that supports file desciptor (Linux) or HANDLE (Windows) events * and timers. */ typedef struct { int exiting; int exit_code; // jobs BPendingGroup pending_jobs; // timers BReactor__TimersTree timers_tree; LinkedList1 timers_expired_list; // limits LinkedList1 active_limits_list; #ifdef BADVPN_USE_WINAPI LinkedList1 iocp_list; HANDLE iocp_handle; LinkedList1 iocp_ready_list; #endif #ifdef BADVPN_USE_EPOLL int efd; // epoll fd struct epoll_event epoll_results[BSYSTEM_MAX_RESULTS]; // epoll returned events buffer int epoll_results_num; // number of events in the array int epoll_results_pos; // number of events processed so far #endif #ifdef BADVPN_USE_KEVENT int kqueue_fd; struct kevent kevent_results[BSYSTEM_MAX_RESULTS]; int kevent_results_num; int kevent_results_pos; #endif #ifdef BADVPN_USE_POLL LinkedList1 poll_enabled_fds_list; int poll_num_enabled_fds; int poll_results_num; int poll_results_pos; struct pollfd *poll_results_pollfds; BFileDescriptor **poll_results_bfds; #endif DebugObject d_obj; #ifndef BADVPN_USE_WINAPI DebugCounter d_fds_counter; #endif #ifdef BADVPN_USE_KEVENT DebugCounter d_kevent_ctr; #endif DebugCounter d_limits_ctr; } BReactor; /** * Initializes the reactor. * {@link BLog_Init} must have been done. * {@link BTime_Init} must have been done. * * @param bsys the object * @return 1 on success, 0 on failure */ int BReactor_Init (BReactor *bsys) WARN_UNUSED; /** * Frees the reactor. * Must not be called from within the event loop ({@link BReactor_Exec}). * There must be no {@link BPending} or {@link BSmallPending} objects using the * pending group returned by {@link BReactor_PendingGroup}. * There must be no running timers in this reactor. * There must be no limit objects in this reactor. * There must be no file descriptors or handles registered * with this reactor. * There must be no {@link BReactorKEvent} objects in this reactor. * * @param bsys the object */ void BReactor_Free (BReactor *bsys); /** * Runs the event loop. * * @param bsys the object * @return value passed to {@link BReactor_Quit} */ int BReactor_Exec (BReactor *bsys); /** * Causes the event loop ({@link BReactor_Exec}) to cease * dispatching events and return. * Any further calls of {@link BReactor_Exec} will return immediately. * * @param bsys the object * @param code value {@link BReactor_Exec} should return. If this is * called more than once, it will return the last code. */ void BReactor_Quit (BReactor *bsys, int code); /** * Starts a timer to expire at the specified time. * The timer must have been initialized with {@link BSmallTimer_Init}. * If the timer is in running state, it must be associated with this reactor. * The timer enters running state, associated with this reactor. * * @param bsys the object * @param bt timer to start * @param mode interpretation of time (BTIMER_SET_ABSOLUTE or BTIMER_SET_RELATIVE) * @param time absolute or relative expiration time */ void BReactor_SetSmallTimer (BReactor *bsys, BSmallTimer *bt, int mode, btime_t time); /** * Stops a timer. * If the timer is in running state, it must be associated with this reactor. * The timer enters not running state. * * @param bsys the object * @param bt timer to stop */ void BReactor_RemoveSmallTimer (BReactor *bsys, BSmallTimer *bt); /** * Starts a timer to expire after its default time. * The timer must have been initialized with {@link BTimer_Init}. * If the timer is in running state, it must be associated with this reactor. * The timer enters running state, associated with this reactor. * * @param bsys the object * @param bt timer to start */ void BReactor_SetTimer (BReactor *bsys, BTimer *bt); /** * Starts a timer to expire after a given time. * The timer must have been initialized with {@link BTimer_Init}. * If the timer is in running state, it must be associated with this reactor. * The timer enters running state, associated with this reactor. * * @param bsys the object * @param bt timer to start * @param after relative expiration time */ void BReactor_SetTimerAfter (BReactor *bsys, BTimer *bt, btime_t after); /** * Starts a timer to expire at the specified time. * The timer must have been initialized with {@link BTimer_Init}. * If the timer is in running state, it must be associated with this reactor. * The timer enters running state, associated with this reactor. * The timer's expiration time is set to the time argument. * * @param bsys the object * @param bt timer to start * @param time absolute expiration time (according to {@link btime_gettime}) */ void BReactor_SetTimerAbsolute (BReactor *bsys, BTimer *bt, btime_t time); /** * Stops a timer. * If the timer is in running state, it must be associated with this reactor. * The timer enters not running state. * * @param bsys the object * @param bt timer to stop */ void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt); /** * Returns a {@link BPendingGroup} object that can be used to schedule jobs for * the reactor to execute. These jobs have complete priority over other events * (timers, file descriptors and Windows handles). * The returned pending group may only be used as an argument to {@link BPending_Init}, * and must not be accessed by other means. * All {@link BPending} and {@link BSmallPending} objects using this group must be * freed before freeing the reactor. * * @param bsys the object * @return pending group for scheduling jobs for the reactor to execute */ BPendingGroup * BReactor_PendingGroup (BReactor *bsys); /** * Executes pending jobs until either: * - the reference job is reached, or * - {@link BReactor_Quit} is called. * The reference job must be reached before the job list empties. * The reference job will not be executed. * * WARNING: Use with care. This should only be used to to work around third-party software * that does not integrade into the jobs system. In particular, you should think about: * - the effects the jobs to be executed may have, and * - the environment those jobs expect to be executed in. * * @param bsys the object * @param ref reference job. It is not accessed in any way, only its address is compared to * pending jobs before they are executed. * @return 1 if the reference job was reached, * 0 if {@link BReactor_Quit} was called (either while executing a job, or before) */ int BReactor_Synchronize (BReactor *bsys, BSmallPending *ref); #ifndef BADVPN_USE_WINAPI /** * Starts monitoring a file descriptor. * * @param bsys the object * @param bs file descriptor object. Must have been initialized with * {@link BFileDescriptor_Init} Must be in not active state. * On success, the file descriptor object enters active state, * associated with this reactor. * @return 1 on success, 0 on failure */ int BReactor_AddFileDescriptor (BReactor *bsys, BFileDescriptor *bs) WARN_UNUSED; /** * Stops monitoring a file descriptor. * * @param bsys the object * @param bs {@link BFileDescriptor} object. Must be in active state, * associated with this reactor. The file descriptor object * enters not active state. */ void BReactor_RemoveFileDescriptor (BReactor *bsys, BFileDescriptor *bs); /** * Sets monitored file descriptor events. * * @param bsys the object * @param bs {@link BFileDescriptor} object. Must be in active state, * associated with this reactor. * @param events events to watch for. Must not have any bits other than * BREACTOR_READ and BREACTOR_WRITE. * This overrides previosly monitored events. */ void BReactor_SetFileDescriptorEvents (BReactor *bsys, BFileDescriptor *bs, int events); #endif typedef struct { BReactor *reactor; int limit; int count; LinkedList1Node active_limits_list_node; DebugObject d_obj; } BReactorLimit; /** * Initializes a limit object. * A limit object consists of a counter integer, which is initialized to * zero, is incremented by {@link BReactorLimit_Increment} up to \a limit, * and is reset to zero every time the event loop performs a wait. * If the event loop has processed all detected events, and before performing * a wait, it determines that timers have expired, the counter will not be reset. * * @param o the object * @param reactor reactor the object is tied to * @param limit maximum counter value. Must be >0. */ void BReactorLimit_Init (BReactorLimit *o, BReactor *reactor, int limit); /** * Frees a limit object. * * @param o the object */ void BReactorLimit_Free (BReactorLimit *o); /** * Attempts to increment the counter of a limit object. * * @param o the object * @return 1 if the counter was lesser than the limit and was incremented, * 0 if the counter was greater or equal to the limit and could not be * incremented */ int BReactorLimit_Increment (BReactorLimit *o); /** * Sets the limit of a limit object. * * @param o the object * @param limit new limit. Must be >0. */ void BReactorLimit_SetLimit (BReactorLimit *o, int limit); #ifdef BADVPN_USE_KEVENT typedef void (*BReactorKEvent_handler) (void *user, u_int fflags, intptr_t data); typedef struct { BReactor *reactor; BReactorKEvent_handler handler; void *user; uintptr_t ident; short filter; int kevent_tag; int **kevent_returned_ptr; DebugObject d_obj; } BReactorKEvent; int BReactorKEvent_Init (BReactorKEvent *o, BReactor *reactor, BReactorKEvent_handler handler, void *user, uintptr_t ident, short filter, u_int fflags, intptr_t data); void BReactorKEvent_Free (BReactorKEvent *o); #endif #ifdef BADVPN_USE_WINAPI #define BREACTOR_IOCP_EVENT_SUCCEEDED 1 #define BREACTOR_IOCP_EVENT_FAILED 2 #define BREACTOR_IOCP_EVENT_EXITING 3 typedef void (*BReactorIOCPOverlapped_handler) (void *user, int event, DWORD bytes); typedef struct { OVERLAPPED olap; BReactor *reactor; void *user; BReactorIOCPOverlapped_handler handler; LinkedList1Node iocp_list_node; int is_ready; LinkedList1Node ready_list_node; int ready_succeeded; DWORD ready_bytes; DebugObject d_obj; } BReactorIOCPOverlapped; HANDLE BReactor_GetIOCPHandle (BReactor *reactor); void BReactorIOCPOverlapped_Init (BReactorIOCPOverlapped *o, BReactor *reactor, void *user, BReactorIOCPOverlapped_handler handler); void BReactorIOCPOverlapped_Free (BReactorIOCPOverlapped *o); void BReactorIOCPOverlapped_Wait (BReactorIOCPOverlapped *o, int *out_succeeded, DWORD *out_bytes); #endif #endif