view src/message/query.c @ 0:d39e1d0d75b6

initial add
author paulo@hit-nxdomain.opendns.com
date Sat, 20 Feb 2010 21:18:28 -0800
parents
children
line source
1 /*
2 * $Id: query.c,v 1.10 2004/06/04 15:44:59 hipnod Exp $
3 *
4 * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net)
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 */
17 #include "gt_gnutella.h"
18 #include "message/msg_handler.h"
20 #include "sha1.h"
21 #include "xml.h"
23 #include "gt_share.h"
24 #include "gt_share_file.h"
25 #include "gt_share_state.h"
27 #include "gt_search.h"
28 #include "gt_search_exec.h"
29 #include "gt_urn.h"
31 #include "transfer/push_proxy.h"
33 /*****************************************************************************/
35 #define LOG_RESULT_PACKETS gt_config_get_int("search/log_result_packets=0")
37 /*****************************************************************************/
39 typedef struct gt_search_reply
40 {
41 uint8_t ttl;
42 uint8_t results; /* number of results on the current packet */
43 GtPacket *packet; /* the current packet to stack results on */
44 gt_guid_t *guid;
45 } gt_search_reply_t;
47 /*****************************************************************************/
49 /* cache of recent queries TODO: flush this on plugin unload */
50 static Dataset *query_cache = NULL;
52 /* flushes the old cache entries */
53 static timer_id flush_timer = 0;
55 /*****************************************************************************/
57 static BOOL is_printable (const char *s)
58 {
59 while (*s)
60 {
61 if (!isprint (*s))
62 return FALSE;
64 s++;
65 }
67 return TRUE;
68 }
70 static void parse_text_meta (const char *data, Dataset **meta)
71 {
72 int rate, freq, min, sec;
73 int n;
74 char *lower;
76 if (!data)
77 return;
79 /* only ASCII strings are plaintext metadata */
80 if (!is_printable (data))
81 return;
83 /* skip strings that start with "urn:", we know what those are */
84 if (!strncasecmp (data, "urn:", 4))
85 return;
87 if (!(lower = STRDUP (data)))
88 return;
90 string_lower (lower);
91 n = sscanf (lower, "%d kbps %d khz %d:%d", &rate, &freq, &min, &sec);
93 /* try again with a slightly different format if it failed */
94 if (n != 4)
95 n = sscanf (lower, "%d kbps(vbr) %d khz %d:%d", &rate, &freq, &min, &sec);
97 free (lower);
99 if (n != 4)
100 {
101 #if 0
102 static int warned = 0;
104 if (warned++ < 4)
105 GT->DBGFN (GT, "unknown plaintext metadata?: %s", data);
106 #endif
108 return;
109 }
111 /* XXX: actually this should be META_DEBUG */
112 if (XML_DEBUG)
113 GT->DBGFN (GT, "parsed %d kbps %d khz %d:%d", rate, freq, min, sec);
115 dataset_insertstr (meta, "bitrate", stringf ("%li", rate * 1000));
116 dataset_insertstr (meta, "frequency", stringf ("%u", freq * 1000));
117 dataset_insertstr (meta, "duration", stringf ("%i", min * 60 + sec));
118 }
120 void gt_parse_extended_data (char *ext_block, gt_urn_t **r_urn,
121 Dataset **r_meta)
122 {
123 gt_urn_t *urn = NULL;
124 char *ext;
126 if (r_urn)
127 *r_urn = NULL;
128 if (r_meta)
129 *r_meta = NULL;
131 if (!ext_block)
132 return;
134 /*
135 * 0x1c is the separator character for so-called "GEM"
136 * (Gnutella-Extension Mechanism) extensions.
137 */
138 while ((ext = string_sep (&ext_block, "\x1c")))
139 {
140 if (string_isempty (ext))
141 break;
143 if (r_urn && (urn = gt_urn_parse (ext)))
144 {
145 free (*r_urn);
146 *r_urn = urn;
147 }
149 if (r_meta)
150 {
151 parse_text_meta (ext, r_meta);
152 gt_xml_parse (ext, r_meta);
153 }
154 }
155 }
157 static BOOL append_result (GtPacket *packet, FileShare *file)
158 {
159 GtShare *share;
160 Hash *hash;
162 if (!(share = share_get_udata (file, GT->name)))
163 return FALSE;
165 /* search results
166 * format: <index#> <file size> <file name> <extra data(include hash)> */
167 gt_packet_put_uint32 (packet, share->index);
168 gt_packet_put_uint32 (packet, file->size);
169 gt_packet_put_str (packet, share->filename);
171 /*
172 * This is the information that goes "between the nulls" in a
173 * query hit. The first null comes after the filename.
174 *
175 * This is a bit specific and icky. It should be abstracted away.
176 */
177 if ((hash = share_get_hash (file, "SHA1")))
178 {
179 char *sha1;
181 assert (hash->len == SHA1_BINSIZE);
183 if ((sha1 = sha1_string (hash->data)))
184 {
185 char buf[128];
186 int len;
188 /* make the hash be uppercase */
189 string_upper (sha1);
191 len = strlen (sha1);
192 assert (len == SHA1_STRLEN);
194 snprintf (buf, sizeof (buf) - 1, "urn:sha1:%s", sha1);
195 len += strlen ("urn:sha1:");
197 gt_packet_put_ustr (packet, buf, len);
198 free (sha1);
199 }
200 }
202 /* put the second null there */
203 gt_packet_put_uint8 (packet, 0);
205 if (gt_packet_error (packet))
206 {
207 gt_packet_free (packet);
208 return FALSE;
209 }
211 return TRUE;
212 }
214 /* add a trailer to the packets */
215 static void transmit_results (TCPC *c, GtPacket *packet, uint8_t hits)
216 {
217 gt_eqhd1_t eqhd1 = EQHD1_EMPTY;
218 gt_eqhd2_t eqhd2 = EQHD2_EMPTY;
219 uint8_t *ggep;
220 size_t ggep_len;
222 /* set the push bit as significant */
223 eqhd2 |= EQHD2_HAS_PUSH;
224 /* set the busy bit as significant */
225 eqhd1 |= EQHD1_HAS_BUSY;
227 /*
228 * We shouldnt mark ourselves firewalled if the destination is
229 * a local ip address and ttl == 1. However, for greater TTLs,
230 * there's no knowing if we should mark it or not...
231 */
232 if (GT_SELF->firewalled)
233 eqhd1 |= EQHD1_PUSH_FLAG;
235 if (upload_availability () == 0)
236 eqhd2 |= EQHD2_BUSY_FLAG;
238 /* add the query hit descriptor
239 * <vendor id> <length> <qhd_data1> <qhd_data2> <private_data> */
240 gt_packet_put_ustr (packet, (const unsigned char *)"GIFT", 4);
241 gt_packet_put_uint8 (packet, 2);
242 gt_packet_put_uint8 (packet, eqhd1);
243 gt_packet_put_uint8 (packet, eqhd2);
245 /* append GGEP block (only contains PUSH proxies for now) */
246 if (gt_push_proxy_get_ggep_block (&ggep, &ggep_len))
247 gt_packet_put_ustr (packet, ggep, ggep_len);
249 /* client identifier */
250 gt_packet_put_ustr (packet, GT_SELF_GUID, 16);
252 if (gt_packet_error (packet))
253 {
254 gt_packet_free (packet);
255 return;
256 }
258 #if 0
259 GT->DBGFN (GT, "packet before twiddling result number: (will twiddle %i)", hits);
260 TRACE_MEM (packet->data, packet->len);
261 #endif
263 /* rewind the packet to the search hit count and replace the hitcount
264 * it is the first byte after the header
265 * XXX: this should use a facility of gt_packet */
266 packet->data[GNUTELLA_HDR_LEN] = hits;
268 #if 0
269 GT->DBGFN (GT, "packet after twiddling:");
270 TRACE_MEM (packet->data, packet->len);
271 #endif
273 if (LOG_RESULT_PACKETS)
274 GT->dbg (GT, "transmitting %i", hits);
276 /* send the reply along the path to the node that queried us */
277 gt_packet_send (c, packet);
278 gt_packet_free (packet);
279 }
281 static BOOL query_request_result (TCPC *c, FileShare *file,
282 gt_search_reply_t *reply)
283 {
284 GtPacket *packet;
286 if (!file)
287 {
288 /* send any remaining data */
289 if (reply->packet)
290 transmit_results (c, reply->packet, reply->results);
292 return FALSE;
293 }
295 packet = reply->packet;
297 if (packet)
298 {
299 /* send the packet if the max results per packet is reached
300 * or the size of the packet is large */
301 if (reply->results == 255 || gt_packet_payload_len (packet) > 2000)
302 {
303 transmit_results (c, packet, reply->results);
305 reply->packet = NULL;
306 reply->results = 0;
308 /* handle this item again */
309 return TRUE;
310 }
312 if (append_result (packet, file))
313 reply->results++;
315 return FALSE;
316 }
318 /* allocate a packet */
319 if (!(packet = gt_packet_new (GT_MSG_QUERY_REPLY, reply->ttl, reply->guid)))
320 {
321 GIFT_ERROR (("mem failure?"));
322 return FALSE;
323 }
325 /* setup the search header */
326 gt_packet_put_uint8 (packet, 0);
327 gt_packet_put_port (packet, GT_SELF->gt_port);
328 gt_packet_put_ip (packet, GT_NODE(c)->my_ip);
329 gt_packet_put_uint32 (packet, 0); /* speed (kbits) */
331 if (gt_packet_error (packet))
332 {
333 GIFT_ERROR (("failed seting up search result packet"));
334 gt_packet_free (packet);
335 return FALSE;
336 }
338 reply->packet = packet;
340 /* handle this item again */
341 return TRUE;
342 }
344 static BOOL query_request_result_free (TCPC *c, FileShare *file,
345 gt_search_reply_t *reply)
346 {
347 GtShare *share;
349 if (!file)
350 {
351 free (reply->guid);
352 free (reply);
353 return FALSE;
354 }
356 /* just a sanity check */
357 if (file && !(share = share_get_udata (file, GT->name)))
358 return FALSE;
360 return FALSE;
361 }
363 /* This emulates the old queue interface */
364 static BOOL send_result (FileShare *file, void **args)
365 {
366 TCPC *c = args[0];
367 gt_search_reply_t *reply = args[1];
369 while (query_request_result (c, file, reply))
370 ;
372 query_request_result_free (c, file, reply);
373 return TRUE;
374 }
376 static void send_results (TCPC *c, List *results, gt_search_reply_t *reply)
377 {
378 void *args[2];
380 args[0] = c;
381 args[1] = reply;
383 results = list_foreach_remove (results, (ListForeachFunc)send_result, args);
384 assert (results == NULL);
386 query_request_result (c, NULL, reply);
387 query_request_result_free (c, NULL, reply);
388 }
390 static int flush_old (ds_data_t *key, ds_data_t *value, time_t *now)
391 {
392 time_t *timestamp = value->data;
394 if (*now - *timestamp >= 10 * EMINUTES)
395 return DS_CONTINUE | DS_REMOVE;
397 return DS_CONTINUE;
398 }
400 static BOOL flush_qcache (Dataset *cache)
401 {
402 time_t now = time (NULL);
404 assert (query_cache != NULL);
405 dataset_foreach_ex (query_cache, DS_FOREACH_EX(flush_old), &now);
407 return TRUE;
408 }
410 /* TODO: need to break up this file soon to isolate these things */
411 static BOOL query_cache_lookup (gt_guid_t *guid)
412 {
413 time_t now;
415 if (dataset_lookup (query_cache, guid, GT_GUID_LEN))
416 return TRUE;
418 /* limit the maximum length the query cache can grow */
419 if (dataset_length (query_cache) >= 2000)
420 return FALSE;
422 /* add the guid for catching duplicates next time */
423 now = time (NULL);
424 dataset_insert (&query_cache, guid, GT_GUID_LEN, &now, sizeof (now));
426 if (!flush_timer)
427 {
428 flush_timer = timer_add (5 * MINUTES, (TimerCallback)flush_qcache,
429 NULL);
430 }
432 return FALSE;
433 }
435 GT_MSG_HANDLER(gt_msg_query)
436 {
437 char *query;
438 char *extended;
439 gt_guid_t *guid;
440 gt_urn_t *urn;
441 List *list;
442 uint8_t ttl;
443 uint8_t hops;
444 unsigned char *hash;
445 gt_query_flags_t flags;
446 gt_search_type_t type;
447 gt_search_reply_t *reply;
449 flags = gt_packet_get_uint16 (packet);
450 query = gt_packet_get_str (packet);
451 extended = gt_packet_get_str (packet);
453 guid = gt_packet_guid (packet);
455 /*
456 * TODO: node->share_state can be null here, if the node hasn't
457 * successfully handshaked yet. Should fix this by storing messages until
458 * handshake is complete.
459 */
460 if (node->share_state && node->share_state->hidden)
461 return;
463 /* don't reply if the host is firewalled and we are too */
464 if ((flags & QF_HAS_FLAGS) && (flags & QF_ONLY_NON_FW) &&
465 GT_SELF->firewalled)
466 {
467 return;
468 }
470 /* don't reply if this is our own search -- TODO: substitute a
471 * full-fledged routing table */
472 if (gt_search_find (guid))
473 {
474 if (MSG_DEBUG)
475 {
476 GT->dbg (GT, "not searching, own search (guid %s)",
477 gt_guid_str (guid));
478 }
480 return;
481 }
483 /* check if we've handled this search already */
484 if (query_cache_lookup (guid))
485 {
486 if (MSG_DEBUG)
487 GT->DBGSOCK (GT, c, "duplicate search (%s)", gt_guid_str (guid));
489 return;
490 }
492 gt_parse_extended_data (extended, &urn, NULL);
494 /* WARNING: this assumes sha1 */
495 hash = gt_urn_data (urn);
497 if (hash)
498 type = GT_SEARCH_HASH;
499 else
500 type = GT_SEARCH_KEYWORD;
502 #if 0
503 GT->DBGFN (GT, "min_speed = %hu, query = '%s', extended data = '%s'",
504 min_speed, query, extended);
505 #endif
507 ttl = gt_packet_ttl (packet);
508 hops = gt_packet_hops (packet);
510 list = gt_search_exec (query, type, urn, ttl, hops);
511 free (urn);
513 if (!list)
514 return;
516 if (!(reply = MALLOC (sizeof (gt_search_reply_t))))
517 {
518 list_free (list);
519 return;
520 }
522 /* set the ttl of the reply to be +1 the hops the request travelled */
523 reply->ttl = gt_packet_hops (packet) + 1;
525 /* use the guid of the packet in replying to results */
526 reply->guid = gt_guid_dup (guid);
528 send_results (c, list, reply);
529 }