diff options
Diffstat (limited to 'src/peerinfo/plugin_rest_peerinfo.c')
-rw-r--r-- | src/peerinfo/plugin_rest_peerinfo.c | 398 |
1 files changed, 267 insertions, 131 deletions
diff --git a/src/peerinfo/plugin_rest_peerinfo.c b/src/peerinfo/plugin_rest_peerinfo.c index 408f9a6f7..a65089b32 100644 --- a/src/peerinfo/plugin_rest_peerinfo.c +++ b/src/peerinfo/plugin_rest_peerinfo.c | |||
@@ -39,6 +39,10 @@ | |||
39 | #define GNUNET_REST_ERROR_UNKNOWN "Unkown Error" | 39 | #define GNUNET_REST_ERROR_UNKNOWN "Unkown Error" |
40 | 40 | ||
41 | /** | 41 | /** |
42 | * How long until we time out during address lookup? | ||
43 | */ | ||
44 | #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) | ||
45 | /** | ||
42 | * The configuration handle | 46 | * The configuration handle |
43 | */ | 47 | */ |
44 | const struct GNUNET_CONFIGURATION_Handle *cfg; | 48 | const struct GNUNET_CONFIGURATION_Handle *cfg; |
@@ -59,6 +63,95 @@ struct Plugin | |||
59 | //TODO add specific structs | 63 | //TODO add specific structs |
60 | 64 | ||
61 | 65 | ||
66 | /** | ||
67 | * Record we keep for each printable address. | ||
68 | */ | ||
69 | struct AddressRecord | ||
70 | { | ||
71 | /** | ||
72 | * Current address-to-string context (if active, otherwise NULL). | ||
73 | */ | ||
74 | struct GNUNET_TRANSPORT_AddressToStringContext *atsc; | ||
75 | |||
76 | /** | ||
77 | * Address expiration time | ||
78 | */ | ||
79 | struct GNUNET_TIME_Absolute expiration; | ||
80 | |||
81 | /** | ||
82 | * Printable address. | ||
83 | */ | ||
84 | char *result; | ||
85 | |||
86 | /** | ||
87 | * Print context this address record belongs to. | ||
88 | */ | ||
89 | struct PrintContext *pc; | ||
90 | }; | ||
91 | |||
92 | |||
93 | /** | ||
94 | * Structure we use to collect printable address information. | ||
95 | */ | ||
96 | struct PrintContext | ||
97 | { | ||
98 | |||
99 | /** | ||
100 | * Kept in DLL. | ||
101 | */ | ||
102 | struct PrintContext *next; | ||
103 | |||
104 | /** | ||
105 | * Kept in DLL. | ||
106 | */ | ||
107 | struct PrintContext *prev; | ||
108 | |||
109 | /** | ||
110 | * Identity of the peer. | ||
111 | */ | ||
112 | struct GNUNET_PeerIdentity peer; | ||
113 | |||
114 | /** | ||
115 | * List of printable addresses. | ||
116 | */ | ||
117 | struct AddressRecord *address_list; | ||
118 | |||
119 | /** | ||
120 | * Number of completed addresses in @e address_list. | ||
121 | */ | ||
122 | unsigned int num_addresses; | ||
123 | |||
124 | /** | ||
125 | * Number of addresses allocated in @e address_list. | ||
126 | */ | ||
127 | unsigned int address_list_size; | ||
128 | |||
129 | /** | ||
130 | * Current offset in @e address_list (counted down). | ||
131 | */ | ||
132 | unsigned int off; | ||
133 | |||
134 | /** | ||
135 | * Hello was friend only, #GNUNET_YES or #GNUNET_NO | ||
136 | */ | ||
137 | int friend_only; | ||
138 | |||
139 | /** | ||
140 | * RequestHandle | ||
141 | */ | ||
142 | struct RequestHandle *handle; | ||
143 | |||
144 | }; | ||
145 | |||
146 | /** | ||
147 | * Head of list of print contexts. | ||
148 | */ | ||
149 | static struct PrintContext *pc_head; | ||
150 | |||
151 | /** | ||
152 | * Tail of list of print contexts. | ||
153 | */ | ||
154 | static struct PrintContext *pc_tail; | ||
62 | 155 | ||
63 | struct RequestHandle | 156 | struct RequestHandle |
64 | { | 157 | { |
@@ -139,8 +232,6 @@ cleanup_handle (void *cls) | |||
139 | { | 232 | { |
140 | struct RequestHandle *handle = cls; | 233 | struct RequestHandle *handle = cls; |
141 | 234 | ||
142 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Test: %i\n", NULL == handle); | ||
143 | |||
144 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | 235 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, |
145 | "Cleaning up\n"); | 236 | "Cleaning up\n"); |
146 | if (NULL != handle->timeout_task) | 237 | if (NULL != handle->timeout_task) |
@@ -212,7 +303,6 @@ do_error (void *cls) | |||
212 | static void | 303 | static void |
213 | peerinfo_list_finished (void *cls) | 304 | peerinfo_list_finished (void *cls) |
214 | { | 305 | { |
215 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "6\n"); | ||
216 | struct RequestHandle *handle = cls; | 306 | struct RequestHandle *handle = cls; |
217 | char *result_str; | 307 | char *result_str; |
218 | struct MHD_Response *resp; | 308 | struct MHD_Response *resp; |
@@ -234,132 +324,177 @@ peerinfo_list_finished (void *cls) | |||
234 | 324 | ||
235 | 325 | ||
236 | /** | 326 | /** |
237 | * Set @a cls to #GNUNET_YES (we have an address!). | 327 | * Iterator callback to go over all addresses and count them. |
238 | * | 328 | * |
239 | * @param cls closure, an `int *` | 329 | * @param cls `struct PrintContext *` with `off` to increment |
240 | * @param address the address (ignored) | 330 | * @param address the address |
241 | * @param expiration expiration time (call is ignored if this is in the past) | 331 | * @param expiration expiration time |
242 | * @return #GNUNET_SYSERR to stop iterating (unless expiration has occured) | 332 | * @return #GNUNET_OK to keep the address and continue |
243 | */ | 333 | */ |
244 | static int | 334 | static int |
245 | check_has_addr (void *cls, | 335 | count_address (void *cls, |
246 | const struct GNUNET_HELLO_Address *address, | 336 | const struct GNUNET_HELLO_Address *address, |
247 | struct GNUNET_TIME_Absolute expiration) | 337 | struct GNUNET_TIME_Absolute expiration) |
248 | { | 338 | { |
249 | int *arg = cls; | 339 | struct PrintContext *pc = cls; |
340 | |||
250 | if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us) | 341 | if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us) |
251 | { | 342 | { |
252 | return GNUNET_YES; /* ignore this address */ | 343 | return GNUNET_OK; /* ignore expired address */ |
253 | } | 344 | } |
254 | *arg = GNUNET_YES; | ||
255 | return GNUNET_SYSERR; | ||
256 | } | ||
257 | 345 | ||
258 | static void | 346 | pc->off++; |
259 | create_array(void *cls) | 347 | return GNUNET_OK; |
260 | { | ||
261 | struct RequestHandle *handle = cls; | ||
262 | // json_t *object; | ||
263 | // object = json_object(); | ||
264 | // | ||
265 | // json_object_set(object,"address",json_string(handle->address)); | ||
266 | // json_object_set(object,"expires",json_string(handle->expiration_str)); | ||
267 | // | ||
268 | // if(NULL == handle->temp_array) | ||
269 | // { | ||
270 | // handle->temp_array = json_array(); | ||
271 | // } | ||
272 | // | ||
273 | // json_array_append(handle->temp_array,object); | ||
274 | // | ||
275 | // json_decref(object); | ||
276 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "5\n"); | ||
277 | json_object_set(handle->response, handle->pubkey, handle->temp_array); | ||
278 | json_decref (handle->temp_array); | ||
279 | } | 348 | } |
280 | 349 | ||
350 | |||
351 | /** | ||
352 | * Print the collected address information to the console and free @a pc. | ||
353 | * | ||
354 | * @param pc printing context | ||
355 | */ | ||
281 | static void | 356 | static void |
282 | create_tmp_array (void *cls) | 357 | dump_pc (struct PrintContext *pc) |
283 | { | 358 | { |
284 | struct RequestHandle *handle = cls; | 359 | struct RequestHandle *handle; |
285 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "4\n"); | 360 | unsigned int i; |
361 | json_t *temp_array; | ||
286 | json_t *object; | 362 | json_t *object; |
287 | json_t *address_json = json_string (handle->address); | 363 | json_t *address; |
288 | json_t *expires_json = json_string (handle->expiration_str); | 364 | json_t *expires; |
289 | object = json_object (); | 365 | char *friend_and_peer; |
290 | 366 | ||
291 | json_object_set (object, "address", address_json); | 367 | temp_array = json_array(); |
292 | json_decref(address_json); | ||
293 | json_object_set (object, "expires", expires_json); | ||
294 | json_decref(expires_json); | ||
295 | GNUNET_free(handle->expiration_str); | ||
296 | 368 | ||
297 | if (NULL == handle->temp_array) | 369 | // printf (_("%sPeer `%s'\n"), |
370 | // (GNUNET_YES == pc->friend_only) ? "F2F: " : "", | ||
371 | // GNUNET_i2s_full (&pc->peer)); | ||
372 | for (i = 0; i < pc->num_addresses; i++) | ||
298 | { | 373 | { |
299 | handle->temp_array = json_array (); | 374 | if (NULL != pc->address_list[i].result) |
375 | { | ||
376 | object = json_object (); | ||
377 | address = json_string(pc->address_list[i].result); | ||
378 | expires = json_string( | ||
379 | GNUNET_STRINGS_absolute_time_to_string (pc->address_list[i].expiration)); | ||
380 | json_object_set (object, "address", address); | ||
381 | json_object_set (object, "expires", expires); | ||
382 | |||
383 | json_decref(address); | ||
384 | json_decref(expires); | ||
385 | |||
386 | json_array_append(temp_array, object); | ||
387 | json_decref(object); | ||
388 | GNUNET_free (pc->address_list[i].result); | ||
389 | } | ||
300 | } | 390 | } |
301 | 391 | ||
302 | json_array_append (handle->temp_array, object); | 392 | if (0 < json_array_size(temp_array)) |
393 | { | ||
394 | GNUNET_asprintf(&friend_and_peer, | ||
395 | "%s%s", | ||
396 | (GNUNET_YES == pc->friend_only) ? "F2F:" : "", | ||
397 | GNUNET_i2s_full (&pc->peer)); | ||
398 | json_object_set(pc->handle->response, | ||
399 | friend_and_peer, | ||
400 | temp_array); | ||
401 | GNUNET_free(friend_and_peer); | ||
402 | } | ||
403 | |||
404 | json_decref (temp_array); | ||
405 | |||
406 | GNUNET_free_non_null (pc->address_list); | ||
407 | GNUNET_CONTAINER_DLL_remove (pc_head, | ||
408 | pc_tail, | ||
409 | pc); | ||
410 | handle = pc->handle; | ||
411 | GNUNET_free (pc); | ||
412 | |||
413 | if ( (NULL == pc_head) && | ||
414 | (NULL == handle->list_it) ) | ||
415 | { | ||
416 | GNUNET_SCHEDULER_add_now (&peerinfo_list_finished, handle); | ||
417 | } | ||
303 | 418 | ||
304 | json_decref (object); | ||
305 | } | 419 | } |
306 | 420 | ||
421 | |||
422 | /** | ||
423 | * Function to call with a human-readable format of an address | ||
424 | * | ||
425 | * @param cls closure | ||
426 | * @param address NULL on error, otherwise 0-terminated printable UTF-8 string | ||
427 | * @param res result of the address to string conversion: | ||
428 | * if #GNUNET_OK: address was valid (conversion to | ||
429 | * string might still have failed) | ||
430 | * if #GNUNET_SYSERR: address is invalid | ||
431 | */ | ||
307 | static void | 432 | static void |
308 | addr_to_str_cb (void *cls, | 433 | process_resolved_address (void *cls, |
309 | const char *address, | 434 | const char *address, |
310 | int res) | 435 | int res) |
311 | { | 436 | { |
312 | struct RequestHandle *handle = cls; | 437 | struct AddressRecord *ar = cls; |
313 | if (NULL == address) | 438 | struct PrintContext *pc = ar->pc; |
314 | { | 439 | |
315 | return; | 440 | if (NULL != address) |
316 | } | ||
317 | if (GNUNET_OK != res) | ||
318 | { | 441 | { |
442 | if (0 != strlen (address)) | ||
443 | { | ||
444 | if (NULL != ar->result) | ||
445 | GNUNET_free (ar->result); | ||
446 | ar->result = GNUNET_strdup (address); | ||
447 | } | ||
319 | return; | 448 | return; |
320 | } | 449 | } |
321 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "3\n"); | 450 | ar->atsc = NULL; |
322 | handle->address = GNUNET_strdup(address); | 451 | if (GNUNET_SYSERR == res) |
323 | GNUNET_assert(false); | 452 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, |
453 | _("Failure: Cannot convert address to string for peer `%s'\n"), | ||
454 | GNUNET_i2s (&ar->pc->peer)); | ||
455 | pc->num_addresses++; | ||
456 | if (pc->num_addresses == pc->address_list_size) | ||
457 | dump_pc (ar->pc); | ||
324 | } | 458 | } |
325 | 459 | ||
460 | |||
326 | /** | 461 | /** |
327 | * Set @a cls to #GNUNET_YES (we have an address!). | 462 | * Iterator callback to go over all addresses. |
328 | * | 463 | * |
329 | * @param cls closure | 464 | * @param cls closure |
330 | * @param address the address (ignored) | 465 | * @param address the address |
331 | * @param expiration expiration time (call is ignored if this is in the past) | 466 | * @param expiration expiration time |
332 | * @return #GNUNET_SYSERR to stop iterating (unless expiration has occured) | 467 | * @return #GNUNET_OK to keep the address and continue |
333 | */ | 468 | */ |
334 | static int | 469 | static int |
335 | address_iteration (void *cls, | 470 | print_address (void *cls, |
336 | const struct GNUNET_HELLO_Address *address, | 471 | const struct GNUNET_HELLO_Address *address, |
337 | struct GNUNET_TIME_Absolute expiration) | 472 | struct GNUNET_TIME_Absolute expiration) |
338 | { | 473 | { |
339 | struct RequestHandle *handle = cls; | 474 | struct PrintContext *pc = cls; |
340 | char *expiration_tmp; | 475 | struct AddressRecord *ar; |
341 | 476 | ||
342 | if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us) | 477 | if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us) |
343 | { | 478 | { |
344 | return GNUNET_YES; /* ignore this address */ | 479 | return GNUNET_OK; /* ignore expired address */ |
345 | } | 480 | } |
346 | expiration_tmp = GNUNET_STRINGS_absolute_time_to_string(expiration); | 481 | |
347 | handle->expiration_str = GNUNET_strdup(expiration_tmp); | 482 | GNUNET_assert (0 < pc->off); |
348 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "2\n"); | 483 | ar = &pc->address_list[--pc->off]; |
349 | GNUNET_TRANSPORT_address_to_string(cfg, | 484 | ar->pc = pc; |
350 | address, | 485 | ar->expiration = expiration; |
351 | GNUNET_NO, | 486 | GNUNET_asprintf (&ar->result, |
352 | GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10), | 487 | "%s:%u:%u", |
353 | &addr_to_str_cb, | 488 | address->transport_name, |
354 | handle); | 489 | address->address_length, |
355 | 490 | address->local_info); | |
356 | GNUNET_SCHEDULER_add_now(&create_tmp_array,handle); | 491 | ar->atsc = GNUNET_TRANSPORT_address_to_string (cfg, |
357 | 492 | address, | |
358 | // GNUNET_SCHEDULER_add_delayed ( | 493 | GNUNET_NO, |
359 | // GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 11), | 494 | TIMEOUT, |
360 | // &create_array, | 495 | &process_resolved_address, |
361 | // handle); | 496 | ar); |
362 | return GNUNET_YES; | 497 | return GNUNET_OK; |
363 | } | 498 | } |
364 | 499 | ||
365 | 500 | ||
@@ -379,52 +514,58 @@ peerinfo_list_iteration(void *cls, | |||
379 | const char *err_msg) | 514 | const char *err_msg) |
380 | { | 515 | { |
381 | struct RequestHandle *handle = cls; | 516 | struct RequestHandle *handle = cls; |
382 | int has_addr; | 517 | struct PrintContext *pc; |
518 | int friend_only; | ||
383 | 519 | ||
384 | if (NULL == handle->response) | 520 | if (NULL == handle->response) |
385 | { | 521 | { |
386 | handle->response = json_object(); | 522 | handle->response = json_object(); |
387 | } | 523 | } |
388 | 524 | ||
389 | if (NULL != err_msg) | ||
390 | { | ||
391 | GNUNET_assert (NULL == peer); | ||
392 | handle->list_it = NULL; | ||
393 | handle->emsg = GNUNET_strdup ("Error in communication with peerinfo"); | ||
394 | handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; | ||
395 | GNUNET_SCHEDULER_add_now (&do_error, handle); | ||
396 | return; | ||
397 | } | ||
398 | if (NULL == peer) | 525 | if (NULL == peer) |
399 | { | 526 | { |
400 | handle->list_it = NULL; | 527 | handle->list_it = NULL; |
401 | GNUNET_SCHEDULER_add_now (&peerinfo_list_finished, handle); | 528 | handle->emsg = GNUNET_strdup ("Error in communication with peerinfo"); |
529 | if (NULL != err_msg) | ||
530 | { | ||
531 | GNUNET_free(handle->emsg); | ||
532 | handle->emsg = GNUNET_strdup (err_msg); | ||
533 | handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; | ||
534 | } | ||
535 | if (NULL == pc_head) | ||
536 | GNUNET_SCHEDULER_add_now (&do_error, handle); | ||
402 | return; | 537 | return; |
403 | } | 538 | } |
404 | if (NULL == hello) | 539 | if (NULL == hello) |
405 | return; | 540 | return; |
406 | has_addr = GNUNET_NO; | 541 | |
542 | friend_only = GNUNET_NO; | ||
543 | if (NULL != hello) | ||
544 | friend_only = GNUNET_HELLO_is_friend_only (hello); | ||
545 | |||
546 | pc = GNUNET_new(struct PrintContext); | ||
547 | GNUNET_CONTAINER_DLL_insert (pc_head, | ||
548 | pc_tail, | ||
549 | pc); | ||
550 | pc->peer = *peer; | ||
551 | pc->friend_only = friend_only; | ||
552 | pc->handle = handle; | ||
407 | GNUNET_HELLO_iterate_addresses (hello, | 553 | GNUNET_HELLO_iterate_addresses (hello, |
408 | GNUNET_NO, | 554 | GNUNET_NO, |
409 | &check_has_addr, | 555 | &count_address, |
410 | &has_addr); | 556 | pc); |
411 | if (GNUNET_NO == has_addr) | 557 | if (0 == pc->off) |
412 | { | 558 | { |
413 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | 559 | dump_pc (pc); |
414 | "HELLO for peer `%4s' has no address, not suitable for hostlist!\n", | ||
415 | GNUNET_i2s (peer)); | ||
416 | return; | 560 | return; |
417 | } | 561 | } |
418 | 562 | pc->address_list_size = pc->off; | |
419 | if (NULL != handle->pubkey) | 563 | pc->address_list = GNUNET_malloc( |
420 | GNUNET_free (handle->pubkey); | 564 | sizeof(struct AddressRecord) * pc->off); |
421 | handle->pubkey = GNUNET_CRYPTO_eddsa_public_key_to_string(&peer->public_key); | ||
422 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "1\n"); | ||
423 | GNUNET_HELLO_iterate_addresses (hello, | 565 | GNUNET_HELLO_iterate_addresses (hello, |
424 | GNUNET_NO, | 566 | GNUNET_NO, |
425 | &address_iteration, | 567 | &print_address, |
426 | handle); | 568 | pc); |
427 | GNUNET_SCHEDULER_add_now(&create_array,handle); | ||
428 | } | 569 | } |
429 | 570 | ||
430 | /** | 571 | /** |
@@ -444,6 +585,7 @@ peerinfo_get (struct GNUNET_REST_RequestHandle *con_handle, | |||
444 | const struct GNUNET_PeerIdentity *specific_peer; | 585 | const struct GNUNET_PeerIdentity *specific_peer; |
445 | GNUNET_PEER_Id peer_id; | 586 | GNUNET_PEER_Id peer_id; |
446 | int include_friend_only; | 587 | int include_friend_only; |
588 | char* include_friend_only_str; | ||
447 | 589 | ||
448 | include_friend_only = GNUNET_NO; | 590 | include_friend_only = GNUNET_NO; |
449 | GNUNET_CRYPTO_hash (GNUNET_REST_API_PEERINFO_FRIEND, | 591 | GNUNET_CRYPTO_hash (GNUNET_REST_API_PEERINFO_FRIEND, |
@@ -453,12 +595,12 @@ peerinfo_get (struct GNUNET_REST_RequestHandle *con_handle, | |||
453 | == GNUNET_CONTAINER_multihashmap_contains (con_handle->url_param_map, | 595 | == GNUNET_CONTAINER_multihashmap_contains (con_handle->url_param_map, |
454 | &key)) | 596 | &key)) |
455 | { | 597 | { |
456 | include_friend_only = *(int*)GNUNET_CONTAINER_multihashmap_get ( | 598 | include_friend_only_str = GNUNET_CONTAINER_multihashmap_get ( |
457 | con_handle->url_param_map, &key); | 599 | con_handle->url_param_map, &key); |
458 | } | 600 | if (0 == strcmp(include_friend_only_str, "yes")) |
459 | if(GNUNET_YES != include_friend_only) | 601 | { |
460 | { | 602 | include_friend_only = GNUNET_YES; |
461 | include_friend_only = GNUNET_NO; | 603 | } |
462 | } | 604 | } |
463 | 605 | ||
464 | specific_peer = NULL; | 606 | specific_peer = NULL; |
@@ -473,12 +615,6 @@ peerinfo_get (struct GNUNET_REST_RequestHandle *con_handle, | |||
473 | specific_peer = GNUNET_PEER_resolve2(peer_id); | 615 | specific_peer = GNUNET_PEER_resolve2(peer_id); |
474 | } | 616 | } |
475 | 617 | ||
476 | |||
477 | //TODO friend_only and special peer | ||
478 | |||
479 | //TODO add behaviour and response | ||
480 | //TODO maybe notify better than iteration | ||
481 | |||
482 | handle->list_it = GNUNET_PEERINFO_iterate(handle->peerinfo_handle, | 618 | handle->list_it = GNUNET_PEERINFO_iterate(handle->peerinfo_handle, |
483 | include_friend_only, | 619 | include_friend_only, |
484 | specific_peer, | 620 | specific_peer, |
@@ -560,7 +696,7 @@ rest_process_request(struct GNUNET_REST_RequestHandle *rest_handle, | |||
560 | struct RequestHandle *handle = GNUNET_new (struct RequestHandle); | 696 | struct RequestHandle *handle = GNUNET_new (struct RequestHandle); |
561 | 697 | ||
562 | handle->response_code = 0; | 698 | handle->response_code = 0; |
563 | handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL; | 699 | handle->timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60); |
564 | handle->proc_cls = proc_cls; | 700 | handle->proc_cls = proc_cls; |
565 | handle->proc = proc; | 701 | handle->proc = proc; |
566 | handle->rest_handle = rest_handle; | 702 | handle->rest_handle = rest_handle; |