Browse Source

[trac499] Add "normalization" of received data

... i.e. copying TCP data into the receive buffer omitting the leading
two-byte message length.
Stephen Morris 14 years ago
parent
commit
77027490d5

+ 1 - 1
src/lib/asiolink/asiolink_utilities.h

@@ -41,7 +41,7 @@ readUint16(const void* buffer) {
 
 /// \brief Write Unisgned 16-Bit Integer to Buffer
 ///
-/// This is essentially a copy of isc::dns::OutputBuffer::writeUint16.  It 
+/// This is essentially a copy of isc::dns::OutputBuffer::writeUint16.  It
 /// should really be moved into a separate library.
 ///
 /// \param value 16-bit value to convert

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

@@ -26,6 +26,8 @@
 #include <exceptions/exceptions.h>
 #include <coroutine.h>
 
+#include <dns/buffer.h>
+
 #include <asiolink/io_error.h>
 #include <asiolink/io_socket.h>
 
@@ -224,6 +226,21 @@ public:
     ///         needed.
     virtual bool receiveComplete(const void* data, size_t length) = 0;
 
+    /// \brief Append Normalized Data
+    ///
+    /// When a UDP buffer is received, the entire buffer contains the data.
+    /// When a TCP buffer is received, the first two bytes of the buffer hold
+    /// a length count.  This method removes those bytes from the buffer.
+    ///
+    /// \param inbuf Input buffer.  This contains the data received over the
+    ///        network connection.
+    /// \param length Amount of data in the input buffer.  If TCP, this includes
+    ///        the two-byte count field.
+    /// \param outbuf Pointer to output buffer to which the data will be
+    ///        appended.
+    virtual void appendNormalizedData(const void* inbuf, size_t length,
+        isc::dns::OutputBufferPtr outbuf) = 0;
+
     /// \brief Cancel I/O On AsioSocket
     virtual void cancel() = 0;
 
@@ -321,6 +338,15 @@ public:
     virtual bool receiveComplete(void*, size_t, size_t&) {
         return (true);
     }
+    /// \brief Append Normalized Data
+    ///
+    /// \param inbuf Unused.
+    /// \param length Unused.
+    /// \param outbuf unused.
+    virtual void appendNormalizedData(const void*, size_t,
+                                      isc::dns::OutputBufferPtr)
+    {
+    }
 
     /// \brief Cancel I/O On AsioSocket
     ///

+ 8 - 7
src/lib/asiolink/io_fetch.cc

@@ -72,8 +72,8 @@ struct IOFetchData {
     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
-    boost::shared_array<char>   data;       ///< Temporary array for data
+    isc::dns::OutputBufferPtr   received;   ///< Received data put here
+    boost::shared_array<char>   staging;    ///< Temporary array for received data
     IOFetch::Callback*          callback;   ///< Called on I/O Completion
     size_t                      cumulative; ///< Cumulative received amount
     bool                        stopped;    ///< Have we stopped running?
@@ -122,8 +122,8 @@ struct IOFetchData {
             ),
         question(query),
         msgbuf(new isc::dns::OutputBuffer(512)),
-        buffer(buff),
-        data(new char[IOFetch::MIN_LENGTH]),
+        received(buff),
+        staging(new char[IOFetch::MIN_LENGTH]),
         callback(cb),
         cumulative(0),
         stopped(false),
@@ -211,11 +211,11 @@ IOFetch::operator()(asio::error_code ec, size_t length) {
         // we check if the operation is complete and if not, loop to read again.
         data_->origin = ASIO_RECVSOCK;
         do {
-            CORO_YIELD data_->socket->asyncReceive(data_->data.get(),
+            CORO_YIELD data_->socket->asyncReceive(data_->staging.get(),
                 static_cast<size_t>(MIN_LENGTH), data_->cumulative,
                 data_->remote.get(), *this);
             data_->cumulative += length;
-        } while (!data_->socket->receiveComplete(data_->data.get(),
+        } while (!data_->socket->receiveComplete(data_->staging.get(),
             data_->cumulative));
 
         /// Copy the answer into the response buffer.  (TODO: If the
@@ -223,7 +223,8 @@ IOFetch::operator()(asio::error_code ec, size_t length) {
         /// MutableBufferSequence, then it could be written to directly by
         /// async_receive_from() and this additional copy step would be
         /// unnecessary.)
-        data_->buffer->writeData(data_->data.get(), length);
+        data_->socket->appendNormalizedData(data_->staging.get(),
+            data_->cumulative, data_->received);
 
         // Finished with this socket, so close it.  This will not generate an
         // I/O error, but reset the origin to unknown in case we change this.

+ 26 - 1
src/lib/asiolink/tcp_socket.h

@@ -119,7 +119,7 @@ public:
     ///        that was determined when the connection was opened.)
     /// \param callback Callback object.
     virtual void asyncSend(const void* data, size_t length,
-        const IOEndpoint* endpoint, C& callback);
+                           const IOEndpoint* endpoint, C& callback);
 
     /// \brief Receive Asynchronously
     ///
@@ -147,6 +147,21 @@ public:
     /// \return true if the receive is complete, false if not.
     virtual bool receiveComplete(const void* data, size_t length);
 
+    /// \brief Append Normalized Data
+    ///
+    /// When a UDP buffer is received, the entire buffer contains the data.
+    /// When a TCP buffer is received, the first two bytes of the buffer hold
+    /// a length count.  This method removes those bytes from the buffer.
+    ///
+    /// \param inbuf Input buffer.  This contains the data received over the
+    ///        network connection.
+    /// \param length Amount of data in the input buffer.  If TCP, this includes
+    ///        the two-byte count field.
+    /// \param outbuf Pointer to output buffer to which the data will be
+    ///        appended
+    virtual void appendNormalizedData(const void* inbuf, size_t length,
+                                      isc::dns::OutputBufferPtr outbuf);
+
     /// \brief Cancel I/O On Socket
     virtual void cancel();
 
@@ -338,6 +353,16 @@ TCPSocket<C>::receiveComplete(const void* data, size_t length) {
     return (complete);
 }
 
+// Copy buffer less leading two bytes to the target buffer.
+
+template <typename C> void
+TCPSocket<C>::appendNormalizedData(const void* inbuf, size_t length,
+                                   isc::dns::OutputBufferPtr outbuf)
+{
+    const uint8_t* bytebuff = static_cast<const uint8_t*>(inbuf);
+    outbuf->writeData(bytebuff + 2, length - 2);
+}
+
 // Cancel I/O on the socket.  No-op if the socket is not open.
 
 template <typename C> void

+ 6 - 25
src/lib/asiolink/tests/io_fetch_unittest.cc

@@ -47,7 +47,7 @@ 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";
+const char TEST_DATA[] = "Test response from server to client";
 
 /// \brief Test fixture for the asiolink::IOFetch.
 class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback
@@ -262,30 +262,11 @@ public:
         // when one of the "servers" in this class has sent back the TEST_DATA.
         // Check the data is as expected/
         if (expected_ == IOFetch::SUCCESS) {
-            size_t offset = 0;      // Offset into start of buffer of data
-            if (protocol_ == IOFetch::UDP) {
-
-                // Check the length of data received against the amount expected.
-                EXPECT_EQ(sizeof(TEST_DATA), result_buff_->getLength());
-
-            } else {
-
-                // Check the length of data received against the amount expected
-                EXPECT_EQ(sizeof(TEST_DATA) + 2, result_buff_->getLength());
-
-                // Check the count field.  This should be equal to the total
-                // length of the packet less 2 (the count field is equal to
-                // the total length of the message less the count field itself -
-                // RFC 1035, section 4.2.2).
-                uint16_t count = readUint16(result_buff_->getData());
-                EXPECT_EQ(result_buff_->getLength(), count + 2);
-
-                // Update offset and count for the content check.
-                offset  += 2;
-            }
-            const void* start = static_cast<const void*>(
-                static_cast<const uint8_t*>(result_buff_->getData()) + offset);
-            EXPECT_TRUE(memcmp(TEST_DATA, start, sizeof(TEST_DATA)) == 0);
+            EXPECT_EQ(sizeof(TEST_DATA), result_buff_->getLength());
+
+            const uint8_t* start = static_cast<const uint8_t*>(result_buff_->getData());
+            EXPECT_TRUE(equal(TEST_DATA, (TEST_DATA + sizeof(TEST_DATA) - 1),
+                              start));
         }
 
         // ... and cause the run loop to exit.

+ 52 - 0
src/lib/asiolink/tests/tcp_socket_unittest.cc

@@ -274,6 +274,58 @@ clientReadComplete(TCPSocket<TCPCallback>& client, TCPCallback& client_cb,
     return (complete);
 }
 
+
+// Receive complete method should return true only if the count in the first
+// two bytes is equal to the size of the rest if the buffer.
+
+TEST(TCPSocket, receiveComplete) {
+    IOService               service;        // Used to instantiate socket
+    TCPSocket<TCPCallback>  test(service);  // Socket under test
+    uint8_t                 buffer[32];     // Buffer to check
+
+    // Expect that the value is true whatever number is written in the first
+    // two bytes of the buffer.
+    uint16_t count = 0;
+    for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
+        writeUint16(count, buffer);
+        if (count == (sizeof(buffer) - 2)) {
+            EXPECT_TRUE(test.receiveComplete(buffer, sizeof(buffer)));
+        } else {
+            EXPECT_FALSE(test.receiveComplete(buffer, sizeof(buffer)));
+        }
+    }
+}
+
+// Check that the normalized data copy only copies all but the first two bytes
+// of the buffer (whatever the count).
+
+TEST(TCPSocket, appendNormalizedData) {
+    IOService               service;        // Used to instantiate socket
+    TCPSocket<TCPCallback>  test(service);  // Socket under test
+    uint8_t                 inbuff[32];     // Buffer to check
+    isc::dns::OutputBufferPtr outbuff(new isc::dns::OutputBuffer(sizeof(inbuff)));
+                                            // Where data is written
+
+    // Initialize the input buffer with data.
+    for (uint8_t i = 0; i < sizeof(inbuff); ++i) {
+        inbuff[i] = i + 1;      // An arbitrary number
+    }
+
+    // Loop to ensure that entire buffer is copied on all count values, no
+    // matter what.
+    uint16_t count = 0;
+    for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
+        writeUint16(count, inbuff);
+        outbuff->clear();
+        test.appendNormalizedData(inbuff, sizeof(inbuff), outbuff);
+
+        EXPECT_EQ((sizeof(inbuff) - 2), outbuff->getLength());
+
+        const uint8_t* outptr = static_cast<const uint8_t*>(outbuff->getData());
+        EXPECT_TRUE(equal(&inbuff[2], &inbuff[sizeof(inbuff) - 1], outptr));
+    }
+}
+
 // TODO: Need to add a test to check the cancel() method
 
 // Tests the operation of a TCPSocket by opening it, sending an asynchronous

+ 50 - 0
src/lib/asiolink/tests/udp_socket_unittest.cc

@@ -35,8 +35,11 @@
 #include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 
+#include <dns/buffer.h>
+
 #include <asio.hpp>
 
+#include <asiolink/asiolink_utilities.h>
 #include <asiolink/io_service.h>
 #include <asiolink/udp_endpoint.h>
 #include <asiolink/udp_socket.h>
@@ -162,6 +165,53 @@ private:
     boost::shared_ptr<PrivateData>  ptr_;   ///< Pointer to private data
 };
 
+// Receive complete method should return true regardless of what is in the first
+// two bytes of a buffer.
+
+TEST(UDPSocket, receiveComplete) {
+    IOService               service;        // Used to instantiate socket
+    UDPSocket<UDPCallback>  test(service);  // Socket under test
+    uint8_t                 buffer[32];     // Buffer to check
+
+    // Expect that the value is true whatever number is written in the first
+    // two bytes of the buffer.
+    uint16_t count = 0;
+    for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
+        writeUint16(count, buffer);
+        EXPECT_TRUE(test.receiveComplete(buffer, sizeof(buffer)));
+    }
+}
+
+// Check that the normalized data copy copies the entire buffer regardless of
+// the first two bytes.
+
+TEST(UDPSocket, appendNormalizedData) {
+    IOService               service;        // Used to instantiate socket
+    UDPSocket<UDPCallback>  test(service);  // Socket under test
+    uint8_t                 inbuff[32];     // Buffer to check
+    isc::dns::OutputBufferPtr outbuff(new isc::dns::OutputBuffer(sizeof(inbuff)));
+                                            // Where data is written
+
+    // Initialize the input buffer with data.
+    for (uint8_t i = 0; i < sizeof(inbuff); ++i) {
+        inbuff[i] = i + 1;      // An arbitrary number
+    }
+
+    // Loop to ensure that entire buffer is copied on all count values, no
+    // matter what.
+    uint16_t count = 0;
+    for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
+        writeUint16(count, inbuff);
+        outbuff->clear();
+        test.appendNormalizedData(inbuff, sizeof(inbuff), outbuff);
+
+        EXPECT_EQ(sizeof(inbuff), outbuff->getLength());
+
+        const uint8_t* outptr = static_cast<const uint8_t*>(outbuff->getData());
+        EXPECT_TRUE(equal(&inbuff[0], &inbuff[sizeof(inbuff) - 1], outptr));
+    }
+}
+
 // TODO: Need to add a test to check the cancel() method
 
 // Tests the operation of a UDPSocket by opening it, sending an asynchronous

