Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:979d80c21077 |
---|---|
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 */ | |
16 | |
17 #include "gt_gnutella.h" | |
18 #include "message/msg_handler.h" | |
19 | |
20 #include "sha1.h" | |
21 #include "xml.h" | |
22 | |
23 #include "gt_share.h" | |
24 #include "gt_share_file.h" | |
25 #include "gt_share_state.h" | |
26 | |
27 #include "gt_search.h" | |
28 #include "gt_search_exec.h" | |
29 #include "gt_urn.h" | |
30 | |
31 #include "transfer/push_proxy.h" | |
32 | |
33 /*****************************************************************************/ | |
34 | |
35 #define LOG_RESULT_PACKETS gt_config_get_int("search/log_result_packets=0") | |
36 | |
37 /*****************************************************************************/ | |
38 | |
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; | |
46 | |
47 /*****************************************************************************/ | |
48 | |
49 /* cache of recent queries TODO: flush this on plugin unload */ | |
50 static Dataset *query_cache = NULL; | |
51 | |
52 /* flushes the old cache entries */ | |
53 static timer_id flush_timer = 0; | |
54 | |
55 /*****************************************************************************/ | |
56 | |
57 static BOOL is_printable (const char *s) | |
58 { | |
59 while (*s) | |
60 { | |
61 if (!isprint (*s)) | |
62 return FALSE; | |
63 | |
64 s++; | |
65 } | |
66 | |
67 return TRUE; | |
68 } | |
69 | |
70 static void parse_text_meta (const char *data, Dataset **meta) | |
71 { | |
72 int rate, freq, min, sec; | |
73 int n; | |
74 char *lower; | |
75 | |
76 if (!data) | |
77 return; | |
78 | |
79 /* only ASCII strings are plaintext metadata */ | |
80 if (!is_printable (data)) | |
81 return; | |
82 | |
83 /* skip strings that start with "urn:", we know what those are */ | |
84 if (!strncasecmp (data, "urn:", 4)) | |
85 return; | |
86 | |
87 if (!(lower = STRDUP (data))) | |
88 return; | |
89 | |
90 string_lower (lower); | |
91 n = sscanf (lower, "%d kbps %d khz %d:%d", &rate, &freq, &min, &sec); | |
92 | |
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); | |
96 | |
97 free (lower); | |
98 | |
99 if (n != 4) | |
100 { | |
101 #if 0 | |
102 static int warned = 0; | |
103 | |
104 if (warned++ < 4) | |
105 GT->DBGFN (GT, "unknown plaintext metadata?: %s", data); | |
106 #endif | |
107 | |
108 return; | |
109 } | |
110 | |
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); | |
114 | |
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 } | |
119 | |
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; | |
125 | |
126 if (r_urn) | |
127 *r_urn = NULL; | |
128 if (r_meta) | |
129 *r_meta = NULL; | |
130 | |
131 if (!ext_block) | |
132 return; | |
133 | |
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; | |
142 | |
143 if (r_urn && (urn = gt_urn_parse (ext))) | |
144 { | |
145 free (*r_urn); | |
146 *r_urn = urn; | |
147 } | |
148 | |
149 if (r_meta) | |
150 { | |
151 parse_text_meta (ext, r_meta); | |
152 gt_xml_parse (ext, r_meta); | |
153 } | |
154 } | |
155 } | |
156 | |
157 static BOOL append_result (GtPacket *packet, FileShare *file) | |
158 { | |
159 GtShare *share; | |
160 Hash *hash; | |
161 | |
162 if (!(share = share_get_udata (file, GT->name))) | |
163 return FALSE; | |
164 | |
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); | |
170 | |
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; | |
180 | |
181 assert (hash->len == SHA1_BINSIZE); | |
182 | |
183 if ((sha1 = sha1_string (hash->data))) | |
184 { | |
185 char buf[128]; | |
186 int len; | |
187 | |
188 /* make the hash be uppercase */ | |
189 string_upper (sha1); | |
190 | |
191 len = strlen (sha1); | |
192 assert (len == SHA1_STRLEN); | |
193 | |
194 snprintf (buf, sizeof (buf) - 1, "urn:sha1:%s", sha1); | |
195 len += strlen ("urn:sha1:"); | |
196 | |
197 gt_packet_put_ustr (packet, buf, len); | |
198 free (sha1); | |
199 } | |
200 } | |
201 | |
202 /* put the second null there */ | |
203 gt_packet_put_uint8 (packet, 0); | |
204 | |
205 if (gt_packet_error (packet)) | |
206 { | |
207 gt_packet_free (packet); | |
208 return FALSE; | |
209 } | |
210 | |
211 return TRUE; | |
212 } | |
213 | |
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; | |
221 | |
222 /* set the push bit as significant */ | |
223 eqhd2 |= EQHD2_HAS_PUSH; | |
224 /* set the busy bit as significant */ | |
225 eqhd1 |= EQHD1_HAS_BUSY; | |
226 | |
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; | |
234 | |
235 if (upload_availability () == 0) | |
236 eqhd2 |= EQHD2_BUSY_FLAG; | |
237 | |
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); | |
244 | |
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); | |
248 | |
249 /* client identifier */ | |
250 gt_packet_put_ustr (packet, GT_SELF_GUID, 16); | |
251 | |
252 if (gt_packet_error (packet)) | |
253 { | |
254 gt_packet_free (packet); | |
255 return; | |
256 } | |
257 | |
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 | |
262 | |
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; | |
267 | |
268 #if 0 | |
269 GT->DBGFN (GT, "packet after twiddling:"); | |
270 TRACE_MEM (packet->data, packet->len); | |
271 #endif | |
272 | |
273 if (LOG_RESULT_PACKETS) | |
274 GT->dbg (GT, "transmitting %i", hits); | |
275 | |
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 } | |
280 | |
281 static BOOL query_request_result (TCPC *c, FileShare *file, | |
282 gt_search_reply_t *reply) | |
283 { | |
284 GtPacket *packet; | |
285 | |
286 if (!file) | |
287 { | |
288 /* send any remaining data */ | |
289 if (reply->packet) | |
290 transmit_results (c, reply->packet, reply->results); | |
291 | |
292 return FALSE; | |
293 } | |
294 | |
295 packet = reply->packet; | |
296 | |
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); | |
304 | |
305 reply->packet = NULL; | |
306 reply->results = 0; | |
307 | |
308 /* handle this item again */ | |
309 return TRUE; | |
310 } | |
311 | |
312 if (append_result (packet, file)) | |
313 reply->results++; | |
314 | |
315 return FALSE; | |
316 } | |
317 | |
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 } | |
324 | |
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) */ | |
330 | |
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 } | |
337 | |
338 reply->packet = packet; | |
339 | |
340 /* handle this item again */ | |
341 return TRUE; | |
342 } | |
343 | |
344 static BOOL query_request_result_free (TCPC *c, FileShare *file, | |
345 gt_search_reply_t *reply) | |
346 { | |
347 GtShare *share; | |
348 | |
349 if (!file) | |
350 { | |
351 free (reply->guid); | |
352 free (reply); | |
353 return FALSE; | |
354 } | |
355 | |
356 /* just a sanity check */ | |
357 if (file && !(share = share_get_udata (file, GT->name))) | |
358 return FALSE; | |
359 | |
360 return FALSE; | |
361 } | |
362 | |
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]; | |
368 | |
369 while (query_request_result (c, file, reply)) | |
370 ; | |
371 | |
372 query_request_result_free (c, file, reply); | |
373 return TRUE; | |
374 } | |
375 | |
376 static void send_results (TCPC *c, List *results, gt_search_reply_t *reply) | |
377 { | |
378 void *args[2]; | |
379 | |
380 args[0] = c; | |
381 args[1] = reply; | |
382 | |
383 results = list_foreach_remove (results, (ListForeachFunc)send_result, args); | |
384 assert (results == NULL); | |
385 | |
386 query_request_result (c, NULL, reply); | |
387 query_request_result_free (c, NULL, reply); | |
388 } | |
389 | |
390 static int flush_old (ds_data_t *key, ds_data_t *value, time_t *now) | |
391 { | |
392 time_t *timestamp = value->data; | |
393 | |
394 if (*now - *timestamp >= 10 * EMINUTES) | |
395 return DS_CONTINUE | DS_REMOVE; | |
396 | |
397 return DS_CONTINUE; | |
398 } | |
399 | |
400 static BOOL flush_qcache (Dataset *cache) | |
401 { | |
402 time_t now = time (NULL); | |
403 | |
404 assert (query_cache != NULL); | |
405 dataset_foreach_ex (query_cache, DS_FOREACH_EX(flush_old), &now); | |
406 | |
407 return TRUE; | |
408 } | |
409 | |
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; | |
414 | |
415 if (dataset_lookup (query_cache, guid, GT_GUID_LEN)) | |
416 return TRUE; | |
417 | |
418 /* limit the maximum length the query cache can grow */ | |
419 if (dataset_length (query_cache) >= 2000) | |
420 return FALSE; | |
421 | |
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)); | |
425 | |
426 if (!flush_timer) | |
427 { | |
428 flush_timer = timer_add (5 * MINUTES, (TimerCallback)flush_qcache, | |
429 NULL); | |
430 } | |
431 | |
432 return FALSE; | |
433 } | |
434 | |
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; | |
448 | |
449 flags = gt_packet_get_uint16 (packet); | |
450 query = gt_packet_get_str (packet); | |
451 extended = gt_packet_get_str (packet); | |
452 | |
453 guid = gt_packet_guid (packet); | |
454 | |
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; | |
462 | |
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 } | |
469 | |
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 } | |
479 | |
480 return; | |
481 } | |
482 | |
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)); | |
488 | |
489 return; | |
490 } | |
491 | |
492 gt_parse_extended_data (extended, &urn, NULL); | |
493 | |
494 /* WARNING: this assumes sha1 */ | |
495 hash = gt_urn_data (urn); | |
496 | |
497 if (hash) | |
498 type = GT_SEARCH_HASH; | |
499 else | |
500 type = GT_SEARCH_KEYWORD; | |
501 | |
502 #if 0 | |
503 GT->DBGFN (GT, "min_speed = %hu, query = '%s', extended data = '%s'", | |
504 min_speed, query, extended); | |
505 #endif | |
506 | |
507 ttl = gt_packet_ttl (packet); | |
508 hops = gt_packet_hops (packet); | |
509 | |
510 list = gt_search_exec (query, type, urn, ttl, hops); | |
511 free (urn); | |
512 | |
513 if (!list) | |
514 return; | |
515 | |
516 if (!(reply = MALLOC (sizeof (gt_search_reply_t)))) | |
517 { | |
518 list_free (list); | |
519 return; | |
520 } | |
521 | |
522 /* set the ttl of the reply to be +1 the hops the request travelled */ | |
523 reply->ttl = gt_packet_hops (packet) + 1; | |
524 | |
525 /* use the guid of the packet in replying to results */ | |
526 reply->guid = gt_guid_dup (guid); | |
527 | |
528 send_results (c, list, reply); | |
529 } |