1 /**
2 * Whiteboard session manager
3 *
4 * Authors:
5 * David Yip <yipdw@rose-hulman.edu>
6 * Bob Jamison (Pedro port)
7 *
8 * Copyright (c) 2005 Authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #include <functional>
14 #include <algorithm>
15 #include <iostream>
17 #include <gtkmm.h>
18 #include <glibmm/i18n.h>
20 #include "xml/node-observer.h"
22 #include "document.h"
23 #include "desktop.h"
24 #include "desktop-handles.h"
26 #include "jabber_whiteboard/message-verifier.h"
27 #include "jabber_whiteboard/session-manager.h"
28 #include "jabber_whiteboard/inkboard-document.h"
29 #include "jabber_whiteboard/new-inkboard-document.h"
31 #define INKBOARD_XMLNS "http://inkscape.org/inkboard"
33 namespace Inkscape {
35 namespace Whiteboard {
37 //#########################################################################
38 //# S E S S I O N M A N A G E R
39 //#########################################################################
41 SessionManager *sessionManagerInstance = NULL;
43 void SessionManager::showClient()
44 {
45 SessionManager::instance().gui.show();
46 }
48 SessionManager&
49 SessionManager::instance()
50 {
51 if (!sessionManagerInstance)
52 sessionManagerInstance = new SessionManager();
53 return *sessionManagerInstance;
54 }
56 SessionManager::SessionManager()
57 {
58 sequenceNumber = 0L;
59 getClient().addXmppEventListener(*this);
61 this->_check_pending_invitations = Glib::signal_timeout().connect(sigc::mem_fun(*this, &SessionManager::_checkInvitationQueue), 50);
62 this->_check_invitation_responses = Glib::signal_timeout().connect(sigc::mem_fun(*this, &SessionManager::_checkInvitationResponseQueue), 50);
63 }
65 SessionManager::~SessionManager()
66 {
67 getClient().removeXmppEventListener(*this);
68 getClient().disconnect();
69 }
71 unsigned long SessionManager::getSequenceNumber()
72 {
73 return sequenceNumber++;
74 }
76 bool
77 SessionManager::send(const Glib::ustring &destJid,
78 const MessageType type,
79 const Glib::ustring &data)
80 {
81 Pedro::DOMString xmlData = Pedro::Parser::encode(data);
82 char *fmt=
83 "<message type='chat' from='%s' to='%s' id='ink_%d'>"
84 "<inkboard xmlns='%s' "
85 "protocol='%d' type='%d' seq='%d'><x:inkboard-data>%s</x:inkboard-data></inkboard>"
86 "<body></body>"
87 "</message>";
88 if (!getClient().write(fmt,
89 getClient().getJid().c_str(),
90 destJid.c_str(),
91 getClient().getMsgId(),
92 INKBOARD_XMLNS,
93 2,
94 (MessageType)type,
95 getSequenceNumber(),
96 xmlData.c_str()
97 ))
98 {
99 return false;
100 }
102 return true;
103 }
105 bool
106 SessionManager::sendGroup(const Glib::ustring &groupJid,
107 const MessageType type,
108 const Glib::ustring &data)
109 {
110 Pedro::DOMString xmlData = Pedro::Parser::encode(data);
111 char *fmt=
112 "<message type='groupchat' from='%s' to='%s' id='ink_%d'>"
113 "<inkboard xmlns='%s' "
114 "protocol='%d' type='%d' seq='%d'><x:inkboard-data>%s</x:inkboard-data></inkboard>"
115 "<body></body>"
116 "</message>";
117 if (!getClient().write(fmt,
118 getClient().getJid().c_str(),
119 groupJid.c_str(),
120 getClient().getMsgId(),
121 INKBOARD_XMLNS,
122 2,
123 type,
124 getSequenceNumber(),
125 xmlData.c_str()
126 ))
127 {
128 return false;
129 }
131 return true;
132 }
134 void
135 SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
136 {
137 int type = event.getType();
139 switch (type) {
140 case Pedro::XmppEvent::EVENT_STATUS:
141 {
142 break;
143 }
144 case Pedro::XmppEvent::EVENT_ERROR:
145 {
146 break;
147 }
148 case Pedro::XmppEvent::EVENT_CONNECTED:
149 {
150 break;
151 }
152 case Pedro::XmppEvent::EVENT_DISCONNECTED:
153 {
154 break;
155 }
156 case Pedro::XmppEvent::EVENT_MESSAGE:
157 {
158 printf("## SM message:%s\n", event.getFrom().c_str());
159 Pedro::Element *root = event.getDOM();
161 if (root)
162 {
163 if (root->getTagAttribute("inkboard", "xmlns") ==
164 INKBOARD_XMLNS)
165 {
166 _processInkboardEvent(event);
167 }
168 }
169 break;
170 }
171 case Pedro::XmppEvent::EVENT_PRESENCE:
172 {
173 break;
174 }
175 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
176 {
177 printf("## SM MUC message:%s\n", event.getFrom().c_str());
178 Pedro::Element *root = event.getDOM();
179 if (root)
180 {
181 if (root->getTagAttribute("inkboard", "xmlns") ==
182 INKBOARD_XMLNS)
183 {
184 _processInkboardEvent(event);
185 }
186 }
187 break;
188 }
189 case Pedro::XmppEvent::EVENT_MUC_JOIN:
190 {
191 break;
192 }
193 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
194 {
195 break;
196 }
197 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
198 {
199 break;
200 }
201 default:
202 {
203 break;
204 }
205 }
206 }
208 /**
209 * Initiates a shared session with a user or conference room.
210 *
211 * \param to The recipient to which this desktop will be linked, specified as a JID.
212 * \param type Type of the session; i.e. private message or group chat.
213 */
214 void
215 SessionManager::doShare(Glib::ustring const& to, SessionType type)
216 {
217 SPDesktop* dt = createInkboardDesktop(to, type);
218 if (dt != NULL) {
219 InkboardDocument* doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
220 if (doc != NULL) {
221 doc->startSessionNegotiation();
222 }
223 }
224 }
226 /**
227 * Clone of sp_file_new and all related subroutines and functions,
228 * with appropriate modifications to use the Inkboard document class.
229 *
230 * \param to The JID to which this Inkboard document will be connected.
231 * \return A pointer to the created desktop, or NULL if a new desktop could not be created.
232 */
233 SPDesktop*
234 SessionManager::createInkboardDesktop(Glib::ustring const& to, SessionType type)
235 {
236 // Create document (sp_repr_document_new clone)
237 SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", type, to);
238 g_return_val_if_fail(doc != NULL, NULL);
240 InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
241 if (inkdoc == NULL) { // this shouldn't ever happen...
242 return NULL;
243 }
245 // Create desktop and attach document
246 SPDesktop *dt = makeInkboardDesktop(doc);
247 _inkboards.push_back(Inkboard_record_type(to, inkdoc));
248 return dt;
249 }
251 void
252 SessionManager::terminateInkboardSession(Glib::ustring const& to)
253 {
254 std::cout << "Terminating Inkboard session to " << to << std::endl;
255 Inkboards_type::iterator i = _inkboards.begin();
256 for(; i != _inkboards.end(); ++i) {
257 if ((*i).first == to) {
258 break;
259 }
260 }
262 if (i != _inkboards.end()) {
263 std::cout << "Erasing Inkboard session to " << to << std::endl;
264 (*i).second->terminateSession();
265 _inkboards.erase(i);
266 }
267 }
269 InkboardDocument*
270 SessionManager::getInkboardSession(Glib::ustring const& to)
271 {
272 Inkboards_type::iterator i = _inkboards.begin();
273 for(; i != _inkboards.end(); ++i) {
274 if ((*i).first == to) {
275 return (*i).second;
276 }
277 }
278 return NULL;
279 }
281 void
282 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
283 {
284 Pedro::Element* root = event.getDOM();
286 if (root == NULL) {
287 g_warning("Received null DOM; ignoring message.");
288 return;
289 }
291 Pedro::DOMString type = root->getTagAttribute("inkboard", "type");
292 Pedro::DOMString seq = root->getTagAttribute("inkboard", "seq");
293 Pedro::DOMString protover = root->getTagAttribute("inkboard", "protocol");
295 if (type.empty() || seq.empty() || protover.empty()) {
296 g_warning("Received incomplete Inkboard message (missing type, protocol, or sequence number); ignoring message.");
297 return;
298 }
300 MessageType mtype = static_cast< MessageType >(atoi(type.c_str()));
302 // Messages that deal with the creation and destruction of sessions should be handled
303 // here in the SessionManager.
304 //
305 // These events are listed below, along with rationale.
306 //
307 // - CONNECT_REQUEST_USER: when we begin to process this message, we will not have an
308 // Inkboard session available to send the message to. Therefore, this message needs
309 // to be handled by the SessionManager.
310 //
311 // - CONNECT_REQUEST_REFUSED_BY_PEER: this message means that the recipient of a
312 // private invitation refused the invitation. In this case, we need to destroy the
313 // Inkboard desktop, document, and session associated with that invitation.
314 // Destruction of these components seems to be more naturally done in the SessionManager
315 // than in the Inkboard document itself (especially since the document may be associated
316 // with multiple desktops).
317 //
318 // - UNSUPPORTED_PROTOCOL_VERSION: this message means that the recipient of an invitation
319 // does not support the version of the Inkboard protocol we are using. In this case,
320 // we have to destroy the Inkboard desktop, document, and session associated with that
321 // invitation. The rationale for doing it in the SessionManager is the same as that
322 // given above.
323 //
324 // - ALREADY_IN_SESSION: similar rationale to above.
325 //
326 // - DISCONNECTED_FROM_USER_SIGNAL: similar rationale to above.
327 //
328 //
329 // All other events can be handled inside an Inkboard session.
331 // The message we are handling will have come from some Jabber ID. We need to verify
332 // that the Inkboard session associated with that JID is in the correct state for the
333 // incoming message (or, in some cases, that the session correctly exists / does not
334 // exist).
335 InkboardDocument* doc = getInkboardSession(event.getFrom());
337 // NOTE: This line refers to a class that hasn't been written yet
338 // MessageValidityTestResult res = MessageVerifier::verifyMessageValidity(event, mtype, doc);
340 MessageValidityTestResult res = RESULT_INVALID;
342 switch (res) {
343 case RESULT_VALID:
344 {
345 switch (mtype) {
346 case CONNECT_REQUEST_USER:
347 case CONNECT_REQUEST_REFUSED_BY_PEER:
348 case UNSUPPORTED_PROTOCOL_VERSION:
349 case ALREADY_IN_SESSION:
350 _handleSessionEvent(mtype, event);
351 break;
352 case DISCONNECTED_FROM_USER_SIGNAL:
353 break;
354 default:
355 if (doc != NULL) {
356 unsigned int seqnum = atoi(seq.c_str());
357 doc->processInkboardEvent(mtype, seqnum, event.getData());
358 }
359 break;
360 }
361 break;
362 }
363 case RESULT_INVALID:
364 default:
365 // FIXME: better warning message
366 g_warning("Received message in invalid context.");
367 break;
368 }
369 }
371 void
372 SessionManager::_handleSessionEvent(MessageType mtype, Pedro::XmppEvent const& event)
373 {
374 switch (mtype) {
375 case CONNECT_REQUEST_USER:
376 _handleIncomingInvitation(event.getFrom());
377 break;
378 case CONNECT_REQUEST_REFUSED_BY_PEER:
379 _handleInvitationResponse(event.getFrom(), DECLINE_INVITATION);
380 break;
381 case ALREADY_IN_SESSION:
382 _handleInvitationResponse(event.getFrom(), PEER_ALREADY_IN_SESSION);
383 break;
384 case UNSUPPORTED_PROTOCOL_VERSION:
385 _handleInvitationResponse(event.getFrom(), UNSUPPORTED_PROTOCOL);
386 break;
387 default:
388 break;
389 }
390 }
392 void
393 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
394 {
395 // don't insert duplicate invitations
396 if (std::find(_pending_invitations.begin(), _pending_invitations.end(), from) != _pending_invitations.end()) {
397 return;
398 }
400 // We need to do the invitation confirm/deny dialog elsewhere --
401 // when this method is called, we're still executing in Pedro's context,
402 // which causes issues when we run a dialog main loop.
403 //
404 // The invitation handling is done in a poller timeout that executes approximately
405 // every 50ms. It calls _checkInvitationQueue.
406 _pending_invitations.push_back(from);
408 }
410 void
411 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
412 {
413 // only handle one response per invitation sender
414 //
415 // TODO: this could have one huge bug: say that Alice sends an invite to Bob, but
416 // Bob is doing something else at the moment and doesn't want to get in an Inkboard
417 // session. Eve intercepts Bob's "reject invitation" message and passes a
418 // "accept invitation" message to Alice that comes before Bob's "reject invitation"
419 // message.
420 //
421 // Does XMPP prevent this sort of attack? Need to investigate that.
422 if (std::find_if(_invitation_responses.begin(), _invitation_responses.end(), CheckInvitationSender(from)) != _invitation_responses.end()) {
423 return;
424 }
426 // We need to do the invitation confirm/deny dialog elsewhere --
427 // when this method is called, we're still executing in Pedro's context,
428 // which causes issues when we run a dialog main loop.
429 //
430 // The invitation handling is done in a poller timeout that executes approximately
431 // every 50ms. It calls _checkInvitationResponseQueue.
432 _invitation_responses.push_back(Invitation_response_type(from, resp));
434 }
436 } // namespace Whiteboard
438 } // namespace Inkscape
441 /*
442 Local Variables:
443 mode:c++
444 c-file-style:"stroustrup"
445 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
446 indent-tabs-mode:nil
447 fill-column:99
448 End:
449 */
450 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :