c7e2a398fe5f9b27711ad0620cbc10ac4c074f48
Stefan Schuermans implement SIP

Stefan Schuermans authored 5 years ago

1) /* Blinker
Stefan Schuermans update copyright header

Stefan Schuermans authored 5 years ago

2)    Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
Stefan Schuermans 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)     }
Stefan Schuermans sip: hang up after connect...

Stefan Schuermans authored 5 years ago

694)     // operator connection failed -> request hang up of phone
695)     if (! m_curConn) {
696)       std::lock_guard<std::mutex> lock(m_sharedData.mtx);
697)       m_sharedData.hangup = true;
698)     }