Use AM_LDFLAGS instead of LDFLAGS
[yazpp-moved-to-github.git] / src / yaz-proxy.cpp
index d623c36..97ec2de 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (c) 1998-2003, Index Data.
+ * Copyright (c) 1998-2004, Index Data.
  * See the file LICENSE for details.
  * 
- * $Id: yaz-proxy.cpp,v 1.71 2003-12-16 14:17:01 adam Exp $
+ * $Id: yaz-proxy.cpp,v 1.91 2004-01-15 23:44:58 adam Exp $
  */
 
 #include <assert.h>
 #include <yaz++/proxy.h>
 #include <yaz/pquery.h>
 
+#if HAVE_XSLT
+#include <libxslt/xsltutils.h>
+#include <libxslt/transform.h>
+#endif
+
 static const char *apdu_name(Z_APDU *apdu)
 {
     switch (apdu->which)
@@ -101,11 +106,18 @@ Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable,
     m_invalid_session = 0;
     m_config = 0;
     m_marcxml_flag = 0;
+    m_stylesheet_schema = 0;
+    m_s2z_stylesheet = 0;
+    m_s2z_database = 0;
+    m_schema = 0;
     m_initRequest_apdu = 0;
     m_initRequest_mem = 0;
+    m_initRequest_options = 0;
+    m_initRequest_version = 0;
     m_apdu_invalid_session = 0;
     m_mem_invalid_session = 0;
-    m_s2z_odr = 0;
+    m_s2z_odr_init = 0;
+    m_s2z_odr_search = 0;
     m_s2z_init_apdu = 0;
     m_s2z_search_apdu = 0;
     m_s2z_present_apdu = 0;
@@ -113,6 +125,8 @@ Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable,
     m_http_version = 0;
     m_soap_ns = 0;
     m_s2z_packing = Z_SRW_recordPacking_string;
+    m_time_tv.tv_sec = 0;
+    m_time_tv.tv_usec = 0;
 }
 
 Yaz_Proxy::~Yaz_Proxy()
@@ -125,8 +139,12 @@ Yaz_Proxy::~Yaz_Proxy()
     xfree (m_default_target);
     xfree (m_proxy_authentication);
     xfree (m_optimize);
-    if (m_s2z_odr)
-       odr_destroy(m_s2z_odr);
+    xfree (m_stylesheet_schema);
+    xfree (m_schema);
+    if (m_s2z_odr_init)
+       odr_destroy(m_s2z_odr_init);
+    if (m_s2z_odr_search)
+       odr_destroy(m_s2z_odr_search);
     delete m_config;
 }
 
@@ -278,7 +296,6 @@ Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu, const char *cookie,
 {
     assert (m_parent);
     Yaz_Proxy *parent = m_parent;
-    Z_OtherInformation **oi;
     Yaz_ProxyClient *c = m_client;
     
     if (!m_proxyTarget)
@@ -318,10 +335,7 @@ Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu, const char *cookie,
            timeout(m_client_idletime);
        }
        if (cql2rpn_fname)
-       {
-           yaz_log(LOG_LOG, "set_pqf_file %s", cql2rpn_fname);
            m_cql2rpn.set_pqf_file(cql2rpn_fname);
-       }
        if (!url[0])
        {
            yaz_log(LOG_LOG, "%sNo default target", m_session_str);
@@ -415,7 +429,7 @@ Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu, const char *cookie,
     {
        if (apdu->which != Z_APDU_initRequest)
        {
-           yaz_log (LOG_LOG, "no first INIT!");
+           yaz_log (LOG_LOG, "%sno init request as first PDU", m_session_str);
            return 0;
        }
         Z_InitRequest *initRequest = apdu->u.initRequest;
@@ -434,7 +448,6 @@ Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu, const char *cookie,
                     odr_strdup (odr_encode(), m_proxy_authentication);
             }
         }
-
        // go through list of clients - and find the lowest/oldest one.
        Yaz_ProxyClient *c_min = 0;
        int min_seq = -1;
@@ -570,6 +583,45 @@ void Yaz_Proxy::display_diagrecs(Z_DiagRec **pp, int num)
     }
 }
 
+void Yaz_Proxy::convert_xsl(Z_NamePlusRecordList *p)
+{
+    if (!m_stylesheet_schema)
+       return;
+    xsltStylesheetPtr xsp;
+
+    xsp = xsltParseStylesheetFile((const xmlChar *) m_stylesheet_schema);
+
+    int i;
+    for (i = 0; i < p->num_records; i++)
+    {
+       Z_NamePlusRecord *npr = p->records[i];
+       if (npr->which == Z_NamePlusRecord_databaseRecord)
+       {
+           Z_External *r = npr->u.databaseRecord;
+           if (r->which == Z_External_octet)
+           {
+               xmlDocPtr res, doc = xmlParseMemory(
+                   (char*) r->u.octet_aligned->buf,
+                   r->u.octet_aligned->len);
+               
+               res = xsltApplyStylesheet(xsp, doc, 0);
+               
+               xmlChar *out_buf;
+               int out_len;
+               xmlDocDumpFormatMemory (res, &out_buf, &out_len, 1);
+
+               p->records[i]->u.databaseRecord = 
+                   z_ext_record(odr_encode(), VAL_TEXT_XML,
+                                (char*) out_buf, out_len);
+               xmlFree(out_buf);
+               xmlFreeDoc(doc);
+               xmlFreeDoc(res);
+           }
+       }
+    }
+    xsltFreeStylesheet(xsp);
+}
+
 void Yaz_Proxy::convert_to_marcxml(Z_NamePlusRecordList *p)
 {
     int i;
@@ -631,17 +683,38 @@ void Yaz_Proxy::convert_to_marcxml(Z_NamePlusRecordList *p)
     yaz_marc_destroy(mt);
 }
 
+void Yaz_Proxy::logtime()
+{
+    if (m_time_tv.tv_sec)
+    {
+       struct timeval tv;
+       gettimeofday(&tv, 0);
+       long diff = (tv.tv_sec - m_time_tv.tv_sec)*1000000 +
+           (tv.tv_usec - m_time_tv.tv_usec);
+       if (diff >= 0)
+           yaz_log(LOG_LOG, "%sElapsed %ld.%03ld", m_session_str,
+                   diff/1000000, (diff/1000)%1000);
+    }
+    m_time_tv.tv_sec = 0;
+    m_time_tv.tv_usec = 0;
+}
+
 int Yaz_Proxy::send_http_response(int code)
 {
     ODR o = odr_encode();
-    const char *ctype = "text/xml";
     Z_GDU *gdu = z_get_HTTP_Response(o, code);
     Z_HTTP_Response *hres = gdu->u.HTTP_Response;
     if (m_http_version)
        hres->version = odr_strdup(o, m_http_version);
+    m_http_keepalive = 0;
+    if (m_log_mask & PROXY_LOG_REQ_CLIENT)
+    {
+       yaz_log (LOG_LOG, "%sSending %s to client", m_session_str,
+                gdu_name(gdu));
+    }
     int len;
     int r = send_GDU(gdu, &len);
-    delete this;
+    logtime();
     return r;
 }
 
