File: srvc_conf.c

package info (click to toggle)
meanwhile 1.0.2-5
  • links: PTS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 2,732 kB
  • ctags: 3,503
  • sloc: ansic: 13,856; sh: 8,360; makefile: 145
file content (865 lines) | stat: -rw-r--r-- 20,619 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865

/*
  Meanwhile - Unofficial Lotus Sametime Community Client Library
  Copyright (C) 2004  Christopher (siege) O'Brien
  
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <glib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "mw_channel.h"
#include "mw_debug.h"
#include "mw_error.h"
#include "mw_message.h"
#include "mw_service.h"
#include "mw_session.h"
#include "mw_srvc_conf.h"
#include "mw_util.h"


/* This thing needs a re-write. More than anything else, I need to
   re-examine the conferencing service protocol from more modern
   clients */


#define PROTOCOL_TYPE   0x00000010
#define PROTOCOL_VER    0x00000002


/** @see mwMsgChannelSend::type
    @see recv */
enum msg_type {
  msg_WELCOME  = 0x0000,  /**< welcome message */
  msg_INVITE   = 0x0001,  /**< outgoing invitation */
  msg_JOIN     = 0x0002,  /**< someone joined */
  msg_PART     = 0x0003,  /**< someone left */
  msg_MESSAGE  = 0x0004,  /**< conference message */
};


/** the conferencing service */
struct mwServiceConference {
  struct mwService service;

  /** call-back handler for this service */
  struct mwConferenceHandler *handler;

  /** collection of conferences in this service */
  GList *confs;
};


/** a conference and its members */
struct mwConference {
  enum mwConferenceState state;   /**< state of the conference */
  struct mwServiceConference *service;  /**< owning service */
  struct mwChannel *channel;      /**< conference's channel */

  char *name;   /**< server identifier for the conference */
  char *title;  /**< topic for the conference */

  struct mwLoginInfo owner;  /**< person who created this conference */
  GHashTable *members;       /**< mapping guint16:mwLoginInfo */
  struct mw_datum client_data;
};


#define MEMBER_FIND(conf, id) \
  g_hash_table_lookup(conf->members, GUINT_TO_POINTER((guint) id))


#define MEMBER_ADD(conf, id, member) \
  g_hash_table_insert(conf->members, GUINT_TO_POINTER((guint) id), member)


#define MEMBER_REM(conf, id) \
  g_hash_table_remove(conf->members, GUINT_TO_POINTER((guint) id));


/** clear and free a login info block */
static void login_free(struct mwLoginInfo *li) {
  mwLoginInfo_clear(li);
  g_free(li);
}


/** generates a random conference name built around a user name */
static char *conf_generate_name(const char *user) {
  guint a, b;
  char *ret;
  
  user = user? user: "";

  srand(clock() + rand());
  a = ((rand() & 0xff) << 8) | (rand() & 0xff);
  b = time(NULL);

  ret = g_strdup_printf("%s(%08x,%04x)", user, b, a);
  g_debug("generated random conference name: '%s'", ret);
  return ret;
}





static struct mwConference *conf_new(struct mwServiceConference *srvc) {

  struct mwConference *conf;
  struct mwSession *session;
  const char *user;

  conf = g_new0(struct mwConference, 1);
  conf->state = mwConference_NEW;
  conf->service = srvc;
  conf->members = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
					(GDestroyNotify) login_free);

  session = mwService_getSession(MW_SERVICE(srvc));
  user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);

  srvc->confs = g_list_prepend(srvc->confs, conf);

  return conf;
}


/** clean and free a conference structure */
static void conf_free(struct mwConference *conf) {
  struct mwServiceConference *srvc;

  /* this shouldn't ever happen, but just to be sure */
  g_return_if_fail(conf != NULL);
  
  srvc = conf->service;

  if(conf->members)
    g_hash_table_destroy(conf->members);

  g_list_remove_all(srvc->confs, conf);

  mw_datum_clear(&conf->client_data);
  
  g_free(conf->name);
  g_free(conf->title);
  g_free(conf);
}


static struct mwConference *conf_find(struct mwServiceConference *srvc,
				      struct mwChannel *chan) {
  GList *l;

  g_return_val_if_fail(srvc != NULL, NULL);
  g_return_val_if_fail(chan != NULL, NULL);
  
  for(l = srvc->confs; l; l = l->next) {
    struct mwConference *conf = l->data;
    if(conf->channel == chan) return conf;
  }

  return NULL;
}


