2017-04-05 03:46:16 +02:00
|
|
|
/*
|
|
|
|
Copyright 2016-2017 Daniil Gentili
|
|
|
|
(https://daniil.it)
|
|
|
|
This file is part of php-libtgvoip.
|
2017-07-06 18:12:49 +02:00
|
|
|
php-libtgvoip 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.
|
2019-10-28 14:10:39 +01:00
|
|
|
php-libtgvoip 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.
|
2017-04-05 03:46:16 +02:00
|
|
|
See the GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along with php-libtgvoip.
|
|
|
|
If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2017-07-01 15:12:52 +02:00
|
|
|
#include "main.h"
|
2017-07-01 19:40:28 +02:00
|
|
|
#include <string.h>
|
|
|
|
#include <wchar.h>
|
|
|
|
#include <map>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
2017-07-11 21:11:23 +02:00
|
|
|
#include <queue>
|
2017-07-01 19:40:28 +02:00
|
|
|
|
|
|
|
#include "libtgvoip/VoIPServerConfig.h"
|
2020-03-01 16:30:57 +01:00
|
|
|
#include "libtgvoip/tools/threading.h"
|
|
|
|
#include "libtgvoip/tools/logging.h"
|
2017-07-01 19:40:28 +02:00
|
|
|
|
2017-06-11 18:13:25 +02:00
|
|
|
using namespace tgvoip;
|
2017-06-22 14:48:52 +02:00
|
|
|
using namespace tgvoip::audio;
|
2017-07-05 23:24:07 +02:00
|
|
|
using namespace std;
|
2017-04-05 03:46:16 +02:00
|
|
|
|
2017-07-18 19:16:27 +02:00
|
|
|
void VoIP::__construct(Php::Parameters ¶ms)
|
2017-07-06 18:12:49 +02:00
|
|
|
{
|
2017-07-18 19:16:27 +02:00
|
|
|
Php::Value self(this);
|
|
|
|
Php::Array empty;
|
|
|
|
|
2017-08-05 17:35:12 +02:00
|
|
|
self["configuration"]["endpoints"] = empty;
|
2017-07-18 19:16:27 +02:00
|
|
|
self["storage"] = empty;
|
|
|
|
self["internalStorage"] = empty;
|
|
|
|
|
|
|
|
self["internalStorage"]["creator"] = params[0];
|
2018-09-02 20:12:51 +02:00
|
|
|
otherID = (int)params[1];
|
2019-03-29 14:07:22 +01:00
|
|
|
self["madeline"] = params[2];
|
|
|
|
callState = (int)params[3];
|
2017-07-18 19:16:27 +02:00
|
|
|
|
|
|
|
initVoIPController();
|
|
|
|
}
|
2019-03-29 14:07:22 +01:00
|
|
|
void VoIP::setCall(Php::Parameters ¶ms)
|
|
|
|
{
|
|
|
|
Php::Array empty;
|
|
|
|
Php::Value self(this);
|
|
|
|
Php::Value call = params[0];
|
|
|
|
Php::Value callId = call["id"];
|
|
|
|
Php::Value callAccessHash = call["access_hash"];
|
|
|
|
Php::Value protocol = params[0]["protocol"];
|
2020-03-01 16:30:57 +01:00
|
|
|
|
|
|
|
Php::Array callArray;
|
|
|
|
callArray["_"] = "inputPhoneCall";
|
|
|
|
callArray["id"] = callId;
|
|
|
|
callArray["access_hash"] = callAccessHash;
|
|
|
|
|
|
|
|
self["internalStorage"]["callID"] = callArray;
|
2019-03-29 14:07:22 +01:00
|
|
|
self["internalStorage"]["protocol"] = protocol;
|
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
void VoIP::initVoIPController()
|
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
inst = std::make_unique<VoIPController>();
|
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
outputFile = NULL;
|
2017-07-20 16:24:17 +02:00
|
|
|
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->implData = (void *)this;
|
2018-09-02 20:12:51 +02:00
|
|
|
VoIPController::Callbacks callbacks;
|
|
|
|
callbacks.connectionStateChanged = [](VoIPController *controller, int state) {
|
2017-07-09 21:12:01 +02:00
|
|
|
((VoIP *)controller->implData)->state = state;
|
2018-09-02 20:12:51 +02:00
|
|
|
if (state == STATE_FAILED)
|
|
|
|
{
|
2017-07-18 20:46:22 +02:00
|
|
|
((VoIP *)controller->implData)->deinitVoIPController();
|
2017-07-18 19:16:27 +02:00
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
};
|
|
|
|
callbacks.signalBarCountChanged = NULL;
|
|
|
|
callbacks.groupCallKeySent = NULL;
|
|
|
|
callbacks.groupCallKeyReceived = NULL;
|
|
|
|
callbacks.upgradeToGroupCallRequested = NULL;
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->SetCallbacks(callbacks);
|
|
|
|
inst->SetAudioDataCallbacks([this](int16_t *buffer, size_t size) { this->sendAudioFrame(buffer, size); }, [this](int16_t *buffer, size_t size) { this->recvAudioFrame(buffer, size); }, [](int16_t *, size_t) {});
|
2017-07-18 20:46:22 +02:00
|
|
|
}
|
2017-07-18 19:16:27 +02:00
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
void VoIP::deinitVoIPController()
|
|
|
|
{
|
|
|
|
if (callState != CALL_STATE_ENDED)
|
|
|
|
{
|
2017-07-20 16:02:53 +02:00
|
|
|
callState = CALL_STATE_ENDED;
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->Stop();
|
2018-09-02 20:12:51 +02:00
|
|
|
|
|
|
|
while (holdFiles.size())
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
fclose(holdFiles.front());
|
|
|
|
holdFiles.pop();
|
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
while (inputFiles.size())
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
fclose(inputFiles.front());
|
|
|
|
inputFiles.pop();
|
|
|
|
}
|
|
|
|
unsetOutputFile();
|
2018-09-02 20:12:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoIP::recvAudioFrame(int16_t *data, size_t size)
|
|
|
|
{
|
|
|
|
MutexGuard m(outputMutex);
|
|
|
|
if (this->outputFile != NULL)
|
|
|
|
{
|
|
|
|
if (fwrite(data, sizeof(int16_t), size, this->outputFile) != size)
|
|
|
|
{
|
|
|
|
LOGE("COULD NOT WRITE DATA TO FILE");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void VoIP::sendAudioFrame(int16_t *data, size_t size)
|
|
|
|
{
|
|
|
|
MutexGuard m(inputMutex);
|
2017-07-20 16:24:17 +02:00
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
if (!this->inputFiles.empty())
|
|
|
|
{
|
|
|
|
if ((readInput = fread(data, sizeof(int16_t), size, this->inputFiles.front())) != size)
|
|
|
|
{
|
|
|
|
fclose(this->inputFiles.front());
|
|
|
|
this->inputFiles.pop();
|
|
|
|
memset(data + (readInput % size), 0, size - (readInput % size));
|
|
|
|
}
|
|
|
|
this->playing = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->playing = false;
|
|
|
|
if (!this->holdFiles.empty())
|
|
|
|
{
|
|
|
|
if ((readInput = fread(data, sizeof(int16_t), size, this->holdFiles.front())) != size)
|
|
|
|
{
|
|
|
|
fseek(this->holdFiles.front(), 0, SEEK_SET);
|
|
|
|
this->holdFiles.push(this->holdFiles.front());
|
|
|
|
this->holdFiles.pop();
|
|
|
|
memset(data + (readInput % size), 0, size - (readInput % size));
|
|
|
|
}
|
|
|
|
}
|
2017-07-18 20:46:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-21 10:40:29 +02:00
|
|
|
Php::Value VoIP::discard(Php::Parameters ¶ms)
|
2017-07-13 18:03:33 +02:00
|
|
|
{
|
2018-09-02 20:12:51 +02:00
|
|
|
if (callState == CALL_STATE_ENDED)
|
|
|
|
{
|
2017-07-24 18:50:54 +02:00
|
|
|
return false;
|
|
|
|
}
|
2017-07-20 16:02:53 +02:00
|
|
|
Php::Value self(this);
|
2018-09-02 20:12:51 +02:00
|
|
|
if (self["madeline"] && self["madeline"].value().instanceOf("danog\\MadelineProto\\MTProto"))
|
|
|
|
{
|
2017-07-21 00:29:39 +02:00
|
|
|
Php::Array reason;
|
|
|
|
Php::Array rating;
|
|
|
|
Php::Value debug;
|
2018-09-02 20:12:51 +02:00
|
|
|
if (params.size() > 0)
|
|
|
|
{
|
2017-07-23 17:49:47 +02:00
|
|
|
reason = params[0];
|
2018-09-02 20:12:51 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-07-23 17:49:47 +02:00
|
|
|
reason["_"] = "phoneCallDiscardReasonDisconnect";
|
2017-07-21 00:29:39 +02:00
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
if (params.size() > 1)
|
|
|
|
{
|
2017-07-21 00:29:39 +02:00
|
|
|
rating = params[1];
|
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
if (params.size() > 2)
|
|
|
|
{
|
2017-07-21 00:29:39 +02:00
|
|
|
debug = params[2];
|
2018-09-02 20:12:51 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
debug = true;
|
2018-09-04 15:49:20 +02:00
|
|
|
|
2020-03-01 16:30:57 +01:00
|
|
|
if (self["internalStorage"]["callID"].value()) {
|
|
|
|
Php::Value res = self["madeline"].value().call("discardCallFrom", self, self["internalStorage"]["callID"].value(), reason, rating, debug);
|
|
|
|
deinitVoIPController();
|
|
|
|
return res;
|
|
|
|
}
|
2017-07-20 16:02:53 +02:00
|
|
|
}
|
2017-07-20 19:32:03 +02:00
|
|
|
deinitVoIPController();
|
2017-07-21 10:40:29 +02:00
|
|
|
return this;
|
2017-07-13 18:03:33 +02:00
|
|
|
}
|
|
|
|
|
2017-07-21 10:40:29 +02:00
|
|
|
Php::Value VoIP::accept()
|
2017-07-18 20:46:22 +02:00
|
|
|
{
|
2018-09-02 20:12:51 +02:00
|
|
|
if (callState != CALL_STATE_INCOMING)
|
|
|
|
return false;
|
2017-07-22 00:29:47 +02:00
|
|
|
callState = CALL_STATE_ACCEPTED;
|
2017-07-18 21:48:26 +02:00
|
|
|
Php::Value self(this);
|
2020-03-01 16:30:57 +01:00
|
|
|
|
|
|
|
Php::Array method;
|
|
|
|
method[0] = self["madeline"].value();
|
|
|
|
method[1] = "acceptCallFrom";
|
|
|
|
|
|
|
|
return Php::call("call_user_func", method, self, self["internalStorage"].value()["callID"].value());
|
2017-07-18 20:46:22 +02:00
|
|
|
}
|
|
|
|
|
2017-09-08 14:04:56 +02:00
|
|
|
Php::Value VoIP::close()
|
|
|
|
{
|
2018-09-02 20:12:51 +02:00
|
|
|
deinitVoIPController();
|
|
|
|
return this;
|
2017-09-08 14:04:56 +02:00
|
|
|
}
|
2017-07-18 20:46:22 +02:00
|
|
|
|
2017-07-18 16:17:03 +02:00
|
|
|
void VoIP::__wakeup()
|
|
|
|
{
|
2017-07-30 19:50:35 +02:00
|
|
|
callState = CALL_STATE_ENDED;
|
2017-07-18 16:17:03 +02:00
|
|
|
}
|
|
|
|
|
2017-07-21 10:40:29 +02:00
|
|
|
Php::Value VoIP::__sleep()
|
2017-07-20 16:02:53 +02:00
|
|
|
{
|
2017-07-31 16:49:30 +02:00
|
|
|
Php::Array res;
|
2017-07-21 10:40:29 +02:00
|
|
|
return res;
|
2017-07-20 16:02:53 +02:00
|
|
|
}
|
|
|
|
|
2017-07-31 20:54:34 +02:00
|
|
|
Php::Value VoIP::startTheMagic()
|
2017-07-01 15:12:52 +02:00
|
|
|
{
|
2018-09-02 20:12:51 +02:00
|
|
|
if (state == STATE_WAIT_INIT_ACK)
|
|
|
|
{
|
2018-03-29 15:33:40 +02:00
|
|
|
Php::Value self(this);
|
2018-09-02 20:12:51 +02:00
|
|
|
if (!self["configuration"])
|
|
|
|
{
|
2017-07-31 20:54:34 +02:00
|
|
|
return false;
|
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
if (self["madeline"].value().instanceOf("danog\\MadelineProto\\MTProto"))
|
|
|
|
{
|
2017-07-31 20:54:34 +02:00
|
|
|
Php::Array reason;
|
|
|
|
Php::Array rating;
|
|
|
|
Php::Value debug;
|
|
|
|
reason["_"] = "phoneCallDiscardReasonDisconnect";
|
|
|
|
debug = false;
|
2020-03-01 16:30:57 +01:00
|
|
|
self["madeline"].value().call("discardCallFrom", self, self["internalStorage"].value()["callID"].value(), reason, rating, debug);
|
2017-07-31 20:54:34 +02:00
|
|
|
}
|
|
|
|
deinitVoIPController();
|
|
|
|
return false;
|
|
|
|
}
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->Start();
|
|
|
|
inst->Connect();
|
2018-03-29 15:33:40 +02:00
|
|
|
Php::Value self(this);
|
2018-09-02 20:12:51 +02:00
|
|
|
self["internalStorage"]["created"] = (int64_t)time(NULL);
|
2017-07-20 16:02:53 +02:00
|
|
|
callState = CALL_STATE_READY;
|
2017-07-31 20:54:34 +02:00
|
|
|
return true;
|
2017-07-01 15:12:52 +02:00
|
|
|
}
|
2017-07-14 21:42:40 +02:00
|
|
|
|
2017-07-18 19:16:27 +02:00
|
|
|
Php::Value VoIP::whenCreated()
|
2017-07-18 16:17:03 +02:00
|
|
|
{
|
2017-07-18 21:48:26 +02:00
|
|
|
|
|
|
|
Php::Value self(this);
|
2020-03-01 16:30:57 +01:00
|
|
|
if (self["internalStorage"].value()["created"])
|
2018-09-02 20:12:51 +02:00
|
|
|
{
|
2020-03-01 16:30:57 +01:00
|
|
|
return self["internalStorage"].value()["created"];
|
2017-07-20 16:02:53 +02:00
|
|
|
}
|
|
|
|
return false;
|
2017-07-18 19:16:27 +02:00
|
|
|
}
|
|
|
|
Php::Value VoIP::isCreator()
|
|
|
|
{
|
2017-07-18 21:48:26 +02:00
|
|
|
|
|
|
|
Php::Value self(this);
|
2020-03-01 16:30:57 +01:00
|
|
|
return self["internalStorage"].value()["creator"];
|
2017-07-18 19:16:27 +02:00
|
|
|
}
|
|
|
|
Php::Value VoIP::getOtherID()
|
|
|
|
{
|
2017-08-03 18:34:53 +02:00
|
|
|
return otherID;
|
2017-07-18 19:16:27 +02:00
|
|
|
}
|
2017-07-26 08:31:52 +02:00
|
|
|
Php::Value VoIP::setMadeline(Php::Parameters ¶ms)
|
2017-07-24 18:50:54 +02:00
|
|
|
{
|
|
|
|
Php::Value self(this);
|
2017-07-26 08:31:52 +02:00
|
|
|
return self["madeline"] = params[0];
|
2017-07-24 18:50:54 +02:00
|
|
|
}
|
2017-07-19 12:42:01 +02:00
|
|
|
Php::Value VoIP::getProtocol()
|
|
|
|
{
|
|
|
|
Php::Value self(this);
|
2020-03-01 16:30:57 +01:00
|
|
|
return self["internalStorage"].value()["protocol"];
|
2017-07-19 12:42:01 +02:00
|
|
|
}
|
2017-07-18 19:16:27 +02:00
|
|
|
Php::Value VoIP::getCallID()
|
|
|
|
{
|
2017-07-18 21:48:26 +02:00
|
|
|
Php::Value self(this);
|
2020-03-01 16:30:57 +01:00
|
|
|
return self["internalStorage"].value()["callID"].value();
|
2017-07-18 16:17:03 +02:00
|
|
|
}
|
|
|
|
|
2017-07-18 19:16:27 +02:00
|
|
|
Php::Value VoIP::getCallState()
|
2017-07-18 16:17:03 +02:00
|
|
|
{
|
2017-07-20 16:02:53 +02:00
|
|
|
return callState;
|
2017-07-18 19:16:27 +02:00
|
|
|
}
|
2017-07-18 16:17:03 +02:00
|
|
|
|
2017-07-18 19:16:27 +02:00
|
|
|
Php::Value VoIP::getVisualization()
|
2017-07-01 15:12:52 +02:00
|
|
|
{
|
2017-07-18 21:48:26 +02:00
|
|
|
Php::Value self(this);
|
2020-03-01 16:30:57 +01:00
|
|
|
Php::Value internalStorage = self["internalStorage"].value();
|
|
|
|
if (internalStorage["visualization"])
|
2018-09-02 20:12:51 +02:00
|
|
|
{
|
2020-03-01 16:30:57 +01:00
|
|
|
return internalStorage["visualization"].value();
|
2017-07-18 19:16:27 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
void VoIP::setVisualization(Php::Parameters ¶ms)
|
|
|
|
{
|
2017-07-18 21:48:26 +02:00
|
|
|
Php::Value self(this);
|
2020-03-01 16:30:57 +01:00
|
|
|
Php::Value internalStorage = self["internalStorage"].value();
|
|
|
|
internalStorage["visualization"] = params[0];
|
|
|
|
self["internalStorage"] = internalStorage;
|
2017-07-14 21:42:40 +02:00
|
|
|
}
|
2017-07-18 16:17:03 +02:00
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
void VoIP::parseConfig()
|
|
|
|
{
|
2017-07-18 21:48:26 +02:00
|
|
|
Php::Value self(this);
|
2018-09-02 20:12:51 +02:00
|
|
|
if (!self["configuration"]["auth_key"])
|
|
|
|
return;
|
|
|
|
|
2020-03-01 16:30:57 +01:00
|
|
|
Php::Value config = self["configuration"].value();
|
|
|
|
Php::Value internalStorage = self["internalStorage"].value();
|
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
VoIPController::Config cfg;
|
2020-03-01 16:30:57 +01:00
|
|
|
cfg.recvTimeout = (double)config["recv_timeout"];
|
|
|
|
cfg.initTimeout = (double)config["init_timeout"];
|
|
|
|
cfg.dataSaving = (int)config["data_saving"];
|
|
|
|
cfg.enableAEC = (bool)config["enable_AEC"];
|
|
|
|
cfg.enableNS = (bool)config["enable_NS"];
|
|
|
|
cfg.enableAGC = (bool)config["enable_AGC"];
|
|
|
|
cfg.enableCallUpgrade = (bool)config["enable_call_upgrade"];
|
|
|
|
|
|
|
|
if (config["log_file_path"])
|
2017-07-14 21:42:40 +02:00
|
|
|
{
|
2020-03-01 16:30:57 +01:00
|
|
|
std::string logFilePath = config["log_file_path"];
|
2018-09-02 20:12:51 +02:00
|
|
|
cfg.logFilePath = logFilePath;
|
2017-07-14 21:42:40 +02:00
|
|
|
}
|
|
|
|
|
2020-03-01 16:30:57 +01:00
|
|
|
if (config["stats_dump_file_path"])
|
2017-07-14 21:42:40 +02:00
|
|
|
{
|
2020-03-01 16:30:57 +01:00
|
|
|
std::string statsDumpFilePath = config["stats_dump_file_path"];
|
2018-09-02 20:12:51 +02:00
|
|
|
cfg.statsDumpFilePath = statsDumpFilePath;
|
2017-07-14 21:42:40 +02:00
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
|
2020-03-01 16:30:57 +01:00
|
|
|
if (config["shared_config"])
|
2019-03-29 14:07:22 +01:00
|
|
|
{
|
2020-03-01 16:30:57 +01:00
|
|
|
Php::Value shared_config = config["shared_config"];
|
2019-03-29 20:00:38 +01:00
|
|
|
ServerConfig::GetSharedInstance()->Update(Php::call("json_encode", Php::call("array_merge", Php::call("\\danog\\MadelineProto\\VoIPServerConfig::getFinal"), shared_config)));
|
2019-03-29 14:07:22 +01:00
|
|
|
}
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->SetConfig(cfg);
|
2017-07-14 21:42:40 +02:00
|
|
|
|
2020-03-01 16:30:57 +01:00
|
|
|
/*char *key = (char *)malloc(256);
|
|
|
|
memcpy(key, config["auth_key"], 256);*/
|
|
|
|
std::string strKey = config["auth_key"];
|
|
|
|
std::vector<uint8_t> key(strKey.begin(), strKey.end());
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->SetEncryptionKey(key, (bool)internalStorage["creator"]);
|
2017-04-05 03:46:16 +02:00
|
|
|
|
2017-07-14 21:42:40 +02:00
|
|
|
vector<Endpoint> eps;
|
2020-03-01 16:30:57 +01:00
|
|
|
Php::Value endpoints = config["endpoints"];
|
2017-07-18 16:17:03 +02:00
|
|
|
for (int i = 0; i < endpoints.size(); i++)
|
2017-07-01 15:12:52 +02:00
|
|
|
{
|
2017-07-18 16:17:03 +02:00
|
|
|
string ip = endpoints[i]["ip"];
|
|
|
|
string ipv6 = endpoints[i]["ipv6"];
|
2017-07-19 12:42:01 +02:00
|
|
|
string peer_tag = endpoints[i]["peer_tag"];
|
2017-04-05 03:46:16 +02:00
|
|
|
|
2017-07-14 21:42:40 +02:00
|
|
|
IPv4Address v4addr(ip);
|
|
|
|
IPv6Address v6addr("::0");
|
2018-09-02 20:12:51 +02:00
|
|
|
unsigned char *pTag = (unsigned char *)malloc(16);
|
2017-04-05 03:46:16 +02:00
|
|
|
|
2017-07-01 15:12:52 +02:00
|
|
|
if (ipv6 != "")
|
|
|
|
{
|
|
|
|
v6addr = IPv6Address(ipv6);
|
2017-04-05 03:46:16 +02:00
|
|
|
}
|
2017-06-11 18:13:25 +02:00
|
|
|
|
2017-07-19 12:42:01 +02:00
|
|
|
if (peer_tag != "")
|
2017-07-01 15:12:52 +02:00
|
|
|
{
|
2017-07-19 12:42:01 +02:00
|
|
|
memcpy(pTag, peer_tag.c_str(), 16);
|
2017-06-11 18:13:25 +02:00
|
|
|
}
|
2019-03-29 14:07:22 +01:00
|
|
|
eps.push_back(Endpoint(endpoints[i]["id"], (int32_t)endpoints[i]["port"], v4addr, v6addr, Endpoint::Type::UDP_RELAY, pTag));
|
2017-07-13 18:03:33 +02:00
|
|
|
free(pTag);
|
2017-04-05 03:46:16 +02:00
|
|
|
}
|
2017-07-23 17:49:47 +02:00
|
|
|
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->SetRemoteEndpoints(eps, (bool)internalStorage["protocol"]["udp_p2p"], (int)internalStorage["protocol"]["max_layer"]);
|
|
|
|
inst->SetNetworkType(config["network_type"]);
|
2020-03-01 16:30:57 +01:00
|
|
|
if (config["proxy"])
|
2018-09-02 20:12:51 +02:00
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->SetProxy(config["proxy"]["protocol"], config["proxy"]["address"], (int32_t)config["proxy"]["port"], config["proxy"]["username"], config["proxy"]["password"]);
|
2017-07-18 19:16:27 +02:00
|
|
|
}
|
2017-07-01 15:12:52 +02:00
|
|
|
}
|
2017-07-11 21:11:23 +02:00
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
Php::Value VoIP::unsetOutputFile()
|
|
|
|
{
|
|
|
|
if (outputFile == NULL)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
MutexGuard m(outputMutex);
|
2017-07-20 19:32:03 +02:00
|
|
|
fflush(outputFile);
|
2017-07-20 16:24:17 +02:00
|
|
|
fclose(outputFile);
|
|
|
|
outputFile = NULL;
|
2017-07-23 17:49:47 +02:00
|
|
|
|
2017-07-20 19:32:03 +02:00
|
|
|
return this;
|
2017-07-20 16:24:17 +02:00
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
Php::Value VoIP::setOutputFile(Php::Parameters ¶ms)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
MutexGuard m(outputMutex);
|
|
|
|
|
|
|
|
if (outputFile != NULL)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
fclose(outputFile);
|
2018-09-02 20:12:51 +02:00
|
|
|
outputFile = NULL;
|
2017-07-20 16:24:17 +02:00
|
|
|
}
|
|
|
|
outputFile = fopen(params[0], "wb");
|
2018-09-02 20:12:51 +02:00
|
|
|
if (outputFile == NULL)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
throw Php::Exception("Could not open file!");
|
|
|
|
return false;
|
|
|
|
}
|
2017-07-20 19:32:03 +02:00
|
|
|
return this;
|
2017-07-11 21:11:23 +02:00
|
|
|
}
|
2017-07-20 16:24:17 +02:00
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
Php::Value VoIP::play(Php::Parameters ¶ms)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
FILE *tmp = fopen(params[0], "rb");
|
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
if (tmp == NULL)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
throw Php::Exception("Could not open file!");
|
|
|
|
return false;
|
2017-07-11 21:11:23 +02:00
|
|
|
}
|
2017-07-20 16:24:17 +02:00
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
MutexGuard m(inputMutex);
|
2017-07-20 16:24:17 +02:00
|
|
|
inputFiles.push(tmp);
|
|
|
|
|
|
|
|
return this;
|
2017-07-11 21:11:23 +02:00
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
Php::Value VoIP::playOnHold(Php::Parameters ¶ms)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
FILE *tmp = NULL;
|
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
MutexGuard m(inputMutex);
|
|
|
|
while (holdFiles.size())
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
fclose(holdFiles.front());
|
|
|
|
holdFiles.pop();
|
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
for (int i = 0; i < params[0].size(); i++)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
tmp = fopen(params[0][i], "rb");
|
2018-09-02 20:12:51 +02:00
|
|
|
if (tmp == NULL)
|
|
|
|
{
|
2017-07-20 16:24:17 +02:00
|
|
|
throw Php::Exception("Could not open file!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
holdFiles.push(tmp);
|
|
|
|
}
|
2017-07-20 19:32:03 +02:00
|
|
|
return this;
|
2017-07-11 21:11:23 +02:00
|
|
|
}
|
|
|
|
|
2017-07-22 16:25:33 +02:00
|
|
|
Php::Value VoIP::setMicMute(Php::Parameters ¶ms)
|
2017-07-01 15:12:52 +02:00
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->SetMicMute((bool)params[0]);
|
2017-08-03 17:30:54 +02:00
|
|
|
return this;
|
2017-07-01 15:12:52 +02:00
|
|
|
}
|
|
|
|
|
2017-07-14 21:42:40 +02:00
|
|
|
Php::Value VoIP::getDebugLog()
|
|
|
|
{
|
2017-07-20 16:02:53 +02:00
|
|
|
Php::Value data;
|
2020-03-17 16:32:35 +01:00
|
|
|
string encoded = inst->GetDebugLog();
|
2018-09-02 20:12:51 +02:00
|
|
|
if (!encoded.empty())
|
|
|
|
{
|
2017-07-20 16:02:53 +02:00
|
|
|
data = Php::call("json_decode", encoded, true);
|
|
|
|
}
|
|
|
|
return data;
|
2017-07-14 21:42:40 +02:00
|
|
|
}
|
|
|
|
|
2017-07-01 15:12:52 +02:00
|
|
|
Php::Value VoIP::getVersion()
|
|
|
|
{
|
|
|
|
return VoIPController::GetVersion();
|
|
|
|
}
|
|
|
|
|
2017-08-21 18:20:37 +02:00
|
|
|
Php::Value VoIP::getSignalBarsCount()
|
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
return inst->GetSignalBarsCount();
|
2017-08-21 18:20:37 +02:00
|
|
|
}
|
|
|
|
|
2017-07-01 15:12:52 +02:00
|
|
|
Php::Value VoIP::getPreferredRelayID()
|
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
return inst->GetPreferredRelayID();
|
2017-07-01 15:12:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Php::Value VoIP::getLastError()
|
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
return inst->GetLastError();
|
2017-07-01 15:12:52 +02:00
|
|
|
}
|
2017-07-14 21:42:40 +02:00
|
|
|
Php::Value VoIP::getDebugString()
|
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
return inst->GetDebugString();
|
2017-07-14 21:42:40 +02:00
|
|
|
}
|
2017-07-01 15:12:52 +02:00
|
|
|
Php::Value VoIP::getStats()
|
|
|
|
{
|
2018-09-04 15:49:20 +02:00
|
|
|
Php::Value stats;
|
2018-09-02 20:12:51 +02:00
|
|
|
VoIPController::TrafficStats _stats;
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->GetStats(&_stats);
|
2017-07-01 15:12:52 +02:00
|
|
|
stats["bytesSentWifi"] = (int64_t)_stats.bytesSentWifi;
|
|
|
|
stats["bytesSentMobile"] = (int64_t)_stats.bytesSentMobile;
|
|
|
|
stats["bytesRecvdWifi"] = (int64_t)_stats.bytesRecvdWifi;
|
|
|
|
stats["bytesRecvdMobile"] = (int64_t)_stats.bytesRecvdMobile;
|
|
|
|
return stats;
|
|
|
|
}
|
2017-06-17 17:24:34 +02:00
|
|
|
|
2018-09-04 15:49:20 +02:00
|
|
|
Php::Value VoIP::getPeerCapabilities()
|
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
return (int64_t)inst->GetPeerCapabilities();
|
2019-03-29 14:07:22 +01:00
|
|
|
}
|
|
|
|
Php::Value VoIP::getConnectionMaxLayer()
|
|
|
|
{
|
|
|
|
return VoIPController::GetConnectionMaxLayer();
|
2018-09-04 15:49:20 +02:00
|
|
|
}
|
|
|
|
void VoIP::requestCallUpgrade()
|
|
|
|
{
|
2020-03-17 16:32:35 +01:00
|
|
|
return inst->RequestCallUpgrade();
|
2018-09-04 15:49:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void VoIP::sendGroupCallKey(Php::Parameters ¶ms)
|
|
|
|
{
|
|
|
|
unsigned char *key = (unsigned char *)malloc(256);
|
|
|
|
memcpy(key, params[0], 256);
|
2020-03-17 16:32:35 +01:00
|
|
|
inst->SendGroupCallKey(key);
|
2018-09-04 15:49:20 +02:00
|
|
|
}
|
|
|
|
|
2017-07-10 00:06:50 +02:00
|
|
|
Php::Value VoIP::getState()
|
|
|
|
{
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2017-07-13 18:54:59 +02:00
|
|
|
Php::Value VoIP::isPlaying()
|
|
|
|
{
|
|
|
|
return playing;
|
|
|
|
}
|
|
|
|
|
2019-03-29 20:00:38 +01:00
|
|
|
void VoIPServerConfigInternal::update(Php::Parameters ¶ms)
|
2019-03-29 14:07:22 +01:00
|
|
|
{
|
2019-03-29 20:00:38 +01:00
|
|
|
Php::Array settings = params[0];
|
|
|
|
ServerConfig::GetSharedInstance()->Update(Php::call("json_encode", settings));
|
2019-03-29 14:07:22 +01:00
|
|
|
}
|
2018-09-02 20:12:51 +02:00
|
|
|
extern "C"
|
2017-07-10 00:06:50 +02:00
|
|
|
{
|
2017-04-05 03:46:16 +02:00
|
|
|
|
2017-07-23 17:49:47 +02:00
|
|
|
/**
|
|
|
|
* Function that is called by PHP right after the PHP process
|
|
|
|
* has started, and that returns an address of an internal PHP
|
|
|
|
* strucure with all the details and features of your extension
|
|
|
|
*
|
|
|
|
* @return void* a pointer to an address that is understood by PHP
|
|
|
|
*/
|
|
|
|
PHPCPP_EXPORT void *get_module()
|
|
|
|
{
|
|
|
|
// static(!) Php::Extension object that should stay in memory
|
|
|
|
// for the entire duration of the process (that's why it's static)
|
2020-03-01 16:30:57 +01:00
|
|
|
static Php::Extension extension("php-libtgvoip", "1.5.0");
|
2017-07-23 17:49:47 +02:00
|
|
|
|
2019-03-29 20:00:38 +01:00
|
|
|
Php::Class<VoIPServerConfigInternal> voipServerConfigInternal("VoIPServerConfigInternal");
|
|
|
|
voipServerConfigInternal.method<&VoIPServerConfigInternal::update>("updateInternal", Php::Protected | Php::Static, {Php::ByVal("config", Php::Type::Array)});
|
2019-03-29 14:07:22 +01:00
|
|
|
|
2017-07-23 17:49:47 +02:00
|
|
|
// description of the class so that PHP knows which methods are accessible
|
|
|
|
Php::Class<VoIP> voip("VoIP");
|
|
|
|
|
|
|
|
voip.method<&VoIP::getState>("getState", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::getCallState>("getCallState", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::getVisualization>("getVisualization", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::setVisualization>("setVisualization", Php::Public | Php::Final, {Php::ByVal("visualization", Php::Type::Array)});
|
|
|
|
voip.method<&VoIP::getOtherID>("getOtherID", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::getProtocol>("getProtocol", Php::Public | Php::Final);
|
2017-07-26 08:31:52 +02:00
|
|
|
voip.method<&VoIP::setMadeline>("setMadeline", Php::Public | Php::Final);
|
2017-07-23 17:49:47 +02:00
|
|
|
voip.method<&VoIP::getCallID>("getCallID", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::isCreator>("isCreator", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::whenCreated>("whenCreated", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::isPlaying>("isPlaying", Php::Public | Php::Final);
|
|
|
|
|
|
|
|
voip.method<&VoIP::discard>("__destruct", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::discard>("discard", Php::Public | Php::Final, {Php::ByVal("reason", Php::Type::Array, false), Php::ByVal("rating", Php::Type::Array, false), Php::ByVal("debug", Php::Type::Bool, false)});
|
|
|
|
voip.method<&VoIP::accept>("accept", Php::Public | Php::Final);
|
2017-09-08 14:04:56 +02:00
|
|
|
voip.method<&VoIP::close>("close", Php::Public | Php::Final);
|
2019-03-29 14:07:22 +01:00
|
|
|
voip.method<&VoIP::setCall>("setCall", Php::Public | Php::Final, {Php::ByVal("phoneCall", Php::Type::Array)});
|
|
|
|
voip.method<&VoIP::__construct>("__construct", Php::Public | Php::Final, {Php::ByVal("creator", Php::Type::Bool), Php::ByVal("otherID", Php::Type::Numeric), Php::ByRef("madeline", Php::Type::Object), Php::ByVal("callState", Php::Type::Numeric)});
|
2017-07-23 17:49:47 +02:00
|
|
|
voip.method<&VoIP::__wakeup>("__wakeup", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::__sleep>("__sleep", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::setMicMute>("setMicMute", Php::Public | Php::Final, {
|
2018-09-02 20:12:51 +02:00
|
|
|
Php::ByVal("type", Php::Type::Bool),
|
|
|
|
});
|
2017-07-23 17:49:47 +02:00
|
|
|
voip.method<&VoIP::parseConfig>("parseConfig", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::getDebugLog>("getDebugLog", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::getLastError>("getLastError", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::getPreferredRelayID>("getPreferredRelayID", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::getVersion>("getVersion", Php::Public | Php::Final);
|
2017-08-21 18:20:37 +02:00
|
|
|
voip.method<&VoIP::getSignalBarsCount>("getSignalBarsCount", Php::Public | Php::Final);
|
2017-07-23 17:49:47 +02:00
|
|
|
voip.method<&VoIP::getDebugString>("getDebugString", Php::Public | Php::Final);
|
|
|
|
voip.method<&VoIP::getStats>("getStats", Php::Public | Php::Final);
|
2018-09-04 15:49:20 +02:00
|
|
|
voip.method<&VoIP::getPeerCapabilities>("getPeerCapabilities", Php::Public | Php::Final);
|
2019-03-29 14:07:22 +01:00
|
|
|
voip.method<&VoIP::getConnectionMaxLayer>("getConnectionMaxLayer", Php::Public);
|
2018-09-04 15:49:20 +02:00
|
|
|
voip.method<&VoIP::sendGroupCallKey>("sendGroupCallKey", Php::Public | Php::Final, {Php::ByVal("key", Php::Type::String)});
|
|
|
|
voip.method<&VoIP::requestCallUpgrade>("requestCallUpgrade", Php::Public | Php::Final);
|
2017-07-23 17:49:47 +02:00
|
|
|
voip.method<&VoIP::startTheMagic>("startTheMagic", Php::Public | Php::Final);
|
|
|
|
|
|
|
|
voip.method<&VoIP::play>("then", Php::Public | Php::Final, {Php::ByVal("file", Php::Type::String)});
|
|
|
|
voip.method<&VoIP::play>("play", Php::Public | Php::Final, {Php::ByVal("file", Php::Type::String)});
|
|
|
|
voip.method<&VoIP::playOnHold>("playOnHold", Php::Public | Php::Final, {Php::ByVal("files", Php::Type::Array)});
|
|
|
|
|
|
|
|
voip.method<&VoIP::setOutputFile>("setOutputFile", Php::Public | Php::Final, {Php::ByVal("file", Php::Type::String)});
|
|
|
|
voip.method<&VoIP::unsetOutputFile>("unsetOutputFile", Php::Public | Php::Final);
|
|
|
|
|
|
|
|
voip.property("configuration", 0, Php::Public);
|
|
|
|
voip.property("storage", 0, Php::Public);
|
|
|
|
voip.property("internalStorage", 0, Php::Private);
|
|
|
|
|
|
|
|
voip.constant("STATE_CREATED", STATE_CREATED);
|
|
|
|
voip.constant("STATE_WAIT_INIT", STATE_WAIT_INIT);
|
|
|
|
voip.constant("STATE_WAIT_INIT_ACK", STATE_WAIT_INIT_ACK);
|
|
|
|
voip.constant("STATE_ESTABLISHED", STATE_ESTABLISHED);
|
|
|
|
voip.constant("STATE_FAILED", STATE_FAILED);
|
|
|
|
voip.constant("STATE_RECONNECTING", STATE_RECONNECTING);
|
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
voip.constant("TGVOIP_ERROR_UNKNOWN", ERROR_UNKNOWN);
|
|
|
|
voip.constant("TGVOIP_ERROR_INCOMPATIBLE", ERROR_INCOMPATIBLE);
|
|
|
|
voip.constant("TGVOIP_ERROR_TIMEOUT", ERROR_TIMEOUT);
|
|
|
|
voip.constant("TGVOIP_ERROR_AUDIO_IO", ERROR_AUDIO_IO);
|
|
|
|
voip.constant("TGVOIP_ERROR_PROXY", ERROR_PROXY);
|
2017-07-23 17:49:47 +02:00
|
|
|
|
|
|
|
voip.constant("NET_TYPE_UNKNOWN", NET_TYPE_UNKNOWN);
|
|
|
|
voip.constant("NET_TYPE_GPRS", NET_TYPE_GPRS);
|
|
|
|
voip.constant("NET_TYPE_EDGE", NET_TYPE_EDGE);
|
|
|
|
voip.constant("NET_TYPE_3G", NET_TYPE_3G);
|
|
|
|
voip.constant("NET_TYPE_HSPA", NET_TYPE_HSPA);
|
|
|
|
voip.constant("NET_TYPE_LTE", NET_TYPE_LTE);
|
|
|
|
voip.constant("NET_TYPE_WIFI", NET_TYPE_WIFI);
|
|
|
|
voip.constant("NET_TYPE_ETHERNET", NET_TYPE_ETHERNET);
|
|
|
|
voip.constant("NET_TYPE_OTHER_HIGH_SPEED", NET_TYPE_OTHER_HIGH_SPEED);
|
|
|
|
voip.constant("NET_TYPE_OTHER_LOW_SPEED", NET_TYPE_OTHER_LOW_SPEED);
|
|
|
|
voip.constant("NET_TYPE_DIALUP", NET_TYPE_DIALUP);
|
|
|
|
voip.constant("NET_TYPE_OTHER_MOBILE", NET_TYPE_OTHER_MOBILE);
|
|
|
|
|
|
|
|
voip.constant("DATA_SAVING_NEVER", DATA_SAVING_NEVER);
|
|
|
|
voip.constant("DATA_SAVING_MOBILE", DATA_SAVING_MOBILE);
|
|
|
|
voip.constant("DATA_SAVING_ALWAYS", DATA_SAVING_ALWAYS);
|
|
|
|
|
|
|
|
voip.constant("PROXY_NONE", PROXY_NONE);
|
|
|
|
voip.constant("PROXY_SOCKS5", PROXY_SOCKS5);
|
|
|
|
|
|
|
|
voip.constant("AUDIO_STATE_NONE", AUDIO_STATE_NONE);
|
|
|
|
voip.constant("AUDIO_STATE_CREATED", AUDIO_STATE_CREATED);
|
|
|
|
voip.constant("AUDIO_STATE_CONFIGURED", AUDIO_STATE_CONFIGURED);
|
|
|
|
voip.constant("AUDIO_STATE_RUNNING", AUDIO_STATE_RUNNING);
|
|
|
|
|
|
|
|
voip.constant("CALL_STATE_NONE", CALL_STATE_NONE);
|
|
|
|
voip.constant("CALL_STATE_REQUESTED", CALL_STATE_REQUESTED);
|
|
|
|
voip.constant("CALL_STATE_INCOMING", CALL_STATE_INCOMING);
|
|
|
|
voip.constant("CALL_STATE_ACCEPTED", CALL_STATE_ACCEPTED);
|
|
|
|
voip.constant("CALL_STATE_CONFIRMED", CALL_STATE_CONFIRMED);
|
|
|
|
voip.constant("CALL_STATE_READY", CALL_STATE_READY);
|
|
|
|
voip.constant("CALL_STATE_ENDED", CALL_STATE_ENDED);
|
|
|
|
|
2018-09-02 20:12:51 +02:00
|
|
|
voip.constant("TGVOIP_PEER_CAP_GROUP_CALLS", TGVOIP_PEER_CAP_GROUP_CALLS);
|
|
|
|
|
2020-03-01 16:30:57 +01:00
|
|
|
voip.constant("PHP_LIBTGVOIP_VERSION", "1.5.0");
|
2017-07-25 10:58:48 +02:00
|
|
|
|
2017-07-23 17:49:47 +02:00
|
|
|
Php::Namespace danog("danog");
|
|
|
|
Php::Namespace MadelineProto("MadelineProto");
|
|
|
|
|
2019-03-29 20:00:38 +01:00
|
|
|
MadelineProto.add(move(voipServerConfigInternal));
|
2017-07-23 17:49:47 +02:00
|
|
|
MadelineProto.add(move(voip));
|
|
|
|
danog.add(move(MadelineProto));
|
|
|
|
extension.add(move(danog));
|
|
|
|
|
|
|
|
return extension;
|
|
|
|
}
|
2017-04-05 03:46:16 +02:00
|
|
|
}
|