@@ -658,8 +731,8 @@ int Yaz_Proxy::send_srw_response(Z_SRW_PDU *srw_pdu)
         z_HTTP_header_add(o, &hres->headers, "Connection", "Keep-Alive");
 
     static Z_SOAP_Handler soap_handlers[2] = {
-#if HAVE_XML2
-       {"http://www.loc.gov/zing/srw/v1.0/", 0,
+#if HAVE_XSLT
+       {"http://www.loc.gov/zing/srw/", 0,
         (Z_SOAP_fun) yaz_srw_codec},
 #endif
        {0, 0, 0}
@@ -673,16 +746,21 @@ int Yaz_Proxy::send_srw_response(Z_SRW_PDU *srw_pdu)
     soap_package->u.generic->ns = soap_handlers[0].ns;
     soap_package->u.generic->p = (void *) srw_pdu;
     soap_package->ns = m_soap_ns;
-    int ret = z_soap_codec_enc(o, &soap_package,
-                              &hres->content_buf, &hres->content_len,
-                              soap_handlers, 0);
+    z_soap_codec_enc_xsl(o, &soap_package,
+                        &hres->content_buf, &hres->content_len,
+                        soap_handlers, 0, m_s2z_stylesheet);
+    if (m_log_mask & PROXY_LOG_REQ_CLIENT)
+    {
+       yaz_log (LOG_LOG, "%sSending %s to client", m_session_str,
+                gdu_name(gdu));
+    }
     int len;
     int r = send_GDU(gdu, &len);
-    if (!m_http_keepalive)
-       delete this;
+    logtime();
+    return r;
 }
 
-int Yaz_Proxy::send_to_srw_client_error(int srw_error)
+int Yaz_Proxy::send_to_srw_client_error(int srw_error, const char *add)
 {
     ODR o = odr_encode();
     Z_SRW_PDU *srw_pdu = yaz_srw_get(o, Z_SRW_searchRetrieve_response);
@@ -692,7 +770,7 @@ int Yaz_Proxy::send_to_srw_client_error(int srw_error)
     srw_res->diagnostics = (Z_SRW_diagnostic *)
        odr_malloc(o, sizeof(*srw_res->diagnostics));
     srw_res->diagnostics[0].code =  odr_intdup(o, srw_error);
-    srw_res->diagnostics[0].details = 0;
+    srw_res->diagnostics[0].details = add ? odr_strdup(o, add) : 0;
     return send_srw_response(srw_pdu);
 }
 
@@ -711,7 +789,7 @@ int Yaz_Proxy::z_to_srw_diag(ODR o, Z_SRW_searchRetrieveResponse *srw_res,
     return 0;
 }
 
-int Yaz_Proxy::send_to_srw_client_ok(int hits, Z_Records *records)
+int Yaz_Proxy::send_to_srw_client_ok(int hits, Z_Records *records, int start)
 {
     ODR o = odr_encode();
     Z_SRW_PDU *srw_pdu = yaz_srw_get(o, Z_SRW_searchRetrieve_response);
@@ -734,19 +812,19 @@ int Yaz_Proxy::send_to_srw_client_ok(int hits, Z_Records *records)
                srw_res->records[i].recordPacking = m_s2z_packing;
                srw_res->records[i].recordData_buf = "67";
                srw_res->records[i].recordData_len = 2;
-               srw_res->records[i].recordPosition = 0;
+               srw_res->records[i].recordPosition = odr_intdup(o, i+start);
                continue;
            }
            Z_External *r = npr->u.databaseRecord;
            oident *ent = oid_getentbyoid(r->direct_reference);
            if (r->which == Z_External_octet && ent->value == VAL_TEXT_XML)
            {
-               srw_res->records[i].recordSchema = "http://www.loc.gov/marcxml/";
+               srw_res->records[i].recordSchema = m_schema;
                srw_res->records[i].recordPacking = m_s2z_packing;
                srw_res->records[i].recordData_buf = (char*) 
                    r->u.octet_aligned->buf;
                srw_res->records[i].recordData_len = r->u.octet_aligned->len;
-               srw_res->records[i].recordPosition = 0;
+               srw_res->records[i].recordPosition = odr_intdup(o, i+start);
            }
            else
            {
@@ -754,7 +832,7 @@ int Yaz_Proxy::send_to_srw_client_ok(int hits, Z_Records *records)
                srw_res->records[i].recordPacking = m_s2z_packing;
                srw_res->records[i].recordData_buf = "67";
                srw_res->records[i].recordData_len = 2;
-               srw_res->records[i].recordPosition = 0;
+               srw_res->records[i].recordPosition = odr_intdup(o, i+start);
            }
        }
     }
@@ -769,47 +847,101 @@ int Yaz_Proxy::send_to_srw_client_ok(int hits, Z_Records *records)
     return send_srw_response(srw_pdu);
     
 }