static const char *conf_state_str(enum mwConferenceState state) {
  switch(state) {
  case mwConference_NEW:      return "new";
  case mwConference_PENDING:  return "pending";
  case mwConference_INVITED:  return "invited";
  case mwConference_OPEN:     return "open";
  case mwConference_CLOSING:  return "closing";
  case mwConference_ERROR:    return "error";

  case mwConference_UNKNOWN:  /* fall through */
  default:                    return "UNKNOWN";
  }
}


static void conf_state(struct mwConference *conf,
		       enum mwConferenceState state) {
  g_return_if_fail(conf != NULL);

  if(conf->state == state) return;

  conf->state = state;
  g_message("conference %s state: %s",
	    NSTR(conf->name), conf_state_str(state));
}


static void recv_channelCreate(struct mwService *srvc,
			       struct mwChannel *chan,
			       struct mwMsgChannelCreate *msg) {

  /* - this is how we really receive invitations
     - create a conference and associate it with the channel
     - obtain the invite data from the msg addtl info
     - mark the conference as INVITED
     - trigger the got_invite event
  */

  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
  struct mwConference *conf;

  struct mwGetBuffer *b;

  char *invite = NULL;
  guint tmp;

  conf = conf_new(srvc_conf);
  conf->channel = chan;

  b = mwGetBuffer_wrap(&msg->addtl);

  guint32_get(b, &tmp);
  mwString_get(b, &conf->name);
  mwString_get(b, &conf->title);
  guint32_get(b, &tmp);
  mwLoginInfo_get(b, &conf->owner);
  guint32_get(b, &tmp);
  mwString_get(b, &invite);

  if(mwGetBuffer_error(b)) {
    g_warning("failure parsing addtl for conference invite");
    mwConference_destroy(conf, ERR_FAILURE, NULL);

  } else {
    struct mwConferenceHandler *h = srvc_conf->handler;
    conf_state(conf, mwConference_INVITED);
    if(h->on_invited)
      h->on_invited(conf, &conf->owner, invite);
  }

  mwGetBuffer_free(b);
  g_free(invite);
}


static void recv_channelAccept(struct mwService *srvc,
			       struct mwChannel *chan,
			       struct mwMsgChannelAccept *msg) {

  ;
}


static void recv_channelDestroy(struct mwService *srvc,
				struct mwChannel *chan,
				struct mwMsgChannelDestroy *msg) {

  /* - find conference from channel
     - trigger got_closed
     - remove conference, dealloc
  */

  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
  struct mwConference *conf = conf_find(srvc_conf, chan);
  struct mwConferenceHandler *h = srvc_conf->handler;

  /* if there's no such conference, then I guess there's nothing to worry
     about. Except of course for the fact that we should never receive a
     channel destroy for a conference that doesn't exist. */
  if(! conf) return;

  conf->channel = NULL;

  conf_state(conf, msg->reason? mwConference_ERROR: mwConference_CLOSING);

  if(h->conf_closed)
    h->conf_closed(conf, msg->reason);

  mwConference_destroy(conf, ERR_SUCCESS, NULL);
}


static void WELCOME_recv(struct mwServiceConference *srvc,
			 struct mwConference *conf,
			 struct mwGetBuffer *b) {

  struct mwConferenceHandler *h;
  guint16 tmp16;
  guint32 tmp32;
  guint32 count;
  GList *l = NULL;

  /* re-read name and title */
  g_free(conf->name);
  g_free(conf->title);
  conf->name = NULL;
  conf->title = NULL;
  mwString_get(b, &conf->name);
  mwString_get(b, &conf->title);

  /* some numbers we don't care about, then a count of members */
  guint16_get(b, &tmp16);
  guint32_get(b, &tmp32);
  guint32_get(b, &count);

  if(mwGetBuffer_error(b)) {
    g_warning("error parsing welcome message for conference");
    mwConference_destroy(conf, ERR_FAILURE, NULL);
    return;
  }
  
  while(count--) {
    guint16 member_id;
    struct mwLoginInfo *member = g_new0(struct mwLoginInfo, 1);

    guint16_get(b, &member_id);
    mwLoginInfo_get(b, member);

    if(mwGetBuffer_error(b)) {
      login_free(member);
      break;
    }

    MEMBER_ADD(conf, member_id, member);
    l = g_list_append(l, member);
  }

  conf_state(conf, mwConference_OPEN);

  h = srvc->handler;
  if(h->conf_opened)
    h->conf_opened(conf, l);

  /* get rid of the GList, but not its contents */
  g_list_free(l);
}


