Browse Source

[trac554] Added IOFetch

IOFetch is a general upstream "fetch" class that should be able to
operate over TCP or UDP.  Related changes have been made in the
associated classes.  So far, only the unit tests for a UDP fetch
have been made (and passed).
Stephen Morris 14 years ago
parent
commit
85b6fa72d6

+ 363 - 7
INSTALL

@@ -1,9 +1,365 @@
-To build "configure" file:
-    autoreconf
+Installation Instructions
+*************************
 
-To then build from source:
-    ./configure
-    make
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
+2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+
+   Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved.  This file is offered as-is,
+without warranty of any kind.
+
+Basic Installation
+==================
+
+   Briefly, the shell commands `./configure; make; make install' should
+configure, build, and install this package.  The following
+more-detailed instructions are generic; see the `README' file for
+instructions specific to this package.  Some packages provide this
+`INSTALL' file but do not implement all of the features documented
+below.  The lack of an optional feature in a given package is not
+necessarily a bug.  More recommendations for GNU packages can be found
+in *note Makefile Conventions: (standards)Makefile Conventions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+   It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring.  Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'.  You need `configure.ac' if
+you want to change it or regenerate `configure' using a newer version
+of `autoconf'.
+
+   The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.
+
+     Running `configure' might take a while.  While running, it prints
+     some messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package, generally using the just-built uninstalled binaries.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.  When installing into a prefix owned by root, it is
+     recommended that the package be configured and built as a regular
+     user, and only the `make install' phase executed with root
+     privileges.
+
+  5. Optionally, type `make installcheck' to repeat any self-tests, but
+     this time using the binaries in their final installed location.
+     This target does not install anything.  Running this target as a
+     regular user, particularly if the prior `make install' required
+     root privileges, verifies that the installation completed
+     correctly.
+
+  6. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+  7. Often, you can also type `make uninstall' to remove the installed
+     files again.  In practice, not all packages have tested that
+     uninstallation works correctly, even though it is required by the
+     GNU Coding Standards.
+
+  8. Some packages, particularly those that use Automake, provide `make
+     distcheck', which can by used by developers to test that all other
+     targets like `make install' and `make uninstall' work correctly.
+     This target is generally not run by end users.
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the `configure' script does not know about.  Run `./configure --help'
+for details on some of the pertinent environment variables.
+
+   You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here
+is an example:
+
+     ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you can use GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.  This
+is known as a "VPATH" build.
+
+   With a non-GNU `make', it is safer to compile the package for one
+architecture at a time in the source code directory.  After you have
+installed the package for one architecture, use `make distclean' before
+reconfiguring for another architecture.
+
+   On MacOS X 10.5 and later systems, you can create libraries and
+executables that work on multiple system types--known as "fat" or
+"universal" binaries--by specifying multiple `-arch' options to the
+compiler but only a single `-arch' option to the preprocessor.  Like
+this:
+
+     ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+                 CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+                 CPP="gcc -E" CXXCPP="g++ -E"
+
+   This is not guaranteed to produce working output in all cases, you
+may have to build one architecture at a time and combine the results
+using the `lipo' tool if you have problems.
+
+Installation Names
+==================
+
+   By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc.  You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX', where PREFIX must be an
+absolute file name.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.  In general, the
+default for these options is expressed in terms of `${prefix}', so that
+specifying just `--prefix' will affect all of the other directory
+specifications that were not explicitly provided.
+
+   The most portable way to affect installation locations is to pass the
+correct locations to `configure'; however, many packages provide one or
+both of the following shortcuts of passing variable assignments to the
+`make install' command line to change installation locations without
+having to reconfigure or recompile.
+
+   The first method involves providing an override variable for each
+affected directory.  For example, `make install
+prefix=/alternate/directory' will choose an alternate location for all
+directory configuration variables that were expressed in terms of
+`${prefix}'.  Any directories that were specified during `configure',
+but not in terms of `${prefix}', must each be overridden at install
+time for the entire installation to be relocated.  The approach of
+makefile variable overrides for each directory variable is required by
+the GNU Coding Standards, and ideally causes no recompilation.
+However, some platforms have known limitations with the semantics of
+shared libraries that end up requiring recompilation when using this
+method, particularly noticeable in packages that use GNU Libtool.
+
+   The second method involves providing the `DESTDIR' variable.  For
+example, `make install DESTDIR=/alternate/directory' will prepend
+`/alternate/directory' before all installation names.  The approach of
+`DESTDIR' overrides is not required by the GNU Coding Standards, and
+does not work on platforms that have drive letters.  On the other hand,
+it does better at avoiding recompilation issues, and works well even
+when some directory options were not specified in terms of `${prefix}'
+at `configure' time.
+
+Optional Features
+=================
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+   Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+   Some packages offer the ability to configure how verbose the
+execution of `make' will be.  For these packages, running `./configure
+--enable-silent-rules' sets the default to minimal output, which can be
+overridden with `make V=1'; while running `./configure
+--disable-silent-rules' sets the default to verbose, which can be
+overridden with `make V=0'.
+
+Particular systems
+==================
+
+   On HP-UX, the default C compiler is not ANSI C compatible.  If GNU
+CC is not installed, it is recommended to use the following options in
+order to use an ANSI C compiler:
+
+     ./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
+
+and if that doesn't work, install pre-built binaries of GCC for HP-UX.
+
+   On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
+parse its `<wchar.h>' header file.  The option `-nodtk' can be used as
+a workaround.  If GNU CC is not installed, it is therefore recommended
+to try
+
+     ./configure CC="cc"
+
+and if that doesn't work, try
+
+     ./configure CC="cc -nodtk"
+
+   On Solaris, don't put `/usr/ucb' early in your `PATH'.  This
+directory contains several dysfunctional programs; working variants of
+these programs are available in `/usr/bin'.  So, if you need `/usr/ucb'
+in your `PATH', put it _after_ `/usr/bin'.
+
+   On Haiku, software installed for all users goes in `/boot/common',
+not `/usr/local'.  It is recommended to use the following options:
+
+     ./configure --prefix=/boot/common
+
+Specifying the System Type
+==========================
+
+   There may be some features `configure' cannot figure out
+automatically, but needs to determine by the type of machine the package
+will run on.  Usually, assuming the package is built to be run on the
+_same_ architectures, `configure' can figure that out, but if it prints
+a message saying it cannot guess the machine type, give it the
+`--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS
+     KERNEL-OS
+
+   See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+   If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+   Variables not defined in a site shell script can be set in the
+environment passed to `configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf bug.  Until the bug is fixed you can use this workaround:
+
+     CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`configure' Invocation
+======================
+
+   `configure' recognizes the following options to control how it
+operates.
+
+`--help'
+`-h'
+     Print a summary of all of the options to `configure', and exit.
+
+`--help=short'
+`--help=recursive'
+     Print a summary of the options unique to this package's
+     `configure', and exit.  The `short' variant lists options used
+     only in the top level, while the `recursive' variant lists options
+     also present in any nested packages.
+
+`--version'
+`-V'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally `config.cache'.  FILE defaults to `/dev/null' to
+     disable caching.
+
+`--config-cache'
+`-C'
+     Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`--prefix=DIR'
+     Use DIR as the installation prefix.  *note Installation Names::
+     for more details, including other options available for fine-tuning
+     the installation locations.
+
+`--no-create'
+`-n'
+     Run the configure checks, but stop before creating any output
+     files.
+
+`configure' also accepts some other, not widely useful, options.  Run
+`configure --help' for more details.
 
-For detailed installation directions, see the guide
-at doc/guide/bind10-guide.html.

+ 16 - 14
src/lib/asiolink/Makefile.am

@@ -12,26 +12,28 @@ CLEANFILES = *.gcno *.gcda
 # have some code fragments that would hit gcc's unused-parameter warning,
 # which would make the build fail with -Werror (our default setting).
 lib_LTLIBRARIES = libasiolink.la
-libasiolink_la_SOURCES = asiolink.h
-libasiolink_la_SOURCES += io_service.cc io_service.h
-libasiolink_la_SOURCES += dns_service.cc dns_service.h
-libasiolink_la_SOURCES += dns_server.h
-libasiolink_la_SOURCES += dns_lookup.h
+libasiolink_la_SOURCES  = asiolink.h
 libasiolink_la_SOURCES += dns_answer.h
-libasiolink_la_SOURCES += simple_callback.h
+libasiolink_la_SOURCES += dns_lookup.h
+libasiolink_la_SOURCES += dns_server.h
+libasiolink_la_SOURCES += dns_service.h dns_service.cc
 libasiolink_la_SOURCES += interval_timer.h interval_timer.cc
-libasiolink_la_SOURCES += recursive_query.h recursive_query.cc
+libasiolink_la_SOURCES += io_address.h io_address.cc
+libasiolink_la_SOURCES += io_endpoint.h io_endpoint.cc
 libasiolink_la_SOURCES += io_error.h
-libasiolink_la_SOURCES += io_socket.cc io_socket.h
+libasiolink_la_SOURCES += io_fetch.h io_fetch.cc
 libasiolink_la_SOURCES += io_message.h
-libasiolink_la_SOURCES += io_address.cc io_address.h
-libasiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
+libasiolink_la_SOURCES += io_service.h io_service.cc
+libasiolink_la_SOURCES += io_socket.h io_socket.cc
+libasiolink_la_SOURCES += recursive_query.h recursive_query.cc
+libasiolink_la_SOURCES += simple_callback.h
+libasiolink_la_SOURCES += tcp_endpoint.h
+libasiolink_la_SOURCES += tcp_server.h tcp_server.cc
+libasiolink_la_SOURCES += tcp_socket.h
 libasiolink_la_SOURCES += udp_endpoint.h
-libasiolink_la_SOURCES += udp_server.h udp_server.cc
-libasiolink_la_SOURCES += udp_socket.h udp_socket.cc
 libasiolink_la_SOURCES += udp_query.h udp_query.cc
-libasiolink_la_SOURCES += tcp_endpoint.h tcp_socket.h
-libasiolink_la_SOURCES += tcp_server.h tcp_server.cc
+libasiolink_la_SOURCES += udp_server.h udp_server.cc
+libasiolink_la_SOURCES += udp_socket.h
 # Note: the ordering matters: -Wno-... must follow -Wextra (defined in
 # B10_CXXFLAGS)
 libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)

+ 0 - 3
src/lib/asiolink/asiolink.h

@@ -34,9 +34,6 @@
 #include <asiolink/io_socket.h>
 #include <asiolink/io_error.h>
 
-#include <asiolink/udp_endpoint.h>
-#include <asiolink/udp_socket.h>
-
 /// \namespace asiolink
 /// \brief A wrapper interface for the ASIO library.
 ///

+ 4 - 1
src/lib/asiolink/io_address.cc

@@ -20,7 +20,10 @@
 
 #include <asio.hpp>
 
-#include <asiolink/asiolink.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
 
 using namespace asio;
 using asio::ip::udp;

+ 3 - 3
src/lib/asiolink/io_address.h

@@ -12,8 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef __IOADDRESS_H
-#define __IOADDRESS_H 1
+#ifndef __IO_ADDRESS_H
+#define __IO_ADDRESS_H 1
 
 // IMPORTANT NOTE: only very few ASIO headers files can be included in
 // this file.  In particular, asio.hpp should never be included here.
@@ -120,7 +120,7 @@ private:
 };
 
 }      // asiolink
-#endif // __IOADDRESS_H
+#endif // __IO_ADDRESS_H
 
 // Local Variables: 
 // mode: c++

+ 304 - 0
src/lib/asiolink/io_asio_socket.h

@@ -0,0 +1,304 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __IO_ASIO_SOCKET_H
+#define __IO_ASIO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file.  In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h>             // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <coroutine.h>
+
+#include <asiolink/io_error.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_completion_cb.h>
+
+using namespace asio;
+
+namespace asiolink {
+
+/// \brief Socket not open
+///
+/// Thrown on an attempt to do read/write to a socket that is not open.
+class SocketNotOpen : public IOError {
+public:
+    SocketNotOpen(const char* file, size_t line, const char* what) :
+        IOError(file, line, what) {}
+};
+
+
+
+/// Forward declaration of an IOEndpoint
+class IOEndpoint;
+
+
+/// \brief I/O Socket with asynchronous operations
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// This is the basic IOSocket with additional operations - open, send, receive
+/// and close.  Depending on how the asiolink code develops, it may be a
+/// temporary class: its main use is to add the template parameter needed for
+/// the derived classes UDPSocket and TCPSocket but without changing the
+/// signature of the more basic IOSocket class.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it.  Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+///
+/// TODO: Check if IOAsioSocket class is still needed
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class IOAsioSocket : public IOSocket {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOAsioSocket(const IOAsioSocket<C>& source);
+    IOAsioSocket& operator=(const IOAsioSocket<C>& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    IOAsioSocket() {}
+public:
+    /// The destructor.
+    virtual ~IOAsioSocket() {}
+    //@}
+
+    /// \brief Return the "native" representation of the socket.
+    ///
+    /// In practice, this is the file descriptor of the socket for
+    /// UNIX-like systems so the current implementation simply uses
+    /// \c int as the type of the return value.
+    /// We may have to need revisit this decision later.
+    ///
+    /// In general, the application should avoid using this method;
+    /// it essentially discloses an implementation specific "handle" that
+    /// can change the internal state of the socket (consider the
+    /// application closes it, for example).
+    /// But we sometimes need to perform very low-level operations that
+    /// requires the native representation.  Passing the file descriptor
+    /// to a different process is one example.
+    /// This method is provided as a necessary evil for such limited purposes.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return The native representation of the socket.  This is the socket
+    /// file descriptor for UNIX-like systems.
+    virtual int getNative() const = 0;
+
+    /// \brief Return the transport protocol of the socket.
+    ///
+    /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+    /// \c IPPROTO_TCP for TCP sockets.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return IPPROTO_UDP for UDP sockets
+    /// \return IPPROTO_TCP for TCP sockets
+    virtual int getProtocol() const = 0;
+
+    /// \brief Open AsioSocket
+    ///
+    /// A call that is a no-op on UDP sockets, this opens a connection to the
+    /// system identified by the given endpoint.
+    ///
+    /// \param endpoint Pointer to the endpoint object.  This is ignored for
+    /// a UDP socket (the target is specified in the send call), but should
+    /// be of type TCPEndpoint for a TCP connection.
+    /// \param callback I/O Completion callback, called when the connect has
+    /// completed.  In the stackless coroutines model, this will be the
+    /// method containing the call to this function, allowing the operation to
+    /// resume after the socket open has completed.
+    ///
+    /// \return true if an asynchronous operation was started and the caller
+    /// should yield and wait for completion, false if not. (i.e. The UDP
+    /// derived class will return false, the TCP class will return true).  This
+    /// optimisation avoids the overhead required to post a callback to the
+    /// I/O Service queue where nothing is done.
+    virtual bool open(const IOEndpoint* endpoint, C& callback) = 0;
+
+    /// \brief Send Asynchronously
+    ///
+    /// This corresponds to async_send_to() for UDP sockets and async_send()
+    /// for TCP.  In both cases an endpoint argument is supplied indicating the
+    /// target of the send - this is ignored for TCP.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param endpoint Target of the send
+    /// \param callback Callback object.
+    virtual void asyncSend(const void* data, size_t length,
+        const IOEndpoint* endpoint, C& callback) = 0;
+
+    /// \brief Receive Asynchronously
+    ///
+    /// This correstponds to async_receive_from() for UDP sockets and
+    /// async_receive() for TCP.  In both cases, an endpoint argument is
+    /// supplied to receive the source of the communication.  For TCP it will
+    /// be filled in with details of the connection.
+    ///
+    /// \param data Buffer to receive incoming message
+    /// \param length Length of the data buffer
+    /// \param cumulative Amount of data that should already be in the buffer.
+    /// \param endpoint Source of the communication
+    /// \param callback Callback object
+    virtual void asyncReceive(void* data, size_t length, size_t cumulative,
+        IOEndpoint* endpoint, C& callback) = 0;
+
+    /// \brief Checks if the data received is complete.
+    ///
+    /// This applies to TCP receives, where the data is a byte stream and a
+    /// receive is not guaranteed to receive the entire message.  DNS messages
+    /// over TCP are prefixed by a two-byte count field.  This method takes the
+    /// amount received so far and the amount received in this I/O and checks
+    /// if the message is complete, returning the appropriate indication.  As
+    /// a side-effect, it also updates the amount received.
+    ///
+    /// For a UDP receive, all the data is received in one I/O, so this is
+    /// effectively a no-op (although it does update the amount received).
+    ///
+    /// \param data Data buffer containing data to date
+    /// \param length Amount of data received in last asynchronous I/O
+    /// \param cumulative On input, amount of data received before the last
+    /// I/O.  On output, the total amount of data received to date.
+    ///
+    /// \return true if the receive is complete, false if another receive is
+    /// needed.
+    virtual bool receiveComplete(void* data, size_t length,
+        size_t& cumulative) = 0;
+
+    /// \brief Cancel I/O On AsioSocket
+    virtual void cancel() = 0;
+
+    /// \brief Close socket
+    virtual void close() = 0;
+};
+
+
+#include "io_socket.h"
+
+/// \brief The \c DummyAsioSocket class is a concrete derived class of
+/// \c IOAsioSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOAsioSocket object without involving system resource
+/// allocation such as real network sockets.
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class DummyAsioSocket : public IOAsioSocket<C> {
+private:
+    DummyAsioSocket(const DummyAsioSocket<C>& source);
+    DummyAsioSocket& operator=(const DummyAsioSocket<C>& source);
+public:
+    /// \brief Constructor from the protocol number.
+    ///
+    /// The protocol must validly identify a standard network protocol.
+    /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+    ///
+    /// \param protocol The network protocol number for the socket.
+    DummyAsioSocket(const int protocol) : protocol_(protocol) {}
+
+    /// \brief A dummy derived method of \c IOAsioSocket::getNative().
+    ///
+    /// \return Always returns -1 as the object is not associated with a real
+    /// (native) socket.
+    virtual int getNative() const { return (-1); }
+
+    /// \brief A dummy derived method of \c IOAsioSocket::getProtocol().
+    ///
+    /// \return Protocol socket was created with
+    virtual int getProtocol() const { return (protocol_); }
+
+
+    /// \brief Open AsioSocket
+    ///
+    /// A call that is a no-op on UDP sockets, this opens a connection to the
+    /// system identified by the given endpoint.
+    ///
+    /// \param endpoint Unused
+    /// \param callback Unused.
+    ///false indicating that the operation completed synchronously.
+    virtual bool open(const IOEndpoint*, C&) {
+        return (false);
+    }
+
+    /// \brief Send Asynchronously
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    ///
+    /// \param data Unused
+    /// \param length Unused
+    /// \param endpoint Unused
+    /// \param callback Unused
+    virtual void asyncSend(const void*, size_t, const IOEndpoint*, C&) {
+    }
+
+    /// \brief Receive Asynchronously
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    ///
+    /// \param data Unused
+    /// \param length Unused
+    /// \param cumulative Unused
+    /// \param endpoint Unused
+    /// \param callback Unused
+    virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) { } 
+    /// \brief Checks if the data received is complete.
+    ///
+    /// \param data Unused
+    /// \param length Unused
+    /// \param cumulative Unused
+    ///
+    /// \return Always true
+    virtual bool receiveComplete(void*, size_t, size_t&) {
+        return (true);
+    }
+
+    /// \brief Cancel I/O On AsioSocket
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    virtual void cancel() {
+    }
+
+    /// \brief Close socket
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    virtual void close() {
+    }
+
+private:
+    const int protocol_;
+};
+
+} // namespace asiolink
+
+#endif // __IO_ASIO_SOCKET_H

+ 17 - 58
src/lib/asiolink/io_completion_cb.h

@@ -15,74 +15,33 @@
 #ifndef __IO_COMPLETION_CB_H
 #define __IO_COMPLETION_CB_H
 
+#include <iostream>
+
 #include <asio/error.hpp>
 #include <asio/error_code.hpp>
-#include <coroutine.h>
 
 namespace asiolink {
 
 /// \brief Asynchronous I/O Completion Callback
 ///
-/// The stackless coroutine code requires that there be an "entry function"
-/// containing the coroutine macros.  When the coroutine yields, its state is
-/// stored and when the "entry function" is called again, it jumps to the
-/// line when processing left off last time.  In BIND-10, that "entry function"
-/// is the Boost asynchronous I/O completion callback - in essence operator().
-///
-/// This class solves the problem of circularity in class definitions.  In
-/// BIND10, classes such as IOFetch contain the coroutine code.  These include
-/// objects of classes such as IOSocket, whose signature has to include the
-/// callback object - IOFetch.  By abstracting the I/O completion callback into
-/// this class, that circularity is broken.
-///
-/// One more point: the asynchronous I/O functions take the callback object by
-/// reference.  But if a derived class object is passed as a reference to its
-/// base class, "class slicing" takes place - the derived part of the class is
-/// lost and only the base class functionality remains.  By storing a pointer
-/// to the true object and having the base class method call the derived class
-/// method through that, the behaviour of class inheritance is restored. In
-/// other words:
-/// \code
-/// class derived: public class base {
-/// :
-/// };
-/// derived drv;
-///
-/// // Call with pointer to base class
-/// void f(base* b, asio::error_code& ec, size_t length) {
-///    b->operator()(ec, length);
-/// }
-///
-/// // Call with reference to base class
-/// void g(base& b, asio::error_code& ec, size_t length) {
-///    b.operator()(ec, length);
-/// }
-///
-/// void function xyz(derived *d, asio::error_code& ec, size_t length) {
-///    f(d, ec, length);  // Calls derived::operator()
-///    g(*d, ec, length); // Also calls derived::operator()
-/// }
-/// \endcode
-
-class IOCompletionCallback : public coroutine {
+/// The two socket classes (UDPSocket and TCPSocket) require that the I/O
+/// completion callback function have an operator() method with the appropriate
+/// signature.  The classes are templates, any class with that method and
+/// signature can be passed as the callback object - there is no need for a
+/// base class defining the interface.  However, some users of the socket
+/// classes do not use the asynchronous I/O operations, yet have to supply a
+/// template parameter.  This is the reason for this class - it is the dummy
+/// template parameter.
+
+class IOCompletionCallback {
 public:
 
-    /// \brief Constructor
-    IOCompletionCallback() : self_(this)
-    {}
-
-    /// \brief Virtual Destructor
-    virtual ~IOCompletionCallback()
+    /// \brief Asynchronous I/O callback method
+    ///
+    /// \param error Unused
+    /// \param length Unused
+    void operator()(asio::error_code, size_t)
     {}
-
-    /// \brief Callback Method
-    virtual void operator()(asio::error_code ec = asio::error_code(),
-        size_t length = 0) {
-        (*self_)(ec, length);
-    }
-
-private:
-    IOCompletionCallback*   self_;      ///< Pointer to real object
 };
 
 } // namespace asiolink

+ 2 - 1
src/lib/asiolink/io_endpoint.cc

@@ -20,7 +20,8 @@
 
 #include <asio.hpp>
 
-#include <asiolink/asiolink.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
 #include <asiolink/tcp_endpoint.h>
 #include <asiolink/udp_endpoint.h>
 

+ 3 - 3
src/lib/asiolink/io_endpoint.h

@@ -12,8 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef __IOENDPOINT_H
-#define __IOENDPOINT_H 1
+#ifndef __IO_ENDPOINT_H
+#define __IO_ENDPOINT_H 1
 
 // IMPORTANT NOTE: only very few ASIO headers files can be included in
 // this file.  In particular, asio.hpp should never be included here.
@@ -115,7 +115,7 @@ public:
 };
 
 }      // asiolink
-#endif // __IOENDPOINT_H
+#endif // __IO_ENDPOINT_H
 
 // Local Variables: 
 // mode: c++

+ 47 - 86
src/lib/asiolink/io_fetch.cc

@@ -18,95 +18,38 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 
-#include <asio.hpp>
-#include <asio/deadline_timer.hpp>
-#include <asio/ip/address.hpp>
-
-#include <boost/shared_ptr.hpp>
 #include <boost/bind.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
 
-#include <dns/buffer.h>
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
-#include <log/dummylog.h>
 #include <dns/opcode.h>
 #include <dns/rcode.h>
+#include <log/dummylog.h>
 
-#include <asiolink.h>
-#include <coroutine.h>
-#include <internal/udpdns.h>
-#include <internal/tcpdns.h>
-#include <internal/iofetch.h>
+#include <asio.hpp>
+#include <asiolink/io_fetch.h>
 
 using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-using isc::log::dlog;
-
-using namespace std;
 using namespace isc::dns;
+using namespace isc::log;
+using namespace std;
 
 namespace asiolink {
 
-// Constructor for the IOFetchData member
-
-/// \brief Constructor
-///
-/// Just fills in the data members of the IOFetchData structure
-///
-/// \param io_service I/O Service object to handle the asynchronous
-///     operations.
-/// \param question DNS question to send to the upstream server.
-/// \param address IP address of upstream server
-/// \param port Port to use for the query
-/// \param buffer Output buffer into which the response (in wire format)
-///     is written (if a response is received).
-/// \param callback Callback object containing the callback to be called
-///     when we terminate.  The caller is responsible for managing this
-///     object and deleting it if necessary.
-/// \param timeout Timeout for the fetch (in ms).  The default value of
-///     -1 indicates no timeout.
-/// \param protocol Protocol to use for the fetch.  The default is UDP
-
-IOFetch::IOFetchData::IOFetchData(IOService& io_service,
-    const isc::dns::Question& query, const IOAddress& address, uint16_t port,
-    isc::dns::OutputBufferPtr buff, Callback* cb, int wait, int protocol)
-        :
-    socket((protocol == IPPROTO_UDP) ?
-        static_cast<IOSocket*>(new UDPSocket(io_service, address)) :
-        static_cast<IOSocket*>(new TCPSocket(io_service, address))
-    ),
-    remote((protocol == IPPROTO_UDP) ?
-        static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
-        static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
-    ),
-    question(query),
-    buffer(buff),
-    msgbuf(new OutputBuffer(512)),         // TODO: Why this number?
-    data(new char[IOFetch::MAX_LENGTH]),
-    callback(cb),
-    rcv_amount(0),
-    stopped(false),
-    timer(io_service.get_io_service()),
-    timeout(wait)
-{
-}
-
-
-
 /// IOFetch Constructor - just initialize the private data
-IOFetch::IOFetch(IOService& io_service, const Question& question,
-    const IOAddress& address, uint16_t port, OutputBufferPtr buffer,
-    Callback *callback, int timeout, int protocol) :
-        data_(new IOFetch::IOFetchData(io_service, question, address, port,
-            buffer, callback, timeout, protocol)
-        )
+
+IOFetch::IOFetch(int protocol, IOService& service,
+    const isc::dns::Question& question, const IOAddress& address, uint16_t port,
+    isc::dns::OutputBufferPtr& buff, Callback* cb, int wait)
+    :
+    data_(new IOFetch::IOFetchData(protocol, service, question, address,
+        port, buff, cb, wait))
 {
 }
 
 /// The function operator is implemented with the "stackless coroutine"
 /// pattern; see internal/coroutine.h for details.
+
 void
 IOFetch::operator()(error_code ec, size_t length) {
     if (ec || data_->stopped) {
@@ -114,6 +57,7 @@ IOFetch::operator()(error_code ec, size_t length) {
     }
 
     CORO_REENTER (this) {
+
         /// Generate the upstream query and render it to wire format
         /// This is done in a different scope to allow inline variable
         /// declarations.
@@ -130,7 +74,7 @@ IOFetch::operator()(error_code ec, size_t length) {
             msg.toWire(renderer);
 
             // As this is a new fetch, clear the amount of data received
-            data_->rcv_amount = 0;
+            data_->cumulative = 0;
 
             dlog("Sending " + msg.toText() + " to " +
                 data_->remote->getAddress().toText());
@@ -148,22 +92,30 @@ IOFetch::operator()(error_code ec, size_t length) {
 
         // Open a connection to the target system.  For speed, if the operation
         // was completed synchronously (i.e. UDP operation) we bypass the yield.
-        bool asynch = data_->socket->open(data->remote.get(), *this);
-        if (asynch) {
+        if (data_->socket->open(data_->remote.get(), *this)) {
             CORO_YIELD;
         }
 
-        // Begin an asynchronous send, and then yield.  When the
+        // Begin an asynchronous send, and then yield.  When the send completes
         // send completes, we will resume immediately after this point.
-        CORO_YIELD data_->socket->async_send(data_->msgbuf->getData(),
+        CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(),
             data_->msgbuf->getLength(), data_->remote.get(), *this);
 
-        /// Begin an asynchronous receive, and yield.  When the receive
-        /// completes, we will resume immediately after this point.
-        CORO_YIELD data_->socket->async_receive(data_->data.get(),
-            static_cast<size_t>(MAX_LENGTH), data_->remote.get(), *this);
-
-        // The message is not rendered yet, so we can't print it easilly
+        // Now receive the response.  Since TCP may not receive the entire
+        // message in one operation, we need to loop until we have received
+        // it. (This can't be done within the asyncReceive() method because
+        // each I/O operation will be done asynchronously and between each one
+        // we need to yield ... and we *really* don't want to set up another
+        // coroutine within that method.)  So after each receive (and yield),
+        // we check if the operation is complete and if not, loop to read again.
+        do {
+            CORO_YIELD data_->socket->asyncReceive(data_->data.get(),
+                static_cast<size_t>(MAX_LENGTH), data_->cumulative,
+                data_->remote.get(), *this);
+        } while (!data_->socket->receiveComplete(data_->data.get(), length,
+            data_->cumulative));
+
+        // The message is not rendered yet, so we can't print it easily
         dlog("Received response from " + data_->remote->getAddress().toText());
 
         /// Copy the answer into the response buffer.  (TODO: If the
@@ -188,6 +140,7 @@ IOFetch::operator()(error_code ec, size_t length) {
 // As the function may be entered multiple times as things wind down, the
 // stopped_ flag checks if stop() has already been called.  If it has,
 // subsequent calls are no-ops.
+
 void
 IOFetch::stop(Result result) {
     if (!data_->stopped) {
@@ -203,15 +156,23 @@ IOFetch::stop(Result result) {
             default:
                 ;
         }
-        data_->stopped = true;
-        data_->socket->cancel();    // Cancel outstanding I/O
-        data_->socket->close();     // ... and close the socket
-        data_->timer.cancel();      // Cancel timeout timer
 
+        // Stop requested, cancel and I/O's on the socket and shut it down,
+        // and cancel the timer.
+        data_->socket->cancel();
+        data_->socket->close();
+
+        data_->timer.cancel();
+
+        // Execute the I/O completion callback (if present).
         if (data_->callback) {
-            (*(data_->callback))(result);   // Call callback
+            (*(data_->callback))(result);
         }
+
+        // Mark that stop() has now been called.
+        data_->stopped = true;
     }
 }
 
 } // namespace asiolink
+

+ 82 - 52
src/lib/asiolink/io_fetch.h

@@ -12,40 +12,44 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef __IOFETCH_H
-#define __IOFETCH_H 1
-
-#include <netinet/in.h>
+#ifndef __IO_FETCH_H
+#define __IO_FETCH_H 1
 
 #include <config.h>
 
-#include <asio.hpp>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>             // for some IPC/network system calls
+
 #include <boost/shared_array.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
 #include <asio/deadline_timer.hpp>
 
+#include <coroutine.h>
+
 #include <dns/buffer.h>
 #include <dns/question.h>
 
-#include <asiolink/asiolink.h>
-#include <asiolink/ioaddress.h>
-#include <asiolink/iocompletioncb.h>
-#include <asiolink/iocompletioncb.h>
-
-
-#include <asiolink/iosocket.h>
-#include <asiolink/ioendpoint.h>
-#include <coroutine.h>
-
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <asiolink/udp_endpoint.h>
 
 
 namespace asiolink {
 
+
 /// \brief Upstream Fetch Processing
 ///
 /// IOFetch is the class used to send upstream fetches and to handle responses.
-/// It is more or less transport-agnostic, although the
-class IOFetch : public IOCompletionCallback  {
+///
+/// \param E Endpoint type to use.
+
+class IOFetch : public coroutine {
 public:
 
     /// \brief Result of Upstream Fetch
@@ -56,8 +60,10 @@ public:
     enum Result {
         SUCCESS = 0,        ///< Success, fetch completed
         TIME_OUT,           ///< Failure, fetch timed out
-        STOPPED             ///< Control code, fetch has been stopped
+        STOPPED,            ///< Control code, fetch has been stopped
+        NOTSET              ///< For testing, indicates value not set
     };
+
     // The next enum is a "trick" to allow constants to be defined in a class
     // declaration.
 
@@ -65,8 +71,10 @@ public:
     enum {
         MAX_LENGTH = 4096   ///< Maximum size of receive buffer
     };
+
     /// \brief I/O Fetch Callback
     ///
+    /// TODO: change documentation
     /// Callback object for when the fetch itself has completed.  Note that this
     /// is different to the IOCompletionCallback; that is used to signal the
     /// completion of an asynchronous I/O call.  The IOFetch::Callback is called
@@ -94,9 +102,9 @@ public:
     ///
     /// The data for IOFetch is held in a separate struct pointed to by a
     /// shared_ptr object.  This is because the IOFetch object will be copied
-    /// often (it is used as a coroutine and passed as callback to many async_*()
-    /// functions) and we want keep the same data).  Organising the data this
-    /// way keeps copying to a minimum.
+    /// often (it is used as a coroutine and passed as callback to many
+    /// async_*() functions) and we want keep the same data).  Organising the
+    /// data in this way keeps copying to a minimum.
     struct IOFetchData {
 
         // The next two members are shared pointers to a base class because what
@@ -104,15 +112,15 @@ public:
         // TCP, which is not known until construction of the IOFetch.  Use of
         // a shared pointer here is merely to ensure deletion when the data
         // object is deleted.
-        boost::shared_ptr<IOSocket> socket;     ///< Socket to use for I/O
+        boost::shared_ptr<IOAsioSocket<IOFetch> > socket;
+                                                ///< Socket to use for I/O
         boost::shared_ptr<IOEndpoint> remote;   ///< Where the fetch was sent
-
         isc::dns::Question          question;   ///< Question to be asked
+        isc::dns::OutputBufferPtr   msgbuf;     ///< Wire buffer for question
         isc::dns::OutputBufferPtr   buffer;     ///< Received data held here
-        isc::dns::OutputBufferPtr   msgbuf;     ///< ... and here
-        boost::shared_array<char>   data;       ///< Temporary array for the data
-        Callback*                   callback;   ///< Called on I/O Completion
-        size_t                      rcv_amount; ///< Received amount
+        boost::shared_array<char>   data;       ///< Temporary array for data
+        IOFetch::Callback*          callback;   ///< Called on I/O Completion
+        size_t                      cumulative; ///< Cumulative received amount
         bool                        stopped;    ///< Have we stopped running?
         asio::deadline_timer        timer;      ///< Timer to measure timeouts
         int                         timeout;    ///< Timeout in ms
@@ -121,7 +129,8 @@ public:
         ///
         /// Just fills in the data members of the IOFetchData structure
         ///
-        /// \param io_service I/O Service object to handle the asynchronous
+        /// \param protocol either IPPROTO_UDP or IPPROTO_TCP
+        /// \param service I/O Service object to handle the asynchronous
         ///     operations.
         /// \param query DNS question to send to the upstream server.
         /// \param address IP address of upstream server
@@ -131,38 +140,60 @@ public:
         /// \param cb Callback object containing the callback to be called
         ///     when we terminate.  The caller is responsible for managing this
         ///     object and deleting it if necessary.
-        /// \param wait Timeout for the fetch (in ms).  The default value of
-        ///     -1 indicates no timeout.
-        /// \param protocol Protocol to use for the fetch.  The default is UDP
-
-        IOFetchData(IOService& io_service, const isc::dns::Question& query,
-            const IOAddress& address, uint16_t port,
-            isc::dns::OutputBufferPtr buff, Callback* cb, int wait = -1,
-            int protocol = IPPROTO_UDP);
+        /// \param wait Timeout for the fetch (in ms).
+        IOFetchData(int protocol, IOService& service,
+            const isc::dns::Question& query, const IOAddress& address,
+            uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
+            int wait)
+            :
+            socket((protocol == IPPROTO_UDP) ?
+                static_cast<IOAsioSocket<IOFetch>*>(
+                    new UDPSocket<IOFetch>(service)) :
+                static_cast<IOAsioSocket<IOFetch>*>(
+                    new TCPSocket<IOFetch>(service))
+                ),
+            remote((protocol == IPPROTO_UDP) ?
+                static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
+                static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
+                ),
+            question(query),
+            msgbuf(new isc::dns::OutputBuffer(512)),
+            buffer(buff),
+            data(new char[IOFetch::MAX_LENGTH]),
+            callback(cb),
+            cumulative(0),
+            stopped(false),
+            timer(service.get_io_service()),
+            timeout(wait)
+        {}
     };
 
     /// \brief Constructor.
     ///
     /// Creates the object that will handle the upstream fetch.
     ///
-    /// \param io_service I/O Service object to handle the asynchronous
+    /// TODO: Need to randomise the source port
+    ///
+    /// \param protocol Fetch protocol, either IPPROTO_UDP or IPPROTO_TCP
+    /// \param service I/O Service object to handle the asynchronous
     ///     operations.
     /// \param question DNS question to send to the upstream server.
-    /// \param address IP address of upstream server
-    /// \param port Port to use for the query
-    /// \param buffer Output buffer into which the response (in wire format)
+    /// \param buff Output buffer into which the response (in wire format)
     ///     is written (if a response is received).
-    /// \param callback Callback object containing the callback to be called
+    /// \param cb Callback object containing the callback to be called
     ///     when we terminate.  The caller is responsible for managing this
     ///     object and deleting it if necessary.
-    /// \param timeout Timeout for the fetch (in ms).  The default value of
+    /// \param address IP address of upstream server
+    /// \param port Port to which to connect on the upstream server
+    /// (default = 53)
+    /// \param wait Timeout for the fetch (in ms).  The default value of
     ///     -1 indicates no timeout.
-    /// \param protocol Protocol to use for the fetch.  The default is UDP
-    IOFetch(IOService& io_service, const isc::dns::Question& question,
-        const IOAddress& address, uint16_t port,
-        isc::dns::OutputBufferPtr buffer, Callback* callback,
-        int timeout = -1, int protocol = IPPROTO_UDP);
+    IOFetch(int protocol, IOService& service,
+        const isc::dns::Question& question, const IOAddress& address,
+        uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
+        int wait = -1);
 
+    // The default constructor and copy constructor are correct for this method.
 
     /// \brief Coroutine entry point
     ///
@@ -174,7 +205,6 @@ public:
     void operator()(asio::error_code ec = asio::error_code(),
         size_t length = 0);
 
-
     /// \brief Terminate query
     ///
     /// This method can be called at any point.  It terminates the current
@@ -184,10 +214,10 @@ public:
     void stop(Result reason = STOPPED);
 
 private:
-    boost::shared_ptr<IOFetchData> data_;   ///< Private data
-};
+    boost::shared_ptr<IOFetchData>  data_;   ///< Private data
 
-}
+};
 
+} // namespace asiolink
 
-#endif // __IOFETCH_H
+#endif // __IO_FETCH_H

+ 3 - 3
src/lib/asiolink/io_message.h

@@ -12,8 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef __IOMESSAGE_H
-#define __IOMESSAGE_H 1
+#ifndef __IO_MESSAGE_H
+#define __IO_MESSAGE_H 1
 
 // IMPORTANT NOTE: only very few ASIO headers files can be included in
 // this file.  In particular, asio.hpp should never be included here.
@@ -97,7 +97,7 @@ private:
 
 
 }      // asiolink
-#endif // __IOMESSAGE_H
+#endif // __IO_MESSAGE_H
 
 // Local Variables: 
 // mode: c++

+ 3 - 69
src/lib/asiolink/io_socket.cc

@@ -14,10 +14,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <asio.hpp>
-
 #include "io_socket.h"
 
+#include <asio.hpp>
 
 using namespace asio;
 
@@ -44,76 +43,11 @@ public:
 
     /// \brief A dummy derived method of \c IOSocket::getNative().
     ///
-    /// \return Always returns -1 as the object is not associated with a real
-    /// (native) socket.
+    /// This version of method always returns -1 as the object is not
+    /// associated with a real (native) socket.
     virtual int getNative() const { return (-1); }
 
-    /// \brief A dummy derived method of \c IOSocket::getProtocol().
-    ///
-    /// \return Protocol socket was created with
     virtual int getProtocol() const { return (protocol_); }
-
-
-    /// \brief Open Socket
-    ///
-    /// A call that is a no-op on UDP sockets, this opens a connection to the
-    /// system identified by the given endpoint.
-    ///
-    /// \param endpoint Unused
-    /// \param callback Unused.
-    ///false indicating that the operation completed synchronously.
-    virtual bool open(const IOEndpoint*, IOCompletionCallback&) {
-        return (false);
-    }
-
-    /// \brief Send Asynchronously
-    ///
-    /// Must be supplied as it is abstract in the base class.
-    ///
-    /// \param data Unused
-    /// \param length Unused
-    /// \param endpoint Unused
-    /// \param callback Unused
-    virtual void asyncSend(const void*, size_t, const IOEndpoint*,
-        IOCompletionCallback&) {
-    }
-
-    /// \brief Receive Asynchronously
-    ///
-    /// Must be supplied as it is abstract in the base class.
-    ///
-    /// \param data Unused
-    /// \param length Unused
-    /// \param cumulative Unused
-    /// \param endpoint Unused
-    /// \param callback Unused
-    virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*,
-        IOCompletionCallback&) {
-    }
-
-    /// \brief Checks if the data received is complete.
-    ///
-    /// \param data Unused
-    /// \param length Unused
-    /// \param cumulative Unused
-    ///
-    /// \return Always true
-    virtual bool receiveComplete(void*, size_t, size_t&) {
-        return (true);
-    }
-
-    /// \brief Cancel I/O On Socket
-    ///
-    /// Must be supplied as it is abstract in the base class.
-    virtual void cancel() {
-    }
-
-    /// \brief Close socket
-    ///
-    /// Must be supplied as it is abstract in the base class.
-    virtual void close() {
-    }
-
 private:
     const int protocol_;
 };

+ 4 - 90
src/lib/asiolink/io_socket.h

@@ -12,8 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef __IOSOCKET_H
-#define __IOSOCKET_H 1
+#ifndef __IO_SOCKET_H
+#define __IO_SOCKET_H 1
 
 // IMPORTANT NOTE: only very few ASIO headers files can be included in
 // this file.  In particular, asio.hpp should never be included here.
@@ -24,16 +24,9 @@
 #include <string>
 
 #include <exceptions/exceptions.h>
-#include <coroutine.h>
-
-#include <asiolink/io_completion_cb.h>
 
 namespace asiolink {
 
-/// Forward declaration of an IOEndpoint
-class IOEndpoint;
-
-
 /// \brief The \c IOSocket class is an abstract base class to represent
 /// various types of network sockets.
 ///
@@ -102,82 +95,6 @@ public:
     /// \return IPPROTO_TCP for TCP sockets
     virtual int getProtocol() const = 0;
 
-    /// \brief Open Socket
-    ///
-    /// A call that is a no-op on UDP sockets, this opens a connection to the
-    /// system identified by the given endpoint.
-    ///
-    /// \param endpoint Pointer to the endpoint object.  This is ignored for
-    /// a UDP socket (the target is specified in the send call), but should
-    /// be of type TCPEndpoint for a TCP connection.
-    /// \param callback I/O Completion callback, called when the connect has
-    /// completed.  In the stackless coroutines model, this will be the
-    /// method containing the call to this function, allowing the operation to
-    /// resume after the socket open has completed.
-    ///
-    /// \return true if an asynchronous operation was started and the caller
-    /// should yield and wait for completion, false if not. (i.e. The UDP
-    /// derived class will return false, the TCP class will return true).  This
-    /// optimisation avoids the overhead required to post a callback to the
-    /// I/O Service queue where nothing is done.
-    virtual bool open(const IOEndpoint* endpoint,
-        IOCompletionCallback& callback) = 0;
-
-    /// \brief Send Asynchronously
-    ///
-    /// This corresponds to async_send_to() for UDP sockets and async_send()
-    /// for TCP.  In both cases an endpoint argument is supplied indicating the
-    /// target of the send - this is ignored for TCP.
-    ///
-    /// \param data Data to send
-    /// \param length Length of data to send
-    /// \param endpoint Target of the send
-    /// \param callback Callback object.
-    virtual void asyncSend(const void* data, size_t length,
-        const IOEndpoint* endpoint, IOCompletionCallback& callback) = 0;
-
-    /// \brief Receive Asynchronously
-    ///
-    /// This correstponds to async_receive_from() for UDP sockets and
-    /// async_receive() for TCP.  In both cases, an endpoint argument is
-    /// supplied to receive the source of the communication.  For TCP it will
-    /// be filled in with details of the connection.
-    ///
-    /// \param data Buffer to receive incoming message
-    /// \param length Length of the data buffer
-    /// \param cumulative Amount of data that should already be in the buffer.
-    /// \param endpoint Source of the communication
-    /// \param callback Callback object
-    virtual void asyncReceive(void* data, size_t length, size_t cumulative,
-        IOEndpoint* endpoint, IOCompletionCallback& callback) = 0;
-
-    /// \brief Checks if the data received is complete.
-    ///
-    /// This applies to TCP receives, where the data is a byte stream and a
-    /// receive is not guaranteed to receive the entire message.  DNS messages
-    /// over TCP are prefixed by a two-byte count field.  This method takes the
-    /// amount received so far and the amount received in this I/O and checks
-    /// if the message is complete, returning the appropriate indication.  As
-    /// a side-effect, it also updates the amount received.
-    ///
-    /// For a UDP receive, all the data is received in one I/O, so this is
-    /// effectively a no-op (although it does update the amount received).
-    ///
-    /// \param data Data buffer containing data to date
-    /// \param length Amount of data received in last asynchronous I/O
-    /// \param cumulative On input, amount of data received before the last
-    /// I/O.  On output, the total amount of data received to date.
-    ///
-    /// \return true if the receive is complete, false if another receive is
-    /// needed.
-    virtual bool receiveComplete(void* data, size_t length, size_t& cumulative) = 0;
-
-    /// \brief Cancel I/O On Socket
-    virtual void cancel() = 0;
-
-    /// \brief Close socket
-    virtual void close() = 0;
-
     /// \brief Return a non-usable "dummy" UDP socket for testing.
     ///
     /// This is a class method that returns a "mock" of UDP socket.
@@ -202,9 +119,6 @@ public:
     static IOSocket& getDummyTCPSocket();
 };
 
-}      // asiolink
-#endif // __IOSOCKET_H
+} // namespace asiolink
 
-// Local Variables: 
-// mode: c++
-// End: 
+#endif // __IO_SOCKET_H

+ 10 - 2
src/lib/asiolink/tcp_server.cc

@@ -20,9 +20,9 @@
 
 #include <log/dummylog.h>
 
+#include <asiolink/io_completion_cb.h>
 #include <asiolink/tcp_endpoint.h>
 #include <asiolink/tcp_socket.h>
-
 #include <asiolink/tcp_server.h>
 
 
@@ -115,7 +115,15 @@ TCPServer::operator()(error_code ec, size_t length) {
         // that would quickly generate an IOMessage object without
         // all these calls to "new".)
         peer_.reset(new TCPEndpoint(socket_->remote_endpoint()));
-        iosock_.reset(new TCPSocket(*socket_));
+
+        // The TCP socket class has been extended with asynchronous functions
+        // and takes as a template parameter a completion callback class.  As
+        // TCPServer does not use these extended functions (only those defined
+        // in the IOSocket base class) - but needs a TCPSocket to get hold of
+        // the underlying Boost TCP socket - use "IOCompletionCallback" -
+        // a basic callback class: it is not used but provides the appropriate
+        // signature.
+        iosock_.reset(new TCPSocket<IOCompletionCallback>(*socket_));
         io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
         bytes_ = length;
 

+ 199 - 46
src/lib/asiolink/tcp_socket.h

@@ -19,55 +19,78 @@
 #error "asio.hpp must be included before including this, see asiolink.h as to why"
 #endif
 
-#include <asiolink/io_socket.h>
+#include <log/dummylog.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>             // for some IPC/network system calls
+
+#include <iostream>
+#include <cstddef>
+
+#include <config.h>
+
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
 
 namespace asiolink {
 
 /// \brief The \c TCPSocket class is a concrete derived class of
 /// \c IOSocket that represents a TCP socket.
 ///
-/// In the current implementation, an object of this class is always
-/// instantiated within the wrapper routines.  Applications are expected to
-/// get access to the object via the abstract base class, \c IOSocket.
-/// This design may be changed when we generalize the wrapper interface.
-class TCPSocket : public IOSocket {
+/// Other notes about \c TCPSocket applies to this class, too.
+///
+/// \param C Callback type
+template <typename C>
+class TCPSocket : public IOAsioSocket<C> {
 private:
-    TCPSocket(const TCPSocket& source);
-    TCPSocket& operator=(const TCPSocket& source);
+    /// \brief Class is non-copyable
+    TCPSocket(const TCPSocket&);
+    TCPSocket& operator=(const TCPSocket&);
+
 public:
+    enum {
+        MAX_SIZE = 4096         // Send and receive size
+    };
+    
     /// \brief Constructor from an ASIO TCP socket.
     ///
-    /// \param socket The ASIO representation of the TCP socket.
-    TCPSocket(asio::ip::tcp::socket& socket) : socket_(socket) {}
+    /// \param socket The ASIO representation of the TCP socket.  It
+    /// is assumed that the caller will open and close the socket, so
+    /// these operations are a no-op for that socket.
+    TCPSocket(asio::ip::tcp::socket& socket);
+
+    /// \brief Constructor
+    ///
+    /// Used when the TCPSocket is being asked to manage its own internal
+    /// socket.  It is assumed that open() and close() will not be used.
+    ///
+    /// \param service I/O Service object used to manage the socket.
+    TCPSocket(IOService& service);
+
+    /// \brief Destructor
+    virtual ~TCPSocket();
 
-    int getNative() const { return (socket_.native()); }
-    int getProtocol() const { return (IPPROTO_TCP); }
+    virtual int getNative() const { return (socket_.native()); }
+    virtual int getProtocol() const { return (IPPROTO_TCP); }
 
     /// \brief Open Socket
     ///
-    /// A call that is a no-op on UDP sockets, this opens a connection to the
-    /// system identified by the given endpoint.
+    /// Opens the TCP socket.  In the model for transport-layer agnostic I/O,
+    /// an "open" operation includes a connection to the remote end (which
+    /// may take time).  This does not happen for TCP, so the method returns
+    /// "false" to indicate that the operation completed synchronously.
     ///
-    /// \param endpoint Pointer to the endpoint object.  This is ignored for
-    /// a UDP socket (the target is specified in the send call), but should
-    /// be of type TCPEndpoint for a TCP connection.
-    /// \param callback I/O Completion callback, called when the connect has
-    /// completed.  In the stackless coroutines model, this will be the
-    /// method containing the call to this function, allowing the operation to
-    /// resume after the socket open has completed.
+    /// \param endpoint Endpoint to which the socket will connect to.
+    /// \param callback Unused.
     ///
-    /// \return true if an asynchronous operation was started and the caller
-    /// should yield and wait for completion, false if not. (i.e. The UDP
-    /// derived class will return false, the TCP class will return true).  This
-    /// optimisation avoids the overhead required to post a callback to the
-    /// I/O Service queue where nothing is done.
-    virtual bool open(const IOEndpoint*, IOCompletionCallback&) {
-        return false;
-    }
+    /// \return false to indicate that the "operation" completed synchronously.
+    virtual bool open(const IOEndpoint* endpoint, C&);
 
     /// \brief Send Asynchronously
     ///
-    /// This corresponds to async_send_to() for UDP sockets and async_send()
+    /// This corresponds to async_send_to() for TCP sockets and async_send()
     /// for TCP.  In both cases an endpoint argument is supplied indicating the
     /// target of the send - this is ignored for TCP.
     ///
@@ -75,13 +98,12 @@ public:
     /// \param length Length of data to send
     /// \param endpoint Target of the send
     /// \param callback Callback object.
-    virtual void asyncSend(const void*, size_t,
-        const IOEndpoint*, IOCompletionCallback&) {
-    }
+    virtual void asyncSend(const void* data, size_t length,
+        const IOEndpoint* endpoint, C& callback);
 
     /// \brief Receive Asynchronously
     ///
-    /// This correstponds to async_receive_from() for UDP sockets and
+    /// This correstponds to async_receive_from() for TCP sockets and
     /// async_receive() for TCP.  In both cases, an endpoint argument is
     /// supplied to receive the source of the communication.  For TCP it will
     /// be filled in with details of the connection.
@@ -89,18 +111,19 @@ public:
     /// \param data Buffer to receive incoming message
     /// \param length Length of the data buffer
     /// \param cumulative Amount of data that should already be in the buffer.
+    /// (This is ignored - every UPD receive fills the buffer from the start.)
     /// \param endpoint Source of the communication
     /// \param callback Callback object
-    virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*,
-        IOCompletionCallback&) {
-    }
+    virtual void asyncReceive(void* data, size_t length, size_t cumulative,
+        IOEndpoint* endpoint, C& callback);
 
     /// \brief Checks if the data received is complete.
     ///
-    /// Checks that the total data received is the amount expected by the
-    /// two-byte header to the message.
+    /// As all the data is received in one I/O, so this is, this is effectively
+    /// a no-op (although it does update the amount of data received).
     ///
-    /// \param data Data buffer containing data to date
+    /// \param data Data buffer containing data to date.  (This is ignored
+    /// for TCP receives.)
     /// \param length Amount of data received in last asynchronous I/O
     /// \param cumulative On input, amount of data received before the last
     /// I/O.  On output, the total amount of data received to date.
@@ -113,17 +136,147 @@ public:
     }
 
     /// \brief Cancel I/O On Socket
-    virtual void cancel() {
-    }
+    virtual void cancel();
 
     /// \brief Close socket
-    virtual void close() {
-    }
+    virtual void close();
+
 
 private:
-    asio::ip::tcp::socket& socket_;
+    // Two variables to hold the socket - a socket and a pointer to it.  This
+    // handles the case where a socket is passed to the TCPSocket on
+    // construction, or where it is asked to manage its own socket.
+    asio::ip::tcp::socket*      socket_ptr_;    ///< Pointer to own socket
+    asio::ip::tcp::socket&      socket_;        ///< Socket
+    bool                        isopen_;        ///< true when socket is open
 };
 
+// Constructor - caller manages socket
+
+template <typename C>
+TCPSocket<C>::TCPSocket(asio::ip::tcp::socket& socket) :
+    socket_ptr_(NULL), socket_(socket), isopen_(true)
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+TCPSocket<C>::TCPSocket(IOService& service) :
+    socket_ptr_(new asio::ip::tcp::socket(service.get_io_service())),
+    socket_(*socket_ptr_), isopen_(false)
+{
+}
+
+// Destructor.  Only delete the socket if we are managing it.
+
+template <typename C>
+TCPSocket<C>::~TCPSocket()
+{
+    delete socket_ptr_;
+}
+
+// Open the socket.  Throws an error on failure
+// TODO: Make the open more resilient
+
+template <typename C> bool
+TCPSocket<C>::open(const IOEndpoint* endpoint, C&) {
+
+    // Ignore opens on already-open socket.  Don't throw a failure because
+    // of uncertainties as to what precedes whan when using asynchronous I/O.
+    // At also allows us a treat a passed-in socket as a self-managed socket.
+
+    if (!isopen_) {
+        if (endpoint->getFamily() == AF_INET) {
+            socket_.open(asio::ip::tcp::v4());
+        }
+        else {
+            socket_.open(asio::ip::tcp::v6());
+        }
+        isopen_ = true;
+
+        // TODO: Complete TCPSocket::open()
+
+    }
+    return (false);
+}
+
+// Send a message.  Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length,
+    const IOEndpoint* endpoint, C& callback)
+{
+    if (isopen_) {
+
+        // Upconvert to a TCPEndpoint.  We need to do this because although
+        // IOEndpoint is the base class of TCPEndpoint and TCPEndpoint, it
+        // doing cont contain a method for getting at the underlying endpoint
+        // type - those are in the derived class and the two classes differ on
+        // return type.
+
+        assert(endpoint->getProtocol() == IPPROTO_TCP);
+        const TCPEndpoint* tcp_endpoint =
+            static_cast<const TCPEndpoint*>(endpoint);
+        std::cerr << "TCPSocket::asyncSend(): sending to " <<
+            tcp_endpoint->getAddress().toText() <<
+            ", port " << tcp_endpoint->getPort() << "\n";
+
+        // TODO: Complete TCPSocket::asyncSend()
+
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to send on a TCP socket that is not open");
+    }
+}
+
+// Receive a message. Note that the "cumulative" argument is ignored - every TCP
+// receive is put into the buffer beginning at the start - there is no concept
+// receiving a subsequent part of a message.  Same critera as before concerning
+// the need for the socket to be open.
+
+template <typename C> void
+TCPSocket<C>::asyncReceive(void* data, size_t length, size_t,
+    IOEndpoint* endpoint, C& callback)
+{
+    if (isopen_) {
+
+        // Upconvert the endpoint again.
+        assert(endpoint->getProtocol() == IPPROTO_TCP);
+        const TCPEndpoint* tcp_endpoint =
+            static_cast<const TCPEndpoint*>(endpoint);
+        std::cerr << "TCPSocket::asyncReceive(): receiving from " <<
+            tcp_endpoint->getAddress().toText() <<
+            ", port " << tcp_endpoint->getPort() << "\n";
+
+        // TODO: Complete TCPSocket::asyncReceive()
+
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to receive from a TCP socket that is not open");
+    }
+}
+
+// Cancel I/O on the socket.  No-op if the socket is not open.
+template <typename C> void
+TCPSocket<C>::cancel() {
+    if (isopen_) {
+        socket_.cancel();
+    }
+}
+
+// Close the socket down.  Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+TCPSocket<C>::close() {
+    if (isopen_ && socket_ptr_) {
+        socket_.close();
+        isopen_ = false;
+    }
+}
+
+} // namespace asiolink
 
-}      // namespace asiolink
 #endif // __TCP_SOCKET_H

+ 9 - 4
src/lib/asiolink/tests/Makefile.am

@@ -15,10 +15,12 @@ CLEANFILES = *.gcno *.gcda
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
-run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
+run_unittests_SOURCES  = run_unittests.cc
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += io_address_unittest.cc
 run_unittests_SOURCES += io_endpoint_unittest.cc
+run_unittests_SOURCES += io_fetch_unittest.cc
 run_unittests_SOURCES += io_socket_unittest.cc
 run_unittests_SOURCES += io_service_unittest.cc
 run_unittests_SOURCES += interval_timer_unittest.cc
@@ -26,15 +28,18 @@ run_unittests_SOURCES += recursive_query_unittest.cc
 run_unittests_SOURCES += udp_endpoint_unittest.cc
 run_unittests_SOURCES += udp_query_unittest.cc
 run_unittests_SOURCES += udp_socket_unittest.cc
-run_unittests_SOURCES += run_unittests.cc
+
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
+
+run_unittests_LDADD  = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
+
 # Note: the ordering matters: -Wno-... must follow -Wextra (defined in
 # B10_CXXFLAGS)
 run_unittests_CXXFLAGS = $(AM_CXXFLAGS)

+ 189 - 0
src/lib/asiolink/tests/io_fetch_unittest.cc

@@ -0,0 +1,189 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+#include <cstdlib>
+#include <string>
+
+#include <string.h>
+
+#include <asio.hpp>
+
+#include <dns/buffer.h>
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_completion_cb.h>
+#include <asiolink/io_service.h>
+
+using namespace asio;
+using namespace isc::dns;
+using asio::ip::udp;
+
+namespace asiolink {
+
+const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
+const uint16_t TEST_PORT(5301);
+// FIXME Shouldn't we send something that is real message?
+const char TEST_DATA[] = "TEST DATA";
+
+/// \brief Test fixture for the asiolink::IOFetch.
+class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback
+{
+public:
+    IOService       service_;       ///< Service to run the query
+    IOFetch::Result expected_;      ///< Expected result of the callback
+    bool            run_;           ///< Did the callback run already?
+    Question        question_;      ///< What to ask
+    OutputBufferPtr buff_;          ///< Buffer to hold result
+    IOFetch         udp_fetch_;     ///< For UDP query test
+    //IOFetch         tcp_fetch_;     ///< For TCP query test
+
+    // The next member is the buffer iin which the "server" (implemented by the
+    // response handler method) receives the question sent by the fetch object.
+    char            server_buff_[512];  ///< Server buffer
+
+    /// \brief Constructor
+    IOFetchTest() :
+        service_(),
+        expected_(IOFetch::NOTSET),
+        run_(false),
+        question_(Name("example.net"), RRClass::IN(), RRType::A()),
+        buff_(new OutputBuffer(512)),
+        udp_fetch_(IPPROTO_UDP, service_, question_, IOAddress(TEST_HOST),
+            TEST_PORT, buff_, this, 100)
+        // tcp_fetch_(service_, question_, IOAddress(TEST_HOST), TEST_PORT,
+        //    buff_, this, 100, IPPROTO_UDP)
+        { }
+
+    /// \brief Fetch completion callback
+    ///
+    /// This is the callback's operator() method which is called when the fetch
+    /// is complete.  Check that the data received is the wire format of the
+    /// question, then send back an arbitrary response.
+    void operator()(IOFetch::Result result) {
+        EXPECT_EQ(expected_, result);   // Check correct result returned
+        EXPECT_FALSE(run_);             // Check it is run only once
+        run_ = true;                    // Note success
+        service_.stop();                // ... and exit run loop
+    }
+
+    /// \brief Response handler, pretending to be remote DNS server
+    ///
+    /// This checks that the data sent is what we expected to receive, and
+    /// sends back a test answer.
+    void respond(udp::endpoint* remote, udp::socket* socket,
+            asio::error_code ec = asio::error_code(), size_t length = 0) {
+
+        // Construct the data buffer for question we expect to receive.
+        OutputBuffer msgbuf(512);
+        Message msg(Message::RENDER);
+        msg.setQid(0);
+        msg.setOpcode(Opcode::QUERY());
+        msg.setRcode(Rcode::NOERROR());
+        msg.setHeaderFlag(Message::HEADERFLAG_RD);
+        msg.addQuestion(question_);
+        MessageRenderer renderer(msgbuf);
+        msg.toWire(renderer);
+
+        // The QID in the incoming data is random so set it to 0 for the
+        // data comparison check. (It was set to 0 when the buffer containing
+        // the expected data was constructed above.)
+        server_buff_[0] = server_buff_[1] = 0;
+
+        // Check that lengths are identical.
+        EXPECT_EQ(msgbuf.getLength(), length);
+        EXPECT_TRUE(memcmp(msgbuf.getData(), server_buff_, length) == 0);
+
+        // ... and return a message back.
+        socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA), *remote);
+    }
+};
+
+
+/// Test that when we run the query and stop it after it was run,
+/// it returns "stopped" correctly.
+///
+/// That is why stop() is posted to the service_ as well instead
+/// of calling it.
+TEST_F(IOFetchTest, UdpStop) {
+    expected_ = IOFetch::STOPPED;
+
+    // Post the query
+    service_.get_io_service().post(udp_fetch_);
+
+    // Post query_.stop() (yes, the boost::bind thing is just
+    // query_.stop()).
+    service_.get_io_service().post(
+       boost::bind(&IOFetch::stop, udp_fetch_, IOFetch::STOPPED));
+
+    // Run both of them.  run() returns when everything in the I/O service
+    // queue has completed.
+    service_.run();
+    EXPECT_TRUE(run_);
+}
+
+// Test that when we queue the query to service_ and call stop() before it gets
+// executed, it acts sanely as well (eg. has the same result as running stop()
+// after - calls the callback).
+TEST_F(IOFetchTest, UdpPrematureStop) {
+    expected_ = IOFetch::STOPPED;
+
+    // Stop before it is started
+    udp_fetch_.stop();
+    service_.get_io_service().post(udp_fetch_);
+
+    service_.run();
+    EXPECT_TRUE(run_);
+}
+
+// Test that it will timeout when no answer arrives.
+TEST_F(IOFetchTest, UdpTimeout) {
+    expected_ = IOFetch::TIME_OUT;
+
+    service_.get_io_service().post(udp_fetch_);
+    service_.run();
+    EXPECT_TRUE(run_);
+}
+
+// Test that it will succeed when we fake an answer and stores the same data we
+// send.  This is done through a real socket on the loopback address.
+TEST_F(IOFetchTest, UdpReceive) {
+    expected_ = IOFetch::SUCCESS;
+
+    udp::socket socket(service_.get_io_service(), udp::v4());
+    socket.set_option(socket_base::reuse_address(true));
+    socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
+
+    udp::endpoint remote;
+    socket.async_receive_from(asio::buffer(server_buff_, sizeof(server_buff_)),
+        remote,
+        boost::bind(&IOFetchTest::respond, this, &remote, &socket, _1, _2));
+    service_.get_io_service().post(udp_fetch_);
+    service_.run();
+
+    socket.close();
+
+    EXPECT_TRUE(run_);
+    ASSERT_EQ(sizeof TEST_DATA, buff_->getLength());
+    EXPECT_EQ(0, memcmp(TEST_DATA, buff_->getData(), sizeof TEST_DATA));
+}
+
+} // namespace asiolink

+ 2 - 0
src/lib/asiolink/tests/io_socket_unittest.cc

@@ -15,6 +15,8 @@
 #include <config.h>
 #include <gtest/gtest.h>
 
+#include <netinet/in.h>
+
 #include <asio.hpp>
 #include <asiolink/io_socket.h>
 

+ 17 - 15
src/lib/asiolink/tests/udp_socket_unittest.cc

@@ -52,14 +52,12 @@
 
 #include <asio.hpp>
 
-#include <asiolink/io_completion_cb.h>
 #include <asiolink/io_service.h>
 #include <asiolink/udp_endpoint.h>
 #include <asiolink/udp_socket.h>
 
 using namespace asio;
 using namespace asiolink;
-using asio::ip::udp;
 using namespace std;
 
 namespace {
@@ -77,7 +75,7 @@ const char INBOUND_DATA[] = "Returned data from server to client";
 /// and the operator() method is called when when an asynchronous I/O
 /// completes.  The arguments to the completion callback are stored for later
 /// retrieval.
-class UDPCallback : public IOCompletionCallback {
+class UDPCallback {
 public:
 
     struct PrivateData {
@@ -187,33 +185,38 @@ private:
 TEST(UDPSocket, SequenceTest) {
 
     // Common objects.
-    IOAddress   server_address(SERVER_ADDRESS);         // Address of target server
-    UDPEndpoint endpoint(server_address, SERVER_PORT);  // Endpoint of target server
     IOService   service;                    // Service object for async control
 
+    // Server
+    IOAddress   server_address(SERVER_ADDRESS); // Address of target server
+    UDPCallback server_cb("Server");        // Server callback
+    UDPEndpoint server_endpoint(            // Endpoint describing server
+        server_address, SERVER_PORT);
+    UDPEndpoint server_remote_endpoint;     // Address where server received message from
+
     // The client - the UDPSocket being tested
-    UDPSocket   client(service);            // Socket under test
+    UDPSocket<UDPCallback>  client(service);// Socket under test
     UDPCallback client_cb("Client");        // Async I/O callback function
+    UDPEndpoint client_remote_endpoint;     // Where client receives message from
     size_t      client_cumulative = 0;      // Cumulative data received
 
     // The server - with which the client communicates.  For convenience, we
     // use the same io_service, and use the endpoint object created for
     // the client to send to as the endpoint object in the constructor.
-    UDPCallback server_cb("Server");
-    udp::socket server(service.get_io_service(), endpoint.getASIOEndpoint());
+    asio::ip::udp::socket server(service.get_io_service(),
+        server_endpoint.getASIOEndpoint());
     server.set_option(socket_base::reuse_address(true));
 
     // Assertion to ensure that the server buffer is large enough
-    char data[UDPSocket::MAX_SIZE];
+    char data[UDPSocket<UDPCallback>::MAX_SIZE];
     ASSERT_GT(sizeof(data), sizeof(OUTBOUND_DATA));
 
     // Open the client socket - the operation should be synchronous
-    EXPECT_FALSE(client.open(&endpoint, client_cb));
+    EXPECT_FALSE(client.open(&server_endpoint, client_cb));
 
     // Issue read on the server.  Completion callback should not have run.
     server_cb.setCalled(false);
     server_cb.setCode(42); // Answer to Life, the Universe and Everything!
-    UDPEndpoint server_remote_endpoint;
     server.async_receive_from(buffer(data, sizeof(data)),
         server_remote_endpoint.getASIOEndpoint(), server_cb);
     EXPECT_FALSE(server_cb.getCalled());
@@ -222,7 +225,7 @@ TEST(UDPSocket, SequenceTest) {
     // be called until we call the io_service.run() method.
     client_cb.setCalled(false);
     client_cb.setCode(7);  // Arbitrary number
-    client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &endpoint, client_cb);
+    client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
     EXPECT_FALSE(client_cb.getCalled());
 
     // Execute the two callbacks.
@@ -244,7 +247,6 @@ TEST(UDPSocket, SequenceTest) {
     client_cb.setLength(12345);             // Arbitrary number
     client_cb.setCalled(false);
     client_cb.setCode(32);                  // Arbitrary number
-    UDPEndpoint client_remote_endpoint;     // To receive address of remote system
     client.asyncReceive(data, sizeof(data), client_cumulative,
         &client_remote_endpoint, client_cb);
 
@@ -256,8 +258,8 @@ TEST(UDPSocket, SequenceTest) {
         server_remote_endpoint.getASIOEndpoint(), server_cb);
 
     // Expect two callbacks to run
-    service.run_one();
-    service.run_one();
+    service.get_io_service().poll();
+    //service.run_one();
 
     EXPECT_TRUE(client_cb.getCalled());
     EXPECT_EQ(0, client_cb.getCode());

+ 13 - 3
src/lib/asiolink/udp_server.cc

@@ -20,10 +20,10 @@
 
 #include <log/dummylog.h>
 
+#include <asiolink/io_completion_cb.h>
 #include <asiolink/udp_endpoint.h>
-#include <asiolink/udp_socket.h>
-
 #include <asiolink/udp_server.h>
+#include <asiolink/udp_socket.h>
 
 using namespace asio;
 using asio::ip::udp;
@@ -203,7 +203,17 @@ UDPServer::operator()(error_code ec, size_t length) {
         // that would quickly generate an IOMessage object without
         // all these calls to "new".)
         data_->peer_.reset(new UDPEndpoint(*data_->sender_));
-        data_->iosock_.reset(new UDPSocket(*data_->socket_));
+
+        // The TCP socket class has been extended with asynchronous functions
+        // and takes as a template parameter a completion callback class.  As
+        // TCPServer does not use these extended functions (only those defined
+        // in the IOSocket base class) - but needs a TCPSocket to get hold of
+        // the underlying Boost TCP socket - use "IOCompletionCallback" -
+        // a basic callback class: it is not used but provides the appropriate
+        // signature.
+        data_->iosock_.reset(
+            new UDPSocket<IOCompletionCallback>(*data_->socket_));
+
         data_->io_message_.reset(new IOMessage(data_->data_.get(),
             data_->bytes_, *data_->iosock_, *data_->peer_));
 

+ 0 - 131
src/lib/asiolink/udp_socket.cc

@@ -1,131 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <config.h>
-#include <iostream>
-#include <unistd.h>             // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <boost/bind.hpp>
-
-#include <asio.hpp>
-#include <asio/deadline_timer.hpp>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-#include <log/dummylog.h>
-#include <dns/opcode.h>
-#include <dns/rcode.h>
-
-#include <coroutine.h>
-#include <asiolink/asiolink.h>
-
-using namespace asio;
-using asio::ip::udp;
-
-using namespace std;
-using namespace isc::dns;
-
-namespace asiolink {
-
-// Constructor - create socket on the fly
-
-UDPSocket::UDPSocket(IOService& service) :
-    socket_ptr_(new asio::ip::udp::socket(service.get_io_service())),
-    socket_(*socket_ptr_)
-{
-}
-
-// Destructor
-
-UDPSocket::~UDPSocket()
-{
-    delete socket_ptr_;
-}
-
-// Open the socket.  Throws an error on failure
-// TODO: Make the open more resolient
-
-bool
-UDPSocket::open(const IOEndpoint* endpoint, IOCompletionCallback&) {
-    if (endpoint->getFamily() == AF_INET) {
-        socket_.open(asio::ip::udp::v4());
-    }
-    else {
-        socket_.open(asio::ip::udp::v6());
-    }
-
-    // Ensure it can send and receive 4K buffers.
-    socket_.set_option(asio::socket_base::send_buffer_size(MAX_SIZE));
-    socket_.set_option(asio::socket_base::receive_buffer_size(MAX_SIZE));
-;
-    // Allow reuse of an existing port/address
-    socket_.set_option(asio::socket_base::reuse_address(true));
-
-    return (false);
-}
-
-// Send a message.
-
-void
-UDPSocket::asyncSend(const void* data, size_t length,
-    const IOEndpoint* endpoint, IOCompletionCallback& callback)
-{
-    // Upconverting.  Not nice, but we have the problem that in the abstract
-    // layer we are given an IOEndpoint.  For UDP code it is a UDPEndpoint
-    // and for TCP code a TCPEndpoint.  However the member that we are
-    // after - the asio endpoint - is different for UPD and TCP and there is
-    // no common ancestor.  Hence the promotion here.
-    assert(endpoint->getProtocol() == IPPROTO_UDP);
-    const UDPEndpoint* udp_endpoint = static_cast<const UDPEndpoint*>(endpoint);
-
-    socket_.async_send_to(buffer(data, length), udp_endpoint->getASIOEndpoint(),
-        callback);
-}
-
-// Receive a message. Note that the "cumulative" argument is ignored - every UDP
-// receive is put into the buffer beginning at the start - there is no concept
-// receiving a subsequent part of a message.
-
-void
-UDPSocket::asyncReceive(void* data, size_t length, size_t, IOEndpoint* endpoint,
-    IOCompletionCallback& callback)
-{
-    // Upconvert the endpoint again.
-    assert(endpoint->getProtocol() == IPPROTO_UDP);
-    UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
-
-    socket_.async_receive_from(buffer(data, length),
-        udp_endpoint->getASIOEndpoint(), callback);
-}
-
-// Cancel I/O on the socket
-void
-UDPSocket::cancel() {
-    socket_.cancel();
-}
-
-// Close the socket down
-
-void
-UDPSocket::close() {
-    socket_.close();
-}
-
-} // namespace asiolink

+ 151 - 12
src/lib/asiolink/udp_socket.h

@@ -19,9 +19,19 @@
 #error "asio.hpp must be included before including this, see asiolink.h as to why"
 #endif
 
+#include <log/dummylog.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>             // for some IPC/network system calls
+
 #include <cstddef>
-#include <asiolink/io_socket.h>
+
+#include <config.h>
+
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
 #include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
 
 namespace asiolink {
 
@@ -29,7 +39,10 @@ namespace asiolink {
 /// \c IOSocket that represents a UDP socket.
 ///
 /// Other notes about \c TCPSocket applies to this class, too.
-class UDPSocket : public IOSocket {
+///
+/// \param C Callback type
+template <typename C>
+class UDPSocket : public IOAsioSocket<C> {
 private:
     /// \brief Class is non-copyable
     UDPSocket(const UDPSocket&);
@@ -42,15 +55,17 @@ public:
     
     /// \brief Constructor from an ASIO UDP socket.
     ///
-    /// \param socket The ASIO representation of the UDP socket.
-    UDPSocket(asio::ip::udp::socket& socket) :
-        socket_ptr_(NULL), socket_(socket)
-    {}
+    /// \param socket The ASIO representation of the UDP socket.  It
+    /// is assumed that the caller will open and close the socket, so
+    /// these operations are a no-op for that socket.
+    UDPSocket(asio::ip::udp::socket& socket);
 
     /// \brief Constructor
     ///
     /// Used when the UDPSocket is being asked to manage its own internal
-    /// socket.
+    /// socket.  It is assumed that open() and close() will not be used.
+    ///
+    /// \param service I/O Service object used to manage the socket.
     UDPSocket(IOService& service);
 
     /// \brief Destructor
@@ -70,7 +85,7 @@ public:
     /// \param callback Unused.
     ///
     /// \return false to indicate that the "operation" completed synchronously.
-    virtual bool open(const IOEndpoint* endpoint, IOCompletionCallback&);
+    virtual bool open(const IOEndpoint* endpoint, C&);
 
     /// \brief Send Asynchronously
     ///
@@ -83,7 +98,7 @@ public:
     /// \param endpoint Target of the send
     /// \param callback Callback object.
     virtual void asyncSend(const void* data, size_t length,
-        const IOEndpoint* endpoint, IOCompletionCallback& callback);
+        const IOEndpoint* endpoint, C& callback);
 
     /// \brief Receive Asynchronously
     ///
@@ -99,7 +114,7 @@ public:
     /// \param endpoint Source of the communication
     /// \param callback Callback object
     virtual void asyncReceive(void* data, size_t length, size_t cumulative,
-        IOEndpoint* endpoint, IOCompletionCallback& callback);
+        IOEndpoint* endpoint, C& callback);
 
     /// \brief Checks if the data received is complete.
     ///
@@ -130,9 +145,133 @@ private:
     // Two variables to hold the socket - a socket and a pointer to it.  This
     // handles the case where a socket is passed to the UDPSocket on
     // construction, or where it is asked to manage its own socket.
-    asio::ip::udp::socket*      socket_ptr_;    ///< Pointer to the socket
+    asio::ip::udp::socket*      socket_ptr_;    ///< Pointer to own socket
     asio::ip::udp::socket&      socket_;        ///< Socket
+    bool                        isopen_;        ///< true when socket is open
 };
 
-}      // namespace asiolink
+// Constructor - caller manages socket
+
+template <typename C>
+UDPSocket<C>::UDPSocket(asio::ip::udp::socket& socket) :
+    socket_ptr_(NULL), socket_(socket), isopen_(true)
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+UDPSocket<C>::UDPSocket(IOService& service) :
+    socket_ptr_(new asio::ip::udp::socket(service.get_io_service())),
+    socket_(*socket_ptr_), isopen_(false)
+{
+}
+
+// Destructor.  Only delete the socket if we are managing it.
+
+template <typename C>
+UDPSocket<C>::~UDPSocket()
+{
+    delete socket_ptr_;
+}
+
+// Open the socket.  Throws an error on failure
+// TODO: Make the open more resilient
+
+template <typename C> bool
+UDPSocket<C>::open(const IOEndpoint* endpoint, C&) {
+
+    // Ignore opens on already-open socket.  Don't throw a failure because
+    // of uncertainties as to what precedes whan when using asynchronous I/O.
+    // At also allows us a treat a passed-in socket as a self-managed socket.
+
+    if (!isopen_) {
+        if (endpoint->getFamily() == AF_INET) {
+            socket_.open(asio::ip::udp::v4());
+        }
+        else {
+            socket_.open(asio::ip::udp::v6());
+        }
+        isopen_ = true;
+
+        // Ensure it can send and receive 4K buffers.
+        socket_.set_option(asio::socket_base::send_buffer_size(MAX_SIZE));
+        socket_.set_option(asio::socket_base::receive_buffer_size(MAX_SIZE));
+    ;
+        // Allow reuse of an existing port/address
+        socket_.set_option(asio::socket_base::reuse_address(true));
+    }
+    return (false);
+}
+
+// Send a message.  Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+UDPSocket<C>::asyncSend(const void* data, size_t length,
+    const IOEndpoint* endpoint, C& callback)
+{
+    if (isopen_) {
+
+        // Upconvert to a UDPEndpoint.  We need to do this because although
+        // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+        // doing cont contain a method for getting at the underlying endpoint
+        // type - those are in the derived class and the two classes differ on
+        // return type.
+
+        assert(endpoint->getProtocol() == IPPROTO_UDP);
+        const UDPEndpoint* udp_endpoint =
+            static_cast<const UDPEndpoint*>(endpoint);
+        socket_.async_send_to(buffer(data, length),
+            udp_endpoint->getASIOEndpoint(), callback);
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to send on a UDP socket that is not open");
+    }
+}
+
+// Receive a message. Note that the "cumulative" argument is ignored - every UDP
+// receive is put into the buffer beginning at the start - there is no concept
+// receiving a subsequent part of a message.  Same critera as before concerning
+// the need for the socket to be open.
+
+template <typename C> void
+UDPSocket<C>::asyncReceive(void* data, size_t length, size_t,
+    IOEndpoint* endpoint, C& callback)
+{
+    if (isopen_) {
+
+        // Upconvert the endpoint again.
+        assert(endpoint->getProtocol() == IPPROTO_UDP);
+        UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
+
+        socket_.async_receive_from(buffer(data, length),
+            udp_endpoint->getASIOEndpoint(), callback);
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to receive from a UDP socket that is not open");
+    }
+}
+
+// Cancel I/O on the socket.  No-op if the socket is not open.
+template <typename C> void
+UDPSocket<C>::cancel() {
+    if (isopen_) {
+        socket_.cancel();
+    }
+}
+
+// Close the socket down.  Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+UDPSocket<C>::close() {
+    if (isopen_ && socket_ptr_) {
+        socket_.close();
+        isopen_ = false;
+    }
+}
+
+} // namespace asiolink
+
 #endif // __UDP_SOCKET_H