Some context: I have some existing code to populate network header structs from istreams
. A motivating excerpt:
struct l2_eth_frame {
using mac_address_t = std::array<std::uint8_t, 6>;
mac_address_t dest, src;
std::uint16_t type;
std::uint32_t vlan;
};
template<typename StreamLike, typename T>
void read_ntoh(StreamLike &is, T &t){
static_assert(std::is_integral<T>::value, "fixed width integer required");
is.read(reinterpret_cast<char*>(&t), sizeof(T));
uint8_t *p = reinterpret_cast<uint8_t*>(&t);
std::reverse(p, p + sizeof(T));
}
template<class StreamLike>
l2_eth_frame read_l2_eth_frame(StreamLike &i)
{
l2_eth_frame e;
i.read(reinterpret_cast<char*>(&e.dest), sizeof(e.dest));
i.read(reinterpret_cast<char*>(&e.src), sizeof(e.src));
read_ntoh(i, e.type);
if(e.type == 0x8100){
i.read(reinterpret_cast<char*>(&e.vlan), sizeof(e.vlan));
}
return e;
}
int main() {
std::ifstream ifs("test.pcap");
auto eth = read_l2_eth_frame(ifs);
}
I'm now trying to reuse this code to also be able to generate network structs from live data (e.g. from a raw socket). To achieve this I wrote a simplistic array_view
class and also an adaptor class that gives it read
and ignore
methods (like an istream) so that it can utilise static polymorphism in the read_{header_type}
methods.
array_view
#pragma once
#include <cassert>
#include <memory>
#include <array>
template <class T>
class basic_array_view {
private:
const T* array;
std::size_t len;
public:
static constexpr std::size_t npos = -1;
basic_array_view() noexcept :
array(nullptr),
len(0)
{}
basic_array_view(const T* array, std::size_t len) noexcept :
array(array),
len(len)
{}
template<std::size_t N>
basic_array_view(const T (& a)[N]) noexcept :
array(std::addressof(a[0])),
len(N)
{}
template<std::size_t N>
basic_array_view(const std::array<T, N> &a) noexcept :
array(a.data()),
len(N)
{}
basic_array_view(const basic_array_view &other) = default;
basic_array_view& operator=(const basic_array_view &other) = default;
constexpr std::size_t size() const noexcept {
return len;
}
const T& operator[](std::size_t pos) const noexcept {
assert(pos < len);
return *(array + pos);
}
const T* data() const noexcept {
assert(len > 0);
return array;
}
basic_array_view<T> subview_right(std::size_t pos=0, std::size_t count=npos){
assert(pos <= len);
return { array + pos, std::min(count, len - pos) };
}
};
using array_view = basic_array_view<char>;
StreamLikeArrayView
#include "array_view.h"
class StreamLikeArrayView {
public:
array_view av;
template<class... Args>
StreamLikeArrayView(Args&&... args) :
av(std::forward<Args>(args)...) {}
void read(char *dest, std::size_t len){
std::copy(av.data(), av.data() + len, dest);
av = av.subview_right(len, array_view::npos);
}
void ignore(std::size_t len){
av = av.subview_right(len, array_view::npos);
}
};
usage
std::array<char, 4096> buf;
const auto bytes_read = ::recv(sd, buf.data(), buf.size(), 0); //assume we have set up some raw socket sd
StreamLikeArrayView slav{buf.data(), bytes_read};
const auto eth = read_l2_eth_frame(slav);
I'm particularly interested in a review of the StreamLikeArrayView
abstraction, and whether there's some superior way to reuse the parsing code across istreams
and arrays.