static void JOIN_recv(struct mwServiceConference *srvc,
		      struct mwConference *conf,
		      struct mwGetBuffer *b) {

  struct mwConferenceHandler *h;
  guint16 m_id;
  struct mwLoginInfo *m;
  
  /* for some inane reason, conferences we create will send a join
     message for ourselves before the welcome message. Since the
     welcome message will list our ID among those in the channel,
     we're going to just pretend that these join messages don't
     exist */
  if(conf->state == mwConference_PENDING)
    return;

  m = g_new0(struct mwLoginInfo, 1);

  guint16_get(b, &m_id);
  mwLoginInfo_get(b, m);

  if(mwGetBuffer_error(b)) {
    g_warning("failed parsing JOIN message in conference");
    login_free(m);
    return;
  }

  MEMBER_ADD(conf, m_id, m);

  h = srvc->handler;
  if(h->on_peer_joined)
    h->on_peer_joined(conf, m);
}


static void PART_recv(struct mwServiceConference *srvc,
		      struct mwConference *conf,
		      struct mwGetBuffer *b) {

  /* - parse who left
     - look up their membership
     - remove them from the members list
     - trigger the event
  */

  struct mwConferenceHandler *h;
  guint16 id = 0;
  struct mwLoginInfo *m;

  guint16_get(b, &id);

  if(mwGetBuffer_error(b)) return;

  m = MEMBER_FIND(conf, id);
  if(! m) return;

  h = srvc->handler;
  if(h->on_peer_parted)
    h->on_peer_parted(conf, m);

  MEMBER_REM(conf, id);
}


static void text_recv(struct mwServiceConference *srvc,
		      struct mwConference *conf,
		      struct mwLoginInfo *m,
		      struct mwGetBuffer *b) {

  /* this function acts a lot like receiving an IM Text message. The text
     message contains only a string */

  char *text = NULL;
  struct mwConferenceHandler *h;
  
  mwString_get(b, &text);

  if(mwGetBuffer_error(b)) {
    g_warning("failed to parse text message in conference");
    g_free(text);
    return;
  }

  h = srvc->handler;
  if(text && h->on_text) {
    h->on_text(conf, m, text);
  }

  g_free(text);
}


static void data_recv(struct mwServiceConference *srvc,
		      struct mwConference *conf,
		      struct mwLoginInfo *m,
		      struct mwGetBuffer *b) {

  /* this function acts a lot like receiving an IM Data message. The
     data message has a type, a subtype, and an opaque. We only
     support typing notification though. */

  /** @todo it's possible that some clients send text in a data
      message, as we've seen rarely in the IM service. Have to add
      support for that here */

  guint32 type, subtype;
  struct mwConferenceHandler *h;

  guint32_get(b, &type);
  guint32_get(b, &subtype);

  if(mwGetBuffer_error(b)) return;

  /* don't know how to deal with any others yet */
  if(type != 0x01) {
    g_message("unknown data message type (0x%08x, 0x%08x)", type, subtype);
    return;
  }

  h = srvc->handler;
  if(h->on_typing) {
    h->on_typing(conf, m, !subtype);
  }
}


static void MESSAGE_recv(struct mwServiceConference *srvc,
			 struct mwConference *conf,
			 struct mwGetBuffer *b) {

  /* - look up who send the message by their id
     - trigger the event
  */

  guint16 id;
  guint32 type;
  struct mwLoginInfo *m;

  /* an empty buffer isn't an error, just ignored */
  if(! mwGetBuffer_remaining(b)) return;

  guint16_get(b, &id);
  guint32_get(b, &type); /* reuse type variable */
  guint32_get(b, &type);

  if(mwGetBuffer_error(b)) return;

  m = MEMBER_FIND(conf, id);
  if(! m) {
    g_warning("received message type 0x%04x from"
	      " unknown conference member %u", type, id);
    return;
  }
  
  switch(type) {
  case 0x01:  /* type is text */
    text_recv(srvc, conf, m, b);
    break;

  case 0x02:  /* type is data */
    data_recv(srvc, conf, m, b);
    break;

  default:
    g_warning("unknown message type 0x%4x received in conference", type);
  }
}


