Commit 14f71420 authored by Anthony Gauchy's avatar Anthony Gauchy
Browse files

HTTP2 client client for Firebase

 - Factorize a HTTP2 client out of Apple PNR client
 - Modification of Apple and Firebase client to use this new Http2Client
 - Http2Client now contains a by request timeout handling
 - Adding test for Firebase and Apple push
 - Async tls connection handling for http2 clients
 - Tls timeout tests for http2 client
 - CI Update to include unit test
parent 37078008
Pipeline #29324 passed with stages
in 41 minutes and 53 seconds
---
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AccessModifierOffset: -4
AlignAfterOpenBracket: true
AlignEscapedNewlinesLeft: false
AlignOperands: true
......@@ -27,6 +27,7 @@ ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
PointerAlignment: Left
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
......@@ -47,7 +48,6 @@ PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
......@@ -58,7 +58,7 @@ SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
Standard: c++17
TabWidth: 4
UseTab: Always
UseTab: ForIndentation
...
......@@ -78,4 +78,4 @@
</profile>
</scannerConfigBuildInfo>
</storageModule>
</cproject>
</cproject>
\ No newline at end of file
......@@ -72,3 +72,6 @@ test/massregister/users.sql
WORK
**/*.kate*
**/.vscode
### TEST ###
BCUnitAutomated-Results.xml
......@@ -64,7 +64,11 @@ job-debian10-ninja-clang:
CMAKE_GENERATOR: Ninja
CC: clang
CXX: clang++
CMAKE_OPTIONS: -DENABLE_UNIT_TESTS=ON
extends: .job-linux
artifacts:
paths:
- OUTPUT/*
#################################################
# XWiki reference documentation
......@@ -112,3 +116,26 @@ job-debian10-deb-deploy:
dependencies:
- job-debian10-deb
#################################################
# UNIT TESTS
#################################################
job-debian10-unit-test:
stage : test
tags: [ "docker-debian10" ]
image: gitlab.linphone.org:4567/bc/public/flexisip/bc-dev-debian10:$DEBIAN_10_IMAGE_VERSION
except:
variables:
- $DEPLOY_RUN
- $DEPLOY_UBUNTU
needs :
- job-debian10-ninja-clang
script :
- export LD_LIBRARY_PATH=/usr/local/lib
- ./OUTPUT/bin/flexisip_tester --verbose --parallel || exit $?
artifacts :
reports :
junit :
- BCUnitAutomated-Results.xml
\ No newline at end of file
......@@ -14,7 +14,7 @@ variables:
CENTOS_7_IMAGE_VERSION: 20210421_python3
CENTOS_8_IMAGE_VERSION: 20210421_python3
DEBIAN_9_IMAGE_VERSION: 20210421_python3
DEBIAN_10_IMAGE_VERSION: 20210421_python3
DEBIAN_10_IMAGE_VERSION: 20210503_asio_unit_test
UBUNTU_18_04_IMAGE_VERSION: 20210421_python3
UBUNTU_ROLLING_IMAGE_VERSION: 20210419_python3
......
......@@ -47,7 +47,7 @@ option(ENABLE_TRANSCODER "Build transcoder support" YES)
option(ENABLE_MDNS "Build multicast DNS support" NO)
option(ENABLE_EXTERNAL_AUTH_PLUGIN "Enable ExternalAuth plugin support" NO)
option(ENABLE_JWE_AUTH_PLUGIN "Enable JweAuth plugin support" NO)
option(ENABLE_UNIT_TESTS "Enable flexisip unit tests (low level tests)" ON)
option(ENABLE_UNIT_TESTS "Enable flexisip unit tests (low level tests)" NO)
option(ENABLE_PACKAGE_SOURCE "Create 'package_source' target for source archive making (CMake >= 3.11)" OFF)
cmake_dependent_option(ENABLE_SPECIFIC_FEATURES "Enable mediarelay specific features" OFF "ENABLE_TRANSCODER" OFF)
......@@ -70,6 +70,10 @@ set(SYSCONF_INSTALL_DIR ${CMAKE_INSTALL_FULL_SYSCONFDIR} CACHE STRING "Config di
set(CONFIG_DIR ${SYSCONF_INSTALL_DIR}/flexisip)
message(STATUS "Config dir: ${CONFIG_DIR}")
set(INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
if(ENABLE_UNIT_TESTS)
set(TESTER_DATA_DIR ${CMAKE_INSTALL_PREFIX}/share/flexisip-tester)
endif()
function(FIND_PROGRAM_REQUIRED varname progname)
find_program(${varname} NAMES "${progname}")
......
############################################################################
# FindLibNgHttp2Asio.cmake
# Copyright (C) 2010-2021 Belledonne Communications, Grenoble France
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
############################################################################
#
# Find libnghttp2_asio and defined the associated target.
#
# Target name: LibNgHttp2Asio
include(FindPackageHandleStandardArgs)
find_path(LIBNGHTTP2ASIO_INCLUDE_DIR nghttp2/asio_http2.h)
find_library(LIBNGHTTP2ASIO_LIBRARY nghttp2_asio)
find_package_handle_standard_args(LibNgHttp2Asio REQUIRED_VARS LIBNGHTTP2ASIO_INCLUDE_DIR LIBNGHTTP2_LIBRARY)
if (LIBNGHTTP2ASIO_FOUND)
add_library(LibNgHttp2Asio SHARED IMPORTED)
set_target_properties(LibNgHttp2Asio PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LIBNGHTTP2ASIO_INCLUDE_DIR}"
IMPORTED_LOCATION "${LIBNGHTTP2ASIO_LIBRARY}"
)
endif()
unset(NGHTTP2_ASIO_INCLUDE_DIR)
unset(NGHTTP2_ASIO_LIBRARY)
\ No newline at end of file
......@@ -2,6 +2,8 @@
/* flexisip-config.h.in. Generated from configure.ac by autoheader. */
#cmakedefine CONFIG_DIR "${CONFIG_DIR}"
#cmakedefine INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}"
#cmakedefine TESTER_DATA_DIR "${TESTER_DATA_DIR}"
#cmakedefine ENABLE_SNMP 1
#cmakedefine ENABLE_LIBODB_MYSQL 1
......
......@@ -3,7 +3,7 @@ FROM gitlab.linphone.org:4567/bc/public/linphone-sdk/bc-dev-debian10:20210421_py
MAINTAINER François Grisez <francois.grisez@belledonne-communications.com>
# Add extra dependencies for Flexisip
RUN sudo su -c 'apt-get -y update && apt-get -y install libjansson-dev libmariadb-dev-compat libnghttp2-dev libprotobuf-dev libsnmp-dev protobuf-compiler && apt-get -y clean'
RUN sudo su -c 'apt-get -y update && apt-get -y install libjansson-dev libmariadb-dev-compat libnghttp2-dev libprotobuf-dev libsnmp-dev protobuf-compiler wget libboost-dev libboost-system-dev libboost-thread-dev && apt-get -y clean'
# Configure OpenSSL policy
# Setting 'CipherString' to 'DEFAULT@SECLEVEL=1' forces libssl to allow
......@@ -13,3 +13,14 @@ RUN sudo su -c 'apt-get -y update && apt-get -y install libjansson-dev libmariad
# Please keep this modification until the CA certificates has been updated
# in each project.
RUN sudo sed -i -E 's/^CipherString[ ]*=(.*)$/CipherString = DEFAULT@SECLEVEL=1/' /etc/ssl/openssl.cnf
# Install libnghttp2_asio
RUN wget https://github.com/nghttp2/nghttp2/releases/download/v1.36.0/nghttp2-1.36.0.tar.bz2 && \
tar xf nghttp2-1.36.0.tar.bz2 && \
cd nghttp2-1.36.0 && \
./configure --prefix=/usr/local --disable-examples --disable-python-bindings --enable-lib-only --enable-asio-lib && \
make && \
sudo make -C src install && \
cd - && \
rm -rf nghttp2-1.36.0.tar.bz2 nghttp2-1.36.0
\ No newline at end of file
Subproject commit 9f80c5272081c59d7101e085a84ee67e2c8ab3af
Subproject commit 9753d96a299dbb53718efd216ab271eb534c511f
############################################################################
# CMakeLists.txt
# Copyright (C) 2010-2020 Belledonne Communications, Grenoble France
# Copyright (C) 2010-2021 Belledonne Communications, Grenoble France
#
############################################################################
#
......@@ -88,10 +88,13 @@ set(FLEXISIP_SOURCES
module.cc
monitor.cc
plugin/plugin-loader.cc
pushnotification/applepush.cc
pushnotification/client.cc
pushnotification/firebasepush.cc
pushnotification/apple/apple-client.cc pushnotification/apple/apple-client.hh
pushnotification/apple/apple-request.cc pushnotification/apple/apple-request.hh
pushnotification/firebase/firebase-client.cc pushnotification/firebase/firebase-client.hh
pushnotification/firebase/firebase-request.cc pushnotification/firebase/firebase-request.hh
pushnotification/client.hh
pushnotification/genericpush.cc
pushnotification/legacy-client.cc pushnotification/legacy-client.hh
pushnotification/microsoftpush.cc
pushnotification/request.cc
pushnotification/service.cc
......@@ -114,6 +117,13 @@ set(FLEXISIP_SOURCES
utils/string-utils.cc
utils/threadpool.cc
utils/timer.cc
utils/transport/http/http2client.cc utils/transport/http/http2client.hh
utils/transport/http/http-headers.cc utils/transport/http/http-headers.hh
utils/transport/http/http-message-context.hh
utils/transport/http/http-message.cc utils/transport/http/http-message.hh
utils/transport/http/http-response.cc utils/transport/http/http-response.hh
utils/transport/http/ng-data-provider.cc utils/transport/http/ng-data-provider.hh
utils/transport/tls-connection.cc utils/transport/tls-connection.hh
utils/uri-utils.cc
)
......@@ -156,6 +166,10 @@ if(ENABLE_REDIS)
endif()
if(ENABLE_PROTOBUF)
#include(FindProtobuf)
#find_package(Protobuf REQUIRED)
#include_directories(${PROTOBUF_INCLUDE_DIR})
# TODO: generate the protobuf wrapper
# protoc --proto_path=$(builddir) --cpp_out=$(builddir) $(pb_files)
set(PROTOBUF_GENERATED_FILES ${CMAKE_CURRENT_BINARY_DIR}/recordserializer-protobuf.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/recordserializer-protobuf.pb.h)
......@@ -312,6 +326,7 @@ if(FLEXISIP_DEPENDENCIES)
endif()
add_dependencies(flexisip flexisip-git-version)
target_compile_features(flexisip PRIVATE cxx_auto_type cxx_variadic_macros)
#target_link_libraries(flexisip PUBLIC ${FLEXISIP_PUBLIC_LIBS} PRIVATE ${FLEXISIP_LIBS} ${PROTOBUF_LIBRARY})
target_link_libraries(flexisip PUBLIC ${FLEXISIP_PUBLIC_LIBS} PRIVATE ${FLEXISIP_LIBS})
target_include_directories(flexisip PRIVATE ${FLEXISIP_INCLUDES})
......
/*
Flexisip, a flexible SIP proxy server with media capabilities.
Copyright (C) 2010-2021 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <flexisip/logmanager.hh>
#include "utils/string-utils.hh"
#include "apple-client.hh"
using namespace std;
namespace flexisip {
namespace pushnotification {
std::string AppleClient::APN_DEV_ADDRESS{"api.development.push.apple.com"};
std::string AppleClient::APN_PROD_ADDRESS{"api.push.apple.com"};
std::string AppleClient::APN_PORT{"443"};
AppleClient::AppleClient(su_root_t& root, const string& trustStorePath, const string& certPath,
const string& certName) {
ostringstream os{};
os << "AppleClient[" << this << "]";
mLogPrefix = os.str();
SLOGD << mLogPrefix << ": constructing AppleClient";
const auto apn_server = (certName.find(".dev") != string::npos) ? APN_DEV_ADDRESS : APN_PROD_ADDRESS;
mHttp2Client = make_unique<Http2Client>(root, apn_server, APN_PORT, trustStorePath, certPath);
}
void AppleClient::sendPush(const std::shared_ptr<Request>& req) {
auto appleReq = dynamic_pointer_cast<AppleRequest>(req);
auto topicLen = appleReq->getAppIdentifier().rfind(".");
auto apnsTopic = appleReq->getAppIdentifier().substr(0, topicLen);
// Check whether the appId is compatible with the payload type
auto endsWithVoip = StringUtils::endsWith(apnsTopic, ".voip");
if ((appleReq->mPayloadType == ApplePushType::Pushkit && !endsWithVoip) ||
(appleReq->mPayloadType != ApplePushType::Pushkit && endsWithVoip)) {
SLOGE << mLogPrefix << ": apns-topic [" << apnsTopic << "] not compatible with payload type ["
<< toString(appleReq->mPayloadType) << "]. Aborting";
appleReq->setState(Request::State::Failed);
return;
}
auto host = mHttp2Client->getHost();
appleReq->getHeaders().add("host", host);
appleReq->setState(Request::State::InProgress);
mHttp2Client->send(
appleReq, [this](const auto& req, const auto& resp) { this->onResponse(req, resp); },
[this](const auto& req) { this->onError(req); });
}
void AppleClient::onResponse(const std::shared_ptr<HttpMessage>& request,
const std::shared_ptr<HttpResponse>& response) {
SLOGD << mLogPrefix << ": onResponseCb " << response->toString();
auto appleReq = dynamic_pointer_cast<AppleRequest>(request);
if (response->getStatusCode() == 200) {
appleReq->setState(Request::State::Successful);
} else {
appleReq->setState(Request::State::Failed);
}
}
void AppleClient::onError(const std::shared_ptr<HttpMessage>& request) {
SLOGD << mLogPrefix << ": onErrorCb";
auto appleReq = dynamic_pointer_cast<AppleRequest>(request);
appleReq->setState(Request::State::Failed);
}
} // namespace pushnotification
} // namespace flexisip
/*
Flexisip, a flexible SIP proxy server with media capabilities.
Copyright (C) 2010-2021 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include "apple-request.hh"
#include "pushnotification/client.hh"
#include "utils/transport/http/http-message.hh"
#include "utils/transport/http/http-response.hh"
#include "utils/transport/http/http2client.hh"
#include "utils/transport/tls-connection.hh"
namespace flexisip {
namespace pushnotification {
/**
* PNR (Push Notification Request) client designed to send push notification to the Apple push API.
*/
class AppleClient : public Client {
public:
AppleClient(su_root_t& root, const std::string& trustStorePath, const std::string& certPath,
const std::string& certName);
/**
* Send the request to the apple PNR service. If the request succeed, if a response is received, the
* AppleClient::onResponse method is called. If the request failed, no response/timeout, tls/handshake errors... the
* AppleClient::onError method is called.
*
* @param req The request to send, this MUST be of AppleRequest type.
*/
void sendPush(const std::shared_ptr<Request>& req) override;
bool isIdle() const noexcept override {
return mHttp2Client->isIdle();
}
void enableInsecureTestMode() {
mHttp2Client->enableInsecureTestMode();
}
static std::string APN_DEV_ADDRESS;
static std::string APN_PORT;
private:
void onResponse(const std::shared_ptr<HttpMessage>& request, const std::shared_ptr<HttpResponse>& response);
void onError(const std::shared_ptr<HttpMessage>& request);
std::unique_ptr<Http2Client> mHttp2Client;
std::string mLogPrefix{};
static std::string APN_PROD_ADDRESS;
};
} // namespace pushnotification
} // namespace flexisip
/*
Flexisip, a flexible SIP proxy server with media capabilities.
Copyright (C) 2010-2021 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <regex>
#include <string>
#include <flexisip/common.hh>
#include "apple-request.hh"
using namespace std;
namespace flexisip {
namespace pushnotification {
AppleRequest::AppleRequest(const PushInfo& info) : Request(info.mAppId, "apple"), mPayloadType{info.mApplePushType} {
const string& deviceToken = info.mDeviceToken;
const string& msg_id = info.mAlertMsgId;
const string& arg = info.mFromName.empty() ? info.mFromUri : info.mFromName;
const string& sound = info.mAlertSound;
const string& callid = info.mCallId;
string date = getPushTimeStamp();
int nwritten = 0;
mBody.assign(MAXPAYLOAD_SIZE + 1, '\0');
mDeviceToken = deviceToken;
checkDeviceToken();
string customPayload = (info.mCustomPayload.empty()) ? "{}" : info.mCustomPayload;
switch (info.mApplePushType) {
case ApplePushType::Unknown:
throw invalid_argument{"Apple push type not set"};
case ApplePushType::Pushkit: {
// We also need msg_id and callid in case the push is received but the device cannot register
constexpr auto rawPayload = R"json({
"aps": {
"sound": "",
"loc-key": "%s",
"loc-args": ["%s"],
"call-id": "%s",
"uuid": %s,
"send-time": "%s"
},
"from-uri": "%s",
"display-name": "%s",
"pn_ttl": %d,
"customPayload": %s
})json";
nwritten = snprintf(mBody.data(), mBody.size(), rawPayload, msg_id.c_str(), arg.c_str(), callid.c_str(),
quoteStringIfNeeded(info.mUid).c_str(), date.c_str(), info.mFromUri.c_str(),
info.mFromName.c_str(), info.mTtl, customPayload.c_str());
break;
}
case ApplePushType::Background: {
// Use a normal push notification with content-available set to 1, no alert, no sound.
constexpr auto rawPayload = R"json({
"aps": {
"badge": 0,
"content-available": 1,
"loc-key": "%s",
"loc-args": ["%s"],
"call-id": "%s",
"uuid": %s,
"send-time": "%s"
},
"from-uri": "%s",
"display-name": "%s",
"pn_ttl": %d,
"customPayload": %s
})json";
nwritten = snprintf(mBody.data(), mBody.size(), rawPayload, msg_id.c_str(), arg.c_str(), callid.c_str(),
quoteStringIfNeeded(info.mUid).c_str(), date.c_str(), info.mFromUri.c_str(),
info.mFromName.c_str(), info.mTtl, customPayload.c_str());
break;
}
case ApplePushType::RemoteBasic: {
/* some apps don't want the push to update the badge - but if they do,
we always put the badge value to 1 because we want to notify the user that
he/she has unread messages even if we do not know the exact count */
constexpr auto rawPayload = R"json({
"aps": {
"alert": {
"loc-key": "%s",
"loc-args": ["%s"]
},
"sound": "%s",
"badge": %d
},
"from-uri": "%s",
"display-name": "%s",
"call-id": "%s",
"pn_ttl": %d,
"uuid": %s,
"send-time": "%s",
"customPayload": %s
})json";
nwritten = snprintf(mBody.data(), mBody.size(), rawPayload, msg_id.c_str(), arg.c_str(), sound.c_str(),
(info.mNoBadge ? 0 : 1), info.mFromUri.c_str(), info.mFromName.c_str(), callid.c_str(),
info.mTtl, quoteStringIfNeeded(info.mUid).c_str(), date.c_str(), customPayload.c_str());
break;
}
case ApplePushType::RemoteWithMutableContent: {
/* some apps don't want the push to update the badge - but if they do,
we always put the badge value to 1 because we want to notify the user that
he/she has unread messages even if we do not know the exact count */
constexpr auto rawPayload = R"json({
"aps": {
"alert": {
"loc-key": "%s",
"loc-args": ["%s"]
},
"sound": "%s",
"mutable-content": 1,
"badge": %d
},
"from-uri": "%s",
"display-name": "%s",
"call-id": "%s",
"pn_ttl": %d,
"uuid": %s,
"send-time": "%s",
"chat-room-addr": "%s",
"customPayload": %s
})json";
nwritten = snprintf(mBody.data(), mBody.size(), rawPayload, msg_id.c_str(), arg.c_str(), sound.c_str(),
(info.mNoBadge ? 0 : 1), info.mFromUri.c_str(), info.mFromName.c_str(), callid.c_str(),
info.mTtl, quoteStringIfNeeded(info.mUid).c_str(), date.c_str(),
info.mChatRoomAddr.c_str(), customPayload.c_str());
break;
}
}
SLOGD << "Apple PNR " << this << " payload is :\n" << mBody.data();
if (nwritten < 0 || unsigned(nwritten) >= mBody.size()) {
SLOGE << "Apple PNR " << this << " cannot be sent because the payload size is higher than " << MAXPAYLOAD_SIZE;
mBody.clear();
return;
}
mBody.resize(nwritten);
auto expire = 0;
if (info.mTtl > 0) {
expire = time(nullptr) + info.mTtl;
}
auto path = string{"/3/device/"} + mDeviceToken;
auto topicLen = mAppId.rfind(".");
auto apnsTopic = mAppId.substr(0, topicLen);
HttpHeaders headers{};
headers.add(":method", "POST");
headers.add(":scheme", "https");
headers.add(":path", move(path));
headers.add("apns-expiration", to_string(expire));
headers.add("apns-topic", apnsTopic);
headers.add("apns-push-type", pushTypeToApnsPushType(mPayloadType));
this->setHeaders(headers);
SLOGD << "Apple PNR " << this << " https headers are :