-int Yaz_Proxy::send_PDU_convert(Z_APDU *apdu, int *len)
+
+int Yaz_Proxy::send_srw_explain_response(Z_SRW_diagnostic *diagnostics,
+                                       int num_diagnostics)
 {
-    if (m_s2z_init_apdu && apdu->which == Z_APDU_initResponse)
+    Yaz_ProxyConfig *cfg = check_reconfigure();
+    if (cfg)
     {
-       m_s2z_init_apdu = 0;
-       Z_InitResponse *res = apdu->u.initResponse;
-       if (*res->result == 0)
-       {
-           send_to_srw_client_error(3);
-       }
-       else
+       int len;
+       char *b = cfg->get_explain(odr_encode(), 0 /* target */,
+                                  m_s2z_database, &len);
+       if (b)
        {
-           handle_incoming_Z_PDU(m_s2z_search_apdu);
+           Z_SRW_PDU *res = yaz_srw_get(odr_encode(), Z_SRW_explain_response);
+           Z_SRW_explainResponse *er = res->u.explain_response;
+
+           er->record.recordData_buf = b;
+           er->record.recordData_len = len;
+           er->record.recordPacking = m_s2z_packing;
+
+           er->diagnostics = diagnostics;
+           er->num_diagnostics = num_diagnostics;
+           return send_srw_response(res);
        }
     }
-    else if (m_s2z_search_apdu && apdu->which == Z_APDU_searchResponse)
+    return send_http_response(404);
+}
+
+int Yaz_Proxy::send_PDU_convert(Z_APDU *apdu, int *len)
+{
+    if (m_http_version)
     {
-       m_s2z_search_apdu = 0;
-       Z_SearchResponse *res = apdu->u.searchResponse;
-       m_s2z_hit_count = *res->resultCount;
-       if (res->records && res->records->which == Z_Records_NSD)
+       if (apdu->which == Z_APDU_initResponse)
        {
-           send_to_srw_client_ok(0, res->records);
+           Z_InitResponse *res = apdu->u.initResponse;
+           if (*res->result == 0)
+           {
+               send_to_srw_client_error(3, 0);
+           }
+           else if (!m_s2z_search_apdu)
+           {
+               send_srw_explain_response(0, 0);
+           }
+           else
+           {
+               handle_incoming_Z_PDU(m_s2z_search_apdu);
+           }
        }
-       else if (m_s2z_present_apdu)
+       else if (m_s2z_search_apdu && apdu->which == Z_APDU_searchResponse)
        {
-           handle_incoming_Z_PDU(m_s2z_present_apdu);
+           m_s2z_search_apdu = 0;
+           Z_SearchResponse *res = apdu->u.searchResponse;
+           m_s2z_hit_count = *res->resultCount;
+           if (res->records && res->records->which == Z_Records_NSD)
+           {
+               send_to_srw_client_ok(0, res->records, 1);
+           }
+           else if (m_s2z_present_apdu && m_s2z_hit_count > 0)
+           {
+               // adjust 
+               Z_PresentRequest *pr = m_s2z_present_apdu->u.presentRequest;
+               
+               if (*pr->resultSetStartPoint <= m_s2z_hit_count)
+               {
+                   if (*pr->numberOfRecordsRequested+ *pr->resultSetStartPoint
+                       > m_s2z_hit_count)
+                       *pr->numberOfRecordsRequested =
+                           1 + m_s2z_hit_count - *pr->resultSetStartPoint;
+               }
+               handle_incoming_Z_PDU(m_s2z_present_apdu);
+           }
+           else
+           {
+               m_s2z_present_apdu = 0;
+               send_to_srw_client_ok(m_s2z_hit_count, res->records, 1);
+           }
        }
-       else
+       else if (m_s2z_present_apdu && apdu->which == Z_APDU_presentResponse)
        {
-           send_to_srw_client_ok(m_s2z_hit_count, res->records);
+           int start = 
+               *m_s2z_present_apdu->u.presentRequest->resultSetStartPoint;
+
+           m_s2z_present_apdu = 0;
+           Z_PresentResponse *res = apdu->u.presentResponse;
+           send_to_srw_client_ok(m_s2z_hit_count, res->records, start);
        }
     }
-    else if (m_s2z_present_apdu && apdu->which == Z_APDU_presentResponse)
+    else
     {
-       m_s2z_present_apdu = 0;
-       Z_PresentResponse *res = apdu->u.presentResponse;
-       send_to_srw_client_ok(m_s2z_hit_count, res->records);
+       if (m_log_mask & PROXY_LOG_REQ_CLIENT)
+           yaz_log (LOG_LOG, "%sSending %s to client", m_session_str,
+                    apdu_name(apdu));
+       int r = send_Z_PDU(apdu, len);
+       logtime();
+       return r;
     }
-    else
-       return send_Z_PDU(apdu, len);
     return 0;
 }
 
@@ -832,8 +964,12 @@ int Yaz_Proxy::send_to_client(Z_APDU *apdu)
        }
        else
        {
-           if (m_marcxml_flag && p && p->which == Z_Records_DBOSD)
-               convert_to_marcxml(p->u.databaseOrSurDiagnostics);
+           if (p && p->which == Z_Records_DBOSD)
+           {
+               if (m_marcxml_flag)
+                   convert_to_marcxml(p->u.databaseOrSurDiagnostics);
+               convert_xsl(p->u.databaseOrSurDiagnostics);
+           }
            if (sr->resultCount)
            {
                yaz_log(LOG_LOG, "%s%d hits", m_session_str,
@@ -864,15 +1000,47 @@ int Yaz_Proxy::send_to_client(Z_APDU *apdu)
                *sr->presentStatus = Z_PresentStatus_failure;
            display_diagrecs(&dr_p, 1);
        }
-       if (m_marcxml_flag && p && p->which == Z_Records_DBOSD)
-           convert_to_marcxml(p->u.databaseOrSurDiagnostics);
+       if (p && p->which == Z_Records_DBOSD)
+       {
+           if (m_marcxml_flag)
+               convert_to_marcxml(p->u.databaseOrSurDiagnostics);
+           convert_xsl(p->u.databaseOrSurDiagnostics);
+       }
+    }
+    else if (apdu->which == Z_APDU_initResponse)
+    {
+       if (m_initRequest_options)
+       {
+           Z_Options *nopt = 
+               (Odr_bitmask *)odr_malloc(odr_encode(),
+                                         sizeof(Odr_bitmask));
+           ODR_MASK_ZERO(nopt);
+
+           int i;
+           for (i = 0; i<24; i++)
+               if (ODR_MASK_GET(m_initRequest_options, i) &&
+                   ODR_MASK_GET(apdu->u.initResponse->options, i))
+                   ODR_MASK_SET(nopt, i);
+           apdu->u.initResponse->options = nopt;           
+       }
+       if (m_initRequest_version)
+       {
+           Z_ProtocolVersion *nopt = 
+               (Odr_bitmask *)odr_malloc(odr_encode(),
+                                         sizeof(Odr_bitmask));
+           ODR_MASK_ZERO(nopt);
+
+           int i;
+           for (i = 0; i<8; i++)
+               if (ODR_MASK_GET(m_initRequest_version, i) &&
+                   ODR_MASK_GET(apdu->u.initResponse->protocolVersion, i))
+                   ODR_MASK_SET(nopt, i);
+           apdu->u.initResponse->protocolVersion = nopt;           
+       }
     }
     int r = send_PDU_convert(apdu, &len);
     if (r)
        return r;
-    if (m_log_mask & PROXY_LOG_APDU_CLIENT)
-       yaz_log (LOG_DEBUG, "%sSending %s to client %d bytes", m_session_str,
-                apdu_name(apdu), len);
     m_bytes_sent += len;
     m_bw_stat.add_bytes(len);
     if (kill_session)
@@ -881,17 +1049,30 @@ int Yaz_Proxy::send_to_client(Z_APDU *apdu)
        m_client = 0;
        m_parent->pre_init();
     }
