885b4ec2dfba48b85de44c989efd88d76dfe3539
[anna.git] / source / ldap / Session.cpp
1 // ANNA - Anna is Not 'N' Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <time.h>
38 #include <ldap.h>
39 #include <poll.h>
40 #include <signal.h>
41
42 #include <anna/core/tracing/Logger.hpp>
43 #include <anna/core/functions.hpp>
44 #include <anna/core/tracing/TraceMethod.hpp>
45
46 #include <anna/xml/Node.hpp>
47 #include <anna/xml/Attribute.hpp>
48
49 #include <anna/app/functions.hpp>
50
51 #include <anna/comm/Communicator.hpp>
52
53 #include <anna/ldap/Session.hpp>
54 #include <anna/ldap/ResultCode.hpp>
55 #include <anna/ldap/Attribute.hpp>
56 #include <anna/ldap/Response.hpp>
57 #include <anna/ldap/Request.hpp>
58
59 #include <anna/ldap/internal/Exception.hpp>
60
61 using namespace std;
62 using namespace anna;
63 using namespace anna::ldap;
64
65 //static
66 const Millisecond Session::DefaultTimeout(1000);
67
68 Session::Session() :
69   comm::Handler(comm::Handler::Type::Custom, comm::Handler::Support::None),
70   a_state(State::Closed),
71   a_ldap(NULL),
72   a_defer(Option::Defer::Never),
73   a_referral(Option::Referral::Off),
74   a_externalID(-1) {
75   comm::Handler::a_communicator = app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION);
76
77   for(int i = ClassCode::Min; i < ClassCode::Max; i ++)
78     a_timeouts [i] = DefaultTimeout;
79
80   a_networkTimeout.tv_sec = -1; a_networkTimeout.tv_usec = 0;
81 }
82
83 //---------------------------------------------------------------------------------
84 // Se invoca desde el ldap::Engine
85 //---------------------------------------------------------------------------------
86 void Session::bind()
87 throw(RuntimeException) {
88   if(a_state != State::Closed)
89     return;
90
91   LOGDEBUG(
92     string msg("ldap::Session::bind | ");
93     msg += asString();
94     Logger::debug(msg, ANNA_FILE_LOCATION);
95   );
96   Guard guard(this, "ldap::Session::bind");
97   LDAP* handle = NULL;
98   int aux;
99   ResultCode resultCode;
100   Response* response(NULL);
101
102   try {
103     resultCode = ldap_initialize(&handle, a_url.c_str());
104
105     if(resultCode != LDAP_SUCCESS)
106       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
107
108     aux = LDAP_VERSION3;
109     resultCode = ldap_set_option(handle, LDAP_OPT_PROTOCOL_VERSION, &aux);
110
111     if(resultCode.isOk() == false)
112       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
113
114     aux = 1;
115     resultCode = ldap_set_option(handle, LDAP_OPT_RESULT_CODE, &aux);
116
117     if(resultCode.isOk() == false)
118       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
119
120     switch(a_defer) {
121     case Session::Option::Defer::Never: aux = LDAP_DEREF_NEVER; break;
122     case Session::Option::Defer::Searching: aux = LDAP_DEREF_SEARCHING; break;
123     case Session::Option::Defer::Finding: aux = LDAP_DEREF_FINDING; break;
124     case Session::Option::Defer::Always: aux = LDAP_DEREF_ALWAYS; break;
125     }
126
127     resultCode = ldap_set_option(handle, LDAP_OPT_DEREF, &aux);
128
129     if(resultCode.isOk() == false)
130       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
131
132     resultCode = ldap_set_option(handle, LDAP_OPT_REFERRALS, (a_referral == Session::Option::Referral::On) ? LDAP_OPT_ON : LDAP_OPT_OFF);
133
134     if(resultCode.isOk() == false)
135       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
136
137     if(a_networkTimeout.tv_sec != -1) {
138       resultCode = ldap_set_option(handle, LDAP_OPT_NETWORK_TIMEOUT, &a_networkTimeout);
139
140       if(resultCode.isOk() == false)
141         throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
142     }
143   } catch(RuntimeException&) {
144     if(handle != NULL)
145       ldap_memfree(&handle);
146
147     throw;
148   }
149
150   try {
151     resultCode = ldap_simple_bind(handle, Request::asCString(a_user), Request::asCString(a_password));
152
153     if(resultCode < LDAP_SUCCESS)
154       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
155
156     int fd = -1;
157     ldap_get_option(handle, LDAP_OPT_DESC, &fd);
158     setfd(fd);
159     app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION)->attach(this);
160     a_ldap = handle;
161     a_state = State::WaitingBind;
162     response_add(Response::instance(ClassCode::Bind, resultCode.getValue()));
163   } catch(RuntimeException&) {
164     ldap_unbind(handle);
165     throw;
166   }
167 }
168
169 const Response* Session::send(const Request* request)
170 throw(RuntimeException) {
171   if(a_state == State::Closed) {
172     string msg(asString());
173     msg += " | Session::bind must be called";
174     throw RuntimeException(msg, ANNA_FILE_LOCATION);
175   }
176
177   if(a_state == State::WaitingBind) {
178     string msg(asString());
179     msg += " | Waiting for connection ack";
180     throw RuntimeException(msg, ANNA_FILE_LOCATION);
181   }
182
183   Response* result(NULL);
184   LOGDEBUG(
185     string msg("ldap::Session::send | ");
186     msg += asString();
187     msg += " | ";
188     msg += request->asString();
189     Logger::debug(msg, ANNA_FILE_LOCATION);
190   );
191   Guard guard(this, "ldap::Session::send");
192   ResultCode resultCode = request->send(*this);
193
194   if(resultCode < 0) {
195     resultCode.extractResultCode(this);
196     throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
197   }
198
199   result = Response::instance(request->getClassCode(), resultCode.getValue());
200   result->setRequest(request);
201   response_add(result);
202   return result;
203 }
204
205 void Session::unbind()
206 throw(RuntimeException) {
207   if(a_state == State::Closed)
208     return;
209
210   /**
211    * Esta invocación terminará invocando al método ldap::Session::finalize
212    */
213   a_communicator->detach(this);
214 }
215
216 //------------------------------------------------------------------------------------------
217 // Se invoca desde el comm::Communicator cuando se detecta activada sobre el fd's asociado
218 // a la ldap::Session
219 //------------------------------------------------------------------------------------------
220 void Session::apply()
221 throw(RuntimeException) {
222   LOGMETHOD(TraceMethod traceMethod("ldap::Session", "apply", ANNA_FILE_LOCATION));
223   LDAP* handle = (LDAP*) a_ldap;
224   LDAPMessage* hmessage(NULL);
225   ResultCode resultCode;
226   // La SIGALRM se tratará en Engine::alarmnCatcher => por ahora sólo se ignora
227   ualarm(100000, 0);
228   // El código que recibe la respuesta no está protegido ante la apareción de una señal.
229   // Cuando este método se ejecuta es porque ya sabemos que ha algo => no habrá espera
230   // Cuando el MSGID de la respuesta recibida no se encuentra en la lista de respuestas esperadas la OpenLDAP
231   // invoca a un select en el que supuestamente se pone a esperar una respuesta 'esperada' ... esperá 100 ms
232   // como máximo ya que la señal que ponemos lo sacará de la espera.
233   resultCode = ldap_result(handle, LDAP_RES_ANY, LDAP_MSG_ONE, NULL, &hmessage);
234   int xerrno = errno;
235   ualarm(0, 0);
236
237   if(resultCode.getValue() == -1) {
238     bool disconnect(false);
239
240     if(resultCode.extractResultCode(this) == false)
241       disconnect = true;
242     else if(resultCode.isServerDown() == true)
243       disconnect = true;
244     else if(resultCode.isConnectError() == true)
245       disconnect = true;
246
247     // Si espera fue interrumpida por una excepción el código de erro LDAP será -1, pero errno será EINTR
248     // En ese caso sólo ignoramos el mensaje, pero no cerramos la conexión.
249     if(disconnect == true && xerrno == EINTR)
250       disconnect = false;
251
252     ldap::Exception lex(this, resultCode, ANNA_FILE_LOCATION);
253
254     if(disconnect == true)
255       lex.trace();
256
257     eventResponseError(resultCode, disconnect);
258
259     if(disconnect == true)
260       a_communicator->detach(this);
261
262     return;
263   }
264
265   IdMessage idMessage = ldap_msgid(hmessage);
266
267   try {
268     switch(resultCode.getValue()) {
269     case LDAP_RES_BIND: receiveBind(idMessage, hmessage); break;
270     case LDAP_RES_SEARCH_ENTRY: receiveEntry(idMessage, hmessage); break;
271     case LDAP_RES_SEARCH_REFERENCE: receiveReference(idMessage, hmessage); break;
272     case LDAP_RES_SEARCH_RESULT: receiveResult(idMessage, hmessage); break;
273     default:
274       LOGWARNING(
275         string msg(asString());
276         msg += functions::asText(" | IdMessage: ", idMessage);
277         msg += " | ";
278         msg += resultCode.asString();
279         Logger::warning(msg, ANNA_FILE_LOCATION);
280       );
281       break;
282     }
283
284     ldap_msgfree(hmessage);
285   } catch(RuntimeException&) {
286     ldap_msgfree(hmessage);
287     throw;
288   }
289 }
290
291 /**
292  * invocado por el comm::Communicator::detach.
293  */
294 void Session::finalize()
295 throw() {
296   LDAP* handle = (LDAP*) a_ldap;
297   a_state = State::Closed;
298
299   if(handle != NULL) {
300     ldap_unbind(handle);
301     a_ldap = NULL;
302   }
303
304   eventServerShutdown();
305   ResultCode resultCode;
306   Response* response;
307   /*
308    * Notifica la finalización de las respuestas pendientes de recibir
309    */
310   resultCode = LDAP_UNAVAILABLE;
311
312   for(response_iterator ii = response_begin(), maxii = response_end(); ii != maxii; ii ++) {
313     response = Session::response(ii);
314     response->setResultCode(resultCode);
315     response->cancelTimer();
316
317     try {
318       eventResponse(*response);
319     } catch(RuntimeException& ex) {
320       ex.trace();
321     }
322
323     Response::release(response);
324   }
325
326   a_responses.clear();
327 }
328
329 void Session::receiveBind(const IdMessage idMessage, Session::HandleMessage _hmessage)
330 throw(RuntimeException) {
331   LDAP* handle = (LDAP*) a_ldap;
332   LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
333
334   // Si se invoca al unbind después de hacer el bind ... y llega la respuesta
335   if(a_state != State::WaitingBind) {
336     string msg("ldap::Session::receiveBind | ");
337     msg += asString();
338     msg += " | Unexpected Bind-response";
339     throw RuntimeException(msg, ANNA_FILE_LOCATION);
340   }
341
342   ResultCode resultCode;
343   int error = LDAP_SUCCESS;
344   Response* response = response_find(idMessage);
345   LOGDEBUG(
346     string msg("ldap::Session::receiveBind | ");
347     msg += asString();
348     msg += " | ";
349     msg += response->asString();
350     Logger::debug(msg, ANNA_FILE_LOCATION);
351   );
352   response->cancelTimer();
353   const int ldap_result  = ldap_parse_result(handle, hmessage, &error, NULL, NULL, NULL, NULL, 0);
354   bool unbindAfterDone = false;
355   resultCode.setValue(ldap_result, error);
356   LOGDEBUG(
357     string msg("ldap::Session::receiveBind | ");
358     msg += asString();
359     msg += " | ";
360     msg += resultCode.asString();
361     msg += functions::asText(" | LDAP Result: ", ldap_result);
362     msg += functions::asText(" | LDAP Error: ", error);
363     Logger::debug(msg, ANNA_FILE_LOCATION);
364   )
365
366   if(resultCode.isOk() == false) {
367     Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
368     ldapex.trace();
369     response->setResultCode(resultCode);
370     unbindAfterDone = true;
371   } else
372     a_state = State::Bound;
373
374   try {
375     eventResponse(*response);
376   } catch(RuntimeException& ex) {
377     ex.trace();
378   }
379
380   response_erase(response);
381
382   if(unbindAfterDone == true)
383     unbind();
384 }
385
386 //------------------------------------------------------------------------------------------------
387 // (1) Necesario para detectar el final de los atributos
388 //------------------------------------------------------------------------------------------------
389 void Session::receiveEntry(const IdMessage idMessage, Session::HandleMessage _hmessage)
390 throw(RuntimeException) {
391   LDAP* handle = (LDAP*) a_ldap;
392   LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
393   BerElement *ber(NULL);
394   BerValue name;
395   Response* response = response_find(idMessage);
396   LOGDEBUG(
397     string msg("ldap::Session::receiveEntry | ");
398     msg += response->asString();
399     Logger::debug(msg, ANNA_FILE_LOCATION);
400   );
401   ResultCode resultCode(ldap_get_dn_ber(handle, hmessage, &ber, &name));
402
403   if(resultCode.isOk() == false) {
404     Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
405     ldapex.trace();
406     response->setResultCode(resultCode);
407     eventIntermediateResponseError(*response);
408     return;
409   }
410
411   BerValue* values;
412   string value;
413   Attribute* attribute;
414   response->setName(value.assign(name.bv_val, name.bv_len));
415   LOGDEBUG(
416     string msg("ldap::Session::receiveEntry | DN: ");
417     msg += value;
418     Logger::debug(msg, ANNA_FILE_LOCATION);
419   );
420
421   while(ldap_get_attribute_ber(handle, hmessage, ber, &name, &values) == LDAP_SUCCESS) {
422     if(name.bv_val == NULL)                             // (1)
423       break;
424
425     attribute = response->createAttribute(value.assign(name.bv_val, name.bv_len));
426     LOGDEBUG(
427       string msg("ldap::Session::receiveEntry | Attribute: ");
428       msg += value;
429       Logger::debug(msg, ANNA_FILE_LOCATION);
430     );
431
432     if(values) {
433       for(int i = 0; values [i].bv_val != NULL; i ++) {
434         attribute->add(value.assign(values [i].bv_val, values [i].bv_len));
435         LOGDEBUG(
436           string msg("ldap::Session::receiveEntry | Value: ");
437           msg += value;
438           Logger::debug(msg, ANNA_FILE_LOCATION);
439         );
440       }
441
442       ber_memfree(values);
443     }
444   }
445
446   ber_free(ber, 0);
447 }
448
449 void Session::receiveReference(const IdMessage idMessage, Session::HandleMessage _hmessage)
450 throw(RuntimeException) {
451   LDAP* handle = (LDAP*) a_ldap;
452   LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
453   char **values(NULL);
454   Response* response = response_find(idMessage);
455   LOGDEBUG(
456     string msg("ldap::Session::receiveReference | ");
457     msg += response->asString();
458     Logger::debug(msg, ANNA_FILE_LOCATION);
459   );
460   ResultCode resultCode(ldap_parse_reference(handle, hmessage, &values, NULL, 0));
461
462   if(resultCode.isOk() == false) {
463     Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
464     ldapex.trace();
465     response->setResultCode(resultCode);
466     eventIntermediateResponseError(*response);
467     return;
468   }
469
470   if(values != NULL) {
471     for(int i = 0; values [i] != NULL; i ++)
472       response->createReferral(values [i]);
473
474     ber_memvfree((void**) values);
475   }
476 }
477
478 void Session::receiveResult(const IdMessage idMessage, Session::HandleMessage _hmessage)
479 throw(RuntimeException) {
480   LDAP* handle = (LDAP*) a_ldap;
481   LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
482   Response* response = response_find(idMessage);
483   LOGDEBUG(
484     string msg("ldap::Session::receiveResult | ");
485     msg += asString();
486     msg += " | ";
487     msg += response->asString();
488     Logger::debug(msg, ANNA_FILE_LOCATION);
489   );
490   int error = LDAP_SUCCESS;
491   char **values(NULL);
492   ResultCode resultCode;
493   bool isOk = true;
494   response->cancelTimer();
495   const int ldap_result = ldap_parse_result(handle, hmessage, &error, NULL, NULL, &values, NULL, 0);
496   resultCode.setValue(ldap_result, error);
497   LOGDEBUG(
498     string msg("ldap::Session::receiveResult | ");
499     msg += asString();
500     msg += " | ";
501     msg += resultCode.asString();
502     msg += functions::asText(" | LDAP Result: ", ldap_result);
503     msg += functions::asText(" | LDAP Error: ", error);
504     Logger::debug(msg, ANNA_FILE_LOCATION);
505   )
506
507   if(resultCode.isOk() == false) {
508     Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
509     ldapex.trace();
510     response->setResultCode(resultCode);
511   } else if(values != NULL) {
512     for(int i = 0; values [i] != NULL; i ++)
513       response->createReferral(values [i]);
514
515     ldap_value_free(values);
516   }
517
518   try {
519     eventResponse(*response);
520   } catch(RuntimeException& ex) {
521     ex.trace();
522   }
523
524   response_erase(response);
525 }
526
527 //-------------------------------------------------------------------------
528 // Se invoca desde ldap::timer::Prototype::expire
529 //-------------------------------------------------------------------------
530 void Session::expireResponse(ldap::Response* response)
531 throw() {
532   LDAP* handle = (LDAP*) a_ldap;
533   ResultCode resultCode;
534   /*
535    * Si lo que ha caducado es una petición de Bind hay que cerrar la conexión
536    * y liberar los recursos.
537    */
538   bool unbindAfterDone(true);
539
540   if(response->getClassCode() != ClassCode::Bind) {
541     if(response->getRequest()->getOnExpiry() == Request::OnExpiry::Abandon) {
542       resultCode = ldap_abandon(handle, response->getIdMessage());
543
544       if(resultCode.isOk() == false) {
545         Exception ldapex(this, resultCode, ANNA_FILE_LOCATION);
546         ldapex.trace();
547       }
548     }
549
550     unbindAfterDone = false;
551   }
552
553   response->setResultCode(resultCode = LDAP_TIMEOUT);
554
555   try {
556     eventResponse(*response);
557   } catch(RuntimeException& ex) {
558     ex.trace();
559   }
560
561   response_erase(response);
562
563   if(unbindAfterDone == true)
564     unbind();
565 }
566
567 void Session::response_add(Response* response)
568 throw() {
569   a_responses.add(response);
570   response->setSession(this);
571
572   try {
573     response->activateTimer();
574   } catch(anna::Exception& ex) {
575     ex.trace();
576   }
577 }
578
579 void Session::response_erase(Response* response)
580 throw() {
581   a_responses.erase(response);
582   Response::release(response);
583 }
584
585 Response* Session::response_find(const IdMessage idMessage)
586 throw(RuntimeException) {
587   ldap::Response* result = a_responses.find(idMessage);
588
589   if(result == NULL) {
590     string msg(asString());
591     msg += functions::asText(" | IdMessage: ", idMessage);
592     msg += " | Message not registered at session";
593     throw RuntimeException(msg, ANNA_FILE_LOCATION);
594   }
595
596   return result;
597 }
598
599 std::string Session::asString() const
600 throw() {
601   string result("ldap::Session { ");
602   result += comm::Handler::asString();
603   result += " | State: ";
604   result += asText(a_state);
605   result += " | URL: ";
606   result += a_url;
607   result += " | User: ";
608   result += Request::asText(a_user);
609
610   if(a_externalID != -1)
611     result += functions::asText(" | ExternalID: ", a_externalID);
612
613   if(hasNetworkTimeout()) {
614     result += functions::asText(" | Network timeout { Sec: ", (int) a_networkTimeout.tv_sec);
615     result += functions::asText(" | uSec: ", (int) a_networkTimeout.tv_usec);
616     result += " }";
617   }
618
619   return result += " }";
620 }
621
622 xml::Node* Session::asXML(xml::Node* parent) const
623 throw() {
624   parent = comm::Handler::asXML(parent);
625   xml::Node* result = parent->createChild("ldap.Session");
626   result->createAttribute("State", asText(a_state));
627   result->createAttribute("User", Request::asText(a_user));
628   result->createAttribute("N", a_responses.size());
629   result->createChild("URL")->createAttribute("Value", a_url);
630
631   if(a_externalID != -1)
632     result->createChild("ExternalID")->createAttribute("Value", a_externalID);
633
634   if(hasNetworkTimeout())
635     result->createChild("NetworkTimeout")->createAttribute("Value", getNetworkTimeout());
636
637   xml::Node* requests = result->createChild("ldap.Requests");
638   const Response* response;
639   const Request* request;
640
641   for(const_response_iterator ii = response_begin(), maxii = response_end(); ii != maxii; ii ++) {
642     if((request = Session::response(ii)->getRequest()) != NULL)
643       request->asXML(requests);
644   }
645
646   return result;
647 }
648
649 int Session::getDangerousFileDescriptor() const
650 throw(RuntimeException) {
651   ResultCode resultCode;
652   int result = -1;
653   resultCode = ldap_get_option((LDAP*) a_ldap, LDAP_OPT_DESC, &result);
654
655   if(resultCode.isOk() == false)
656     throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
657
658   LOGWARNING(
659     string msg("Session::getDangerousFileDescriptor | Result: ");
660     msg += functions::asString(result);
661     Logger::warning(msg, ANNA_FILE_LOCATION);
662   );
663   return result;
664 }
665
666 const char* Session::asText(const State::_v state)
667 throw() {
668   static const char* states [] = { "Closed", "WaitingBind", "Bound" };
669   return states [state];
670 }
671
672 IdMessage Session::SortById::value(const Response* response)
673 throw() {
674   return response->getIdMessage();
675 }