bytefluo 0 PURPOSE bytefluo is a C++ class for reading simple integer scalar values with specified byte order from a buffer, regardless of the native byte order of the computer on which the code is running. It will throw an exception if any attempt is made to access data beyond the specified bounds of the buffer. The class was created to simplify the low-level parsing of binary data structures and to avoid having to write code such as uint16_t foo = static_cast<uint16_t>(*bar++) << 8; foo |= static_cast<uint16_t>(*bar++); or uint16_t foo = ntohs(*(uint16_t *)bar); bar += 2; and instead just write uint16_t foo; bar >> foo; (where this bar is a bytefluo object rather than the raw pointer of the previous examples). So, given some raw data, and a bytefluo object through which we will access the data, such as unsigned char bytes[7] = { 0x99,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF }; bytefluo buf(bytes, bytes + sizeof(bytes), bytefluo::big); we can then do things like this uint16_t a; uint8_t b; uint32_t c; buf >> a >> b >> c; assert(a == 0x99AA); assert(b == 0xBB); assert(c == 0xCCDDEEFF); 1 BUILDING bytefluo is a C++ header-only library file; there are no other source files to compile and no libs to link with. Just include bytefluo.h in your code and start using it. 2 TESTING The bytefluo library is distributed with a self-contained unit test in the file bytefluo_test.cpp. Compile this file and run it. OS X $ clang++ -std=c++11 -stdlib=libc++ -O3 -Wall -I. bytefluo_test.cpp $ ./a.out tests executed N, tests failed 0 Alternatively, use the makefile in build/clang $ cd build/clang/ $ make test clang++ -std=c++11 -stdlib=libc++ -O3 -Wall -I../.. ../../bytefluo_test.cpp -o bytefluo_test ./bytefluo_test tests executed N, tests failed 0 Windows C:\test> cl /EHsc /W4 /O2 /nologo bytefluo_test.cpp C:\test> bytefluo_test tests executed N, tests failed 0 Alternatively, double-click the project file in build/msvc2013. (Where N is the number of tests in the current version of test suite.) The bytefluo library has been tested by the author under the following compilers - clang++ 3.9.0 on OS X - Microsoft Visual C++ 2013 community edition on Windows 3 USING The unit test source, bytefluo_test.cpp, contains many examples of use. Here is another: #include "bytefluo.h" int main() { const unsigned char bytes[8] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }; bytefluo buf(bytes, bytes + sizeof(bytes), bytefluo::big); uint32_t x; buf >> x; // x = 0x00112233 buf.set_byte_order(bytefluo::little); buf >> x; // x = 0x77665544 } 3.1 OVERVIEW The important things to know about bytefluo are - When you create a bytefluo object you tell it what data it will provide access to, and the initial byte order for any scalar reads that will be performed. - The bytefluo object does not hold a copy of the given data; it merely manages access to that data. - The bytefluo object maintains a cursor, which is set initially at the start of the managed data. All reads begin at the current cursor location and advance the cursor by the size of the data read. - If a requested read or seek would take the cursor outside the bounds of the managed data the read or seek does not take place, the state of the bytefluo object remains unchanged and the bytefluo object throws a bytefluo_exception object, which is derived from std::runtime_error. - Assuming that the bytefluo object is managing access to valid memory, we provide either the strong guarantee or the nothrow guarantee for all operations. - The bytefluo implementation is entriely within the header file, bytefluo.h. To use the class just #include "bytefluo.h". 3.2 DETAIL 3.2.1 CONSTRUCTION bytefluo(const void * begin, const void * end, byte_order bo) The bytefluo object will manage access to the bytes in the half open range [begin, end). Multi-byte scalars will be read assuming the given byte order 'bo'. 'bo' must be one of bytefluo::big most-significant byte has lowest address bytefluo::little least-significant byte has lowest address Throws bytefluo_exception if begin > end or if 'bo' is neither big nor little. Example: uint8_t foo[99]; bytefluo buf(foo, foo + sizeof(foo), bytefluo::big); 3.2.2 DEFAULT CONSTRUCTION bytefluo() The bytefluo object will manage access to the empty half open range [0, 0), with scalar reads defaulting to big-endian. Throws nothing. Example: bytefluo buf; size_t siz = buf.size(); // siz = 0 bool at_end = buf.eos(); // at_end = true 3.2.3 CONSTRUCTION FROM VECTOR template <class item_type> bytefluo bytefluo_from_vector( const std::vector<item_type> & vec, bytefluo::byte_order bo) This free function is provided as a convenient shorthand for bytefluo(&vec[0], &vec[0] + vec.size(), bo) for a non-empty vector and bytefluo(0, 0, bo) for an empty vector. NOTE that any operations on the vector that might change the value of &vec[0] or vec.size() (e.g. adding elements to the vector may cause the vector to reallocate its buffer) will silently invalidate the associated bytefluo object so that attempts to read the vector contents via that bytefluo object may cause a CRASH. Example: std::vector<uint8_t> vec(99); bytefluo buf(bytefluo_from_vector(vec, bytefluo::big)); 3.2.4 SET DATA RANGE bytefluo & set_data_range(const void * begin, const void * end) The bytefluo object will manage access to the bytes in the half open range [begin, end). Throws bytefluo_exception if begin > end. The cursor is set to the beginning of the range. The current byte-order setting is unaffected. Returns *this. Example: bytefluo buf; size_t siz = buf.size(); // siz = 0 uint8_t foo[99]; buf.set_data_range(foo, foo + sizeof(foo)); siz = buf.size(); // siz = 99 3.2.5 SET BYTE ORDER bytefluo & set_byte_order(byte_order bo) Specify the byte arrangement to be used on subsequent scalar reads. 'bo' must be one of bytefluo::big most-significant byte has lowest address bytefluo::little least-significant byte has lowest address Throws bytefluo_exception if 'bo' is neither big nor little. The current data range and cursor position are unaffected. Returns *this. Example: bytefluo buf(...); buf.set_byte_order(bytefluo::big); 3.2.6 READ INTEGER SCALAR VIA operator>>() template <typename scalar_type> bytefluo & operator>>(scalar_type & out) Read an integer scalar value from buffer at current cursor position. The scalar is read assuming the byte order set at construction or at the last call to set_byte_order(). [Cf. read_le() and read_be().] The cursor is advanced by the size of the scalar read. Returns *this. Throws bytefluo_exception if the read would move the cursor after the end of the managed data range. Example: bytefluo buf(...); uint16_t foo, bar; buf >> foo >> bar; // read two successive shorts, foo followed by bar 3.2.7 READ INTEGER SCALAR VIA read_be() AND read_le() FUNCTIONS template <typename scalar_type> bytefluo & read_be(scalar_type & out) template <typename scalar_type> bytefluo & read_le(scalar_type & out) Read an integer scalar value from buffer at current cursor position. The scalar is read assuming big-endian byte order for read_be() and little-endian byte order for read_le(), *regardless* of the byte order set at construction or at the last call to set_byte_order(). [Cf. operator>>().] The cursor is advanced by the size of the scalar read. Returns *this. Throws bytefluo_exception if the read would move the cursor after the end of the managed data range. Example: bytefluo buf(...); uint16_t foo, bar; // read big-endian foo followed by little-endian bar buf.read_be(foo).read_le(bar); 3.2.8 READ ARBITRARY NUMBER OF BYTES bytefluo & read(void * dest, size_t len) Copy 'len' bytes from buffer at current cursor position to given 'dest' location. The cursor is advanced by the number of bytes copied. The current byte order setting has no affect on this operation. Returns *this. Throws bytefluo_exception if the read would move the cursor after the end of the managed data range. Example: bytefluo buf(...); uint8_t foo[23]; buf.read(foo, sizeof(foo)); 3.2.9 MOVE THE CURSOR size_t seek_begin(size_t pos) size_t seek_current(long pos) size_t seek_end(size_t pos) These functions move the cursor 'pos' bytes from stream beginning, the current cursor location and the stream end respectively. They all return the distance from buffer start to new cursor location. seek_begin() and seek_end() require pos to be positive; seek_current() may have a positive (move cursor toward end) or negative (move cursor toward begin) actual parameter. Throws bytefluo_exception if the move would put the cursor before the beginning or after the end of the managed data range. Example: bytefluo buf(...); size_t pos = buf.seek_end(3); // cursor is 3 bytes from buffer end 3.2.10 TEST FOR END OF STREAM bool eos() const Returns true if and only if the cursor is at the end of the stream. Throws nothing. Example: bytefluo buf(...); buf.seek_end(0); bool at_end = buf.eos(); // at_end = true 3.2.11 GET STREAM SIZE size_t size() const Returns the number of bytes in the stream. Throws nothing. Example: std::vector<unsigned char> v(99); bytefluo buf(bytefluo_from_vector(v, bytefluo::big)); size_t siz = buf.size(); // siz = 99 3.2.12 GET CURSOR POSITION size_t tellg() const Returns the distance from the buffer start to the current cursor location. Throws nothing. Example: bytefluo buf(...); buf.seek_begin(13); buf.seek_current(-3); size_t pos = buf.tellg(); // pos = 10 3.2.13 THE EXCEPTIONS If something goes wrong the bytefluo object will throw a bytefluo_exception object. You can catch this via the exception base class with catch (const std::runtime_error & e) { std::clog << e.what() << '\n'; } or directly with catch (const bytefluo_exception & e) { std::clog << e.what() << '\n'; } If you do the latter you can get access to the bytefluo exception id, for example: catch (const bytefluo_exception & e) { std::clog << e.what() << '\n'; if (e.id() == bytefluo_exception::attempt_to_read_past_end) . . . } The exception ids and their symbolic names are 1 null_begin_non_null_end 2 null_end_non_null_begin 3 end_precedes_begin 4 invalid_byte_order 5 attempt_to_read_past_end 6 attempt_to_seek_after_end 7 attempt_to_seek_before_beginning 4 LICENSE Distributed under the MIT License: MIT License Copyright (c) 2008,2009,2010,2016,2017 Anthony C. Hay Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5 AUTHOR Anthony C. Hay March 2017 anthony.hay.1@gmail.com