Added state-handle and some support for asynchronous activities.
[yaz-moved-to-github.git] / server / seshigh.c
1 /*
2  * Copyright (C) 1994, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: seshigh.c,v $
7  * Revision 1.13  1995-03-30 09:09:24  quinn
8  * Added state-handle and some support for asynchronous activities.
9  *
10  * Revision 1.12  1995/03/29  15:40:16  quinn
11  * Ongoing work. Statserv is now dynamic by default
12  *
13  * Revision 1.11  1995/03/28  09:16:21  quinn
14  * Added record packing to the search request
15  *
16  * Revision 1.10  1995/03/27  08:34:24  quinn
17  * Added dynamic server functionality.
18  * Released bindings to session.c (is now redundant)
19  *
20  * Revision 1.9  1995/03/22  15:01:26  quinn
21  * Adjusting record packing.
22  *
23  * Revision 1.8  1995/03/22  10:13:21  quinn
24  * Working on record packer
25  *
26  * Revision 1.7  1995/03/21  15:53:31  quinn
27  * Little changes.
28  *
29  * Revision 1.6  1995/03/21  12:30:09  quinn
30  * Beginning to add support for record packing.
31  *
32  * Revision 1.5  1995/03/17  10:44:13  quinn
33  * Added catch of null-string in makediagrec
34  *
35  * Revision 1.4  1995/03/17  10:18:08  quinn
36  * Added memory management.
37  *
38  * Revision 1.3  1995/03/16  17:42:39  quinn
39  * Little changes
40  *
41  * Revision 1.2  1995/03/16  13:29:01  quinn
42  * Partitioned server.
43  *
44  * Revision 1.1  1995/03/15  16:02:10  quinn
45  * Modded session.c to seshigh.c
46  *
47  */
48
49 #include <stdlib.h>
50 #include <assert.h>
51
52 #include <comstack.h>
53 #include <eventl.h>
54 #include <session.h>
55 #include <proto.h>
56 #include <oid.h>
57
58 #include <backend.h>
59
60 #include <iso2709.h>
61
62 #define ENCODE_BUFFER_SIZE 10000
63
64 static int process_apdu(IOCHAN chan);
65 static int process_initRequest(IOCHAN client, Z_InitRequest *req);
66 static int process_searchRequest(IOCHAN client, Z_SearchRequest *req);
67 static int process_presentRequest(IOCHAN client, Z_PresentRequest *req);
68
69 association *create_association(IOCHAN channel, COMSTACK link)
70 {
71     association *new;
72
73     if (!(new = malloc(sizeof(*new))))
74         return 0;
75     new->client_chan = channel;
76     new->client_link = link;
77     if (!(new->decode = odr_createmem(ODR_DECODE)) ||
78         !(new->encode = odr_createmem(ODR_ENCODE)))
79         return 0;
80     if (!(new->encode_buffer = malloc(ENCODE_BUFFER_SIZE)))
81         return 0;
82     odr_setbuf(new->encode, new->encode_buffer, ENCODE_BUFFER_SIZE);
83     new->state = ASSOC_UNINIT;
84     new->input_buffer = 0;
85     new->input_buffer_len = 0;
86     if (cs_getproto(link) == CS_Z3950)
87         new->proto = PROTO_Z3950;
88     else
89         new->proto = PROTO_SR;
90     return new;
91 }
92
93 void destroy_association(association *h)
94 {
95     odr_destroy(h->decode);
96     odr_destroy(h->encode);
97     free(h->encode_buffer);
98     if (h->input_buffer)
99         free(h->input_buffer);
100     if (h->backend)
101         bend_close(h->backend);
102     free(h);
103 }
104
105 void ir_session(IOCHAN h, int event)
106 {
107     int res;
108     association *assoc = iochan_getdata(h);
109     COMSTACK conn = assoc->client_link;
110
111     if (event == EVENT_INPUT)
112     {
113         assert(assoc && conn);
114         res = cs_get(conn, &assoc->input_buffer, &assoc->input_buffer_len);
115         switch (res)
116         {
117             case 0: case -1: /* connection closed by peer */
118                 fprintf(stderr, "Closed connection\n");
119                 cs_close(conn);
120                 destroy_association(assoc);
121                 iochan_destroy(h);
122                 return;
123             case 1:  /* incomplete read */
124                 return;
125             default: /* data! */
126                 assoc->input_apdu_len = res;
127                 if (process_apdu(h) < 0)
128                 {
129                     fprintf(stderr, "Operation failed\n");
130                     cs_close(conn);
131                     destroy_association(assoc);
132                     iochan_destroy(h);
133                 }
134                 else if (cs_more(conn)) /* arrange to be called again */
135                     iochan_setevent(h, EVENT_INPUT);
136         }
137     }
138     else if (event == EVENT_OUTPUT)
139     {
140         switch (res = cs_put(conn, assoc->encode_buffer, assoc->encoded_len))
141         {
142             case -1:
143                 fprintf(stderr, "Closed connection\n");
144                 cs_close(conn);
145                 destroy_association(assoc);
146                 iochan_destroy(h);
147             case 0: /* all sent */
148                 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset */
149                 break;
150             case 1: /* partial send */
151                 break; /* we'll get called again */
152         }
153     }
154     else if (event == EVENT_EXCEPT)
155     {
156         fprintf(stderr, "Exception on line\n");
157         cs_close(conn);
158         destroy_association(assoc);
159         iochan_destroy(h);
160     }
161 }
162
163 static int process_apdu(IOCHAN chan)
164 {
165     Z_APDU *apdu;
166     int res;
167     association *assoc = iochan_getdata(chan);
168
169     odr_setbuf(assoc->decode, assoc->input_buffer, assoc->input_apdu_len);
170     if (!z_APDU(assoc->decode, &apdu, 0))
171     {
172         odr_perror(assoc->decode, "Incoming APDU");
173         return -1;
174     }
175     switch (apdu->which)
176     {
177         case Z_APDU_initRequest:
178             res = process_initRequest(chan, apdu->u.initRequest); break;
179         case Z_APDU_searchRequest:
180             res = process_searchRequest(chan, apdu->u.searchRequest); break;
181         case Z_APDU_presentRequest:
182             res = process_presentRequest(chan, apdu->u.presentRequest); break;
183         default:
184             fprintf(stderr, "Bad APDU\n");
185             return -1;
186     }
187     odr_reset(assoc->decode); /* release incopming APDU */
188     odr_reset(assoc->encode); /* release stuff alloced before encoding */
189     return res;
190 }
191
192 static int process_initRequest(IOCHAN client, Z_InitRequest *req)
193 {
194     Z_APDU apdu, *apdup;
195     Z_InitResponse resp;
196     bool_t result = 1;
197     association *assoc = iochan_getdata(client);
198     bend_initrequest binitreq;
199     bend_initresult *binitres;
200     Odr_bitmask options, protocolVersion;
201
202     fprintf(stderr, "Got initRequest.\n");
203     if (req->implementationId)
204         fprintf(stderr, "Id:        %s\n", req->implementationId);
205     if (req->implementationName)
206         fprintf(stderr, "Name:      %s\n", req->implementationName);
207     if (req->implementationVersion)
208         fprintf(stderr, "Version:   %s\n", req->implementationVersion);
209
210     binitreq.configname = "default-config";
211     if (!(binitres = bend_init(&binitreq)) || binitres->errcode)
212     {
213         fprintf(stderr, "Bad response from backend\n");
214         return -1;
215     }
216
217     assoc->backend = binitres->handle;
218     apdup = &apdu;
219     apdu.which = Z_APDU_initResponse;
220     apdu.u.initResponse = &resp;
221     resp.referenceId = req->referenceId;
222     ODR_MASK_ZERO(&options);
223     if (ODR_MASK_GET(req->options, Z_Options_search))
224         ODR_MASK_SET(&options, Z_Options_search);
225     if (ODR_MASK_GET(req->options, Z_Options_present))
226         ODR_MASK_SET(&options, Z_Options_present);
227     if (ODR_MASK_GET(req->options, Z_Options_delSet))
228         ODR_MASK_SET(&options, Z_Options_delSet);
229     if (ODR_MASK_GET(req->options, Z_Options_namedResultSets))
230         ODR_MASK_SET(&options, Z_Options_namedResultSets);
231     resp.options = &options;
232     ODR_MASK_ZERO(&protocolVersion);
233     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_1))
234         ODR_MASK_SET(&protocolVersion, Z_ProtocolVersion_1);
235     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_2))
236         ODR_MASK_SET(&protocolVersion, Z_ProtocolVersion_2);
237     resp.protocolVersion = &protocolVersion;
238     assoc->maximumRecordSize = *req->maximumRecordSize;
239     /*
240      * This is not so hot. The big todo for ODR is dynamic memory allocation
241      * on encoding.
242      */
243     if (assoc->maximumRecordSize > ENCODE_BUFFER_SIZE - 1000)
244         assoc->maximumRecordSize = ENCODE_BUFFER_SIZE - 1000;
245     assoc->preferredMessageSize = *req->preferredMessageSize;
246     if (assoc->preferredMessageSize > assoc->maximumRecordSize)
247         assoc->preferredMessageSize = assoc->maximumRecordSize;
248     resp.preferredMessageSize = &assoc->preferredMessageSize;
249     resp.maximumRecordSize = &assoc->maximumRecordSize;
250     resp.result = &result;
251     resp.implementationId = "YAZ";
252     resp.implementationName = "Index Data/YAZ Generic Frontend Server";
253     resp.implementationVersion = "$Revision: 1.13 $";
254     resp.userInformationField = 0;
255     if (!z_APDU(assoc->encode, &apdup, 0))
256     {
257         odr_perror(assoc->encode, "Encode initres");
258         return -1;
259     }
260     odr_getbuf(assoc->encode, &assoc->encoded_len);
261     odr_reset(assoc->encode);
262     iochan_setflags(client, EVENT_OUTPUT | EVENT_EXCEPT);
263     return 0;
264 }
265
266 static Z_Records *diagrec(oid_proto proto, int error, char *addinfo)
267 {
268     static Z_Records rec;
269     oident bib1;
270     static Z_DiagRec dr;
271     static int err;
272
273     bib1.proto = proto;
274     bib1.class = CLASS_DIAGSET;
275     bib1.value = VAL_BIB1;
276
277     fprintf(stderr, "Diagnostic: %d -- %s\n", error, addinfo ? addinfo :
278         "NULL");
279     err = error;
280     rec.which = Z_Records_NSD;
281     rec.u.nonSurrogateDiagnostic = &dr;
282     dr.diagnosticSetId = oid_getoidbyent(&bib1);
283     dr.condition = &err;
284     dr.addinfo = addinfo ? addinfo : "";
285     return &rec;
286 }
287
288 static Z_NamePlusRecord *surrogatediagrec(oid_proto proto, char *dbname,
289                                             int error, char *addinfo)
290 {
291     static Z_NamePlusRecord rec;
292     static Z_DiagRec dr;
293     static int err;
294     oident bib1;
295
296     bib1.proto = proto;
297     bib1.class = CLASS_DIAGSET;
298     bib1.value = VAL_BIB1;
299
300     fprintf(stderr, "SurrogateDiagnotic: %d -- %s\n", error, addinfo);
301     err = error;
302     rec.databaseName = dbname;
303     rec.which = Z_NamePlusRecord_surrogateDiagnostic;
304     rec.u.surrogateDiagnostic = &dr;
305     dr.diagnosticSetId = oid_getoidbyent(&bib1);
306     dr.condition = &err;
307     dr.addinfo = addinfo ? addinfo : "";
308     return &rec;
309 }
310
311 #define MAX_RECORDS 256
312
313 static Z_Records *pack_records(association *a, char *setname, int start,
314                                 int *num, Z_ElementSetNames *esn,
315                                 int *next, int *pres)
316 {
317     int recno, total_length = 0, toget = *num;
318     static Z_Records records;
319     static Z_NamePlusRecordList reclist;
320     static Z_NamePlusRecord *list[MAX_RECORDS];
321     oident recform;
322     Odr_oid *oid;
323
324     records.which = Z_Records_DBOSD;
325     records.u.databaseOrSurDiagnostics = &reclist;
326     reclist.num_records = 0;
327     reclist.records = list;
328     *pres = Z_PRES_SUCCESS;
329     *num = 0;
330     *next = 0;
331
332     recform.proto = a->proto;
333     recform.class = CLASS_RECSYN;
334     recform.value = VAL_USMARC;
335     if (!(oid = odr_oiddup(a->encode, oid_getoidbyent(&recform))))
336         return 0;
337
338     fprintf(stderr, "Request to pack %d+%d\n", start, toget);
339     fprintf(stderr, "pms=%d, mrs=%d\n", a->preferredMessageSize,
340         a->maximumRecordSize);
341     for (recno = start; reclist.num_records < toget; recno++)
342     {
343         bend_fetchrequest freq;
344         bend_fetchresult *fres;
345         Z_NamePlusRecord *thisrec;
346         Z_DatabaseRecord *thisext;
347
348         if (reclist.num_records == MAX_RECORDS - 1)
349         {
350             *pres = Z_PRES_PARTIAL_2;
351             break;
352         }
353         freq.setname = setname;
354         freq.number = recno;
355         if (!(fres = bend_fetch(a->backend, &freq, 0)))
356         {
357             *pres = Z_PRES_FAILURE;
358             return diagrec(a->proto, 2, "Backend interface problem");
359         }
360         /* backend should be able to signal whether error is system-wide
361            or only pertaining to current record */
362         if (fres->errcode)
363         {
364             *pres = Z_PRES_FAILURE;
365             return diagrec(a->proto, fres->errcode, fres->errstring);
366         }
367         fprintf(stderr, "  Got record, len=%d, total=%d\n",
368             fres->len, total_length);
369         if (fres->len + total_length > a->preferredMessageSize)
370         {
371             fprintf(stderr, "  In drop-zone\n");
372             /* record is small enough, really */
373             if (fres->len <= a->preferredMessageSize)
374             {
375                 fprintf(stderr, "  Dropped last normal-sized record\n");
376                 *pres = Z_PRES_PARTIAL_2;
377                 break;
378             }
379             /* record can only be fetched by itself */
380             if (fres->len < a->maximumRecordSize)
381             {
382                 fprintf(stderr, "  Record > prefmsgsz\n");
383                 if (toget > 1)
384                 {
385                     fprintf(stderr, "  Dropped it\n");
386                     reclist.records[reclist.num_records] =
387                          surrogatediagrec(a->proto, fres->basename, 16, 0);
388                     reclist.num_records++;
389                     *pres = Z_PRES_PARTIAL_2;
390                     break;
391                 }
392             }
393             else /* too big entirely */
394             {
395                 fprintf(stderr, "Record > maxrcdsz\n");
396                 reclist.records[reclist.num_records] =
397                     surrogatediagrec(a->proto, fres->basename, 17, 0);
398                 reclist.num_records++;
399                 *pres = Z_PRES_PARTIAL_2;
400                 break;
401             }
402         }
403         if (!(thisrec = odr_malloc(a->encode, sizeof(*thisrec))))
404             return 0;
405         if (!(thisrec->databaseName = odr_malloc(a->encode,
406             strlen(fres->basename) + 1)))
407             return 0;
408         strcpy(thisrec->databaseName, fres->basename);
409         thisrec->which = Z_NamePlusRecord_databaseRecord;
410         if (!(thisrec->u.databaseRecord = thisext =  odr_malloc(a->encode,
411             sizeof(Z_DatabaseRecord))))
412             return 0;
413         thisext->direct_reference = oid; /* should be OID for current MARC */
414         thisext->indirect_reference = 0;
415         thisext->descriptor = 0;
416         thisext->which = ODR_EXTERNAL_octet;
417         if (!(thisext->u.octet_aligned = odr_malloc(a->encode,
418             sizeof(Odr_oct))))
419             return 0;
420         if (!(thisext->u.octet_aligned->buf = odr_malloc(a->encode, fres->len)))
421             return 0;
422         memcpy(thisext->u.octet_aligned->buf, fres->record, fres->len);
423         thisext->u.octet_aligned->len = thisext->u.octet_aligned->size =
424             fres->len;
425         reclist.records[reclist.num_records] = thisrec;
426         reclist.num_records++;
427         total_length += fres->len;
428         (*num)++;
429         *next = fres->last_in_set ? 0 : recno + 1;
430     }
431     return &records;
432 }
433
434 static int process_searchRequest(IOCHAN client, Z_SearchRequest *req)
435 {
436     Z_APDU apdu, *apdup;
437     Z_SearchResponse resp;
438     association *assoc = iochan_getdata(client);
439     int nulint = 0;
440     bool_t sr = 1;
441     bend_searchrequest bsrq;
442     bend_searchresult *bsrt;
443     oident *oent;
444     int next = 0;
445
446     fprintf(stderr, "Got SearchRequest.\n");
447     apdup = &apdu;
448     apdu.which = Z_APDU_searchResponse;
449     apdu.u.searchResponse = &resp;
450     resp.referenceId = req->referenceId;
451
452     resp.records = 0;
453     if (req->query->which == Z_Query_type_1)
454     {
455         Z_RPNQuery *q = req->query->u.type_1;
456
457         if (!(oent = oid_getentbyoid(q->attributeSetId)) ||
458             oent->class != CLASS_ATTSET ||
459             oent->value != VAL_BIB1)
460             resp.records = diagrec(assoc->proto, 121, 0);
461     }
462     if (!resp.records)
463     {
464         int toget;
465         Z_ElementSetNames *setnames;
466         int presst = 0;
467
468         bsrq.setname = req->resultSetName;
469         bsrq.replace_set = *req->replaceIndicator;
470         bsrq.num_bases = req->num_databaseNames;
471         bsrq.basenames = req->databaseNames;
472         bsrq.query = req->query;
473
474         if (!(bsrt = bend_search(assoc->backend, &bsrq, 0)))
475             return -1;
476         else if (bsrt->errcode)
477             resp.records = diagrec(assoc->proto, bsrt->errcode,
478                 bsrt->errstring);
479         else
480             resp.records = 0;
481
482         resp.resultCount = &bsrt->hits;
483
484         /* how many records does the user agent want, then? */
485         if (bsrt->hits <= *req->smallSetUpperBound)
486         {
487             toget = bsrt->hits;
488             setnames = req->smallSetElementSetNames;
489         }
490         else if (bsrt->hits < *req->largeSetLowerBound)
491         {
492             toget = *req->mediumSetPresentNumber;
493             setnames = req->mediumSetElementSetNames;
494         }
495         else
496             toget = 0;
497
498         if (toget)
499         {
500             resp.records = pack_records(assoc, req->resultSetName, 1, &toget,
501                 setnames, &next, &presst);
502             if (!resp.records)
503                 return -1;
504             resp.numberOfRecordsReturned = &toget;
505             resp.nextResultSetPosition = &next;
506             resp.searchStatus = &sr;
507             resp.resultSetStatus = &sr;
508             resp.presentStatus = &presst;
509         }
510         else
511         {
512             resp.records = 0;
513             resp.numberOfRecordsReturned = &nulint;
514             resp.nextResultSetPosition = &nulint;
515             resp.searchStatus = &sr;
516             resp.resultSetStatus = &sr;
517             resp.presentStatus = 0;
518         }
519     }
520     else
521     {
522         resp.resultCount = &nulint;
523         resp.numberOfRecordsReturned = &nulint;
524         resp.nextResultSetPosition = &nulint;
525         resp.searchStatus = &nulint;
526         resp.resultSetStatus = 0;
527     }
528
529     if (!z_APDU(assoc->encode, &apdup, 0))
530     {
531         odr_perror(assoc->encode, "Encode searchres");
532         return -1;
533     }
534     odr_getbuf(assoc->encode, &assoc->encoded_len);
535     odr_reset(assoc->encode);
536     iochan_setflags(client, EVENT_OUTPUT | EVENT_EXCEPT);
537     return 0;
538 }
539
540 static int process_presentRequest(IOCHAN client, Z_PresentRequest *req)
541 {
542     Z_APDU apdu, *apdup;
543     Z_PresentResponse resp;
544     association *assoc = iochan_getdata(client);
545     int presst, next, num;
546
547     fprintf(stderr, "Got PresentRequest.\n");
548     apdup = &apdu;
549     apdu.which = Z_APDU_presentResponse;
550     apdu.u.presentResponse = &resp;
551     resp.referenceId = req->referenceId;
552
553     num = *req->numberOfRecordsRequested;
554     resp.records = pack_records(assoc, req->resultSetId,
555         *req->resultSetStartPoint, &num, req->elementSetNames, &next, &presst);
556     if (!resp.records)
557         return -1;
558     resp.numberOfRecordsReturned = &num;
559     resp.presentStatus = &presst;
560     resp.nextResultSetPosition = &next;
561
562     if (!z_APDU(assoc->encode, &apdup, 0))
563     {
564         odr_perror(assoc->encode, "Encode presentres");
565         return -1;
566     }
567     odr_getbuf(assoc->encode, &assoc->encoded_len);
568     odr_reset(assoc->encode);
569     iochan_setflags(client, EVENT_OUTPUT | EVENT_EXCEPT);
570     return 0;
571 }