+ 20 - 1
src/lib/asiolink/udp_socket.h

@@ -107,7 +107,7 @@ public:
     /// \param endpoint Target of the send
     /// \param callback Callback object.
     virtual void asyncSend(const void* data, size_t length,
-        const IOEndpoint* endpoint, C& callback);
+                           const IOEndpoint* endpoint, C& callback);
 
     /// \brief Receive Asynchronously
     ///
@@ -137,6 +137,24 @@ public:
         return (true);
     }
 
+    /// \brief Append Normalized Data
+    ///
+    /// When a UDP buffer is received, the entire buffer contains the data.
+    /// When a TCP buffer is received, the first two bytes of the buffer hold
+    /// a length count.  This method removes those bytes from the buffer.
+    ///
+    /// \param inbuf Input buffer.  This contains the data received over the
+    ///        network connection.
+    /// \param length Amount of data in the input buffer.  If TCP, this includes
+    ///        the two-byte count field.
+    /// \param outbuf Pointer to output buffer to which the data will be
+    ///        appended
+    virtual void appendNormalizedData(const void* inbuf, size_t length,
+                                      isc::dns::OutputBufferPtr outbuf)
+    {
+        outbuf->writeData(inbuf, length);
+    }
+
     /// \brief Cancel I/O On Socket
     virtual void cancel();
 
@@ -271,6 +289,7 @@ UDPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
 }
 
 // Cancel I/O on the socket.  No-op if the socket is not open.
+
 template <typename C> void
 UDPSocket<C>::cancel() {
     if (isopen_) {