Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #pragma once
18 :
19 : #ifdef HAVE_CONFIG_H
20 : #include "config.h"
21 : #endif
22 :
23 : #include "logger.h"
24 :
25 : #include "conference.h"
26 : #include "media/recordable.h"
27 : #include "media/peerrecorder.h"
28 : #include "media/media_codec.h"
29 : #include "media/media_attribute.h"
30 :
31 : #include <dhtnet/ip_utils.h>
32 :
33 : #include <atomic>
34 : #include <mutex>
35 : #include <map>
36 : #include <sstream>
37 : #include <memory>
38 : #include <vector>
39 : #include <condition_variable>
40 : #include <set>
41 : #include <list>
42 : #include <functional>
43 :
44 : template<typename T>
45 : bool
46 791 : is_uninitialized(std::weak_ptr<T> const& weak)
47 : {
48 : using wt = std::weak_ptr<T>;
49 791 : return !weak.owner_before(wt {}) && !wt {}.owner_before(weak);
50 : }
51 :
52 : namespace jami {
53 :
54 : class VoIPLink;
55 : class Account;
56 : class AudioDeviceGuard;
57 :
58 : class Call;
59 : class Conference;
60 :
61 : using CallMap = std::map<std::string, std::shared_ptr<Call>>;
62 :
63 : namespace video {
64 : class VideoGenerator;
65 : }
66 :
67 : /*
68 : * @file call.h
69 : * @brief A call is the base class for protocol-based calls
70 : */
71 :
72 : class Call : public Recordable, public PeerRecorder, public std::enable_shared_from_this<Call>
73 : {
74 : public:
75 : /**
76 : * Tell where we're at with the call. The call gets Connected when we know
77 : * from the other end what happened with out call. A call can be 'Connected'
78 : * even if the call state is Busy, or Error.
79 : *
80 : * Audio should be transmitted when ConnectionState = Connected AND
81 : * CallState = Active.
82 : *
83 : * \note modify validStateTransition/getStateStr if this enum changes
84 : */
85 : enum class ConnectionState : unsigned { DISCONNECTED, TRYING, PROGRESSING, RINGING, CONNECTED, COUNT__ };
86 :
87 : /**
88 : * The Call State.
89 : *
90 : * \note modify validStateTransition/getStateStr if this enum changes
91 : */
92 : enum class CallState : unsigned { INACTIVE, ACTIVE, HOLD, BUSY, PEER_BUSY, MERROR, OVER, COUNT__ };
93 :
94 : enum class LinkType { GENERIC, SIP };
95 :
96 : using SubcallSet = std::set<std::shared_ptr<Call>, std::owner_less<std::shared_ptr<Call>>>;
97 : using OnReadyCb = std::function<void(bool)>;
98 : using StateListenerCb = std::function<bool(CallState, ConnectionState, int)>;
99 :
100 : /**
101 : * This determines if the call originated from the local user (OUTGOING)
102 : * or from some remote peer (INCOMING, MISSED).
103 : */
104 : enum class CallType : unsigned { INCOMING, OUTGOING, MISSED };
105 :
106 : virtual ~Call();
107 :
108 2373 : std::weak_ptr<Call> weak() { return std::static_pointer_cast<Call>(shared_from_this()); }
109 :
110 0 : virtual LinkType getLinkType() const { return LinkType::GENERIC; }
111 :
112 : /**
113 : * Return a reference on the call id
114 : * @return call id
115 : */
116 21773 : const std::string& getCallId() const { return id_; }
117 :
118 : /**
119 : * Return a reference on the conference id
120 : * @return call id
121 : */
122 428 : std::shared_ptr<Conference> getConference() const { return conf_.lock(); }
123 791 : bool isConferenceParticipant() const { return not is_uninitialized(conf_); }
124 :
125 3150 : std::weak_ptr<Account> getAccount() const { return account_; }
126 : std::string getAccountId() const;
127 :
128 176 : CallType getCallType() const { return type_; }
129 :
130 : /**
131 : * Set the peer number (destination on outgoing)
132 : * not protected by mutex (when created)
133 : * @param number peer number
134 : */
135 390 : void setPeerNumber(const std::string& number) { peerNumber_ = number; }
136 :
137 : /**
138 : * Get the peer number (destination on outgoing)
139 : * not protected by mutex (when created)
140 : * @return std::string The peer number
141 : */
142 2499 : const std::string& getPeerNumber() const { return peerNumber_; }
143 : /**
144 : * Set the display name (caller in ingoing)
145 : * not protected by mutex (when created)
146 : * @return std::string The peer display name
147 : */
148 101 : void setPeerDisplayName(const std::string& name) { peerDisplayName_ = name; }
149 :
150 : /**
151 : * Get "To" from the invite
152 : * @note Used to make the difference between incoming calls for accounts and for conversations
153 : * @return the "To" that was present in the invite
154 : */
155 632 : const std::string& toUsername() const { return toUsername_; }
156 : /**
157 : * Updated by sipvoiplink, corresponds to the "To" in the invite
158 : * @param username "To"
159 : */
160 101 : void toUsername(const std::string& username) { toUsername_ = username; }
161 :
162 : /**
163 : * Get the peer display name (caller in ingoing)
164 : * not protected by mutex (when created)
165 : * @return std::string The peer name
166 : */
167 : const std::string& getPeerDisplayName() const { return peerDisplayName_; }
168 : /**
169 : * Tell if the call is incoming
170 : * @return true if yes false otherwise
171 : */
172 1390 : bool isIncoming() const { return type_ == CallType::INCOMING; }
173 :
174 : /**
175 : * Set the state of the call (protected by mutex)
176 : * @param call_state The call state
177 : * @param cnx_state The call connection state
178 : * @param code Optional error-dependent code (used to report more information)
179 : * @return true if the requested state change was valid, false otherwise
180 : */
181 : bool setState(CallState call_state, signed code = 0);
182 : bool setState(CallState call_state, ConnectionState cnx_state, signed code = 0);
183 : bool setState(ConnectionState cnx_state, signed code = 0);
184 :
185 : /**
186 : * Get the call state of the call (protected by mutex)
187 : * @return CallState The call state
188 : */
189 : CallState getState() const;
190 :
191 : /**
192 : * Get the connection state of the call (protected by mutex)
193 : * @return ConnectionState The connection state
194 : */
195 : ConnectionState getConnectionState() const;
196 :
197 : std::string getStateStr() const;
198 :
199 : void setIPToIP(bool IPToIP) { isIPToIP_ = IPToIP; }
200 :
201 : virtual std::map<std::string, std::string> getDetails() const;
202 :
203 : /**
204 : * Answer the call
205 : */
206 : virtual void answer() = 0;
207 :
208 : /**
209 : * Answer a call with a list of media attributes.
210 : * @param mediaList The list of the media attributes.
211 : * The media attributes set by the caller of this method will
212 : * determine the response sent to the peer and the configuration
213 : * of the local media.
214 : * If the media list is empty, the current media set when the call
215 : * was created will be used.
216 : */
217 : virtual void answer(const std::vector<libjami::MediaMap>& mediaList) = 0;
218 :
219 : /**
220 : * Check the media of an incoming media change request.
221 : * This method checks the new media against the current media. It
222 : * determines if the differences are significant enough to require
223 : * more processing.
224 : * For instance, this can be used to check if the a change request
225 : * must be reported to the client for confirmation or can be handled
226 : * by the daemon.
227 : * The conditions that cause this method to return true are implementation
228 : * specific.
229 : *
230 : * @param the new media list from the remote
231 : * @return true if the new media differs from the current media
232 : **/
233 : virtual bool checkMediaChangeRequest(const std::vector<libjami::MediaMap>& remoteMediaList) = 0;
234 :
235 : /**
236 : * Process incoming media change request.
237 : *
238 : * @param the new media list from the remote
239 : */
240 : virtual void handleMediaChangeRequest(const std::vector<libjami::MediaMap>& remoteMediaList) = 0;
241 :
242 : /**
243 : * Answer to a media update request.
244 : * The media attributes set by the caller of this method will
245 : * determine the response to send to the peer and the configuration
246 : * of the local media.
247 : * @param mediaList The list of media attributes. An empty media
248 : * list means the media update request was not accepted, meaning the
249 : * call continue with the current media. It's up to the implementation
250 : * to determine wether an answer will be sent to the peer.
251 : * @param isRemote True if the media list is from the remote peer
252 : */
253 : virtual void answerMediaChangeRequest(const std::vector<libjami::MediaMap>& mediaList, bool isRemote = false) = 0;
254 : /**
255 : * Hang up the call
256 : * @param reason
257 : */
258 : virtual void hangup(int reason) = 0;
259 :
260 : /**
261 : * Refuse incoming call
262 : */
263 : virtual void refuse() = 0;
264 :
265 : /**
266 : * Transfer a call to specified URI
267 : * @param to The recipient of the call
268 : */
269 : virtual void transfer(const std::string& to) = 0;
270 :
271 : /**
272 : * Attended transfer
273 : * @param The target call id
274 : * @return True on success
275 : */
276 : virtual bool attendedTransfer(const std::string& to) = 0;
277 :
278 : /**
279 : * Put a call on hold
280 : * @param cb On hold can be queued if waiting for ICE. This callback will be called when ready
281 : * @return bool True on success, False if failed or pending
282 : */
283 : virtual bool onhold(OnReadyCb&& cb) = 0;
284 :
285 : /**
286 : * Resume a call from hold state
287 : * @param cb On hold can be queued if waiting for ICE. This callback will be called when ready
288 : * @return bool True on success, False if failed or pending
289 : */
290 : virtual bool offhold(OnReadyCb&& cb) = 0;
291 :
292 : virtual void sendKeyframe(int streamIdx = -1) = 0;
293 :
294 : /**
295 : * Check wether ICE is enabled for media
296 : */
297 : virtual bool isIceEnabled() const = 0;
298 :
299 : /**
300 : * Peer has hung up a call
301 : */
302 : virtual void peerHungup();
303 :
304 : virtual void removeCall();
305 :
306 : /**
307 : * Update recording state. Typically used to send notifications
308 : * to peers about the local recording session state
309 : */
310 : virtual void updateRecState(bool state) = 0;
311 :
312 631 : void addStateListener(StateListenerCb&& listener)
313 : {
314 631 : std::lock_guard lk {callMutex_};
315 631 : stateChangedListeners_.emplace_back(std::move(listener));
316 631 : }
317 :
318 : /**
319 : * Attach subcall to this instance.
320 : * If this subcall is answered, this subcall and this instance will be merged using merge().
321 : */
322 : void addSubCall(Call& call);
323 :
324 : ///
325 : /// Return true if this call instance is a subcall (internal call for multi-device handling)
326 : ///
327 3956 : bool isSubcall() const
328 : {
329 3956 : std::lock_guard lk {callMutex_};
330 7912 : return parent_ != nullptr;
331 3956 : }
332 :
333 : /**
334 : * @return Call duration in milliseconds
335 : */
336 277 : std::chrono::milliseconds getCallDuration() const
337 : {
338 277 : return duration_start_ == time_point::min()
339 31 : ? std::chrono::milliseconds::zero()
340 554 : : std::chrono::duration_cast<std::chrono::milliseconds>(clock::now() - duration_start_);
341 : }
342 :
343 : // media management
344 : virtual bool toggleRecording();
345 :
346 : virtual std::vector<MediaAttribute> getMediaAttributeList() const = 0;
347 :
348 : virtual std::map<std::string, bool> getAudioStreams() const = 0;
349 :
350 : #ifdef ENABLE_VIDEO
351 : virtual void createSinks(ConfInfo& infos) = 0;
352 : #endif
353 :
354 0 : virtual void switchInput(const std::string& = {}) {};
355 :
356 : /**
357 : * mute/unmute a media of a call
358 : * @param mediaType type of media
359 : * @param isMuted true for muting, false for unmuting
360 : */
361 : virtual void muteMedia(const std::string& mediaType, bool isMuted) = 0;
362 :
363 : /**
364 : * Send DTMF
365 : * @param code The char code
366 : */
367 : virtual void carryingDTMFdigits(char code) = 0;
368 :
369 : /**
370 : * Make a change request of the current media with the provided media
371 : * @param mediaList the new media list
372 : * @return true on success
373 : */
374 : virtual bool requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) = 0;
375 :
376 : /**
377 : * Retrieve current medias list
378 : * @return current medias
379 : */
380 : virtual std::vector<libjami::MediaMap> currentMediaList() const = 0;
381 :
382 : /**
383 : * Send a message to a call identified by its callid
384 : *
385 : * @param A list of mimetype/payload pairs
386 : * @param The sender of this message (could be another participant of a conference)
387 : */
388 : virtual void sendTextMessage(const std::map<std::string, std::string>& messages, const std::string& from) = 0;
389 :
390 : void onTextMessage(std::map<std::string, std::string>&& messages);
391 :
392 0 : virtual std::shared_ptr<SystemCodecInfo> getAudioCodec() const { return {}; }
393 0 : virtual std::shared_ptr<SystemCodecInfo> getVideoCodec() const { return {}; }
394 :
395 : virtual void restartMediaSender() = 0;
396 :
397 : // Media status methods
398 : virtual bool hasVideo() const = 0;
399 : virtual bool isCaptureDeviceMuted(const MediaType& mediaType) const = 0;
400 :
401 : /**
402 : * A Call can be in a conference. If this is the case, the other side
403 : * will send conference information describing the rendered image
404 : * @msg A JSON object describing the conference
405 : */
406 : void setConferenceInfo(const std::string& msg);
407 :
408 : virtual void enterConference(std::shared_ptr<Conference> conference) = 0;
409 : virtual void exitConference() = 0;
410 :
411 0 : std::vector<std::map<std::string, std::string>> getConferenceInfos() const
412 : {
413 0 : return confInfo_.toVectorMapStringString();
414 : }
415 :
416 : std::unique_ptr<AudioDeviceGuard> audioGuard;
417 : void sendConfOrder(const Json::Value& root);
418 : void sendConfInfo(const std::string& json);
419 : void resetConfInfo();
420 :
421 : virtual void monitor() const = 0;
422 :
423 6 : int conferenceProtocolVersion() const { return peerConfProtocol_; }
424 :
425 : protected:
426 : using clock = std::chrono::steady_clock;
427 : using time_point = clock::time_point;
428 : virtual void merge(Call& scall);
429 :
430 : /**
431 : * Constructor of a call
432 : * @param id Unique identifier of the call
433 : * @param type set definitely this call as incoming/outgoing
434 : * @param details volatile details to customize the call creation
435 : */
436 : Call(const std::shared_ptr<Account>& account,
437 : const std::string& id,
438 : Call::CallType type,
439 : const std::map<std::string, std::string>& details = {});
440 :
441 : // TODO all these members are not protected against multi-thread access
442 :
443 : const std::string id_ {};
444 :
445 : ///< MultiDevice: parent call, nullptr otherwise. Access protected by callMutex_.
446 : mutable std::shared_ptr<Call> parent_;
447 :
448 : ///< MultiDevice: list of attached subcall
449 : SubcallSet subcalls_;
450 :
451 : using MsgList = std::list<std::pair<std::map<std::string, std::string>, std::string>>;
452 :
453 : ///< MultiDevice: message waiting to be sent (need a valid subcall)
454 : MsgList pendingOutMessages_;
455 :
456 : /** Protect every attribute that can be changed by two threads */
457 : mutable std::recursive_mutex callMutex_ {};
458 :
459 : mutable std::mutex confInfoMutex_ {};
460 : mutable ConfInfo confInfo_ {};
461 : time_point duration_start_ {time_point::min()};
462 :
463 : private:
464 : bool validStateTransition(CallState newState);
465 :
466 : void checkPendingIM();
467 :
468 : void checkAudio();
469 :
470 : void subcallStateChanged(Call&, Call::CallState, Call::ConnectionState);
471 :
472 : SubcallSet safePopSubcalls();
473 :
474 : std::vector<StateListenerCb> stateChangedListeners_ {};
475 :
476 : protected:
477 : /** Unique conference ID, used exclusively in case of a conference */
478 : std::weak_ptr<Conference> conf_ {};
479 :
480 : /** Type of the call */
481 : CallType type_;
482 :
483 : /** Associate account ID */
484 : std::weak_ptr<Account> account_;
485 :
486 : /** Disconnected/Progressing/Trying/Ringing/Connected */
487 : ConnectionState connectionState_ {ConnectionState::DISCONNECTED};
488 :
489 : /** Inactive/Active/Hold/Busy/Error */
490 : CallState callState_ {CallState::INACTIVE};
491 :
492 : std::string reason_ {};
493 :
494 : /** Direct IP-to-IP or classic call */
495 : bool isIPToIP_ {false};
496 :
497 : /** Number of the peer */
498 : std::string peerNumber_ {};
499 :
500 : /** Peer Display Name */
501 : std::string peerDisplayName_ {};
502 :
503 : time_t timestamp_start_ {0};
504 :
505 : ///< MultiDevice: message received by subcall to merged yet
506 : MsgList pendingInMessages_;
507 :
508 : /// Supported conference protocol version
509 : int peerConfProtocol_ {0};
510 : std::string toUsername_ {};
511 : };
512 :
513 : // Helpers
514 :
515 : /**
516 : * Obtain a shared smart pointer of instance
517 : */
518 : inline std::shared_ptr<Call>
519 884 : getPtr(Call& call)
520 : {
521 884 : return call.shared_from_this();
522 : }
523 :
524 : } // namespace jami
|