Parse Clutter Filter Map (Message 15)

This commit is contained in:
Dan Paulat 2021-06-15 21:59:38 -05:00
parent f36d57b71d
commit 3ab4ec4b9b
15 changed files with 682 additions and 22 deletions

View file

@ -0,0 +1,37 @@
#include <scwx/util/vectorbuf.hpp>
#include <gtest/gtest.h>
namespace scwx
{
namespace util
{
TEST(vectorbuf, smiles)
{
std::vector<char> v;
vectorbuf vb(v);
std::istream is(&vb);
v.reserve(7);
memcpy(v.data(), "smiles", 7);
vb.update_read_pointers(7);
EXPECT_EQ(is.eof(), false);
EXPECT_EQ(is.fail(), false);
char data[7];
is.read(data, 7);
EXPECT_EQ(std::string(data), std::string("smiles"));
EXPECT_EQ(is.eof(), false);
EXPECT_EQ(is.fail(), false);
is.read(data, 1);
EXPECT_EQ(is.eof(), true);
EXPECT_EQ(is.fail(), true);
}
} // namespace util
} // namespace scwx

View file

@ -8,7 +8,8 @@ find_package(BZip2)
find_package(GTest) find_package(GTest)
set(SRC_MAIN source/scwx/wxtest.cpp) set(SRC_MAIN source/scwx/wxtest.cpp)
set(SRC_UTIL_TESTS source/scwx/util/rangebuf.test.cpp) set(SRC_UTIL_TESTS source/scwx/util/rangebuf.test.cpp
source/scwx/util/vectorbuf.test.cpp)
set(SRC_WSR88D_TESTS source/scwx/wsr88d/ar2v_file.test.cpp) set(SRC_WSR88D_TESTS source/scwx/wsr88d/ar2v_file.test.cpp)
add_executable(wxtest ${SRC_MAIN} add_executable(wxtest ${SRC_MAIN}

View file

@ -0,0 +1,27 @@
#pragma once
#include <streambuf>
#include <vector>
namespace scwx
{
namespace util
{
class vectorbuf : public std::streambuf
{
public:
vectorbuf(std::vector<char>& v);
~vectorbuf() = default;
vectorbuf(const vectorbuf&) = delete;
vectorbuf& operator=(const vectorbuf&) = delete;
void update_read_pointers(size_t size);
private:
std::vector<char>& v_;
};
} // namespace util
} // namespace scwx

View file

@ -25,7 +25,7 @@ public:
Ar2vFile& operator=(const Ar2vFile&) = delete; Ar2vFile& operator=(const Ar2vFile&) = delete;
Ar2vFile(Ar2vFile&&) noexcept; Ar2vFile(Ar2vFile&&) noexcept;
Ar2vFile& operator=(Ar2vFile&&); Ar2vFile& operator=(Ar2vFile&&) noexcept;
bool LoadFile(const std::string& filename); bool LoadFile(const std::string& filename);

View file

@ -0,0 +1,46 @@
#pragma once
#include <scwx/wsr88d/rda/message.hpp>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
class ClutterFilterMapImpl;
class ClutterFilterMap : public Message
{
public:
explicit ClutterFilterMap();
~ClutterFilterMap();
ClutterFilterMap(const Message&) = delete;
ClutterFilterMap& operator=(const ClutterFilterMap&) = delete;
ClutterFilterMap(ClutterFilterMap&&) noexcept;
ClutterFilterMap& operator=(ClutterFilterMap&&) noexcept;
uint16_t map_generation_date() const;
uint16_t map_generation_time() const;
uint16_t number_of_elevation_segments() const;
uint16_t number_of_range_zones(uint16_t e, uint16_t a) const;
uint16_t op_code(uint16_t e, uint16_t a, uint16_t z) const;
uint16_t end_range(uint16_t e, uint16_t a, uint16_t z) const;
bool Parse(std::istream& is);
static std::unique_ptr<ClutterFilterMap> Create(MessageHeader&& header,
std::istream& is);
static const size_t NUM_AZIMUTH_SEGMENTS = 360u;
private:
std::unique_ptr<ClutterFilterMapImpl> p;
};
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,42 @@
#pragma once
#include <scwx/wsr88d/rda/message_header.hpp>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
class MessageImpl;
class Message
{
protected:
explicit Message();
Message(const Message&) = delete;
Message& operator=(const Message&) = delete;
Message(Message&&) noexcept;
Message& operator=(Message&&) noexcept;
bool ValidateSize(std::istream& is, size_t bytesRead) const;
public:
virtual ~Message();
const MessageHeader& header() const;
void set_header(MessageHeader&& header);
virtual bool Parse(std::istream& is) = 0;
private:
std::unique_ptr<MessageImpl> p;
};
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,39 @@
#pragma once
#include <scwx/wsr88d/rda/message.hpp>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
struct MessageInfo
{
std::unique_ptr<Message> message;
bool headerValid;
bool messageValid;
MessageInfo() : message(nullptr), headerValid(false), messageValid(false) {}
};
class MessageFactory
{
private:
explicit MessageFactory() = delete;
~MessageFactory() = delete;
MessageFactory(const Message&) = delete;
MessageFactory& operator=(const MessageFactory&) = delete;
MessageFactory(MessageFactory&&) noexcept = delete;
MessageFactory& operator=(MessageFactory&&) noexcept = delete;
public:
static MessageInfo Create(std::istream& is);
};
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -1,3 +1,5 @@
#pragma once
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@ -20,7 +22,7 @@ public:
MessageHeader& operator=(const MessageHeader&) = delete; MessageHeader& operator=(const MessageHeader&) = delete;
MessageHeader(MessageHeader&&) noexcept; MessageHeader(MessageHeader&&) noexcept;
MessageHeader& operator=(MessageHeader&&); MessageHeader& operator=(MessageHeader&&) noexcept;
uint16_t message_size() const; uint16_t message_size() const;
uint8_t rda_redundant_channel() const; uint8_t rda_redundant_channel() const;
@ -31,6 +33,8 @@ public:
uint16_t number_of_message_segments() const; uint16_t number_of_message_segments() const;
uint16_t message_segment_number() const; uint16_t message_segment_number() const;
void set_message_size(uint16_t messageSize);
bool Parse(std::istream& is); bool Parse(std::istream& is);
static const size_t SIZE = 16u; static const size_t SIZE = 16u;

View file

@ -0,0 +1,19 @@
#include <scwx/util/vectorbuf.hpp>
namespace scwx
{
namespace util
{
vectorbuf::vectorbuf(std::vector<char>& v) : v_(v)
{
update_read_pointers(0);
}
void vectorbuf::update_read_pointers(size_t size)
{
setg(v_.data(), v_.data(), v_.data() + size);
}
} // namespace util
} // namespace scwx

View file

@ -1,5 +1,5 @@
#include <scwx/wsr88d/ar2v_file.hpp> #include <scwx/wsr88d/ar2v_file.hpp>
#include <scwx/wsr88d/rda/message_header.hpp> #include <scwx/wsr88d/rda/message_factory.hpp>
#include <scwx/util/rangebuf.hpp> #include <scwx/util/rangebuf.hpp>
#include <fstream> #include <fstream>
@ -54,7 +54,7 @@ Ar2vFile::Ar2vFile() : p(std::make_unique<Ar2vFileImpl>()) {}
Ar2vFile::~Ar2vFile() = default; Ar2vFile::~Ar2vFile() = default;
Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept = default; Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept = default;
Ar2vFile& Ar2vFile::operator=(Ar2vFile&&) = default; Ar2vFile& Ar2vFile::operator=(Ar2vFile&&) noexcept = default;
bool Ar2vFile::LoadFile(const std::string& filename) bool Ar2vFile::LoadFile(const std::string& filename)
{ {
@ -112,6 +112,8 @@ bool Ar2vFile::LoadFile(const std::string& filename)
void Ar2vFileImpl::LoadLDMRecords(std::ifstream& f) void Ar2vFileImpl::LoadLDMRecords(std::ifstream& f)
{ {
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Loading LDM Records";
numRecords_ = 0; numRecords_ = 0;
while (f.peek() != EOF) while (f.peek() != EOF)
@ -163,6 +165,8 @@ void Ar2vFileImpl::LoadLDMRecords(std::ifstream& f)
void Ar2vFileImpl::ParseLDMRecords() void Ar2vFileImpl::ParseLDMRecords()
{ {
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Parsing LDM Records";
size_t count = 0; size_t count = 0;
for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++) for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++)
@ -177,18 +181,13 @@ void Ar2vFileImpl::ParseLDMRecords()
while (!ss.eof()) while (!ss.eof())
{ {
// TODO: Parse message, not just header rda::MessageInfo msgInfo = rda::MessageFactory::Create(ss);
rda::MessageHeader header; if (!msgInfo.headerValid)
if (!header.Parse(ss))
{ {
// Invalid header // Invalid message
break; break;
} }
// Seek to the end of the current message
ss.seekg(header.message_size() * 2 - rda::MessageHeader::SIZE,
std::ios_base::cur);
off_t offset = 0; off_t offset = 0;
uint16_t nextSize = 0u; uint16_t nextSize = 0u;
do do

View file

@ -0,0 +1,234 @@
#include <scwx/wsr88d/rda/clutter_filter_map.hpp>
#include <istream>
#include <vector>
#include <boost/log/trivial.hpp>
#ifdef WIN32
# include <WinSock2.h>
#else
# include <arpa/inet.h>
#endif
namespace scwx
{
namespace wsr88d
{
namespace rda
{
static const std::string logPrefix_ =
"[scwx::wsr88d::rda::clutter_filter_map] ";
struct RangeZone
{
uint16_t opCode;
uint16_t endRange;
};
class ClutterFilterMapImpl
{
public:
explicit ClutterFilterMapImpl() :
mapGenerationDate_(), mapGenerationTime_(), rangeZones_() {};
~ClutterFilterMapImpl() = default;
uint16_t mapGenerationDate_;
uint16_t mapGenerationTime_;
std::vector<std::vector<std::vector<RangeZone>>> rangeZones_;
};
ClutterFilterMap::ClutterFilterMap() :
Message(), p(std::make_unique<ClutterFilterMapImpl>())
{
}
ClutterFilterMap::~ClutterFilterMap() = default;
ClutterFilterMap::ClutterFilterMap(ClutterFilterMap&&) noexcept = default;
ClutterFilterMap&
ClutterFilterMap::operator=(ClutterFilterMap&&) noexcept = default;
uint16_t ClutterFilterMap::map_generation_date() const
{
return p->mapGenerationDate_;
}
uint16_t ClutterFilterMap::map_generation_time() const
{
return p->mapGenerationTime_;
}
uint16_t ClutterFilterMap::number_of_elevation_segments() const
{
return static_cast<uint16_t>(p->rangeZones_.size());
}
uint16_t ClutterFilterMap::number_of_range_zones(uint16_t e, uint16_t a) const
{
return static_cast<uint16_t>(p->rangeZones_[e][a].size());
}
uint16_t ClutterFilterMap::op_code(uint16_t e, uint16_t a, uint16_t z) const
{
return p->rangeZones_[e][a][z].opCode;
}
uint16_t ClutterFilterMap::end_range(uint16_t e, uint16_t a, uint16_t z) const
{
return p->rangeZones_[e][a][z].endRange;
}
bool ClutterFilterMap::Parse(std::istream& is)
{
BOOST_LOG_TRIVIAL(debug)
<< logPrefix_ << "Parsing Clutter Filter Map (Message Type 15)";
bool messageValid = true;
size_t bytesRead = 0;
uint16_t numElevationSegments = 0;
is.read(reinterpret_cast<char*>(&p->mapGenerationDate_), 2);
is.read(reinterpret_cast<char*>(&p->mapGenerationTime_), 2);
is.read(reinterpret_cast<char*>(&numElevationSegments), 2);
bytesRead += 6;
p->mapGenerationDate_ = htons(p->mapGenerationDate_);
p->mapGenerationTime_ = htons(p->mapGenerationTime_);
numElevationSegments = htons(numElevationSegments);
if (is.eof())
{
BOOST_LOG_TRIVIAL(warning) << logPrefix_ << "Reached end of file (1)";
messageValid = false;
}
else
{
if (p->mapGenerationDate_ < 1)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid date: " << p->mapGenerationDate_;
messageValid = false;
}
if (p->mapGenerationTime_ > 1440)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid time: " << p->mapGenerationTime_;
messageValid = false;
}
if (numElevationSegments < 1 || numElevationSegments > 5)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_
<< "Invalid number of elevation segments: " << numElevationSegments;
messageValid = false;
}
}
if (!messageValid)
{
numElevationSegments = 0;
}
p->rangeZones_.resize(numElevationSegments);
for (uint16_t e = 0; e < numElevationSegments && messageValid; e++)
{
p->rangeZones_[e].resize(NUM_AZIMUTH_SEGMENTS);
for (uint16_t a = 0; a < NUM_AZIMUTH_SEGMENTS && messageValid; a++)
{
uint16_t numRangeZones;
is.read(reinterpret_cast<char*>(&numRangeZones), 2);
bytesRead += 2;
numRangeZones = htons(numRangeZones);
if (is.eof())
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Reached end of file (2)";
messageValid = false;
}
else
{
if (numRangeZones < 1 || numRangeZones > 20)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_
<< "Invalid number of range zones: " << numRangeZones;
messageValid = false;
}
}
if (!messageValid)
{
break;
}
p->rangeZones_[e][a].resize(numRangeZones);
for (uint16_t z = 0; z < numRangeZones && messageValid; z++)
{
RangeZone& zone = p->rangeZones_[e][a][z];
is.read(reinterpret_cast<char*>(&zone.opCode), 2);
is.read(reinterpret_cast<char*>(&zone.endRange), 2);
bytesRead += 4;
zone.opCode = htons(zone.opCode);
zone.endRange = htons(zone.endRange);
if (is.eof())
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Reached end of file (3)";
messageValid = false;
}
else
{
if (zone.opCode > 2)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid op code: " << zone.opCode;
messageValid = false;
}
if (zone.endRange > 511)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid end range: " << zone.endRange;
messageValid = false;
}
}
}
}
}
if (!ValidateSize(is, bytesRead))
{
messageValid = false;
}
if (!messageValid)
{
p->rangeZones_.resize(0);
p->rangeZones_.shrink_to_fit();
}
return messageValid;
}
std::unique_ptr<ClutterFilterMap>
ClutterFilterMap::Create(MessageHeader&& header, std::istream& is)
{
std::unique_ptr<ClutterFilterMap> message =
std::make_unique<ClutterFilterMap>();
message->set_header(std::move(header));
message->Parse(is);
return message;
}
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,72 @@
#include <scwx/wsr88d/rda/message.hpp>
#include <istream>
#include <boost/log/trivial.hpp>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
static const std::string logPrefix_ = "[scwx::wsr88d::rda::message] ";
class MessageImpl
{
public:
explicit MessageImpl() : header_() {};
~MessageImpl() = default;
MessageHeader header_;
};
Message::Message() : p(std::make_unique<MessageImpl>()) {}
Message::~Message() = default;
Message::Message(Message&&) noexcept = default;
Message& Message::operator=(Message&&) noexcept = default;
bool Message::ValidateSize(std::istream& is, size_t bytesRead) const
{
bool messageValid = true;
size_t dataSize = header().message_size() * 2 - header().SIZE;
if (bytesRead != dataSize)
{
is.seekg(static_cast<std::streamoff>(dataSize) -
static_cast<std::streamoff>(bytesRead),
std::ios_base::cur);
if (bytesRead < dataSize)
{
BOOST_LOG_TRIVIAL(trace)
<< logPrefix_ << "Message contents smaller than size: " << bytesRead
<< " < " << dataSize << " bytes";
}
if (bytesRead > dataSize)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Message contents larger than size: " << bytesRead
<< " > " << dataSize << " bytes";
messageValid = false;
}
}
return messageValid;
}
const MessageHeader& Message::header() const
{
return p->header_;
}
void Message::set_header(MessageHeader&& header)
{
p->header_ = std::move(header);
}
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,130 @@
#include <scwx/wsr88d/rda/message_factory.hpp>
#include <scwx/util/vectorbuf.hpp>
#include <scwx/wsr88d/rda/clutter_filter_map.hpp>
#include <istream>
#include <unordered_map>
#include <vector>
#include <boost/log/trivial.hpp>
namespace scwx
{
namespace wsr88d
{
namespace rda
{
static const std::string logPrefix_ = "[scwx::wsr88d::rda::message_factory] ";
typedef std::function<std::unique_ptr<Message>(MessageHeader&&, std::istream&)>
CreateMessageFunction;
static const std::unordered_map<uint8_t, CreateMessageFunction> create_ {
{15, ClutterFilterMap::Create}};
static std::vector<char> messageData_;
static size_t bufferedSize_;
static util::vectorbuf messageBuffer_(messageData_);
static std::istream messageBufferStream_(&messageBuffer_);
MessageInfo MessageFactory::Create(std::istream& is)
{
MessageInfo info;
MessageHeader header;
info.headerValid = header.Parse(is);
info.messageValid = info.headerValid;
if (info.headerValid && create_.find(header.message_type()) == create_.end())
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Unknown message type: "
<< static_cast<unsigned>(header.message_type());
info.messageValid = false;
}
if (info.messageValid)
{
uint16_t segment = header.message_segment_number();
uint16_t totalSegments = header.number_of_message_segments();
uint8_t messageType = header.message_type();
size_t dataSize = header.message_size() * 2 - MessageHeader::SIZE;
std::istream* messageStream = nullptr;
if (totalSegments == 1)
{
BOOST_LOG_TRIVIAL(trace) << logPrefix_ << "Found Message "
<< static_cast<unsigned>(messageType);
messageStream = &is;
}
else
{
BOOST_LOG_TRIVIAL(trace)
<< logPrefix_ << "Found Message "
<< static_cast<unsigned>(messageType) << " Segment " << segment
<< "/" << totalSegments;
if (segment == 1)
{
// Estimate total message size
messageData_.reserve(dataSize * totalSegments);
bufferedSize_ = 0;
}
if (messageData_.capacity() < bufferedSize_ + dataSize)
{
BOOST_LOG_TRIVIAL(debug)
<< logPrefix_ << "Bad size estimate, increasing size";
// Estimate remaining size
uint16_t remainingSegments =
std::max<uint16_t>(totalSegments - segment + 1, 100u);
size_t remainingSize = remainingSegments * dataSize;
messageData_.reserve(bufferedSize_ + remainingSize);
}
is.read(messageData_.data() + bufferedSize_, dataSize);
bufferedSize_ += dataSize;
if (is.eof())
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "End of file reached trying to buffer message";
info.messageValid = false;
messageData_.shrink_to_fit();
bufferedSize_ = 0;
}
else if (segment == totalSegments)
{
messageBuffer_.update_read_pointers(bufferedSize_);
header.set_message_size(
static_cast<uint16_t>(bufferedSize_ / 2 + MessageHeader::SIZE));
messageStream = &messageBufferStream_;
}
}
if (messageStream != nullptr)
{
info.message =
create_.at(messageType)(std::move(header), *messageStream);
messageData_.shrink_to_fit();
bufferedSize_ = 0;
}
}
else if (info.headerValid)
{
// Seek to the end of the current message
is.seekg(header.message_size() * 2 - rda::MessageHeader::SIZE,
std::ios_base::cur);
}
return info;
}
} // namespace rda
} // namespace wsr88d
} // namespace scwx