static void recv(struct mwService *srvc, struct mwChannel *chan,
		 guint16 type, struct mwOpaque *data) {

  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
  struct mwConference *conf = conf_find(srvc_conf, chan);
  struct mwGetBuffer *b;

  g_return_if_fail(conf != NULL);

  b = mwGetBuffer_wrap(data);

  switch(type) {
  case msg_WELCOME:
    WELCOME_recv(srvc_conf, conf, b);
    break;

  case msg_JOIN:
    JOIN_recv(srvc_conf, conf, b);
    break;

  case msg_PART:
    PART_recv(srvc_conf, conf, b);
    break;

  case msg_MESSAGE:
    MESSAGE_recv(srvc_conf, conf, b);
    break;

  default:
    ; /* hrm. should log this. TODO */
  }
}


static void clear(struct mwServiceConference *srvc) {
  struct mwConferenceHandler *h;

  while(srvc->confs)
    conf_free(srvc->confs->data);

  h = srvc->handler;
  if(h && h->clear)
    h->clear(srvc);
  srvc->handler = NULL;
}


static const char *name(struct mwService *srvc) {
  return "Basic Conferencing";
}


static const char *desc(struct mwService *srvc) {
  return "Multi-user plain-text conferencing";
}


static void start(struct mwService *srvc) {
  mwService_started(srvc);
}


static void stop(struct mwServiceConference *srvc) {
  while(srvc->confs)
    mwConference_destroy(srvc->confs->data, ERR_SUCCESS, NULL);

  mwService_stopped(MW_SERVICE(srvc));
}


struct mwServiceConference *
mwServiceConference_new(struct mwSession *session,
			struct mwConferenceHandler *handler) {

  struct mwServiceConference *srvc_conf;
  struct mwService *srvc;

  g_return_val_if_fail(session != NULL, NULL);
  g_return_val_if_fail(handler != NULL, NULL);

  srvc_conf = g_new0(struct mwServiceConference, 1);
  srvc = &srvc_conf->service;

  mwService_init(srvc, session, mwService_CONFERENCE);
  srvc->start = start;
  srvc->stop = (mwService_funcStop) stop;
  srvc->recv_create = recv_channelCreate;
  srvc->recv_accept = recv_channelAccept;
  srvc->recv_destroy = recv_channelDestroy;
  srvc->recv = recv;
  srvc->clear = (mwService_funcClear) clear;
  srvc->get_name = name;
  srvc->get_desc = desc;

  srvc_conf->handler = handler;

  return srvc_conf;
}


struct mwConference *mwConference_new(struct mwServiceConference *srvc,
				      const char *title) {
  struct mwConference *conf;

  g_return_val_if_fail(srvc != NULL, NULL);

  conf = conf_new(srvc);
  conf->title = g_strdup(title);

  return conf;
}


struct mwServiceConference *
mwConference_getService(struct mwConference *conf) {
  g_return_val_if_fail(conf != NULL, NULL);
  return conf->service;
}


const char *mwConference_getName(struct mwConference *conf) {
  g_return_val_if_fail(conf != NULL, NULL);
  return conf->name;
}


const char *mwConference_getTitle(struct mwConference *conf) {
  g_return_val_if_fail(conf != NULL, NULL);
  return conf->title;
}


GList *mwConference_getMembers(struct mwConference *conf) {
  g_return_val_if_fail(conf != NULL, NULL);
  g_return_val_if_fail(conf->members != NULL, NULL);

  return map_collect_values(conf->members);
}


int mwConference_open(struct mwConference *conf) {
  struct mwSession *session;
  struct mwChannel *chan;
  struct mwPutBuffer *b;
  int ret;
  
  g_return_val_if_fail(conf != NULL, -1);
  g_return_val_if_fail(conf->service != NULL, -1);
  g_return_val_if_fail(conf->state == mwConference_NEW, -1);
  g_return_val_if_fail(conf->channel == NULL, -1);

  session = mwService_getSession(MW_SERVICE(conf->service));
  g_return_val_if_fail(session != NULL, -1);

  if(! conf->name) {
    char *user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);
    conf->name = conf_generate_name(user? user: "meanwhile");
  }

  chan = mwChannel_newOutgoing(mwSession_getChannels(session));
  mwChannel_setService(chan, MW_SERVICE(conf->service));
  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
  mwChannel_setProtoVer(chan, PROTOCOL_VER);
  
#if 0
  /* offer all known ciphers */
  mwChannel_populateSupportedCipherInstances(chan);
#endif

  b = mwPutBuffer_new();
  mwString_put(b, conf->name);
  mwString_put(b, conf->title);
  guint32_put(b, 0x00);
  mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b);

  ret = mwChannel_create(chan);
  if(ret) {
    conf_state(conf, mwConference_ERROR);
  } else {
    conf_state(conf, mwConference_PENDING);
    conf->channel = chan;
  }

  return ret;
}


