implement SIP
Stefan Schuermans authored 5 years ago
|
3) Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4) a blinkenarea.org project */
5)
6) #include <chrono>
7) #include <condition_variable>
8) #include <iostream>
9) #include <mutex>
10) #include <sstream>
11) #include <stdarg.h>
12) #include <stdio.h>
13) #include <string>
14) #include <string.h>
15) #include <thread>
16)
17) #ifdef BLINKER_CFG_LINPHONE
18) #include <linphone/linphonecore.h>
19) #endif
20)
21) #include "Directory.h"
22) #include "File.h"
23) #include "Module.h"
24) #include "NameFile.h"
25) #include "OpConn.h"
26) #include "OpConnIf.h"
27) #include "SipPhone.h"
28) #include "Time.h"
29) #include "TimeCallee.h"
30)
31) namespace Blinker {
32)
33) #ifdef BLINKER_CFG_LINPHONE
34)
35) /**
36) * everything using the linphone library happens in a separate thread,
37) * because some call to linphone take long (up to two seconds)
38) */
39) namespace linphone {
40)
41) static char const *loglevel2str(OrtpLogLevel lev)
42) {
43) switch(lev) {
44) case ORTP_DEBUG: return "DEBUG";
45) case ORTP_MESSAGE: return "MESSAGE";
46) case ORTP_WARNING: return "WARNING";
47) case ORTP_ERROR: return "ERROR";
48) case ORTP_FATAL: return "FATAL";
49) case ORTP_TRACE: return "TRACE";
50) case ORTP_LOGLEV_END: return "LOGLEV_END";
51) default: return "?";
52) }
53) }
54)
55) static char const *gstate2str(LinphoneGlobalState gstate)
56) {
57) switch (gstate) {
58) case LinphoneGlobalOff: return "Off";
59) case LinphoneGlobalStartup: return "Startup";
60) case LinphoneGlobalOn: return "On";
61) case LinphoneGlobalShutdown: return "Shutdown";
62) default: return "?";
63) }
64) }
65)
66) static char const *rstate2str(LinphoneRegistrationState rstate)
67) {
68) switch (rstate) {
69) case LinphoneRegistrationNone: return "None";
70) case LinphoneRegistrationProgress: return "Progress";
71) case LinphoneRegistrationOk: return "Ok";
72) case LinphoneRegistrationCleared: return "Cleared";
73) case LinphoneRegistrationFailed: return "Failed";
74) default: return "?";
75) }
76) }
77)
78) static char const *cstate2str(LinphoneCallState cstate)
79) {
80) switch (cstate) {
81) case LinphoneCallIdle: return "Idle";
82) case LinphoneCallIncomingReceived: return "IncomingReceived";
83) case LinphoneCallOutgoingInit: return "OutgoingInit";
84) case LinphoneCallOutgoingProgress: return "OutgoingProgress";
85) case LinphoneCallOutgoingRinging: return "OutgoingRinging";
86) case LinphoneCallOutgoingEarlyMedia: return "OutgoingEarlyMedia";
87) case LinphoneCallConnected: return "Connected";
88) case LinphoneCallStreamsRunning: return "StreamsRunning";
89) case LinphoneCallPausing: return "Pausing";
90) case LinphoneCallPaused: return "Paused";
91) case LinphoneCallResuming: return "Resuming";
92) case LinphoneCallRefered: return "Refered";
93) case LinphoneCallError: return "Error";
94) case LinphoneCallEnd: return "End";
95) case LinphoneCallPausedByRemote: return "PausedByRemote";
96) case LinphoneCallUpdatedByRemote: return "UpdatedByRemote";
97) case LinphoneCallIncomingEarlyMedia: return "IncomingEarlyMedia";
98) case LinphoneCallUpdating: return "Updating";
99) case LinphoneCallReleased: return "Released";
100) default: return "?";
101) }
102) }
103)
104) std::string glLogFileName; ///< name of global log file
105) std::ofstream glLogStream; ///< stream to open global log file
106) unsigned int glLogLineCnt; ///< number of lines already logged
107)
108) /**
109) * @brief log information from linphone library
110) * @param[in] lev log level
111) * @param[in] fmt format string
112) * @param[in] args arguments to insert into format string
113) */
114) void log_handler(OrtpLogLevel lev, const char *fmt, va_list args)
115) {
116) // (re-)open logfile on first line
117) if (glLogLineCnt == 0) {
118) // close old file and clean up (just to be on the safe side)
119) glLogStream.close();
120) glLogStream.clear();
121) // keep previous log file
122) rename(glLogFileName.c_str(), (glLogFileName + ".prev").c_str());
123) // open new file
124) glLogStream.open(glLogFileName.c_str(), std::ios::out);
125) }
126) // write log line
127) char buffer[4096];
128) vsnprintf(buffer, sizeof(buffer) - 1, fmt, args);
129) buffer[sizeof(buffer) - 1] = 0;
130) glLogStream << Time::now().toStr() << " "
131) << loglevel2str(lev) << " " << buffer << std::endl;
132) // count lines
133) ++glLogLineCnt;
134) // close file if maximum line count reached
135) if (glLogLineCnt >= 10000) {
136) // close old file and clean up
137) glLogStream.close();
138) glLogStream.clear();
139) // reset line count, so new file is opened on next line
140) glLogLineCnt = 0;
141) }
142) }
143)
144) /// global init
145) void init(std::string const &logFileName)
146) {
147) glLogFileName = logFileName;
148) glLogLineCnt = 0;
149) linphone_core_set_log_handler(log_handler);
150) }
151)
152) /// data of SIP client worker thread
153) struct Data {
154) SipPhone::ConfigData const *configData; ///< config data for worker thread
155) SipPhone::SharedData *sharedData; ///< data shared with main Blinker thread
156) LinphoneCoreVTable callbacks; ///< callback table
157) LinphoneCore *lc; ///< linphone core object, if active
158) LinphoneCall *call; ///< current call, if available
159) std::string playback; ///< name of playback file
160) std::ofstream logStream; ///< stream to open log file
161) unsigned int logLineCnt; ///< number of lines already logged to file
162) };
163)
164) /**
165) * @breif set playback file
166) * @param[in,out] data data of SIP client
167) * @param[in] name name of sound file (without extension)
168) */
169) void set_playback(Data &data, std::string const &name)
170) {
171) Directory dir(data.configData->soundDir);
172) data.playback = dir.getFile(name + ".wav").getPath();
173) }
174)
175) void start_playback(Data &data)
176) {
177) if (data.lc && ! data.playback.empty()) {
178) linphone_core_set_play_file(data.lc, data.playback.c_str());
179) }
180) }
181)
182) /**
183) * @brief write log message
184) * @param[in,out] data data of SIP client
185) * @param[in] line lie to log
186) */
187) void log(Data &data, std::string const &line)
188) {
189) // (re-)open logfile on first line
190) if (data.logLineCnt == 0) {
191) // close old file and clean up (just to be on the safe side)
192) data.logStream.close();
193) data.logStream.clear();
194) // keep previous log file
195) rename(data.configData->logFileName.c_str(),
196) (data.configData->logFileName + ".prev").c_str());
197) // open new file
198) data.logStream.open(data.configData->logFileName.c_str(), std::ios::out);
199) }
200) // write log line
201) data.logStream << Time::now().toStr() << " " << line << std::endl;
202) // count lines
203) ++data.logLineCnt;
204) // close file if maximum line count reached
205) if (data.logLineCnt >= 10000) {
206) // close old file and clean up
207) data.logStream.close();
208) data.logStream.clear();
209) // reset line count, so new file is opened on next line
210) data.logLineCnt = 0;
211) }
212) }
213)
214) /**
215) * @brief global SIP state changed
216) * @param[in] lc linphone core object
217) * @param[in] gstate new global SIP state
218) * @param[in] message informational message
219) */
220) void global_state_changed(struct _LinphoneCore *lc,
221) LinphoneGlobalState gstate,
222) const char *message)
223) {
224) Data *data = (Data*)linphone_core_get_user_data(lc);
225)
226) std::stringstream line;
227) line << "global state changed to " << gstate2str(gstate) << ": " << message;
228) log(*data, line.str());
229) }
230)
231) /**
232) * @brief registration state changed
233) * @param[in] lc linphone core object
234) * @param[in] cfg proxy configuration
235) * @param[in] rstate new registration state
236) * @param[in] message informational message
237) */
238) void registration_state_changed(struct _LinphoneCore *lc,
239) LinphoneProxyConfig *cfg,
240) LinphoneRegistrationState rstate,
241) const char *message)
242) {
243) Data *data = (Data*)linphone_core_get_user_data(lc);
244) (void)cfg;
245)
246) std::stringstream line;
247) line << "registration state changed to " << rstate2str(rstate) << ": "
248) << message;
249) log(*data, line.str());
250) }
251)
252) /**
253) * @brief call state changed
254) * @param[in] lc linphone core object
255) * @param[in] call call object
256) * @param[in] cstate new call state
257) * @param[in] message informational message
258) */
259) void call_state_changed(struct _LinphoneCore *lc, LinphoneCall *call,
260) LinphoneCallState cstate, const char *message)
261) {
262) Data *data = (Data*)linphone_core_get_user_data(lc);
263)
264) std::stringstream line;
265) line << "call state changed to " << cstate2str(cstate) << ": "
266) << message;
267) log(*data, line.str());
268)
269) // current call -> handle state changes
270) if (data->call == call) {
271) switch (cstate) {
272) case LinphoneCallStreamsRunning:
273) start_playback(*data);
274) break;
275) case LinphoneCallRefered:
276) case LinphoneCallError:
277) case LinphoneCallEnd:
278) case LinphoneCallReleased:
279) data->call = nullptr;
280) {
281) std::lock_guard<std::mutex> lock(data->sharedData->mtx);
282) data->sharedData->terminated = true;
283) }
284) break;
285) default:
286) break;
287) }
288) }
289) // other call, but current call active -> reject call
290) else if (data->call) {
291) switch (cstate) {
292) case LinphoneCallIncomingReceived:
293) log(*data, "rejecting call (busy)");
294) linphone_core_decline_call(lc, call, LinphoneReasonBusy);
295) break;
296) default:
297) break;
298) }
299) }
300) // no call active -> accept call
301) else {
302) switch (cstate) {
303) case LinphoneCallIncomingReceived:
304) data->call = call;
305) {
306) std::lock_guard<std::mutex> lock(data->sharedData->mtx);
307) data->sharedData->accepted = true;
308) }
309) data->playback.clear();
310) log(*data, "accepting call");
311) linphone_core_accept_call(lc, call);
312) break;
313) default:
314) break;
315) }
316) }
317) }
318)
319) /**
320) * @brief DTMF received
321) * @param[in] lc linphone core object
322) * @param[in] call call object
323) * @param[in] dtmf DTMF as ASCII character
324) */
325) void dtmf_received(struct _LinphoneCore* lc, LinphoneCall *call, int dtmf)
326) {
327) Data *data = (Data*)linphone_core_get_user_data(lc);
328)
329) // check if current call
330) bool current = data->call == call;
331)
332) // check DTMF
333) char c;
334) if (dtmf >= '0' && dtmf <= '9') {
335) c = dtmf;
336) } else if (dtmf == '*') {
337) c = '*';
338) } else if (dtmf == '#') {
339) c = '#';
340) } else {
341) c = '?';
342) }
343)
344) std::stringstream line;
345) line << "dtmf received " << (current ? "(current call)" : "(other call)")
346) << ": " << c;
347) log(*data, line.str());
348)
349) // ignore DTMF from other calls
350) if (! current) {
351) return;
352) }
353)
354) // report DTMF keys
355) {
356) std::lock_guard<std::mutex> lock(data->sharedData->mtx);
357) data->sharedData->dtmf += c;
358) }
359) }
360)
361) /**
362) * @brief register if not active and login data available
363) * @param[in,out] data data of SIP client
364) * @param[in] server SIP server
365) * @param[in] username SIP username
366) * @param[in] password SIP password
367) */
368) void do_register(Data &data, std::string const &server,
369) std::string const &username, std::string &password)
370) {
371) if (data.lc) {
372) return;
373) }
374) if (server.empty()) {
375) return;
376) }
377)
378) // create linphone core object
379) data.lc = linphone_core_new(&data.callbacks, NULL, NULL, &data);
380) if (! data.lc) {
381) log(data, "failed to create linphone core");
382) return;
383) }
384) log(data, "linphone core created");
385)
386) // set auth data
387) if (! username.empty() && ! password.empty()) {
388) LinphoneAuthInfo *auth_info =
389) linphone_auth_info_new(username.c_str(), username.c_str(),
390) password.c_str(), "", "");
391) if (! auth_info) {
392) log(data, "failed to create auth info");
393) linphone_core_destroy(data.lc);
394) data.lc = nullptr;
395) return;
396) }
397) linphone_core_add_auth_info(data.lc, auth_info);
398) log(data, "auth info set");
399) } else {
400) log(data, "no auth data available");
401) }
402)
403) // configure proxy
404) std::string sipserver = "sip:" + server;
405) std::string identity = "sip:" + username + "@" + server;
406) LinphoneProxyConfig *proxy = linphone_core_create_proxy_config(data.lc);
407) if (! proxy) {
408) log(data, "failed to create proxy config");
409) linphone_core_destroy(data.lc);
410) data.lc = nullptr;
411) return;
412) }
413) linphone_proxy_config_set_server_addr(proxy, sipserver.c_str());
414) linphone_proxy_config_set_identity(proxy, identity.c_str());
415) linphone_proxy_config_enable_register(proxy, TRUE);
416) linphone_core_add_proxy_config(data.lc, proxy);
417) log(data, "proxy config set");
418)
419) // tell linphone not to rind and to use files instead of sound card
420) linphone_core_set_ring(data.lc, nullptr);
421) linphone_core_use_files(data.lc, TRUE);
422) linphone_core_set_play_file(data.lc, nullptr);
423) }
424)
425) /**
426) * @brief deregister if active
427) * @param[in,out] data data of SIP client
428) */
429) void do_deregister(Data &data)
430) {
431) if (! data.lc) {
432) return;
433) }
434)
435) log(data, "destroying...");
436) linphone_core_destroy(data.lc);
437) data.lc = nullptr;
438) data.call = nullptr;
439) log(data, "destroyed");
440) }
441)
442) /**
443) * @brief play sound on call if active
444) * @param[in,out] data data of SIP client
445) * @param[in] name name of sound
446) */
447) void do_play(Data &data, std::string const &name)
448) {
449) if (! data.lc) {
450) return;
451) }
452) if (! data.call) {
453) return;
454) }
455)
456) std::stringstream line;
457) line << "playing sound \"" << name << "\"";
458) log(data, line.str());
459) set_playback(data, name);
460) start_playback(data);
461) }
462)
463) /**
464) * @brief hangup call if active
465) * @param[in,out] data data of SIP client
466) */
467) void do_hangup(Data &data)
468) {
469) if (! data.lc) {
470) return;
471) }
472) if (! data.call) {
473) return;
474) }
475)
476) log(data, "terminating call...");
477) linphone_core_terminate_call(data.lc, data.call);
478) data.call = nullptr;
479) log(data, "call terminated");
480) }
481)
482) /**
483) * @brief linphone worker thread
484) * @param[in] soundDir directory of sound files
485) * @param[in,out] data shared data for communication with main Blinker thread
486) */
487) void worker(SipPhone::ConfigData const &configData,
488) SipPhone::SharedData &sharedData)
489) {
490) // set up data of SIP client
491) Data data;
492) data.configData = &configData;
493) data.sharedData = &sharedData;
494) memset(&data.callbacks, 0, sizeof(data.callbacks));
495) data.callbacks.global_state_changed = global_state_changed;
496) data.callbacks.registration_state_changed = registration_state_changed;
497) data.callbacks.call_state_changed = call_state_changed;
498) data.callbacks.dtmf_received = dtmf_received;
499) data.lc = nullptr;
500) data.call = nullptr;
501) data.logLineCnt = 0;
502)
503) // main loop of worker - with shared data locked
504) std::chrono::milliseconds timeout(10);
505) std::unique_lock<std::mutex> lock(sharedData.mtx);
506) while (sharedData.run) {
507)
508) // hangup
509) if (sharedData.hangup) {
510) sharedData.hangup = false;
511) sharedData.reqPlay = false;
512) sharedData.dtmf.clear();
513) sharedData.terminated = true;
514) // execute hangup with unlocked shared data (parallelism!)
515) lock.unlock();
516) do_hangup(data);
517) lock.lock();
518) continue; // re-check everything after re-locking mutex
519) }
520)
521) // re-register (including initial registration and derigstration)
522) if (sharedData.reregister) {
523) sharedData.reregister = false;
524) sharedData.hangup = false;
525) sharedData.reqPlay = false;
526) sharedData.dtmf.clear();
527) sharedData.terminated = true;
528) std::string server = sharedData.server;
529) std::string username = sharedData.username;
530) std::string password = sharedData.password;
531) // execute re-registration with unlocked shared data (parallelism!)
532) lock.unlock();
533) do_deregister(data);
534) do_register(data, server, username, password);
535) lock.lock();
536) continue; // re-check everything after re-locking mutex
537) }
538)
539) // play sound
540) if (sharedData.reqPlay) {
541) sharedData.reqPlay = false;
542) std::string name = sharedData.reqPlayName;
543) // execute hangup with unlocked shared data (parallelism!)
544) lock.unlock();
545) do_play(data, name);
546) lock.lock();
547) continue; // re-check everything after re-locking mutex
548) }
549)
550) // background processing by linphone if active
551) if (data.lc) {
552) lock.unlock();
553) linphone_core_iterate(data.lc);
554) lock.lock();
555) }
556)
557) // nothing to do, wait for signal
558) sharedData.cond.wait_for(lock, timeout);
559)
560) } // while (sharedData.run)
561) }
562)
563) } // namespace linphone
564)
565) #endif // #ifdef BLINKER_CFG_LINPHONE
566)
567) /**
568) * @brief global initialization (call once before using this class)
569) * @param[in] globalLogDir directory for global SIP log files
570) */
571) void SipPhone::init(const Directory &globalLogDir)
572) {
573) #ifdef BLINKER_CFG_LINPHONE
574) linphone::init(globalLogDir.getFile("sip.log").getPath());
575) #endif
576) }
577)
578) /**
579) * @brief constructor
580) * @param[in] name module name
581) * @param[in] mgrs managers
582) * @param[in] dirBase base directory
583) */
584) SipPhone::SipPhone(const std::string &name, Mgrs &mgrs,
585) const Directory &dirBase):
586) Module(name, mgrs, dirBase),
587) m_fileServer(dirBase.getFile("server")),
588) m_fileUsername(dirBase.getFile("username")),
589) m_filePassword(dirBase.getFile("password")),
590) m_fileTarget(dirBase.getFile("target")),
591) m_configData(),
592) m_worker(),
593) m_sharedData(),
594) m_curConn(nullptr)
595) {
596) m_configData.soundDir = dirBase.getSubdir("sounds").getPath();
597) m_configData.logFileName = dirBase.getFile("sip.log").getPath();
598) m_sharedData.run = true;
599) m_sharedData.reregister = false;
600) m_sharedData.reqPlay = false;
601) m_sharedData.hangup = false;
602) m_sharedData.accepted = false;
603) m_sharedData.terminated = false;
604)
605) m_mgrs.m_callMgr.requestTimeCall(this, Time::now());
606)
607) #ifdef BLINKER_CFG_LINPHONE
608) m_worker = std::thread(linphone::worker, std::ref(m_configData),
609) std::ref(m_sharedData));
610) #endif
611)
612) sipRegister();
613) }
614)
615) /// virtual destructor
616) SipPhone::~SipPhone()
617) {
618) if (m_curConn) {
619) m_curConn->close();
620) m_curConn = nullptr;
621) }
622)
623) sipDeregister();
624)
625) {
626) std::lock_guard<std::mutex> lock(m_sharedData.mtx);
627) m_sharedData.run = false;
628) }
629) m_sharedData.cond.notify_one();
630) #ifdef BLINKER_CFG_LINPHONE
631) m_worker.join();
632) #endif
633)
634) m_mgrs.m_callMgr.cancelTimeCall(this);
635) }
636)
637) /// check for update of configuration
638) void SipPhone::updateConfig()
639) {
640) // server, username or password file modified -> re-connect
641) if (m_fileServer.checkModified() || m_fileUsername.checkModified() ||
642) m_filePassword.checkModified()) {
643) // re-register
644) sipDeregister();
645) sipRegister();
646) }
647)
648) // target file was modified -> re-get target operator interface to connect to
649) if (m_fileTarget.checkModified()) {
650) m_fileTarget.update();
651) }
652) }
653)
654) /// callback when requested time reached
655) void SipPhone::timeCall()
656) {
657) Time now = Time::now();
658)
659) // get information from SIP phone worker
660) bool accepted;
661) std::string dtmf;
662) bool terminated;
663) {
664) std::lock_guard<std::mutex> lock(m_sharedData.mtx);
665) accepted = m_sharedData.accepted;
666) m_sharedData.accepted = false;
667) dtmf = m_sharedData.dtmf;
668) m_sharedData.dtmf.clear();
669) terminated = m_sharedData.terminated;
670) m_sharedData.terminated = false;
671) }
672)
673) // phone call has terminated or new one has been accepted
674) if (terminated || accepted) {
675) // close connection if any
676) if (m_curConn) {
677) // request hang up of phone
678) {
679) std::lock_guard<std::mutex> lock(m_sharedData.mtx);
680) m_sharedData.hangup = true;
681) }
682) // close and forget connection
683) m_curConn->close();
684) m_curConn = nullptr;
685) }
686) }
687)
688) // phone call has been accepted
689) if (accepted) {
690) // try to open new connection to target operator interface
691) if (m_fileTarget.m_valid) {
692) m_curConn = m_mgrs.m_opMgr.connect(m_fileTarget.m_obj.m_str, this);
693) }
|