View file

@ -44,14 +44,11 @@ public:
uint16_t messageSegmentNumber_; uint16_t messageSegmentNumber_;
}; };
MessageHeader::MessageHeader() : MessageHeader::MessageHeader() : p(std::make_unique<MessageHeaderImpl>()) {}
p(std::make_unique<MessageHeaderImpl>())
{
}
MessageHeader::~MessageHeader() = default; MessageHeader::~MessageHeader() = default;
MessageHeader::MessageHeader(MessageHeader&&) noexcept = default; MessageHeader::MessageHeader(MessageHeader&&) noexcept = default;
MessageHeader& MessageHeader::operator=(MessageHeader&&) = default; MessageHeader& MessageHeader::operator=(MessageHeader&&) noexcept = default;
uint16_t MessageHeader::message_size() const uint16_t MessageHeader::message_size() const
{ {
@ -93,6 +90,11 @@ uint16_t MessageHeader::message_segment_number() const
return p->messageSegmentNumber_; return p->messageSegmentNumber_;
} }
void MessageHeader::set_message_size(uint16_t messageSize)
{
p->messageSize_ = messageSize;
}
bool MessageHeader::Parse(std::istream& is) bool MessageHeader::Parse(std::istream& is)
{ {
bool headerValid = true; bool headerValid = true;

View file

@ -2,12 +2,20 @@ project(scwx-data)
find_package(Boost) find_package(Boost)
set(HDR_UTIL include/scwx/util/rangebuf.hpp) set(HDR_UTIL include/scwx/util/rangebuf.hpp
set(SRC_UTIL source/scwx/util/rangebuf.cpp) include/scwx/util/vectorbuf.hpp)
set(SRC_UTIL source/scwx/util/rangebuf.cpp
source/scwx/util/vectorbuf.cpp)
set(HDR_WSR88D include/scwx/wsr88d/ar2v_file.hpp) set(HDR_WSR88D include/scwx/wsr88d/ar2v_file.hpp)
set(SRC_WSR88D source/scwx/wsr88d/ar2v_file.cpp) set(SRC_WSR88D source/scwx/wsr88d/ar2v_file.cpp)
set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/message_header.hpp) set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_map.hpp
set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/message_header.cpp) include/scwx/wsr88d/rda/message.hpp
include/scwx/wsr88d/rda/message_factory.hpp
include/scwx/wsr88d/rda/message_header.hpp)
set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_map.cpp
source/scwx/wsr88d/rda/message.cpp
source/scwx/wsr88d/rda/message_factory.cpp
source/scwx/wsr88d/rda/message_header.cpp)
add_library(wxdata OBJECT ${HDR_UTIL} add_library(wxdata OBJECT ${HDR_UTIL}
${SRC_UTIL} ${SRC_UTIL}