int mwConference_destroy(struct mwConference *conf,
			 guint32 reason, const char *text) {

  struct mwServiceConference *srvc;
  struct mwOpaque info = { 0, 0 };
  int ret = 0;

  g_return_val_if_fail(conf != NULL, -1);

  srvc = conf->service;
  g_return_val_if_fail(srvc != NULL, -1);

  /* remove conference from the service */
  srvc->confs = g_list_remove_all(srvc->confs, conf);

  /* close the channel if applicable */
  if(conf->channel) {
    if(text && *text) {
      info.len = strlen(text);
      info.data = (guchar *) text;
    }

    ret = mwChannel_destroy(conf->channel, reason, &info);
  }
  
  /* free the conference */
  conf_free(conf);

  return ret;
}


int mwConference_accept(struct mwConference *conf) {
  /* - if conference is not INVITED, return -1
     - accept the conference channel
     - send an empty JOIN message
  */

  struct mwChannel *chan;
  int ret;

  g_return_val_if_fail(conf != NULL, -1);
  g_return_val_if_fail(conf->state == mwConference_INVITED, -1);

  chan = conf->channel;
  ret = mwChannel_accept(chan);

  if(! ret)
    ret = mwChannel_sendEncrypted(chan, msg_JOIN, NULL, FALSE);

  return ret;
}


int mwConference_invite(struct mwConference *conf,
			struct mwIdBlock *who,
			const char *text) {

  struct mwPutBuffer *b;
  struct mwOpaque o;
  int ret;

  g_return_val_if_fail(conf != NULL, -1);
  g_return_val_if_fail(conf->channel != NULL, -1);
  g_return_val_if_fail(who != NULL, -1);

  b = mwPutBuffer_new();

  mwIdBlock_put(b, who);
  guint16_put(b, 0x00);
  guint32_put(b, 0x00);
  mwString_put(b, text);
  mwString_put(b, who->user);

  mwPutBuffer_finalize(&o, b);
  ret = mwChannel_sendEncrypted(conf->channel, msg_INVITE, &o, FALSE);
  mwOpaque_clear(&o);

  return ret;
}


int mwConference_sendText(struct mwConference *conf, const char *text) {
  struct mwPutBuffer *b;
  struct mwOpaque o;
  int ret;

  g_return_val_if_fail(conf != NULL, -1);
  g_return_val_if_fail(conf->channel != NULL, -1);

  b = mwPutBuffer_new();

  guint32_put(b, 0x01);
  mwString_put(b, text);

  mwPutBuffer_finalize(&o, b);
  ret = mwChannel_sendEncrypted(conf->channel, msg_MESSAGE, &o, FALSE);
  mwOpaque_clear(&o);

  return ret;
}


int mwConference_sendTyping(struct mwConference *conf, gboolean typing) {
  struct mwPutBuffer *b;
  struct mwOpaque o;
  int ret;

  g_return_val_if_fail(conf != NULL, -1);
  g_return_val_if_fail(conf->channel != NULL, -1);
  g_return_val_if_fail(conf->state == mwConference_OPEN, -1);

  b = mwPutBuffer_new();

  guint32_put(b, 0x02);
  guint32_put(b, 0x01);
  guint32_put(b, !typing);
  mwOpaque_put(b, NULL);

  mwPutBuffer_finalize(&o, b);
  ret = mwChannel_sendEncrypted(conf->channel, msg_MESSAGE, &o, FALSE);
  mwOpaque_clear(&o);

  return ret;
}


void mwConference_setClientData(struct mwConference *conference,
			     gpointer data, GDestroyNotify clear) {

  g_return_if_fail(conference != NULL);
  mw_datum_set(&conference->client_data, data, clear);
}


gpointer mwConference_getClientData(struct mwConference *conference) {
  g_return_val_if_fail(conference != NULL, NULL);
  return mw_datum_get(&conference->client_data);
}


void mwConference_removeClientData(struct mwConference *conference) {
  g_return_if_fail(conference != NULL);
  mw_datum_clear(&conference->client_data);
}


struct mwConferenceHandler *
mwServiceConference_getHandler(struct mwServiceConference *srvc) {
  g_return_val_if_fail(srvc != NULL, NULL);
  return srvc->handler;
}


GList *mwServiceConference_getConferences(struct mwServiceConference *srvc) {
  g_return_val_if_fail(srvc != NULL, NULL);
  return g_list_copy(srvc->confs);
}