+    if (m_http_version)
+    {
+       if (!m_http_keepalive)
+       {
+#if 1
+           timeout(1);
+#else
+           shutdown();
+           return -1;
+#endif
+       }
+    }
     return r;
 }
 
 int Yaz_ProxyClient::send_to_target(Z_APDU *apdu)
 {
     int len = 0;
+    const char *apdu_name_tmp = apdu_name(apdu);
     int r = send_Z_PDU(apdu, &len);
     if (m_root->get_log_mask() & PROXY_LOG_REQ_SERVER)
        yaz_log (LOG_LOG, "%sSending %s to %s %d bytes",
                 get_session_str(),
-                apdu_name(apdu), get_hostname(), len);
+                apdu_name_tmp, get_hostname(), len);
     m_bytes_sent += len;
     return r;
 }
@@ -1124,12 +1305,17 @@ Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
 }
 
 
-void Yaz_Proxy::recv_GDU(Z_GDU *apdu, int len)
+void Yaz_Proxy::inc_request_no()
 {
     char *cp = strchr(m_session_str, ' ');
     m_request_no++;
     if (cp)
        sprintf(cp+1, "%d ", m_request_no);
+}
+
+void Yaz_Proxy::recv_GDU(Z_GDU *apdu, int len)
+{
+    inc_request_no();
 
     m_bytes_recv += len;
     
@@ -1143,11 +1329,11 @@ void Yaz_Proxy::recv_GDU(Z_GDU *apdu, int len)
     m_bw_stat.add_bytes(len);
     m_pdu_stat.add_bytes(1);
 
+    gettimeofday(&m_time_tv, 0);
+
     int bw_total = m_bw_stat.get_total();
     int pdu_total = m_pdu_stat.get_total();
 
-    yaz_log(LOG_LOG, "%sstat bw=%d pdu=%d limit-bw=%d limit-pdu=%d",
-           m_session_str, bw_total, pdu_total, m_bw_max, m_pdu_max);
     int reduce = 0;
     if (m_bw_max)
     {
@@ -1166,7 +1352,10 @@ void Yaz_Proxy::recv_GDU(Z_GDU *apdu, int len)
     }
     if (reduce)  
     {
-       yaz_log(LOG_LOG, "%sLimit delay=%d", m_session_str, reduce);
+       yaz_log(LOG_LOG, "%sdelay=%d bw=%d pdu=%d limit-bw=%d limit-pdu=%d",
+               m_session_str, reduce, bw_total, pdu_total,
+               m_bw_max, m_pdu_max);
+       
        m_bw_hold_PDU = apdu;  // save PDU and signal "on hold"
        timeout(reduce);       // call us reduce seconds later
     }
@@ -1222,16 +1411,32 @@ Z_APDU *Yaz_Proxy::handle_query_transformation(Z_APDU *apdu)
     {
        Z_RPNQuery *rpnquery = 0;
        Z_SearchRequest *sr = apdu->u.searchRequest;
+       char *addinfo = 0;
        
        yaz_log(LOG_LOG, "%sCQL: %s", m_session_str,
                sr->query->u.type_104->u.cql);
 
        int r = m_cql2rpn.query_transform(sr->query->u.type_104->u.cql,
-                                         &rpnquery, odr_encode());
+                                         &rpnquery, odr_encode(),
+                                         &addinfo);
        if (r == -3)
            yaz_log(LOG_LOG, "%sNo CQL to RPN table", m_session_str);
        else if (r)
+       {
            yaz_log(LOG_LOG, "%sCQL Conversion error %d", m_session_str, r);
+           Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
+
+           new_apdu->u.searchResponse->referenceId = sr->referenceId;
+           new_apdu->u.searchResponse->records =
+               create_nonSurrogateDiagnostics(odr_encode(),
+                                              yaz_diag_srw_to_bib1(r),
+                                              addinfo);
+           *new_apdu->u.searchResponse->searchStatus = 0;
+
+           send_to_client(new_apdu);
+
+           return 0;
+       }
        else
        {
            sr->query->which = Z_Query_type_1;
@@ -1280,17 +1485,24 @@ Z_APDU *Yaz_Proxy::handle_syntax_validation(Z_APDU *apdu)
        int err = 0;
        char *addinfo = 0;
        Yaz_ProxyConfig *cfg = check_reconfigure();
-       
+
+       Z_RecordComposition rc_temp, *rc = 0;
+       if (sr->smallSetElementSetNames)
+       {
+           rc_temp.which = Z_RecordComp_simple;
+           rc_temp.u.simple = sr->smallSetElementSetNames;
+           rc = &rc_temp;
+       }
+           
        if (cfg)
            err = cfg->check_syntax(odr_encode(),
                                    m_default_target,
-                                   sr->preferredRecordSyntax,
-                                   &addinfo);
+                                   sr->preferredRecordSyntax, rc,
+                                   &addinfo, &m_stylesheet_schema, &m_schema);
        if (err == -1)
        {
            sr->preferredRecordSyntax =
-               yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN,
-                                      VAL_USMARC);
+               yaz_oidval_to_z3950oid(odr_encode(), CLASS_RECSYN, VAL_USMARC);
            m_marcxml_flag = 1;
        }
        else if (err)
@@ -1317,12 +1529,12 @@ Z_APDU *Yaz_Proxy::handle_syntax_validation(Z_APDU *apdu)
        if (cfg)
            err = cfg->check_syntax(odr_encode(), m_default_target,
                                    pr->preferredRecordSyntax,
-                                   &addinfo);
+                                   pr->recordComposition,
+                                   &addinfo, &m_stylesheet_schema, &m_schema);
        if (err == -1)
        {
            pr->preferredRecordSyntax =
-               yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN,
-                                      VAL_USMARC);
+               yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN, VAL_USMARC);
            m_marcxml_flag = 1;
        }
        else if (err)
@@ -1343,221 +1555,28 @@ Z_APDU *Yaz_Proxy::handle_syntax_validation(Z_APDU *apdu)
     return apdu;
 }
 
