1 /**
2  * @file session.c
3  *
4  * purple
5  *
6  * Purple is the legal property of its developers, whose names are too numerous
7  * to list here.  Please refer to the COPYRIGHT file distributed with this
8  * source distribution.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
23  */
24 
25 #include "internal.h"
26 
27 #include "content.h"
28 #include "debug.h"
29 #include "session.h"
30 #include "jingle.h"
31 
32 #include <string.h>
33 
34 struct _JingleSessionPrivate
35 {
36 	gchar *sid;
37 	JabberStream *js;
38 	gchar *remote_jid;
39 	gchar *local_jid;
40 	gboolean is_initiator;
41 	gboolean state;
42 	GList *contents;
43 	GList *pending_contents;
44 };
45 
46 #define JINGLE_SESSION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_SESSION, JingleSessionPrivate))
47 
48 static void jingle_session_class_init (JingleSessionClass *klass);
49 static void jingle_session_init (JingleSession *session);
50 static void jingle_session_finalize (GObject *object);
51 static void jingle_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
52 static void jingle_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
53 
54 static GObjectClass *parent_class = NULL;
55 
56 enum {
57 	PROP_0,
58 	PROP_SID,
59 	PROP_JS,
60 	PROP_REMOTE_JID,
61 	PROP_LOCAL_JID,
62 	PROP_IS_INITIATOR,
63 	PROP_STATE,
64 	PROP_CONTENTS,
65 	PROP_PENDING_CONTENTS,
66 };
67 
68 GType
jingle_session_get_type()69 jingle_session_get_type()
70 {
71 	static GType type = 0;
72 
73 	if (type == 0) {
74 		static const GTypeInfo info = {
75 			sizeof(JingleSessionClass),
76 			NULL,
77 			NULL,
78 			(GClassInitFunc) jingle_session_class_init,
79 			NULL,
80 			NULL,
81 			sizeof(JingleSession),
82 			0,
83 			(GInstanceInitFunc) jingle_session_init,
84 			NULL
85 		};
86 		type = g_type_register_static(G_TYPE_OBJECT, "JingleSession", &info, 0);
87 	}
88 	return type;
89 }
90 
91 static void
jingle_session_class_init(JingleSessionClass * klass)92 jingle_session_class_init (JingleSessionClass *klass)
93 {
94 	GObjectClass *gobject_class = (GObjectClass*)klass;
95 	parent_class = g_type_class_peek_parent(klass);
96 
97 	gobject_class->finalize = jingle_session_finalize;
98 	gobject_class->set_property = jingle_session_set_property;
99 	gobject_class->get_property = jingle_session_get_property;
100 
101 	g_object_class_install_property(gobject_class, PROP_SID,
102 			g_param_spec_string("sid",
103 			"Session ID",
104 			"The unique session ID of the Jingle Session.",
105 			NULL,
106 			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
107 
108 	g_object_class_install_property(gobject_class, PROP_JS,
109 			g_param_spec_pointer("js",
110 			"JabberStream",
111 			"The Jabber stream associated with this session.",
112 			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
113 
114 	g_object_class_install_property(gobject_class, PROP_REMOTE_JID,
115 			g_param_spec_string("remote-jid",
116 			"Remote JID",
117 			"The JID of the remote participant.",
118 			NULL,
119 			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
120 
121 	g_object_class_install_property(gobject_class, PROP_LOCAL_JID,
122 			g_param_spec_string("local-jid",
123 			"Local JID",
124 			"The JID of the local participant.",
125 			NULL,
126 			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
127 
128 	g_object_class_install_property(gobject_class, PROP_IS_INITIATOR,
129 			g_param_spec_boolean("is-initiator",
130 			"Is Initiator",
131 			"Whether or not the local JID is the initiator of the session.",
132 			FALSE,
133 			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
134 
135 	g_object_class_install_property(gobject_class, PROP_STATE,
136 			g_param_spec_boolean("state",
137 			"State",
138 			"The state of the session (PENDING=FALSE, ACTIVE=TRUE).",
139 			FALSE,
140 			G_PARAM_READABLE));
141 
142 	g_object_class_install_property(gobject_class, PROP_CONTENTS,
143 			g_param_spec_pointer("contents",
144 			"Contents",
145 			"The active contents contained within this session",
146 			G_PARAM_READABLE));
147 
148 	g_object_class_install_property(gobject_class, PROP_PENDING_CONTENTS,
149 			g_param_spec_pointer("pending-contents",
150 			"Pending contents",
151 			"The pending contents contained within this session",
152 			G_PARAM_READABLE));
153 
154 	g_type_class_add_private(klass, sizeof(JingleSessionPrivate));
155 }
156 
157 static void
jingle_session_init(JingleSession * session)158 jingle_session_init (JingleSession *session)
159 {
160 	session->priv = JINGLE_SESSION_GET_PRIVATE(session);
161 	memset(session->priv, 0, sizeof(*session->priv));
162 }
163 
164 static void
jingle_session_finalize(GObject * session)165 jingle_session_finalize (GObject *session)
166 {
167 	JingleSessionPrivate *priv = JINGLE_SESSION_GET_PRIVATE(session);
168 	purple_debug_info("jingle","jingle_session_finalize\n");
169 
170 	g_hash_table_remove(priv->js->sessions, priv->sid);
171 
172 	g_free(priv->sid);
173 	g_free(priv->remote_jid);
174 	g_free(priv->local_jid);
175 
176 	for (; priv->contents; priv->contents =
177 			g_list_delete_link(priv->contents, priv->contents)) {
178 		g_object_unref(priv->contents->data);
179 	}
180 	for (; priv->pending_contents; priv->pending_contents =
181 			g_list_delete_link(priv->pending_contents, priv->pending_contents)) {
182 		g_object_unref(priv->pending_contents->data);
183 	}
184 
185 	parent_class->finalize(session);
186 }
187 
188 static void
jingle_session_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)189 jingle_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
190 {
191 	JingleSession *session;
192 
193 	g_return_if_fail(object != NULL);
194 	g_return_if_fail(JINGLE_IS_SESSION(object));
195 
196 	session = JINGLE_SESSION(object);
197 
198 	switch (prop_id) {
199 		case PROP_SID:
200 			g_free(session->priv->sid);
201 			session->priv->sid = g_value_dup_string(value);
202 			break;
203 		case PROP_JS:
204 			session->priv->js = g_value_get_pointer(value);
205 			break;
206 		case PROP_REMOTE_JID:
207 			g_free(session->priv->remote_jid);
208 			session->priv->remote_jid = g_value_dup_string(value);
209 			break;
210 		case PROP_LOCAL_JID:
211 			g_free(session->priv->local_jid);
212 			session->priv->local_jid = g_value_dup_string(value);
213 			break;
214 		case PROP_IS_INITIATOR:
215 			session->priv->is_initiator = g_value_get_boolean(value);
216 			break;
217 		case PROP_STATE:
218 			session->priv->state = g_value_get_boolean(value);
219 			break;
220 		case PROP_CONTENTS:
221 			session->priv->contents = g_value_get_pointer(value);
222 			break;
223 		case PROP_PENDING_CONTENTS:
224 			session->priv->pending_contents = g_value_get_pointer(value);
225 			break;
226 		default:
227 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
228 			break;
229 	}
230 }
231 
232 static void
jingle_session_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)233 jingle_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
234 {
235 	JingleSession *session;
236 
237 	g_return_if_fail(object != NULL);
238 	g_return_if_fail(JINGLE_IS_SESSION(object));
239 
240 	session = JINGLE_SESSION(object);
241 
242 	switch (prop_id) {
243 		case PROP_SID:
244 			g_value_set_string(value, session->priv->sid);
245 			break;
246 		case PROP_JS:
247 			g_value_set_pointer(value, session->priv->js);
248 			break;
249 		case PROP_REMOTE_JID:
250 			g_value_set_string(value, session->priv->remote_jid);
251 			break;
252 		case PROP_LOCAL_JID:
253 			g_value_set_string(value, session->priv->local_jid);
254 			break;
255 		case PROP_IS_INITIATOR:
256 			g_value_set_boolean(value, session->priv->is_initiator);
257 			break;
258 		case PROP_STATE:
259 			g_value_set_boolean(value, session->priv->state);
260 			break;
261 		case PROP_CONTENTS:
262 			g_value_set_pointer(value, session->priv->contents);
263 			break;
264 		case PROP_PENDING_CONTENTS:
265 			g_value_set_pointer(value, session->priv->pending_contents);
266 			break;
267 		default:
268 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
269 			break;
270 	}
271 }
272 
273 
274 JingleSession *
jingle_session_create(JabberStream * js,const gchar * sid,const gchar * local_jid,const gchar * remote_jid,gboolean is_initiator)275 jingle_session_create(JabberStream *js, const gchar *sid,
276 			const gchar *local_jid, const gchar *remote_jid,
277 			gboolean is_initiator)
278 {
279 	JingleSession *session = g_object_new(jingle_session_get_type(),
280 			"js", js,
281 			"sid", sid,
282 			"local-jid", local_jid,
283 			"remote-jid", remote_jid,
284 			"is_initiator", is_initiator,
285 			NULL);
286 
287 	/* insert it into the hash table */
288 	if (!js->sessions) {
289 		purple_debug_info("jingle",
290 				"Creating hash table for sessions\n");
291 		js->sessions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
292 	}
293 	purple_debug_info("jingle",
294 			"inserting session with key: %s into table\n", sid);
295 	g_hash_table_insert(js->sessions, g_strdup(sid), session);
296 
297 	return session;
298 }
299 
300 JabberStream *
jingle_session_get_js(JingleSession * session)301 jingle_session_get_js(JingleSession *session)
302 {
303 	JabberStream *js;
304 	g_object_get(session, "js", &js, NULL);
305 	return js;
306 }
307 
308 gchar *
jingle_session_get_sid(JingleSession * session)309 jingle_session_get_sid(JingleSession *session)
310 {
311 	gchar *sid;
312 	g_object_get(session, "sid", &sid, NULL);
313 	return sid;
314 }
315 
316 gchar *
jingle_session_get_local_jid(JingleSession * session)317 jingle_session_get_local_jid(JingleSession *session)
318 {
319 	gchar *local_jid;
320 	g_object_get(session, "local-jid", &local_jid, NULL);
321 	return local_jid;
322 }
323 
324 gchar *
jingle_session_get_remote_jid(JingleSession * session)325 jingle_session_get_remote_jid(JingleSession *session)
326 {
327 	gchar *remote_jid;
328 	g_object_get(session, "remote-jid", &remote_jid, NULL);
329 	return remote_jid;
330 }
331 
332 gboolean
jingle_session_is_initiator(JingleSession * session)333 jingle_session_is_initiator(JingleSession *session)
334 {
335 	gboolean is_initiator;
336 	g_object_get(session, "is-initiator", &is_initiator, NULL);
337 	return is_initiator;
338 }
339 
340 gboolean
jingle_session_get_state(JingleSession * session)341 jingle_session_get_state(JingleSession *session)
342 {
343 	gboolean state;
344 	g_object_get(session, "state", &state, NULL);
345 	return state;
346 }
347 
348 GList *
jingle_session_get_contents(JingleSession * session)349 jingle_session_get_contents(JingleSession *session)
350 {
351 	GList *contents;
352 	g_object_get(session, "contents", &contents, NULL);
353 	return contents;
354 }
355 
356 GList *
jingle_session_get_pending_contents(JingleSession * session)357 jingle_session_get_pending_contents(JingleSession *session)
358 {
359 	GList *pending_contents;
360 	g_object_get(session, "pending-contents", &pending_contents, NULL);
361 	return pending_contents;
362 }
363 
364 JingleSession *
jingle_session_find_by_sid(JabberStream * js,const gchar * sid)365 jingle_session_find_by_sid(JabberStream *js, const gchar *sid)
366 {
367 	JingleSession *session = NULL;
368 
369 	if (js->sessions)
370 		session = g_hash_table_lookup(js->sessions, sid);
371 
372 	purple_debug_info("jingle", "find_by_id %s\n", sid);
373 	purple_debug_info("jingle", "lookup: %p\n", session);
374 
375 	return session;
376 }
377 
find_by_jid_ghr(gpointer key,gpointer value,gpointer user_data)378 static gboolean find_by_jid_ghr(gpointer key,
379 		gpointer value, gpointer user_data)
380 {
381 	JingleSession *session = (JingleSession *)value;
382 	const gchar *jid = user_data;
383 	gboolean use_bare = strchr(jid, '/') == NULL;
384 	gchar *remote_jid = jingle_session_get_remote_jid(session);
385 	gchar *cmp_jid = use_bare ? jabber_get_bare_jid(remote_jid)
386 				  : g_strdup(remote_jid);
387 	g_free(remote_jid);
388 	if (purple_strequal(jid, cmp_jid)) {
389 		g_free(cmp_jid);
390 		return TRUE;
391 	}
392 	g_free(cmp_jid);
393 
394 	return FALSE;
395 }
396 
397 JingleSession *
jingle_session_find_by_jid(JabberStream * js,const gchar * jid)398 jingle_session_find_by_jid(JabberStream *js, const gchar *jid)
399 {
400 	return js->sessions != NULL ?
401 			g_hash_table_find(js->sessions,
402 			find_by_jid_ghr, (gpointer)jid) : NULL;
403 }
404 
405 static xmlnode *
jingle_add_jingle_packet(JingleSession * session,JabberIq * iq,JingleActionType action)406 jingle_add_jingle_packet(JingleSession *session,
407 			 JabberIq *iq, JingleActionType action)
408 {
409 	xmlnode *jingle = iq ?
410 			xmlnode_new_child(iq->node, "jingle") :
411 			xmlnode_new("jingle");
412 	gchar *local_jid = jingle_session_get_local_jid(session);
413 	gchar *remote_jid = jingle_session_get_remote_jid(session);
414 	gchar *sid = jingle_session_get_sid(session);
415 
416 	xmlnode_set_namespace(jingle, JINGLE);
417 	xmlnode_set_attrib(jingle, "action", jingle_get_action_name(action));
418 
419 	if (jingle_session_is_initiator(session)) {
420 		xmlnode_set_attrib(jingle, "initiator", local_jid);
421 		xmlnode_set_attrib(jingle, "responder", remote_jid);
422 	} else {
423 		xmlnode_set_attrib(jingle, "initiator", remote_jid);
424 		xmlnode_set_attrib(jingle, "responder", local_jid);
425 	}
426 
427 	xmlnode_set_attrib(jingle, "sid", sid);
428 
429 	g_free(local_jid);
430 	g_free(remote_jid);
431 	g_free(sid);
432 
433 	return jingle;
434 }
435 
436 JabberIq *
jingle_session_create_ack(JingleSession * session,const xmlnode * jingle)437 jingle_session_create_ack(JingleSession *session, const xmlnode *jingle)
438 {
439 	JabberIq *result = jabber_iq_new(
440 			jingle_session_get_js(session),
441 			JABBER_IQ_RESULT);
442 	xmlnode *packet = xmlnode_get_parent(jingle);
443 	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
444 	xmlnode_set_attrib(result->node, "from", xmlnode_get_attrib(packet, "to"));
445 	xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from"));
446 	return result;
447 }
448 
449 static JabberIq *
jingle_create_iq(JingleSession * session)450 jingle_create_iq(JingleSession *session)
451 {
452 	JabberStream *js = jingle_session_get_js(session);
453 	JabberIq *result = jabber_iq_new(js, JABBER_IQ_SET);
454 	gchar *from = jingle_session_get_local_jid(session);
455 	gchar *to = jingle_session_get_remote_jid(session);
456 
457 	xmlnode_set_attrib(result->node, "from", from);
458 	xmlnode_set_attrib(result->node, "to", to);
459 
460 	g_free(from);
461 	g_free(to);
462 	return result;
463 }
464 
465 xmlnode *
jingle_session_to_xml(JingleSession * session,xmlnode * jingle,JingleActionType action)466 jingle_session_to_xml(JingleSession *session, xmlnode *jingle, JingleActionType action)
467 {
468 	if (action != JINGLE_SESSION_INFO && action != JINGLE_SESSION_TERMINATE) {
469 		GList *iter;
470 		if (action == JINGLE_CONTENT_ACCEPT
471 				|| action == JINGLE_CONTENT_ADD
472 				|| action == JINGLE_CONTENT_REMOVE)
473 			iter = jingle_session_get_pending_contents(session);
474 		else
475 			iter = jingle_session_get_contents(session);
476 
477 		for (; iter; iter = g_list_next(iter)) {
478 			jingle_content_to_xml(iter->data, jingle, action);
479 		}
480 	}
481 	return jingle;
482 }
483 
484 JabberIq *
jingle_session_to_packet(JingleSession * session,JingleActionType action)485 jingle_session_to_packet(JingleSession *session, JingleActionType action)
486 {
487 	JabberIq *iq = jingle_create_iq(session);
488 	xmlnode *jingle = jingle_add_jingle_packet(session, iq, action);
489 	jingle_session_to_xml(session, jingle, action);
490 	return iq;
491 }
492 
jingle_session_handle_action(JingleSession * session,xmlnode * jingle,JingleActionType action)493 void jingle_session_handle_action(JingleSession *session, xmlnode *jingle, JingleActionType action)
494 {
495 	GList *iter;
496 	if (action == JINGLE_CONTENT_ADD || action == JINGLE_CONTENT_REMOVE)
497 		iter = jingle_session_get_pending_contents(session);
498 	else
499 		iter = jingle_session_get_contents(session);
500 
501 	for (; iter; iter = g_list_next(iter)) {
502 		jingle_content_handle_action(iter->data, jingle, action);
503 	}
504 }
505 
506 JingleContent *
jingle_session_find_content(JingleSession * session,const gchar * name,const gchar * creator)507 jingle_session_find_content(JingleSession *session, const gchar *name, const gchar *creator)
508 {
509 	GList *iter;
510 
511 	if (name == NULL)
512 		return NULL;
513 
514 	iter = session->priv->contents;
515 	for (; iter; iter = g_list_next(iter)) {
516 		JingleContent *content = iter->data;
517 		gchar *cname = jingle_content_get_name(content);
518 		gboolean result = purple_strequal(name, cname);
519 		g_free(cname);
520 
521 		if (creator != NULL) {
522 			gchar *ccreator = jingle_content_get_creator(content);
523 			result = (result && purple_strequal(creator, ccreator));
524 			g_free(ccreator);
525 		}
526 
527 		if (result == TRUE)
528 			return content;
529 	}
530 	return NULL;
531 }
532 
533 JingleContent *
jingle_session_find_pending_content(JingleSession * session,const gchar * name,const gchar * creator)534 jingle_session_find_pending_content(JingleSession *session, const gchar *name, const gchar *creator)
535 {
536 	GList *iter;
537 
538 	if (name == NULL)
539 		return NULL;
540 
541 	iter = session->priv->pending_contents;
542 	for (; iter; iter = g_list_next(iter)) {
543 		JingleContent *content = iter->data;
544 		gchar *cname = jingle_content_get_name(content);
545 		gboolean result = purple_strequal(name, cname);
546 		g_free(cname);
547 
548 		if (creator != NULL) {
549 			gchar *ccreator = jingle_content_get_creator(content);
550 			result = (result && purple_strequal(creator, ccreator));
551 			g_free(ccreator);
552 		}
553 
554 		if (result == TRUE)
555 			return content;
556 	}
557 	return NULL;
558 }
559 
560 void
jingle_session_add_content(JingleSession * session,JingleContent * content)561 jingle_session_add_content(JingleSession *session, JingleContent* content)
562 {
563 	session->priv->contents =
564 			g_list_append(session->priv->contents, content);
565 	jingle_content_set_session(content, session);
566 }
567 
568 void
jingle_session_remove_content(JingleSession * session,const gchar * name,const gchar * creator)569 jingle_session_remove_content(JingleSession *session, const gchar *name, const gchar *creator)
570 {
571 	JingleContent *content =
572 			jingle_session_find_content(session, name, creator);
573 
574 	if (content) {
575 		session->priv->contents =
576 				g_list_remove(session->priv->contents, content);
577 		g_object_unref(content);
578 	}
579 }
580 
581 void
jingle_session_add_pending_content(JingleSession * session,JingleContent * content)582 jingle_session_add_pending_content(JingleSession *session, JingleContent* content)
583 {
584 	session->priv->pending_contents =
585 			g_list_append(session->priv->pending_contents, content);
586 	jingle_content_set_session(content, session);
587 }
588 
589 void
jingle_session_remove_pending_content(JingleSession * session,const gchar * name,const gchar * creator)590 jingle_session_remove_pending_content(JingleSession *session, const gchar *name, const gchar *creator)
591 {
592 	JingleContent *content = jingle_session_find_pending_content(session, name, creator);
593 
594 	if (content) {
595 		session->priv->pending_contents =
596 				g_list_remove(session->priv->pending_contents, content);
597 		g_object_unref(content);
598 	}
599 }
600 
601 void
jingle_session_accept_content(JingleSession * session,const gchar * name,const gchar * creator)602 jingle_session_accept_content(JingleSession *session, const gchar *name, const gchar *creator)
603 {
604 	JingleContent *content = jingle_session_find_pending_content(session, name, creator);
605 
606 	if (content) {
607 		g_object_ref(content);
608 		jingle_session_remove_pending_content(session, name, creator);
609 		jingle_session_add_content(session, content);
610 	}
611 }
612 
613 void
jingle_session_accept_session(JingleSession * session)614 jingle_session_accept_session(JingleSession *session)
615 {
616 	session->priv->state = TRUE;
617 }
618 
619 JabberIq *
jingle_session_terminate_packet(JingleSession * session,const gchar * reason)620 jingle_session_terminate_packet(JingleSession *session, const gchar *reason)
621 {
622 	JabberIq *iq = jingle_session_to_packet(session,
623 			JINGLE_SESSION_TERMINATE);
624 	xmlnode *jingle = xmlnode_get_child(iq->node, "jingle");
625 
626 	if (reason != NULL) {
627 		xmlnode *reason_node;
628 		reason_node = xmlnode_new_child(jingle, "reason");
629 		xmlnode_new_child(reason_node, reason);
630 	}
631 	return iq;
632 }
633 
634 JabberIq *
jingle_session_redirect_packet(JingleSession * session,const gchar * sid)635 jingle_session_redirect_packet(JingleSession *session, const gchar *sid)
636 {
637 	JabberIq *iq = jingle_session_terminate_packet(session,
638 			"alternative-session");
639 	xmlnode *alt_session;
640 
641 	if (sid == NULL)
642 		return iq;
643 
644 	alt_session = xmlnode_get_child(iq->node,
645 			"jingle/reason/alternative-session");
646 
647 	if (alt_session != NULL) {
648 		xmlnode *sid_node = xmlnode_new_child(alt_session, "sid");
649 		xmlnode_insert_data(sid_node, sid, -1);
650 	}
651 	return iq;
652 }
653 
654