message.h: Add wrapper around protobufs ugly func names
[cmumble:cmumble.git] / src / cmumble.c
1 #include "../config.h"
2
3 #include <string.h>
4 #include <stdlib.h>
5
6 #include "varint.h"
7 #include "cmumble.h"
8 #include "io.h"
9 #include "connection.h"
10 #include "util.h"
11
12 static void
13 recv_udp_tunnel(MumbleProto__UDPTunnel *tunnel, struct cmumble *cm)
14 {
15         int64_t session, sequence;
16         uint32_t pos = 1, read = 0;
17         uint8_t frame_len, terminator;
18         struct cmumble_user *user = NULL;
19         uint8_t *data = tunnel->packet.data;
20         size_t len = tunnel->packet.len;
21
22         session  = decode_varint(&data[pos], &read, len-pos);
23         pos += read;
24         sequence = decode_varint(&data[pos], &read, len-pos);
25         pos += read;
26
27         user = find_user(cm, session);
28         if (user == NULL) {
29                 g_printerr("received audio packet from unknown user, "
30                            "dropping.\n");
31                 return;
32         }
33
34         do {
35                 frame_len  = data[pos] & 0x7F;
36                 terminator = data[pos] & 0x80;
37                 pos += 1;
38
39                 if (frame_len == 0 || frame_len > len-pos)
40                         break;
41
42                 cmumble_audio_push(cm, user, &data[pos], frame_len);
43
44                 pos += frame_len;
45                 sequence++;
46         } while (terminator);
47 }
48
49 static void
50 recv_version(MumbleProto__Version *version, struct cmumble *cm)
51 {
52         if (cm->verbose) {
53                 g_print("version: 0x%x\n", version->version);
54                 g_print("release: %s\n", version->release);
55         }
56 }
57
58 static void
59 recv_channel_state(MumbleProto__ChannelState *state,
60                    struct cmumble *cm)
61 {
62         struct cmumble_channel *channel;
63
64         channel = find_channel(cm, state->channel_id);
65         if (channel == NULL) {
66                 channel = g_slice_new0(struct cmumble_channel);
67                 if (channel == NULL) {
68                         g_printerr("Out of memory.\n");
69                         exit(1);
70                 }
71                 cm->channels = g_list_prepend(cm->channels, channel);
72
73                 if (channel->name)
74                         g_free(channel->name);
75                 if (channel->description)
76                         g_free(channel->description);
77         }
78
79         channel->id = state->channel_id;
80         if (state->name)
81                 channel->name = g_strdup(state->name);
82         channel->parent = state->parent;
83         if (state->description)
84                 channel->description = g_strdup(state->description);
85         
86         channel->temporary = state->temporary;
87         channel->position = state->position;
88 }
89
90 static void
91 recv_server_sync(MumbleProto__ServerSync *sync, struct cmumble *cm)
92 {
93         cm->session = sync->session;
94         cm->user = find_user(cm, cm->session);
95
96         if (sync->welcome_text)
97                 g_print("Welcome Message: %s\n", sync->welcome_text);
98         if (cm->verbose)
99                 g_print("got session: %d\n", cm->session);
100 }
101
102 static void
103 recv_crypt_setup(MumbleProto__CryptSetup *crypt, struct cmumble *cm)
104 {
105 #if 0
106         int i;
107
108         if (crypt->has_key) {
109                 g_print("key: 0x");
110                 for (i = 0; i < crypt->key.len; ++i)
111                         g_print("%x", crypt->key.data[i]);
112                 g_print("\n");
113         }
114         if (crypt->has_client_nonce) {
115                 g_print("client nonce: 0x");
116                 for (i = 0; i < crypt->client_nonce.len; ++i)
117                         g_print("%x", crypt->client_nonce.data[i]);
118                 g_print("\n");
119         }
120         if (crypt->has_server_nonce) {
121                 g_print("server nonce: 0x");
122                 for (i = 0; i < crypt->server_nonce.len; ++i)
123                         g_print("%x", crypt->server_nonce.data[i]);
124                 g_print("\n");
125         }
126 #endif
127 }
128
129 static void
130 recv_codec_version(MumbleProto__CodecVersion *codec,
131                    struct cmumble *cm)
132 {
133         if (cm->verbose)
134                 g_print("Codec Version: alpha: %d, beta: %d, pefer_alpha: %d\n",
135                         codec->alpha, codec->beta, codec->prefer_alpha);
136 }
137
138 static void
139 recv_user_remove(MumbleProto__UserRemove *remove, struct cmumble *cm)
140 {
141         struct cmumble_user *user = NULL;
142
143         user = find_user(cm, remove->session);
144         if (user) {
145                 cm->users = g_list_remove(cm->users, user);
146                 g_free(user->name);
147                 /* FIXME: destroy playback pipeline */
148                 g_slice_free(struct cmumble_user, user);
149         }
150 }
151
152 static void
153 recv_user_state(MumbleProto__UserState *state, struct cmumble *cm)
154 {
155         struct cmumble_user *user = NULL;
156
157         user = find_user(cm, state->session);
158         if (user) {
159                 /* update */
160
161                 if (state->has_channel_id)
162                         user->channel = find_channel(cm, state->channel_id);
163
164                 return;
165         }
166
167         user = g_slice_new0(struct cmumble_user);
168         if (user == NULL) {
169                 g_printerr("Out of memory.\n");
170                 exit(1);
171         }
172
173         user->session = state->session;
174         user->name = g_strdup(state->name);
175         user->id = state->user_id;
176         user->channel = find_channel(cm, state->channel_id);
177
178         if (cm->session == user->session)
179                 cm->user = user;
180
181         cmumble_audio_create_playback_pipeline(cm, user);
182         if (cm->verbose)
183                 g_print("receive user: %s\n", user->name);
184         cm->users = g_list_prepend(cm->users, user);
185 }
186
187 static void
188 recv_text_message(MumbleProto__TextMessage *text, struct cmumble *cm)
189 {
190         struct cmumble_user *user;
191
192         user = find_user(cm, text->actor);
193         if (user != NULL)
194                 g_print("%s> %s\n", user->name, text->message);
195 }
196
197 static void
198 recv_reject(MumbleProto__Reject *reject, struct cmumble *cm)
199 {
200         switch (reject->type) {
201         case MUMBLE_REJECT_TYPE(None):
202         case MUMBLE_REJECT_TYPE(WrongVersion):
203         case MUMBLE_REJECT_TYPE(InvalidUsername):
204         case MUMBLE_REJECT_TYPE(WrongUserPW):
205         case MUMBLE_REJECT_TYPE(WrongServerPW):
206         case MUMBLE_REJECT_TYPE(UsernameInUse):
207         case MUMBLE_REJECT_TYPE(ServerFull):
208         case MUMBLE_REJECT_TYPE(NoCertificate):
209                 g_printerr("Connection rejected: %s\n", reject->reason);
210                 break;
211         default:
212                 break;
213         }
214         g_main_loop_quit(cm->loop);
215 }
216
217 static const struct {
218 #define MUMBLE_MSG(a, b) void (* a)(MumbleProto__##a *, struct cmumble *);
219         MUMBLE_MSGS
220 #undef MUMBLE_MSG
221 } callbacks = {
222         .Version                = recv_version,
223         .UDPTunnel              = recv_udp_tunnel,
224         .Authenticate           = NULL,
225         .Ping                   = NULL,
226         .Reject                 = recv_reject,
227         .ServerSync             = recv_server_sync,
228         .ChannelRemove          = NULL,
229         .ChannelState           = recv_channel_state,
230         .UserRemove             = recv_user_remove,
231         .UserState              = recv_user_state,
232         .BanList                = NULL,
233         .TextMessage            = recv_text_message,
234         .PermissionDenied       = NULL,
235         .ACL                    = NULL,
236         .QueryUsers             = NULL,
237         .CryptSetup             = recv_crypt_setup,
238         .ContextActionModify    = NULL,
239         .ContextAction          = NULL,
240         .UserList               = NULL,
241         .VoiceTarget            = NULL,
242         .PermissionQuery        = NULL,
243         .CodecVersion           = recv_codec_version,
244         .UserStats              = NULL,
245         .RequestBlob            = NULL,
246         .ServerConfig           = NULL,
247         .SuggestConfig          = NULL,
248 };
249
250 static gboolean
251 do_ping(struct cmumble *cm)
252 {
253         MumbleProto__Ping ping;
254         GTimeVal tv;
255
256         g_get_current_time(&tv);
257         mumble_proto__ping__init(&ping);
258         ping.timestamp = tv.tv_sec;
259         ping.resync = 1;
260         cmumble_send_msg(cm, &ping.base);
261
262         return TRUE;
263 }
264
265 void
266 cmumble_protocol_init(struct cmumble *cm)
267 {
268         MumbleProto__Version version;
269         MumbleProto__Authenticate authenticate;
270         GSource *source;
271
272         mumble_proto__version__init(&version);
273         version.version = 0x010203;
274         version.release = PACKAGE_STRING;
275         version.os = "Gentoo/Linux";
276         cmumble_send_msg(cm, &version.base);
277
278         mumble_proto__authenticate__init(&authenticate);
279         authenticate.username = cm->user_name;
280         authenticate.password = "";
281         authenticate.n_celt_versions = 1;
282         authenticate.celt_versions = (int32_t[]) { 0x8000000b };
283         cmumble_send_msg(cm, &authenticate.base);
284
285         source = g_timeout_source_new_seconds(5);
286         g_source_set_callback(source, (GSourceFunc) do_ping, cm, NULL);
287         g_source_attach(source, NULL);
288         g_source_unref(source);
289 }
290
291 gchar *user = "unkown";
292 gchar *host = "localhost";
293 gint port = 64738;
294 gboolean verbose = FALSE;
295
296 static GOptionEntry entries[] = {
297         {
298                 "user", 'u', 0, G_OPTION_ARG_STRING, &user,
299                 "user name", "N"
300         },
301         {
302                 "host", 'h', 0, G_OPTION_ARG_STRING, &host,
303                 "Host name or ip address of the mumble server", "N"
304         },
305         {
306                 "port", 'p', 0, G_OPTION_ARG_INT, &port,
307                 "port of the mumble server", "N"
308         },
309         {
310                 "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
311                 "Turn on verbosity", NULL
312         },
313         { NULL }
314 };
315
316 int main(int argc, char **argv)
317 {
318         struct cmumble cm;
319         GError *error = NULL;
320         GOptionContext *context;
321
322         context = g_option_context_new("command line mumble client");
323         g_option_context_add_main_entries(context, entries, "cmumble");
324         g_option_context_add_group(context, gst_init_get_option_group());
325
326         if (!g_option_context_parse(context, &argc, &argv, &error)) {
327                 g_printerr("option parsing failed: %s\n", error->message);
328                 exit(1);
329         }
330
331         memset(&cm, 0, sizeof(cm));
332
333         cm.user_name = user;
334         cm.users = NULL;
335         cm.verbose = verbose;
336
337         g_type_init();
338         cm.loop = g_main_loop_new(NULL, FALSE);
339         cm.callbacks = (const callback_t *) &callbacks;
340
341         cmumble_commands_init(&cm);
342         if (cmumble_connection_init(&cm, host, port) < 0)
343                 return 1;
344
345         gst_init(&argc, &argv);
346
347         if (cmumble_audio_init(&cm) < 0)
348                 return 1;
349         cmumble_io_init(&cm);
350
351         g_main_loop_run(cm.loop);
352
353         g_main_loop_unref(cm.loop);
354
355         cmumble_io_fini(&cm);
356         cmumble_audio_init(&cm);
357         cmumble_connection_fini(&cm);
358
359         return 0;
360 }