1 ////////////////////////////////////////////////////////////////////////////////
3 // Author: Andy Rushton
4 // Copyright: (c) Southampton University 1999-2004
5 // (c) Andy Rushton 2004 onwards
6 // License: BSD License, see ../docs/license.html
8 // Contains all the platform-specific socket handling used by the TCP and UDP classes
10 // TODO - any conversion required to support IPv6
12 ////////////////////////////////////////////////////////////////////////////////
14 #include "ip_sockets.hpp"
15 #include "dprintf.hpp"
19 // Windoze-specific includes
21 #define ERRNO WSAGetLastError()
22 #define HERRNO WSAGetLastError()
23 #define IOCTL ioctlsocket
24 #define CLOSE closesocket
25 #define SHUT_RDWR SD_BOTH
28 #if _MSC_VER < 1600 // not defined before Visual Studio 10
29 #define EINPROGRESS WSAEINPROGRESS
30 #define EWOULDBLOCK WSAEWOULDBLOCK
31 #define ECONNRESET WSAECONNRESET
34 // Generic Unix includes
35 // fix for older versions of Darwin?
36 #define _BSD_SOCKLEN_T_ int
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <sys/ioctl.h>
41 #include <netinet/in.h>
45 #define INVALID_SOCKET -1
47 #define HERRNO h_errno
49 #define SOCKET_ERROR -1
52 #define SOCKLEN_T socklen_t
53 #define SEND_FLAGS MSG_NOSIGNAL
55 // Sun put some definitions in a different place
56 #include <sys/filio.h>
60 ////////////////////////////////////////////////////////////////////////////////
65 ////////////////////////////////////////////////////////////////////////////////
68 // get an operating-system error message given an error code
69 static std::string
error_string(int error
)
71 std::string result
= "error " + dformat("%d",error
);
74 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|FORMAT_MESSAGE_FROM_SYSTEM
|FORMAT_MESSAGE_IGNORE_INSERTS
,
77 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // "User default language"
85 // the error message is for some perverse reason newline terminated - remove this
86 if (result
[result
.size()-1] == '\n')
87 result
.erase(result
.end()-1);
88 if (result
[result
.size()-1] == '\r')
89 result
.erase(result
.end()-1);
91 char* message
= strerror(error
);
92 if (message
&& message
[0])
98 // convert address:port into a sockaddr
99 static void convert_address(unsigned long address
, unsigned short port
, sockaddr
& sa
)
101 sa
.sa_family
= AF_INET
;
102 unsigned short network_port
= htons(port
);
103 memcpy(&sa
.sa_data
[0], &network_port
, sizeof(network_port
));
104 unsigned long network_address
= htonl(address
);
105 memcpy(&sa
.sa_data
[2], &network_address
, sizeof(network_address
));
108 // // convert host:port into a sockaddr
109 // static void convert_host(hostent& host, unsigned short port, sockaddr& sa)
111 // sa.sa_family = host.h_addrtype;
112 // unsigned short network_port = htons(port);
113 // memcpy(&sa.sa_data[0], &network_port, sizeof(network_port));
114 // memcpy(&sa.sa_data[2], host.h_addr, host.h_length);
117 // convert sockaddr to address:port
118 static void convert_sockaddr(const sockaddr
& sa
, unsigned long& address
, unsigned short& port
)
120 unsigned short network_port
= 0;
121 memcpy(&network_port
, &sa
.sa_data
[0], sizeof(network_port
));
122 port
= ntohs(network_port
);
123 unsigned long network_address
= 0;
124 memcpy(&network_address
, &sa
.sa_data
[2], sizeof(network_address
));
125 address
= ntohl(network_address
);
128 ////////////////////////////////////////////////////////////////////////////////
130 // Windows requires that Winsock is initialised before use and closed after
131 // These routines initialise once on first use and close on the destruction of the last object using it
132 // on non-windows platforms, I still increment/decrement the sockets count variable for diagnostic purposes
134 static int sockets_count
= 0;
136 static int sockets_init(void)
139 if (sockets_count
++ == 0)
142 WSAData winsock_info
;
143 // request Winsock 2.0 or higher
144 error
= WSAStartup(MAKEWORD(2,0),&winsock_info
);
150 static int sockets_close(void)
153 if (--sockets_count
== 0)
156 if (WSACleanup() == SOCKET_ERROR
)
163 ////////////////////////////////////////////////////////////////////////////////
164 // Socket Implementation - common code to manipulate a TCP socket
166 class IP_socket_internals
169 IP_socket_type m_type
;
171 unsigned long m_remote_address
;
172 unsigned short m_remote_port
;
174 mutable std::string m_message
;
177 // disable copying of the internals
178 IP_socket_internals(const IP_socket_internals
&);
179 IP_socket_internals
& operator=(const IP_socket_internals
&);
183 ////////////////////////////////////////////////////////////////////////////
184 // PIMPL alias counting
197 ////////////////////////////////////////////////////////////////////////////
198 // constructors/destructors
200 // construct an invalid socket
201 IP_socket_internals(void) : m_type(undefined_socket_type
), m_socket(INVALID_SOCKET
), m_error(0), m_count(1)
203 set_error(sockets_init());
207 ~IP_socket_internals(void)
210 set_error(sockets_close());
213 ////////////////////////////////////////////////////////////////////////////
214 // initialisation, connection
216 bool initialised(void) const
218 return m_socket
!= INVALID_SOCKET
;
221 // attach this object to a pre-opened socket
222 bool set(SOCKET socket
, unsigned long remote_address
, unsigned short remote_port
)
224 if (initialised()) close();
227 m_remote_address
= remote_address
;
228 m_remote_port
= remote_port
;
232 // create a raw socket attached to this object
233 bool initialise(IP_socket_type type
)
235 if (initialised()) close();
237 if ((type
!= TCP
) && (type
!= UDP
))
239 set_error(-1, "Illegal socket type");
242 // create an anonymous socket
243 m_socket
= ::socket(AF_INET
, ((type
== TCP
) ? SOCK_STREAM
: SOCK_DGRAM
), 0);
244 if (m_socket
== INVALID_SOCKET
)
250 // record the type on success only
252 // set the socket into non-blocking mode
253 unsigned long nonblocking
= 1;
254 if (IOCTL(m_socket
, FIONBIO
, &nonblocking
) == SOCKET_ERROR
)
262 // function for performing IP lookup (i.e. gethostbyname)
263 // could be standalone but making it a member means that it can use the socket's error handler
264 // - remote_address: IP name or number
265 // - returns the IP address as a number - zero if there's an error
266 unsigned long ip_lookup(const std::string
& remote_address
)
268 unsigned long result
= 0;
269 // Lookup the IP address to convert it into a host record
270 // this DOES lookup IP address names as well (not according to MS help !!)
271 // TODO - convert this to use ::getaddrinfo - ::gethostbyname is deprecated
272 hostent
* host_info
= ::gethostbyname(remote_address
.c_str());
278 // extract the address from the host info
279 unsigned long network_address
= 0;
280 memcpy(&network_address
, host_info
->h_addr
, host_info
->h_length
);
281 result
= ntohl(network_address
);
285 // tests whether a socket is ready for communication
286 bool select(bool readable
, bool writeable
, unsigned wait
)
288 if (!initialised()) return false;
289 // set up the readable set
291 fd_set
* readable_set_ptr
= 0;
294 FD_ZERO(&readable_set
);
295 FD_SET(m_socket
,&readable_set
);
296 readable_set_ptr
= &readable_set
;
298 // set up the writeable set
299 fd_set writeable_set
;
300 fd_set
* writeable_set_ptr
= 0;
303 FD_ZERO(&writeable_set
);
304 FD_SET(m_socket
,&writeable_set
);
305 writeable_set_ptr
= &writeable_set
;
307 // TODO - check the error set and lookup the error?
308 fd_set
* error_set_ptr
= 0;
309 // set up the timout value
310 // Note: a null pointer implements a blocking select
311 // a pointer to a zero value implements a zero-wait poll
312 // a pointer to a positive value implements a poll with a timeout
313 // I currently only implement polling with timeout which may be zero - no blocking
315 timeval
* timeout_ptr
= 0;
316 timeout
.tv_sec
= wait
/1000000;
317 timeout
.tv_usec
= wait%1000000
;
318 timeout_ptr
= &timeout
;
319 // now test the socket
320 int select_result
= ::select(m_socket
+1, readable_set_ptr
, writeable_set_ptr
, error_set_ptr
, timeout_ptr
);
321 switch(select_result
)
324 // select failed with an error - trap the error
328 // timeout exceeded without a connection appearing
331 // at least one connection is pending
332 // TODO - do we need to do the extra socket options checking on Posix?
333 // TODO - does this connect in any way to the error_set above?
338 // bind the socket to a port so that it can receive from specific address
339 bool bind(unsigned long remote_address
, unsigned short local_port
)
341 if (!initialised()) return false;
342 // name the socket and bind it to a port - this is a requirement for a server
344 convert_address(INADDR_ANY
, local_port
, server
);
345 if (::bind(m_socket
, &server
, sizeof(server
)) == SOCKET_ERROR
)
354 // bind the socket to a port so that it can receive from any address
355 bool bind_any(unsigned short local_port
)
357 return bind(INADDR_ANY
, local_port
);
360 // set this socket up to be a listening port
361 // must have been bound to a local port already
362 // - length of backlog queue to manage - may be zero
363 // - returns success status
364 bool listen(unsigned short queue
)
366 if (!initialised()) return false;
367 // set the port to listen for incoming connections
368 if (::listen(m_socket
, (int)queue
) == SOCKET_ERROR
)
377 // test whether there's an incoming connection on the socket
378 // only applicable if it has been set up as a listening port
379 bool accept_ready(unsigned wait
)
381 // the test for a connection being ready is the same as the test for whether the socket is readable
382 // see documentation for select
383 return select(true, false, wait
);
386 // accept a connection on the socket
387 // only applicable if it has been set up as a listening port
388 // - returns socket filled in with the accepted connection's details - or with the error fields set
389 IP_socket
accept(void)
391 if (!initialised()) return IP_socket();
393 // accept the connection, at the same time getting the address of the connecting client
395 SOCKLEN_T saddress_length
= sizeof(saddress
);
396 SOCKET socket
= ::accept(m_socket
, &saddress
, &saddress_length
);
397 if (socket
== INVALID_SOCKET
)
399 // only set the result socket with an error
400 result
.m_impl
->set_error(ERRNO
);
403 // extract the contents of the address
404 unsigned long remote_address
= 0;
405 unsigned short remote_port
= 0;
406 convert_sockaddr(saddress
, remote_address
, remote_port
);
407 result
.m_impl
->set(socket
, remote_address
, remote_port
);
411 // client connect to a server
412 // - remote_address: IP number of remote address to connect to
413 // - remote_port: port to connect to
414 bool connect(unsigned long remote_address
, unsigned short remote_port
)
416 if (!initialised()) return false;
417 // fill in the connection data structure
418 sockaddr connect_data
;
419 convert_address(remote_address
, remote_port
, connect_data
);
420 // connect binds the socket to a local address
421 // if connectionless it simply sets the default remote address
422 // if connectioned it makes the connection
423 if (::connect(m_socket
, &connect_data
, sizeof(connect_data
)) == SOCKET_ERROR
)
425 // the socket is non-blocking, so connect will almost certainly fail with EINPROGRESS which is not an error
426 // only catch real errors
428 if (error
!= EINPROGRESS
&& error
!= EWOULDBLOCK
)
434 // extract the remote connection details for local storage
435 convert_sockaddr(connect_data
, m_remote_address
, m_remote_port
);
439 // test whether a socket is connected and ready to communicate
440 bool connected(unsigned wait
)
442 if (!initialised()) return false;
443 // Linux and Windows docs say test with select for whether socket is
444 // writable. However, a problem has been reported with Linux whereby
445 // the OS will report a socket as writable when it isn't
446 // first use the select method
447 if (!select(false, true, wait
))
450 // Windows needs no further processing - select method works
453 // Posix version needs further checking using the socket options
454 // DJDM: socket has returned EINPROGRESS on the first attempt at connection
455 // it has also returned that it can be written to
456 // we must now ask it if it has actually connected - using getsockopt
458 socklen_t serror
= sizeof(int);
459 if (::getsockopt(m_socket
, SOL_SOCKET
, SO_ERROR
, &error
, &serror
)==0)
460 // handle the error value - one of them means that the socket has connected
461 if (!error
|| error
== EISCONN
)
472 if (shutdown(m_socket
,SHUT_RDWR
) == SOCKET_ERROR
)
477 if (CLOSE(m_socket
) == SOCKET_ERROR
)
483 m_socket
= INVALID_SOCKET
;
484 m_remote_address
= 0;
489 ////////////////////////////////////////////////////////////////////////////
492 bool send_ready(unsigned wait
)
494 // determines whether the socket is ready to send by testing whether it is writable
495 return select(false, true, wait
);
498 bool send (std::string
& data
)
500 if (!initialised()) return false;
501 // send the data - this will never block but may not send all the data
502 int bytes
= ::send(m_socket
, data
.c_str(), data
.size(), SEND_FLAGS
);
503 if (bytes
== SOCKET_ERROR
)
508 // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent
513 bool send_packet(std::string
& data
, unsigned long address
= 0, unsigned short port
= 0)
515 if (!initialised()) return false;
516 // if no address specified, rely on the socket having been connected (can I test this?)
517 // so use the standard send, otherwise use the sendto function
521 bytes
= ::send(m_socket
, data
.c_str(), data
.size(), SEND_FLAGS
);
526 convert_address(address
, port
, saddress
);
527 bytes
= ::sendto(m_socket
, data
.c_str(), data
.size(), SEND_FLAGS
, &saddress
, sizeof(saddress
));
529 if (bytes
== SOCKET_ERROR
)
534 // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent
539 bool receive_ready(unsigned wait
)
541 // determines whether the socket is ready to receive by testing whether it is readable
542 return select(true, false, wait
);
545 bool receive (std::string
& data
)
547 if (!initialised()) return false;
548 // determine how much data is available to read
549 unsigned long bytes
= 0;
550 if (IOCTL(m_socket
, FIONREAD
, &bytes
) == SOCKET_ERROR
)
555 // get the data up to the amount claimed to be present - this is non-blocking
556 char* buffer
= new char[bytes
+1];
557 int read
= ::recv(m_socket
, buffer
, bytes
, 0);
558 if (read
== SOCKET_ERROR
)
567 // TODO - check whether this is an appropriate conditon to close the socket
572 // this is binary data so copy the bytes including nulls
573 data
.append(buffer
,read
);
579 bool receive_packet(std::string
& data
, unsigned long& address
, unsigned short& port
)
581 if (!initialised()) return false;
582 // determine how much data is available to read
583 unsigned long bytes
= 0;
584 if (IOCTL(m_socket
, FIONREAD
, &bytes
) == SOCKET_ERROR
)
589 // get the data up to the amount claimed to be present - this is non-blocking
590 // also get the sender's details
591 char* buffer
= new char[bytes
+1];
593 SOCKLEN_T saddress_length
= sizeof(saddress
);
594 int read
= ::recvfrom(m_socket
, buffer
, bytes
, 0, &saddress
, &saddress_length
);
595 if (read
== SOCKET_ERROR
)
597 // UDP connection reset means that a previous sent failed to deliver cos the address was unknown
598 // this is NOT an error with the sending server socket, which IS still usable
600 if (error
!= ECONNRESET
)
608 // this is binary data so copy the bytes including nulls
609 data
.append(buffer
,read
);
610 // also retrieve the sender's details
611 convert_sockaddr(saddress
, address
, port
);
616 bool receive_packet(std::string
& data
)
618 // call the above and then discard the address details
619 unsigned long address
= 0;
620 unsigned short port
= 0;
621 return receive_packet(data
, address
, port
);
624 ////////////////////////////////////////////////////////////////////////////
627 IP_socket_type
type(void) const
632 unsigned short local_port(void) const
634 if (!initialised()) return 0;
636 SOCKLEN_T saddress_length
= sizeof(saddress
);
637 if (::getsockname(m_socket
, &saddress
, &saddress_length
) != 0)
642 unsigned long address
= 0;
643 unsigned short port
= 0;
644 convert_sockaddr(saddress
, address
, port
);
648 unsigned long remote_address(void) const
650 return m_remote_address
;
653 unsigned short remote_port(void) const
655 return m_remote_port
;
658 ////////////////////////////////////////////////////////////////////////////
661 void set_error (int error
, const char* message
= 0) const
666 if (message
&& (message
[0] != 0))
669 m_message
= error_string(error
);
673 int error(void) const
678 void clear_error (void) const
684 std::string
message(void) const
691 ////////////////////////////////////////////////////////////////////////////////
692 // Socket - common code to manipulate a socket
694 // create an uninitialised socket
695 IP_socket::IP_socket(void) : m_impl(new IP_socket_internals
)
699 // create an initialised socket
700 // - type: create either a TCP or UDP socket - if neither, creates an uninitialised socket
701 IP_socket::IP_socket(IP_socket_type type
) : m_impl(new IP_socket_internals
)
706 // destroy the socket, closing it if open
707 IP_socket::~IP_socket(void)
709 if (m_impl
->decrement())
713 ////////////////////////////////////////////////////////////////////////////
714 // copying is implemented as aliasing
716 IP_socket::IP_socket(const IP_socket
& right
) : m_impl(0)
718 // make this an alias of right
719 m_impl
= right
.m_impl
;
723 IP_socket
& IP_socket::operator=(const IP_socket
& right
)
725 // make self-copy safe
726 if (m_impl
== right
.m_impl
) return *this;
727 // first dealias the existing implementation
728 if (m_impl
->decrement())
730 // now make this an alias of right
731 m_impl
= right
.m_impl
;
736 ////////////////////////////////////////////////////////////////////////////
737 // initialisation, connection
739 // initialise the socket
740 // - type: create either a TCP or UDP socket
741 // - returns success status
742 bool IP_socket::initialise(IP_socket_type type
)
744 return m_impl
->initialise(type
);
747 // test whether this is an initialised socket
748 // - returns whether this is initialised
749 bool IP_socket::initialised(void) const
751 return m_impl
->initialised();
754 // close, i.e. disconnect the socket
755 // - returns a success flag
756 bool IP_socket::close(void)
758 return m_impl
->close();
761 // function for performing IP lookup (i.e. gethostbyname)
762 // could be standalone but making it a member means that it can use the socket's error handler
763 // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96)
764 // - returns the IP address as a long integer - zero if there's an error
765 unsigned long IP_socket::ip_lookup(const std::string
& remote_address
)
767 return m_impl
->ip_lookup(remote_address
);
770 // test whether a socket is ready to communicate
771 // - readable: test whether socket is ready to read
772 // - writeable: test whether a socket is ready to write
773 // - timeout: if socket is not ready, time to wait before giving up - in micro-seconds - 0 means don't wait
774 // returns false if not ready or error - use error() method to tell - true if ready
775 bool IP_socket::select(bool readable
, bool writeable
, unsigned timeout
)
777 return m_impl
->select(readable
, writeable
, timeout
);
780 // bind the socket to a port so that it can receive from specific address - typically used by a client
781 // - remote_address: IP number of remote server to send/receive to/from
782 // - local_port: port on local machine to bind to the address
783 // - returns success flag
784 bool IP_socket::bind(unsigned long remote_address
, unsigned short local_port
)
786 return m_impl
->bind(remote_address
, local_port
);
789 // bind the socket to a port so that it can receive from any address - typically used by a server
790 // - local_port: port on local machine to bind to the address
791 // - returns success flag
792 bool IP_socket::bind_any(unsigned short local_port
)
794 return m_impl
->bind_any(local_port
);
797 // initialise a socket and set this socket up to be a listening port
798 // - queue: length of backlog queue to manage - may be zero
799 // - returns success status
800 bool IP_socket::listen(unsigned short queue
)
802 return m_impl
->listen(queue
);
805 // test for a connection on the object's socket - only applicable if it has been set up as a listening port
806 // - returns true if a connection is ready to be accepted
807 bool IP_socket::accept_ready(unsigned timeout
) const
809 return m_impl
->accept_ready(timeout
);
812 // accept a connection on the object's socket - only applicable if it has been set up as a listening port
813 // - returns the connection as a new socket
814 IP_socket
IP_socket::accept(void)
816 return m_impl
->accept();
819 // client connect to a server
820 // - address: IP number already lookup up with ip_lookup
821 // - port: port to connect to
822 // - returns a success flag
823 bool IP_socket::connect(unsigned long address
, unsigned short port
)
825 return m_impl
->connect(address
, port
);
828 // test whether a socket is connected and ready to communicate, returns on successful connect or timeout
829 // - timeout: how long to wait in microseconds if not connected yet
830 // - returns success flag
831 bool IP_socket::connected(unsigned timeout
)
833 return m_impl
->connected(timeout
);
836 ////////////////////////////////////////////////////////////////////////////
839 // test whether a socket is connected and ready to send data, returns if ready or on timeout
840 // - timeout: how long to wait in microseconds if not connected yet (blocking)
842 bool IP_socket::send_ready(unsigned timeout
)
844 return m_impl
->send_ready(timeout
);
847 // send data through the socket - if the data is long only part of it may
848 // be sent. The sent part is removed from the data, so the same string can
849 // be sent again and again until it is empty.
850 // - data: string containing data to be sent - any data successfully sent is removed
851 // - returns success flag
852 bool IP_socket::send (std::string
& data
)
854 return m_impl
->send(data
);
857 // send data through a connectionless (UDP) socket
858 // the data will be sent as a single packet
859 // - packet: string containing data to be sent - any data successfully sent is removed
860 // - remote_address: address of the remote host to send to - optional if the socket has been connected to remote
861 // - remote_port: port of the remote host to send to - optional if the socket has been connected to remote
862 // - returns success flag
863 bool IP_socket::send_packet(std::string
& packet
, unsigned long remote_address
, unsigned short remote_port
)
865 return m_impl
->send_packet(packet
, remote_address
, remote_port
);
868 // send data through a connectionless (UDP) socket
869 // the data will be sent as a single packet
870 // only works if the socket has been connected to remote
871 // - packet: string containing data to be sent - any data successfully sent is removed
872 // - returns success flag
873 bool IP_socket::send_packet(std::string
& packet
)
875 return m_impl
->send_packet(packet
);
878 // test whether a socket is connected and ready to receive data, returns if ready or on timeout
879 // - timeout: how long to wait in microseconds if not connected yet (blocking)
881 bool IP_socket::receive_ready(unsigned timeout
)
883 return m_impl
->receive_ready(timeout
);
886 // receive data through a connection-based (TCP) socket
887 // if the data is long only part of it may be received. The received data
888 // is appended to the string, building it up in stages, so the same string
889 // can be received again and again until all information has been
891 // - data: string receiving data from socket - any data successfully received is appended
892 // - returns success flag
893 bool IP_socket::receive (std::string
& data
)
895 return m_impl
->receive(data
);
898 // receive data through a connectionless (UDP) socket
899 // - packet: string receiving data from socket - any data successfully received is appended
900 // - remote_address: returns the address of the remote host received from
901 // - remote_port: returns the port of the remote host received from
902 // - returns success flag
903 bool IP_socket::receive_packet(std::string
& packet
, unsigned long& remote_address
, unsigned short& remote_port
)
905 return m_impl
->receive_packet(packet
, remote_address
, remote_port
);
908 // variant of above which does not give back the address and port of the sender
909 // receive data through a connectionless (UDP) socket
910 // - packet: string receiving data from socket - any data successfully received is appended
911 // - returns success flag
912 bool IP_socket::receive_packet(std::string
& packet
)
914 return m_impl
->receive_packet(packet
);
917 ////////////////////////////////////////////////////////////////////////////
920 IP_socket_type
IP_socket::type(void) const
922 return m_impl
->type();
925 unsigned short IP_socket::local_port(void) const
927 return m_impl
->local_port();
930 unsigned long IP_socket::remote_address(void) const
932 return m_impl
->remote_address();
935 unsigned short IP_socket::remote_port(void) const
937 return m_impl
->remote_port();
940 ////////////////////////////////////////////////////////////////////////////
943 void IP_socket::set_error (int error
, const std::string
& message
) const
945 m_impl
->set_error(error
, message
.c_str());
948 void IP_socket::clear_error (void) const
950 m_impl
->clear_error();
953 int IP_socket::error(void) const
955 return m_impl
->error();
958 std::string
IP_socket::message(void) const
960 return m_impl
->message();
963 ////////////////////////////////////////////////////////////////////////////////
965 } // end namespace stlplus