├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── CMakeModules └── FindAsterisk.cmake ├── README.md ├── cdr_redis └── cdr_redis.c ├── conf ├── res_redis.conf └── res_redis_v1.conf ├── config.h.in ├── include ├── message_queue_pubsub.h ├── pbx_event_message_serializer.h └── shared.h ├── lib ├── ast_event_message_serializer.c ├── ast_stasis_message_serializer.c └── msq_redis.c ├── res_config_redis └── res_config_redis.c ├── res_redis ├── res_redis.c └── res_redis_v1.c └── tests ├── CMakeLists.txt └── test.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | build 3 | *.bak 4 | *.bak* 5 | 6 | # Object files 7 | *.o 8 | *.ko 9 | *.obj 10 | *.elf 11 | 12 | # Precompiled Headers 13 | *.gch 14 | *.pch 15 | 16 | # Libraries 17 | *.lib 18 | *.a 19 | *.la 20 | *.lo 21 | 22 | # Shared objects (inc. Windows DLLs) 23 | *.dll 24 | *.so 25 | *.so.* 26 | *.dylib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.i*86 33 | *.x86_64 34 | *.hex -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | # Setup Build Matrix 4 | 5 | compiler: 6 | # - clang 7 | - gcc 8 | 9 | env: 10 | # - REPOS=lucid 11 | # - REPOS=precise 12 | - REPOS=trusty 13 | - REPOS=utopic 14 | # - REPOS=vivid 15 | 16 | # Install Required Devel Packages 17 | before_install: 18 | - echo "deb http://archive.ubuntu.com/ubuntu ${REPOS} main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/trusty.list 19 | - echo "deb http://archive.ubuntu.com/ubuntu ${REPOS}-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/trusty.list 20 | - echo "deb http://security.ubuntu.com/ubuntu/ ${REPOS}-security main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/trusty.list 21 | - echo "deb http://archive.canonical.com/ubuntu/ ${REPOS} partner" | sudo tee -a /etc/apt/sources.list.d/trusty.list 22 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 23 | - sudo apt-get update -qq 24 | - sudo apt-get install -qq asterisk asterisk-dev asterisk-config asterisk-modules libhiredis-dev redis-server libevent-dev libevent-pthreads-2.0-5 libevent-2.0-5 25 | 26 | install: 27 | - sudo apt-get -qq install libgtest-dev 28 | - "cd /usr/src/gtest && sudo cmake . && sudo cmake --build . && sudo mv libg* /usr/local/lib/ ; cd -" 29 | - sudo apt-get -qq install google-mock 30 | - "cd /usr/src/gmock && sudo cmake . && sudo cmake --build . && sudo mv libg* /usr/local/lib/ ; cd -" 31 | - sudo apt-get -qq install gcc-4.9 g++-4.9 32 | - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.9 90 33 | - sudo update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-4.9 90 34 | - cd ${TRAVIS_BUILD_DIR} 35 | - wget http://ftp.de.debian.org/debian/pool/main/l/lcov/lcov_1.11.orig.tar.gz 36 | - tar xf lcov_1.11.orig.tar.gz 37 | - sudo make -C lcov-1.11/ install 38 | - gem install coveralls-lcov 39 | - lcov --version 40 | - g++ --version 41 | 42 | before_script: 43 | - cd ${TRAVIS_BUILD_DIR} 44 | - lcov --directory . --zerocounters 45 | 46 | script: 47 | - cd ${TRAVIS_BUILD_DIR} 48 | - cmake . 49 | - make 50 | - ctest --verbose 51 | 52 | after_success: 53 | - cd ${TRAVIS_BUILD_DIR} 54 | - lcov --directory . --capture --output-file coverage.info 55 | - lcov --remove coverage.info 'tests/*' '/usr/*' --output-file coverage.info 56 | - lcov --list coverage.info 57 | - coveralls-lcov --repo-token 4cMjCM2IFzMimsDZ1c3pyh6LZBk1dCLaM coverage.info 58 | 59 | # Report Results 60 | notifications: 61 | email: 62 | recipients: 63 | - ddegroot@talon.nl 64 | - c@zu.io 65 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(ast_redis) 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | #-------------------------------------------------- 5 | # Compiler Options 6 | #-------------------------------------------------- 7 | find_package(PkgConfig REQUIRED) 8 | 9 | SET(CMAKE_BUILD_TYPE "Debug") 10 | SET(CMAKE_C_FLAGS_DEBUG "-g -O0 -Wall -Werror") 11 | SET(CMAKE_C_FLAGS_RELEASE "-O2 -Wall") 12 | SET(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Werror") 13 | SET(CMAKE_CXX_FLAGS_RELEASE "-O2 -Wall") 14 | 15 | # Testing 16 | enable_testing() 17 | add_subdirectory(tests) 18 | #-------------------------------------------------- 19 | # PThread is required 20 | #-------------------------------------------------- 21 | find_package (Threads) 22 | 23 | #-------------------------------------------------- 24 | # HiRedis is required 25 | #-------------------------------------------------- 26 | pkg_check_modules (HIREDIS REQUIRED hiredis>=0.11.0) 27 | 28 | #-------------------------------------------------- 29 | # LibEvent is required 30 | 31 | #-------------------------------------------------- 32 | pkg_check_modules (LIBEVENT REQUIRED libevent) 33 | 34 | #-------------------------------------------------- 35 | # Asterisk is required 36 | #-------------------------------------------------- 37 | SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules") 38 | FIND_PACKAGE(Asterisk REQUIRED) 39 | 40 | #-------------------------------------------------- 41 | # Compile Sources 42 | #-------------------------------------------------- 43 | INCLUDE_DIRECTORIES(/usr/include/ ${PROJECT_BINARY_DIR}) 44 | 45 | # 46 | # CDR_REDIS 47 | # 48 | add_library(cdr_redis SHARED 49 | cdr_redis/cdr_redis.c 50 | ) 51 | set_target_properties(cdr_redis PROPERTIES PREFIX "") 52 | 53 | # 54 | # RES_CONFIG_REDIS 55 | # 56 | add_library(res_config_redis SHARED 57 | res_config_redis/res_config_redis.c 58 | ) 59 | 60 | # 61 | # RES_REDIS 62 | # 63 | add_library(res_redis SHARED 64 | # include/shared.h 65 | # include/message_queue_pubsub.h 66 | # include/pbx_event_message_serializer.h 67 | lib/msq_redis.c 68 | @PBX_EVENT_SERIALIZER@ 69 | res_redis/res_redis.c 70 | ) 71 | set_target_properties(res_redis PROPERTIES PREFIX "") 72 | 73 | # 74 | # RES_REDIS_v1 75 | # 76 | add_library(res_redis_v1 SHARED 77 | include/shared.h 78 | include/message_queue_pubsub.h 79 | include/pbx_event_message_serializer.h 80 | lib/msq_redis.c 81 | @PBX_EVENT_SERIALIZER@ 82 | res_redis/res_redis_v1.c 83 | ) 84 | set_target_properties(res_redis_v1 PROPERTIES PREFIX "") 85 | 86 | set_target_properties(res_config_redis PROPERTIES PREFIX "") 87 | target_link_libraries(res_config_redis -l@HIREDIS_LIBRARIES@ ${CMAKE_THREAD_LIBS_INIT}) 88 | target_link_libraries(res_redis -l@HIREDIS_LIBRARIES@ -l@LIBEVENT_LIBRARIES@ ${CMAKE_THREAD_LIBS_INIT}) 89 | target_link_libraries(res_redis_v1 -l@HIREDIS_LIBRARIES@ -l@LIBEVENT_LIBRARIES@ ${CMAKE_THREAD_LIBS_INIT}) 90 | 91 | #-------------------------------------------------- 92 | # Install 93 | #-------------------------------------------------- 94 | install(TARGETS cdr_redis DESTINATION ${ASTERISK_MOD_DIR}) 95 | install(TARGETS res_redis DESTINATION ${ASTERISK_MOD_DIR}) 96 | install(TARGETS res_redis_v1 DESTINATION ${ASTERISK_MOD_DIR}) 97 | install(TARGETS res_config_redis DESTINATION ${ASTERISK_MOD_DIR}) 98 | install(FILES conf/res_redis.conf DESTINATION ${ASTERISK_ETC_DIR}/ COMPONENT config) 99 | install(FILES conf/res_redis_v1.conf DESTINATION ${ASTERISK_ETC_DIR}/ COMPONENT config) 100 | -------------------------------------------------------------------------------- /CMakeModules/FindAsterisk.cmake: -------------------------------------------------------------------------------- 1 | # Locate asterisk includes and library 2 | # This module defines 3 | # ASTERISK_LIBRARY_DIR, the name of the asterisk modules library 4 | # ASTERISK_INCLUDE_DIR, where to find asterisk includes 5 | # 6 | 7 | #find_package(PkgConfig) 8 | #pkg_check_modules (ASTERISK asterisk) 9 | 10 | if (ASTERISK_FOUND) 11 | set(ASTERISK_INCLUDE_DIR ${ASTERISK_INCLUDE_DIRS}) 12 | set(ASTERISK_LIBRARY_DIR ${ASTERISK_LIBRARY_DIRS}) 13 | else (ASTERISK_FOUND) 14 | find_path(ASTERISK_INCLUDE_DIR asterisk.h 15 | HINTS 16 | PATH_SUFFIXES 17 | PATHS 18 | /usr/include 19 | ~/Library/Frameworks 20 | /Library/Frameworks 21 | /usr/local/asterisk*/include 22 | /usr/local 23 | /usr 24 | /sw 25 | /opt/asterisk*/include 26 | /opt/local 27 | /opt/csw 28 | /opt 29 | /mingw 30 | ) 31 | find_path(ASTERISK_LIBRARY_DIR app_dial.so 32 | HINTS 33 | PATH_SUFFIXES modules 34 | PATHS 35 | /usr/lib/asterisk 36 | /usr/lib64/asterisk 37 | /usr/local/lib/asterisk 38 | /usr/local/lib64/asterisk 39 | /usr/local/asterisk*/lib/asterisk 40 | /opt/asterisk*/lib/asterisk 41 | ) 42 | endif(ASTERISK_FOUND) 43 | 44 | IF(NOT ASTERISK_INCLUDE_DIR) 45 | MESSAGE(FATAL_ERROR "Build will fail, asterisk was not found") 46 | RETURN() 47 | ENDIF(NOT ASTERISK_INCLUDE_DIR) 48 | 49 | # Register directories 50 | INCLUDE_DIRECTORIES(/usr/include ${ASTERISK_INCLUDE_DIR}) 51 | LINK_DIRECTORIES(${ASTERISK_LIBRARY_DIR}) 52 | set(CMAKE_REQUIRED_INCLUDES ${ASTERISK_INCLUDE_DIR}) 53 | 54 | # Find Header Files 55 | INCLUDE (CheckIncludeFiles) 56 | CHECK_INCLUDE_FILES("asterisk.h;asterisk/module.h;asterisk/logger.h;asterisk/config.h" HAVE_PBX_ASTERISK_H) 57 | CHECK_INCLUDE_FILES("asterisk.h;asterisk/version.h" HAVE_PBX_VERSION_H) 58 | CHECK_INCLUDE_FILES("asterisk.h;asterisk/ast_version.h" HAVE_PBX_AST_VERSION_H) 59 | CHECK_INCLUDE_FILES("asterisk.h;asterisk/lock.h" HAVE_PBX_LOCK_H) 60 | CHECK_INCLUDE_FILES("asterisk.h;asterisk/event.h" HAVE_PBX_EVENT_H) 61 | CHECK_INCLUDE_FILES("asterisk.h;asterisk/stasis.h" HAVE_PBX_STASIS_H) 62 | if(NOT HAVE_PBX_STASIS_H) 63 | set(HAVE_PBX_VERSION_11 1) 64 | set(PBX_EVENT_SERIALIZER lib/ast_event_message_serializer.c) 65 | MESSAGE("Dealing with Asterisk <= Version 11") 66 | else(NOT HAVE_PBX_STASIS_H) 67 | set(HAVE_PBX_VERSION_12 1) 68 | set(PBX_EVENT_SERIALIZER lib/ast_stasis_message_serializer.c) 69 | MESSAGE("Dealing with Asterisk >= Version 12") 70 | endif() 71 | 72 | # CHECK_TYPE_SIZE("int" SIZEOF_INT) 73 | 74 | if(NOT HAVE_PBX_AST_VERSION_H AND NOT HAVE_PBX_VERSION_H) 75 | MESSAGE(FATAL_ERROR "Build will fail, asterisk version was not found") 76 | RETURN() 77 | endif() 78 | 79 | IF(IS_DIRECTORY ${ASTERISK_LIBRARY_DIR}/../etc/asterisk/) 80 | SET(ASTERISK_ETC_DIR ${ASTERISK_LIBRARY_DIR}/../etc/asterisk/) 81 | else(IS_DIRECTORY ${ASTERISK_LIBRARY_DIR}/../etc/asterisk/) 82 | SET(ASTERISK_ETC_DIR /etc/asterisk) 83 | endif() 84 | SET(ASTERISK_MOD_DIR ${ASTERISK_LIBRARY_DIR}/asterisk/modules/) 85 | 86 | MESSAGE("-- Set ASTERISK_INCLUDE_DIR = ${ASTERISK_INCLUDE_DIR}") 87 | MESSAGE("-- Set ASTERISK_LIBRARY_DIR = ${ASTERISK_LIBRARY_DIR}") 88 | CONFIGURE_FILE ("${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/config.h" ) 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Welcome the Redis Plugin for Asterisk 2 | 3 | Travis Continues Integration Status: [![Travis](http://img.shields.io/travis/dkgroot/ast_redis.svg?style=flat)](https://travis-ci.org/dkgroot/ast_redis) 4 | Code-Coverage: [![Coverage Status](https://coveralls.io/repos/dkgroot/ast_redis/badge.svg)](https://coveralls.io/r/dkgroot/ast_redis) 5 | 6 | This project provides three modules to plugin to asterisk, namely: 7 | - res_redis (Partially Working (Not TheadSafe) / Currently Work in progress / Asterisk-11 for now) 8 | provising distributed devstate between asterisk cluster nodes via redis Pub/Sub 9 | - cdr_redis (To Be Done) 10 | will provide posting of cdr events to a redis database 11 | - res_config_redis (To be Done) 12 | will add realtime database capability via redis. 13 | - started work on replacement res_redis_v1 14 | 15 | ### Prerequisites 16 | - cmake 17 | - libevent 18 | - hiredis >= 0.11 19 | 20 | ### Configuring 21 | checkout the github repository 22 | mkdir build 23 | cd build 24 | cmake .. 25 | 26 | ### Build and Install 27 | make 28 | make install 29 | 30 | - - - 31 | -------------------------------------------------------------------------------- /cdr_redis/cdr_redis.c: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | #include 6 | 7 | ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | -------------------------------------------------------------------------------- /conf/res_redis.conf: -------------------------------------------------------------------------------- 1 | ; 2 | ; Sample configuration file for res_corosync. 3 | ; 4 | ; This module allows events to be shared amongst a local cluster of 5 | ; Asterisk servers. Specifically, the types of events that may be 6 | ; shared include: 7 | ; 8 | ; - Device State (for shared presence information) 9 | ; 10 | ; - Message Waiting Indication, or MWI (to allow Voicemail to live on 11 | ; a server that is different from where the phones are registered) 12 | ; 13 | ; For more information about Corosync, see: http://www.corosync.org/ 14 | ; 15 | 16 | [general] 17 | ; 18 | ; One or more redis server to connect to. Will be tried in order, untill connection is established 19 | ; 20 | servers = 127.0.0.1:6379, 10.15.15.195:6379, /var/run/redis/redis.sock 21 | 22 | ;serialization_mode = [base64, json, xml] ; to be implemented 23 | 24 | ; 25 | ; MWI Events 26 | ; 27 | ;mwi_prefix = 31 28 | ; 29 | ; Publish Message Waiting Indication (MWI) events from this server to the cluster. 30 | publish_mwi_event = asterisk:mwi 31 | ; 32 | ; Subscribe to MWI events from the cluster. 33 | subscribe_mwi_event = asterisk:mwi 34 | 35 | ; 36 | : DeviceStateChange Events 37 | ; 38 | ;devicestate_prefix_change = 31 39 | ; 40 | ; Publish Device State Change (presence) events from this server to the cluster. 41 | publish_devicestate_change_event = asterisk:device_state_change 42 | 43 | ; 44 | ; Subscribe to Device State Change (presence) events from the cluster. 45 | subscribe_devicestate_change_event = asterisk:device_state_change 46 | 47 | ; 48 | ; DeviceState Events 49 | ; 50 | ;devicestate_prefix = 31 51 | ; 52 | ; Publish Device State events from this server to the cluster. 53 | publish_devicestate_event = asterisk:device_state 54 | 55 | ; 56 | ; Subscribe to Device State events from the cluster. 57 | subscribe_devicestate_event = asterisk:device_state 58 | 59 | 60 | -------------------------------------------------------------------------------- /conf/res_redis_v1.conf: -------------------------------------------------------------------------------- 1 | ; 2 | ; Sample configuration file for res_redis. 3 | ; 4 | ; This module allows events to be shared amongst a local cluster of 5 | ; Asterisk servers via one or more redis servers. 6 | ; Specifically, the types of events that may be shared include: 7 | ; 8 | ; - Message Waiting Indication, or MWI (to allow Voicemail to live on 9 | ; a server that is different from where the phones are registered) 10 | ; 11 | ; - Device Change State 12 | ; 13 | ; - Device State (for shared presence information) 14 | ; 15 | 16 | [general] 17 | server = 127.0.0.1:6379 ; One or more redis servers to connect to. Will be tried in order, until connection is established 18 | server = 10.15.15.195:6379 ; Can be either uri 19 | server = /var/run/redis/redis.sock ; or socket path 20 | 21 | [mwi] 22 | publish = true ; Required [True/False]: state will be published 23 | subscribe = true ; Required [True/False]: will listen for state changes 24 | channel = asterisk:mwi ; Required [String]: channel to listen on for mwi messages 25 | device_prefix = 31 ; Optional [String]: The Device string will be rewritten by inserting the prefix; ie: SCCP/98031 will become SCCP/3198031 26 | dump_state_table_on_connection = true 27 | 28 | [device_state_change] 29 | publish = true ; Required [True/False]: state will be published 30 | subscribe = true ; Required [True/False]: will listen for state changes 31 | channel = asterisk:device_state_change ; Required [String]: channel to listen on for device_state_change messages 32 | device_prefix = 31 ; Optional [String]: The Device string will be rewritten by inserting the prefix; ie: SCCP/98031 will become SCCP/3198031 33 | dump_state_table_on_connection = true 34 | 35 | [device_state] 36 | publish = true ; Required [True/False]: state will be published 37 | subscribe = true ; Required [True/False]: will listen for state changes 38 | channel = asterisk:device_state ; Required [String]: channel to listen on for device_state messages 39 | device_prefix = 31 ; Optional [String]: The Device string will be rewritten by inserting the prefix; ie: SCCP/98031 will become SCCP/3198031 40 | dump_state_table_on_connection = true 41 | 42 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------------- 2 | * This file is autogenerated from config.h.in 3 | * during the cmake configuration of your project. If you need to make changes 4 | * edit the original file NOT THIS FILE. 5 | * --------------------------------------------------------------------------*/ 6 | #ifndef _CONFIGURATION_HEADER_GUARD_H_ 7 | #define _CONFIGURATION_HEADER_GUARD_H_ 8 | 9 | #cmakedefine HAVE_PBX_AST_VERSION_H @HAVE_PBX_AST_VERSION_H@ 10 | #cmakedefine HAVE_PBX_VERSION_H @HAVE_PBX_VERSION_H@ 11 | #cmakedefine HAVE_PBX_ASTERISK_H @HAVE_PBX_ASTERISK_H@ 12 | #cmakedefine HAVE_PBX_LOCK_H @HAVE_PBX_LOCK_H@ 13 | #cmakedefine HAVE_PBX_EVENT_H @HAVE_PBX_EVENT_H@ 14 | #cmakedefine HAVE_PBX_STASIS_H @HAVE_PBX_STASIS_H@ 15 | #cmakedefine HAVE_PBX_VERSION_11 @HAVE_PBX_VERSION_11@ 16 | #cmakedefine HAVE_PBX_VERSION_12 @HAVE_PBX_VERSION_12@ 17 | //#cmakedefine HAVE_PBX_VERSION_13 @HAVE_PBX_VERSION_13@ 18 | //#define SIZEOF_INT @SIZEOF_INT@ 19 | 20 | // fix clang against asterisk inline issue 21 | #ifdef __clang__ 22 | #define LOW_MEMORY 23 | #endif 24 | 25 | #endif -------------------------------------------------------------------------------- /include/message_queue_pubsub.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * res_redis -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2015, Diederik de Groot 5 | * 6 | * Diederik de Groot 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | #ifndef _MESSAGE_QUEUE_PUBSUB_H_ 13 | #define _MESSAGE_QUEUE_PUBSUB_H_ 14 | 15 | //#include 16 | #include "../include/shared.h" 17 | 18 | /* 19 | * declarations 20 | */ 21 | typedef enum { 22 | PUBLISH, 23 | SUBSCRIBE, 24 | } msq_type_t; 25 | 26 | typedef void (*msq_subscription_callback_t)(event_type_t msq_event, void *reply, void *privdata); 27 | typedef void (*msq_connection_callback_t)(int status); 28 | typedef void (*msq_command_callback_t)(void *reply, void *privdata); 29 | 30 | typedef struct msq_event_map msq_event_t; 31 | 32 | /* 33 | * public 34 | */ 35 | exception_t msq_add_server(const char *url, int port, const char *socket); 36 | void msq_list_servers(); 37 | exception_t msq_remove_all_servers(); 38 | 39 | exception_t msq_start(); 40 | exception_t msq_stop(); 41 | event_type_t msq_find_channel(const char *channelname); 42 | exception_t msq_set_channel(event_type_t channel, msq_type_t type, boolean_t onoff); 43 | exception_t msq_publish(event_type_t channel, const char *publishmsg); 44 | exception_t msq_add_subscription(event_type_t channel, const char *channelstr, const char *patternstr, msq_subscription_callback_t callback); 45 | void msq_list_subscriptions(); 46 | exception_t msq_drop_all_subscriptions(); 47 | exception_t msq_send_subscribe(event_type_t channel); 48 | exception_t msq_send_unsubscribe(event_type_t channel); 49 | 50 | exception_t msq_start_eventloop(); 51 | exception_t msq_stop_eventloop(); 52 | //send_command(...) 53 | 54 | #endif /* _MESSAGE_QUEUE_PUBSUB_H_ */ 55 | -------------------------------------------------------------------------------- /include/pbx_event_message_serializer.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * res_redis -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2015, Diederik de Groot 5 | * 6 | * Diederik de Groot 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | #ifndef _AST_EVENT_MESSAGE_SERIALIZER_H_ 13 | #define _AST_EVENT_MESSAGE_SERIALIZER_H_ 14 | 15 | #include "shared.h" 16 | 17 | #define MAX_JSON_BUFFERLEN 1024 18 | 19 | typedef void (*pbx_subscription_callback_t) (event_type_t event_type, char *data); 20 | typedef struct pbx_event_map pbx_event_map_t; 21 | 22 | exception_t pbx_subscribe(event_type_t event_type, pbx_subscription_callback_t callback); 23 | exception_t pbx_unsubscribe(event_type_t event_type); 24 | exception_t pbx_publish(event_type_t event_type, char *jsonmsgbuffer, size_t buf_len); 25 | 26 | /* should become private instead / to be removed*/ 27 | exception_t message2json(char *jsonmsgbuffer, const size_t msg_len, const struct ast_event *event); 28 | exception_t json2message(struct ast_event **eventref, enum ast_event_type event_type, const char *jsonmsgbuffer, boolean_t *cacheable); 29 | 30 | #endif /* _AST_EVENT_MESSAGE_SERIALIZER_H_ */ 31 | -------------------------------------------------------------------------------- /include/shared.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * res_redis -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2015, Diederik de Groot 5 | * 6 | * Diederik de Groot 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! 14 | * \file 15 | * \author Diederik de Groot 16 | * 17 | * This module is based on the res_corosync module. 18 | */ 19 | #ifndef _SHARED_GUARD_H_ 20 | #define _SHARED_GUARD_H_ 21 | 22 | #define ARRAY_LEN(a) (size_t) (sizeof(a) / sizeof(0[a])) 23 | typedef enum { FALSE = 0, TRUE = 1 } boolean_t; 24 | 25 | typedef enum exceptions { 26 | NO_EXCEPTION = 0, 27 | EID_SELF_EXCEPTION = 1, 28 | MALLOC_EXCEPTION = 100, 29 | LIBEVENT_EXCEPTION = 101, 30 | EXISTS_EXCEPTION = 102, 31 | DECODING_EXCEPTION = 103, 32 | REDIS_EXCEPTION = 104, 33 | GENERAL_EXCEPTION = 105, 34 | } exception_t; 35 | 36 | static struct { 37 | const char *str; 38 | } exception2str[] = { 39 | [NO_EXCEPTION] = {""}, 40 | [EID_SELF_EXCEPTION] = {"EID Same as our own"}, 41 | [MALLOC_EXCEPTION] = {"Malloc/Free Exception"}, 42 | [LIBEVENT_EXCEPTION] = {"LibEvent Exception"}, 43 | [EXISTS_EXCEPTION] = {"Already Exists Exception"}, 44 | [DECODING_EXCEPTION] = {"Decoding Exception"}, 45 | [REDIS_EXCEPTION] = {"Redis Exception"}, 46 | [GENERAL_EXCEPTION] = {"General Exception"}, 47 | }; 48 | 49 | /* 50 | enum returnvalues { 51 | ERROR = 0, 52 | EID_SELF = 1, 53 | OK = 2, 54 | OK_CACHABLE = 3, 55 | }; 56 | */ 57 | 58 | typedef enum event_type { 59 | EVENT_ALL = 0x00, 60 | EVENT_CUSTOM = 0x01, 61 | EVENT_MWI = 0x02, 62 | EVENT_SUB = 0x03, 63 | EVENT_UNSUB = 0x04, 64 | EVENT_DEVICE_STATE = 0x05, 65 | EVENT_DEVICE_STATE_CHANGE = 0x06, 66 | EVENT_CEL = 0x07, 67 | EVENT_SECURITY = 0x08, 68 | EVENT_NETWORK_CHANGE = 0x09, 69 | EVENT_PRESENCE_STATE = 0x0a, 70 | EVENT_ACL_CHANGE = 0x0b, 71 | EVENT_PING = 0x0c, 72 | EVENT_TOTAL = 0x0d, 73 | } event_type_t; 74 | 75 | /* pipe logging back to asterisk */ 76 | void _log_verbose(int level, const char *file, int line, const char *function, const char *fmt, ...) __attribute__((format(printf, 5, 6))); 77 | 78 | #define log_debug(...) _log_verbose(4, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__) 79 | #define log_verbose(_level, ...) _log_verbose(_level, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__) 80 | /* end logging */ 81 | 82 | /* */ 83 | #endif /* _SHARED_GUARD_H_ */ 84 | -------------------------------------------------------------------------------- /lib/ast_event_message_serializer.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * res_redis -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2015, Diederik de Groot 5 | * 6 | * Diederik de Groot 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | #include "config.h" 13 | 14 | #include 15 | 16 | #define AST_MODULE "res_redis" 17 | 18 | ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") 19 | #include 20 | #include 21 | #include 22 | 23 | #include "../include/pbx_event_message_serializer.h" 24 | #include "../include/shared.h" 25 | /* 26 | * declaration 27 | */ 28 | static void ast_event_cb(const struct ast_event *event, void *data); 29 | 30 | 31 | /* 32 | * globals 33 | */ 34 | AST_RWLOCK_DEFINE_STATIC(event_map_lock); 35 | 36 | struct pbx_event_map { 37 | const int ast_event_type; 38 | const char *name; 39 | struct ast_event_sub *sub; 40 | boolean_t publish; 41 | boolean_t subscribe; 42 | boolean_t dump_state_table_on_connection; 43 | pbx_subscription_callback_t callback; 44 | }; 45 | static pbx_event_map_t event_map[AST_EVENT_TOTAL] = { 46 | [EVENT_MWI] = {.ast_event_type = AST_EVENT_MWI, .name = "mwi"}, 47 | [EVENT_DEVICE_STATE_CHANGE] = {.ast_event_type = AST_EVENT_DEVICE_STATE_CHANGE, .name = "device_state_change"}, 48 | [EVENT_DEVICE_STATE] = {.ast_event_type = AST_EVENT_DEVICE_STATE, .name = "device_state"}, 49 | [EVENT_PING] = {.ast_event_type = AST_EVENT_PING, .name = "ping"}, 50 | }; 51 | 52 | /* 53 | * public 54 | */ 55 | exception_t pbx_subscribe(event_type_t event_type, pbx_subscription_callback_t callback) 56 | { 57 | exception_t res = GENERAL_EXCEPTION; 58 | log_verbose(2, "PBX: Enter (%s)\n", __PRETTY_FUNCTION__); 59 | ast_rwlock_rdlock(&event_map_lock); 60 | if (event_map[event_type].sub) { 61 | pbx_unsubscribe(event_type); 62 | } else { 63 | event_map[event_type].callback = callback; 64 | event_map[event_type].sub = ast_event_subscribe_new(event_map[event_type].ast_event_type, ast_event_cb, &event_type); 65 | res = NO_EXCEPTION; 66 | } 67 | ast_rwlock_unlock(&event_map_lock); 68 | log_verbose(2, "Redis: Exit %s%s\n", res ? ", Exception Occured: " : "", res ? exception2str[res].str : ""); 69 | return res; 70 | } 71 | 72 | exception_t pbx_unsubscribe(event_type_t event_type) 73 | { 74 | exception_t res = GENERAL_EXCEPTION; 75 | ast_rwlock_rdlock(&event_map_lock); 76 | if (!event_map[event_type].sub) { 77 | // not subscribed error 78 | } else { 79 | event_map[event_type].sub = ast_event_unsubscribe((struct ast_event_sub *)event_map[event_type].sub); 80 | event_map[event_type].callback = NULL; 81 | res = NO_EXCEPTION; 82 | } 83 | ast_rwlock_unlock(&event_map_lock); 84 | return res; 85 | } 86 | 87 | exception_t pbx_publish(event_type_t event_type, char *jsonmsgbuffer, size_t buf_len) 88 | { 89 | exception_t res = GENERAL_EXCEPTION; 90 | return res; 91 | } 92 | 93 | /* 94 | * private 95 | */ 96 | static void ast_event_cb(const struct ast_event *event, void *data) { 97 | event_type_t event_type = (event_type_t)data; 98 | char *jsonbuffer = NULL; 99 | if (!(jsonbuffer = malloc(MAX_JSON_BUFFERLEN))) { 100 | // malloc error 101 | } 102 | if (message2json(jsonbuffer, MAX_JSON_BUFFERLEN, event)) { 103 | event_map[event_type].callback(event_type, jsonbuffer); 104 | } else { 105 | // error 106 | } 107 | ast_free(jsonbuffer); 108 | } 109 | 110 | /* copied from asterisk/event.c */ 111 | struct ast_event { 112 | /*! Event type */ 113 | enum ast_event_type type:16; 114 | /*! Total length of the event */ 115 | uint16_t event_len:16; 116 | /*! The data payload of the event, made up of information elements */ 117 | unsigned char payload[0]; 118 | } __attribute__((packed)); 119 | 120 | static const struct ie_map { 121 | enum ast_event_ie_pltype ie_pltype; 122 | const char *name; 123 | } ie_maps[AST_EVENT_IE_TOTAL] = { 124 | [AST_EVENT_IE_NEWMSGS] = { AST_EVENT_IE_PLTYPE_UINT, "NewMessages" }, // 0x0001 125 | [AST_EVENT_IE_OLDMSGS] = { AST_EVENT_IE_PLTYPE_UINT, "OldMessages" }, 126 | [AST_EVENT_IE_MAILBOX] = { AST_EVENT_IE_PLTYPE_STR, "Mailbox" }, 127 | [AST_EVENT_IE_UNIQUEID] = { AST_EVENT_IE_PLTYPE_UINT, "UniqueID" }, 128 | [AST_EVENT_IE_EVENTTYPE] = { AST_EVENT_IE_PLTYPE_UINT, "EventType" }, 129 | [AST_EVENT_IE_EXISTS] = { AST_EVENT_IE_PLTYPE_UINT, "Exists" }, 130 | [AST_EVENT_IE_DEVICE] = { AST_EVENT_IE_PLTYPE_STR, "Device" }, 131 | [AST_EVENT_IE_STATE] = { AST_EVENT_IE_PLTYPE_UINT, "State" }, 132 | [AST_EVENT_IE_CONTEXT] = { AST_EVENT_IE_PLTYPE_STR, "Context" }, 133 | [AST_EVENT_IE_EID] = { AST_EVENT_IE_PLTYPE_RAW, "EntityID" }, 134 | [AST_EVENT_IE_CEL_EVENT_TYPE] = { AST_EVENT_IE_PLTYPE_UINT, "CELEventType" }, 135 | [AST_EVENT_IE_CEL_EVENT_TIME] = { AST_EVENT_IE_PLTYPE_UINT, "CELEventTime" }, 136 | [AST_EVENT_IE_CEL_EVENT_TIME_USEC] = { AST_EVENT_IE_PLTYPE_UINT, "CELEventTimeUSec" }, 137 | [AST_EVENT_IE_CEL_USEREVENT_NAME] = { AST_EVENT_IE_PLTYPE_UINT, "CELUserEventName" }, 138 | [AST_EVENT_IE_CEL_CIDNAME] = { AST_EVENT_IE_PLTYPE_STR, "CELCIDName" }, 139 | [AST_EVENT_IE_CEL_CIDNUM] = { AST_EVENT_IE_PLTYPE_STR, "CELCIDNum" }, 140 | [AST_EVENT_IE_CEL_EXTEN] = { AST_EVENT_IE_PLTYPE_STR, "CELExten" }, 141 | [AST_EVENT_IE_CEL_CONTEXT] = { AST_EVENT_IE_PLTYPE_STR, "CELContext" }, 142 | [AST_EVENT_IE_CEL_CHANNAME] = { AST_EVENT_IE_PLTYPE_STR, "CELChanName" }, 143 | [AST_EVENT_IE_CEL_APPNAME] = { AST_EVENT_IE_PLTYPE_STR, "CELAppName" }, 144 | [AST_EVENT_IE_CEL_APPDATA] = { AST_EVENT_IE_PLTYPE_STR, "CELAppData" }, 145 | [AST_EVENT_IE_CEL_AMAFLAGS] = { AST_EVENT_IE_PLTYPE_STR, "CELAMAFlags" }, 146 | [AST_EVENT_IE_CEL_ACCTCODE] = { AST_EVENT_IE_PLTYPE_UINT, "CELAcctCode" }, 147 | [AST_EVENT_IE_CEL_UNIQUEID] = { AST_EVENT_IE_PLTYPE_STR, "CELUniqueID" }, 148 | [AST_EVENT_IE_CEL_USERFIELD] = { AST_EVENT_IE_PLTYPE_STR, "CELUserField" }, 149 | [AST_EVENT_IE_CEL_CIDANI] = { AST_EVENT_IE_PLTYPE_STR, "CELCIDani" }, 150 | [AST_EVENT_IE_CEL_CIDRDNIS] = { AST_EVENT_IE_PLTYPE_STR, "CELCIDrdnis" }, 151 | [AST_EVENT_IE_CEL_CIDDNID] = { AST_EVENT_IE_PLTYPE_STR, "CELCIDdnid" }, 152 | [AST_EVENT_IE_CEL_PEER] = { AST_EVENT_IE_PLTYPE_STR, "CELPeer" }, 153 | [AST_EVENT_IE_CEL_LINKEDID] = { AST_EVENT_IE_PLTYPE_STR, "CELLinkedID" }, 154 | [AST_EVENT_IE_CEL_PEERACCT] = { AST_EVENT_IE_PLTYPE_STR, "CELPeerAcct" }, 155 | [AST_EVENT_IE_CEL_EXTRA] = { AST_EVENT_IE_PLTYPE_STR, "CELExtra" }, 156 | [AST_EVENT_IE_SECURITY_EVENT] = { AST_EVENT_IE_PLTYPE_STR, "SecurityEvent" }, 157 | [AST_EVENT_IE_EVENT_VERSION] = { AST_EVENT_IE_PLTYPE_UINT, "EventVersion" }, 158 | [AST_EVENT_IE_SERVICE] = { AST_EVENT_IE_PLTYPE_STR, "Service" }, 159 | [AST_EVENT_IE_MODULE] = { AST_EVENT_IE_PLTYPE_STR, "Module" }, 160 | [AST_EVENT_IE_ACCOUNT_ID] = { AST_EVENT_IE_PLTYPE_STR, "AccountID" }, 161 | [AST_EVENT_IE_SESSION_ID] = { AST_EVENT_IE_PLTYPE_STR, "SessionID" }, 162 | [AST_EVENT_IE_SESSION_TV] = { AST_EVENT_IE_PLTYPE_STR, "SessionTV" }, 163 | [AST_EVENT_IE_ACL_NAME] = { AST_EVENT_IE_PLTYPE_STR, "ACLName" }, 164 | [AST_EVENT_IE_LOCAL_ADDR] = { AST_EVENT_IE_PLTYPE_STR, "LocalAddress" }, 165 | [AST_EVENT_IE_REMOTE_ADDR] = { AST_EVENT_IE_PLTYPE_STR, "RemoteAddress" }, 166 | [AST_EVENT_IE_EVENT_TV] = { AST_EVENT_IE_PLTYPE_STR, "EventTV" }, 167 | [AST_EVENT_IE_REQUEST_TYPE] = { AST_EVENT_IE_PLTYPE_STR, "RequestType" }, 168 | [AST_EVENT_IE_REQUEST_PARAMS] = { AST_EVENT_IE_PLTYPE_STR, "RequestParams" }, 169 | [AST_EVENT_IE_AUTH_METHOD] = { AST_EVENT_IE_PLTYPE_STR, "AuthMethod" }, 170 | [AST_EVENT_IE_SEVERITY] = { AST_EVENT_IE_PLTYPE_STR, "Severity" }, 171 | [AST_EVENT_IE_EXPECTED_ADDR] = { AST_EVENT_IE_PLTYPE_STR, "ExpectedAddress" }, 172 | [AST_EVENT_IE_CHALLENGE] = { AST_EVENT_IE_PLTYPE_STR, "Challenge" }, 173 | [AST_EVENT_IE_RESPONSE] = { AST_EVENT_IE_PLTYPE_STR, "Response" }, 174 | [AST_EVENT_IE_EXPECTED_RESPONSE] = { AST_EVENT_IE_PLTYPE_STR, "ExpectedResponse" }, 175 | [AST_EVENT_IE_RECEIVED_CHALLENGE] = { AST_EVENT_IE_PLTYPE_STR, "ReceivedChallenge" }, 176 | [AST_EVENT_IE_RECEIVED_HASH] = { AST_EVENT_IE_PLTYPE_STR, "ReceivedHash" }, 177 | [AST_EVENT_IE_USING_PASSWORD] = { AST_EVENT_IE_PLTYPE_UINT, "UsingPassword" }, 178 | [AST_EVENT_IE_ATTEMPTED_TRANSPORT] = { AST_EVENT_IE_PLTYPE_STR, "AttemptedTransport" }, 179 | [AST_EVENT_IE_CACHABLE] = { AST_EVENT_IE_PLTYPE_UINT, "Cachable" }, 180 | [AST_EVENT_IE_PRESENCE_PROVIDER] = { AST_EVENT_IE_PLTYPE_STR, "PresenceProvider" }, 181 | [AST_EVENT_IE_PRESENCE_STATE] = { AST_EVENT_IE_PLTYPE_UINT, "PresenceState" }, 182 | [AST_EVENT_IE_PRESENCE_SUBTYPE] = { AST_EVENT_IE_PLTYPE_STR, "PresenceSubtype" }, 183 | [AST_EVENT_IE_PRESENCE_MESSAGE] = { AST_EVENT_IE_PLTYPE_STR, "PresenceMessage" }, 184 | }; 185 | /* end copy */ 186 | 187 | /* Fix: required because of broken _ast_event_str_to_ie_type implementation */ 188 | int fixed_ast_event_str_to_ie_type(const char *str, enum ast_event_ie_type *ie_type) 189 | { 190 | int i; 191 | //for (i = 0; i < ARRAY_LEN(ie_maps); i++) { // broken, should start at 0x0001 192 | for (i = 1; i < ARRAY_LEN(ie_maps); i++) { 193 | if (!ie_maps[i].name) { 194 | continue; 195 | } 196 | if (strcasecmp(ie_maps[i].name, str)) { 197 | continue; 198 | } 199 | *ie_type = i; 200 | return 0; 201 | } 202 | 203 | return -1; 204 | } 205 | /* End Fix */ 206 | 207 | 208 | inline static void trim_char_bothends(char *inout, char chr) 209 | { 210 | if (!chr || inout[0] == chr) { 211 | memmove(inout+0, inout+1, strlen(inout)); // strip first 212 | } 213 | if (!chr || inout[strlen(inout)-1] == chr) { 214 | inout[strlen(inout)-1] = '\0'; // strip last 215 | } 216 | } 217 | 218 | /* generic ast_event to json encode */ 219 | exception_t message2json(char *msg, const size_t msg_len, const struct ast_event *event) 220 | { 221 | exception_t res = DECODING_EXCEPTION; 222 | unsigned int curpos = 1; 223 | memset(msg, 0, msg_len); 224 | msg[0] = '{'; 225 | 226 | struct ast_event_iterator i; 227 | if (ast_event_iterator_init(&i, event)) { 228 | ast_log(LOG_ERROR, "Failed to initialize event iterator. :-(\n"); 229 | return res; 230 | } 231 | ast_debug(1, "Encoding Event: %s\n", ast_event_get_type_name(event)); 232 | do { 233 | enum ast_event_ie_type ie_type; 234 | enum ast_event_ie_pltype ie_pltype; 235 | const char *ie_type_name; 236 | ie_type = ast_event_iterator_get_ie_type(&i); 237 | ie_type_name = ast_event_get_ie_type_name(ie_type); 238 | ie_pltype = ast_event_get_ie_pltype(ie_type); 239 | 240 | ast_debug(1, "iteration: %d, %s, %d\n", ie_type, ie_type_name, ie_pltype); 241 | switch (ie_pltype) { 242 | case AST_EVENT_IE_PLTYPE_UNKNOWN: 243 | case AST_EVENT_IE_PLTYPE_EXISTS: 244 | snprintf(msg + curpos, msg_len - curpos, "\"%s\":\"exists\",", ie_type_name); 245 | break; 246 | case AST_EVENT_IE_PLTYPE_STR: 247 | snprintf(msg + curpos, msg_len - curpos, "\"%s\":\"%s\",", ie_type_name, ast_event_iterator_get_ie_str(&i)); 248 | break; 249 | case AST_EVENT_IE_PLTYPE_UINT: 250 | snprintf(msg + curpos, msg_len - curpos, "\"%s\":%u,", ie_type_name, ast_event_iterator_get_ie_uint(&i)); 251 | curpos = strlen(msg); 252 | if (ie_type == AST_EVENT_IE_STATE) { 253 | snprintf(msg + curpos, msg_len - curpos, "\"statestr\":\"%s\",", ast_devstate_str(ast_event_iterator_get_ie_uint(&i))); 254 | } 255 | break; 256 | case AST_EVENT_IE_PLTYPE_BITFLAGS: 257 | snprintf(msg + curpos, msg_len - curpos, "\"%s\":%u,", ie_type_name, ast_event_iterator_get_ie_bitflags(&i)); 258 | break; 259 | case AST_EVENT_IE_PLTYPE_RAW: 260 | if (ie_type == AST_EVENT_IE_EID) { 261 | char eid_buf[32]; 262 | ast_eid_to_str(eid_buf, sizeof(eid_buf), ast_event_iterator_get_ie_raw(&i)); 263 | snprintf(msg + curpos, msg_len - curpos, "\"%s\":\"%s\",", ast_event_get_ie_type_name(ie_type), eid_buf); 264 | } else { 265 | const void *rawbuf = ast_event_get_ie_raw(event, ie_type); 266 | snprintf(msg + curpos, msg_len - curpos, "\"%s\",", (unsigned char *)rawbuf); 267 | } 268 | break; 269 | } 270 | ast_debug(1, "encoded string: '%s'\n", msg); 271 | curpos = strlen(msg); 272 | } while (!ast_event_iterator_next(&i)); 273 | 274 | // replace the last comma with '}' instead 275 | msg[curpos-1] = '}'; 276 | 277 | ast_debug(1, "encoded string: '%s'\n", msg); 278 | return NO_EXCEPTION; 279 | } 280 | 281 | /* generic json to ast_event decoder */ 282 | exception_t json2message(struct ast_event **eventref, enum ast_event_type event_type, const char *msg, boolean_t *cacheable) 283 | { 284 | exception_t res = DECODING_EXCEPTION; 285 | struct ast_event *event = *eventref; 286 | struct ast_eid eid; 287 | char *tokenstr = strdupa(msg); 288 | trim_char_bothends(tokenstr,0); 289 | 290 | char *entry = NULL; 291 | char *key = NULL; 292 | char *value = NULL; 293 | char delims[]=","; 294 | int cache = 0;; 295 | 296 | // if (!(event = ast_event_new(event_type, AST_EVENT_IE_END))) { /* can't use this because it automatically adds my local EID to the new event */ 297 | // return DECODING_EXCEPTION; 298 | // } 299 | if (!(event = ast_calloc(1, sizeof(*event)))) { /* resorting to local copy of ast_event structure :-( */ 300 | return MALLOC_EXCEPTION; 301 | } 302 | event->type = htons(event_type); 303 | event->event_len = htons(sizeof(*event)); 304 | 305 | ast_debug(1, "Decoding Msg2Event %s, content: '%s'\n", ast_event_get_type_name(event), tokenstr); 306 | entry = strtok(tokenstr, delims); 307 | while (entry) { 308 | value = strdupa(entry); 309 | key = strsep(&value, ":"); 310 | trim_char_bothends(value, '"'); 311 | trim_char_bothends(key, '"'); 312 | ast_debug(1, "Key: %s, Value: %s\n", key, value); 313 | 314 | enum ast_event_ie_type ie_type; 315 | enum ast_event_ie_pltype ie_pltype; 316 | const char *ie_type_name; 317 | if (!fixed_ast_event_str_to_ie_type(key, &ie_type)){ 318 | ie_pltype = ast_event_get_ie_pltype(ie_type); 319 | ie_type_name = ast_event_get_ie_type_name(ie_type); 320 | 321 | ast_debug(1, "Dealing with %s\n", ie_type_name); 322 | 323 | switch(ie_pltype) { 324 | case AST_EVENT_IE_PLTYPE_UNKNOWN: 325 | break; 326 | case AST_EVENT_IE_PLTYPE_EXISTS: 327 | ast_event_append_ie_uint(&event, AST_EVENT_IE_EXISTS, atoi(value)); 328 | break; 329 | case AST_EVENT_IE_PLTYPE_UINT: 330 | if (ie_type == AST_EVENT_IE_CACHABLE) { 331 | cache = atoi(value); 332 | } 333 | ast_event_append_ie_uint(&event, ie_type, atoi(value)); 334 | break; 335 | case AST_EVENT_IE_PLTYPE_BITFLAGS: 336 | ast_event_append_ie_bitflags(&event, ie_type, atoi(value)); 337 | break; 338 | case AST_EVENT_IE_PLTYPE_STR: 339 | ast_event_append_ie_str(&event, ie_type, value); 340 | break; 341 | case AST_EVENT_IE_PLTYPE_RAW: 342 | if (ie_type == AST_EVENT_IE_EID) { 343 | ast_str_to_eid(&eid, value); 344 | if (!ast_eid_cmp(&ast_eid_default, &eid)) { 345 | // Don't feed events back in that originated locally. Quit now. 346 | res = EID_SELF_EXCEPTION; 347 | goto failed; 348 | } 349 | ast_event_append_ie_raw(&event, ie_type, &eid, sizeof(eid)); 350 | } else { 351 | ast_event_append_ie_raw(&event, ie_type, value, strlen(value)); 352 | } 353 | break; 354 | } 355 | /* realloc inside one of the append functions failed */ 356 | if (!event) { 357 | return DECODING_EXCEPTION; 358 | } 359 | } 360 | entry = strtok(NULL, delims); 361 | } 362 | 363 | if (!ast_event_get_ie_raw(event, AST_EVENT_IE_EID)) { 364 | ast_event_append_eid(&event); 365 | } 366 | 367 | ast_debug(1, "decoded msg into event\n"); 368 | *cacheable = cache ? TRUE : FALSE; 369 | *eventref = event; 370 | return NO_EXCEPTION; 371 | 372 | failed: 373 | ast_event_destroy(event); 374 | return res; 375 | } 376 | -------------------------------------------------------------------------------- /lib/ast_stasis_message_serializer.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * res_redis -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2015, Diederik de Groot 5 | * 6 | * Diederik de Groot 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | #include "config.h" 13 | 14 | #ifdef HAVE_PBX_STASIS_H 15 | #include 16 | 17 | #define AST_MODULE "res_redis" 18 | 19 | ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "../include/pbx_event_message_serializer.h" 27 | #include "../include/shared.h" 28 | 29 | /* 30 | * declaration 31 | */ 32 | static void ast_event_cb(const struct ast_event *event, void *data); 33 | 34 | /* 35 | * globals 36 | */ 37 | AST_RWLOCK_DEFINE_STATIC(event_map_lock); 38 | 39 | struct pbx_event_map { 40 | int ast_event_type; 41 | const char *name; 42 | struct stasis_subscription *sub; 43 | pbx_subscription_callback_t *callback; 44 | }; 45 | static pbx_event_map_t event_map[AST_EVENT_TOTAL] = { 46 | [EVENT_MWI] = {.ast_event_type = AST_EVENT_MWI, .name = "mwi"}, 47 | [EVENT_DEVICE_STATE_CHANGE] = {.ast_event_type = AST_EVENT_DEVICE_STATE_CHANGE, .name = "device_state_change"}, 48 | [EVENT_DEVICE_STATE] = {.ast_event_type = AST_EVENT_DEVICE_STATE, .name = "device_state"}, 49 | [EVENT_PING] = {.ast_event_type = AST_EVENT_PING, .name = "ping"}, 50 | }; 51 | 52 | /* 53 | * public 54 | */ 55 | int pbx_subscribe(event_type_t event_type, pbx_subscription_callback *callback) 56 | { 57 | /* ast_rwlock_rdlock(&event_map_lock); 58 | if (event_map[event_type].sub) { 59 | pbx_unsubscribe(event_type); 60 | } else { 61 | event_map[event_type].callback = callback; 62 | event_map[event_type].sub = ast_event_subscribe_new(event_map[event_type].ast_event_type, ast_event_cb, &event_type); 63 | } 64 | ast_rwlock_unlock(&event_map_lock); 65 | return 0;*/ 66 | return -1; 67 | } 68 | 69 | int pbx_unsubscribe(event_type_t event_type) 70 | { 71 | /* ast_rwlock_rdlock(&event_map_lock); 72 | if (!event_map[event_type].sub) { 73 | // not subscribed error 74 | } else { 75 | event_map[event_type].sub = ast_event_unsubscribe((struct ast_event_sub *)event_map[event_type].sub); 76 | event_map[event_type].callback = NULL; 77 | } 78 | ast_rwlock_unlock(&event_map_lock); 79 | return 0;*/ 80 | return -1; 81 | } 82 | 83 | int pbx_publish(event_type_t event_type, char *jsonmsgbuffer, size_t buf_len) 84 | { 85 | return -1; 86 | } 87 | 88 | /* 89 | * private 90 | */ 91 | static void ast_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *smsg); 92 | event_type_t event_type = (event_type_t)data; 93 | /* char *jsonbuffer = NULL; 94 | if (!(jsonbuffer = malloc(MAX_JSON_BUFFERLEN))) { 95 | // malloc error 96 | } 97 | if (message2json(jsonbuffer, MAX_JSON_BUFFERLEN, event)) { 98 | event_map[event_type].callback(event_type, jsonbuffer); 99 | } else { 100 | // error 101 | } 102 | ast_free(jsonbuffer);*/ 103 | } 104 | 105 | #endif -------------------------------------------------------------------------------- /lib/msq_redis.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * res_redis -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2015, Diederik de Groot 5 | * 6 | * Diederik de Groot 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | #include "config.h" 13 | 14 | #include "../include/message_queue_pubsub.h" 15 | 16 | //#define _GNU_SOURCE 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "../include/shared.h" 27 | 28 | /* 29 | * declarations 30 | */ 31 | typedef struct msq_connection_map msq_connection_map_t; 32 | 33 | static void redis_ping_subscription_cb(event_type_t msq_event, void *reply, void *privdata); 34 | 35 | static exception_t msq_processRedisAsyncConnError(redisAsyncContext *Conn); 36 | exception_t _msq_remove_server(const char *url, int port, const char *socket); 37 | exception_t _msq_connect_to_next_server(); 38 | exception_t _msq_disconnect(); 39 | 40 | exception_t _msq_remove_subscription(event_type_t channel); 41 | exception_t _msq_toggle_subscriptions(boolean_t on); 42 | 43 | /* 44 | * global 45 | */ 46 | pthread_rwlock_t msq_event_map_rwlock = PTHREAD_RWLOCK_INITIALIZER; 47 | /* should move to res_redis/res_redis_v1.c */ 48 | struct msq_event_map { 49 | const char *name; 50 | boolean_t publish; 51 | boolean_t subscribe; 52 | boolean_t active; 53 | char *channel; 54 | char *pattern; 55 | msq_subscription_callback_t callback; 56 | }; 57 | static msq_event_t msq_event_map[] = { 58 | [EVENT_MWI] = {.name="mwi",}, 59 | [EVENT_DEVICE_STATE] = {.name="device_state",}, 60 | [EVENT_DEVICE_STATE_CHANGE] = {.name="device_state_change"}, 61 | [EVENT_PING] = {.name="ping", .publish=TRUE, .subscribe=TRUE, .active=FALSE, .channel="asterisk:ping", .pattern="", .callback=redis_ping_subscription_cb} 62 | }; 63 | 64 | enum connection_type { 65 | NONE, 66 | SOCKET, 67 | URL 68 | }; 69 | 70 | pthread_rwlock_t msq_server_rwlock = PTHREAD_RWLOCK_INITIALIZER; 71 | typedef struct msq_servers server_t; 72 | struct msq_servers { 73 | char *url; 74 | int port; 75 | char *socket; 76 | enum connection_type connection_type; 77 | redisAsyncContext *redisConn[2]; 78 | pthread_t thread; 79 | struct event_base *evloop_base; 80 | server_t *next; 81 | }; 82 | static server_t *servers_root = NULL; 83 | static server_t *current_server = NULL; 84 | 85 | pthread_mutex_t msq_startstop_mutex = PTHREAD_MUTEX_INITIALIZER; 86 | volatile boolean_t stopped; 87 | 88 | /* RAII LOCK */ 89 | static inline void unlock_rwlock(pthread_rwlock_t **lock) 90 | { 91 | if (*lock) { 92 | pthread_rwlock_unlock(*lock); 93 | } 94 | } 95 | #if __clang__ 96 | #define raii_rdlock(_x) {auto __attribute__((cleanup(unlock_rwlock))) pthread_rwlock_t *__rd_dtor##__LINE__ = _x; pthread_rwlock_rdlock(_x);((void*)__rd_dtor##__LINE__);} 97 | #define raii_wrlock(_x) {auto __attribute__((cleanup(unlock_rwlock))) pthread_rwlock_t *__wr_dtor##__LINE__ = _x; pthread_rwlock_wrlock(_x);((void*)__wr_dtor##__LINE__);} 98 | #else 99 | #define raii_rdlock(_x) {auto __attribute__((cleanup(unlock_rwlock))) pthread_rwlock_t *__rd_dtor##__LINE__ = _x; pthread_rwlock_rdlock(_x);} 100 | #define raii_wrlock(_x) {auto __attribute__((cleanup(unlock_rwlock))) pthread_rwlock_t *__wr_dtor##__LINE__ = _x; pthread_rwlock_wrlock(_x);} 101 | #endif 102 | /* END RAII */ 103 | 104 | /* 105 | * public 106 | */ 107 | exception_t msq_add_server(const char *url, int port, const char *socket) 108 | { 109 | log_verbose(2, "RedisMSQ: (%s) enter \n", __PRETTY_FUNCTION__); 110 | exception_t res = GENERAL_EXCEPTION; 111 | raii_wrlock(&msq_server_rwlock); 112 | server_t *server = servers_root; 113 | server_t *prevserver = server; 114 | 115 | // check exists 116 | while (server) { 117 | if ( (socket && server->socket && !strcasecmp(server->socket, socket)) || 118 | ((port && server->port && server->port == port) && 119 | (url && server->url && !strcasecmp(server->url, url))) 120 | ){ 121 | log_debug("RedisMSQ: Server Already Added: url:'%s', port:%d, socket:'%s'\n", url, port, socket); 122 | return EXISTS_EXCEPTION; 123 | } 124 | 125 | prevserver = server; 126 | server = server->next; 127 | } 128 | 129 | if (!server) { 130 | if (!(server = calloc(1, sizeof(server_t)))) { 131 | log_debug("RedisMSQ: Malloc Exception\n"); 132 | res = MALLOC_EXCEPTION; 133 | } 134 | if (port && url && strlen(url) > 0) { 135 | log_debug("RedisMSQ: Adding URL Based Connection to Server: [%s:%d]\n", url, port); 136 | server->url = strdup(url); 137 | server->port = port; 138 | server->connection_type = URL; 139 | res = NO_EXCEPTION; 140 | } else if (socket && strlen(socket) > 0) { 141 | log_debug("RedisMSQ: Adding Socket: [%s] Connection\n", socket); 142 | server->socket = strdup(socket); 143 | server->connection_type = SOCKET; 144 | res = NO_EXCEPTION; 145 | } else { 146 | log_debug("RedisMSQ: Failed to Add Server [url:%s / port:%d / socket:%s]\n", url, port, socket); 147 | free(server); 148 | server = NULL; 149 | } 150 | server->next = NULL; 151 | // tie the node into the list 152 | if (server) { 153 | if (servers_root == NULL) { 154 | servers_root = server; 155 | } else if (prevserver) { 156 | prevserver->next = server; 157 | } else { 158 | log_debug("RedisMSQ: Failed to Add Server to the list.\n"); 159 | free(server); 160 | server = NULL; 161 | res = GENERAL_EXCEPTION; 162 | } 163 | } 164 | } 165 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 166 | return res; 167 | } 168 | 169 | void msq_list_servers() 170 | { 171 | raii_rdlock(&msq_server_rwlock); 172 | server_t *server = servers_root; 173 | 174 | int index = 0; 175 | log_verbose(1,"+-------+--------+--------------------------------------------------------------+\n"); 176 | log_verbose(1,"| index | type | connection |\n"); 177 | log_verbose(1,"+-------|--------|--------------------------------------------------------------+\n"); 178 | while (server) { 179 | if (server->connection_type == SOCKET) { 180 | log_verbose(1,"| %5.5d | %6s | %60.60s |\n", index, "socket", server->socket); 181 | } else { 182 | char serverurlport [60]; 183 | snprintf(serverurlport, 60, "%s:%d", server->url, server->port); 184 | log_verbose(1,"| %5.5d | %6s | %60.60s |\n", index, "url", serverurlport); 185 | } 186 | index++; 187 | server = server->next; 188 | } 189 | log_verbose(1,"+-------+--------+--------------------------------------------------------------+\n"); 190 | } 191 | 192 | exception_t msq_remove_all_servers() 193 | { 194 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 195 | exception_t res = GENERAL_EXCEPTION; 196 | raii_wrlock(&msq_server_rwlock); 197 | server_t *nextserver = servers_root; 198 | 199 | while (servers_root) { 200 | nextserver = servers_root->next; 201 | if (servers_root->socket) { 202 | free(servers_root->socket); 203 | servers_root->socket = NULL; 204 | } 205 | if (servers_root->url) { 206 | free(servers_root->url); 207 | servers_root->port = 0; 208 | servers_root->url = NULL; 209 | } 210 | servers_root->connection_type = NONE; 211 | free(servers_root); 212 | servers_root = nextserver; 213 | res = NO_EXCEPTION; 214 | } 215 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 216 | return res; 217 | } 218 | 219 | exception_t _msq_remove_server(const char *url, int port, const char *socket) 220 | { 221 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 222 | exception_t res = GENERAL_EXCEPTION; 223 | raii_wrlock(&msq_server_rwlock); 224 | 225 | server_t *server = NULL; 226 | server_t *prevserver = NULL; 227 | server_t *node2bfreed = NULL; 228 | 229 | // check exists 230 | for (server = servers_root; server != NULL; server = server->next) { 231 | if ( (socket && server->socket && !strcasecmp(server->socket, socket)) || 232 | ((port && server->port && server->port == port) && 233 | (url && server->url && !strcasecmp(server->url, url))) 234 | ){ 235 | node2bfreed = server; 236 | break; 237 | } 238 | prevserver = server; 239 | } 240 | 241 | // found ? 242 | if (node2bfreed == NULL) { 243 | log_verbose(1, "server to be removed could not be found\n"); 244 | return res; 245 | } else if (prevserver == NULL) { 246 | servers_root = node2bfreed->next; 247 | } else { 248 | prevserver = node2bfreed->next; 249 | } 250 | 251 | // found -> free content 252 | if (server->socket) { 253 | free(server->socket); 254 | server->socket = NULL; 255 | } 256 | if (server->url) { 257 | free(server->url); 258 | server->url = NULL; 259 | } 260 | free(server); 261 | res = NO_EXCEPTION; 262 | 263 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 264 | return res; 265 | } 266 | 267 | static exception_t msq_processRedisAsyncConnError(redisAsyncContext *Conn) 268 | { 269 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 270 | if (Conn && Conn->err) { 271 | log_debug("RedisMSQ: Asynchronous Error: '%s'\n", Conn->errstr); 272 | return REDIS_EXCEPTION; 273 | } 274 | return NO_EXCEPTION; 275 | } 276 | 277 | exception_t _msq_connect_to_next_server() 278 | { 279 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 280 | exception_t res = NO_EXCEPTION; 281 | // check if connected 282 | if (current_server) { 283 | _msq_disconnect(); 284 | } 285 | 286 | raii_wrlock(&msq_server_rwlock); 287 | server_t *server = current_server; 288 | if (server && server->next) { 289 | current_server = current_server->next; 290 | } else { 291 | current_server = servers_root; 292 | } 293 | 294 | switch (current_server->connection_type) { 295 | case SOCKET: 296 | current_server->redisConn[PUBLISH] = redisAsyncConnectUnix(current_server->socket); 297 | res |= msq_processRedisAsyncConnError(current_server->redisConn[PUBLISH]); 298 | current_server->redisConn[SUBSCRIBE] = redisAsyncConnectUnix(current_server->socket); 299 | res |= msq_processRedisAsyncConnError(current_server->redisConn[SUBSCRIBE]); 300 | break; 301 | case URL: 302 | current_server->redisConn[PUBLISH] = redisAsyncConnect(current_server->url, current_server->port); 303 | res |= msq_processRedisAsyncConnError(current_server->redisConn[PUBLISH]); 304 | current_server->redisConn[SUBSCRIBE] = redisAsyncConnect(current_server->url, current_server->port); 305 | res |= msq_processRedisAsyncConnError(current_server->redisConn[SUBSCRIBE]); 306 | break; 307 | default: 308 | log_verbose(2, "RedisMSQ: Cannot hanfle connection_type\n"); 309 | } 310 | 311 | //res |= msq_start_eventloop(); 312 | res |= _msq_toggle_subscriptions(TRUE); 313 | 314 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 315 | return res; 316 | } 317 | 318 | exception_t _msq_disconnect() 319 | { 320 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 321 | exception_t res = NO_EXCEPTION; 322 | 323 | res |= _msq_toggle_subscriptions(FALSE); 324 | 325 | if (!res && current_server) { 326 | raii_wrlock(&msq_server_rwlock); 327 | if (current_server->redisConn[PUBLISH]) { 328 | // call general unsubscribe 329 | redisAsyncDisconnect(current_server->redisConn[PUBLISH]); 330 | res |= msq_processRedisAsyncConnError(current_server->redisConn[PUBLISH]); 331 | current_server->redisConn[PUBLISH] = NULL; 332 | } 333 | if (current_server->redisConn[SUBSCRIBE]) { 334 | // call general unsubscribe 335 | redisAsyncDisconnect(current_server->redisConn[SUBSCRIBE]); 336 | res |= msq_processRedisAsyncConnError(current_server->redisConn[SUBSCRIBE]); 337 | current_server->redisConn[SUBSCRIBE] = NULL; 338 | } 339 | //res |= msq_stop_eventloop(); 340 | } 341 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 342 | return res; 343 | } 344 | 345 | exception_t msq_start() 346 | { 347 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 348 | exception_t res = NO_EXCEPTION; 349 | 350 | pthread_mutex_lock(&msq_startstop_mutex); 351 | _msq_connect_to_next_server(); 352 | stopped = FALSE; 353 | pthread_mutex_unlock(&msq_startstop_mutex); 354 | 355 | return res; 356 | } 357 | 358 | exception_t msq_stop() 359 | { 360 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 361 | exception_t res = NO_EXCEPTION; 362 | 363 | pthread_mutex_lock(&msq_startstop_mutex); 364 | stopped = TRUE; 365 | _msq_disconnect(); 366 | pthread_mutex_unlock(&msq_startstop_mutex); 367 | return res; 368 | } 369 | 370 | /* should move to res_redis/res_redis_v1.c */ 371 | event_type_t msq_find_channel(const char *channelname) 372 | { 373 | event_type_t chan; 374 | raii_rdlock(&msq_event_map_rwlock); 375 | for (chan = 0; chan < ARRAY_LEN(msq_event_map); chan++ ) { 376 | if (msq_event_map[chan].name && !strcasecmp(channelname, msq_event_map[chan].name)) { 377 | return chan; 378 | } 379 | } 380 | return 0; 381 | } 382 | 383 | /* should move to res_redis/res_redis_v1.c */ 384 | exception_t msq_set_channel(event_type_t channel, msq_type_t type, boolean_t onoff) 385 | { 386 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 387 | if (msq_event_map[channel].name) { 388 | if (type == PUBLISH) { 389 | msq_event_map[channel].publish = onoff; 390 | } else if (type == SUBSCRIBE) { 391 | msq_event_map[channel].subscribe = onoff; 392 | } else { 393 | log_debug("Error: Unknown msq_type: [%d]", type); 394 | } 395 | } else { 396 | log_debug("Error: Unknown channel: [%d]", channel); 397 | } 398 | exception_t res = NO_EXCEPTION; 399 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 400 | return res; 401 | } 402 | 403 | /* should move to res_redis/res_redis_v1.c */ 404 | exception_t msq_add_subscription(event_type_t channel, const char *channelstr, const char *patternstr, msq_subscription_callback_t callback) 405 | { 406 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 407 | exception_t res = GENERAL_EXCEPTION; 408 | 409 | raii_wrlock(&msq_event_map_rwlock); 410 | if (msq_event_map[channel].name) { 411 | if (msq_event_map[channel].channel || msq_event_map[channel].callback || msq_event_map[channel].pattern) { 412 | // previous subscription 413 | log_debug("Error: previous subscription exists"); 414 | return res; 415 | } else { 416 | if (!channelstr || !callback) { 417 | log_debug("Error: add_subscription required both a channelstr and a callback"); 418 | return res; 419 | } 420 | if (channelstr) { 421 | msq_event_map[channel].channel = strdup(channelstr); 422 | } 423 | if (callback) { 424 | msq_event_map[channel].callback = callback; 425 | } 426 | if (patternstr) { 427 | msq_event_map[channel].pattern = strdup(patternstr); 428 | } 429 | msq_event_map[channel].active = FALSE; 430 | res = NO_EXCEPTION; 431 | } 432 | } else { 433 | log_debug("Error: not a valid channel"); 434 | return res; 435 | } 436 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 437 | return res; 438 | } 439 | 440 | /* should move to res_redis/res_redis_v1.c */ 441 | void msq_list_subscriptions() 442 | { 443 | event_type_t chan = 0; 444 | 445 | log_verbose(1,"+----------------------+--------------------------------+--------------------------------+-----------+-----------+--------+\n"); 446 | log_verbose(1,"| channelname | channel | pattern | publish | subscribe | active |\n"); 447 | log_verbose(1,"+----------------------|--------------------------------|--------------------------------|-----------|-----------|--------+\n"); 448 | raii_rdlock(&msq_event_map_rwlock); 449 | for (chan = 0; chan < ARRAY_LEN(msq_event_map); chan++ ) { 450 | if (msq_event_map[chan].name) { 451 | log_verbose(1,"| %20.20s | %30.30s | %30.30s | %9s | %9s | %6s |\n", 452 | msq_event_map[chan].name, 453 | msq_event_map[chan].channel, 454 | msq_event_map[chan].pattern, 455 | msq_event_map[chan].publish ? "ON" : "OFF", 456 | msq_event_map[chan].subscribe ? "ON" : "OFF", 457 | msq_event_map[chan].active ? "ON" : "OFF" 458 | ); 459 | } 460 | } 461 | log_verbose(1,"+----------------------|--------------------------------|--------------------------------|-----------|-----------|--------+\n"); 462 | } 463 | 464 | /* should move to res_redis/res_redis_v1.c */ 465 | exception_t _msq_drop_all_subscriptions(event_type_t channel) 466 | { 467 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 468 | exception_t res = GENERAL_EXCEPTION; 469 | event_type_t chan = 0; 470 | 471 | raii_wrlock(&msq_event_map_rwlock); 472 | for (chan = 0; chan < ARRAY_LEN(msq_event_map); chan++ ) { 473 | if (msq_event_map[channel].name) { 474 | if (msq_event_map[channel].channel) { 475 | free(msq_event_map[channel].channel); 476 | } 477 | if (msq_event_map[channel].pattern) { 478 | free(msq_event_map[channel].pattern); 479 | } 480 | msq_event_map[channel].callback = NULL; 481 | msq_event_map[channel].active = FALSE; 482 | res = NO_EXCEPTION; 483 | } 484 | } 485 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 486 | return res; 487 | } 488 | 489 | /* should move to res_redis/res_redis_v1.c */ 490 | exception_t _msq_remove_subscription(event_type_t channel) 491 | { 492 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 493 | exception_t res = GENERAL_EXCEPTION; 494 | raii_wrlock(&msq_event_map_rwlock); 495 | if (msq_event_map[channel].name) { 496 | if (!msq_event_map[channel].channel || !msq_event_map[channel].callback) { 497 | log_debug("Error: no previous subscription exists"); 498 | return res; 499 | } else { 500 | if (msq_event_map[channel].channel) { 501 | free(msq_event_map[channel].channel); 502 | } 503 | if (msq_event_map[channel].pattern) { 504 | free(msq_event_map[channel].pattern); 505 | } 506 | msq_event_map[channel].callback = NULL; 507 | msq_event_map[channel].active = FALSE; 508 | res = NO_EXCEPTION; 509 | } 510 | } else { 511 | log_debug("Error: not a valid channel"); 512 | return res; 513 | } 514 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 515 | return res; 516 | } 517 | 518 | /* should move to res_redis/res_redis_v1.c */ 519 | exception_t _msq_toggle_subscriptions(boolean_t on) 520 | { 521 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 522 | exception_t res = NO_EXCEPTION; 523 | event_type_t chan = 0; 524 | raii_wrlock(&msq_event_map_rwlock); 525 | for (chan = 0; chan < ARRAY_LEN(msq_event_map) && !res; chan++ ) { 526 | if (msq_event_map[chan].subscribe) { 527 | if (on) { 528 | res |= msq_send_subscribe(chan); 529 | if (!res) { 530 | msq_event_map[chan].active = TRUE; 531 | } 532 | } else { 533 | res |= msq_send_unsubscribe(chan); 534 | if (!res) { 535 | msq_event_map[chan].active = FALSE; 536 | } 537 | } 538 | } 539 | } 540 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 541 | return res; 542 | } 543 | 544 | exception_t msq_publish(event_type_t channel, const char *publishmsg) 545 | { 546 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 547 | exception_t res = NO_EXCEPTION; 548 | raii_rdlock(&msq_event_map_rwlock); 549 | if (msq_event_map[channel].channel) { 550 | log_verbose(1,"RedisMSQ: PUBLISH channel: '%s', mesg: '%s'\n", msq_event_map[channel].channel, publishmsg); 551 | redisAsyncCommand(current_server->redisConn[PUBLISH], NULL, NULL, "PUBLISH %s %b", msq_event_map[channel].channel, publishmsg, (size_t)strlen(publishmsg)); 552 | res |= msq_processRedisAsyncConnError(current_server->redisConn[PUBLISH]); 553 | } 554 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 555 | return res; 556 | } 557 | 558 | exception_t msq_send_subscribe(event_type_t channel) 559 | { 560 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 561 | exception_t res = NO_EXCEPTION; 562 | 563 | raii_rdlock(&msq_event_map_rwlock); 564 | if (msq_event_map[channel].name) { 565 | if (msq_event_map[channel].channel || msq_event_map[channel].callback) { 566 | if (msq_event_map[channel].pattern) { 567 | log_verbose(1,"RedisMSQ: SUBSCRIBE channel: '%s:%s'\n", msq_event_map[channel].channel, msq_event_map[channel].pattern); 568 | redisAsyncCommand(current_server->redisConn[SUBSCRIBE], NULL, NULL, "SUBSCRIBE %s:%s", msq_event_map[channel].channel, msq_event_map[channel].pattern); 569 | res |= msq_processRedisAsyncConnError(current_server->redisConn[SUBSCRIBE]); 570 | } else { 571 | log_verbose(1,"RedisMSQ: SUBSCRIBE channel: '%s'\n", msq_event_map[channel].channel); 572 | redisAsyncCommand(current_server->redisConn[SUBSCRIBE], NULL, NULL, "SUBSCRIBE %s", msq_event_map[channel].channel); 573 | res |= msq_processRedisAsyncConnError(current_server->redisConn[SUBSCRIBE]); 574 | } 575 | } else { 576 | res = GENERAL_EXCEPTION; 577 | } 578 | } else { 579 | log_debug("Error: not a valid channel"); 580 | return res; 581 | } 582 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 583 | return res; 584 | } 585 | 586 | exception_t msq_send_unsubscribe(event_type_t channel) 587 | { 588 | log_verbose(2, "RedisMSQ: (%s) enter\n", __PRETTY_FUNCTION__); 589 | exception_t res = NO_EXCEPTION; 590 | raii_rdlock(&msq_event_map_rwlock); 591 | if (msq_event_map[channel].name) { 592 | if (!msq_event_map[channel].channel || !msq_event_map[channel].callback) { 593 | if (msq_event_map[channel].pattern) { 594 | log_verbose(1,"RedisMSQ: UNSUBSCRIBE channel: '%s:%s'\n", msq_event_map[channel].channel, msq_event_map[channel].pattern); 595 | redisAsyncCommand(current_server->redisConn[SUBSCRIBE], NULL, NULL, "UNSUBSCRIBE %s:%s", msq_event_map[channel].channel, msq_event_map[channel].pattern); 596 | res |= msq_processRedisAsyncConnError(current_server->redisConn[SUBSCRIBE]); 597 | } else { 598 | log_verbose(1,"RedisMSQ: UNSUBSCRIBE channel: '%s'\n", msq_event_map[channel].channel); 599 | redisAsyncCommand(current_server->redisConn[SUBSCRIBE], NULL, NULL, "UNSUBSCRIBE %s", msq_event_map[channel].channel); 600 | res |= msq_processRedisAsyncConnError(current_server->redisConn[SUBSCRIBE]); 601 | } 602 | } else { 603 | res = GENERAL_EXCEPTION; 604 | } 605 | } else { 606 | log_debug("Error: not a valid channel"); 607 | return res; 608 | } 609 | log_verbose(2, "RedisMSQ: (%s) exit %s%s%s\n", __PRETTY_FUNCTION__, res ? " [Exception Occured: " : "", res ? exception2str[res].str : "", res ? "]" : ""); 610 | return res; 611 | } 612 | 613 | static void redis_ping_subscription_cb(event_type_t msq_event, void *reply, void *privdata) 614 | { 615 | log_verbose(2, "Ping Callback...\n"); 616 | } 617 | 618 | /* should move to res_redis/res_redis_v1.c */ 619 | static void redis_connect_cb(const redisAsyncContext *c, int status) 620 | { 621 | if (status != REDIS_OK) { 622 | log_verbose(2, "Error: %s\n", c->errstr); 623 | //_msq_connect_to_next_server(); 624 | } else { 625 | log_verbose(2, "Connected...\n"); 626 | } 627 | } 628 | 629 | /* should move to res_redis/res_redis_v1.c */ 630 | static void redis_disconnect_cb(const redisAsyncContext *c, int status) 631 | { 632 | if (status != REDIS_OK) { 633 | log_verbose(2, "Error: %s\n", c->errstr); 634 | } else { 635 | log_verbose(2, "Disonnected...\n"); 636 | } 637 | pthread_mutex_lock(&msq_startstop_mutex); 638 | if (!stopped) { 639 | pthread_mutex_unlock(&msq_startstop_mutex); 640 | _msq_connect_to_next_server(); 641 | return; 642 | } 643 | pthread_mutex_unlock(&msq_startstop_mutex); 644 | } 645 | 646 | 647 | /* 648 | * eventloop 649 | */ 650 | #define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1 651 | static void *eventloop_dispatch_thread(void *data) 652 | { 653 | log_debug("Entering eventlog_dispatch thread"); 654 | struct event_base *eventbase = data; 655 | redisLibeventAttach(current_server->redisConn[PUBLISH],eventbase); 656 | redisLibeventAttach(current_server->redisConn[SUBSCRIBE],eventbase); 657 | 658 | redisAsyncSetConnectCallback(current_server->redisConn[PUBLISH], redis_connect_cb); 659 | redisAsyncSetDisconnectCallback(current_server->redisConn[PUBLISH], redis_disconnect_cb); 660 | 661 | redisAsyncSetConnectCallback(current_server->redisConn[SUBSCRIBE], redis_connect_cb); 662 | redisAsyncSetDisconnectCallback(current_server->redisConn[SUBSCRIBE], redis_disconnect_cb); 663 | 664 | event_base_dispatch(eventbase); 665 | log_debug("Exiting eventlog_dispatch thread"); 666 | 667 | // cleanup ? 668 | 669 | return NULL; 670 | } 671 | 672 | exception_t msq_start_eventloop() 673 | { 674 | //raii_wrlock(&msq_server_rwlock); 675 | exception_t res = GENERAL_EXCEPTION; 676 | if (!current_server) { 677 | return res; 678 | } 679 | event_init(); 680 | if ((current_server->evloop_base = event_base_new()) == NULL) { 681 | log_debug("Unable to create socket accept event base"); 682 | return LIBEVENT_EXCEPTION; 683 | } 684 | if (pthread_create(¤t_server->thread, NULL, eventloop_dispatch_thread, current_server->evloop_base)) { 685 | log_debug("Error starting Redis dispatch thread.\n"); 686 | return LIBEVENT_EXCEPTION; 687 | } 688 | return NO_EXCEPTION; 689 | } 690 | 691 | exception_t msq_stop_eventloop() 692 | { 693 | //raii_wrlock(&msq_server_rwlock); 694 | if (event_base_loopexit(current_server->evloop_base, NULL)) { 695 | log_debug("Error shutting down server"); 696 | return LIBEVENT_EXCEPTION; 697 | } 698 | int s = pthread_join(current_server->thread, NULL); 699 | if (s != 0) 700 | log_debug("Error thread join.\n"); 701 | event_base_free(current_server->evloop_base); 702 | return NO_EXCEPTION; 703 | } 704 | 705 | /* 706 | https://gist.github.com/dspezia/4149768 707 | http://stackoverflow.com/questions/13568465/reconnecting-with-hiredis 708 | 709 | 710 | 711 | 712 | void checkConnections() 713 | { 714 | if (current->server.ev_loop == NULL) { 715 | printf("Connecting %d..."); 716 | if (!_msq_connect_to_next_server()) { 717 | redisAeAttach(current->server.ev_loop, singleton.servers[i] ); 718 | redisAsyncSetConnectCallback( singleton.servers[i],connectCallback); 719 | redisAsyncSetDisconnectCallback( singleton.servers[i],disconnectCallback); 720 | } 721 | } 722 | } 723 | 724 | int reconnectIfNeeded( struct aeEventLoop *loop, long long id, void *clientData) { 725 | checkConnections(); 726 | return 1000; 727 | } 728 | 729 | int start_main_event_loop(struct aeEventLoop *loop, long, long id, void *ClientData) { 730 | time_t t = time(NULL); 731 | 732 | checkConnections(); 733 | aeCreateTimeEvent(evloop, 5, reconnectIfNeeded, NULL, NULL); 734 | aeMain(evloop); 735 | } 736 | 737 | */ -------------------------------------------------------------------------------- /res_config_redis/res_config_redis.c: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | #include 6 | 7 | #define AST_MODULE "res_config_redis" 8 | 9 | ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") 10 | 11 | #include 12 | //#include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "config.h" 25 | 26 | AST_MUTEX_DEFINE_STATIC(redis_lock); 27 | AST_THREADSTORAGE(query_buf); 28 | AST_THREADSTORAGE(result_buf); 29 | 30 | #define RES_CONFIG_REDIS_CONF "res_config_redis.conf" 31 | 32 | static redisContext *redisConn = NULL; 33 | 34 | #define MAX_DB_OPTION_SIZE 64 35 | static char hostname[MAX_DB_OPTION_SIZE] = ""; 36 | //static char dbuser[MAX_DB_OPTION_SIZE] = ""; 37 | //static char dbpass[MAX_DB_OPTION_SIZE] = ""; 38 | //static char dbname[MAX_DB_OPTION_SIZE] = ""; 39 | //static char dbappname[MAX_DB_OPTION_SIZE] = ""; 40 | //static char dbsock[MAX_DB_OPTION_SIZE] = ""; 41 | static int port = 6379; 42 | struct timeval timeout = { 1, 500000 }; // 1.5 seconds 43 | 44 | static int parse_config(int reload); 45 | static int redis_reconnect(const char *database); 46 | static char *handle_cli_realtime_redis_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); 47 | 48 | static struct ast_cli_entry cli_realtime[] = { 49 | AST_CLI_DEFINE(handle_cli_realtime_redis_status, "Shows connection information for the Redis RealTime driver"), 50 | }; 51 | 52 | 53 | #ifdef HAVE_PBX_VERSION_11 54 | static struct ast_variable *realtime_redis(const char *database, const char *tablename, va_list ap) 55 | #else 56 | static struct ast_variable *realtime_redis(const char *database, const char *tablename, const struct ast_variable *fields) 57 | #endif 58 | { 59 | struct ast_variable *var = NULL; 60 | return var; 61 | } 62 | 63 | #ifdef HAVE_PBX_VERSION_11 64 | static struct ast_config *realtime_multi_redis(const char *database, const char *table, va_list ap) 65 | #else 66 | static struct ast_config *realtime_multi_redis(const char *database, const char *table, const struct ast_variable *fields) 67 | #endif 68 | { 69 | struct ast_config *cfg = NULL; 70 | return cfg; 71 | } 72 | 73 | #ifdef HAVE_PBX_VERSION_11 74 | static int update_redis(const char *database, const char *tablename, const char *keyfield, 75 | const char *lookup, va_list ap) 76 | #else 77 | static int update_redis(const char *database, const char *tablename, const char *keyfield, 78 | const char *lookup, const struct ast_variable *fields) 79 | #endif 80 | { 81 | return -1; 82 | } 83 | 84 | #ifdef HAVE_PBX_VERSION_11 85 | static int update2_redis(const char *database, const char *tablename, va_list ap) 86 | #else 87 | static int update2_redis(const char *database, const char *tablename, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields) 88 | #endif 89 | { 90 | return -1; 91 | } 92 | 93 | #ifdef HAVE_PBX_VERSION_11 94 | static int store_redis(const char *database, const char *table, va_list ap) 95 | #else 96 | static int store_redis(const char *database, const char *table, const struct ast_variable *fields) 97 | #endif 98 | { 99 | ast_mutex_unlock(&redis_lock); 100 | if (!redis_reconnect(database)) { 101 | ast_mutex_unlock(&redis_lock); 102 | return -1; 103 | } 104 | return -1; 105 | } 106 | 107 | #ifdef HAVE_PBX_VERSION_11 108 | static int destroy_redis(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) 109 | #else 110 | static int destroy_redis(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *fields) 111 | #endif 112 | { 113 | ast_mutex_unlock(&redis_lock); 114 | if (!redis_reconnect(database)) { 115 | ast_mutex_unlock(&redis_lock); 116 | return -1; 117 | } 118 | return -1; 119 | } 120 | 121 | static struct ast_config *config_redis(const char *database, const char *table, 122 | const char *file, struct ast_config *cfg, 123 | struct ast_flags flags, const char *suggested_incl, const char *who_asked) 124 | { 125 | // pre-check 126 | ast_mutex_lock(&redis_lock); 127 | // do load config 128 | ast_mutex_unlock(&redis_lock); 129 | return cfg; 130 | } 131 | 132 | static int require_redis(const char *database, const char *tablename, va_list ap) 133 | { 134 | return -1; 135 | } 136 | 137 | static int unload_redis(const char *database, const char *tablename) 138 | { 139 | return -1; 140 | } 141 | 142 | static struct ast_config_engine redis_engine = { 143 | .name = "redis", 144 | .load_func = config_redis, 145 | .realtime_func = realtime_redis, 146 | .realtime_multi_func = realtime_multi_redis, 147 | .store_func = store_redis, 148 | .destroy_func = destroy_redis, 149 | .update_func = update_redis, 150 | .update2_func = update2_redis, 151 | .require_func = require_redis, 152 | .unload_func = unload_redis, 153 | }; 154 | 155 | static int load_module(void) 156 | { 157 | ast_log(LOG_NOTICE, "Loading res_config_redis...\n"); 158 | 159 | ast_debug(1, "Loading res_config_redis...\n"); 160 | // if(!parse_config(0)) { 161 | // return AST_MODULE_LOAD_DECLINE; 162 | // } 163 | ast_config_engine_register(&redis_engine); 164 | 165 | ast_cli_register_multiple(cli_realtime, ARRAY_LEN(cli_realtime)); 166 | 167 | ast_debug(1, "Done Loading res_config_redis...\n"); 168 | return 0; 169 | } 170 | 171 | static int unload_module(void) 172 | { 173 | ast_debug(1, "Unloading res_config_redis...\n"); 174 | ast_mutex_lock(&redis_lock); 175 | if (redisConn) { 176 | // disconnect 177 | redisConn = NULL; 178 | } 179 | ast_cli_unregister_multiple(cli_realtime, ARRAY_LEN(cli_realtime)); 180 | ast_config_engine_deregister(&redis_engine); 181 | /* Unlock so something else can destroy the lock. */ 182 | ast_mutex_unlock(&redis_lock); 183 | 184 | ast_debug(1, "Done Unloading res_config_redis...\n"); 185 | return 0; 186 | } 187 | 188 | static int reload(void) 189 | { 190 | ast_debug(1, "Reloading res_config_redis...\n"); 191 | parse_config(1); 192 | ast_debug(1, "Done Reloading res_config_redis...\n"); 193 | return 0; 194 | } 195 | 196 | static int parse_config(int is_reload) 197 | { 198 | ast_debug(1, "Parsing Config '%s'...\n", RES_CONFIG_REDIS_CONF); 199 | ast_mutex_lock(&redis_lock); 200 | 201 | ast_mutex_unlock(&redis_lock); 202 | return 1; 203 | } 204 | 205 | static int redis_reconnect(const char *database) 206 | { 207 | redisContext *conn = NULL; 208 | if (redisConn) { 209 | if (!redisConn->err) { 210 | // everything is still ok 211 | return -1; 212 | } else { 213 | // reconnect 214 | redisFree(redisConn); 215 | } 216 | } 217 | 218 | conn = redisConnectWithTimeout(hostname, port, timeout); 219 | 220 | if (conn == NULL || conn->err) { 221 | if (conn) { 222 | ast_log(LOG_ERROR, "Connection error: %s\n", conn->errstr); 223 | } else { 224 | ast_log(LOG_ERROR, "Connection error: Can't allocated redis context\n"); 225 | } 226 | return 0; 227 | } 228 | redisConn = conn; 229 | return -1; 230 | } 231 | 232 | static char *handle_cli_realtime_redis_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) 233 | { 234 | //char status[256]; 235 | 236 | switch (cmd) { 237 | case CLI_INIT: 238 | e->command = "realtime show redis status"; 239 | e->usage = 240 | "Usage: realtime show redis status\n" 241 | " Shows connection information for the Redis RealTime driver\n"; 242 | return NULL; 243 | case CLI_GENERATE: 244 | return NULL; 245 | } 246 | 247 | if (a->argc != 4) { 248 | return CLI_SHOWUSAGE; 249 | } 250 | if (redisConn) { 251 | return CLI_SUCCESS; 252 | } else { 253 | return CLI_FAILURE; 254 | } 255 | } 256 | 257 | /* needs usecount semantics defined */ 258 | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Redis RealTime Configuration Driver", 259 | .load = load_module, 260 | .unload = unload_module, 261 | .reload = reload, 262 | .load_pri = AST_MODPRI_REALTIME_DRIVER, 263 | ); 264 | -------------------------------------------------------------------------------- /res_redis/res_redis.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * res_redis -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2015, Diederik de Groot 5 | * 6 | * Diederik de Groot 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! 14 | * \file 15 | * \author Diederik de Groot 16 | * 17 | * This module is based on the res_corosync module. 18 | */ 19 | 20 | /*** MODULEINFO 21 | hiredis 22 | extended 23 | ***/ 24 | 25 | 26 | #include "config.h" 27 | #include 28 | 29 | #define AST_MODULE "res_redis" 30 | 31 | ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #ifdef HAVE_PBX_STASIS_H 44 | #include 45 | #endif 46 | 47 | #define AST_LOG_NOTICE_DEBUG(...) {ast_log(LOG_NOTICE, __VA_ARGS__);ast_debug(1, __VA_ARGS__);} 48 | 49 | #include "../include/pbx_event_message_serializer.h" 50 | #include "../include/shared.h" 51 | 52 | /* globals */ 53 | AST_RWLOCK_DEFINE_STATIC(event_types_lock); 54 | AST_MUTEX_DEFINE_STATIC(redis_lock); 55 | AST_MUTEX_DEFINE_STATIC(redis_write_lock); 56 | 57 | #define MAX_EVENT_LENGTH 1024 58 | pthread_t dispatch_thread_id = AST_PTHREADT_NULL; 59 | struct event_base *eventbase = NULL; 60 | unsigned int stoprunning = 0; 61 | static redisAsyncContext *redisSubConn = NULL; 62 | static redisAsyncContext *redisPubConn = NULL; 63 | char default_servers[] = "127.0.0.1:6379"; 64 | char *servers = NULL; 65 | char *curserver = NULL; 66 | static char default_eid_str[32]; 67 | 68 | /* predeclarations */ 69 | #ifdef HAVE_PBX_STASIS_H 70 | static void ast_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *smsg); 71 | #else 72 | static void ast_event_cb(const struct ast_event *event, void *data); 73 | #endif 74 | static void redis_dump_ast_event_cache(); 75 | static void redis_subscription_cb(redisAsyncContext *c, void *r, void *privdata); 76 | static void redis_unsubscribe_cb(redisAsyncContext *c, void *r, void *privdata); 77 | static void redis_subscribe_to_channels(void); 78 | static void redis_unsubscribe_from_channels(void); 79 | 80 | static struct loc_event_type { 81 | const char *name; 82 | #ifdef HAVE_PBX_STASIS_H 83 | struct stasis_subscription *sub; 84 | #else 85 | struct ast_event_sub *sub; 86 | #endif 87 | unsigned char publish; 88 | unsigned char subscribe; 89 | unsigned char publish_default; 90 | unsigned char subscribe_default; 91 | char *channelstr; 92 | char *prefix; 93 | } event_types[] = { 94 | [AST_EVENT_MWI] = { .name = "mwi"}, 95 | [AST_EVENT_DEVICE_STATE_CHANGE] = { .name = "device_state_change"}, 96 | [AST_EVENT_DEVICE_STATE] = { .name = "device_state"}, 97 | [AST_EVENT_PING] = { .name = "ping", .publish_default = 1, .subscribe_default = 1 }, 98 | }; 99 | 100 | enum { 101 | PUBLISH, 102 | SUBSCRIBE, 103 | PREFIX, 104 | }; 105 | 106 | static int redis_connect_nextserver() 107 | { 108 | static char *remaining; 109 | char delims[] = ","; 110 | char *server; 111 | char *host; 112 | char *portstr; 113 | int port = 6379; 114 | 115 | if (redisPubConn) { 116 | //redisAsyncDisconnect(redisPubConn); 117 | //redisAsyncFree(redisPubConn); 118 | } 119 | if (redisSubConn) { 120 | redis_unsubscribe_from_channels(); 121 | 122 | //redisAsyncDisconnect(redisSubConn); 123 | //redisAsyncFree(redisSubConn); 124 | } 125 | if (!remaining) { 126 | strcat(servers, delims); 127 | curserver = strtok_r(servers, delims, &remaining); 128 | } else { 129 | curserver = strtok_r(NULL, delims, &remaining); 130 | } 131 | while (servers && curserver) { 132 | server = ast_trim_blanks(ast_strdupa(ast_skip_blanks(curserver))); 133 | ast_debug(1, "Connecting to curserver:'%s', server:'%s' / servers:'%s' / remaining:'%s'\n", curserver, server, servers, remaining); 134 | if (server[0] == '/') { 135 | AST_LOG_NOTICE_DEBUG("Use Socket: %s\n", server); 136 | redisPubConn = redisAsyncConnectUnix(server); 137 | redisSubConn = redisAsyncConnectUnix(server); 138 | } else { 139 | ast_sockaddr_split_hostport(server, &host, &portstr, 0); 140 | if (!ast_strlen_zero(portstr)) { 141 | port = atoi(portstr); 142 | } 143 | AST_LOG_NOTICE_DEBUG("Use Server: '%s', Port: '%d'\n", server, port); 144 | if (!ast_strlen_zero(server)) { 145 | redisPubConn = redisAsyncConnect(server, port); 146 | redisSubConn = redisAsyncConnect(server, port); 147 | } 148 | } 149 | if (redisPubConn == NULL || redisPubConn->err || redisSubConn == NULL || redisSubConn->err) { 150 | if (redisPubConn || redisSubConn) { 151 | ast_log(LOG_ERROR, "Connection error: %s / %s\n", redisPubConn->errstr, redisSubConn->errstr); 152 | } else { 153 | ast_log(LOG_ERROR, "Connection error: Can't allocated redis context\n"); 154 | } 155 | if (servers) { 156 | curserver = strtok_r(NULL, delims, &remaining); 157 | continue; 158 | } else { 159 | return 0; 160 | } 161 | } 162 | AST_LOG_NOTICE_DEBUG("Async Connection Started %s\n", curserver); 163 | redis_dump_ast_event_cache(); 164 | break; 165 | } 166 | return -1; 167 | } 168 | 169 | void redis_pong_cb(redisAsyncContext *c, void *r, void *privdata) { 170 | redisReply *reply = r; 171 | AST_LOG_NOTICE_DEBUG("Handle Pong\n"); 172 | if (reply == NULL) { 173 | return; 174 | } 175 | //freeReplyObject(reply); 176 | redisAsyncFree(c); 177 | } 178 | 179 | void redis_meet_cb(redisAsyncContext *c, void *r, void *privdata) { 180 | redisReply *reply = r; 181 | AST_LOG_NOTICE_DEBUG("Handle Meet\n"); 182 | if (reply == NULL) { 183 | return; 184 | } 185 | //freeReplyObject(reply); 186 | redisAsyncFree(c); 187 | } 188 | 189 | void redis_unsubscribe_cb(redisAsyncContext *c, void *r, void *privdata) { 190 | redisReply *reply = r; 191 | AST_LOG_NOTICE_DEBUG("Handle Unsubscribe\n"); 192 | if (reply == NULL) { 193 | return; 194 | } 195 | //freeReplyObject(reply); 196 | redisAsyncFree(c); 197 | } 198 | 199 | /* 200 | void redis_publish_cb(redisAsyncContext *c, void *r, void *privdata) { 201 | redisReply *reply = r; 202 | AST_LOG_NOTICE_DEBUG("Handle Publish\n"); 203 | if (reply == NULL) { 204 | return; 205 | } 206 | //freeReplyObject(reply); 207 | redisAsyncFree(c); 208 | AST_LOG_NOTICE_DEBUG("Published & Freed\n"); 209 | } 210 | */ 211 | 212 | static void redis_subscription_cb(redisAsyncContext *c, void *r, void *privdata) 213 | { 214 | #ifndef HAVE_PBX_STASIS_H 215 | enum ast_event_type event_type; 216 | redisReply *reply = r; 217 | if (reply == NULL) { 218 | return; 219 | } 220 | if (reply->type == REDIS_REPLY_ARRAY) { 221 | AST_LOG_NOTICE_DEBUG("(%s) Handle Subscription Callback\n", __PRETTY_FUNCTION__); 222 | if (!strcasecmp(reply->element[0]->str, "MESSAGE")) { 223 | struct loc_event_type *etype = NULL; 224 | if (!ast_strlen_zero(reply->element[1]->str)) { 225 | for (event_type = 0; event_type < ARRAY_LEN(event_types); event_type++) { 226 | ast_rwlock_rdlock(&event_types_lock); 227 | if (!event_types[event_type].channelstr) { 228 | ast_rwlock_unlock(&event_types_lock); 229 | continue; 230 | } 231 | if (!strcmp(event_types[event_type].channelstr, reply->element[1]->str)) { 232 | etype = &event_types[event_type]; 233 | ast_rwlock_unlock(&event_types_lock); 234 | break; 235 | } 236 | ast_rwlock_unlock(&event_types_lock); 237 | } 238 | 239 | if (etype) { 240 | if (!ast_strlen_zero(reply->element[2]->str)) { 241 | ast_debug(1, "start decoding'\n"); 242 | 243 | if (etype->publish) { 244 | if (!strcasecmp(reply->element[1]->str, etype->channelstr)) { 245 | #ifdef HAVE_PBX_STASIS_H 246 | 247 | #else 248 | struct ast_event *event = NULL; 249 | char *msg = ast_strdupa(reply->element[2]->str); 250 | unsigned int res = 0; 251 | 252 | if (strlen(reply->element[2]->str) < ast_event_minimum_length()) { 253 | ast_log(LOG_ERROR, "Ignoring event that's too small. %u < %u\n", (unsigned int) strlen(reply->element[2]->str), (unsigned int) ast_event_minimum_length()); 254 | return; 255 | } 256 | boolean_t cacheable = FALSE; 257 | if ((res = json2message(&event, event_type, msg, &cacheable)) < 100) { 258 | if (res == EID_SELF_EXCEPTION) { 259 | // skip feeding back to self 260 | ast_debug(1, "Originated Here. skip (Exception: %s)'\n", exception2str[res].str); 261 | return; 262 | } else { 263 | if (!cacheable) { 264 | #ifdef HAVE_PBX_STASIS_H 265 | //ast_publish_device_state(); 266 | #else 267 | ast_event_queue(event); 268 | #endif 269 | } else { 270 | #ifdef HAVE_PBX_STASIS_H 271 | //ast_publish_device_state(); 272 | #else 273 | ast_event_queue_and_cache(event); 274 | #endif 275 | } 276 | ast_debug(1, "ast_event sent'\n"); 277 | res = NO_EXCEPTION; 278 | } 279 | } else { 280 | ast_log(LOG_ERROR, "error decoding '%s' exception: %d\n", msg, res); 281 | } 282 | #endif 283 | } else { 284 | ast_debug(1, "has different channelstr '%s'\n", etype->channelstr); 285 | } 286 | } else { 287 | ast_debug(1, "event_type should not be published\n"); 288 | } 289 | } else { 290 | ast_debug(1, "message content is zero\n"); 291 | } 292 | } else { 293 | ast_log(LOG_ERROR, "event_type does not exist'\n"); 294 | } 295 | } 296 | } else { 297 | int j; 298 | for (j = 0; j < reply->elements; j++) { 299 | ast_debug(1, "REDIS_SUBSCRIPTION_CB: [%u]: %s\n", j, reply->element[j]->str); 300 | } 301 | } 302 | } 303 | #endif 304 | //exit: 305 | //redisAsyncFree(c); 306 | } 307 | 308 | void redis_connect_cb(const redisAsyncContext *c, int status) { 309 | if (status != REDIS_OK) { 310 | printf("Error: %s\n", c->errstr); 311 | return; 312 | } 313 | AST_LOG_NOTICE_DEBUG("Connected\n"); 314 | } 315 | 316 | void redis_disconnect_cb(const redisAsyncContext *c, int status) { 317 | if (status != REDIS_OK) { 318 | printf("Error: %s\n", c->errstr); 319 | return; 320 | } 321 | AST_LOG_NOTICE_DEBUG("Disconnected\n"); 322 | ast_mutex_lock(&redis_lock); 323 | if (!stoprunning) { 324 | redis_connect_nextserver(); 325 | } 326 | ast_mutex_unlock(&redis_lock); 327 | } 328 | 329 | 330 | static void redis_dump_ast_event_cache() 331 | { 332 | if (dispatch_thread_id != AST_PTHREADT_NULL) { 333 | ast_debug(1, "Dumping Ast Event Cache to %s\n", curserver); 334 | unsigned int i = 0; 335 | // flush all changes 336 | for (i = 0; i < ARRAY_LEN(event_types); i++) { 337 | ast_rwlock_rdlock(&event_types_lock); 338 | if (!event_types[i].publish) { 339 | ast_rwlock_unlock(&event_types_lock); 340 | ast_debug(1, "%s skipping not published\n", event_types[i].name); 341 | continue; 342 | } 343 | ast_rwlock_unlock(&event_types_lock); 344 | 345 | ast_debug(1, "subscribe %s\n", event_types[i].name); 346 | #ifdef HAVE_PBX_STASIS_H 347 | struct stasis_subscription *event_sub; 348 | event_sub = stasis_subscribe(ast_device_state_topic_all(), ast_event_cb, NULL); 349 | usleep(500); 350 | ast_debug(1, "Dumping Past %s Events\n", event_types[i].name); 351 | stasis_cache_dump(ast_device_state_cache(), NULL); 352 | // stasis_cache_dump_by_eid(); 353 | stasis_unsubscribe(event_sub) 354 | //destroy ? 355 | #else 356 | struct ast_event_sub *event_sub; 357 | event_sub = ast_event_subscribe_new(i, ast_event_cb, NULL); 358 | ast_event_sub_append_ie_raw(event_sub, AST_EVENT_IE_EID, &ast_eid_default, sizeof(ast_eid_default)); 359 | usleep(500); 360 | ast_debug(1, "Dumping Past %s Events\n", event_types[i].name); 361 | ast_event_dump_cache(event_sub); 362 | ast_event_sub_destroy(event_sub); 363 | #endif 364 | } 365 | AST_LOG_NOTICE_DEBUG("Ast Event Cache Dumped to %s\n", curserver); 366 | redis_subscribe_to_channels(); 367 | } 368 | } 369 | 370 | #ifdef HAVE_PBX_STASIS_H 371 | static void ast_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *smsg) 372 | #else 373 | static void ast_event_cb(const struct ast_event *event, void *data) 374 | #endif 375 | { 376 | ast_debug(1, "ast_event_cb\n"); 377 | #ifndef HAVE_PBX_STASIS_H 378 | const struct ast_eid *eid; 379 | char eid_str[32] = ""; 380 | eid = ast_event_get_ie_raw(event, AST_EVENT_IE_EID); 381 | if (eid) { 382 | ast_eid_to_str(eid_str, sizeof(eid_str), (struct ast_eid *) eid); 383 | } 384 | //AST_LOG_NOTICE_DEBUG("(ast_event_cb) Got event with EID: '%s' / '%s'\n", eid_str, default_eid_str); 385 | 386 | if (ast_event_get_type(event) == AST_EVENT_PING) { 387 | AST_LOG_NOTICE_DEBUG("(ast_event_cb) Got event PING from server with EID: '%s' (Add Handler)\n", eid ? eid_str : ""); 388 | /* 389 | if (ast_event_get_type(event) == AST_EVENT_PING) { 390 | const struct ast_eid *eid; 391 | char buf[128] = ""; 392 | 393 | eid = ast_event_get_ie_raw(event, AST_EVENT_IE_EID); 394 | ast_eid_to_str(buf, sizeof(buf), (struct ast_eid *) eid); 395 | ast_debug(1, "(ast_deliver_event) Got event PING from server with EID: '%s'\n", buf); 396 | 397 | ast_event_queue(event); 398 | */ 399 | /* 400 | ast_mutex_lock(&redis_write_lock); 401 | redisAsyncCommand(redisPubConn, redis_meet_cb, NULL, "MEET"); 402 | if (redisPubConn->err) { 403 | ast_log(LOG_ERROR, "redisAsyncCommand Send error: %s\n", redisPubConn->errstr); 404 | } 405 | ast_mutex_unlock(&redis_write_lock); 406 | */ 407 | 408 | ast_mutex_lock(&redis_write_lock); 409 | redisAsyncCommand(redisPubConn, redis_pong_cb, (char*)eid_str, "PING"); 410 | if (redisPubConn->err) { 411 | ast_log(LOG_ERROR, "redisAsyncCommand Send error: %s\n", redisPubConn->errstr); 412 | } 413 | ast_mutex_unlock(&redis_write_lock); 414 | } 415 | 416 | if (eid && ast_eid_cmp(&ast_eid_default, eid)) { 417 | // If the event didn't originate from this server, don't send it back out. 418 | ast_debug(1, "(ast_event_cb) didn't originate on this server, don't send it back out, skipping: '%s')\n", eid_str); 419 | return; 420 | } 421 | AST_LOG_NOTICE_DEBUG("(ast_event_cb) Got event from EID: '%s'\n", eid ? eid_str : ""); 422 | #endif 423 | 424 | // decode event2msg 425 | ast_debug(1, "(ast_event_cb) decode incoming message\n"); 426 | struct loc_event_type *etype; 427 | char *msg = ast_alloca(MAX_EVENT_LENGTH + 1); 428 | if (!msg) { 429 | return /* MALLOC_ERROR */; 430 | } 431 | 432 | ast_rwlock_rdlock(&event_types_lock); 433 | etype = &event_types[ast_event_get_type(event)]; 434 | ast_rwlock_unlock(&event_types_lock); 435 | 436 | if (etype) { 437 | if (etype->publish) { 438 | 439 | #ifdef HAVE_PBX_STASIS_H 440 | const struct ast_eid *eid; 441 | char eid_str[32] = ""; 442 | 443 | struct ast_json * 444 | if ((msg = stasis_message_to_json(smsg, NULL))) { 445 | #else 446 | if (!message2json(msg, MAX_EVENT_LENGTH, event)) { 447 | #endif 448 | AST_LOG_NOTICE_DEBUG("sending 'PUBLISH %s \"%s\"'\n", etype->channelstr, msg); 449 | ast_mutex_lock(&redis_write_lock); 450 | // redisAsyncCommand(redisPubConn, redis_publish_cb, NULL, "PUBLISH %s %b", etype->channelstr, msg, (size_t)strlen(msg)); 451 | redisAsyncCommand(redisPubConn, NULL, NULL, "PUBLISH %s %b", etype->channelstr, msg, (size_t)strlen(msg)); 452 | if (redisPubConn->err) { 453 | ast_log(LOG_ERROR, "redisAsyncCommand Send error: %s\n", redisPubConn->errstr); 454 | } 455 | ast_mutex_unlock(&redis_write_lock); 456 | } else { 457 | ast_log(LOG_ERROR, "error encoding %s'\n", msg); 458 | } 459 | } else { 460 | ast_debug(1, "event_type should not be published'\n"); 461 | } 462 | } else { 463 | ast_log(LOG_ERROR, "event_type does not exist'\n"); 464 | } 465 | } 466 | 467 | static int set_event(const char *event_type, int pubsub, char *str) 468 | { 469 | unsigned int i; 470 | AST_LOG_NOTICE_DEBUG("set_event: %s, %s\n", pubsub ? "PUBLISH" : "SUBSCRIBE", str); 471 | 472 | for (i = 0; i < ARRAY_LEN(event_types); i++) { 473 | if (!event_types[i].name || strcasecmp(event_type, event_types[i].name)) { 474 | continue; 475 | } 476 | switch (pubsub) { 477 | case PUBLISH: 478 | event_types[i].publish = 1; 479 | event_types[i].channelstr = str; 480 | break; 481 | case SUBSCRIBE: 482 | event_types[i].subscribe = 1; 483 | event_types[i].channelstr = str; 484 | break; 485 | case PREFIX: 486 | event_types[i].prefix = str; 487 | break; 488 | } 489 | 490 | break; 491 | } 492 | AST_LOG_NOTICE_DEBUG("set_event: returning: %d\n", (i == ARRAY_LEN(event_types)) ? -1 : 0); 493 | 494 | return (i == ARRAY_LEN(event_types)) ? -1 : 0; 495 | } 496 | 497 | static void redis_unsubscribe_from_channels(void) 498 | { 499 | unsigned int i = 0; 500 | ast_rwlock_wrlock(&event_types_lock); 501 | for (i = 0; i < ARRAY_LEN(event_types); i++) { 502 | if (!event_types[i].publish) { 503 | ast_debug(1, "Skipping '%s' not published\n", event_types[i].channelstr); 504 | continue; 505 | } 506 | if (event_types[i].sub) { 507 | #ifdef HAVE_PBX_STASIS_H 508 | statis_unsubscribe(event_types[i].sub); 509 | #else 510 | ast_event_unsubscribe(event_types[i].sub); 511 | #endif 512 | } 513 | AST_LOG_NOTICE_DEBUG("Unsubscribing from redis channel '%s'\n", event_types[i].channelstr); 514 | ast_mutex_lock(&redis_write_lock); 515 | redisAsyncCommand(redisSubConn, redis_unsubscribe_cb, NULL, "UNSUBSCRIBE %s", event_types[i].channelstr); 516 | if (redisSubConn->err) { 517 | ast_log(LOG_ERROR, "redisAsyncCommand Send error: %s\n", redisSubConn->errstr); 518 | } 519 | ast_mutex_unlock(&redis_write_lock); 520 | } 521 | ast_rwlock_unlock(&event_types_lock); 522 | } 523 | 524 | static void redis_subscribe_to_channels(void) 525 | { 526 | unsigned int i = 0; 527 | ast_rwlock_wrlock(&event_types_lock); 528 | for (i = 0; i < ARRAY_LEN(event_types); i++) { 529 | if (!event_types[i].publish) { 530 | ast_debug(1, "Skipping '%s' not published\n", event_types[i].channelstr); 531 | continue; 532 | } 533 | if (event_types[i].publish && !event_types[i].sub) { 534 | #ifdef HAVE_PBX_STASIS_H 535 | event_types[i].sub = stasis_subscribe(ast_device_state_topic_all(), ast_event_cb, NULL); 536 | #else 537 | event_types[i].sub = ast_event_subscribe(i, ast_event_cb, "res_redis", NULL, AST_EVENT_IE_END); 538 | #endif 539 | } 540 | AST_LOG_NOTICE_DEBUG("Subscribing to redis channel '%s'\n", event_types[i].channelstr); 541 | ast_mutex_lock(&redis_write_lock); 542 | redisAsyncCommand(redisSubConn, redis_subscription_cb, NULL, "SUBSCRIBE %s", event_types[i].channelstr); 543 | if (redisSubConn->err) { 544 | ast_log(LOG_ERROR, "redisAsyncCommand Send error: %s\n", redisSubConn->errstr); 545 | } 546 | ast_mutex_unlock(&redis_write_lock); 547 | } 548 | ast_rwlock_unlock(&event_types_lock); 549 | } 550 | 551 | static char *redis_show_members(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) 552 | { 553 | switch (cmd) { 554 | case CLI_INIT: 555 | e->command = "res_redis show members"; 556 | e->usage = 557 | "Usage: res_redis show members\n"; 558 | return NULL; 559 | 560 | case CLI_GENERATE: 561 | return NULL; /* no completion */ 562 | } 563 | 564 | if (a->argc != e->args) { 565 | return CLI_SHOWUSAGE; 566 | } 567 | 568 | //if (!event) { 569 | // return CLI_FAILURE; 570 | //} 571 | return CLI_SUCCESS; 572 | } 573 | 574 | static char *redis_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) 575 | { 576 | switch (cmd) { 577 | case CLI_INIT: 578 | e->command = "res_redis config"; 579 | e->usage = 580 | "Usage: res_redis config\n"; 581 | return NULL; 582 | 583 | case CLI_GENERATE: 584 | return NULL; /* no completion */ 585 | } 586 | 587 | if (a->argc != e->args) { 588 | return CLI_SHOWUSAGE; 589 | } 590 | 591 | //if (!event) { 592 | // return CLI_FAILURE; 593 | //} 594 | return CLI_SUCCESS; 595 | } 596 | 597 | static char *redis_ping(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) 598 | { 599 | struct ast_event *event; 600 | 601 | switch (cmd) { 602 | case CLI_INIT: 603 | e->command = "res_redis ping/meet"; 604 | e->usage = 605 | "Usage: res_redis ping/meet\n" 606 | " Send a test ping to the cluster.\n" 607 | "A NOTICE will be in the log for every ping received\n" 608 | "on a server.\n If you send a ping, you should see a NOTICE\n" 609 | "in the log for every server in the cluster.\n"; 610 | return NULL; 611 | 612 | case CLI_GENERATE: 613 | return NULL; /* no completion */ 614 | } 615 | 616 | if (a->argc != e->args) { 617 | return CLI_SHOWUSAGE; 618 | } 619 | 620 | event = ast_event_new(AST_EVENT_PING, AST_EVENT_IE_END); 621 | 622 | if (!event) { 623 | return CLI_FAILURE; 624 | } 625 | 626 | #ifdef HAVE_PBX_STASIS_H 627 | //ast_publish_device_state(); 628 | #else 629 | ast_event_queue_and_cache(event); 630 | #endif 631 | 632 | return CLI_SUCCESS; 633 | } 634 | 635 | 636 | static struct ast_cli_entry redis_cli[] = { 637 | AST_CLI_DEFINE(redis_show_config, "Show configuration"), 638 | AST_CLI_DEFINE(redis_show_members, "Show cluster members"), 639 | AST_CLI_DEFINE(redis_ping, "Send a test ping to the cluster"), 640 | }; 641 | 642 | static int load_general_config(struct ast_config *cfg) 643 | { 644 | struct ast_variable *v; 645 | int res = 0; 646 | 647 | ast_rwlock_wrlock(&event_types_lock); 648 | for (v = ast_variable_browse(cfg, "general"); v && !res; v = v->next) { 649 | if (!strcasecmp(v->name, "servers")) { 650 | if (servers) { 651 | ast_free(servers); 652 | servers = NULL; 653 | } 654 | servers = strdup(v->value); 655 | AST_LOG_NOTICE_DEBUG("Set Servers %s to '%s'\n", servers, v->value); 656 | 657 | } else if (!strcasecmp(v->name, "mwi_prefix")) { 658 | res = set_event("mwi", PREFIX, strdup(v->value)); 659 | } else if (!strcasecmp(v->name, "publish_mwi_event")) { 660 | res = set_event("mwi", PUBLISH, strdup(v->value)); 661 | } else if (!strcasecmp(v->name, "subscribe_mwi_event")) { 662 | res = set_event("mwi", SUBSCRIBE, strdup(v->value)); 663 | 664 | } else if (!strcasecmp(v->name, "devicestate_prefix")) { 665 | res = set_event("device_state", PREFIX, strdup(v->value)); 666 | } else if (!strcasecmp(v->name, "publish_devicestate_event")) { 667 | res = set_event("device_state", PUBLISH, strdup(v->value)); 668 | } else if (!strcasecmp(v->name, "subscribe_devicestate_event")) { 669 | res = set_event("device_state", SUBSCRIBE, strdup(v->value)); 670 | 671 | } else if (!strcasecmp(v->name, "devicestate_change_prefix")) { 672 | res = set_event("device_state_change", PREFIX, strdup(v->value)); 673 | } else if (!strcasecmp(v->name, "publish_devicestate_change_event")) { 674 | res = set_event("device_state_change", PUBLISH, strdup(v->value)); 675 | } else if (!strcasecmp(v->name, "subscribe_devicestate_change_event")) { 676 | res = set_event("device_state_change", SUBSCRIBE, strdup(v->value)); 677 | } else { 678 | ast_log(LOG_WARNING, "Unknown option '%s'\n", v->name); 679 | } 680 | } 681 | ast_rwlock_unlock(&event_types_lock); 682 | if (!servers) { 683 | servers = strdup(default_servers); 684 | } 685 | AST_LOG_NOTICE_DEBUG("Done loading config\n"); 686 | 687 | return res; 688 | } 689 | 690 | static int load_config(unsigned int reload) 691 | { 692 | static const char filename[] = "res_redis.conf"; 693 | struct ast_config *cfg; 694 | const char *cat = NULL; 695 | struct ast_flags config_flags = { 0 }; 696 | int res = 0; 697 | 698 | cfg = ast_config_load(filename, config_flags); 699 | 700 | if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) { 701 | return -1; 702 | } 703 | 704 | while ((cat = ast_category_browse(cfg, cat))) { 705 | if (!strcasecmp(cat, "general")) { 706 | res = load_general_config(cfg); 707 | } else { 708 | ast_log(LOG_WARNING, "Unknown configuration section '%s'\n", cat); 709 | } 710 | } 711 | 712 | ast_config_destroy(cfg); 713 | 714 | return res; 715 | } 716 | 717 | static void *dispatch_thread_handler(void *data) 718 | { 719 | struct event_base *eventbase = data; 720 | 721 | redisLibeventAttach(redisPubConn,eventbase); 722 | redisLibeventAttach(redisSubConn,eventbase); 723 | 724 | redisAsyncSetConnectCallback(redisPubConn, redis_connect_cb); 725 | redisAsyncSetDisconnectCallback(redisPubConn, redis_disconnect_cb); 726 | 727 | redisAsyncSetConnectCallback(redisSubConn, redis_connect_cb); 728 | redisAsyncSetDisconnectCallback(redisSubConn, redis_disconnect_cb); 729 | 730 | event_base_dispatch(eventbase); 731 | return NULL; 732 | } 733 | 734 | 735 | static void cleanup_module(void) 736 | { 737 | unsigned int i = 0; 738 | redis_unsubscribe_from_channels(); 739 | for (i = 0; i < ARRAY_LEN(event_types); i++) { 740 | event_types[i].publish = 0; 741 | event_types[i].subscribe = 0; 742 | if (event_types[i].channelstr) { 743 | ast_free(event_types[i].channelstr); 744 | event_types[i].channelstr = NULL; 745 | } 746 | if (event_types[i].prefix) { 747 | ast_free(event_types[i].prefix); 748 | event_types[i].prefix = NULL; 749 | } 750 | } 751 | 752 | ast_mutex_lock(&redis_lock); 753 | stoprunning = 1; 754 | event_base_loopbreak(eventbase); 755 | ast_mutex_unlock(&redis_lock); 756 | 757 | if (dispatch_thread_id != AST_PTHREADT_NULL) { 758 | pthread_kill(dispatch_thread_id, SIGURG); 759 | pthread_join(dispatch_thread_id, NULL); 760 | } 761 | 762 | if (servers) { 763 | ast_free(servers); 764 | servers = NULL; 765 | } 766 | } 767 | 768 | static int load_module(void) 769 | { 770 | enum ast_module_load_result res = AST_MODULE_LOAD_FAILURE; 771 | 772 | AST_LOG_NOTICE_DEBUG("Loading res_config_redis...\n"); 773 | ast_debug(1, "Loading res_config_redis...\n"); 774 | 775 | ast_eid_to_str(default_eid_str, sizeof(default_eid_str), &ast_eid_default); 776 | if (load_config(0)) { 777 | // simply not configured is not a fatal error 778 | ast_log(LOG_ERROR, "Declining load of the module, until config issue is resolved\n"); 779 | res = AST_MODULE_LOAD_DECLINE; 780 | return res; 781 | //goto failed; 782 | } 783 | 784 | ast_cli_register_multiple(redis_cli, ARRAY_LEN(redis_cli)); 785 | 786 | /* create libevent base */ 787 | eventbase = event_base_new(); 788 | 789 | /* connect to the first available redis server */ 790 | if (!redis_connect_nextserver()) { 791 | if (redisPubConn->err) { 792 | ast_log(LOG_ERROR, "connecting to any of the redis servers failed: error: %s..\n", redisPubConn->errstr); 793 | } 794 | if (redisSubConn->err) { 795 | ast_log(LOG_ERROR, "connecting to any of the redis servers failed: error: %s..\n", redisSubConn->errstr); 796 | } 797 | goto failed; 798 | } 799 | 800 | if (ast_pthread_create_background(&dispatch_thread_id, NULL, dispatch_thread_handler, eventbase)) { 801 | ast_log(LOG_ERROR, "Error starting Redis dispatch thread.\n"); 802 | goto failed; 803 | } 804 | 805 | #ifdef HAVE_PBX_STASIS_H 806 | #else 807 | ast_enable_distributed_devstate(); 808 | #endif 809 | redis_dump_ast_event_cache(); 810 | 811 | AST_LOG_NOTICE_DEBUG("res_redis loaded\n"); 812 | 813 | return AST_MODULE_LOAD_SUCCESS; 814 | 815 | failed: 816 | cleanup_module(); 817 | return res; 818 | } 819 | 820 | static int unload_module(void) 821 | { 822 | ast_debug(1, "Unloading res_config_redis...\n"); 823 | ast_cli_unregister_multiple(redis_cli, ARRAY_LEN(redis_cli)); 824 | 825 | cleanup_module(); 826 | ast_debug(1, "Done Unloading res_config_redis...\n"); 827 | AST_LOG_NOTICE_DEBUG("Done Unloading res_config_redis...\n"); 828 | return 0; 829 | } 830 | 831 | static int reload(void) 832 | { 833 | ast_debug(1, "Reloading res_redis not implemented yet!...\n"); 834 | enum ast_module_load_result res = AST_MODULE_LOAD_DECLINE; 835 | 836 | /* 837 | enum ast_module_load_result res = AST_MODULE_LOAD_FAILURE; 838 | unsigned int i = 0; 839 | 840 | ast_debug(1, "Reloading res_config_redis...\n"); 841 | cleanup_module(); 842 | if (load_config(1)) { 843 | // simply not configured is not a fatal error 844 | res = AST_MODULE_LOAD_DECLINE; 845 | goto failed; 846 | } 847 | redisAsyncDisconnect(redisSubConn); 848 | redisAsyncDisconnect(redisPubConn); 849 | 850 | if (!redis_connect_nextserver()) { 851 | if (redisPubConn->err) { 852 | ast_log(LOG_ERROR, "connecting to any of the redis servers failed: error: %s..\n", redisPubConn->errstr); 853 | } 854 | if (redisSubConn->err) { 855 | ast_log(LOG_ERROR, "connecting to any of the redis servers failed: error: %s..\n", redisSubConn->errstr); 856 | } 857 | goto failed; 858 | } 859 | redis_dump_ast_event_cache(); 860 | 861 | ast_rwlock_wrlock(&event_types_lock); 862 | for (i = 0; i < ARRAY_LEN(event_types); i++) { 863 | if (!event_types[i].publish) { 864 | ast_debug(1, "Skipping '%s' not published\n", event_types[i].channelstr); 865 | continue; 866 | } 867 | if (event_types[i].publish && !event_types[i].sub) { 868 | event_types[i].sub = ast_event_subscribe(i, ast_event_cb, "res_redis", NULL, AST_EVENT_IE_END); 869 | } 870 | AST_LOG_NOTICE_DEBUG("Subscribing to redis channel '%s'\n", event_types[i].channelstr); 871 | redisAsyncCommand(redisSubConn, redis_subscription_cb, NULL, "SUBSCRIBE %s", event_types[i].channelstr); 872 | if (redisSubConn->err) { 873 | ast_log(LOG_ERROR, "redisAsyncCommand Send error: %s\n", redisSubConn->errstr); 874 | } 875 | } 876 | ast_rwlock_unlock(&event_types_lock); 877 | 878 | ast_debug(1, "Done Reloading res_config_redis...\n"); 879 | return AST_MODULE_LOAD_SUCCESS; 880 | failed: 881 | cleanup_module(); 882 | */ 883 | return res; 884 | } 885 | 886 | void _log_verbose(int level, const char *file, int line, const char *function, const char *fmt, ...) 887 | { 888 | va_list ap; 889 | va_start(ap, fmt); 890 | if (level >= 4) { 891 | __ast_verbose_ap(file, line, function, level, NULL, fmt, ap); 892 | } else { 893 | __ast_verbose_ap(file, line, function, level, NULL, fmt, ap); 894 | } 895 | va_end(ap); 896 | } 897 | 898 | 899 | //AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Redis"); 900 | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Redis RealTime PubSub Driver", 901 | .load = load_module, 902 | .unload = unload_module, 903 | .reload = reload, 904 | .load_pri = AST_MODPRI_REALTIME_DRIVER, 905 | ); 906 | -------------------------------------------------------------------------------- /res_redis/res_redis_v1.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * res_redis -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2015, Diederik de Groot 5 | * 6 | * Diederik de Groot 7 | * 8 | * This program is free software, distributed under the terms of 9 | * the GNU General Public License Version 2. See the LICENSE file 10 | * at the top of the source tree. 11 | */ 12 | 13 | /*! 14 | * \file 15 | * \author Diederik de Groot 16 | * 17 | * This module is based on the res_corosync module. 18 | */ 19 | 20 | /*** MODULEINFO 21 | hiredis 22 | extended 23 | ***/ 24 | 25 | 26 | #include "config.h" 27 | #include 28 | 29 | #define AST_MODULE "res_redis_v1" 30 | 31 | ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #ifdef HAVE_PBX_STASIS_H 45 | #include 46 | #endif 47 | 48 | #include "../include/pbx_event_message_serializer.h" 49 | #include "../include/message_queue_pubsub.h" 50 | #include "../include/shared.h" 51 | 52 | pthread_rwlock_t msq_event_channel_map_rwlock = PTHREAD_RWLOCK_INITIALIZER; 53 | 54 | /* 55 | * declarations 56 | */ 57 | static char *redis_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); 58 | static char *redis_ping(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); 59 | static struct ast_cli_entry redis_cli[] = { 60 | AST_CLI_DEFINE(redis_show_config, "Show configuration"), 61 | AST_CLI_DEFINE(redis_ping, "Send a test ping to the cluster"), 62 | }; 63 | 64 | /* 65 | * globals 66 | */ 67 | //AST_RWLOCK_DEFINE_STATIC(event_types_lock); 68 | AST_MUTEX_DEFINE_STATIC(reload_lock); 69 | static char *default_eid_str; 70 | 71 | AST_RWLOCK_DEFINE_STATIC(event_map_lock); 72 | 73 | typedef struct event_map { 74 | const char *name; 75 | boolean_t publish; 76 | boolean_t subscribe; 77 | boolean_t active; 78 | char *channel; 79 | char *pattern; 80 | // msq_subscription_callback_t callback; 81 | } event_t; 82 | static event_t event_map[] = { 83 | [EVENT_MWI] = {.name="mwi",}, 84 | [EVENT_DEVICE_STATE] = {.name="device_state",}, 85 | [EVENT_DEVICE_STATE_CHANGE] = {.name="device_state_change"}, 86 | [EVENT_PING] = {.name="ping"} 87 | }; 88 | 89 | event_type_t find_event_byname(const char *channelname) 90 | { 91 | event_type_t chan; 92 | ast_rwlock_rdlock(&event_map_lock); 93 | for (chan = 0; chan < ARRAY_LEN(event_map); chan++ ) { 94 | if (event_map[chan].name && !strcasecmp(channelname, event_map[chan].name)) { 95 | ast_rwlock_rdlock(&event_map_lock); 96 | return chan; 97 | } 98 | } 99 | ast_rwlock_rdlock(&event_map_lock); 100 | return chan; 101 | } 102 | 103 | 104 | static void cleanup_module(void) 105 | { 106 | log_verbose(2, "res_redis: Enter (%s)\n", __PRETTY_FUNCTION__); 107 | exception_t res = NO_EXCEPTION; 108 | 109 | // cleanup first; 110 | msq_list_servers(); 111 | msq_remove_all_servers(); 112 | msq_list_servers(); 113 | 114 | log_verbose(2, "res_redis: Exit %s%s\n", res ? ", Exception Occured: " : "", res ? exception2str[res].str : ""); 115 | } 116 | 117 | static int load_general_config(struct ast_config *cfg) 118 | { 119 | struct ast_variable *v; 120 | int res = 0; 121 | ast_debug(2,"Loading config: [general] section\n"); 122 | 123 | for (v = ast_variable_browse(cfg, "general"); v && !res; v = v->next) { 124 | if (!strcasecmp(v->name, "server")) { 125 | ast_debug(4, "Add Server: %s\n", v->value); 126 | if (v->value[0] == '/') { 127 | res |= msq_add_server(NULL, 0, v->value); 128 | } else { 129 | char *valuestr = strdupa(v->value); 130 | char *url = strsep(&valuestr, ":"); 131 | ast_log(LOG_WARNING, "v->value: %s URL:%s, PortStr:%s, Port%d\n", v->value, url, valuestr, atoi(valuestr)); 132 | int port = atoi(valuestr); 133 | res |= msq_add_server(url, port, NULL); 134 | } 135 | ast_debug(4,"Server %s Added\n", v->value); 136 | } else { 137 | ast_log(LOG_WARNING, "Unknown option '%s'\n", v->name); 138 | } 139 | } 140 | ast_debug(2,"Done loading config: [general] section\n"); 141 | return res; 142 | } 143 | 144 | void msq_channel_cb(event_type_t msq_event, void *reply, void *privdata) 145 | { 146 | ast_log(LOG_NOTICE, "msq_channel_cb\n"); 147 | } 148 | 149 | void pbx_channel_cb(event_type_t msq_event, void *reply, void *privdata) 150 | { 151 | ast_log(LOG_NOTICE, "pbx_channel_cb\n"); 152 | } 153 | 154 | static int load_channel_config(struct ast_config *cfg, const char *cat) 155 | { 156 | struct ast_variable *v; 157 | int res = 0; 158 | ast_debug(2,"Loading loading category [%s]\n", cat); 159 | 160 | //lookup by channelname 161 | event_type_t channel = find_event_byname(cat); 162 | if (!channel) { 163 | ast_log(LOG_ERROR, "channel in category:'%s' could not be found\n", cat); 164 | return res!=1; 165 | } 166 | 167 | for (v = ast_variable_browse(cfg, cat); v && !res; v = v->next) { 168 | if (!strcasecmp(v->name, "publish")) { 169 | res |= msq_set_channel(channel, PUBLISH, ast_true(v->value)); 170 | // res |= pbx_set_channel(channel, PUBLISH, ast_true(v->value)); 171 | } else if (!strcasecmp(v->name, "subscribe")) { 172 | res |= msq_set_channel(channel, SUBSCRIBE, ast_true(v->value)); 173 | //res |= pbx_set_channel(channel, SUBSCRIBE, ast_true(v->value)); 174 | } else if (!strcasecmp(v->name, "channel")) { 175 | res |= msq_add_subscription(channel, v->value, "", msq_channel_cb);; 176 | //res |= pbx_set_subscription_cb(channel, 177 | } else if (!strcasecmp(v->name, "device_prefix")) { 178 | res |= 0; 179 | } else if (!strcasecmp(v->name, "dump_state_table_on_connection")) { 180 | res |= 0; 181 | } else { 182 | ast_log(LOG_WARNING, "Unknown option '%s'\n", v->name); 183 | //res = 1; 184 | } 185 | } 186 | ast_debug(2,"Done loading category [%s]\n", cat); 187 | return res; 188 | } 189 | 190 | static int load_config(unsigned int reload) 191 | { 192 | static const char filename[] = "res_redis_v1.conf"; 193 | log_verbose(2, "res_redis: Enter (%s)\n", __PRETTY_FUNCTION__); 194 | struct ast_config *cfg; 195 | const char *cat = NULL; 196 | struct ast_flags config_flags = { 0 }; 197 | int res = 0; 198 | 199 | cfg = ast_config_load(filename, config_flags); 200 | 201 | if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) { 202 | return -1; 203 | } 204 | 205 | if (reload) { 206 | cleanup_module(); 207 | } 208 | 209 | while ((cat = ast_category_browse(cfg, cat))) { 210 | if (!strcasecmp(cat, "general")) { 211 | res = load_general_config(cfg); 212 | } else { 213 | res = load_channel_config(cfg, cat); 214 | } 215 | } 216 | 217 | ast_config_destroy(cfg); 218 | 219 | return res; 220 | } 221 | 222 | static int load_module(void) 223 | { 224 | enum ast_module_load_result res = AST_MODULE_LOAD_FAILURE; 225 | ast_log(LOG_NOTICE,"Loading res_config_redis...\n"); 226 | 227 | ast_eid_to_str(default_eid_str, sizeof(default_eid_str), &ast_eid_default); 228 | if (load_config(0)) { 229 | ast_log(LOG_ERROR,"Declining load of the module, until config issue is resolved\n"); 230 | res = AST_MODULE_LOAD_DECLINE; 231 | goto failed; 232 | } 233 | msq_list_servers(); 234 | msq_list_subscriptions(); 235 | 236 | // start libevent loop 237 | 238 | // dump currently cached events 239 | 240 | // subscribe to channels 241 | 242 | ast_cli_register_multiple(redis_cli, ARRAY_LEN(redis_cli)); 243 | ast_enable_distributed_devstate(); 244 | 245 | msq_start(); 246 | 247 | ast_log(LOG_NOTICE,"res_redis loaded\n"); 248 | return AST_MODULE_LOAD_SUCCESS; 249 | failed: 250 | cleanup_module(); 251 | return res; 252 | } 253 | 254 | static int unload_module(void) 255 | { 256 | ast_debug(1, "Unloading res_config_redis...\n"); 257 | ast_cli_unregister_multiple(redis_cli, ARRAY_LEN(redis_cli)); 258 | 259 | msq_stop(); 260 | cleanup_module(); 261 | 262 | ast_debug(1, "Done Unloading res_config_redis...\n"); 263 | return 0; 264 | } 265 | 266 | static int reload(void) 267 | { 268 | enum ast_module_load_result res = AST_MODULE_LOAD_DECLINE; 269 | ast_debug(1, "Reloading res_redis not implemented yet!...\n"); 270 | ast_mutex_lock(&reload_lock); 271 | load_config(1); 272 | ast_mutex_unlock(&reload_lock); 273 | goto failed; 274 | 275 | failed: 276 | cleanup_module(); 277 | return res; 278 | } 279 | 280 | void _log_verbose(int level, const char *file, int line, const char *function, const char *fmt, ...) 281 | { 282 | va_list ap; 283 | va_start(ap, fmt); 284 | if (level >= 4) { 285 | __ast_verbose_ap(file, line, function, level, NULL, fmt, ap); 286 | } else { 287 | __ast_verbose_ap(file, line, function, level, NULL, fmt, ap); 288 | } 289 | va_end(ap); 290 | } 291 | 292 | static char *redis_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) 293 | { 294 | switch (cmd) { 295 | case CLI_INIT: 296 | e->command = "res_redis config"; 297 | e->usage = 298 | "Usage: res_redis config\n"; 299 | return NULL; 300 | 301 | case CLI_GENERATE: 302 | return NULL; /* no completion */ 303 | } 304 | 305 | if (a->argc != e->args) { 306 | return CLI_SHOWUSAGE; 307 | } 308 | 309 | //if (!event) { 310 | // return CLI_FAILURE; 311 | //} 312 | return CLI_SUCCESS; 313 | } 314 | 315 | static char *redis_ping(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) 316 | { 317 | struct ast_event *event; 318 | 319 | switch (cmd) { 320 | case CLI_INIT: 321 | e->command = "res_redis ping/meet"; 322 | e->usage = 323 | "Usage: res_redis ping/meet\n" 324 | " Send a test ping to the cluster.\n" 325 | "A NOTICE will be in the log for every ping received\n" 326 | "on a server.\n If you send a ping, you should see a NOTICE\n" 327 | "in the log for every server in the cluster.\n"; 328 | return NULL; 329 | 330 | case CLI_GENERATE: 331 | return NULL; /* no completion */ 332 | } 333 | 334 | if (a->argc != e->args) { 335 | return CLI_SHOWUSAGE; 336 | } 337 | 338 | event = ast_event_new(AST_EVENT_PING, AST_EVENT_IE_END); 339 | 340 | if (!event) { 341 | return CLI_FAILURE; 342 | } 343 | 344 | #ifdef HAVE_PBX_STASIS_H 345 | //ast_publish_device_state(); 346 | #else 347 | ast_event_queue_and_cache(event); 348 | #endif 349 | 350 | return CLI_SUCCESS; 351 | } 352 | 353 | 354 | //AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Redis"); 355 | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Redis RealTime PubSub Driver", 356 | .load = load_module, 357 | .unload = unload_module, 358 | .reload = reload, 359 | .load_pri = AST_MODPRI_REALTIME_DRIVER, 360 | ); 361 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_BUILD_TYPE Debug) 2 | 3 | find_package(Threads REQUIRED) 4 | #find_package(GTest REQUIRED) 5 | #find_package(GTest) 6 | 7 | #if(NOT GTEST_FOUND) 8 | # #message(FATAL_ERROR "GTest/GMock was not found") 9 | # message("GTest/GMock was not found") 10 | # return() 11 | #endif() 12 | 13 | include_directories(${GTEST_INCLUDE_DIRS}) 14 | 15 | set(GMOCK_LIBRARIES "gmock") 16 | 17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -W -Wall") 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0") # debug, no optimisation 19 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") # enabling coverage 20 | 21 | # adding source to test executable 22 | add_executable(tests 23 | test.cpp # main 24 | ) 25 | 26 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 27 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib//) 28 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../res_config_redis/) 29 | 30 | target_link_libraries(tests ${GTEST_BOTH_LIBRARIES}) 31 | target_link_libraries(tests ${GMOCK_LIBRARIES}) 32 | target_link_libraries(tests ${CMAKE_THREAD_LIBS_INIT}) 33 | add_test(AllTests tests) 34 | 35 | -------------------------------------------------------------------------------- /tests/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) { 4 | ::testing::InitGoogleTest(&argc, argv); 5 | return RUN_ALL_TESTS(); 6 | } 7 | --------------------------------------------------------------------------------