Commit 02d6fbf0 authored by Gabriel Margiani's avatar Gabriel Margiani

Telefon.

parent 70bc1292
--language=c++
--enable=all
--suppress=unusedFunction
......@@ -17,13 +17,24 @@
**/
#include "account.h"
#include "call.h"
#include "sipphone.h"
p3::account::account() {};
p3::account::account(sipphone& p) : phone(p){};
p3::account::~account() {};
void p3::account::onRegState(pj::OnRegStateParam &prm) {
}
void p3::account::onIncomingCall(pj::OnIncomingCallParam &iprm) {
p3::call* c = phone.register_new_call(iprm.callId);
phone.register_number(c->get_nr(), c->get_id());
pj::CallOpParam o;
if (phone.get_call_count() < 1) {
o.statusCode = PJSIP_SC_BUSY_HERE;
c->answer(o);
} else {
o.statusCode = PJSIP_SC_RINGING;
c->answer(o);
}
}
......@@ -23,9 +23,14 @@
#include <pjsua2.hpp>
namespace p3 {
class sipphone;
class account : public pj::Account {
sipphone& phone;
public:
account();
explicit account(sipphone& p);
~account();
virtual void onRegState(pj::OnRegStateParam &prm);
......
......@@ -18,19 +18,56 @@
#include "call.h"
#include "sipphone.h"
#include "phonebook.h"
p3::call::call(p3::sipphone &p, p3::account &acc, int call_id) :
p3::call::call(int i, p3::sipphone &p, p3::account &acc, int call_id) :
Call(acc, call_id),
phone(p)
phone(p),
p3id(i),
state(p3::callState::NONE),
ringing(false)
{}
p3::call::~call() {
if (ringing) {
phone.stop_ringing();
}
}
void p3::call::onCallState(pj::OnCallStateParam& prm) {
pj::CallInfo ci = getInfo();
if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
phone.delete_call(getId());
switch (ci.state) {
case PJSIP_INV_STATE_DISCONNECTED:
phone.get_event_hook().run_hook("disconnected", p3id, get_nr());
state = p3::callState::NONE;
phone.delete_call(p3id);
break;
case PJSIP_INV_STATE_CALLING:
state = p3::callState::CALLING;
phone.get_event_hook().run_hook("calling", p3id, get_nr());
break;
case PJSIP_INV_STATE_CONFIRMED:
state = p3::callState::RUNNING;
if (ringing) {
phone.stop_ringing();
ringing = false;
}
break;
case PJSIP_INV_STATE_EARLY:
if (ci.role == PJSIP_ROLE_UAS) {
if (!ringing) {
phone.start_ringing();
ringing = true;
}
state = p3::callState::INCOMMING;
phone.get_event_hook().run_hook("incomming", p3id, get_nr());
}
break;
case PJSIP_INV_STATE_NULL:
case PJSIP_INV_STATE_INCOMING:
case PJSIP_INV_STATE_CONNECTING:
default:
break;
}
}
......@@ -47,3 +84,19 @@ void p3::call::onCallMediaState(pj::OnCallMediaStateParam& prm) {
}
}
}
int p3::call::get_id() {
return p3id;
}
std::string p3::call::get_nr() {
return p3::phonebook::normalize_number(getInfo().remoteUri);
}
p3::callState p3::call::get_state() {
return state;
}
char p3::call::get_state_char() {
return static_cast<char>(state);
}
......@@ -22,22 +22,44 @@
#include <pjsua2.hpp>
#include "account.h"
#include "eventhook.h"
namespace p3 {
class sipphone;
enum class callState: char {
NONE = 'X',
CALLING = 'c',
INCOMMING = '*',
RUNNING = ' ',
HOLDING = '&'
};
class call : public pj::Call {
p3::sipphone & phone;
int p3id;
callState state;
bool ringing;
public:
call(sipphone& p, account& acc, int call_id = PJSUA_INVALID_ID);
call(int id, sipphone& p, account& acc, int call_id = PJSUA_INVALID_ID);
~call();
void onCallState(pj::OnCallStateParam &prm);
void onCallMediaState(pj::OnCallMediaStateParam &prm);
int get_id();
std::string get_nr();
callState get_state();
char get_state_char();
};
}
......
......@@ -28,6 +28,8 @@
std::map<std::string, std::string> p3::client::command_shorthands {
{"c", "call"},
{"h", "hangup"},
{"a", "answer"},
{"l", "list"},
{"q", "quit"},
{"x", "exit"}
};
......@@ -100,6 +102,7 @@ void p3::client::run() {
connection->send(p3::protocol::COMMAND, get_input("", COMMAND));
while (r) {
p3::mQueueMessage m = connection->receive(-1);
bool print_list = true;
switch (m.get_command()) {
case p3::protocol::ASK_NUMBER:
connection->send(p3::protocol::ANSWER, get_input(m.get_value(), NUMBER));
......@@ -107,10 +110,14 @@ void p3::client::run() {
case p3::protocol::ASK_ID:
connection->send(p3::protocol::ANSWER, get_input(m.get_value(), ID));
continue;
case p3::protocol::BEGINQTEXT:
case p3::protocol::BEGINQUESTIONTEXT:
if (!argv.empty()) {
print_list = false;
}
case p3::protocol::BEGINTEXT:
m = connection->receive(30);
while (m.get_command() != p3::protocol::ENDQTEXT) {
if (argv.empty()) {
while (m.get_command() != p3::protocol::ENDTEXT) {
if (print_list) {
std::cout << m.get_value() << std::endl;
}
m = connection->receive(30);
......
/**
* This file is part of 3phone
* Copyright (C) 2015 Gabriel Margiani
*
* 3phone 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 3 of the License, or
* (at your option) any later version.
*
* 3phone 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 3phone. If not, see <http://www.gnu.org/licenses/>.
**/
#include <unistd.h>
#include <signal.h>
#include "eventhook.h"
p3::eventHook::eventHook(std::string c, phonebook& p) : command(c), book(p) {
signal(SIGCHLD, SIG_IGN);
}
void p3::eventHook::run_hook(std::string event, int cid, std::string nr) {
int pid = fork();
if (pid == 0) {
execl(
command.c_str(),
command.c_str(),
event.c_str(),
std::to_string(cid).c_str(),
book.address_by_nr(nr).c_str(),
(char *)0
);
}
}
......@@ -16,21 +16,28 @@
* along with 3phone. If not, see <http://www.gnu.org/licenses/>.
**/
#ifndef __3P_3PHONE_H__
#define __3P_3PHONE_H__
#ifndef __P3_EVENT_HOOK_H__
#define __P3_EVENT_HOOK_H__
#include "error.h"
#include <string>
// pjloglevel
#ifndef DEBUG
#define LOGLEVEL 0
#else
#define LOGLEVEL 5
#endif
#include "phonebook.h"
namespace p3 {
class eventHook {
std::string command;
phonebook& book;
public:
explicit eventHook(std::string c, phonebook& p);
#define MAX_CALLS 20
void run_hook(std::string event, int call_id, std::string nr);
};
// C !!
bool exit;
}
#endif
......@@ -17,21 +17,122 @@
**/
#include <iostream>
#include <fstream>
#include <sstream>
#include <regex>
#include <boost/algorithm/string.hpp>
#include "phonebook.h"
p3::phonebook::phonebook(p3::config& c) : conf(c) {}
p3::phonebook::phonebook(p3::config& c) : conf(c) {
int id_count = 0;
std::ifstream f(conf.get_string("phonebook:filename"));
std::string l;
while(std::getline(f, l)) {
std::istringstream il(l);
std::string nr("");
std::string name("");
std::string slug("");
if(std::getline(il, name, '>')) {
if(std::getline(il, nr, '>')) {
std::getline(il, slug);
}
}
std::string p3::phonebook::parse_number(std::string nr) {
std::cout << "parsing Number " << nr << std::endl;
normalize(nr);
normalize(slug);
boost::trim(name);
if (nr.find("sip:") != 0) {
data[id_count] = {nr, name, slug};
normalize(name);
std::cout << "Loaded " << nr << " > " << name << " > " << slug << std::endl;
if (!nr.empty()) index[nr] = id_count;
if (!name.empty()) index[name] = id_count;
if (!slug.empty()) index[slug] = id_count;
id_count++;
}
}
void p3::phonebook::normalize(std::string& s) {
boost::to_lower(s);
s = std::regex_replace(s,std::regex("\\s+"), "");
}
std::string p3::phonebook::make_uri(std::string nr) {
normalize(nr);
try {
int id = index.at(nr);
nr = data[id][0];
} catch (std::out_of_range& e) {
// ignore
}
if (nr.find("sip:") == std::string::npos) {
nr = "sip:" + nr;
}
if (nr.find('@') == std::string::npos) {
nr += "@" + conf.get_string("phonebook:default_uri");
}
if (nr.find("<") == std::string::npos) {
nr = '<' + nr + '>';
}
return nr;
}
std::string p3::phonebook::uri_by_nr(std::string nr) {
try {
int id = index.at(nr);
return "\"" + data[id][1] +
"\" <sip:" + nr + "@" +
conf.get_string("phonebook:default_uri") +
">";
} catch (std::out_of_range& e) {
return "<sip:" + nr + "@" +
conf.get_string("phonebook:default_uri") +
">";
}
}
std::string p3::phonebook::address_by_nr(std::string nr) {
try {
int id = index.at(nr);
return data[id][1] +
" <" + nr + ">" +
(data[id][2].empty() ? "" : " (" + data[id][2] + ")");
} catch (std::out_of_range& e) {
return nr;
}
}
std::string p3::phonebook::find_nr(std::string s) {
normalize(s);
try {
int id = index.at(s);
return data[id][0];
} catch (std::out_of_range& e) {
return "";
}
}
std::string p3::phonebook::slug_by_nr(std::string nr) {
try {
int id = index.at(nr);
return data[id][2];
} catch (std::out_of_range& e) {
return "";
}
}
std::string p3::phonebook::normalize_number(std::string nr) {
normalize(nr);
std::regex r("<sip:(.*)@.*>");
std::smatch match;
if (std::regex_search(nr, match, r) && match.size() > 1) {
return match.str(1);
}
return nr;
}
......@@ -20,6 +20,8 @@
#define __P3_PHONEBOOK_H__
#include <string>
#include <map>
#include <array>
#include "config.h"
......@@ -29,12 +31,24 @@ namespace p3 {
config& conf;
std::map<int, std::array<std::string, 3> > data;
std::map<std::string, int> index;
static void normalize(std::string& s);
public:
explicit phonebook(config& c);
std::string parse_number(std::string nr);
std::string make_uri(std::string nr);
std::string find_nr(std::string s);
std::string uri_by_nr(std::string nr);
std::string address_by_nr(std::string nr);
std::string slug_by_nr(std::string nr);
static std::string normalize_number(std::string nr);
};
}
......
......@@ -33,10 +33,9 @@ namespace p3 {
ASK,
ASK_NUMBER,
ASK_ID,
BEGINQTEXT,
ENDQTEXT,
ANSWER,
BEGINQUESTIONTEXT,
BEGINTEXT,
TEXT,
ENDTEXT,
......
......@@ -24,7 +24,6 @@
std::atomic_bool p3::server::terminate_threads(false);
std::map<std::string, std::string> p3::server::default_config {
{"test", "test"},
{"pj:max_calls", "10"},
{"pj:log_level", "5"},
{"account:id_uri", "sip:test@3phone.com"},
......@@ -34,14 +33,18 @@ std::map<std::string, std::string> p3::server::default_config {
{"account:username", "test"},
{"account:password", "pass"},
{"phonebook:default_uri", "3phone.com"},
{"phonebook:filename", "~/.3phone/phonebook"},
{"hook:command", "~/.3phone/hook"},
{"phone:ringtone", "/usr/share/3phone/rt.wav"},
};
p3::server::server() :
conf(std::string(std::getenv("HOME")) + "/.3phonerc", p3::server::default_config),
connection(p3::mQueue::DefaultId, true),
phone(conf),
book(conf)
book(conf),
hook(conf.get_string("hook:command"), book),
phone(conf, hook)
{}
p3::server::~server() {
......@@ -137,55 +140,86 @@ void p3::clientHandler::run() {
}
void p3::clientHandler::parse_command(std::string c) {
if ("call" == c) {
handle_call();
} else if ("hangup" == c){
handle_hangup();
} else {
std::cout << "Unknown command received: " << c << std::endl;
connection.send(p3::protocol::ERROR, "Invalid Command '" + c + "'");
}
}
void p3::clientHandler::handle_call() {
try {
connection.send(p3::protocol::ASK_NUMBER, "Number to call");
p3::mQueueMessage n = connection.receive(listenTimeout);
std::string nr = book.parse_number(n.get_value());
phone.call(nr);
if ("call" == c) {
handle_call();
} else if ("hangup" == c) {
handle_hangup();
} else if ("answer" == c) {
handle_answer();
} else if ("list" == c) {
handle_list();
} else {
std::cout << "Unknown command received: " << c << std::endl;
connection.send(p3::protocol::ERROR, "Invalid Command '" + c + "'");
}
} catch (p3::perror & e) {
connection.send(p3::protocol::ERROR, e.what());
return;
}
}
void p3::clientHandler::handle_call() {
connection.send(p3::protocol::ASK_NUMBER, "Number to call");
p3::mQueueMessage n = connection.receive(listenTimeout);
std::string nr = book.make_uri(n.get_value());
phone.call(nr);
connection.send(p3::protocol::OK, "Call OK");
}
void p3::clientHandler::handle_hangup() {
try {
std::cout << phone.get_call_count() << std::endl;
if (phone.get_call_count() > 1) {
send_calls_menu();
connection.send(p3::protocol::ASK_ID, "Call to hangup (id)");
p3::mQueueMessage n = connection.receive(listenTimeout);
phone.hangup(std::stoi(n.get_value()));
} else if (phone.get_call_count() == 1){
phone.hangup(-1);
phone.hangup(get_call_id("Call to hangup (id)"));
connection.send(p3::protocol::OK, "Hangup OK");
}
void p3::clientHandler::handle_answer() {
phone.answer(get_call_id("Call to answer (id)", p3::callState::INCOMMING));
connection.send(p3::protocol::OK, "Answer OK");
}
void p3::clientHandler::handle_list() {
send_calls_list();
connection.send(p3::protocol::OK, "List OK");
}
int p3::clientHandler::get_call_id(std::string q, p3::callState filter) {
int nr = phone.get_call_count(filter);
if (nr == 1) {
return -1;
} else if (nr > 1) {
send_calls_list(filter, true);
connection.send(p3::protocol::ASK_ID, q);
p3::mQueueMessage n = connection.receive(listenTimeout);
std::string phnr = book.find_nr(n.get_value());
if (phnr.empty()) {
return std::stoi(n.get_value());
} else {
connection.send(p3::protocol::ERROR, "No call to Hangup");
return;
nr = phone.get_id_by_nr(phnr);
if (nr == -1) {
throw p3::perror("getCallId","Invalid id " + n.get_value());
}
return nr;
}
} catch (p3::perror & e) {
connection.send(p3::protocol::ERROR, e.what());
return;
} else {
throw p3::perror("getCallId", q + " No calls found");
}
connection.send(p3::protocol::OK, "Hangup OK");
}
void p3::clientHandler::send_calls_menu() {
std::map<int, std::string> cl = phone.get_call_list();
connection.send(p3::protocol::BEGINQTEXT, "Call list");
for (auto c : cl) {
connection.send(p3::protocol::TEXT, std::to_string(c.first) + " " + c.second);
void p3::clientHandler::send_calls_list(p3::callState filter, bool qtext) {
auto l = phone.get_call_list(filter);
if (l.size() == 0) {
return;
}
connection.send(
qtext ? p3::protocol::BEGINQUESTIONTEXT : p3::protocol::BEGINTEXT,
"Begin Call List"
);
for (auto c : l) {
connection.send(
p3::protocol::TEXT,
c.second.second + std::string(" ") +
std::to_string(c.first) + " " + c.second.first
);
}
connection.send(p3::protocol::ENDQTEXT, "Call list");
connection.send(p3::protocol::ENDTEXT, "End Call List");
}
......@@ -26,8 +26,10 @@
#include <map>
#include "msgq.h"
#include "call.h"
#include "sipphone.h"
#include "phonebook.h"
#include "eventhook.h"
#include "config.h"
namespace p3 {
......@@ -40,9 +42,12 @@ namespace p3 {
std::list<std::thread *> clients;
mQueue connection;
sipphone phone;
phonebook book;
eventHook hook;
sipphone phone;
static std::atomic_bool terminate_threads;
void dispatch_thread(std::string id);
......@@ -71,8 +76,17 @@ namespace p3 {
void parse_command(std::string c);
void handle_call();
void handle_hangup();
void send_calls_menu();
void handle_answer();
void handle_list();
void send_calls_list(
callState filter = callState::NONE,
bool qtext = false
);
int get_call_id(
std::string q = "Select Call",
callState filter = callState::NONE
);
public:
clientHandler(std::string i, sipphone & p, phonebook & b);
......
......@@ -21,7 +21,7 @@
#include "error.h"
#include "sipphone.h"
p3::sipphone::sipphone(p3::config& c) : conf(c), active_call(-1) {
p3::sipphone::sipphone(p3::config& c, p3::eventHook& e) : conf(c), eventhook(e), id_counter(0), active_call(-1), ring_index(0) {
// Initiate pj
endpoint = new pj::Endpoint;
......@@ -70,27 +70,31 @@ p3::sipphone::sipphone(p3::config& c) : conf(c), active_call(-1) {
conf.get_string("account:password")
)
);
acc = new p3::account;
acc = new p3::account(*this);
try {
acc->create(acc_cfg);
} catch(pj::Error& error) {
delete acc;
throw p3::perror("sipphone: pjInit: Account error", error.info());
throw p3::perror("sipphone:pjInit: Account error", error.info());
}
ringtone = new pj::AudioMediaPlayer;
try {
ringtone->createPlayer(conf.get_string("phone:ringtone"));
} catch(pj::Error& err) {
throw p3::perror("sipphone:ringtone:", err.info());
}
}
p3::sipphone::~sipphone() {
calls_mutex.lock();
for (auto c : calls) {
delete c.second;
}
calls_mutex.unlock();
threads_mutex.lock();
for (auto t : pj_threads) {
delete [] t.second.second;
pj_threads.erase(t.first);
}
threads_mutex.lock();
for (auto c : calls) {
delete c.second;
}
for (auto t : pj_threads) {
delete [] t.second.second;
pj_threads.erase(t.first);
}
delete ringtone;
endpoint->libDestroy();
delete acc;
delete endpoint;
......@@ -100,27 +104,56 @@ void p3::sipphone::register_thread(std::string id) {
std::lock_guard<std::mutex> g(threads_mutex);
pj_thread_desc* ptd = new pj_thread_desc[1];
pj_thread_t* tptr;
std::cout << "registering " << id << std::endl;
pj_thread_register(id.c_str(), *ptd, &tptr);
pj_threads[id] = { tptr, ptd };
}
void p3::sipphone::unregister_thread(std::string id) {
std::lock_guard<std::mutex> g(threads_mutex);
std::cout << "unregistering " << id << std::endl;
pj_thread_destroy(pj_threads[id].first);
}
void p3::sipphone::start_ringing() {
if (ring_index == 0) {
try {
pj::AudioMedia& play_med = endpoint->audDevManager().getPlaybackDevMedia();
ringtone->startTransmit(play_med);
} catch (pj::Error& err) {
throw p3::perror("sipphone:startRinging:"</