Commit b46d6ca0 authored by Gabriel Margiani's avatar Gabriel Margiani

Added some documentation to the code.

parent 28a5878f
......@@ -16,16 +16,22 @@
* along with 3phone. If not, see <http://www.gnu.org/licenses/>.
**/
#include <iostream>
#include "call.h"
#include "sipphone.h"
#include "phonebook.h"
#include "error.h"
p3::call::call(int i, p3::sipphone &p, p3::account &acc, int call_id) :
Call(acc, call_id),
phone(p),
p3id(i),
state(p3::callState::NONE),
ringing(false)
ringing(false),
hold(false),
mute(false),
aud_med(nullptr)
{}
p3::call::~call() {
......@@ -74,13 +80,17 @@ void p3::call::onCallState(pj::OnCallStateParam& prm) {
void p3::call::onCallMediaState(pj::OnCallMediaStateParam& prm) {
pj::CallInfo ci = getInfo();
aud_med = nullptr;
for (unsigned i = 0; i < ci.media.size(); i++) {
if (ci.media[i].type==PJMEDIA_TYPE_AUDIO && getMedia(i)) {
pj::AudioMedia *aud_med = static_cast<pj::AudioMedia*>(getMedia(i));
std::cout << "media reconnect." << std::endl;
aud_med = static_cast<pj::AudioMedia*>(getMedia(i));
pj::AudDevManager& mgr = pj::Endpoint::instance().audDevManager();
aud_med->startTransmit(mgr.getPlaybackDevMedia());
mgr.getCaptureDevMedia().startTransmit(*aud_med);
break;
}
}
}
......@@ -100,3 +110,60 @@ p3::callState p3::call::get_state() {
char p3::call::get_state_char() {
return static_cast<char>(state);
}
void p3::call::hold_toggle() {
try {
std::cout << "holdtx" << std::endl;
pj::AudDevManager& mgr = pj::Endpoint::instance().audDevManager();
pj::CallOpParam o;
if (hold) {
if (phone.hold_music == nullptr) {
reinvite(o);
} else if (aud_med != nullptr) {
phone.hold_music->stopTransmit(*aud_med);
aud_med->startTransmit(mgr.getPlaybackDevMedia());
mgr.getCaptureDevMedia().startTransmit(*aud_med);
}
hold = false;
} else {
if (phone.hold_music == nullptr) {
setHold(o);
} else if (aud_med != nullptr) {
aud_med->stopTransmit(mgr.getPlaybackDevMedia());
mgr.getCaptureDevMedia().stopTransmit(*aud_med);
phone.hold_music->startTransmit(*aud_med);
}
hold = true;
}
} catch (pj::Error& e) {
throw p3::perror("call:hold toggle", e.info());
}
}
void p3::call::mute_toggle() {
if (ringing) {
phone.stop_ringing();
ringing = false;
return;
}
try {
if (mute) {
if (aud_med != nullptr) {
aud_med->adjustRxLevel(1);
}
mute = false;
} else {
if (aud_med != nullptr) {
std::cout << "mutetx" << std::endl;
aud_med->adjustRxLevel(0);
}
mute = true;
}
} catch (pj::Error& e) {
throw p3::perror("call:hold toggle", e.info());
}
}
pj::AudioMedia* p3::call::get_audio_media() {
return aud_med;
}
......@@ -45,6 +45,11 @@ namespace p3 {
callState state;
bool ringing;
bool hold;
bool mute;
pj::AudioMedia* aud_med;
public:
call(int id, sipphone& p, account& acc, int call_id = PJSUA_INVALID_ID);
......@@ -55,11 +60,16 @@ namespace p3 {
void onCallMediaState(pj::OnCallMediaStateParam &prm);
pj::AudioMedia* get_audio_media();
int get_id();
std::string get_nr();
callState get_state();
char get_state_char();
void hold_toggle();
void mute_toggle();
};
}
......
......@@ -28,8 +28,16 @@
std::map<std::string, std::string> p3::client::command_shorthands {
{"c", "call"},
{"h", "hangup"},
{"ha", "hangupall"},
{"p", "hold"},
{"pause", "hold"},
{"m", "mute"},
{"ma", "muteall"},
{"a", "answer"},
{"l", "list"},
{"j", "conference"},
{"t", "transfer"},
{"join", "conference"},
{"q", "quit"},
{"x", "exit"}
};
......
......@@ -27,6 +27,16 @@
namespace p3 {
/**
* Basic client for 3phone server. Just opens a connection to the server,
* sends any command given on the commandline, asks for more information
* if needed, prints the server response and exits.
* There is a interactive mode where commands can be entered on stdin.
*
* This is nothing more than a fronted to the very uncomfortable message
* queue based ui of the server.
*/
class client {
mQueue * connection;
......
......@@ -18,10 +18,12 @@
#include <fstream>
#include <sstream>
#include <iostream>
#include <boost/algorithm/string.hpp>
#include "config.h"
#include "error.h"
p3::config::config(std::string p, std::map<std::string, std::string>& d) : data(d), defaults(d), filename(p) {
load_config();
......@@ -62,15 +64,30 @@ void p3::config::save_config() {
f.close();
}
std::string p3::config::get_string(std::string k) {
return data.at(k);
std::string p3::config::get_string(const std::string& k) {
try {
return data.at(k);
} catch (std::out_of_range& e) {
std::cout << "!! config::get_string(): Invalide configuration key: " << k << std::endl;
throw;
}
}
int p3::config::get_int(std::string k) {
return std::stoi(data.at(k));
int p3::config::get_int(const std::string& k) {
try {
return std::stoi(data.at(k));
} catch (std::out_of_range& e) {
std::cout << "!! config::get_string(): Invalide configuration key: " << k << std::endl;
throw;
}
}
bool p3::config::get_bool(std::string k) {
std::string s = data.at(k);
return (s == "true" || s == "True" || s == "1");
bool p3::config::get_bool(const std::string& k) {
try {
std::string s = data.at(k);
return (s == "true" || s == "True" || s == "1");
} catch (std::out_of_range& e) {
std::cout << "!! config::get_string(): Invalide configuration key: " << k << std::endl;
throw;
}
}
......@@ -37,9 +37,9 @@ namespace p3 {
void load_config();
void save_config();
std::string get_string(std::string k);
int get_int(std::string k);
bool get_bool(std::string k);
std::string get_string(const std::string& k);
int get_int(const std::string& k);
bool get_bool(const std::string& k);
};
}
......
......@@ -25,6 +25,17 @@
namespace p3 {
/**
* Sometimes, when something important happens, sipphone/call classes call
* run_hook() on an instance of this class. This runs an user-defined
* script (eventHook::command, loaded from config during server init)
* with the following arguments:
* 1: Event description given by the caller, see call/phone implementation
* 2: The id of the call associated to the even.
* 3: The "Address" (as formatted by the phonebook) of the other party on
* the call.
*/
class eventHook {
std::string command;
......
......@@ -23,17 +23,31 @@
#include "msgq.h"
#include "error.h"
/**
* 3phone is a simple sip phone using a client-server model.
* The server is responsible for everything related to server connection
* and phone calls, while the client only takes user input and forwards it
* to the server, like telling the server to call a phone number
* or to answer a call. Especially, the calls audio is not transfered
* back to the client, so it makes no sense to have client and server on
* different machines.
*/
void print_usage() {
std::cout << "3phone - p3 usage: " << std::endl
<< "p3 s erve - start the server" << std::endl
<< "p3 quit, stop - terminate server" << std::endl
<< "p3 reload - reload phonebook and some parts of the configuration." << std::endl
<< "p3 c all [nr] - call someone" << std::endl
<< "p3 a nswer - answer a currently ringing call" << std::endl
<< "p3 l ist - list current calls" << std::endl
<< "p3 h angup [id] - hanup" << std::endl
<< "p3 p ause, hold [id] - pause/hold/reinvite" << std::endl
<< "p3 ha ngupall - hanup all current calls" << std::endl
<< "p3 p ause, hold [id] - pause/hold/reinvite (will play a sound or send HOLD)" << std::endl
<< "p3 m ute [id] - mute ringtone if ringing, else mute microphone." << std::endl
<< "p3 ma muteall - mute microphone for all calls." << std::endl
<< "p3 j oin - conference current calls" << std::endl
<< "p3 f orward [id] [nr] - forward call" << std::endl
<< "p3 t ransfer [id] [nr] - forward call" << std::endl
<< "p3 r ecord [file] - record" << std::endl
<< "-I for an interactive shell" << std::endl;
}
......@@ -61,6 +75,7 @@ int run_client(int argc, char *argv[]) {
return 0;
}
// Tell the server to quit, see protocol.h/server.h/client.h
int send_quit() {
try {
p3::mQueue q(p3::mQueue::DefaultId);
......@@ -80,7 +95,7 @@ int main(int argc, char *argv[]) {
print_usage();
} else if (a == "s" || a == "serve" || a == "server") {
return run_server();
} else if (a == "quit" || a == "stop") {
} else if (a == "quit" || a == "stop") { // Quit has to be send without client connection.
return send_quit();
} else {
return run_client(argc, argv);
......
......@@ -27,7 +27,7 @@ const bool p3::mQueue::Create = true;
const int p3::mQueue::DefaultSendTimeout = 10; /* 10sec timeout... */
const char * p3::mQueue::DefaultId = "p3Main";
p3::mQueue::mQueue(std::string id, bool c) {
p3::mQueue::mQueue(const std::string& id, bool c) {
delOnDestruct = c;
key = "/p3::mQueue!" + id;
......@@ -91,12 +91,12 @@ p3::mQueue::~mQueue() {
}
}
void p3::mQueue::send(p3::protocol c, std::string v) {
void p3::mQueue::send(p3::protocol c, const std::string& v) {
p3::mQueueMessage q = p3::mQueueMessage(c, v);
send(q);
}
void p3::mQueue::send(p3::mQueueMessage & qmsg) {
void p3::mQueue::send(const p3::mQueueMessage & qmsg) {
std::string msg = qmsg.get_str();
std::cout << "S: " << key << ": " << msg << std::endl;
timespec tsp;
......@@ -155,16 +155,16 @@ long p3::mQueue::get_msg_count() {
p3::mQueueMessage::mQueueMessage(const char * message) : command(static_cast<p3::protocol>(*message)), value(message+1) {};
p3::mQueueMessage::mQueueMessage(protocol c, std::string &v) : command(c), value(v) {};
p3::mQueueMessage::mQueueMessage(protocol c, std::string v) : command(c), value(v) {};
p3::protocol p3::mQueueMessage::get_command() {
p3::protocol p3::mQueueMessage::get_command() const {
return command;
}
std::string& p3::mQueueMessage::get_value() {
std::string p3::mQueueMessage::get_value() const {
return value;
}
std::string p3::mQueueMessage::get_str() {
std::string p3::mQueueMessage::get_str() const {
return static_cast<char>(command) + value;
}
......@@ -28,18 +28,29 @@
namespace p3 {
/**
* We use unix message queues for the client/server communications.
* A queue needs to be created by one process using an id and can then
* be opened by others. Generally, they could be used to communicate in
* two directions, but then there is a chance of reading your own packets
* back in again, so we use one queue for input and one for output.
*
* Our messages are made of first protocol part (see protocol.h), followed
* by any data as string of max 251 bytes.
*/
class mQueueMessage {
protocol command;
std::string value;
public:
explicit mQueueMessage(const char * message);
mQueueMessage(protocol c, std::string &v);
mQueueMessage(protocol c, std::string v);
protocol get_command();
std::string& get_value();
protocol get_command() const;
std::string get_value() const;
std::string get_str();
std::string get_str() const;
};
/* Message queue */
......@@ -59,13 +70,15 @@ namespace p3 {
static const int DefaultSendTimeout;
static const char * DefaultId;
// create implies delete on destruct.
mQueue(std::string id, bool create = false);
// create implies delete on destruct, only use it on
// one side of the connection. Remember to inform
// the other party before closing a connection
mQueue(const std::string& id, bool create = false);
~mQueue();
void send(mQueueMessage & m);
void send(protocol c, std::string w);
void send(const mQueueMessage & m);
void send(protocol c, const std::string& w);
// Timeout in sec. -1 for blocking. Will throw on timeout!
mQueueMessage receive(int timeout = -1);
......
......@@ -24,9 +24,17 @@
#include "phonebook.h"
p3::phonebook::phonebook(p3::config& c) : conf(c) {
int id_count = 0;
std::ifstream f(conf.get_string("phonebook:filename"));
p3::config* p3::phonebook::conf = nullptr;
p3::phonebook::phonebook() : id_count(0) {
}
void p3::phonebook::load() {
data.clear();
index.clear();
id_count = 0;
std::ifstream f(conf->get_string("phonebook:filename"));
std::string l;
while(std::getline(f, l)) {
std::istringstream il(l);
......@@ -47,8 +55,6 @@ p3::phonebook::phonebook(p3::config& c) : conf(c) {
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;
......@@ -56,6 +62,46 @@ p3::phonebook::phonebook(p3::config& c) : conf(c) {
}
}
void p3::phonebook::set_config(p3::config& c) {
conf = &c;
}
void p3::phonebook::add(const std::string& nr) {
if (nr.empty() || index.count(nr)) return;
std::regex rnm("\"(.*)\"");
std::regex rnr("sip:(.*)@(.*)>?");
std::smatch match;
std::string name("");
std::string num(nr);
if (std::regex_search(nr, match, rnm) && match.size() > 1) {
name = match.str(1);
}
if (std::regex_search(nr, match, rnr) && match.size() > 2) {
if (match.str(2) == conf->get_string("phonebook:default_uri")) {
num = match.str(1);
} else {
num = "<sip:" + match.str(1) + "@" + match.str(2) + ">";
}
}
data[id_count] = {num, name, ""};
index[num] = id_count;
if (!name.empty()) index[name] = id_count;
id_count++;
std::ofstream f(
conf->get_string("phonebook:filename"),
std::ofstream::out | std::ofstream::app
);
if (f) {
f << name << "\t< " << num << "\t<" << std::endl;
}
}
void p3::phonebook::normalize(std::string& s) {
boost::to_lower(s);
s = std::regex_replace(s,std::regex("\\s+"), "");
......@@ -73,7 +119,7 @@ std::string p3::phonebook::make_uri(std::string nr) {
nr = "sip:" + nr;
}
if (nr.find('@') == std::string::npos) {
nr += "@" + conf.get_string("phonebook:default_uri");
nr += "@" + conf->get_string("phonebook:default_uri");
}
if (nr.find("<") == std::string::npos) {
nr = '<' + nr + '>';
......@@ -82,21 +128,21 @@ std::string p3::phonebook::make_uri(std::string nr) {
return nr;
}
std::string p3::phonebook::uri_by_nr(std::string nr) {
std::string p3::phonebook::uri_by_nr(const std::string& nr) {
try {
int id = index.at(nr);
return "\"" + data[id][1] +
"\" <sip:" + nr + "@" +
conf.get_string("phonebook:default_uri") +
conf->get_string("phnebook:default_uri") +
">";
} catch (std::out_of_range& e) {
return "<sip:" + nr + "@" +
conf.get_string("phonebook:default_uri") +
conf->get_string("phonebook:default_uri") +
">";
}
}
std::string p3::phonebook::address_by_nr(std::string nr) {
std::string p3::phonebook::address_by_nr(const std::string& nr) {
try {
int id = index.at(nr);
return data[id][1] +
......@@ -118,7 +164,7 @@ std::string p3::phonebook::find_nr(std::string s) {
}
std::string p3::phonebook::slug_by_nr(std::string nr) {
std::string p3::phonebook::slug_by_nr(const std::string& nr) {
try {
int id = index.at(nr);
return data[id][2];
......@@ -129,7 +175,8 @@ std::string p3::phonebook::slug_by_nr(std::string nr) {
std::string p3::phonebook::normalize_number(std::string nr) {
normalize(nr);
std::regex r("<sip:(.*)@.*>");
std::regex r("<sip:(.*)@" +
conf->get_string("phonebook:default_uri") + ">");
std::smatch match;
if (std::regex_search(nr, match, r) && match.size() > 1) {
return match.str(1);
......
......@@ -27,10 +27,21 @@
namespace p3 {
/**
* The phonebook class represents a simple phone-book with the
* functionality to convert between sip uri's, simple phone numbers,
* names and short slugs.
* Some of these might not be available.
* A connection between phone book entry and call is held by the siphone.
*
* TODO fix some problems with real sip-url (sip-to-sip voip)
*/
class phonebook {
config& conf;
static config* conf;
int id_count;
std::map<int, std::array<std::string, 3> > data;
std::map<std::string, int> index;
......@@ -38,16 +49,34 @@ namespace p3 {
public:
explicit phonebook(config& c);
explicit phonebook();
static void set_config(config& conf);
// Load the phonebook from disc
void load();
// Add a phone number to the phonebook,
// nr might be in any valid format (URI or number).
void add(const std::string& nr);
// Build a number of the form <sip:..@...> in
// a sane way. nr might be anything between a phone
// number formatted with spaces and a sip URI.
std::string make_uri(std::string nr);
// Find a number by exact match with anything in the
// phonebook (names, slugs, numbers)
std::string find_nr(std::string s);
std::string uri_by_nr(std::string nr);
std::string address_by_nr(std::string nr);
// In the following, nr has to be exactly as in the phonebook.
std::string slug_by_nr(std::string nr);
// Address of the form 'Name <sip:34@ab.cd> (slug)'
std::string address_by_nr(const std::string& nr);
std::string uri_by_nr(const std::string& nr);
std::string slug_by_nr(const std::string& nr);
// Bring a number from "human-formatted" to phonebook form.
static std::string normalize_number(std::string nr);
};
......
......@@ -20,28 +20,91 @@
#define __P3_PROTOCOL_H__
namespace p3 {
/**
* The 3phone protocol commands
* Generally every command consists of a protocol byte followed by
* argument data. This data is command specific and might or might not
* be relevant.
* The protocol is based on a strict client-server model, so one party is
* always listening, except for when a command is received, and has always
* to be the last one speaking, say answer everything in the end with a
* terminating OK or alike. Generally no package might be left unanswered
* on both sides, so after sending something, client and server role change.
*
* There is an exception the question-answer rule. Some commands like BEGINTEXT
* extend a dataset over multiple packets. The receiving party has to wait for
* incoming packets now without answering until ENDTEXT, then the normal answer
* has to be send.
*
* There are other commands where the client might not need to answer.
*
* The protocol is very general, and has nothing to do with the phone specific
* command (see COMMAND below).
*/
enum class protocol : char {
HELLO,
BYE,
QUIT,
SET_TIMEOUT,
// Server and channel management
HELLO, // Data: [channel id]. Answer: OK, on [channel id]
// MAIN CHANNEL.
// Used to initiate a server connection on the
// channel [channel id].
BYE, // Data: *, Answer: none.
// I will exit now. In case I'm the channel owner
// the channel might be destroyed.
QUIT, // Data: *, Answer: none.
// MAIN CHANNEL.
// Tells the server to exit.
SET_TIMEOUT, // Data: [timeout], ANSWER: OK/ERROR
// Set the listen timeout on the server for the
// current connection. (see msgq for valid values)
// General
COMMAND, // Data: [command], Answer: * (depends on command)
// Tell the server to do [command].
// see clientHandler::parse_command for what is
// implemented
OK, // Data: *, Answer: none/next command.
// Ask for more data
ASK, // Data: [what] (UI-String), Answer: ANSWER/*
// Generally ask for input.