-static int hex_digit (int ch)
-{
-    if (ch >= '0' && ch <= '9')
-        return ch - '0';
-    else if (ch >= 'a' && ch <= 'f')
-        return ch - 'a'+10;
-    else if (ch >= 'A' && ch <= 'F')
-        return ch - 'A'+10;
-    return 0;
-}
-
-static char *uri_val(const char *path, const char *name, ODR o)
+Z_ElementSetNames *Yaz_Proxy::mk_esn_from_schema(ODR o, const char *schema)
 {
-    size_t nlen = strlen(name);
-    if (*path != '?')
-        return 0;
-    path++;
-    while (path && *path)
-    {
-        const char *p1 = strchr(path, '=');
-        if (!p1)
-            break;
-        if ((size_t)(p1 - path) == nlen && !memcmp(path, name, nlen))
-        {
-            size_t i = 0;
-            char *ret;
-            
-            path = p1 + 1;
-            p1 = strchr(path, '&');
-            if (!p1)
-                p1 = strlen(path) + path;
-            ret = (char*) odr_malloc(o, p1 - path + 1);
-            while (*path && *path != '&')
-            {
-                if (*path == '+')
-                {
-                    ret[i++] = ' ';
-                    path++;
-                }
-                else if (*path == '%' && path[1] && path[2])
-                {
-                    ret[i++] = hex_digit (path[1])*16 + hex_digit (path[2]);
-                    path = path + 3;
-                }
-                else
-                    ret[i++] = *path++;
-            }
-            ret[i] = '\0';
-            return ret;
-        }
-        path = strchr(p1, '&');
-        if (path)
-            path++;
-    }
-    return 0;
-}
-
-void uri_val_int(const char *path, const char *name, ODR o, int **intp)
-{
-    const char *v = uri_val(path, name, o);
-    if (v)
-        *intp = odr_intdup(o, atoi(v));
-}
-
-int yaz_check_for_sru(Z_HTTP_Request *hreq, Z_SRW_PDU **srw_pdu,
-                     char **soap_ns, ODR decode)
-{
-    if (!strcmp(hreq->method, "GET"))
-    {
-        char *db = "Default";
-        const char *p0 = hreq->path, *p1;
-#if HAVE_XML2
-        int ret = -1;
-        char *charset = 0;
-        Z_SOAP *soap_package = 0;
-        static Z_SOAP_Handler soap_handlers[2] = {
-            {"http://www.loc.gov/zing/srw/v1.0/", 0,
-             (Z_SOAP_fun) yaz_srw_codec},
-            {0, 0, 0}
-        };
-#endif
-        
-        if (*p0 == '/')
-            p0++;
-        p1 = strchr(p0, '?');
-        if (!p1)
-            p1 = p0 + strlen(p0);
-        if (p1 != p0)
-        {
-            db = (char*) odr_malloc(decode, p1 - p0 + 1);
-            memcpy (db, p0, p1 - p0);
-            db[p1 - p0] = '\0';
-        }
-#if HAVE_XML2
-        if (p1 && *p1 == '?' && p1[1])
-        {
-            Z_SRW_PDU *sr = yaz_srw_get(decode, Z_SRW_searchRetrieve_request);
-            char *query = uri_val(p1, "query", decode);
-            char *pQuery = uri_val(p1, "pQuery", decode);
-            char *sortKeys = uri_val(p1, "sortKeys", decode);
-            
-           *srw_pdu = sr;
-            if (query)
-            {
-                sr->u.request->query_type = Z_SRW_query_type_cql;
-                sr->u.request->query.cql = query;
-            }
-            if (pQuery)
-            {
-                sr->u.request->query_type = Z_SRW_query_type_pqf;
-                sr->u.request->query.pqf = pQuery;
-            }
-            if (sortKeys)
-            {
-                sr->u.request->sort_type = Z_SRW_sort_type_sort;
-                sr->u.request->sort.sortKeys = sortKeys;
-            }
-            sr->u.request->recordSchema = uri_val(p1, "recordSchema", decode);
-            sr->u.request->recordPacking = uri_val(p1, "recordPacking", decode);
-            if (!sr->u.request->recordPacking)
-                sr->u.request->recordPacking = "xml";
-            uri_val_int(p1, "maximumRecords", decode, 
-                        &sr->u.request->maximumRecords);
-            uri_val_int(p1, "startRecord", decode,
-                        &sr->u.request->startRecord);
-            if (sr->u.request->startRecord)
-                yaz_log(LOG_LOG, "startRecord=%d", *sr->u.request->startRecord);
-            sr->u.request->database = db;
-           *soap_ns = "SRU";
-           return 0;
-        }
-#endif
-       return 1;
-    }
-    return 2;
+    if (!schema)
+       return 0;
+    Z_ElementSetNames *esn = (Z_ElementSetNames *)
+       odr_malloc(o, sizeof(Z_ElementSetNames));
+    esn->which = Z_ElementSetNames_generic;
+    esn->u.generic = odr_strdup(o, schema);
+    return esn;
 }
 
-int yaz_check_for_srw(Z_HTTP_Request *hreq, Z_SRW_PDU **srw_pdu,
-                     char **soap_ns, ODR decode)
+void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
 {
-    if (!strcmp(hreq->method, "POST"))
+    if (m_s2z_odr_init)
     {
-       const char *content_type = z_HTTP_header_lookup(hreq->headers,
-                                                       "Content-Type");
-       if (content_type && !yaz_strcmp_del("text/xml", content_type, "; "))
-       {
-           char *db = "Default";
-           const char *p0 = hreq->path, *p1;
-           Z_SOAP *soap_package = 0;
-            int ret = -1;
-            int http_code = 500;
-            const char *charset_p = 0;
-            char *charset = 0;
-           
-            static Z_SOAP_Handler soap_handlers[2] = {
-#if HAVE_XML2
-                {"http://www.loc.gov/zing/srw/v1.0/", 0,
-                 (Z_SOAP_fun) yaz_srw_codec},
-#endif
-                {0, 0, 0}
-            };
-           
-           if (*p0 == '/')
-               p0++;
-           p1 = strchr(p0, '?');
-           if (!p1)
-               p1 = p0 + strlen(p0);
-           if (p1 != p0)
-           {
-               db = (char*) odr_malloc(decode, p1 - p0 + 1);
-               memcpy (db, p0, p1 - p0);
-               db[p1 - p0] = '\0';
-           }
-
-            if ((charset_p = strstr(content_type, "; charset=")))
-            {
-                int i = 0;
-                charset_p += 10;
-                while (i < 20 && charset_p[i] &&
-                       !strchr("; \n\r", charset_p[i]))
-                    i++;
-                charset = (char*) odr_malloc(decode, i+1);
-                memcpy(charset, charset_p, i);
-                charset[i] = '\0';
-                yaz_log(LOG_LOG, "SOAP encoding %s", charset);
-            }
-            ret = z_soap_codec(decode, &soap_package, 
-                               &hreq->content_buf, &hreq->content_len,
-                               soap_handlers);
-           if (!ret && soap_package->which == Z_SOAP_generic &&
-               soap_package->u.generic->no == 0)
-           {
-               *srw_pdu = (Z_SRW_PDU*) soap_package->u.generic->p;
-               
-               if ((*srw_pdu)->which == Z_SRW_searchRetrieve_request &&
-                   (*srw_pdu)->u.request->database == 0)
-                   (*srw_pdu)->u.request->database = db;
-
-               *soap_ns = odr_strdup(decode, soap_package->ns);
-               return 0;
-           }
-           return 1;
-       }
+       odr_destroy(m_s2z_odr_init);
+       m_s2z_odr_init = 0;
     }
-    return 2;
-}
-
-void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
-{
-    Z_SRW_PDU *srw_pdu = 0;
-    char *soap_ns = 0;
-    if (m_s2z_odr)
+    if (m_s2z_odr_search)
     {
-       odr_destroy(m_s2z_odr);
-       m_s2z_odr = 0;
+       odr_destroy(m_s2z_odr_search);
+       m_s2z_odr_search = 0;
     }
 
     m_http_keepalive = 0;
@@ -1581,18 +1600,56 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
         m_http_version = "1.1";
     }
 
