terom@200: #ifndef NETWORK_BUFFER_HH terom@200: #define NETWORK_BUFFER_HH terom@200: terom@200: #include "Socket.hh" terom@200: #include "../Error.hh" terom@200: terom@200: #include terom@200: terom@284: /** terom@200: * Minimum chunk size to avoid handling single bytes at a time (for resize, mainly) terom@200: */ terom@200: const size_t NETWORK_BUFFER_CHUNK_SIZE = 1024; terom@200: terom@284: /** terom@284: * Base class of errors thrown by NetworkBuffer* methods terom@284: */ terom@200: class NetworkBufferError : public Error { terom@200: public: terom@200: NetworkBufferError (const std::string &message) : Error(message) { } terom@200: }; terom@200: terom@284: /** terom@284: * Base buffer-manipulation operations for buffered socket send/recv terom@200: */ terom@200: class NetworkBufferBase { terom@200: protected: terom@284: /** The socket that we use */ terom@200: NetworkSocket socket; terom@284: terom@284: /** The buffer itself */ terom@284: char *buf; terom@200: terom@284: /** Buffer size and current read/write offset */ terom@200: size_t size, offset; terom@200: terom@200: public: terom@284: /** terom@284: * Allocate buf using the given initial size, and set offset to zero terom@284: */ terom@200: NetworkBufferBase (NetworkSocket &socket, size_t size_hint); terom@284: terom@284: /** terom@284: * Free()'s the buf terom@284: */ terom@200: ~NetworkBufferBase (void); terom@200: terom@200: private: terom@284: /** terom@284: * No copying, these are undefined terom@284: */ terom@200: NetworkBufferBase (const NetworkBufferBase ©); terom@200: NetworkBufferBase& operator= (const NetworkBufferBase ©); terom@200: terom@200: protected: terom@284: /** terom@284: * Resize the buffer, allocating enough new space to hold bytes at the end, and leaving any terom@284: * existing data at the beginning in-place terom@284: * terom@284: * @param item_size the number of bytes that must fit at the end of the buffer terom@284: */ terom@200: void resize (size_t item_size); terom@284: terom@284: /** terom@284: * Trim the buffer, discarding bytes at the beginning. Updates offset to match. terom@284: * terom@284: * @param prefix_size the number of bytes to discard terom@284: */ terom@200: void trim (size_t prefix_size); terom@200: }; terom@200: terom@284: /** terom@200: * Buffered prefix-len socket input terom@200: */ terom@200: class NetworkBufferInput : public NetworkBufferBase { terom@200: public: terom@284: /** terom@284: * @see NetworkBufferBase terom@284: */ terom@200: NetworkBufferInput (NetworkSocket &socket, size_t size_hint); terom@200: terom@200: private: terom@284: /** terom@284: * Attempts to recv the given number of bytes into our buf, returning true on success terom@284: * terom@284: * @param item_size minimum number of bytes of data that we need in the buffer terom@284: * @return bool true if the buffer now contains at least item_size bytes terom@200: */ terom@200: bool try_read (size_t item_size); terom@200: terom@284: /** terom@284: * Tests if the buffer contains at least the given amount of data, but doesn't recv or anything terom@284: * terom@284: * @param data_size number of bytes that we are expecting terom@284: * @return bool true if the buffer contains at least data_size bytes terom@200: */ terom@200: bool have_data (size_t data_size); terom@200: terom@200: public: terom@284: // @{ terom@284: /** terom@284: * terom@284: * Attempts to read the length prefix into val_ref, returning true on success, false if there's not enough data terom@284: * in the buffer terom@284: * terom@284: * @param val_ref stores the value read here if we have it terom@284: * @return bool was val_ref set terom@200: */ terom@200: bool peek_prefix (uint16_t &val_ref); terom@200: bool peek_prefix (uint32_t &val_ref); terom@284: // @} terom@200: terom@284: /** terom@284: * This attempts to read a length-prefix of the given type (using peek_prefix), and then the associated data. terom@284: * If succesful, this sets prefix to the length of the data, and buf_ref to point at the data inside our buffer terom@284: * and returns true, else false. terom@284: * terom@284: * This will try and consume data from the buffer, or recv if needed. terom@284: * terom@284: * @param prefix stores the data length here terom@284: * @param buf_ref stores a pointer to the data here terom@284: * @return bool true if we have the full data, false if we need to wait for more data on the socket terom@284: * terom@284: * @see peek_prefix terom@284: * @see flush_data terom@200: */ terom@200: template bool peek_data (PrefixType &prefix, char *&buf_ref) { terom@200: size_t missing = 0; terom@200: terom@200: do { terom@200: // do we have the prefix? terom@200: if (peek_prefix(prefix)) { terom@200: // do we already have the payload? terom@200: if (offset >= sizeof(PrefixType) + prefix) { terom@200: break; terom@200: terom@200: } else { terom@200: missing = (sizeof(PrefixType) + prefix) - offset; terom@200: } terom@200: terom@200: } else { terom@200: missing = sizeof(PrefixType); terom@200: } terom@200: terom@200: // sanity-check terom@200: // XXX: a zero-prefix will trigger this terom@200: assert(missing); terom@200: terom@200: // try and read the missing data terom@200: if (try_read(missing) == false) { terom@200: // if unable to read what we need, return zero. terom@200: return false; terom@200: } terom@200: terom@200: // assess the situation again terom@200: } while (true); terom@200: terom@200: // update the buf_ref to point past the prefix-length terom@200: buf_ref = buf + sizeof(PrefixType); terom@200: terom@200: // return terom@200: return true; terom@200: } terom@284: terom@284: /** terom@284: * This flushes a prefix-length worth of data from the buffer, i.e. it first reads the prefix, and then trims terom@284: * the prefix and the data away. Don't call this unless you *know* that the buffer contains enough data. terom@284: * terom@284: * @see peek_data terom@284: */ terom@200: template void flush_data (void) { terom@200: PrefixType prefix; terom@200: terom@200: // we *must* have a valid prefix terom@365: if (!peek_prefix(prefix)) terom@365: assert(false); terom@200: terom@200: // trim the bytes out terom@200: trim(sizeof(PrefixType) + prefix); terom@200: } terom@200: }; terom@200: terom@284: /** terom@200: * Buffered prefix-len socket output terom@200: */ terom@200: class NetworkBufferOutput : public NetworkBufferBase { terom@200: public: terom@284: /** terom@284: * @see NetworkBufferBase terom@284: */ terom@200: NetworkBufferOutput (NetworkSocket &socket, size_t size_hint); terom@200: terom@200: private: terom@284: /** terom@284: * Write the given data to the socket, either now of later. terom@284: * terom@284: * If our buffer is empty, fast-path the given buf_ptr directly to send(), then copy the remaining portion to terom@284: * our buffer for later use with flush_write. terom@284: * terom@284: * @param buf_ptr the data that we need to send terom@284: * @param buf_size number of bytes in buf_ptr terom@200: */ terom@200: void push_write (char *buf_ptr, size_t buf_size); terom@200: terom@200: public: terom@284: /** terom@284: * If we have data in our buffer, flush it out using send(). terom@200: */ terom@200: void flush_write (void); terom@200: terom@284: // @{ terom@284: /** terom@284: * Write out the given data, writing first the prefix, and then the data itself, using push_write. terom@284: * terom@284: * @param buf the data to write terom@284: * @param prefix the amount of data terom@200: */ terom@200: void write_prefix (char *buf, uint16_t prefix); terom@200: void write_prefix (char *buf, uint32_t prefix); terom@284: // @} terom@200: }; terom@200: terom@200: #endif