anthay/bytefluo
Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
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