-    if (yaz_check_for_srw(hreq, &srw_pdu, &soap_ns, odr_decode()) == 0
-       || yaz_check_for_sru(hreq, &srw_pdu, &soap_ns, odr_decode()) == 0)
+    Z_SRW_PDU *srw_pdu = 0;
+    Z_SOAP *soap_package = 0;
+    char *charset = 0;
+    Z_SRW_diagnostic *diagnostic = 0;
+    int num_diagnostic = 0;
+    if (yaz_srw_decode(hreq, &srw_pdu, &soap_package, odr_decode(),
+                      &charset) == 0
+       || yaz_sru_decode(hreq, &srw_pdu, &soap_package, odr_decode(),
+                         &charset, &diagnostic, &num_diagnostic) == 0)
     {
-       m_s2z_odr = odr_createmem(ODR_ENCODE);
-       m_soap_ns = odr_strdup(m_s2z_odr, soap_ns);
+       m_s2z_odr_init = odr_createmem(ODR_ENCODE);
+       m_s2z_odr_search = odr_createmem(ODR_ENCODE);
+       m_soap_ns = odr_strdup(m_s2z_odr_search, soap_package->ns);
        m_s2z_init_apdu = 0;
        m_s2z_search_apdu = 0;
        m_s2z_present_apdu = 0;
+
+       m_s2z_stylesheet = 0;
+       
        if (srw_pdu->which == Z_SRW_searchRetrieve_request)
        {
            Z_SRW_searchRetrieveRequest *srw_req = srw_pdu->u.request;
 
+           m_s2z_database = odr_strdup(m_s2z_odr_init, srw_req->database);
+           // recordXPath unsupported.
+           if (srw_req->recordXPath)
+            {
+               yaz_add_srw_diagnostic(odr_decode(),
+                                      &diagnostic, &num_diagnostic,
+                                      72, 0);
+            }
+           // must have a query
+           if (!srw_req->query.cql)
+           {
+               yaz_add_srw_diagnostic(odr_decode(),
+                                      &diagnostic, &num_diagnostic,
+                                      7, "query");
+           }
+           // sort unsupported
+           if (srw_req->sort_type != Z_SRW_sort_type_none)
+           {
+               yaz_add_srw_diagnostic(odr_decode(),
+                                      &diagnostic, &num_diagnostic,
+                                      80, 0);
+           }
+           // save stylesheet
+           if (srw_req->stylesheet)
+               m_s2z_stylesheet =
+                   odr_strdup(m_s2z_odr_init, srw_req->stylesheet);
+                                             
            // set packing for response records ..
            if (srw_req->recordPacking &&
                !strcmp(srw_req->recordPacking, "xml"))
@@ -1600,28 +1657,42 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
            else
                m_s2z_packing = Z_SRW_recordPacking_string;
 
+           if (num_diagnostic)
+           {
+               Z_SRW_PDU *srw_pdu =
+                   yaz_srw_get(odr_encode(),
+                               Z_SRW_searchRetrieve_response);
+               Z_SRW_searchRetrieveResponse *srw_res = srw_pdu->u.response;
+               
+               srw_res->diagnostics = diagnostic;
+               srw_res->num_diagnostics = num_diagnostic;
+               send_srw_response(srw_pdu);
+               return;
+           }
+
            // prepare search PDU
-           m_s2z_search_apdu = zget_APDU(m_s2z_odr, Z_APDU_searchRequest);
+           m_s2z_search_apdu = zget_APDU(m_s2z_odr_search,
+                                         Z_APDU_searchRequest);
            Z_SearchRequest *z_searchRequest =
                m_s2z_search_apdu->u.searchRequest;
 
            z_searchRequest->num_databaseNames = 1;
            z_searchRequest->databaseNames = (char**)
-               odr_malloc(m_s2z_odr, sizeof(char *));
-           z_searchRequest->databaseNames[0] = odr_strdup(m_s2z_odr,
+               odr_malloc(m_s2z_odr_search, sizeof(char *));
+           z_searchRequest->databaseNames[0] = odr_strdup(m_s2z_odr_search,
                                                           srw_req->database);
            
            // query transformation
            Z_Query *query = (Z_Query *)
-               odr_malloc(odr_encode(), sizeof(Z_Query));
+               odr_malloc(m_s2z_odr_search, sizeof(Z_Query));
            z_searchRequest->query = query;
            
            if (srw_req->query_type == Z_SRW_query_type_cql)
            {
                Z_External *ext = (Z_External *) 
-                   odr_malloc(m_s2z_odr, sizeof(*ext));
+                   odr_malloc(m_s2z_odr_search, sizeof(*ext));
                ext->direct_reference = 
-                   odr_getoidbystr(m_s2z_odr, "1.2.840.10003.16.2");
+                   odr_getoidbystr(m_s2z_odr_search, "1.2.840.10003.16.2");
                ext->indirect_reference = 0;
                ext->descriptor = 0;
                ext->which = Z_External_CQL;
@@ -1637,7 +1708,7 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
                
                pqf_parser = yaz_pqf_create ();
                
-               RPNquery = yaz_pqf_parse (pqf_parser, m_s2z_odr,
+               RPNquery = yaz_pqf_parse (pqf_parser, m_s2z_odr_search,
                                          srw_req->query.pqf);
                if (!RPNquery)
                {
@@ -1647,7 +1718,7 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
                    yaz_log(LOG_LOG, "%*s^\n", off+4, "");
                    yaz_log(LOG_LOG, "Bad PQF: %s (code %d)\n", pqf_msg, code);
                    
-                   send_to_srw_client_error(10);
+                   send_to_srw_client_error(10, 0);
                    return;
                }
                query->which = Z_Query_type_1;
@@ -1657,7 +1728,7 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
            }
            else
            {
-               send_to_srw_client_error(11);
+               send_to_srw_client_error(7, "query");
                return;
            }
 
@@ -1671,31 +1742,58 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
                start = *srw_req->startRecord;
            if (max > 0)
            {
-               if (start <= 1)  // Z39.50 piggyback
+                // Some backend, such as Voyager doesn't honor piggyback
+               // So we use present always (0 &&).
+               if (0 && start <= 1)  // Z39.50 piggyback
                {
                    *z_searchRequest->smallSetUpperBound = max;
                    *z_searchRequest->mediumSetPresentNumber = max;
                    *z_searchRequest->largeSetLowerBound = 2000000000; // 2e9
+
                    z_searchRequest->preferredRecordSyntax =
-                       yaz_oidval_to_z3950oid(m_s2z_odr, CLASS_RECSYN,
+                       yaz_oidval_to_z3950oid(m_s2z_odr_search, CLASS_RECSYN,
                                               VAL_TEXT_XML);
+                   if (srw_req->recordSchema)
+                   {
+                       z_searchRequest->smallSetElementSetNames =
+                           z_searchRequest->mediumSetElementSetNames =
+                           mk_esn_from_schema(m_s2z_odr_search,
+                                              srw_req->recordSchema);
+                   }
                }
                else   // Z39.50 present
                {
-                   m_s2z_present_apdu = zget_APDU(m_s2z_odr, 
+                   m_s2z_present_apdu = zget_APDU(m_s2z_odr_search, 
                                                   Z_APDU_presentRequest);
                    Z_PresentRequest *z_presentRequest = 
                        m_s2z_present_apdu->u.presentRequest;
                    *z_presentRequest->resultSetStartPoint = start;
                    *z_presentRequest->numberOfRecordsRequested = max;
                    z_presentRequest->preferredRecordSyntax =
-                       yaz_oidval_to_z3950oid(m_s2z_odr, CLASS_RECSYN,
+                       yaz_oidval_to_z3950oid(m_s2z_odr_search, CLASS_RECSYN,
                                               VAL_TEXT_XML);
+                   z_presentRequest->recordComposition =
+                       (Z_RecordComposition *)
+                       odr_malloc(m_s2z_odr_search,
+                                  sizeof(Z_RecordComposition));
+                   if (srw_req->recordSchema)
+                   {
+                       z_presentRequest->recordComposition->which = 
+                           Z_RecordComp_simple;                    
+                       z_presentRequest->recordComposition->u.simple =
+                           mk_esn_from_schema(m_s2z_odr_search,
+                                              srw_req->recordSchema);
+                   }
                }
            }
            if (!m_client)
            {
-               m_s2z_init_apdu = zget_APDU(m_s2z_odr, Z_APDU_initRequest);
+               m_s2z_init_apdu = zget_APDU(m_s2z_odr_init,
+                                           Z_APDU_initRequest);
+               
+               // prevent m_initRequest_apdu memory from being grabbed
+               // in Yaz_Proxy::handle_incoming_Z_PDU
+               m_initRequest_apdu = m_s2z_init_apdu;
                handle_incoming_Z_PDU(m_s2z_init_apdu);
                return;
            }
@@ -1705,6 +1803,67 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
                return;
            }
        }
+       else if (srw_pdu->which == Z_SRW_explain_request)
+       {
+           Z_SRW_explainRequest *srw_req = srw_pdu->u.explain_request;
+
+           m_s2z_database = odr_strdup(m_s2z_odr_init, srw_req->database);
+
+           // save stylesheet
+           if (srw_req->stylesheet)
+               m_s2z_stylesheet =
+                   odr_strdup(m_s2z_odr_init, srw_req->stylesheet);
+
+           if (srw_req->recordPacking &&
+               !strcmp(srw_req->recordPacking, "xml"))
+               m_s2z_packing = Z_SRW_recordPacking_XML;
+           else
+               m_s2z_packing = Z_SRW_recordPacking_string;
+
+           if (num_diagnostic)
+           {
+               send_srw_explain_response(diagnostic, num_diagnostic);
+               return;
+           }
+
+           if (!m_client)
+           {
+               m_s2z_init_apdu = zget_APDU(m_s2z_odr_init,
+                                           Z_APDU_initRequest);
+               
+               // prevent m_initRequest_apdu memory from being grabbed
+               // in Yaz_Proxy::handle_incoming_Z_PDU
+               m_initRequest_apdu = m_s2z_init_apdu;
+               handle_incoming_Z_PDU(m_s2z_init_apdu);
+           }
+           else
+               send_srw_explain_response(0, 0);
+           return;
+       }
+       else if (srw_pdu->which == Z_SRW_scan_request)
+        {
+           m_s2z_database = odr_strdup(m_s2z_odr_init,
+                                       srw_pdu->u.scan_request->database);
+
+           yaz_add_srw_diagnostic(odr_decode(),
+                                  &diagnostic, &num_diagnostic,
+                                  4, "scan");
+           Z_SRW_PDU *srw_pdu =
+               yaz_srw_get(odr_encode(),
+                           Z_SRW_scan_response);
+           Z_SRW_scanResponse *srw_res = srw_pdu->u.scan_response;
+           
+           srw_res->diagnostics = diagnostic;
+           srw_res->num_diagnostics = num_diagnostic;
+           send_srw_response(srw_pdu);
+           return;
+        }
+       else
+        {
+           m_s2z_database = 0;
+
+           send_to_srw_client_error(4, 0);
+        }
     }
     int len = 0;
     Z_GDU *p = z_get_HTTP_Response(odr_encode(), 400);
@@ -1749,17 +1908,46 @@ void Yaz_Proxy::handle_incoming_Z_PDU(Z_APDU *apdu)
                nmem_destroy(m_initRequest_mem);
            m_initRequest_apdu = apdu;
            m_initRequest_mem = odr_extract_mem(odr_decode());
+
+           // save init options for the response..
+           m_initRequest_options = apdu->u.initRequest->options;
+           
+           apdu->u.initRequest->options = 
+               (Odr_bitmask *)nmem_malloc(m_initRequest_mem,
+                                          sizeof(Odr_bitmask));
+           ODR_MASK_ZERO(apdu->u.initRequest->options);
+           int i;
+           for (i = 0; i<= 24; i++)
+               ODR_MASK_SET(apdu->u.initRequest->options, i);
+           ODR_MASK_CLEAR(apdu->u.initRequest->options,
+                          Z_Options_negotiationModel);
+
+           // make new version
+           m_initRequest_version = apdu->u.initRequest->protocolVersion;
+           apdu->u.initRequest->protocolVersion = 
+               (Odr_bitmask *)nmem_malloc(m_initRequest_mem,
+                                          sizeof(Odr_bitmask));
+           ODR_MASK_ZERO(apdu->u.initRequest->protocolVersion);
+
+           for (i = 0; i<= 8; i++)
+               ODR_MASK_SET(apdu->u.initRequest->protocolVersion, i);
        }
        if (m_client->m_init_flag)
        {
            if (handle_init_response_for_invalid_session(apdu))
                return;
-           Z_APDU *apdu = m_client->m_initResponse;
-           apdu->u.initResponse->otherInfo = 0;
+           Z_APDU *apdu2 = m_client->m_initResponse;
+           apdu2->u.initResponse->otherInfo = 0;
            if (m_client->m_cookie && *m_client->m_cookie)
-               set_otherInformationString(apdu, VAL_COOKIE, 1,
+               set_otherInformationString(apdu2, VAL_COOKIE, 1,
                                           m_client->m_cookie);
-           send_to_client(apdu);
+           apdu2->u.initResponse->referenceId =
+               apdu->u.initRequest->referenceId;
+           apdu2->u.initResponse->options = m_client->m_initResponse_options;
+           apdu2->u.initResponse->protocolVersion = 
+               m_client->m_initResponse_version;
+
+           send_to_client(apdu2);
            return;
        }
        m_client->m_init_flag = 1;
@@ -1874,6 +2062,7 @@ void Yaz_ProxyClient::shutdown()
 
 void Yaz_Proxy::failNotify()
 {
+    inc_request_no();
     yaz_log (LOG_LOG, "%sConnection closed by client",
             get_session_str());
     shutdown();
@@ -1881,6 +2070,8 @@ void Yaz_Proxy::failNotify()
 
 void Yaz_ProxyClient::failNotify()
 {
+    if (m_server)
+       m_server->inc_request_no();
     yaz_log (LOG_LOG, "%sConnection closed by target %s", 
             get_session_str(), get_hostname());
     shutdown();
@@ -1921,19 +2112,14 @@ void Yaz_ProxyClient::pre_init_client()
     Z_APDU *apdu = create_Z_PDU(Z_APDU_initRequest);
     Z_InitRequest *req = apdu->u.initRequest;
     
-    ODR_MASK_SET(req->options, Z_Options_search);
-    ODR_MASK_SET(req->options, Z_Options_present);
-    ODR_MASK_SET(req->options, Z_Options_namedResultSets);
-    ODR_MASK_SET(req->options, Z_Options_triggerResourceCtrl);
-    ODR_MASK_SET(req->options, Z_Options_scan);
-    ODR_MASK_SET(req->options, Z_Options_sort);
-    ODR_MASK_SET(req->options, Z_Options_extendedServices);
-    ODR_MASK_SET(req->options, Z_Options_delSet);
-    
-    ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_1);
-    ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_2);
-    ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_3);
-    
+    int i;
+    for (i = 0; i<= 24; i++)
+       ODR_MASK_SET(req->options, i);
+    ODR_MASK_CLEAR(apdu->u.initRequest->options,
+                  Z_Options_negotiationModel);
+    for (i = 0; i<= 10; i++)
+       ODR_MASK_SET(req->protocolVersion, i);
+
     if (send_to_target(apdu) < 0)
     {
        delete this;
@@ -2049,6 +2235,8 @@ void Yaz_Proxy::timeoutNotify()
        }
        else
        {
+           inc_request_no();
+
            yaz_log (LOG_LOG, "%sTimeout (client to proxy)", m_session_str);
            shutdown();
        }
@@ -2060,12 +2248,29 @@ void Yaz_Proxy::timeoutNotify()
     }
 }
 
+void Yaz_Proxy::markInvalid()
+{
+    m_client = 0;
+    m_invalid_session = 1;
+}
+
 void Yaz_ProxyClient::timeoutNotify()
 {
+    if (m_server)
+       m_server->inc_request_no();
+
     yaz_log (LOG_LOG, "%sTimeout (proxy to target) %s", get_session_str(),
             get_hostname());
     m_waiting = 1;
     m_root->pre_init();
+    if (m_server && m_init_flag)
+    {
+       // target timed out in a session that was properly initialized
+       // server object stay alive but we mark it as invalid so it
+       // gets initialized again
+       m_server->markInvalid();
+       m_server = 0;
+    }
     shutdown();
 }
 
@@ -2085,6 +2290,8 @@ Yaz_ProxyClient::Yaz_ProxyClient(IYaz_PDU_Observable *the_PDU_Observable,
     m_waiting = 0;
     m_init_odr = odr_createmem (ODR_DECODE);
     m_initResponse = 0;
+    m_initResponse_options = 0;
+    m_initResponse_version = 0;
     m_resultSetStartPoint = 0;
     m_bytes_sent = m_bytes_recv = 0;
     m_pdu_recv = 0;
@@ -2149,6 +2356,8 @@ void Yaz_ProxyClient::recv_Z_PDU(Z_APDU *apdu, int len)
        odr_reset (m_init_odr);
         nmem_transfer (m_init_odr->mem, nmem);
         m_initResponse = apdu;
+       m_initResponse_options = apdu->u.initResponse->options;
+       m_initResponse_version = apdu->u.initResponse->protocolVersion;
 
        Z_InitResponse *ir = apdu->u.initResponse;
        char *im0 = ir->implementationName;
@@ -2227,7 +2436,7 @@ int Yaz_Proxy::server(const char *addr)
     int r = Yaz_Z_Assoc::server(addr);
     if (!r)
     {
-       yaz_log(LOG_LOG, "%sStarted listener on %s", m_session_str, addr);
+       yaz_log(LOG_LOG, "%sStarted proxy " VERSION " on %s", m_session_str, addr);
        timeout(1);
     }
     return r;