81e987e7858184479ae5d65d4fcb648b89cb2ff8
[yazpp-moved-to-github.git] / src / yaz-proxy.cpp
1 /*
2  * Copyright (c) 1998-2003, Index Data.
3  * See the file LICENSE for details.
4  * 
5  * $Id: yaz-proxy.cpp,v 1.48 2003-10-01 13:13:51 adam Exp $
6  */
7
8 #include <assert.h>
9 #include <time.h>
10
11 #include <yaz/log.h>
12 #include <yaz/diagbib1.h>
13 #include <yaz++/proxy.h>
14
15 static const char *apdu_name(Z_APDU *apdu)
16 {
17     switch (apdu->which)
18     {
19     case Z_APDU_initRequest:
20         return "initRequest";
21     case Z_APDU_initResponse:
22         return "initResponse";
23     case Z_APDU_searchRequest:
24         return "searchRequest";
25     case Z_APDU_searchResponse:
26         return "searchResponse";
27     case Z_APDU_presentRequest:
28         return "presentRequest";
29     case Z_APDU_presentResponse:
30         return "presentResponse";
31     case Z_APDU_deleteResultSetRequest:
32         return "deleteResultSetRequest";
33     case Z_APDU_deleteResultSetResponse:
34         return "deleteResultSetResponse";
35     case Z_APDU_scanRequest:
36         return "scanRequest";
37     case Z_APDU_scanResponse:
38         return "scanResponse";
39     case Z_APDU_sortRequest:
40         return "sortRequest";
41     case Z_APDU_sortResponse:
42         return "sortResponse";
43     case Z_APDU_extendedServicesRequest:
44         return "extendedServicesRequest";
45     case Z_APDU_extendedServicesResponse:
46         return "extendedServicesResponse";
47     case Z_APDU_close:
48         return "close";
49     }
50     return "other";
51 }
52
53
54 Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable) :
55     Yaz_Z_Assoc(the_PDU_Observable), m_bw_stat(60), m_pdu_stat(60)
56 {
57     m_PDU_Observable = the_PDU_Observable;
58     m_client = 0;
59     m_parent = 0;
60     m_clientPool = 0;
61     m_seqno = 1;
62     m_keepalive = 0;
63     m_proxyTarget = 0;
64     m_default_target = 0;
65     m_proxy_authentication = 0;
66     m_max_clients = 150;
67     m_seed = time(0);
68     m_client_idletime = 600;
69     m_target_idletime = 600;
70     m_optimize = xstrdup ("1");
71     strcpy(m_session_str, "x");
72     m_session_no=0;
73     m_bytes_sent = m_bytes_recv = 0;
74     m_bw_hold_PDU = 0;
75     m_bw_max = 0;
76     m_pdu_max = 0;
77     m_max_record_retrieve = 0;
78 }
79
80 Yaz_Proxy::~Yaz_Proxy()
81 {
82     yaz_log(LOG_LOG, "%s Closed %d/%d sent/recv bytes total", m_session_str,
83             m_bytes_sent, m_bytes_recv);
84     xfree (m_proxyTarget);
85     xfree (m_default_target);
86     xfree (m_proxy_authentication);
87     xfree (m_optimize);
88 }
89
90 int Yaz_Proxy::set_config(const char *config)
91 {
92     int r = m_config.read_xml(config);
93     return r;
94 }
95
96 void Yaz_Proxy::set_default_target(const char *target)
97 {
98     xfree (m_default_target);
99     m_default_target = 0;
100     if (target)
101         m_default_target = (char *) xstrdup (target);
102 }
103
104 void Yaz_Proxy::set_proxy_authentication (const char *auth)
105 {
106     xfree (m_proxy_authentication);
107     m_proxy_authentication = 0;
108     if (auth)
109         m_proxy_authentication = (char *) xstrdup (auth);
110 }
111
112 IYaz_PDU_Observer *Yaz_Proxy::sessionNotify(IYaz_PDU_Observable
113                                             *the_PDU_Observable, int fd)
114 {
115     Yaz_Proxy *new_proxy = new Yaz_Proxy(the_PDU_Observable);
116     new_proxy->m_parent = this;
117     new_proxy->m_config = m_config;
118     new_proxy->timeout(m_client_idletime);
119     new_proxy->m_target_idletime = m_target_idletime;
120     new_proxy->set_default_target(m_default_target);
121     new_proxy->set_APDU_log(get_APDU_log());
122     new_proxy->set_proxy_authentication(m_proxy_authentication);
123     sprintf(new_proxy->m_session_str, "%ld:%d", (long) time(0), m_session_no);
124     m_session_no++;
125     yaz_log (LOG_LOG, "%s New session %s", new_proxy->m_session_str,
126              the_PDU_Observable->getpeername());
127     return new_proxy;
128 }
129
130 char *Yaz_Proxy::get_cookie(Z_OtherInformation **otherInfo)
131 {
132     int oid[OID_SIZE];
133     Z_OtherInformationUnit *oi;
134     struct oident ent;
135     ent.proto = PROTO_Z3950;
136     ent.oclass = CLASS_USERINFO;
137     ent.value = (oid_value) VAL_COOKIE;
138     assert (oid_ent_to_oid (&ent, oid));
139
140     if (oid_ent_to_oid (&ent, oid) && 
141         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
142         oi->which == Z_OtherInfo_characterInfo)
143         return oi->information.characterInfo;
144     return 0;
145 }
146
147 char *Yaz_Proxy::get_proxy(Z_OtherInformation **otherInfo)
148 {
149     int oid[OID_SIZE];
150     Z_OtherInformationUnit *oi;
151     struct oident ent;
152     ent.proto = PROTO_Z3950;
153     ent.oclass = CLASS_USERINFO;
154     ent.value = (oid_value) VAL_PROXY;
155     if (oid_ent_to_oid (&ent, oid) &&
156         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
157         oi->which == Z_OtherInfo_characterInfo)
158         return oi->information.characterInfo;
159     return 0;
160 }
161
162 Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu)
163 {
164     assert (m_parent);
165     Yaz_Proxy *parent = m_parent;
166     Z_OtherInformation **oi;
167     Yaz_ProxyClient *c = m_client;
168     
169     get_otherInfoAPDU(apdu, &oi);
170     char *cookie = get_cookie(oi);
171
172     if (!m_proxyTarget)
173     {
174         const char *proxy_host = get_proxy(oi);
175         if (!proxy_host)
176             proxy_host = m_default_target;
177         
178         const char *url = 0;
179         m_config.get_target_info(proxy_host, &url, &m_keepalive, &m_bw_max,
180                                  &m_pdu_max, &m_max_record_retrieve);
181         
182         if (!url)
183         {
184             yaz_log(LOG_LOG, "%s No default target", m_session_str);
185             return 0;
186         }
187         m_proxyTarget = (char*) xstrdup(url);
188     }
189     if (cookie && *cookie)
190     {
191         Yaz_ProxyClient *cc = 0;
192         
193         for (c = parent->m_clientPool; c; c = c->m_next)
194         {
195             assert (c->m_prev);
196             assert (*c->m_prev == c);
197             if (c->m_cookie && !strcmp(cookie,c->m_cookie) &&
198                 !strcmp(m_proxyTarget, c->get_hostname()))
199             {
200                 cc = c;
201             }
202         }
203         if (cc)
204         {
205             // found it in cache
206             c = cc;
207             // The following handles "cancel"
208             // If connection is busy (waiting for PDU) and
209             // we have an initRequest we can safely do re-open
210             if (c->m_waiting && apdu->which == Z_APDU_initRequest)
211             {
212                 yaz_log (LOG_LOG, "%s REOPEN target=%s", m_session_str,
213                          c->get_hostname());
214                 c->close();
215                 c->client(m_proxyTarget);
216                 c->m_init_flag = 0;
217
218                 c->m_last_ok = 0;
219                 c->m_cache.clear();
220                 c->m_last_resultCount = 0;
221                 c->m_sr_transform = 0;
222                 c->m_waiting = 0;
223                 c->m_resultSetStartPoint = 0;
224                 c->timeout(m_target_idletime); 
225             }
226             c->m_seqno = parent->m_seqno;
227             if (c->m_server && c->m_server != this)
228                 c->m_server->m_client = 0;
229             c->m_server = this;
230             (parent->m_seqno)++;
231             yaz_log (LOG_DEBUG, "get_client 1 %p %p", this, c);
232             return c;
233         }
234     }
235     else if (!c)
236     {
237         Yaz_ProxyClient *cc = 0;
238         
239         for (c = parent->m_clientPool; c; c = c->m_next)
240         {
241             assert (c->m_prev);
242             assert (*c->m_prev == c);
243             if (c->m_server == 0 && c->m_cookie == 0 && 
244                 !strcmp(m_proxyTarget, c->get_hostname()))
245             {
246                 cc = c;
247             }
248         }
249         if (cc)
250         {
251             // found it in cache
252             c = cc;
253
254             yaz_log (LOG_LOG, "%s REUSE %d %d %s",
255                      m_session_str,
256                      c->m_seqno, parent->m_seqno, c->get_hostname());
257
258             c->m_seqno = parent->m_seqno;
259             assert(c->m_server == 0);
260             c->m_server = this;
261             
262             (parent->m_seqno)++;
263             return c;
264         }
265     }
266     if (!m_client)
267     {
268         if (apdu->which != Z_APDU_initRequest)
269         {
270             yaz_log (LOG_LOG, "no first INIT!");
271             return 0;
272         }
273         Z_InitRequest *initRequest = apdu->u.initRequest;
274
275         if (!initRequest->idAuthentication)
276         {
277             if (m_proxy_authentication)
278             {
279                 initRequest->idAuthentication =
280                     (Z_IdAuthentication *)
281                     odr_malloc (odr_encode(),
282                                 sizeof(*initRequest->idAuthentication));
283                 initRequest->idAuthentication->which =
284                     Z_IdAuthentication_open;
285                 initRequest->idAuthentication->u.open =
286                     odr_strdup (odr_encode(), m_proxy_authentication);
287             }
288         }
289
290         // go through list of clients - and find the lowest/oldest one.
291         Yaz_ProxyClient *c_min = 0;
292         int min_seq = -1;
293         int no_of_clients = 0;
294         if (parent->m_clientPool)
295             yaz_log (LOG_LOG, "Existing sessions");
296         for (c = parent->m_clientPool; c; c = c->m_next)
297         {
298             yaz_log (LOG_LOG, " Session %-3d wait=%d %s cookie=%s", c->m_seqno,
299                                c->m_waiting, c->get_hostname(),
300                                c->m_cookie ? c->m_cookie : "");
301             no_of_clients++;
302             if (min_seq < 0 || c->m_seqno < min_seq)
303             {
304                 min_seq = c->m_seqno;
305                 c_min = c;
306             }
307         }
308         if (no_of_clients >= parent->m_max_clients)
309         {
310             c = c_min;
311             if (c->m_waiting || strcmp(m_proxyTarget, c->get_hostname()))
312             {
313                 yaz_log (LOG_LOG, "%s MAXCLIENTS Destroy %d",
314                          m_session_str, c->m_seqno);
315                 if (c->m_server && c->m_server != this)
316                     delete c->m_server;
317                 c->m_server = 0;
318             }
319             else
320             {
321                 yaz_log (LOG_LOG, "%s MAXCLIENTS Reuse %d %d %s",
322                          m_session_str,
323                          c->m_seqno, parent->m_seqno, c->get_hostname());
324                 xfree (c->m_cookie);
325                 c->m_cookie = 0;
326                 if (cookie)
327                     c->m_cookie = xstrdup(cookie);
328                 c->m_seqno = parent->m_seqno;
329                 if (c->m_server && c->m_server != this)
330                 {
331                     c->m_server->m_client = 0;
332                     delete c->m_server;
333                 }
334                 (parent->m_seqno)++;
335                 return c;
336             }
337         }
338         else
339         {
340             yaz_log (LOG_LOG, "%s NEW %d %s",
341                      m_session_str, parent->m_seqno, m_proxyTarget);
342             c = new Yaz_ProxyClient(m_PDU_Observable->clone());
343             c->m_next = parent->m_clientPool;
344             if (c->m_next)
345                 c->m_next->m_prev = &c->m_next;
346             parent->m_clientPool = c;
347             c->m_prev = &parent->m_clientPool;
348         }
349
350         xfree (c->m_cookie);
351         c->m_cookie = 0;
352         if (cookie)
353             c->m_cookie = xstrdup(cookie);
354
355         c->m_seqno = parent->m_seqno;
356         c->client(m_proxyTarget);
357         c->m_init_flag = 0;
358         c->m_last_resultCount = 0;
359         c->m_last_ok = 0;
360         c->m_cache.clear();
361         c->m_sr_transform = 0;
362         c->m_waiting = 0;
363         c->m_resultSetStartPoint = 0;
364         c->timeout(30);
365
366         (parent->m_seqno)++;
367     }
368     yaz_log (LOG_DEBUG, "get_client 3 %p %p", this, c);
369     return c;
370 }
371
372 void Yaz_Proxy::display_diagrecs(Z_DiagRec **pp, int num)
373 {
374     int i;
375     for (i = 0; i<num; i++)
376     {
377         oident *ent;
378         Z_DefaultDiagFormat *r;
379         Z_DiagRec *p = pp[i];
380         if (p->which != Z_DiagRec_defaultFormat)
381         {
382             yaz_log(LOG_LOG, "%s Error no diagnostics", m_session_str);
383             return;
384         }
385         else
386             r = p->u.defaultFormat;
387         if (!(ent = oid_getentbyoid(r->diagnosticSetId)) ||
388             ent->oclass != CLASS_DIAGSET || ent->value != VAL_BIB1)
389             yaz_log(LOG_LOG, "%s Error unknown diagnostic set", m_session_str);
390         switch (r->which)
391         {
392         case Z_DefaultDiagFormat_v2Addinfo:
393             yaz_log(LOG_LOG, "%s Error %d %s:%s",
394                     m_session_str,
395                     *r->condition, diagbib1_str(*r->condition),
396                     r->u.v2Addinfo);
397             break;
398         case Z_DefaultDiagFormat_v3Addinfo:
399             yaz_log(LOG_LOG, "%s Error %d %s:%s",
400                     m_session_str,
401                     *r->condition, diagbib1_str(*r->condition),
402                     r->u.v3Addinfo);
403             break;
404         }
405     }
406 }
407
408 int Yaz_Proxy::send_to_client(Z_APDU *apdu)
409 {
410     int len = 0;
411     if (apdu->which == Z_APDU_searchResponse)
412     {
413         Z_SearchResponse *sr = apdu->u.searchResponse;
414         Z_Records *p = sr->records;
415         if (p && p->which == Z_Records_NSD)
416         {
417             Z_DiagRec dr, *dr_p = &dr;
418             dr.which = Z_DiagRec_defaultFormat;
419             dr.u.defaultFormat = p->u.nonSurrogateDiagnostic;
420
421             display_diagrecs(&dr_p, 1);
422         }
423         else
424         {
425             if (sr->resultCount)
426                 yaz_log(LOG_LOG, "%s %d hits", m_session_str,
427                         *sr->resultCount);
428         }
429     }
430     else if (apdu->which == Z_APDU_presentResponse)
431     {
432         Z_PresentResponse *sr = apdu->u.presentResponse;
433         Z_Records *p = sr->records;
434         if (p && p->which == Z_Records_NSD)
435         {
436             Z_DiagRec dr, *dr_p = &dr;
437             dr.which = Z_DiagRec_defaultFormat;
438             dr.u.defaultFormat = p->u.nonSurrogateDiagnostic;
439
440             display_diagrecs(&dr_p, 1);
441         }
442     }
443     int r = send_Z_PDU(apdu, &len);
444     yaz_log (LOG_LOG, "%s Sending %s to client %d bytes", m_session_str,
445              apdu_name(apdu), len);
446     m_bytes_sent += len;
447     m_bw_stat.add_bytes(len);
448     return r;
449 }
450
451 int Yaz_ProxyClient::send_to_target(Z_APDU *apdu)
452 {
453     int len = 0;
454     int r = send_Z_PDU(apdu, &len);
455     yaz_log (LOG_LOG, "%s Sending %s to %s %d bytes",
456              get_session_str(),
457              apdu_name(apdu), get_hostname(), len);
458     m_bytes_sent += len;
459     return r;
460 }
461
462 Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
463 {
464     if (*m_parent->m_optimize == '0')
465         return apdu;      // don't optimize result sets..
466     if (apdu->which == Z_APDU_presentRequest)
467     {
468         Z_PresentRequest *pr = apdu->u.presentRequest;
469         Z_NamePlusRecordList *npr;
470         int toget = *pr->numberOfRecordsRequested;
471         int start = *pr->resultSetStartPoint;
472
473         if (m_client->m_last_resultSetId &&
474             !strcmp(m_client->m_last_resultSetId, pr->resultSetId))
475         {
476             if (m_client->m_cache.lookup (odr_encode(), &npr, start, toget,
477                                           pr->preferredRecordSyntax,
478                                           pr->recordComposition))
479             {
480                 yaz_log (LOG_LOG, "%s Returned cache records for present request", 
481                          m_session_str);
482                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentResponse);
483                 new_apdu->u.presentResponse->referenceId = pr->referenceId;
484                 
485                 new_apdu->u.presentResponse->numberOfRecordsReturned
486                     = odr_intdup(odr_encode(), toget);
487                                                                  
488                 new_apdu->u.presentResponse->records = (Z_Records*)
489                     odr_malloc(odr_encode(), sizeof(Z_Records));
490                 new_apdu->u.presentResponse->records->which = Z_Records_DBOSD;
491                 new_apdu->u.presentResponse->records->u.databaseOrSurDiagnostics = npr;
492                 new_apdu->u.presentResponse->nextResultSetPosition =
493                     odr_intdup(odr_encode(), start+toget);
494
495                 send_to_client(new_apdu);
496                 return 0;
497             }
498         }
499     }
500
501     if (apdu->which != Z_APDU_searchRequest)
502         return apdu;
503     Z_SearchRequest *sr = apdu->u.searchRequest;
504     Yaz_Z_Query *this_query = new Yaz_Z_Query;
505     Yaz_Z_Databases this_databases;
506
507     this_databases.set(sr->num_databaseNames, (const char **)
508                        sr->databaseNames);
509     
510     this_query->set_Z_Query(sr->query);
511
512     char query_str[80];
513     this_query->print(query_str, sizeof(query_str)-1);
514     yaz_log(LOG_LOG, "%s Query %s", m_session_str, query_str);
515
516     if (m_client->m_last_ok && m_client->m_last_query &&
517         m_client->m_last_query->match(this_query) &&
518         !strcmp(m_client->m_last_resultSetId, sr->resultSetName) &&
519         m_client->m_last_databases.match(this_databases))
520     {
521         delete this_query;
522         if (m_client->m_last_resultCount > *sr->smallSetUpperBound &&
523             m_client->m_last_resultCount < *sr->largeSetLowerBound)
524         {
525             Z_NamePlusRecordList *npr;
526             int toget = *sr->mediumSetPresentNumber;
527             Z_RecordComposition *comp = 0;
528
529             if (toget > m_client->m_last_resultCount)
530                 toget = m_client->m_last_resultCount;
531             
532             if (sr->mediumSetElementSetNames)
533             {
534                 comp = (Z_RecordComposition *)
535                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
536                 comp->which = Z_RecordComp_simple;
537                 comp->u.simple = sr->mediumSetElementSetNames;
538             }
539  
540             if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
541                                           sr->preferredRecordSyntax, comp))
542             {
543                 yaz_log (LOG_LOG, "%s Returned cache records for medium set",
544                          m_session_str);
545                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
546                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
547                 new_apdu->u.searchResponse->resultCount =
548                     &m_client->m_last_resultCount;
549                 
550                 new_apdu->u.searchResponse->numberOfRecordsReturned
551                     = odr_intdup(odr_encode(), toget);
552                                                         
553                 new_apdu->u.searchResponse->presentStatus =
554                     odr_intdup(odr_encode(), Z_PresentStatus_success);
555                 new_apdu->u.searchResponse->records = (Z_Records*)
556                     odr_malloc(odr_encode(), sizeof(Z_Records));
557                 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
558                 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
559                 new_apdu->u.searchResponse->nextResultSetPosition =
560                     odr_intdup(odr_encode(), toget+1);
561                 send_to_client(new_apdu);
562                 return 0;
563             }
564             else
565             {
566                 // medium Set
567                 // send present request (medium size)
568                 yaz_log (LOG_LOG, "%s Optimizing search for medium set",
569                          m_session_str);
570
571                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
572                 Z_PresentRequest *pr = new_apdu->u.presentRequest;
573                 pr->referenceId = sr->referenceId;
574                 pr->resultSetId = sr->resultSetName;
575                 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
576                 *pr->numberOfRecordsRequested = toget;
577                 pr->recordComposition = comp;
578                 m_client->m_sr_transform = 1;
579                 return new_apdu;
580             }
581         }
582         else if (m_client->m_last_resultCount >= *sr->largeSetLowerBound ||
583             m_client->m_last_resultCount <= 0)
584         {
585             // large set. Return pseudo-search response immediately
586             yaz_log (LOG_LOG, "%s Optimizing search for large set",
587                      m_session_str);
588             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
589             new_apdu->u.searchResponse->referenceId = sr->referenceId;
590             new_apdu->u.searchResponse->resultCount =
591                 &m_client->m_last_resultCount;
592             send_to_client(new_apdu);
593             return 0;
594         }
595         else
596         {
597             Z_NamePlusRecordList *npr;
598             int toget = m_client->m_last_resultCount;
599             Z_RecordComposition *comp = 0;
600             // small set
601             // send a present request (small set)
602             
603             if (sr->smallSetElementSetNames)
604             {
605                 comp = (Z_RecordComposition *)
606                     odr_malloc(odr_encode(), sizeof(Z_RecordComposition));
607                 comp->which = Z_RecordComp_simple;
608                 comp->u.simple = sr->smallSetElementSetNames;
609             }
610
611             if (m_client->m_cache.lookup (odr_encode(), &npr, 1, toget,
612                                           sr->preferredRecordSyntax, comp))
613             {
614                 yaz_log (LOG_LOG, "%s Returned cache records for small set",
615                          m_session_str);
616                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
617                 new_apdu->u.searchResponse->referenceId = sr->referenceId;
618                 new_apdu->u.searchResponse->resultCount =
619                     &m_client->m_last_resultCount;
620                 
621                 new_apdu->u.searchResponse->numberOfRecordsReturned
622                     = odr_intdup(odr_encode(), toget);
623                                                                  
624                 new_apdu->u.searchResponse->presentStatus =
625                     odr_intdup(odr_encode(), Z_PresentStatus_success);
626                 new_apdu->u.searchResponse->records = (Z_Records*)
627                     odr_malloc(odr_encode(), sizeof(Z_Records));
628                 new_apdu->u.searchResponse->records->which = Z_Records_DBOSD;
629                 new_apdu->u.searchResponse->records->u.databaseOrSurDiagnostics = npr;
630                 new_apdu->u.searchResponse->nextResultSetPosition =
631                     odr_intdup(odr_encode(), toget+1);
632                 send_to_client(new_apdu);
633                 return 0;
634             }
635             else
636             {
637                 yaz_log (LOG_LOG, "%s Optimizing search for small set",
638                          m_session_str);
639                 Z_APDU *new_apdu = create_Z_PDU(Z_APDU_presentRequest);
640                 Z_PresentRequest *pr = new_apdu->u.presentRequest;
641                 pr->referenceId = sr->referenceId;
642                 pr->resultSetId = sr->resultSetName;
643                 pr->preferredRecordSyntax = sr->preferredRecordSyntax;
644                 *pr->numberOfRecordsRequested = toget;
645                 pr->recordComposition = comp;
646                 m_client->m_sr_transform = 1;
647                 return new_apdu;
648             }
649         }
650     }
651     else
652     {
653         delete m_client->m_last_query;
654         m_client->m_last_query = this_query;
655         m_client->m_last_ok = 0;
656         m_client->m_cache.clear();
657         m_client->m_resultSetStartPoint = 0;
658
659         xfree (m_client->m_last_resultSetId);
660         m_client->m_last_resultSetId = xstrdup (sr->resultSetName);
661
662         m_client->m_last_databases.set(sr->num_databaseNames,
663                                        (const char **) sr->databaseNames);
664     }
665     return apdu;
666 }
667
668
669 void Yaz_Proxy::recv_Z_PDU(Z_APDU *apdu, int len)
670 {
671     int reduce = 0;
672     m_bytes_recv += len;
673     
674     yaz_log (LOG_LOG, "%s Receiving %s from client %d bytes", m_session_str,
675              apdu_name(apdu), len);
676
677     if (m_bw_hold_PDU)     // double incoming PDU. shutdown now.
678         shutdown();
679
680     m_bw_stat.add_bytes(len);
681     m_pdu_stat.add_bytes(1);
682
683     int bw_total = m_bw_stat.get_total();
684     int pdu_total = m_pdu_stat.get_total();
685
686     yaz_log(LOG_LOG, "%s stat bw=%d pdu=%d limit-bw=%d limit-pdu=%d",
687             m_session_str, bw_total, pdu_total, m_bw_max, m_pdu_max);
688     if (m_bw_max)
689     {
690         if (bw_total > m_bw_max)
691         {
692             reduce = (bw_total/m_bw_max);
693         }
694     }
695     if (m_pdu_max)
696     {
697         if (pdu_total > m_pdu_max)
698         {
699             int nreduce = (60/m_pdu_max);
700             reduce = (reduce > nreduce) ? reduce : nreduce;
701         }
702     }
703     if (reduce)  
704     {
705         yaz_log(LOG_LOG, "%s Limit delay=%d", m_session_str, reduce);
706         m_bw_hold_PDU = apdu;  // save PDU and signal "on hold"
707         timeout(reduce);       // call us reduce seconds later
708     }
709     else
710         recv_Z_PDU_0(apdu);    // all fine. Proceed receive PDU as usual
711 }
712
713 void Yaz_Proxy::handle_max_record_retrieve(Z_APDU *apdu)
714 {
715     if (m_max_record_retrieve)
716     {
717         if (apdu->which == Z_APDU_presentRequest)
718         {
719             Z_PresentRequest *pr = apdu->u.presentRequest;
720             if (pr->numberOfRecordsRequested && 
721                 *pr->numberOfRecordsRequested > m_max_record_retrieve)
722                 *pr->numberOfRecordsRequested = m_max_record_retrieve;
723         }
724     }
725 }
726
727 void Yaz_Proxy::recv_Z_PDU_0(Z_APDU *apdu)
728 {
729     // Determine our client.
730     m_client = get_client(apdu);
731     if (!m_client)
732     {
733         delete this;
734         return;
735     }
736     m_client->m_server = this;
737
738     if (apdu->which == Z_APDU_initRequest)
739     {
740         if (m_client->m_init_flag)
741         {
742             Z_APDU *apdu = m_client->m_initResponse;
743             apdu->u.initResponse->otherInfo = 0;
744             if (m_client->m_cookie && *m_client->m_cookie)
745                 set_otherInformationString(apdu, VAL_COOKIE, 1,
746                                            m_client->m_cookie);
747             send_to_client(apdu);
748             return;
749         }
750         m_client->m_init_flag = 1;
751     }
752     handle_max_record_retrieve(apdu);
753
754     apdu = result_set_optimize(apdu);
755     if (!apdu)
756         return;
757
758     // delete other info part from PDU before sending to target
759     Z_OtherInformation **oi;
760     get_otherInfoAPDU(apdu, &oi);
761     if (oi)
762         *oi = 0;
763
764     if (apdu->which == Z_APDU_presentRequest &&
765         m_client->m_resultSetStartPoint == 0)
766     {
767         Z_PresentRequest *pr = apdu->u.presentRequest;
768         m_client->m_resultSetStartPoint = *pr->resultSetStartPoint;
769         m_client->m_cache.copy_presentRequest(apdu->u.presentRequest);
770     } else {
771         m_client->m_resultSetStartPoint = 0;
772     }
773     if (m_client->send_to_target(apdu) < 0)
774     {
775         delete m_client;
776         m_client = 0;
777         delete this;
778     }
779     else
780         m_client->m_waiting = 1;
781 }
782
783 void Yaz_Proxy::connectNotify()
784 {
785 }
786
787 void Yaz_Proxy::shutdown()
788 {
789     // only keep if keep_alive flag is set...
790     if (m_keepalive && m_client && m_client->m_waiting == 0)
791     {
792         yaz_log (LOG_LOG, "%s Shutdown (client to proxy) keepalive %s",
793                  m_session_str,
794                  m_client->get_hostname());
795         assert (m_client->m_waiting != 2);
796         // Tell client (if any) that no server connection is there..
797         m_client->m_server = 0;
798     }
799     else if (m_client)
800     {
801         yaz_log (LOG_LOG, "%s Shutdown (client to proxy) close %s",
802                  m_session_str,
803                  m_client->get_hostname());
804         assert (m_client->m_waiting != 2);
805         delete m_client;
806     }
807     else if (!m_parent)
808     {
809         yaz_log (LOG_LOG, "%s shutdown (client to proxy) bad state",
810                  m_session_str);
811         assert (m_parent);
812     }
813     else 
814     {
815         yaz_log (LOG_LOG, "%s Shutdown (client to proxy)",
816                  m_session_str);
817     }
818     delete this;
819 }
820
821 const char *Yaz_ProxyClient::get_session_str() 
822 {
823     if (!m_server)
824         return "0";
825     return m_server->get_session_str();
826 }
827
828 void Yaz_ProxyClient::shutdown()
829 {
830     yaz_log (LOG_LOG, "%s Shutdown (proxy to target) %s", get_session_str(),
831              get_hostname());
832     delete m_server;
833     delete this;
834 }
835
836 void Yaz_Proxy::failNotify()
837 {
838     yaz_log (LOG_LOG, "%s Connection closed by client",
839              get_session_str());
840     shutdown();
841 }
842
843 void Yaz_ProxyClient::failNotify()
844 {
845     yaz_log (LOG_LOG, "%s Connection closed by target %s", 
846              get_session_str(), get_hostname());
847     shutdown();
848 }
849
850 void Yaz_ProxyClient::connectNotify()
851 {
852     yaz_log (LOG_LOG, "%s Connection accepted by %s", get_session_str(),
853              get_hostname());
854     int to;
855     if (m_server)
856         to = m_server->get_target_idletime();
857     else
858         to = 600;
859     timeout(to);
860 }
861
862 IYaz_PDU_Observer *Yaz_ProxyClient::sessionNotify(IYaz_PDU_Observable
863                                                   *the_PDU_Observable, int fd)
864 {
865     return new Yaz_ProxyClient(the_PDU_Observable);
866 }
867
868 Yaz_ProxyClient::~Yaz_ProxyClient()
869 {
870     if (m_prev)
871         *m_prev = m_next;
872     if (m_next)
873         m_next->m_prev = m_prev;
874     m_waiting = 2;     // for debugging purposes only.
875     odr_destroy(m_init_odr);
876     delete m_last_query;
877     xfree (m_last_resultSetId);
878     xfree (m_cookie);
879 }
880
881 void Yaz_Proxy::timeoutNotify()
882 {
883     if (m_bw_hold_PDU)
884     {
885         timeout(m_client_idletime);
886         Z_APDU *apdu = m_bw_hold_PDU;
887         m_bw_hold_PDU = 0;
888         recv_Z_PDU_0(apdu);
889     }
890     else
891     {
892         yaz_log (LOG_LOG, "%s Timeout (client to proxy)", m_session_str);
893         shutdown();
894     }
895 }
896
897 void Yaz_ProxyClient::timeoutNotify()
898 {
899     yaz_log (LOG_LOG, "%s Timeout (proxy to target) %s", get_session_str(),
900              get_hostname());
901     shutdown();
902 }
903
904 Yaz_ProxyClient::Yaz_ProxyClient(IYaz_PDU_Observable *the_PDU_Observable) :
905     Yaz_Z_Assoc (the_PDU_Observable)
906 {
907     m_cookie = 0;
908     m_next = 0;
909     m_prev = 0;
910     m_init_flag = 0;
911     m_last_query = 0;
912     m_last_resultSetId = 0;
913     m_last_resultCount = 0;
914     m_last_ok = 0;
915     m_sr_transform = 0;
916     m_waiting = 0;
917     m_init_odr = odr_createmem (ODR_DECODE);
918     m_initResponse = 0;
919     m_resultSetStartPoint = 0;
920     m_bytes_sent = m_bytes_recv = 0;
921 }
922
923 const char *Yaz_Proxy::option(const char *name, const char *value)
924 {
925     if (!strcmp (name, "optimize")) {
926         if (value) {
927             xfree (m_optimize); 
928             m_optimize = xstrdup (value);
929         }
930         return m_optimize;
931     }
932     return 0;
933 }
934
935 void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu, int len)
936 {
937     m_bytes_recv += len;
938     m_waiting = 0;
939     yaz_log (LOG_LOG, "%s Receiving %s from %s %d bytes", get_session_str(),
940              apdu_name(apdu), get_hostname(), len);
941     if (apdu->which == Z_APDU_initResponse)
942     {
943         NMEM nmem = odr_extract_mem (odr_decode());
944         odr_reset (m_init_odr);
945         nmem_transfer (m_init_odr->mem, nmem);
946         m_initResponse = apdu;
947         nmem_destroy (nmem);
948     }
949     if (apdu->which == Z_APDU_searchResponse)
950     {
951         Z_SearchResponse *sr = apdu->u.searchResponse;
952         m_last_resultCount = *sr->resultCount;
953         int status = *sr->searchStatus;
954         if (status && (!sr->records || sr->records->which == Z_Records_DBOSD))
955         {
956             m_last_ok = 1;
957             
958             if (sr->records && sr->records->which == Z_Records_DBOSD)
959             {
960                 m_cache.add(odr_decode(),
961                             sr->records->u.databaseOrSurDiagnostics, 1,
962                             *sr->resultCount);
963             }
964         }
965     }
966     if (apdu->which == Z_APDU_presentResponse)
967     {
968         Z_PresentResponse *pr = apdu->u.presentResponse;
969         if (m_sr_transform)
970         {
971             m_sr_transform = 0;
972             Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
973             Z_SearchResponse *sr = new_apdu->u.searchResponse;
974             sr->referenceId = pr->referenceId;
975             *sr->resultCount = m_last_resultCount;
976             sr->records = pr->records;
977             sr->nextResultSetPosition = pr->nextResultSetPosition;
978             sr->numberOfRecordsReturned = pr->numberOfRecordsReturned;
979             apdu = new_apdu;
980         }
981         if (pr->records->which == Z_Records_DBOSD && m_resultSetStartPoint)
982         {
983             m_cache.add(odr_decode(),
984                         pr->records->u.databaseOrSurDiagnostics,
985                         m_resultSetStartPoint, -1);
986             m_resultSetStartPoint = 0;
987         }
988     }
989     if (m_cookie)
990         set_otherInformationString (apdu, VAL_COOKIE, 1, m_cookie);
991     if (m_server)
992     {
993         m_server->send_to_client(apdu);
994     }
995     if (apdu->which == Z_APDU_close)
996     {
997         shutdown();
998     }
999 }