More on scansets.
[yaz-moved-to-github.git] / zoom / zoom-c.c
1 /*
2  * $Id: zoom-c.c,v 1.16 2002-01-02 10:30:25 adam Exp $
3  *
4  * ZOOM layer for C, connections, result sets, queries.
5  */
6 #include <assert.h>
7 #include <yaz/xmalloc.h>
8 #include <yaz/otherinfo.h>
9 #include <yaz/log.h>
10 #include <yaz/pquery.h>
11 #include <yaz/diagbib1.h>
12
13 #include "zoom-p.h"
14
15 #if HAVE_SYS_POLL_H
16 #include <sys/poll.h>
17 #endif
18
19 static ZOOM_Event ZOOM_Event_create (int kind)
20 {
21     ZOOM_Event event = xmalloc (sizeof(*event));
22     event->kind = kind;
23     event->next = 0;
24     event->prev = 0;
25     return event;
26 }
27
28 static void ZOOM_Event_destroy (ZOOM_Event event)
29 {
30     xfree (event);
31 }
32
33 static void ZOOM_connection_put_event (ZOOM_connection c, ZOOM_Event event)
34 {
35     // put in back of queue
36     if (c->m_queue_back)
37     {
38         c->m_queue_back->prev = event;
39         assert (c->m_queue_front);
40     }
41     else
42     {
43         assert (!c->m_queue_front);
44         c->m_queue_front = event;
45     }
46     event->next = c->m_queue_back;
47     event->prev = 0;
48     c->m_queue_back = event;
49 }
50
51 static ZOOM_Event ZOOM_connection_get_event(ZOOM_connection c)
52 {
53     // get from front of queue
54     ZOOM_Event event = c->m_queue_front;
55     if (!event)
56         return 0;
57     assert (c->m_queue_back);
58     c->m_queue_front = event->prev;
59     if (c->m_queue_front)
60     {
61         assert (c->m_queue_back);
62         c->m_queue_front->next = 0;
63     }
64     else
65         c->m_queue_back = 0;
66     c->last_event = event->kind;
67     return event;
68 }
69
70 static void clear_error (ZOOM_connection c)
71 {
72     c->error = ZOOM_ERROR_NONE;
73     xfree (c->addinfo);
74     c->addinfo = 0;
75 }
76
77 ZOOM_task ZOOM_connection_add_task (ZOOM_connection c, int which)
78 {
79     ZOOM_task *taskp = &c->tasks;
80     while (*taskp)
81         taskp = &(*taskp)->next;
82     *taskp = xmalloc (sizeof(**taskp));
83     (*taskp)->running = 0;
84     (*taskp)->which = which;
85     (*taskp)->next = 0;
86     clear_error (c);
87     return *taskp;
88 }
89
90 void ZOOM_connection_remove_task (ZOOM_connection c)
91 {
92     ZOOM_task task = c->tasks;
93
94     if (task)
95     {
96         c->tasks = task->next;
97         switch (task->which)
98         {
99         case ZOOM_TASK_SEARCH:
100             ZOOM_resultset_destroy (task->u.search.resultset);
101             break;
102         case ZOOM_TASK_RETRIEVE:
103             ZOOM_resultset_destroy (task->u.retrieve.resultset);
104             break;
105         case ZOOM_TASK_CONNECT:
106             break;
107         case ZOOM_TASK_SCAN:
108             ZOOM_scanset_destroy (task->u.scan.scan);
109             break;
110         default:
111             assert (0);
112         }
113         xfree (task);
114     }
115 }
116
117 void ZOOM_connection_remove_tasks (ZOOM_connection c)
118 {
119     while (c->tasks)
120         ZOOM_connection_remove_task(c);
121 }
122
123 static ZOOM_record record_cache_lookup (ZOOM_resultset r,
124                                          int pos,
125                                          const char *elementSetName);
126
127 ZOOM_connection ZOOM_connection_create (ZOOM_options options)
128 {
129     ZOOM_connection c = xmalloc (sizeof(*c));
130
131     c->cs = 0;
132     c->mask = 0;
133     c->state = STATE_IDLE;
134     c->error = ZOOM_ERROR_NONE;
135     c->addinfo = 0;
136     c->buf_in = 0;
137     c->len_in = 0;
138     c->buf_out = 0;
139     c->len_out = 0;
140     c->resultsets = 0;
141
142     c->options = ZOOM_options_create_with_parent(options);
143
144     c->host_port = 0;
145     c->proxy = 0;
146
147     c->cookie_out = 0;
148     c->cookie_in = 0;
149     c->tasks = 0;
150
151     c->odr_in = odr_createmem (ODR_DECODE);
152     c->odr_out = odr_createmem (ODR_ENCODE);
153
154     c->async = 0;
155     c->support_named_resultsets = 0;
156     c->last_event = ZOOM_EVENT_NONE;
157
158     c->m_queue_front = 0;
159     c->m_queue_back = 0;
160     return c;
161 }
162
163 /* set database names. Take local databases (if set); otherwise
164    take databases given in ZURL (if set); otherwise use Default */
165 static char **set_DatabaseNames (ZOOM_connection con, ZOOM_options options,
166                                  int *num)
167 {
168     char **databaseNames;
169     const char *c;
170     int no = 2;
171     const char *cp = ZOOM_options_get (options, "databaseName");
172     
173     if (!cp || !*cp)
174     {
175         cp = strchr (con->host_port, '/');
176         if (cp)
177             cp++;
178         }
179     if (cp)
180     {
181         c = cp;
182         while ((c = strchr(c, '+')))
183         {
184             c++;
185             no++;
186         }
187     }
188     else
189         cp = "Default";
190     databaseNames = odr_malloc (con->odr_out, no * sizeof(*databaseNames));
191     no = 0;
192     while (*cp)
193     {
194         c = strchr (cp, '+');
195         if (!c)
196             c = cp + strlen(cp);
197         else if (c == cp)
198         {
199             cp++;
200             continue;
201         }
202         /* cp ptr to first char of db name, c is char
203            following db name */
204         databaseNames[no] = odr_malloc (con->odr_out, 1+c-cp);
205         memcpy (databaseNames[no], cp, c-cp);
206         databaseNames[no++][c-cp] = '\0';
207         cp = c;
208         if (*cp)
209             cp++;
210     }
211     databaseNames[no] = NULL;
212     *num = no;
213     return databaseNames;
214 }
215
216 ZOOM_connection ZOOM_connection_new (const char *host, int portnum)
217 {
218     ZOOM_connection c = ZOOM_connection_create (0);
219
220     ZOOM_connection_connect (c, host, portnum);
221     return c;
222 }
223
224 void ZOOM_connection_connect(ZOOM_connection c,
225                               const char *host, int portnum)
226 {
227     const char *val;
228     ZOOM_task task;
229
230     val = ZOOM_options_get (c->options, "proxy");
231     if (val && *val)
232         c->proxy = xstrdup (val);
233     else
234         c->proxy = 0;
235
236     if (portnum)
237     {
238         char hostn[128];
239         sprintf (hostn, "%.80s:%d", host, portnum);
240         c->host_port = xstrdup(hostn);
241     }
242     else
243         c->host_port = xstrdup(host);
244
245     ZOOM_options_set(c->options, "host", c->host_port);
246
247     c->async = ZOOM_options_get_bool (c->options, "async", 0);
248     
249     task = ZOOM_connection_add_task (c, ZOOM_TASK_CONNECT);
250
251     if (!c->async)
252     {
253         while (ZOOM_event (1, &c))
254             ;
255     }
256 }
257
258 ZOOM_query ZOOM_query_create(void)
259 {
260     ZOOM_query s = xmalloc (sizeof(*s));
261
262     s->refcount = 1;
263     s->query = 0;
264     s->sort_spec = 0;
265     s->odr = odr_createmem (ODR_ENCODE);
266
267     return s;
268 }
269
270 void ZOOM_query_destroy(ZOOM_query s)
271 {
272     if (!s)
273         return;
274
275     (s->refcount)--;
276     yaz_log (LOG_DEBUG, "ZOOM_query_destroy count=%d", s->refcount);
277     if (s->refcount == 0)
278     {
279         odr_destroy (s->odr);
280         xfree (s);
281     }
282 }
283
284 int ZOOM_query_prefix(ZOOM_query s, const char *str)
285 {
286     s->query = odr_malloc (s->odr, sizeof(*s->query));
287     s->query->which = Z_Query_type_1;
288     s->query->u.type_1 =  p_query_rpn(s->odr, PROTO_Z3950, str);
289     if (!s->query->u.type_1)
290         return -1;
291     return 0;
292 }
293
294 int ZOOM_query_sortby(ZOOM_query s, const char *criteria)
295 {
296     s->sort_spec = yaz_sort_spec (s->odr, criteria);
297     if (!s->sort_spec)
298         return -1;
299     return 0;
300 }
301
302 static int do_write(ZOOM_connection c);
303
304 void ZOOM_connection_destroy(ZOOM_connection c)
305 {
306     ZOOM_resultset r;
307     if (!c)
308         return;
309     if (c->cs)
310         cs_close (c->cs);
311     for (r = c->resultsets; r; r = r->next)
312         r->connection = 0;
313
314     xfree (c->buf_in);
315     xfree (c->addinfo);
316     odr_destroy (c->odr_in);
317     odr_destroy (c->odr_out);
318     ZOOM_options_destroy (c->options);
319     ZOOM_connection_remove_tasks (c);
320     xfree (c->host_port);
321     xfree (c);
322 }
323
324 void ZOOM_resultset_addref (ZOOM_resultset r)
325 {
326     if (r)
327         (r->refcount)++;
328 }
329 ZOOM_resultset ZOOM_resultset_create ()
330 {
331     ZOOM_resultset r = xmalloc (sizeof(*r));
332
333     r->refcount = 1;
334     r->size = 0;
335     r->odr = odr_createmem (ODR_ENCODE);
336     r->start = 0;
337     r->piggyback = 1;
338     r->setname = 0;
339     r->count = 0;
340     r->record_cache = 0;
341     r->r_sort_spec = 0;
342     r->r_query = 0;
343     r->search = 0;
344     r->connection = 0;
345     r->next = 0;
346     return r;
347 }
348
349 ZOOM_resultset ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
350 {
351     ZOOM_resultset r;
352     ZOOM_query s = ZOOM_query_create();
353
354     ZOOM_query_prefix (s, q);
355
356     r = ZOOM_connection_search (c, s);
357     ZOOM_query_destroy (s);
358     return r;
359 }
360
361 ZOOM_resultset ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
362 {
363     ZOOM_resultset r = ZOOM_resultset_create ();
364     ZOOM_task task;
365     const char *cp;
366
367     r->r_sort_spec = q->sort_spec;
368     r->r_query = q->query;
369     r->search = q;
370
371     r->options = ZOOM_options_create_with_parent(c->options);
372
373     r->start = ZOOM_options_get_int(r->options, "start", 0);
374     r->count = ZOOM_options_get_int(r->options, "count", 0);
375     r->piggyback = ZOOM_options_get_bool (r->options, "piggyback", 1);
376     cp = ZOOM_options_get (r->options, "setname");
377     if (cp)
378         r->setname = xstrdup (cp);
379     
380     r->connection = c;
381
382     r->next = c->resultsets;
383     c->resultsets = r;
384
385     task = ZOOM_connection_add_task (c, ZOOM_TASK_SEARCH);
386     task->u.search.resultset = r;
387     ZOOM_resultset_addref (r);  
388
389     (q->refcount)++;
390
391     if (!c->async)
392     {
393         while (ZOOM_event (1, &c))
394             ;
395     }
396     return r;
397 }
398
399 void ZOOM_resultset_destroy(ZOOM_resultset r)
400 {
401     if (!r)
402         return;
403     (r->refcount)--;
404     yaz_log (LOG_DEBUG, "destroy r = %p count=%d", r, r->refcount);
405     if (r->refcount == 0)
406     {
407         if (r->connection)
408         {
409             /* remove ourselves from the resultsets in connection */
410             ZOOM_resultset *rp = &r->connection->resultsets;
411             while (1)
412             {
413                 assert (*rp);   /* we must be in this list!! */
414                 if (*rp == r)
415                 {   /* OK, we're here - take us out of it */
416                     *rp = (*rp)->next;
417                     break;
418                 }
419                 rp = &(*rp)->next;
420             }
421         }
422         ZOOM_query_destroy (r->search);
423         ZOOM_options_destroy (r->options);
424         odr_destroy (r->odr);
425         xfree (r->setname);
426         xfree (r);
427     }
428 }
429
430 size_t ZOOM_resultset_size (ZOOM_resultset r)
431 {
432     return r->size;
433 }
434
435 static void do_close (ZOOM_connection c)
436 {
437     if (c->cs)
438         cs_close(c->cs);
439     c->cs = 0;
440     c->mask = 0;
441     c->state = STATE_IDLE;
442 }
443
444 static void ZOOM_resultset_retrieve (ZOOM_resultset r,
445                                       int force_sync, int start, int count)
446 {
447     ZOOM_task task;
448     ZOOM_connection c;
449     const char *cp;
450
451     if (!r)
452         return;
453     c = r->connection;
454     if (!c)
455         return;
456     task = ZOOM_connection_add_task (c, ZOOM_TASK_RETRIEVE);
457     task->u.retrieve.resultset = r;
458     task->u.retrieve.start = start;
459     task->u.retrieve.count = count;
460
461     ZOOM_resultset_addref (r);
462
463     if (!r->connection->async || force_sync)
464         while (r->connection && ZOOM_event (1, &r->connection))
465             ;
466 }
467
468 void ZOOM_resultset_records (ZOOM_resultset r, ZOOM_record *recs,
469                               size_t start, size_t count)
470 {
471     int force_present = 0;
472
473     if (!r)
474         return ;
475     if (count && recs)
476         force_present = 1;
477     ZOOM_resultset_retrieve (r, force_present, start, count);
478     if (force_present)
479     {
480         size_t i;
481         for (i = 0; i< count; i++)
482             recs[i] = ZOOM_resultset_record_immediate (r, i+start);
483     }
484 }
485
486 static int do_connect (ZOOM_connection c)
487 {
488     void *add;
489     const char *effective_host;
490
491     if (c->proxy)
492         effective_host = c->proxy;
493     else
494         effective_host = c->host_port;
495
496     yaz_log (LOG_DEBUG, "do_connect host=%s", effective_host);
497
498     assert (!c->cs);
499     c->cs = cs_create_host (effective_host, 0, &add);
500
501     if (c->cs)
502     {
503         int ret = cs_connect (c->cs, add);
504         yaz_log (LOG_DEBUG, "cs_connect returned %d", ret);
505         if (ret >= 0)
506         {
507             c->state = STATE_CONNECTING; 
508             c->mask = ZOOM_SELECT_READ | ZOOM_SELECT_WRITE | 
509                 ZOOM_SELECT_EXCEPT;
510             return 1;
511         }
512     }
513     c->state = STATE_IDLE;
514     c->error = ZOOM_ERROR_CONNECT;
515     return 0;
516 }
517
518 int z3950_connection_socket(ZOOM_connection c)
519 {
520     if (c->cs)
521         return cs_fileno(c->cs);
522     return -1;
523 }
524
525 int z3950_connection_mask(ZOOM_connection c)
526 {
527     if (c->cs)
528         return c->mask;
529     return 0;
530 }
531
532 static int encode_APDU(ZOOM_connection c, Z_APDU *a, ODR out)
533 {
534     char str[120];
535
536     assert (a);
537     sprintf (str, "send_APDU t=%p type=%d", c, a->which);
538     if (c->cookie_out)
539     {
540         Z_OtherInformation **oi;
541         yaz_oi_APDU(a, &oi);
542         yaz_oi_set_string_oidval(oi, out, VAL_COOKIE, 1, c->cookie_out);
543     }
544     if (!z_APDU(out, &a, 0, 0))
545     {
546         FILE *outf = fopen("/tmp/apdu.txt", "w");
547         if (outf)
548         {
549             ODR odr_pr = odr_createmem(ODR_PRINT);
550             fprintf (outf, "a=%p\n", a);
551             odr_setprint(odr_pr, outf);
552             z_APDU(odr_pr, &a, 0, 0);
553             odr_destroy(odr_pr);
554             fclose (outf);
555         }
556         c->error = ZOOM_ERROR_ENCODE;
557         do_close (c);
558         return -1;
559     }
560     
561     return 0;
562 }
563
564 static int send_APDU (ZOOM_connection c, Z_APDU *a)
565 {
566     ZOOM_Event event;
567     assert (a);
568     if (encode_APDU(c, a, c->odr_out))
569         return -1;
570     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
571     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
572     ZOOM_connection_put_event (c, event);
573     odr_reset(c->odr_out);
574     do_write (c);
575     return 0;   
576 }
577
578 static int ZOOM_connection_send_init (ZOOM_connection c)
579 {
580     const char *impname;
581     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_initRequest);
582     Z_InitRequest *ireq = apdu->u.initRequest;
583     Z_IdAuthentication *auth = odr_malloc(c->odr_out, sizeof(*auth));
584     const char *auth_groupId = ZOOM_options_get (c->options, "group");
585     const char *auth_userId = ZOOM_options_get (c->options, "user");
586     const char *auth_password = ZOOM_options_get (c->options, "pass");
587     
588     ODR_MASK_SET(ireq->options, Z_Options_search);
589     ODR_MASK_SET(ireq->options, Z_Options_present);
590     ODR_MASK_SET(ireq->options, Z_Options_scan);
591     ODR_MASK_SET(ireq->options, Z_Options_sort);
592     ODR_MASK_SET(ireq->options, Z_Options_extendedServices);
593     ODR_MASK_SET(ireq->options, Z_Options_namedResultSets);
594     
595     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_1);
596     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_2);
597     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_3);
598     
599     impname = ZOOM_options_get (c->options, "implementationName");
600     ireq->implementationName =
601         odr_malloc (c->odr_out, 15 + (impname ? strlen(impname) : 0));
602     strcpy (ireq->implementationName, "");
603     if (impname)
604     {
605         strcat (ireq->implementationName, impname);
606         strcat (ireq->implementationName, "/");
607     }                                          
608     strcat (ireq->implementationName, "ZOOM-C/YAZ");
609     
610     *ireq->maximumRecordSize =
611         ZOOM_options_get_int (c->options, "maximumRecordSize", 1024*1024);
612     *ireq->preferredMessageSize =
613         ZOOM_options_get_int (c->options, "preferredMessageSize", 1024*1024);
614     
615     if (auth_groupId || auth_password)
616     {
617         Z_IdPass *pass = odr_malloc(c->odr_out, sizeof(*pass));
618         int i = 0;
619         pass->groupId = 0;
620         if (auth_groupId && *auth_groupId)
621         {
622             pass->groupId = odr_malloc(c->odr_out, strlen(auth_groupId)+1);
623             strcpy(pass->groupId, auth_groupId);
624             i++;
625         }
626         pass->userId = 0;
627         if (auth_userId && *auth_userId)
628         {
629             pass->userId = odr_malloc(c->odr_out, strlen(auth_userId)+1);
630             strcpy(pass->userId, auth_userId);
631             i++;
632         }
633         pass->password = 0;
634         if (auth_password && *auth_password)
635         {
636             pass->password = odr_malloc(c->odr_out, strlen(auth_password)+1);
637             strcpy(pass->password, auth_password);
638             i++;
639         }
640         if (i)
641         {
642             auth->which = Z_IdAuthentication_idPass;
643             auth->u.idPass = pass;
644             ireq->idAuthentication = auth;
645         }
646     }
647     else if (auth_userId)
648     {
649         auth->which = Z_IdAuthentication_open;
650         auth->u.open = odr_malloc(c->odr_out, strlen(auth_userId)+1);
651         strcpy(auth->u.open, auth_userId);
652         ireq->idAuthentication = auth;
653     }
654     if (c->proxy)
655         yaz_oi_set_string_oidval(&ireq->otherInfo, c->odr_out,
656                                  VAL_PROXY, 1, c->host_port);
657     assert (apdu);
658     send_APDU (c, apdu);
659     
660     return 0;
661 }
662
663 static int ZOOM_connection_send_search (ZOOM_connection c)
664 {
665     ZOOM_resultset r;
666     int lslb, ssub, mspn;
667     const char *syntax;
668     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_searchRequest);
669     Z_SearchRequest *search_req = apdu->u.searchRequest;
670     const char *elementSetName;
671     const char *smallSetElementSetName;
672     const char *mediumSetElementSetName;
673     const char *schema;
674
675     assert (c->tasks);
676     assert (c->tasks->which == ZOOM_TASK_SEARCH);
677
678     r = c->tasks->u.search.resultset;
679
680     elementSetName =
681         ZOOM_options_get (r->options, "elementSetName");
682     smallSetElementSetName  =
683         ZOOM_options_get (r->options, "smallSetElementSetName");
684     mediumSetElementSetName =
685         ZOOM_options_get (r->options, "mediumSetElementSetName");
686     schema =
687         ZOOM_options_get (r->options, "schema");
688
689     if (!smallSetElementSetName)
690         smallSetElementSetName = elementSetName;
691
692     if (!mediumSetElementSetName)
693         mediumSetElementSetName = elementSetName;
694
695     assert (r);
696     assert (r->r_query);
697
698     /* prepare query for the search request */
699     search_req->query = r->r_query;
700
701     search_req->databaseNames =
702         set_DatabaseNames (c, r->options, &search_req->num_databaseNames);
703
704     /* get syntax (no need to provide unless piggyback is in effect) */
705     syntax = ZOOM_options_get (r->options, "preferredRecordSyntax");
706
707     lslb = ZOOM_options_get_int (r->options, "largeSetLowerBound", -1);
708     ssub = ZOOM_options_get_int (r->options, "smallSetUpperBound", -1);
709     mspn = ZOOM_options_get_int (r->options, "mediumSetPresentNumber", -1);
710     if (lslb != -1 && ssub != -1 && mspn != -1)
711     {
712         /* So're a Z39.50 expert? Let's hope you don't do sort */
713         *search_req->largeSetLowerBound = lslb;
714         *search_req->smallSetUpperBound = ssub;
715         *search_req->mediumSetPresentNumber = mspn;
716     }
717     else if (r->start == 0 && r->count > 0
718              && r->piggyback && !r->r_sort_spec && !schema)
719     {
720         /* Regular piggyback - do it unless we're going to do sort */
721         *search_req->largeSetLowerBound = 2000000000;
722         *search_req->smallSetUpperBound = r->count;
723         *search_req->mediumSetPresentNumber = r->count;
724         smallSetElementSetName = 0;  /* no need to provide this */
725     }
726     else
727     {
728         /* non-piggyback. Need not provide elementsets or syntaxes .. */
729         smallSetElementSetName = 0;
730         mediumSetElementSetName = 0;
731         syntax = 0;
732     }
733     if (smallSetElementSetName && *smallSetElementSetName)
734     {
735         Z_ElementSetNames *esn = odr_malloc (c->odr_out, sizeof(*esn));
736         
737         esn->which = Z_ElementSetNames_generic;
738         esn->u.generic = odr_strdup (c->odr_out, smallSetElementSetName);
739         search_req->smallSetElementSetNames = esn;
740     }
741     if (mediumSetElementSetName && *mediumSetElementSetName)
742     {
743         Z_ElementSetNames *esn = odr_malloc (c->odr_out, sizeof(*esn));
744         
745         esn->which = Z_ElementSetNames_generic;
746         esn->u.generic = odr_strdup (c->odr_out, mediumSetElementSetName);
747         search_req->mediumSetElementSetNames = esn;
748     }
749     if (syntax)
750         search_req->preferredRecordSyntax =
751             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
752     
753     if (!r->setname)
754     {
755         if (c->support_named_resultsets)
756         {
757             char setname[14];
758             int ord;
759             /* find the lowest unused ordinal so that we re-use
760                result sets on the server. */
761             for (ord = 1; ; ord++)
762             {
763                 ZOOM_resultset rp;
764                 sprintf (setname, "%d", ord);
765                 for (rp = c->resultsets; rp; rp = rp->next)
766                     if (rp->setname && !strcmp (rp->setname, setname))
767                         break;
768                 if (!rp)
769                     break;
770             }
771             r->setname = xstrdup (setname);
772             yaz_log (LOG_DEBUG, "allocating %s", r->setname);
773         }
774         else
775             r->setname = xstrdup ("default");
776         ZOOM_options_set (r->options, "setname", r->setname);
777     }
778     search_req->resultSetName = odr_strdup(c->odr_out, r->setname);
779     /* send search request */
780     send_APDU (c, apdu);
781     r->r_query = 0;
782     return 1;
783 }
784
785 static void response_diag (ZOOM_connection c, Z_DiagRec *p)
786 {
787     Z_DefaultDiagFormat *r;
788     char *addinfo = 0;
789     
790     xfree (c->addinfo);
791     c->addinfo = 0;
792     if (p->which != Z_DiagRec_defaultFormat)
793     {
794         c->error = ZOOM_ERROR_DECODE;
795         return;
796     }
797     r = p->u.defaultFormat;
798     switch (r->which)
799     {
800     case Z_DefaultDiagFormat_v2Addinfo:
801         addinfo = r->u.v2Addinfo;
802         break;
803     case Z_DefaultDiagFormat_v3Addinfo:
804         addinfo = r->u.v3Addinfo;
805         break;
806     }
807     if (addinfo)
808         c->addinfo = xstrdup (addinfo);
809     c->error = *r->condition;
810 }
811
812 ZOOM_record ZOOM_record_clone (ZOOM_record srec)
813 {
814     char *buf;
815     int size;
816     ODR odr_enc;
817     ZOOM_record nrec;
818
819     odr_enc = odr_createmem(ODR_ENCODE);
820     if (!z_NamePlusRecord (odr_enc, &srec->npr, 0, 0))
821         return 0;
822     buf = odr_getbuf (odr_enc, &size, 0);
823     
824     nrec = xmalloc (sizeof(*nrec));
825     nrec->odr = odr_createmem(ODR_DECODE);
826     nrec->wrbuf_marc = 0;
827     odr_setbuf (nrec->odr, buf, size, 0);
828     z_NamePlusRecord (nrec->odr, &nrec->npr, 0, 0);
829     
830     odr_destroy (odr_enc);
831     return nrec;
832 }
833
834 ZOOM_record ZOOM_resultset_record_immediate (ZOOM_resultset s,size_t pos)
835 {
836     return record_cache_lookup (s, pos, 0);
837 }
838
839 ZOOM_record ZOOM_resultset_record (ZOOM_resultset r, size_t pos)
840 {
841     ZOOM_resultset_retrieve (r, 1, pos, 1);
842     return ZOOM_resultset_record_immediate (r, pos);
843 }
844
845 void ZOOM_record_destroy (ZOOM_record rec)
846 {
847     if (!rec)
848         return;
849     if (rec->wrbuf_marc)
850         wrbuf_free (rec->wrbuf_marc, 1);
851     odr_destroy (rec->odr);
852     xfree (rec);
853 }
854
855 void *ZOOM_record_get (ZOOM_record rec, const char *type, size_t *len)
856 {
857     Z_NamePlusRecord *npr;
858     if (!rec)
859         return 0;
860     npr = rec->npr;
861     if (!npr)
862         return 0;
863     if (!strcmp (type, "database"))
864     {
865         return npr->databaseName;
866     }
867     else if (!strcmp (type, "syntax"))
868     {
869         if (npr->which == Z_NamePlusRecord_databaseRecord)
870         {
871             Z_External *r = (Z_External *) npr->u.databaseRecord;
872             oident *ent = oid_getentbyoid(r->direct_reference);
873             if (ent)
874                 return ent->desc;
875         }
876         return "none";
877     }
878     else if (!strcmp (type, "render"))
879     {
880         if (npr->which == Z_NamePlusRecord_databaseRecord)
881         {
882             Z_External *r = (Z_External *) npr->u.databaseRecord;
883             oident *ent = oid_getentbyoid(r->direct_reference);
884             
885             if (r->which == Z_External_sutrs)
886             {
887                 *len = r->u.sutrs->len;
888                 return r->u.sutrs->buf;
889             }
890             else if (r->which == Z_External_octet)
891             {
892                 switch (ent->value)
893                 {
894                 case VAL_SOIF:
895                 case VAL_HTML:
896                 case VAL_SUTRS:
897                     break;
898                 case VAL_TEXT_XML:
899                 case VAL_APPLICATION_XML:
900                     break;
901                 default:
902                     if (!rec->wrbuf_marc)
903                         rec->wrbuf_marc = wrbuf_alloc();
904                     if (marc_display_wrbuf (r->u.octet_aligned->buf,
905                                         rec->wrbuf_marc, 0,
906                                             r->u.octet_aligned->len) > 0)
907                     {
908                         *len = wrbuf_len(rec->wrbuf_marc);
909                         return wrbuf_buf(rec->wrbuf_marc);
910                     }
911                 }
912                 *len = r->u.octet_aligned->len;
913                 return r->u.octet_aligned->buf;
914             }
915             else if (r->which == Z_External_grs1)
916             {
917                 *len = 5;
918                 return "GRS-1";
919             }
920         }
921         return 0;
922     }
923     else if (!strcmp (type, "raw"))
924     {
925         if (npr->which == Z_NamePlusRecord_databaseRecord)
926         {
927             *len = -1;
928             return (Z_External *) npr->u.databaseRecord;
929         }
930         return 0;
931     }
932     return 0;
933 }
934
935 static void record_cache_add (ZOOM_resultset r,
936                               Z_NamePlusRecord *npr,
937                               int pos,
938                               const char *elementSetName)
939 {
940     ZOOM_record_cache rc;
941
942     for (rc = r->record_cache; rc; rc = rc->next)
943     {
944         if (pos == rc->pos)
945         {
946             if ((!elementSetName && !rc->elementSetName)
947                 || (elementSetName && rc->elementSetName &&
948                     !strcmp (elementSetName, rc->elementSetName)))
949             {
950                 /* not destroying rc->npr (it's handled by nmem )*/
951                 rc->rec.npr = npr;
952                 /* keeping wrbuf_marc too */
953                 return;
954             }
955         }
956
957     }
958     rc = odr_malloc (r->odr, sizeof(*rc));
959     rc->rec.npr = npr; 
960     rc->rec.odr = 0;
961     rc->rec.wrbuf_marc = 0;
962     if (elementSetName)
963         rc->elementSetName = odr_strdup (r->odr, elementSetName);
964     else
965         rc->elementSetName = 0;
966     rc->pos = pos;
967     rc->next = r->record_cache;
968     r->record_cache = rc;
969 }
970
971 static ZOOM_record record_cache_lookup (ZOOM_resultset r,
972                                          int pos,
973                                          const char *elementSetName)
974 {
975     ZOOM_record_cache rc;
976
977     for (rc = r->record_cache; rc; rc = rc->next)
978     {
979         if (pos == rc->pos)
980         {
981             if ((!elementSetName && !rc->elementSetName)
982                 || (elementSetName && rc->elementSetName &&
983                     !strcmp (elementSetName, rc->elementSetName)))
984                 return &rc->rec;
985         }
986     }
987     return 0;
988 }
989                                              
990 static void handle_records (ZOOM_connection c, Z_Records *sr,
991                             int present_phase)
992 {
993     ZOOM_resultset resultset;
994
995     if (!c->tasks)
996         return ;
997     switch (c->tasks->which)
998     {
999     case ZOOM_TASK_SEARCH:
1000         resultset = c->tasks->u.search.resultset;
1001         break;
1002     case ZOOM_TASK_RETRIEVE:
1003         resultset = c->tasks->u.retrieve.resultset;        
1004         break;
1005     default:
1006         return;
1007     }
1008     if (sr && sr->which == Z_Records_NSD)
1009     {
1010         Z_DiagRec dr, *dr_p = &dr;
1011         dr.which = Z_DiagRec_defaultFormat;
1012         dr.u.defaultFormat = sr->u.nonSurrogateDiagnostic;
1013         
1014         response_diag (c, dr_p);
1015     }
1016     else if (sr && sr->which == Z_Records_multipleNSD)
1017     {
1018         if (sr->u.multipleNonSurDiagnostics->num_diagRecs >= 1)
1019             response_diag(c, sr->u.multipleNonSurDiagnostics->diagRecs[0]);
1020         else
1021             c->error = ZOOM_ERROR_DECODE;
1022     }
1023     else 
1024     {
1025         if (resultset->count + resultset->start > resultset->size)
1026             resultset->count = resultset->size - resultset->start;
1027         if (resultset->count < 0)
1028             resultset->count = 0;
1029         if (sr && sr->which == Z_Records_DBOSD)
1030         {
1031             int i;
1032             NMEM nmem = odr_extract_mem (c->odr_in);
1033             Z_NamePlusRecordList *p =
1034                 sr->u.databaseOrSurDiagnostics;
1035             for (i = 0; i<p->num_records; i++)
1036             {
1037                 record_cache_add (resultset, p->records[i],
1038                                   i+ resultset->start, 0);
1039             }
1040             /* transfer our response to search_nmem .. we need it later */
1041             nmem_transfer (resultset->odr->mem, nmem);
1042             nmem_destroy (nmem);
1043             if (present_phase && p->num_records == 0)
1044             {
1045                 /* present response and we didn't get any records! */
1046                 c->error = ZOOM_ERROR_DECODE;
1047             }
1048         }
1049         else if (present_phase)
1050         {
1051             /* present response and we didn't get any records! */
1052             c->error = ZOOM_ERROR_DECODE;
1053         }
1054     }
1055 }
1056
1057 static void handle_present_response (ZOOM_connection c, Z_PresentResponse *pr)
1058 {
1059     handle_records (c, pr->records, 1);
1060 }
1061
1062 static void handle_search_response (ZOOM_connection c, Z_SearchResponse *sr)
1063 {
1064     ZOOM_resultset resultset;
1065
1066     yaz_log (LOG_DEBUG, "got search response");
1067
1068     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
1069         return ;
1070
1071     resultset = c->tasks->u.search.resultset;
1072
1073     resultset->size = *sr->resultCount;
1074     handle_records (c, sr->records, 0);
1075 }
1076
1077 static void sort_response (ZOOM_connection c, Z_SortResponse *res)
1078 {
1079     if (res->diagnostics && res->num_diagnostics > 0)
1080         response_diag (c, res->diagnostics[0]);
1081 }
1082
1083 static int scan_response (ZOOM_connection c, Z_ScanResponse *res)
1084 {
1085     NMEM nmem = odr_extract_mem (c->odr_in);
1086     ZOOM_scanset scan;
1087
1088     if (!c->tasks || c->tasks->which != ZOOM_TASK_SCAN)
1089         return 0;
1090     scan = c->tasks->u.scan.scan;
1091
1092     if (res->entries && res->entries->nonsurrogateDiagnostics)
1093         response_diag(c, res->entries->nonsurrogateDiagnostics[0]);
1094     scan->scan_response = res;
1095     nmem_transfer (scan->odr->mem, nmem);
1096     nmem_destroy (nmem);
1097 }
1098
1099 static int send_sort (ZOOM_connection c)
1100 {
1101     ZOOM_resultset  resultset;
1102
1103     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
1104         return 0;
1105
1106     resultset = c->tasks->u.search.resultset;
1107
1108     if (c->error)
1109     {
1110         resultset->r_sort_spec = 0;
1111         return 0;
1112     }
1113     if (resultset->r_sort_spec)
1114     {
1115         Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_sortRequest);
1116         Z_SortRequest *req = apdu->u.sortRequest;
1117         
1118         req->num_inputResultSetNames = 1;
1119         req->inputResultSetNames = (Z_InternationalString **)
1120             odr_malloc (c->odr_out, sizeof(*req->inputResultSetNames));
1121         req->inputResultSetNames[0] =
1122             odr_strdup (c->odr_out, resultset->setname);
1123         req->sortedResultSetName = odr_strdup (c->odr_out, resultset->setname);
1124         req->sortSequence = resultset->r_sort_spec;
1125         resultset->r_sort_spec = 0;
1126         send_APDU (c, apdu);
1127         return 1;
1128     }
1129     return 0;
1130 }
1131
1132 static int send_present (ZOOM_connection c)
1133 {
1134     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_presentRequest);
1135     Z_PresentRequest *req = apdu->u.presentRequest;
1136     int i = 0;
1137     const char *syntax = 
1138         ZOOM_options_get (c->options, "preferredRecordSyntax");
1139     const char *element =
1140         ZOOM_options_get (c->options, "elementSetName");
1141     const char *schema =
1142         ZOOM_options_get (c->options, "schema");
1143     ZOOM_resultset  resultset;
1144
1145     if (!c->tasks)
1146         return 0;
1147
1148     switch (c->tasks->which)
1149     {
1150     case ZOOM_TASK_SEARCH:
1151         resultset = c->tasks->u.search.resultset;
1152         break;
1153     case ZOOM_TASK_RETRIEVE:
1154         resultset = c->tasks->u.retrieve.resultset;
1155         resultset->start = c->tasks->u.retrieve.start;
1156         resultset->count = c->tasks->u.retrieve.count;
1157
1158         if (resultset->start >= resultset->size)
1159             return 0;
1160         if (resultset->start + resultset->count > resultset->size)
1161             resultset->count = resultset->size - resultset->start;
1162         break;
1163     default:
1164         return 0;
1165     }
1166
1167     if (c->error)                  /* don't continue on error */
1168         return 0;
1169     if (resultset->start < 0)
1170         return 0;
1171     for (i = 0; i<resultset->count; i++)
1172     {
1173         ZOOM_record rec =
1174             record_cache_lookup (resultset, i + resultset->start, 0);
1175         if (!rec)
1176             break;
1177     }
1178     if (i == resultset->count)
1179         return 0;
1180
1181     resultset->start += i;
1182     resultset->count -= i;
1183     *req->resultSetStartPoint = resultset->start + 1;
1184     *req->numberOfRecordsRequested = resultset->count;
1185     assert (*req->numberOfRecordsRequested > 0);
1186
1187     if (syntax && *syntax)
1188         req->preferredRecordSyntax =
1189             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
1190
1191     if (schema && *schema)
1192     {
1193         Z_RecordComposition *compo = odr_malloc (c->odr_out, sizeof(*compo));
1194
1195         req->recordComposition = compo;
1196         compo->which = Z_RecordComp_complex;
1197         compo->u.complex = (Z_CompSpec *)
1198             odr_malloc(c->odr_out, sizeof(*compo->u.complex));
1199         compo->u.complex->selectAlternativeSyntax = (bool_t *) 
1200             odr_malloc(c->odr_out, sizeof(bool_t));
1201         *compo->u.complex->selectAlternativeSyntax = 0;
1202
1203         compo->u.complex->generic = (Z_Specification *)
1204             odr_malloc(c->odr_out, sizeof(*compo->u.complex->generic));
1205
1206         compo->u.complex->generic->schema = (Odr_oid *)
1207             yaz_str_to_z3950oid (c->odr_out, CLASS_SCHEMA, schema);
1208
1209         if (!compo->u.complex->generic->schema)
1210         {
1211             /* OID wasn't a schema! Try record syntax instead. */
1212
1213             compo->u.complex->generic->schema = (Odr_oid *)
1214                 yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, schema);
1215         }
1216         if (element && *element)
1217         {
1218             compo->u.complex->generic->elementSpec = (Z_ElementSpec *)
1219                 odr_malloc(c->odr_out, sizeof(Z_ElementSpec));
1220             compo->u.complex->generic->elementSpec->which =
1221                 Z_ElementSpec_elementSetName;
1222             compo->u.complex->generic->elementSpec->u.elementSetName =
1223                 odr_strdup (c->odr_out, element);
1224         }
1225         else
1226             compo->u.complex->generic->elementSpec = 0;
1227         compo->u.complex->num_dbSpecific = 0;
1228         compo->u.complex->dbSpecific = 0;
1229         compo->u.complex->num_recordSyntax = 0;
1230         compo->u.complex->recordSyntax = 0;
1231     }
1232     else if (element && *element)
1233     {
1234         Z_ElementSetNames *esn = odr_malloc (c->odr_out, sizeof(*esn));
1235         Z_RecordComposition *compo = odr_malloc (c->odr_out, sizeof(*compo));
1236         
1237         esn->which = Z_ElementSetNames_generic;
1238         esn->u.generic = odr_strdup (c->odr_out, element);
1239         compo->which = Z_RecordComp_simple;
1240         compo->u.simple = esn;
1241         req->recordComposition = compo;
1242     }
1243     req->resultSetId = odr_strdup(c->odr_out, resultset->setname);
1244     send_APDU (c, apdu);
1245     return 1;
1246 }
1247
1248 ZOOM_scanset ZOOM_connection_scan (ZOOM_connection c, const char *start)
1249 {
1250     ZOOM_scanset scan = xmalloc (sizeof(*scan));
1251
1252     scan->connection = c;
1253     scan->odr = odr_createmem (ODR_DECODE);
1254     scan->options = ZOOM_options_create_with_parent (c->options);
1255     scan->refcount = 1;
1256     scan->scan_response = 0;
1257
1258     if ((scan->termListAndStartPoint =
1259          p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet,
1260                       start)))
1261     {
1262         ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_SCAN);
1263         task->u.scan.scan = scan;
1264         
1265         (scan->refcount)++;
1266         if (!c->async)
1267         {
1268             while (ZOOM_event (1, &c))
1269                 ;
1270         }
1271     }
1272     return scan;
1273 }
1274
1275 void ZOOM_scanset_destroy (ZOOM_scanset scan)
1276 {
1277     if (!scan)
1278         return;
1279     (scan->refcount)--;
1280     if (scan->refcount == 0)
1281     {
1282         odr_destroy (scan->odr);
1283         
1284         ZOOM_options_destroy (scan->options);
1285         xfree (scan);
1286     }
1287 }
1288
1289 int send_scan (ZOOM_connection c)
1290 {
1291     ZOOM_scanset scan;
1292     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_scanRequest);
1293     Z_ScanRequest *req = apdu->u.scanRequest;
1294     if (!c->tasks)
1295         return 0;
1296     assert (c->tasks->which == ZOOM_TASK_SCAN);
1297     scan = c->tasks->u.scan.scan;
1298
1299     req->termListAndStartPoint = scan->termListAndStartPoint;
1300     req->attributeSet = scan->attributeSet;
1301
1302     *req->numberOfTermsRequested =
1303         ZOOM_options_get_int(scan->options, "number", 10);
1304
1305     req->preferredPositionInResponse =
1306         odr_intdup (c->odr_out,
1307                     ZOOM_options_get_int(scan->options, "position", 1));
1308
1309     req->stepSize =
1310         odr_intdup (c->odr_out,
1311                     ZOOM_options_get_int(scan->options, "stepSize", 0));
1312     
1313     req->databaseNames = set_DatabaseNames (c, scan->options, 
1314                                             &req->num_databaseNames);
1315
1316     send_APDU (c, apdu);
1317
1318     return 1;
1319 }
1320
1321 size_t ZOOM_scanset_size (ZOOM_scanset scan)
1322 {
1323     if (!scan || !scan->scan_response || !scan->scan_response->entries)
1324         return 0;
1325     return scan->scan_response->entries->num_entries;
1326 }
1327
1328 const char *ZOOM_scanset_term (ZOOM_scanset scan, size_t pos,
1329                                int *occ, size_t *len)
1330 {
1331     const char *term = 0;
1332     size_t noent = ZOOM_scanset_size (scan);
1333     Z_ScanResponse *res = scan->scan_response;
1334     
1335     *len = 0;
1336     *occ = 0;
1337     if (pos >= noent)
1338         return 0;
1339     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
1340     {
1341         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
1342         
1343         if (t->term->which == Z_Term_general)
1344         {
1345             term = t->term->u.general->buf;
1346             *len = t->term->u.general->len;
1347         }
1348         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
1349     }
1350     return term;
1351 }
1352
1353 static int ZOOM_connection_exec_task (ZOOM_connection c)
1354 {
1355     ZOOM_task task = c->tasks;
1356
1357     yaz_log (LOG_DEBUG, "ZOOM_connection_exec_task");
1358     if (!task)
1359         return 0;
1360     if (c->error != ZOOM_ERROR_NONE ||
1361         (!c->cs && task->which != ZOOM_TASK_CONNECT))
1362     {
1363         ZOOM_connection_remove_tasks (c);
1364         return 0;
1365     }
1366     yaz_log (LOG_DEBUG, "ZOOM_connection_exec_task type=%d", task->which);
1367     if (task->running)
1368         return 0;
1369     task->running = 1;
1370     switch (task->which)
1371     {
1372     case ZOOM_TASK_SEARCH:
1373         /* see if search hasn't been sent yet. */
1374         if (ZOOM_connection_send_search (c))
1375             return 1;
1376         break;
1377     case ZOOM_TASK_RETRIEVE:
1378         if (send_present (c))
1379             return 1;
1380         break;
1381     case ZOOM_TASK_CONNECT:
1382         if (do_connect(c))
1383             return 1;
1384         break;
1385     case ZOOM_TASK_SCAN:
1386         if (send_scan(c))
1387             return 1;
1388     }
1389     ZOOM_connection_remove_task (c);
1390     return 0;
1391 }
1392
1393 static int send_sort_present (ZOOM_connection c)
1394 {
1395     int r = send_sort (c);
1396     if (!r)
1397         r = send_present (c);
1398     return r;
1399 }
1400
1401 static void handle_apdu (ZOOM_connection c, Z_APDU *apdu)
1402 {
1403     Z_InitResponse *initrs;
1404     
1405     yaz_log (LOG_DEBUG, "hande_apdu type=%d", apdu->which);
1406     c->mask = 0;
1407     switch(apdu->which)
1408     {
1409     case Z_APDU_initResponse:
1410         initrs = apdu->u.initResponse;
1411         if (!*initrs->result)
1412         {
1413             c->error = ZOOM_ERROR_INIT;
1414         }
1415         else
1416         {
1417             char *cookie =
1418                 yaz_oi_get_string_oidval (&apdu->u.initResponse->otherInfo,
1419                                           VAL_COOKIE, 1, 0);
1420             xfree (c->cookie_in);
1421             c->cookie_in = 0;
1422             if (cookie)
1423                 c->cookie_in = xstrdup(cookie);
1424             if (ODR_MASK_GET(initrs->options, Z_Options_namedResultSets) &&
1425                 ODR_MASK_GET(initrs->protocolVersion, Z_ProtocolVersion_3))
1426                 c->support_named_resultsets = 1;
1427             if (c->tasks)
1428             {
1429                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
1430                 ZOOM_connection_remove_task (c);
1431             }
1432             ZOOM_connection_exec_task (c);
1433         }
1434         break;
1435     case Z_APDU_searchResponse:
1436         handle_search_response (c, apdu->u.searchResponse);
1437         if (!send_sort_present (c))
1438             ZOOM_connection_remove_task (c);
1439         break;
1440     case Z_APDU_presentResponse:
1441         handle_present_response (c, apdu->u.presentResponse);
1442         if (!send_present (c))
1443             ZOOM_connection_remove_task (c);
1444         break;
1445     case Z_APDU_sortResponse:
1446         sort_response (c, apdu->u.sortResponse);
1447         if (!send_present (c))
1448             ZOOM_connection_remove_task (c);
1449         break;
1450     case Z_APDU_scanResponse:
1451         scan_response (c, apdu->u.scanResponse);
1452         ZOOM_connection_remove_task (c);
1453     }
1454 }
1455
1456 static int do_read (ZOOM_connection c)
1457 {
1458     int r;
1459     Z_APDU *apdu;
1460     ZOOM_Event event;
1461     
1462     event = ZOOM_Event_create (ZOOM_EVENT_RECV_DATA);
1463     ZOOM_connection_put_event (c, event);
1464     
1465     r = cs_get (c->cs, &c->buf_in, &c->len_in);
1466     if (r == 1)
1467         return 0;
1468     if (r <= 0)
1469     {
1470         c->error= ZOOM_ERROR_CONNECTION_LOST;
1471         do_close (c);
1472     }
1473     else
1474     {
1475         ZOOM_Event event;
1476         odr_reset (c->odr_in);
1477         odr_setbuf (c->odr_in, c->buf_in, r, 0);
1478         event = ZOOM_Event_create (ZOOM_EVENT_RECV_APDU);
1479         ZOOM_connection_put_event (c, event);
1480         if (!z_APDU (c->odr_in, &apdu, 0, 0))
1481         {
1482             c->error = ZOOM_ERROR_DECODE;
1483             do_close (c);
1484         }
1485         else
1486         {
1487             handle_apdu (c, apdu);
1488         }
1489     }
1490     return 1;
1491 }
1492
1493 static int do_write_ex (ZOOM_connection c, char *buf_out, int len_out)
1494 {
1495     int r;
1496     ZOOM_Event event;
1497     
1498     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
1499     ZOOM_connection_put_event (c, event);
1500
1501     if ((r=cs_put (c->cs, buf_out, len_out)) < 0)
1502     {
1503         if (c->state == STATE_CONNECTING)
1504             c->error = ZOOM_ERROR_CONNECT;
1505         else
1506             c->error = ZOOM_ERROR_CONNECTION_LOST;
1507         do_close (c);
1508         return 1;
1509     }
1510     else if (r == 1)
1511     {
1512         c->state = STATE_ESTABLISHED;
1513         c->mask = ZOOM_SELECT_READ|ZOOM_SELECT_WRITE|ZOOM_SELECT_EXCEPT;
1514     }
1515     else
1516     {
1517         c->state = STATE_ESTABLISHED;
1518         c->mask = ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT;
1519     }
1520     return 0;
1521 }
1522
1523 static int do_write(ZOOM_connection c)
1524 {
1525     return do_write_ex (c, c->buf_out, c->len_out);
1526 }
1527
1528
1529 const char *ZOOM_connection_option_get (ZOOM_connection c, const char *key)
1530 {
1531     return ZOOM_options_get (c->options, key);
1532 }
1533
1534 void ZOOM_connection_option_set (ZOOM_connection c, const char *key,
1535                                   const char *val)
1536 {
1537     ZOOM_options_set (c->options, key, val);
1538 }
1539
1540 const char *ZOOM_resultset_option_get (ZOOM_resultset r, const char *key)
1541 {
1542     return ZOOM_options_get (r->options, key);
1543 }
1544
1545 void ZOOM_resultset_option_set (ZOOM_resultset r, const char *key,
1546                                   const char *val)
1547 {
1548     ZOOM_options_set (r->options, key, val);
1549 }
1550
1551
1552 int ZOOM_connection_errcode (ZOOM_connection c)
1553 {
1554     return ZOOM_connection_error (c, 0, 0);
1555 }
1556
1557 const char *ZOOM_connection_errmsg (ZOOM_connection c)
1558 {
1559     const char *msg;
1560     ZOOM_connection_error (c, &msg, 0);
1561     return msg;
1562 }
1563
1564 const char *ZOOM_connection_addinfo (ZOOM_connection c)
1565 {
1566     const char *addinfo;
1567     ZOOM_connection_error (c, 0, &addinfo);
1568     return addinfo;
1569 }
1570
1571 int ZOOM_connection_error (ZOOM_connection c, const char **cp,
1572                             const char **addinfo)
1573 {
1574     int error = c->error;
1575     if (cp)
1576     {
1577         switch (error)
1578         {
1579         case ZOOM_ERROR_NONE:
1580             *cp = "No error"; break;
1581         case ZOOM_ERROR_CONNECT:
1582             *cp = "Connect failed"; break;
1583         case ZOOM_ERROR_MEMORY:
1584             *cp = "Out of memory"; break;
1585         case ZOOM_ERROR_ENCODE:
1586             *cp = "Encoding failed"; break;
1587         case ZOOM_ERROR_DECODE:
1588             *cp = "Decoding failed"; break;
1589         case ZOOM_ERROR_CONNECTION_LOST:
1590             *cp = "Connection lost"; break;
1591         case ZOOM_ERROR_INIT:
1592             *cp = "Init rejected"; break;
1593         case ZOOM_ERROR_INTERNAL:
1594             *cp = "Internal failure"; break;
1595         case ZOOM_ERROR_TIMEOUT:
1596             *cp = "Timeout"; break;
1597         default:
1598             *cp = diagbib1_str (error);
1599         }
1600     }
1601     if (addinfo)
1602     {
1603         if (c->addinfo)
1604             *addinfo = c->addinfo;
1605         else
1606             *addinfo = "";
1607     }
1608     return c->error;
1609 }
1610
1611 int ZOOM_connection_do_io(ZOOM_connection c, int mask)
1612 {
1613     ZOOM_Event event = 0;
1614 #if 0
1615     int r = cs_look(c->cs);
1616     yaz_log (LOG_LOG, "ZOOM_connection_do_io c=%p mask=%d cs_look=%d",
1617              c, mask, r);
1618     
1619     if (r == CS_NONE)
1620     {
1621         event = ZOOM_Event_create (ZOOM_EVENT_IO_CONNECT);
1622         c->error = ZOOM_ERROR_CONNECT;
1623         do_close (c);
1624         ZOOM_connection_put_event (c, event);
1625     }
1626     else if (r == CS_CONNECT)
1627     {
1628         event = ZOOM_Event_create (ZOOM_EVENT_IO_CONNECT);
1629         yaz_log (LOG_LOG, "calling rcvconnect");
1630         if (cs_rcvconnect (c->cs) < 0)
1631         {
1632             c->error = ZOOM_ERROR_CONNECT;
1633             do_close (c);
1634             ZOOM_connection_put_event (c, event);
1635         }
1636         else
1637         {
1638             ZOOM_connection_put_event (c, event);
1639             ZOOM_connection_send_init (c);
1640         }
1641     }
1642     else
1643     {
1644         if (mask & ZOOM_SELECT_READ)
1645             do_read (c);
1646         if (c->cs && (mask & ZOOM_SELECT_WRITE))
1647             do_write (c);
1648     }   
1649 #else
1650     yaz_log (LOG_DEBUG, "ZOOM_connection_do_io c=%p mask=%d", c, mask);
1651     if (c->state == STATE_CONNECTING)
1652     {
1653         event = ZOOM_Event_create (ZOOM_EVENT_CONNECT);
1654         if (mask & ZOOM_SELECT_WRITE)
1655         {
1656             ZOOM_connection_put_event (c, event);
1657             ZOOM_connection_send_init (c);
1658         }
1659         else
1660         {
1661             c->error = ZOOM_ERROR_CONNECT;
1662             do_close (c);
1663             ZOOM_connection_put_event (c, event);
1664         }
1665     }
1666     else if (c->state == STATE_ESTABLISHED)
1667     {
1668         if (mask & ZOOM_SELECT_READ)
1669             do_read (c);
1670         if (c->cs && (mask & ZOOM_SELECT_WRITE))
1671             do_write (c);
1672     }
1673     else
1674     {
1675         event = ZOOM_Event_create (ZOOM_EVENT_UNKNOWN);
1676         ZOOM_connection_put_event (c, event);
1677         c->error = ZOOM_ERROR_INTERNAL;
1678         do_close (c);
1679     }
1680 #endif
1681     return 1;
1682 }
1683
1684 int ZOOM_connection_last_event(ZOOM_connection cs)
1685 {
1686     if (!cs)
1687         return ZOOM_EVENT_NONE;
1688     return cs->last_event;
1689 }
1690
1691 int ZOOM_event (int no, ZOOM_connection *cs)
1692 {
1693 #if HAVE_SYS_POLL_H
1694     struct pollfd pollfds[1024];
1695     ZOOM_connection poll_cs[1024];
1696 #else
1697     struct timeval tv;
1698     fd_set input, output, except;
1699 #endif
1700     int i, r, nfds;
1701     int max_fd = 0;
1702
1703     for (i = 0; i<no; i++)
1704     {
1705         ZOOM_connection c = cs[i];
1706         ZOOM_Event event;
1707         if (c && (event = ZOOM_connection_get_event(c)))
1708         {
1709             ZOOM_Event_destroy (event);
1710             return i+1;
1711         }
1712     }
1713     for (i = 0; i<no; i++)
1714     {
1715         ZOOM_connection c = cs[i];
1716         ZOOM_Event event;
1717         if (c && ZOOM_connection_exec_task (c))
1718         {
1719             if ((event = ZOOM_connection_get_event(c)))
1720             {
1721                 ZOOM_Event_destroy (event);
1722                 return i+1;
1723             }
1724         }
1725     }
1726 #if HAVE_SYS_POLL_H
1727
1728 #else
1729     tv.tv_sec = 15;
1730     tv.tv_usec = 0;
1731     
1732     FD_ZERO (&input);
1733     FD_ZERO (&output);
1734     FD_ZERO (&except);
1735 #endif
1736     nfds = 0;
1737     for (i = 0; i<no; i++)
1738     {
1739         ZOOM_connection c = cs[i];
1740         int fd, mask;
1741         
1742         if (!c)
1743             continue;
1744         fd = z3950_connection_socket(c);
1745         mask = z3950_connection_mask(c);
1746
1747         if (fd == -1)
1748             continue;
1749         if (max_fd < fd)
1750             max_fd = fd;
1751
1752 #if HAVE_SYS_POLL_H
1753         if (mask)
1754         {
1755             short poll_events = 0;
1756
1757             if (mask & ZOOM_SELECT_READ)
1758                 poll_events += POLLIN;
1759             if (mask & ZOOM_SELECT_WRITE)
1760                 poll_events += POLLOUT;
1761             if (mask & ZOOM_SELECT_EXCEPT)
1762                 poll_events += POLLERR;
1763             pollfds[nfds].fd = fd;
1764             pollfds[nfds].events = poll_events;
1765             pollfds[nfds].revents = 0;
1766             poll_cs[nfds] = c;
1767             nfds++;
1768         }
1769 #else
1770         if (mask & ZOOM_SELECT_READ)
1771         {
1772             FD_SET (fd, &input);
1773             nfds++;
1774         }
1775         if (mask & ZOOM_SELECT_WRITE)
1776         {
1777             FD_SET (fd, &output);
1778             nfds++;
1779         }
1780         if (mask & ZOOM_SELECT_EXCEPT)
1781         {
1782             FD_SET (fd, &except);
1783             nfds++;
1784         }
1785 #endif
1786     }
1787     if (!nfds)
1788         return 0;
1789 #if HAVE_SYS_POLL_H
1790     yaz_log (LOG_DEBUG, "poll start");
1791     r = poll (pollfds, nfds, 15000);
1792     yaz_log (LOG_DEBUG, "poll stop, returned r=%d", r);
1793     for (i = 0; i<nfds; i++)
1794     {
1795         ZOOM_connection c = poll_cs[i];
1796         if (r && c->mask)
1797         {
1798             int mask = 0;
1799             if (pollfds[i].revents & POLLIN)
1800                 mask += ZOOM_SELECT_READ;
1801             if (pollfds[i].revents & POLLOUT)
1802                 mask += ZOOM_SELECT_WRITE;
1803             if (pollfds[i].revents & POLLERR)
1804                 mask += ZOOM_SELECT_EXCEPT;
1805             if (mask)
1806                 ZOOM_connection_do_io(c, mask);
1807         }
1808         else if (r == 0 && c->mask)
1809         {
1810             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
1811             /* timeout and this connection was waiting */
1812             c->error = ZOOM_ERROR_TIMEOUT;
1813             do_close (c);
1814             ZOOM_connection_put_event(c, event);
1815         }
1816     }
1817 #else
1818     yaz_log (LOG_DEBUG, "select start");
1819     r = select (max_fd+1, &input, &output, &except, &tv);
1820     yaz_log (LOG_DEBUG, "select stop, returned r=%d", r);
1821     for (i = 0; i<no; i++)
1822     {
1823         ZOOM_connection c = cs[i];
1824         int fd, mask;
1825
1826         if (!c)
1827             continue;
1828         fd = z3950_connection_socket(c);
1829         mask = 0;
1830         if (r && c->mask)
1831         {
1832             /* no timeout and real socket */
1833             if (FD_ISSET(fd, &input))
1834                 mask += ZOOM_SELECT_READ;
1835             if (FD_ISSET(fd, &output))
1836                 mask += ZOOM_SELECT_WRITE;
1837             if (FD_ISSET(fd, &except))
1838                 mask += ZOOM_SELECT_EXCEPT;
1839             if (mask)
1840                 ZOOM_connection_do_io(c, mask);
1841         }
1842         if (r == 0 && c->mask)
1843         {
1844             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_IO_TIMEOUT);
1845             /* timeout and this connection was waiting */
1846             c->error = ZOOM_ERROR_TIMEOUT;
1847             do_close (c);
1848             yaz_log (LOG_DEBUG, "timeout");
1849             ZOOM_connection_put_event(c, event);
1850         }
1851     }
1852 #endif
1853     for (i = 0; i<no; i++)
1854     {
1855         ZOOM_connection c = cs[i];
1856         ZOOM_Event event;
1857         if (c && (event = ZOOM_connection_get_event(c)))
1858         {
1859             ZOOM_Event_destroy (event);
1860             return i+1;
1861         }
1862     }
1863     return 0;
1864 }