Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 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 793 : is_uninitialized(std::weak_ptr<T> const& weak)
47 : {
48 : using wt = std::weak_ptr<T>;
49 793 : 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 2370 : 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 22080 : 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 793 : bool isConferenceParticipant() const { return not is_uninitialized(conf_); }
124 :
125 3151 : std::weak_ptr<Account> getAccount() const { return account_; }
126 : std::string getAccountId() const;
127 :
128 177 : 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 2497 : 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 633 : 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 1375 : 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 a call with a list of media attributes.
205 : * @param mediaList The list of the media attributes.
206 : * The media attributes set by the caller of this method will
207 : * determine the response sent to the peer and the configuration
208 : * of the local media.
209 : * If the media list is empty, the current media set when the call
210 : * was created will be used.
211 : */
212 : virtual void answer(const std::vector<libjami::MediaMap>& mediaList) = 0;
213 :
214 : /**
215 : * Check the media of an incoming media change request.
216 : * This method checks the new media against the current media. It
217 : * determines if the differences are significant enough to require
218 : * more processing.
219 : * For instance, this can be used to check if the a change request
220 : * must be reported to the client for confirmation or can be handled
221 : * by the daemon.
222 : * The conditions that cause this method to return true are implementation
223 : * specific.
224 : *
225 : * @param the new media list from the remote
226 : * @return true if the new media differs from the current media
227 : **/
228 : virtual bool checkMediaChangeRequest(const std::vector<libjami::MediaMap>& remoteMediaList) = 0;
229 :
230 : /**
231 : * Process incoming media change request.
232 : *
233 : * @param the new media list from the remote
234 : */
235 : virtual void handleMediaChangeRequest(const std::vector<libjami::MediaMap>& remoteMediaList) = 0;
236 :
237 : /**
238 : * Answer to a media update request.
239 : * The media attributes set by the caller of this method will
240 : * determine the response to send to the peer and the configuration
241 : * of the local media.
242 : * @param mediaList The list of media attributes. An empty media
243 : * list means the media update request was not accepted, meaning the
244 : * call continue with the current media. It's up to the implementation
245 : * to determine wether an answer will be sent to the peer.
246 : * @param isRemote True if the media list is from the remote peer
247 : */
248 : virtual void answerMediaChangeRequest(const std::vector<libjami::MediaMap>& mediaList, bool isRemote = false) = 0;
249 : /**
250 : * Hang up the call
251 : * @param reason
252 : */
253 : virtual void hangup(int reason) = 0;
254 :
255 : /**
256 : * Refuse incoming call
257 : */
258 : virtual void refuse() = 0;
259 :
260 : /**
261 : * Transfer a call to specified URI
262 : * @param to The recipient of the call
263 : */
264 : virtual void transfer(const std::string& to) = 0;
265 :
266 : /**
267 : * Attended transfer
268 : * @param The target call id
269 : * @return True on success
270 : */
271 : virtual bool attendedTransfer(const std::string& to) = 0;
272 :
273 : /**
274 : * Put a call on hold
275 : * @param cb On hold can be queued if waiting for ICE. This callback will be called when ready
276 : * @return bool True on success, False if failed or pending
277 : */
278 : virtual bool onhold(OnReadyCb&& cb) = 0;
279 :
280 : /**
281 : * Resume a call from hold state
282 : * @param cb On hold can be queued if waiting for ICE. This callback will be called when ready
283 : * @return bool True on success, False if failed or pending
284 : */
285 : virtual bool offhold(OnReadyCb&& cb) = 0;
286 :
287 : virtual void sendKeyframe(int streamIdx = -1) = 0;
288 :
289 : /**
290 : * Check wether ICE is enabled for media
291 : */
292 : virtual bool isIceEnabled() const = 0;
293 :
294 : /**
295 : * Peer has hung up a call
296 : */
297 : virtual void peerHungup();
298 :
299 : virtual void removeCall();
300 :
301 : /**
302 : * Update recording state. Typically used to send notifications
303 : * to peers about the local recording session state
304 : */
305 : virtual void updateRecState(bool state) = 0;
306 :
307 631 : void addStateListener(StateListenerCb&& listener)
308 : {
309 631 : std::lock_guard lk {callMutex_};
310 631 : stateChangedListeners_.emplace_back(std::move(listener));
311 631 : }
312 :
313 : /**
314 : * Attach subcall to this instance.
315 : * If this subcall is answered, this subcall and this instance will be merged using merge().
316 : */
317 : void addSubCall(Call& call);
318 :
319 : ///
320 : /// Return true if this call instance is a subcall (internal call for multi-device handling)
321 : ///
322 3971 : bool isSubcall() const
323 : {
324 3971 : std::lock_guard lk {callMutex_};
325 7941 : return parent_ != nullptr;
326 3970 : }
327 :
328 : /**
329 : * @return Call duration in milliseconds
330 : */
331 279 : std::chrono::milliseconds getCallDuration() const
332 : {
333 279 : return duration_start_ == time_point::min()
334 33 : ? std::chrono::milliseconds::zero()
335 558 : : std::chrono::duration_cast<std::chrono::milliseconds>(clock::now() - duration_start_);
336 : }
337 :
338 : // media management
339 : virtual bool toggleRecording();
340 :
341 : virtual std::vector<MediaAttribute> getMediaAttributeList() const = 0;
342 :
343 : virtual std::map<std::string, bool> getAudioStreams() const = 0;
344 :
345 : #ifdef ENABLE_VIDEO
346 : virtual void createSinks(ConfInfo& infos) = 0;
347 : #endif
348 :
349 0 : virtual void switchInput(const std::string& = {}) {};
350 :
351 : /**
352 : * mute/unmute a media of a call
353 : * @param mediaType type of media
354 : * @param isMuted true for muting, false for unmuting
355 : */
356 : virtual void muteMedia(const std::string& mediaType, bool isMuted) = 0;
357 :
358 : /**
359 : * Send DTMF
360 : * @param code The char code
361 : */
362 : virtual void carryingDTMFdigits(char code) = 0;
363 :
364 : /**
365 : * Make a change request of the current media with the provided media
366 : * @param mediaList the new media list
367 : * @return true on success
368 : */
369 : virtual bool requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) = 0;
370 :
371 : /**
372 : * Retrieve current medias list
373 : * @return current medias
374 : */
375 : virtual std::vector<libjami::MediaMap> currentMediaList() const = 0;
376 :
377 : /**
378 : * Send a message to a call identified by its callid
379 : *
380 : * @param A list of mimetype/payload pairs
381 : * @param The sender of this message (could be another participant of a conference)
382 : */
383 : virtual void sendTextMessage(const std::map<std::string, std::string>& messages, const std::string& from) = 0;
384 :
385 : void onTextMessage(std::map<std::string, std::string>&& messages);
386 :
387 0 : virtual std::shared_ptr<SystemCodecInfo> getAudioCodec() const { return {}; }
388 0 : virtual std::shared_ptr<SystemCodecInfo> getVideoCodec() const { return {}; }
389 :
390 : virtual void restartMediaSender() = 0;
391 :
392 : // Media status methods
393 : virtual bool hasVideo() const = 0;
394 : virtual bool isCaptureDeviceMuted(const MediaType& mediaType) const = 0;
395 :
396 : /**
397 : * A Call can be in a conference. If this is the case, the other side
398 : * will send conference information describing the rendered image
399 : * @msg A JSON object describing the conference
400 : */
401 : void setConferenceInfo(const std::string& msg);
402 :
403 : virtual void enterConference(std::shared_ptr<Conference> conference) = 0;
404 : virtual void exitConference() = 0;
405 :
406 0 : std::vector<std::map<std::string, std::string>> getConferenceInfos() const
407 : {
408 0 : return confInfo_.toVectorMapStringString();
409 : }
410 :
411 : std::unique_ptr<AudioDeviceGuard> audioGuard;
412 : void sendConfOrder(const Json::Value& root);
413 : void sendConfInfo(const std::string& json);
414 : void resetConfInfo();
415 :
416 : virtual void monitor() const = 0;
417 :
418 6 : int conferenceProtocolVersion() const { return peerConfProtocol_; }
419 :
420 : protected:
421 : using clock = std::chrono::steady_clock;
422 : using time_point = clock::time_point;
423 : virtual void merge(Call& scall);
424 :
425 : /**
426 : * Constructor of a call
427 : * @param id Unique identifier of the call
428 : * @param type set definitely this call as incoming/outgoing
429 : * @param details volatile details to customize the call creation
430 : */
431 : Call(const std::shared_ptr<Account>& account,
432 : const std::string& id,
433 : Call::CallType type,
434 : const std::map<std::string, std::string>& details = {});
435 :
436 : // TODO all these members are not protected against multi-thread access
437 :
438 : const std::string id_ {};
439 :
440 : ///< MultiDevice: parent call, nullptr otherwise. Access protected by callMutex_.
441 : mutable std::shared_ptr<Call> parent_;
442 :
443 : ///< MultiDevice: list of attached subcall
444 : SubcallSet subcalls_;
445 :
446 : using MsgList = std::list<std::pair<std::map<std::string, std::string>, std::string>>;
447 :
448 : ///< MultiDevice: message waiting to be sent (need a valid subcall)
449 : MsgList pendingOutMessages_;
450 :
451 : /** Protect every attribute that can be changed by two threads */
452 : mutable std::recursive_mutex callMutex_ {};
453 :
454 : mutable std::mutex confInfoMutex_ {};
455 : mutable ConfInfo confInfo_ {};
456 : time_point duration_start_ {time_point::min()};
457 :
458 : private:
459 : bool validStateTransition(CallState newState);
460 :
461 : void checkPendingIM();
462 :
463 : void checkAudio();
464 :
465 : void subcallStateChanged(Call&, Call::CallState, Call::ConnectionState);
466 :
467 : SubcallSet safePopSubcalls();
468 :
469 : std::vector<StateListenerCb> stateChangedListeners_ {};
470 :
471 : protected:
472 : /** Unique conference ID, used exclusively in case of a conference */
473 : std::weak_ptr<Conference> conf_ {};
474 :
475 : /** Type of the call */
476 : CallType type_;
477 :
478 : /** Associate account ID */
479 : std::weak_ptr<Account> account_;
480 :
481 : /** Disconnected/Progressing/Trying/Ringing/Connected */
482 : ConnectionState connectionState_ {ConnectionState::DISCONNECTED};
483 :
484 : /** Inactive/Active/Hold/Busy/Error */
485 : CallState callState_ {CallState::INACTIVE};
486 :
487 : std::string reason_ {};
488 :
489 : /** Direct IP-to-IP or classic call */
490 : bool isIPToIP_ {false};
491 :
492 : /** Number of the peer */
493 : std::string peerNumber_ {};
494 :
495 : /** Peer Display Name */
496 : std::string peerDisplayName_ {};
497 :
498 : time_t timestamp_start_ {0};
499 :
500 : ///< MultiDevice: message received by subcall to merged yet
501 : MsgList pendingInMessages_;
502 :
503 : /// Supported conference protocol version
504 : int peerConfProtocol_ {0};
505 : std::string toUsername_ {};
506 : };
507 :
508 : // Helpers
509 :
510 : /**
511 : * Obtain a shared smart pointer of instance
512 : */
513 : inline std::shared_ptr<Call>
514 877 : getPtr(Call& call)
515 : {
516 877 : return call.shared_from_this();
517 : }
518 :
519 : } // namespace jami
|