4f6cd7913e767652c63093d75b8dcec14d9dc326
[yaz-moved-to-github.git] / client / client.c
1 /*
2  * Copyright (c) 1995, Index Data.
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: client.c,v $
7  * Revision 1.33  1996-05-09 07:25:22  quinn
8  * Sm
9  *
10  * Revision 1.32  1996/03/15  11:05:33  adam
11  * The user can set the preferred query type (prefix, ccl, ..) with the
12  * querytype command.
13  *
14  * Revision 1.31  1996/02/20  12:51:54  quinn
15  * Fixed problems with EXTERNAL.
16  *
17  * Revision 1.30  1996/02/12  18:18:09  quinn
18  * Fidgeting.
19  *
20  * Revision 1.29  1996/01/02  08:57:25  quinn
21  * Changed enums in the ASN.1 .h files to #defines. Changed oident.class to oclass
22  *
23  * Revision 1.28  1995/12/14  11:09:31  quinn
24  * Added Explain record syntax to the format command.
25  *
26  * Revision 1.27  1995/12/12  16:37:02  quinn
27  * Added destroy element to data1_node.
28  *
29  * Revision 1.26  1995/12/12  14:11:00  quinn
30  * Minimal.
31  *
32  * Revision 1.25  1995/11/13  09:27:22  quinn
33  * Fiddling with the variant stuff.
34  *
35  * Revision 1.24  1995/10/30  12:41:13  quinn
36  * Added hostname lookup for server.
37  *
38  * Revision 1.23  1995/10/18  16:12:30  quinn
39  * Better diagnostics.
40  *
41  * Revision 1.22  1995/10/11  14:49:12  quinn
42  * Smallish.
43  *
44  * Revision 1.21  1995/09/29  17:01:47  quinn
45  * More Windows work
46  *
47  * Revision 1.20  1995/08/29  14:24:13  quinn
48  * Added second half of close-handshake
49  *
50  * Revision 1.19  1995/08/29  11:17:28  quinn
51  * Added code to receive close
52  *
53  * Revision 1.18  1995/08/28  12:21:27  quinn
54  * Client can now ask for simple element set names.
55  *
56  * Revision 1.17  1995/08/17  12:45:02  quinn
57  * Fixed minor problems with GRS-1. Added support in c&s.
58  *
59  * Revision 1.16  1995/08/15  12:00:04  quinn
60  * Updated External
61  *
62  * Revision 1.15  1995/06/22  09:28:03  quinn
63  * Fixed bug in SUTRS processing.
64  *
65  * Revision 1.14  1995/06/19  12:37:41  quinn
66  * Added BER dumper.
67  *
68  * Revision 1.13  1995/06/16  10:29:11  quinn
69  * *** empty log message ***
70  *
71  * Revision 1.12  1995/06/15  07:44:57  quinn
72  * Moving to v3.
73  *
74  * Revision 1.11  1995/06/14  15:26:40  quinn
75  * *** empty log message ***
76  *
77  * Revision 1.10  1995/06/06  14:56:58  quinn
78  * Better diagnostics.
79  *
80  * Revision 1.9  1995/06/06  08:15:19  quinn
81  * Cosmetic.
82  *
83  * Revision 1.8  1995/06/05  10:52:22  quinn
84  * Added SCAN.
85  *
86  * Revision 1.7  1995/06/02  09:50:09  quinn
87  * Smallish.
88  *
89  * Revision 1.6  1995/05/31  08:29:21  quinn
90  * Nothing significant.
91  *
92  * Revision 1.5  1995/05/29  08:10:47  quinn
93  * Moved oid.c to util.
94  *
95  * Revision 1.4  1995/05/22  15:30:13  adam
96  * Client uses prefix query notation.
97  *
98  * Revision 1.3  1995/05/22  15:06:53  quinn
99  * *** empty log message ***
100  *
101  * Revision 1.2  1995/05/22  14:56:40  quinn
102  * *** empty log message ***
103  *
104  * Revision 1.1  1995/05/22  11:30:31  quinn
105  * Added prettier client.
106  *
107  *
108  */
109
110 /*
111  * This is the obligatory little toy client, whose primary purpose is
112  * to illustrate the use of the YAZ service-level API.
113  */
114
115 #include <stdio.h>
116 #include <stdlib.h>
117 #ifdef WINDOWS
118 #include <time.h>
119 #else
120 #include <sys/time.h>
121 #endif
122 #include <assert.h>
123 #ifdef _AIX
124 #include <sys/select.h>
125 #endif
126
127 #include <comstack.h>
128 #include <tcpip.h>
129 #ifdef USE_XTIMOSI
130 #include <xmosi.h>
131 #endif
132
133 #include <proto.h>
134 #include <marcdisp.h>
135 #include <diagbib1.h>
136
137 #include <pquery.h>
138
139 #if CCL2RPN
140 #include <yaz-ccl.h>
141 #endif
142
143 #define C_PROMPT "Z> "
144
145 static ODR out, in, print;              /* encoding and decoding streams */
146 static COMSTACK conn = 0;               /* our z-association */
147 static Z_IdAuthentication *auth = 0;    /* our current auth definition */
148 static char database[512] = "Default";  /* Database name */
149 static int setnumber = 0;               /* current result set number */
150 static int smallSetUpperBound = 0;
151 static int largeSetLowerBound = 1;
152 static int mediumSetPresentNumber = 0;
153 static Z_ElementSetNames *elementSetNames = 0; 
154 static int setno = 1;                   /* current set offset */
155 static int protocol = PROTO_Z3950;      /* current app protocol */
156 static int recordsyntax = VAL_USMARC;
157 static int sent_close = 0;
158 static ODR_MEM session_mem;             /* memory handle for init-response */
159 static Z_InitResponse *session = 0;     /* session parameters */
160 static char last_scan[512] = "0";
161 static char last_cmd[100] = "?";
162 static oid_value attributeset = VAL_BIB1;
163 static FILE *marcdump = 0;
164 static char marcdump_file[512] = "marc.out";
165
166 typedef enum {
167     QueryType_Prefix,
168     QueryType_CCL,
169     QueryType_CCL2RPN
170 } QueryType;
171
172 static QueryType queryType = QueryType_Prefix;
173
174 #if CCL2RPN
175 static CCL_bibset bibset;               /* CCL bibset handle */
176 #endif
177
178 static void send_apdu(Z_APDU *a)
179 {
180     char *buf;
181     int len;
182
183     if (!z_APDU(out, &a, 0))
184     {
185         odr_perror(out, "Encoding APDU");
186         exit(1);
187     }
188     buf = odr_getbuf(out, &len, 0);
189     odr_reset(out); /* release the APDU structure  */
190     if (cs_put(conn, buf, len) < 0)
191     {
192         fprintf(stderr, "cs_put: %s", cs_errmsg(cs_errno(conn)));
193         exit(1);
194     }
195 }
196
197 /* INIT SERVICE ------------------------------- */
198
199 static void send_initRequest()
200 {
201     Z_APDU *apdu = zget_APDU(out, Z_APDU_initRequest);
202     Z_InitRequest *req = apdu->u.initRequest;
203
204     ODR_MASK_SET(req->options, Z_Options_search);
205     ODR_MASK_SET(req->options, Z_Options_present);
206     ODR_MASK_SET(req->options, Z_Options_namedResultSets);
207     ODR_MASK_SET(req->options, Z_Options_triggerResourceCtrl);
208     ODR_MASK_SET(req->options, Z_Options_scan);
209
210     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_1);
211     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_2);
212     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_3);
213
214     *req->maximumRecordSize = 1024*1024;
215
216     req->idAuthentication = auth;
217
218     send_apdu(apdu);
219     printf("Sent initrequest.\n");
220 }
221
222 static int process_initResponse(Z_InitResponse *res)
223 {
224     /* save session parameters for later use */
225     session_mem = odr_extract_mem(in);
226     session = res;
227
228     if (!*res->result)
229         printf("Connection rejected by target.\n");
230     else
231         printf("Connection accepted by target.\n");
232     if (res->implementationId)
233         printf("ID     : %s\n", res->implementationId);
234     if (res->implementationName)
235         printf("Name   : %s\n", res->implementationName);
236     if (res->implementationVersion)
237         printf("Version: %s\n", res->implementationVersion);
238     if (res->userInformationField)
239     {
240         printf("UserInformationfield:\n");
241         if (!z_External(print, (Z_External**)&res-> userInformationField,
242             0))
243         {
244             odr_perror(print, "Printing userinfo\n");
245             odr_reset(print);
246         }
247         if (res->userInformationField->which == Z_External_octet)
248         {
249             printf("Guessing visiblestring:\n");
250             printf("'%s'\n", res->userInformationField->u. octet_aligned->buf);
251         }
252     }
253     return 0;
254 }
255
256 int cmd_open(char *arg)
257 {
258     void *add;
259     char type[100], addr[100];
260     CS_TYPE t;
261
262     if (conn)
263     {
264         printf("Already connected.\n");
265         return 0;
266     }
267     if (!*arg || sscanf(arg, "%[^:]:%s", type, addr) < 2)
268     {
269         fprintf(stderr, "Usage: open (osi|tcp) ':' [tsel '/']host[':'port]\n");
270         return 0;
271     }
272 #ifdef USE_XTIMOSI
273     if (!strcmp(type, "osi"))
274     {
275         if (!(add = mosi_strtoaddr(addr)))
276         {
277             perror(arg);
278             return 0;
279         }
280         t = mosi_type;
281         protocol = PROTO_SR;
282     }
283     else
284 #endif
285     if (!strcmp(type, "tcp"))
286     {
287         if (!(add = tcpip_strtoaddr(addr)))
288         {
289             perror(arg);
290             return 0;
291         }
292         t = tcpip_type;
293         protocol = PROTO_Z3950;
294     }
295     else
296     {
297         fprintf(stderr, "Bad type: %s\n", type);
298         return 0;
299     }
300     if (!(conn = cs_create(t, 1, protocol)))
301     {
302         perror("cs_create");
303         return 0;
304     }
305     printf("Connecting...");
306     fflush(stdout);
307     if (cs_connect(conn, add) < 0)
308     {
309         perror("connect");
310         cs_close(conn);
311         conn = 0;
312         return 0;
313     }
314     printf("Ok.\n");
315     send_initRequest();
316     return 2;
317 }
318
319 int cmd_authentication(char *arg)
320 {
321     static Z_IdAuthentication au;
322     static char open[256];
323
324     if (!*arg)
325     {
326         printf("Auth field set to null\n");
327         auth = 0;
328         return 1;
329     }
330     auth = &au;
331     au.which = Z_IdAuthentication_open;
332     au.u.open = open;
333     strcpy(open, arg);
334     return 1;
335 }
336
337 /* SEARCH SERVICE ------------------------------ */
338
339 static void display_variant(Z_Variant *v, int level)
340 {
341     int i;
342
343     for (i = 0; i < v->num_triples; i++)
344     {
345         printf("%*sclass=%d,type=%d", level * 4, "", *v->triples[i]->class,
346             *v->triples[i]->type);
347         if (v->triples[i]->which == Z_Triple_internationalString)
348             printf(",value=%s\n", v->triples[i]->value.internationalString);
349         else
350             printf("\n");
351     }
352 }
353
354 static void display_grs1(Z_GenericRecord *r, int level)
355 {
356     int i;
357
358     if (!r)
359         return;
360     for (i = 0; i < r->num_elements; i++)
361     {
362         Z_TaggedElement *t;
363
364         printf("%*s", level * 4, "");
365         t = r->elements[i];
366         printf("(");
367         if (t->tagType)
368             printf("%d,", *t->tagType);
369         else
370             printf("?,");
371         if (t->tagValue->which == Z_StringOrNumeric_numeric)
372             printf("%d) ", *t->tagValue->u.numeric);
373         else
374             printf("%s) ", t->tagValue->u.string);
375         if (t->content->which == Z_ElementData_subtree)
376         {
377             printf("\n");
378             display_grs1(t->content->u.subtree, level+1);
379         }
380         else if (t->content->which == Z_ElementData_string)
381             printf("%s\n", t->content->u.string);
382         else if (t->content->which == Z_ElementData_numeric)
383             printf("%d\n", *t->content->u.numeric);
384         else if (t->content->which == Z_ElementData_noDataRequested)
385             printf("[No data requested]\n");
386         else if (t->content->which == Z_ElementData_elementEmpty)
387             printf("[Element empty]\n");
388         else if (t->content->which == Z_ElementData_elementNotThere)
389             printf("[Element not there]\n");
390         else
391             printf("??????\n");
392         if (t->appliedVariant)
393             display_variant(t->appliedVariant, level+1);
394         if (t->metaData && t->metaData->supportedVariants)
395         {
396             int c;
397
398             printf("%*s---- variant list\n", (level+1)*4, "");
399             for (c = 0; c < t->metaData->num_supportedVariants; c++)
400             {
401                 printf("%*svariant #%d\n", (level+1)*4, "", c);
402                 display_variant(t->metaData->supportedVariants[c], level + 2);
403             }
404         }
405     }
406 }
407
408 static void display_record(Z_DatabaseRecord *p)
409 {
410     Z_External *r = (Z_External*) p;
411     oident *ent = oid_getentbyoid(r->direct_reference);
412
413     /*
414      * Tell the user what we got.
415      */
416     if (r->direct_reference)
417     {
418         printf("Record type: ");
419         if (ent)
420             printf("%s\n", ent->desc);
421         else if (!odr_oid(print, &r->direct_reference, 0))
422         {
423             odr_perror(print, "print oid");
424             odr_reset(print);
425         }
426     }
427
428     /* Check if this is a known, ASN.1 type tucked away in an octet string */
429     if (ent && r->which == Z_External_octet)
430     {
431         Z_ext_typeent *type = z_ext_getentbyref(ent->value);
432         void *rr;
433
434         if (type)
435         {
436             /*
437              * Call the given decoder to process the record.
438              */
439             odr_setbuf(in, (char*)p->u.octet_aligned->buf,
440                 p->u.octet_aligned->len, 0);
441             if (!(*type->fun)(in, &rr, 0))
442             {
443                 odr_perror(in, "Decoding constructed record.");
444                 fprintf(stderr, "[Near %d]\n", odr_offset(in));
445                 fprintf(stderr, "Packet dump:\n---------\n");
446                 odr_dumpBER(stderr, (char*)p->u.octet_aligned->buf,
447                     p->u.octet_aligned->len);
448                 fprintf(stderr, "---------\n");
449                 exit(1);
450             }
451             /*
452              * Note: we throw away the original, BER-encoded record here.
453              * Do something else with it if you want to keep it.
454              */
455             r->u.sutrs = rr;    /* we don't actually check the type here. */
456             r->which = type->what;
457         }
458     }
459     if (r->which == Z_External_octet && p->u.octet_aligned->len)
460         marc_display ((char*)p->u.octet_aligned->buf, stdout);
461     else if (ent->value == VAL_SUTRS)
462     {
463         if (r->which != Z_External_sutrs)
464         {
465             printf("Expecting single SUTRS type for SUTRS.\n");
466             return;
467         }
468         printf("%.*s", r->u.sutrs->len, r->u.sutrs->buf);
469     }
470     else if (ent->value == VAL_GRS1)
471     {
472         if (r->which != Z_External_grs1)
473         {
474             printf("Expecting single GRS type for GRS.\n");
475             return;
476         }
477         display_grs1(r->u.grs1, 0);
478     }
479     else 
480     {
481         printf("Unknown record representation.\n");
482         if (!z_External(print, &r, 0))
483         {
484             odr_perror(print, "Printing external");
485             odr_reset(print);
486         }
487     }
488 }
489
490 static void display_diagrec(Z_DiagRec *p)
491 {
492     oident *ent;
493 #ifdef Z_95
494     Z_DefaultDiagFormat *r;
495 #else
496     Z_DiagRec *r = p;
497 #endif
498
499     printf("Diagnostic message from database:\n");
500 #ifdef Z_95
501     if (p->which != Z_DiagRec_defaultFormat)
502     {
503         printf("Diagnostic record not in default format.\n");
504         return;
505     }
506     else
507         r = p->u.defaultFormat;
508 #endif
509     if (!(ent = oid_getentbyoid(r->diagnosticSetId)) ||
510         ent->oclass != CLASS_DIAGSET || ent->value != VAL_BIB1)
511         printf("Missing or unknown diagset\n");
512     printf("    [%d] %s", *r->condition, diagbib1_str(*r->condition));
513     if (r->addinfo && *r->addinfo)
514         printf(" -- '%s'\n", r->addinfo);
515     else
516         printf("\n");
517 }
518
519 static void display_nameplusrecord(Z_NamePlusRecord *p)
520 {
521     if (p->databaseName)
522         printf("[%s]", p->databaseName);
523     if (p->which == Z_NamePlusRecord_surrogateDiagnostic)
524         display_diagrec(p->u.surrogateDiagnostic);
525     else
526         display_record(p->u.databaseRecord);
527 }
528
529 static void display_records(Z_Records *p)
530 {
531     int i;
532
533     if (p->which == Z_Records_NSD)
534         display_diagrec(p->u.nonSurrogateDiagnostic);
535     else
536     {
537         printf("Records: %d\n", p->u.databaseOrSurDiagnostics->num_records);
538         for (i = 0; i < p->u.databaseOrSurDiagnostics->num_records; i++)
539             display_nameplusrecord(p->u.databaseOrSurDiagnostics->records[i]);
540     }
541 }
542
543 static int send_searchRequest(char *arg)
544 {
545     Z_APDU *apdu = zget_APDU(out, Z_APDU_searchRequest);
546     Z_SearchRequest *req = apdu->u.searchRequest;
547     char *databaseNames = database;
548     Z_Query query;
549 #if CCL2RPN
550     struct ccl_rpn_node *rpn;
551     int error, pos;
552     oident bib1;
553 #endif
554     char setstring[100];
555     Z_RPNQuery *RPNquery;
556     Odr_oct ccl_query;
557
558 #if CCL2RPN
559     if (queryType == QueryType_CCL2RPN)
560     {
561         rpn = ccl_find_str(bibset, arg, &error, &pos);
562         if (error)
563         {
564             printf("CCL ERROR: %s\n", ccl_err_msg(error));
565             return 0;
566         }
567     }
568 #endif
569
570     if (!strcmp(arg, "@big")) /* strictly for troublemaking */
571     {
572         static unsigned char big[2100];
573         static Odr_oct bigo;
574
575         /* send a very big referenceid to test transport stack etc. */
576         memset(big, 'A', 2100);
577         bigo.len = bigo.size = 2100;
578         bigo.buf = big;
579         req->referenceId = &bigo;
580     }
581
582     if (setnumber >= 0)
583     {
584         sprintf(setstring, "%d", ++setnumber);
585         req->resultSetName = setstring;
586     }
587     *req->smallSetUpperBound = smallSetUpperBound;
588     *req->largeSetLowerBound = largeSetLowerBound;
589     *req->mediumSetPresentNumber = mediumSetPresentNumber;
590     if (smallSetUpperBound > 0 || (largeSetLowerBound > 1 &&
591         mediumSetPresentNumber > 0))
592     {
593         oident prefsyn;
594
595         prefsyn.proto = protocol;
596         prefsyn.oclass = CLASS_RECSYN;
597         prefsyn.value = recordsyntax;
598         req->preferredRecordSyntax =
599             odr_oiddup(out, oid_getoidbyent(&prefsyn));
600         req->smallSetElementSetNames =
601             req->mediumSetElementSetNames = elementSetNames;
602     }
603     req->num_databaseNames = 1;
604     req->databaseNames = &databaseNames;
605
606     req->query = &query;
607
608     switch (queryType)
609     {
610     case QueryType_Prefix:
611         query.which = Z_Query_type_1;
612         RPNquery = p_query_rpn (out, protocol, arg);
613         if (!RPNquery)
614         {
615             printf("Prefix query error\n");
616             return 0;
617         }
618         query.u.type_1 = RPNquery;
619         break;
620     case QueryType_CCL:
621         query.which = Z_Query_type_2;
622         query.u.type_2 = &ccl_query;
623         ccl_query.buf = (unsigned char*) arg;
624         ccl_query.len = strlen(arg);
625         break;
626 #if CCL2RPN
627     case QueryType_CCL2RPN:
628         query.which = Z_Query_type_1;
629         assert((RPNquery = ccl_rpn_query(rpn)));
630         bib1.proto = protocol;
631         bib1.oclass = CLASS_ATTSET;
632         bib1.value = VAL_BIB1;
633         RPNquery->attributeSetId = oid_getoidbyent(&bib1);
634         query.u.type_1 = RPNquery;
635         break;
636 #endif
637     default:
638         printf ("Unsupported query type\n");
639         return 0;
640     }
641     send_apdu(apdu);
642     setno = 1;
643     printf("Sent searchRequest.\n");
644     return 2;
645 }
646
647 static int process_searchResponse(Z_SearchResponse *res)
648 {
649     if (*res->searchStatus)
650         printf("Search was a success.\n");
651     else
652         printf("Search was a bloomin' failure.\n");
653     printf("Number of hits: %d, setno %d\n",
654         *res->resultCount, setnumber);
655     printf("records returned: %d\n",
656         *res->numberOfRecordsReturned);
657     setno += *res->numberOfRecordsReturned;
658     if (res->records)
659         display_records(res->records);
660     return 0;
661 }
662
663 static int cmd_find(char *arg)
664 {
665     if (!*arg)
666     {
667         printf("Find what?\n");
668         return 0;
669     }
670     if (!conn)
671     {
672         printf("Not connected yet\n");
673         return 0;
674     }
675     if (!send_searchRequest(arg))
676         return 0;
677     return 2;
678 }
679
680 static int cmd_ssub(char *arg)
681 {
682     if (!(smallSetUpperBound = atoi(arg)))
683         return 0;
684     return 1;
685 }
686
687 static int cmd_lslb(char *arg)
688 {
689     if (!(largeSetLowerBound = atoi(arg)))
690         return 0;
691     return 1;
692 }
693
694 static int cmd_mspn(char *arg)
695 {
696     if (!(mediumSetPresentNumber = atoi(arg)))
697         return 0;
698     return 1;
699 }
700
701 static int cmd_status(char *arg)
702 {
703     printf("smallSetUpperBound: %d\n", smallSetUpperBound);
704     printf("largeSetLowerBound: %d\n", largeSetLowerBound);
705     printf("mediumSetPresentNumber: %d\n", mediumSetPresentNumber);
706     return 1;
707 }
708
709 static int cmd_base(char *arg)
710 {
711     if (!*arg)
712     {
713         printf("Usage: base <database>\n");
714         return 0;
715     }
716     strcpy(database, arg);
717     return 1;
718 }
719
720 static int cmd_setnames(char *arg)
721 {
722     if (setnumber < 0)
723     {
724         printf("Set numbering enabled.\n");
725         setnumber = 0;
726     }
727     else
728     {
729         printf("Set numbering disabled.\n");
730         setnumber = -1;
731     }
732     return 1;
733 }
734
735 /* PRESENT SERVICE ----------------------------- */
736
737 static int send_presentRequest(char *arg)
738 {
739     Z_APDU *apdu = zget_APDU(out, Z_APDU_presentRequest);
740     Z_PresentRequest *req = apdu->u.presentRequest;
741     Z_RecordComposition compo;
742     oident prefsyn;
743     int nos = 1;
744     char *p;
745     char setstring[100];
746
747     if ((p = strchr(arg, '+')))
748     {
749         nos = atoi(p + 1);
750         *p = 0;
751     }
752     if (*arg)
753         setno = atoi(arg);
754
755     if (setnumber >= 0)
756     {
757         sprintf(setstring, "%d", setnumber);
758         req->resultSetId = setstring;
759     }
760     req->resultSetStartPoint = &setno;
761     req->numberOfRecordsRequested = &nos;
762     prefsyn.proto = protocol;
763     prefsyn.oclass = CLASS_RECSYN;
764     prefsyn.value = recordsyntax;
765     req->preferredRecordSyntax = oid_getoidbyent(&prefsyn);
766     if (elementSetNames)
767     {
768         req->recordComposition = &compo;
769         compo.which = Z_RecordComp_simple;
770         compo.u.simple = elementSetNames;
771     }
772     send_apdu(apdu);
773     printf("Sent presentRequest (%d+%d).\n", setno, nos);
774     return 2;
775 }
776
777 void process_close(Z_Close *req)
778 {
779     Z_APDU *apdu = zget_APDU(out, Z_APDU_close);
780     Z_Close *res = apdu->u.close;
781
782     static char *reasons[] =
783     {
784         "finished",
785         "shutdown",
786         "system problem",
787         "cost limit reached",
788         "resources",
789         "security violation",
790         "protocolError",
791         "lack of activity",
792         "peer abort",
793         "unspecified"
794     };
795
796     printf("Reason: %s, message: %s\n", reasons[*req->closeReason],
797         req->diagnosticInformation ? req->diagnosticInformation : "NULL");
798     if (sent_close)
799     {
800         printf("Goodbye.\n");
801         exit(0);
802     }
803     *res->closeReason = Z_Close_finished;
804     send_apdu(apdu);
805     printf("Sent response.\n");
806     sent_close = 1;
807 }
808
809 static int cmd_show(char *arg)
810 {
811     if (!send_presentRequest(arg))
812         return 0;
813     return 2;
814 }
815
816 int cmd_quit(char *arg)
817 {
818     printf("See you later, alligator.\n");
819     exit(0);
820 }
821
822 int cmd_cancel(char *arg)
823 {
824     Z_APDU *apdu = zget_APDU(out, Z_APDU_triggerResourceControlRequest);
825     Z_TriggerResourceControlRequest *req =
826         apdu->u.triggerResourceControlRequest;
827     bool_t false = 0;
828     
829     if (!session)
830     {
831         printf("Session not initialized yet\n");
832         return 0;
833     }
834     if (!ODR_MASK_GET(session->options, Z_Options_triggerResourceCtrl))
835     {
836         printf("Target doesn't support cancel (trigger resource ctrl)\n");
837         return 0;
838     }
839     *req->requestedAction = Z_TriggerResourceCtrl_cancel;
840     req->resultSetWanted = &false;
841
842     send_apdu(apdu);
843     printf("Sent cancel request\n");
844     return 2;
845 }
846
847 int send_scanrequest(char *string, int pp, int num)
848 {
849     Z_APDU *apdu = zget_APDU(out, Z_APDU_scanRequest);
850     Z_ScanRequest *req = apdu->u.scanRequest;
851     char *db = database;
852
853     req->num_databaseNames = 1;
854     req->databaseNames = &db;
855     req->termListAndStartPoint = p_query_scan(out, protocol,
856                                               &req->attributeSet, string);
857     req->numberOfTermsRequested = &num;
858     req->preferredPositionInResponse = &pp;
859     send_apdu(apdu);
860     return 2;
861 }
862
863 void display_term(Z_TermInfo *t)
864 {
865     if (t->term->which == Z_Term_general)
866     {
867         printf("%.*s (%d)\n", t->term->u.general->len, t->term->u.general->buf,
868             t->globalOccurrences ? *t->globalOccurrences : -1);
869         sprintf(last_scan, "%.*s", t->term->u.general->len,
870             t->term->u.general->buf);
871     }
872     else
873         printf("Term type not general.\n");
874 }
875
876 void process_scanResponse(Z_ScanResponse *res)
877 {
878     int i;
879
880     printf("SCAN: %d entries, position=%d\n", *res->numberOfEntriesReturned,
881         *res->positionOfTerm);
882     if (*res->scanStatus != Z_Scan_success)
883         printf("Scan returned code %d\n", *res->scanStatus);
884     if (!res->entries)
885         return;
886     if (res->entries->which == Z_ListEntries_entries)
887     {
888         Z_Entries *ent = res->entries->u.entries;
889
890         for (i = 0; i < ent->num_entries; i++)
891             if (ent->entries[i]->which == Z_Entry_termInfo)
892             {
893                 printf("%c ", i + 1 == *res->positionOfTerm ? '*' : ' ');
894                 display_term(ent->entries[i]->u.termInfo);
895             }
896             else
897                 display_diagrec(ent->entries[i]->u.surrogateDiagnostic);
898     }
899     else
900         display_diagrec(res->entries->u.nonSurrogateDiagnostics->diagRecs[0]);
901 }
902
903 int cmd_scan(char *arg)
904 {
905     if (!session)
906     {
907         printf("Session not initialized yet\n");
908         return 0;
909     }
910     if (!ODR_MASK_GET(session->options, Z_Options_scan))
911     {
912         printf("Target doesn't support scan\n");
913         return 0;
914     }
915     if (*arg)
916     {
917         if (send_scanrequest(arg, 5, 20) < 0)
918             return 0;
919     }
920     else
921         if (send_scanrequest(last_scan, 1, 20) < 0)
922             return 0;
923     return 2;
924 }
925
926 int cmd_format(char *arg)
927 {
928     if (!arg || !*arg)
929     {
930         printf("Usage: format <recordsyntax>\n");
931         return 0;
932     }
933     if (!strcmp(arg, "sutrs"))
934     {
935         printf("Preferred format is SUTRS.\n");
936         recordsyntax = VAL_SUTRS;
937         return 1;
938     }
939     else if (!strcmp(arg, "usmarc"))
940     {
941         printf("Preferred format is USMARC\n");
942         recordsyntax = VAL_USMARC;
943         return 1;
944     }
945     else if (!strcmp(arg, "danmarc"))
946     {
947         printf("Preferred format is DANMARC\n");
948         recordsyntax = VAL_DANMARC;
949         return 1;
950     }
951     else if (!strcmp(arg, "grs1"))
952     {
953         printf("Preferred format is GRS1\n");
954         recordsyntax = VAL_GRS1;
955         return 1;
956     }
957     else if (!strcmp(arg, "explain"))
958     {
959         printf("Preferred format is Explain\n");
960         recordsyntax = VAL_EXPLAIN;
961         return 1;
962     }
963     else
964     {
965         printf("Specify one of {sutrs,usmarc,danmarc,grs1}.\n");
966         return 0;
967     }
968 }
969
970 int cmd_elements(char *arg)
971 {
972     static Z_ElementSetNames esn;
973     static char what[100];
974
975     if (!arg || !*arg)
976     {
977         printf("Usage: elements <esn>\n");
978         return 0;
979     }
980     strcpy(what, arg);
981     esn.which = Z_ElementSetNames_generic;
982     esn.u.generic = what;
983     elementSetNames = &esn;
984     return 1;
985 }
986
987 int cmd_attributeset(char *arg)
988 {
989     char what[100];
990     oid_value v;
991
992     if (!arg || !*arg)
993     {
994         printf("Usage: attributeset <setname>\n");
995         return 0;
996     }
997     sscanf(arg, "%s", what);
998     if ((v = oid_getvalbyname(what)) == VAL_NONE)
999     {
1000         printf("Unknown attribute set name\n");
1001         return 0;
1002     }
1003     attributeset = v;
1004     return 1;
1005 }
1006
1007 int cmd_querytype (char *arg)
1008 {
1009     if (!strcmp (arg, "ccl"))
1010         queryType = QueryType_CCL;
1011     else if (!strcmp (arg, "prefix"))
1012         queryType = QueryType_Prefix;
1013 #if CCL2RPN
1014     else if (!strcmp (arg, "ccl2rpn") || !strcmp (arg, "cclrpn"))
1015         queryType = QueryType_CCL2RPN;
1016 #endif
1017     else
1018     {
1019         printf ("Querytype must be one of:\n");
1020         printf (" prefix         - Prefix query\n");
1021         printf (" ccl            - CCL query\n");
1022 #if CCL2RPN
1023         printf (" ccl2rpn        - CCL query converted to RPN\n");
1024 #endif
1025         return 0;
1026     }
1027     return 1;
1028 }
1029
1030 int cmd_close(char *arg)
1031 {
1032     Z_APDU *apdu = zget_APDU(out, Z_APDU_close);
1033     Z_Close *req = apdu->u.close;
1034
1035     *req->closeReason = Z_Close_finished;
1036     send_apdu(apdu);
1037     printf("Sent close request.\n");
1038     sent_close = 1;
1039     return 2;
1040 }
1041
1042 static void initialize(void)
1043 {
1044 #if CCL2RPN
1045     FILE *inf;
1046 #endif
1047
1048     if (!(out = odr_createmem(ODR_ENCODE)) ||
1049         !(in = odr_createmem(ODR_DECODE)) ||
1050         !(print = odr_createmem(ODR_PRINT)))
1051     {
1052         fprintf(stderr, "failed to allocate ODR streams\n");
1053         exit(1);
1054     }
1055     setvbuf(stdout, 0, _IONBF, 0);
1056
1057 #if CCL2RPN
1058     bibset = ccl_qual_mk (); 
1059     inf = fopen ("default.bib", "r");
1060     if (inf)
1061     {
1062         ccl_qual_file (bibset, inf);
1063         fclose (inf);
1064     }
1065 #endif
1066 }
1067
1068 static int client(void)
1069 {
1070     static struct {
1071         char *cmd;
1072         int (*fun)(char *arg);
1073         char *ad;
1074     } cmd[] = {
1075         {"open", cmd_open, "('tcp'|'osi')':'[<tsel>'/']<host>[':'<port>]"},
1076         {"quit", cmd_quit, ""},
1077         {"find", cmd_find, "<query>"},
1078         {"base", cmd_base, "<base-name>"},
1079         {"show", cmd_show, "<rec#>['+'<#recs>]"},
1080         {"scan", cmd_scan, "<term>"},
1081         {"authentication", cmd_authentication, "<acctstring>"},
1082         {"lslb", cmd_lslb, "<largeSetLowerBound>"},
1083         {"ssub", cmd_ssub, "<smallSetUpperBound>"},
1084         {"mspn", cmd_mspn, "<mediumSetPresentNumber>"},
1085         {"status", cmd_status, ""},
1086         {"setnames", cmd_setnames, ""},
1087         {"cancel", cmd_cancel, ""},
1088         {"format", cmd_format, "<recordsyntax>"},
1089         {"elements", cmd_elements, "<elementSetName>"},
1090         {"close", cmd_close, ""},
1091         {"attributeset", cmd_attributeset, "<attrset>"},
1092         {"querytype", cmd_querytype, "<type>"},
1093         {0,0}
1094     };
1095     char *netbuffer= 0;
1096     int netbufferlen = 0;
1097     int i;
1098     Z_APDU *apdu;
1099
1100     while (1)
1101     {
1102         int res;
1103         fd_set input;
1104         char line[1024], word[1024], arg[1024];
1105
1106         FD_ZERO(&input);
1107         FD_SET(0, &input);
1108         if (conn)
1109             FD_SET(cs_fileno(conn), &input);
1110         if ((res = select(20, &input, 0, 0, 0)) < 0)
1111         {
1112             perror("select");
1113             exit(1);
1114         }
1115         if (!res)
1116             continue;
1117         if (FD_ISSET(0, &input))
1118         {
1119             /* quick & dirty way to get a command line. */
1120             if (!gets(line))
1121                 break;
1122             if ((res = sscanf(line, "%s %[^;]", word, arg)) <= 0)
1123             {
1124                 strcpy(word, last_cmd);
1125                 *arg = '\0';
1126             }
1127             else if (res == 1)
1128                 *arg = 0;
1129             strcpy(last_cmd, word);
1130             for (i = 0; cmd[i].cmd; i++)
1131                 if (!strncmp(cmd[i].cmd, word, strlen(word)))
1132                 {
1133                     res = (*cmd[i].fun)(arg);
1134                     break;
1135                 }
1136             if (!cmd[i].cmd) /* dump our help-screen */
1137             {
1138                 printf("Unknown command: %s.\n", word);
1139                 printf("Currently recognized commands:\n");
1140                 for (i = 0; cmd[i].cmd; i++)
1141                     printf("   %s %s\n", cmd[i].cmd, cmd[i].ad);
1142                 res = 1;
1143             }
1144             if (res < 2)
1145                 printf(C_PROMPT);
1146         }
1147         if (conn && FD_ISSET(cs_fileno(conn), &input))
1148         {
1149             do
1150             {
1151                 if ((res = cs_get(conn, &netbuffer, &netbufferlen)) < 0)
1152                 {
1153                     perror("cs_get");
1154                     exit(1);
1155                 }
1156                 if (!res)
1157                 {
1158                     printf("Target closed connection.\n");
1159                     exit(1);
1160                 }
1161                 odr_reset(in); /* release APDU from last round */
1162                 odr_setbuf(in, netbuffer, res, 0);
1163                 if (!z_APDU(in, &apdu, 0))
1164                 {
1165                     odr_perror(in, "Decoding incoming APDU");
1166                     fprintf(stderr, "[Near %d]\n", odr_offset(in));
1167                     fprintf(stderr, "Packet dump:\n---------\n");
1168                     odr_dumpBER(stderr, netbuffer, res);
1169                     fprintf(stderr, "---------\n");
1170                     exit(1);
1171                 }
1172 #if 0
1173                 if (!z_APDU(print, &apdu, 0))
1174                 {
1175                     odr_perror(print, "Failed to print incoming APDU");
1176                     odr_reset(print);
1177                     continue;
1178                 }
1179 #endif
1180                 switch(apdu->which)
1181                 {
1182                     case Z_APDU_initResponse:
1183                         process_initResponse(apdu->u.initResponse);
1184                         break;
1185                     case Z_APDU_searchResponse:
1186                         process_searchResponse(apdu->u.searchResponse);
1187                         break;
1188                     case Z_APDU_scanResponse:
1189                         process_scanResponse(apdu->u.scanResponse);
1190                         break;
1191                     case Z_APDU_presentResponse:
1192                         printf("Received presentResponse.\n");
1193                         setno +=
1194                             *apdu->u.presentResponse->numberOfRecordsReturned;
1195                         if (apdu->u.presentResponse->records)
1196                             display_records(apdu->u.presentResponse->records);
1197                         else
1198                             printf("No records.\n");
1199                         break;
1200                     case Z_APDU_close:
1201                         printf("Target has closed the association.\n");
1202                         process_close(apdu->u.close);
1203                         break;
1204                     default:
1205                         printf("Received unknown APDU type (%d).\n", 
1206                             apdu->which);
1207                         exit(1);
1208                 }
1209                 printf(C_PROMPT);
1210                 fflush(stdout);
1211             }
1212             while (cs_more(conn));
1213         }
1214     }
1215     return 0;
1216 }
1217
1218 int main(int argc, char **argv)
1219 {
1220     initialize();
1221     if (argc > 1)
1222         cmd_open(argv[1]);
1223     else
1224         printf(C_PROMPT);
1225     if (*marcdump_file && !(marcdump = fopen(marcdump_file, "a")))
1226     {
1227         perror(marcdump_file);
1228         exit(1);
1229     }
1230     return client();
1231 }