Initial Level 3 header and description information

This commit is contained in:
Dan Paulat 2021-12-24 10:03:53 -06:00
parent a280f37289
commit 0303412519
6 changed files with 855 additions and 0 deletions

View file

@ -0,0 +1,45 @@
#pragma once
#include <cstdint>
#include <memory>
namespace scwx
{
namespace wsr88d
{
namespace rpg
{
class Level3MessageHeaderImpl;
class Level3MessageHeader
{
public:
explicit Level3MessageHeader();
~Level3MessageHeader();
Level3MessageHeader(const Level3MessageHeader&) = delete;
Level3MessageHeader& operator=(const Level3MessageHeader&) = delete;
Level3MessageHeader(Level3MessageHeader&&) noexcept;
Level3MessageHeader& operator=(Level3MessageHeader&&) noexcept;
int16_t message_code() const;
uint16_t date_of_message() const;
uint32_t time_of_message() const;
uint32_t length_of_message() const;
uint16_t source_id() const;
uint16_t destination_id() const;
uint16_t number_blocks() const;
bool Parse(std::istream& is);
static const size_t SIZE = 18u;
private:
std::unique_ptr<Level3MessageHeaderImpl> p;
};
} // namespace rpg
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,61 @@
#pragma once
#include <scwx/wsr88d/message.hpp>
#include <cstdint>
#include <memory>
namespace scwx
{
namespace wsr88d
{
namespace rpg
{
class ProductDescriptionBlockImpl;
class ProductDescriptionBlock : public Message
{
public:
explicit ProductDescriptionBlock();
~ProductDescriptionBlock();
ProductDescriptionBlock(const ProductDescriptionBlock&) = delete;
ProductDescriptionBlock& operator=(const ProductDescriptionBlock&) = delete;
ProductDescriptionBlock(ProductDescriptionBlock&&) noexcept;
ProductDescriptionBlock& operator=(ProductDescriptionBlock&&) noexcept;
int16_t block_divider() const;
int32_t latitude_of_radar() const;
int32_t longitude_of_radar() const;
int16_t height_of_radar() const;
int16_t product_code() const;
uint16_t operational_mode() const;
uint16_t volume_coverage_pattern() const;
int16_t sequence_number() const;
uint16_t volume_scan_number() const;
uint16_t volume_scan_date() const;
uint32_t volume_scan_start_time() const;
uint16_t generation_date_of_product() const;
uint32_t generation_time_of_product() const;
uint16_t elevation_number() const;
uint8_t version() const;
uint8_t spot_blank() const;
uint32_t offset_to_symbology() const;
uint32_t offset_to_graphic() const;
uint32_t offset_to_tabular() const;
bool IsCompressionEnabled() const;
bool Parse(std::istream& is);
static const size_t SIZE = 102u;
private:
std::unique_ptr<ProductDescriptionBlockImpl> p;
};
} // namespace rpg
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,55 @@
#pragma once
#include <memory>
#include <string>
namespace scwx
{
namespace wsr88d
{
namespace rpg
{
class WmoHeaderImpl;
/**
* @brief The WMO Header is defined in WMO Manual No. 386, with additional codes
* defined in WMO Codes Manual 306. The NWS summarizes the relevant
* information.
*
* <https://www.roc.noaa.gov/WSR88D/Level_III/Level3Info.aspx>
* <https://www.weather.gov/tg/head>
* <https://www.weather.gov/tg/headef>
* <https://www.weather.gov/tg/bbb>
* <https://www.weather.gov/tg/awips>
*/
class WmoHeader
{
public:
explicit WmoHeader();
~WmoHeader();
WmoHeader(const WmoHeader&) = delete;
WmoHeader& operator=(const WmoHeader&) = delete;
WmoHeader(WmoHeader&&) noexcept;
WmoHeader& operator=(WmoHeader&&) noexcept;
const std::string& data_type() const;
const std::string& geographic_designator() const;
const std::string& bulletin_id() const;
const std::string& icao() const;
const std::string& date_time() const;
const std::string& bbb_indicator() const;
const std::string& product_category() const;
const std::string& product_designator() const;
bool Parse(std::istream& is);
private:
std::unique_ptr<WmoHeaderImpl> p;
};
} // namespace rpg
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,176 @@
#include <scwx/wsr88d/rpg/level3_message_header.hpp>
#include <istream>
#include <string>
#include <boost/log/trivial.hpp>
#ifdef WIN32
# include <WinSock2.h>
#else
# include <arpa/inet.h>
#endif
namespace scwx
{
namespace wsr88d
{
namespace rpg
{
static const std::string logPrefix_ =
"[scwx::wsr88d::rpg::level3_message_header] ";
class Level3MessageHeaderImpl
{
public:
explicit Level3MessageHeaderImpl() :
messageCode_ {},
dateOfMessage_ {},
timeOfMessage_ {},
lengthOfMessage_ {},
sourceId_ {},
destinationId_ {},
numberBlocks_ {} {};
~Level3MessageHeaderImpl() = default;
int16_t messageCode_;
uint16_t dateOfMessage_;
uint32_t timeOfMessage_;
uint32_t lengthOfMessage_;
uint16_t sourceId_;
uint16_t destinationId_;
uint16_t numberBlocks_;
};
Level3MessageHeader::Level3MessageHeader() :
p(std::make_unique<Level3MessageHeaderImpl>())
{
}
Level3MessageHeader::~Level3MessageHeader() = default;
Level3MessageHeader::Level3MessageHeader(Level3MessageHeader&&) noexcept =
default;
Level3MessageHeader&
Level3MessageHeader::operator=(Level3MessageHeader&&) noexcept = default;
int16_t Level3MessageHeader::message_code() const
{
return p->messageCode_;
}
uint16_t Level3MessageHeader::date_of_message() const
{
return p->dateOfMessage_;
}
uint32_t Level3MessageHeader::time_of_message() const
{
return p->timeOfMessage_;
}
uint32_t Level3MessageHeader::length_of_message() const
{
return p->lengthOfMessage_;
}
uint16_t Level3MessageHeader::source_id() const
{
return p->sourceId_;
}
uint16_t Level3MessageHeader::destination_id() const
{
return p->destinationId_;
}
uint16_t Level3MessageHeader::number_blocks() const
{
return p->numberBlocks_;
}
bool Level3MessageHeader::Parse(std::istream& is)
{
bool headerValid = true;
is.read(reinterpret_cast<char*>(&p->messageCode_), 2);
is.read(reinterpret_cast<char*>(&p->dateOfMessage_), 2);
is.read(reinterpret_cast<char*>(&p->timeOfMessage_), 4);
is.read(reinterpret_cast<char*>(&p->lengthOfMessage_), 4);
is.read(reinterpret_cast<char*>(&p->sourceId_), 2);
is.read(reinterpret_cast<char*>(&p->destinationId_), 2);
is.read(reinterpret_cast<char*>(&p->numberBlocks_), 2);
p->messageCode_ = ntohs(p->messageCode_);
p->dateOfMessage_ = ntohs(p->dateOfMessage_);
p->timeOfMessage_ = ntohl(p->timeOfMessage_);
p->lengthOfMessage_ = ntohl(p->lengthOfMessage_);
p->sourceId_ = ntohs(p->sourceId_);
p->destinationId_ = ntohs(p->destinationId_);
p->numberBlocks_ = ntohs(p->numberBlocks_);
if (is.eof())
{
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Reached end of file";
headerValid = false;
}
else
{
if (p->messageCode_ < -131 ||
(p->messageCode_ > -16 && p->messageCode_ < 0) ||
p->messageCode_ > 211)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid message code: " << p->messageCode_;
headerValid = false;
}
if (p->dateOfMessage_ < 1u || p->dateOfMessage_ > 32'767u)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid date: " << p->dateOfMessage_;
headerValid = false;
}
if (p->timeOfMessage_ > 86'399u)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid time: " << p->timeOfMessage_;
headerValid = false;
}
if (p->lengthOfMessage_ < 18 || p->lengthOfMessage_ > 1'329'270u)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid length: " << p->lengthOfMessage_;
headerValid = false;
}
if (p->sourceId_ > 999u)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid source ID: " << p->sourceId_;
headerValid = false;
}
if (p->destinationId_ > 999u)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid destination ID: " << p->destinationId_;
headerValid = false;
}
if (p->numberBlocks_ < 1u || p->numberBlocks_ > 51u)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid block count: " << p->numberBlocks_;
headerValid = false;
}
}
if (headerValid)
{
BOOST_LOG_TRIVIAL(trace)
<< logPrefix_ << "Message code: " << p->messageCode_;
}
return headerValid;
}
} // namespace rpg
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,306 @@
#include <scwx/wsr88d/rpg/product_description_block.hpp>
#include <array>
#include <istream>
#include <set>
#include <string>
#include <boost/log/trivial.hpp>
#ifdef WIN32
# include <WinSock2.h>
#else
# include <arpa/inet.h>
#endif
namespace scwx
{
namespace wsr88d
{
namespace rpg
{
static const std::string logPrefix_ =
"[scwx::wsr88d::rpg::product_description_block] ";
static const std::set<int16_t> compressedProducts_ = {
32, 94, 99, 134, 135, 138, 149, 152, 153, 154, 155, 159, 161, 163, 165,
167, 168, 170, 172, 173, 174, 175, 176, 177, 178, 179, 193, 195, 202};
class ProductDescriptionBlockImpl
{
public:
explicit ProductDescriptionBlockImpl() :
blockDivider_ {},
latitudeOfRadar_ {},
longitudeOfRadar_ {},
heightOfRadar_ {},
productCode_ {},
operationalMode_ {},
volumeCoveragePattern_ {},
sequenceNumber_ {},
volumeScanNumber_ {},
volumeScanDate_ {},
volumeScanStartTime_ {},
generationDateOfProduct_ {},
generationTimeOfProduct_ {},
elevationNumber_ {},
halfword31_ {},
halfword32_ {},
halfword33_ {},
halfword34_ {},
halfword35_ {},
version_ {},
spotBlank_ {},
offsetToSymbology_ {},
offsetToGraphic_ {},
offsetToTabular_ {},
parameters_ {} {};
~ProductDescriptionBlockImpl() = default;
int16_t blockDivider_;
int32_t latitudeOfRadar_;
int32_t longitudeOfRadar_;
int16_t heightOfRadar_;
int16_t productCode_;
uint16_t operationalMode_;
uint16_t volumeCoveragePattern_;
int16_t sequenceNumber_;
uint16_t volumeScanNumber_;
uint16_t volumeScanDate_;
uint32_t volumeScanStartTime_;
uint16_t generationDateOfProduct_;
uint32_t generationTimeOfProduct_;
// 27-28: Product dependent parameters 1 and 2 (Table V)
uint16_t elevationNumber_;
// 30: Product dependent parameter 3 (Table V)
// 31-46: Product dependent (Note 1)
uint16_t halfword31_;
uint16_t halfword32_;
uint16_t halfword33_;
uint16_t halfword34_;
uint16_t halfword35_;
// Halfwords 36-46 are unused
// 47-53: Product dependent parameters 4-10 (Table V, Note 3)
uint8_t version_;
uint8_t spotBlank_;
uint32_t offsetToSymbology_;
uint32_t offsetToGraphic_;
uint32_t offsetToTabular_;
std::array<uint16_t, 10> parameters_;
};
ProductDescriptionBlock::ProductDescriptionBlock() :
p(std::make_unique<ProductDescriptionBlockImpl>())
{
}
ProductDescriptionBlock::~ProductDescriptionBlock() = default;
ProductDescriptionBlock::ProductDescriptionBlock(
ProductDescriptionBlock&&) noexcept = default;
ProductDescriptionBlock& ProductDescriptionBlock::operator=(
ProductDescriptionBlock&&) noexcept = default;
int16_t ProductDescriptionBlock::block_divider() const
{
return p->blockDivider_;
}
int32_t ProductDescriptionBlock::latitude_of_radar() const
{
return p->latitudeOfRadar_;
}
int32_t ProductDescriptionBlock::longitude_of_radar() const
{
return p->longitudeOfRadar_;
}
int16_t ProductDescriptionBlock::height_of_radar() const
{
return p->heightOfRadar_;
}
int16_t ProductDescriptionBlock::product_code() const
{
return p->productCode_;
}
uint16_t ProductDescriptionBlock::operational_mode() const
{
return p->operationalMode_;
}
uint16_t ProductDescriptionBlock::volume_coverage_pattern() const
{
return p->volumeCoveragePattern_;
}
int16_t ProductDescriptionBlock::sequence_number() const
{
return p->sequenceNumber_;
}
uint16_t ProductDescriptionBlock::volume_scan_number() const
{
return p->volumeScanNumber_;
}
uint16_t ProductDescriptionBlock::volume_scan_date() const
{
return p->volumeScanDate_;
}
uint32_t ProductDescriptionBlock::volume_scan_start_time() const
{
return p->volumeScanStartTime_;
}
uint16_t ProductDescriptionBlock::generation_date_of_product() const
{
return p->generationDateOfProduct_;
}
uint32_t ProductDescriptionBlock::generation_time_of_product() const
{
return p->generationTimeOfProduct_;
}
uint16_t ProductDescriptionBlock::elevation_number() const
{
return p->elevationNumber_;
}
uint8_t ProductDescriptionBlock::version() const
{
return p->version_;
}
uint8_t ProductDescriptionBlock::spot_blank() const
{
return p->spotBlank_;
}
uint32_t ProductDescriptionBlock::offset_to_symbology() const
{
return p->offsetToSymbology_;
}
uint32_t ProductDescriptionBlock::offset_to_graphic() const
{
return p->offsetToGraphic_;
}
uint32_t ProductDescriptionBlock::offset_to_tabular() const
{
return p->offsetToTabular_;
}
bool ProductDescriptionBlock::IsCompressionEnabled() const
{
bool isCompressed = false;
if (compressedProducts_.contains(p->productCode_))
{
isCompressed = (p->parameters_[7] == 1u);
}
return isCompressed;
}
bool ProductDescriptionBlock::Parse(std::istream& is)
{
bool blockValid = true;
is.read(reinterpret_cast<char*>(&p->blockDivider_), 2); // 10
is.read(reinterpret_cast<char*>(&p->latitudeOfRadar_), 4); // 11-12
is.read(reinterpret_cast<char*>(&p->longitudeOfRadar_), 4); // 13-14
is.read(reinterpret_cast<char*>(&p->heightOfRadar_), 2); // 15
is.read(reinterpret_cast<char*>(&p->productCode_), 2); // 16
is.read(reinterpret_cast<char*>(&p->operationalMode_), 2); // 17
is.read(reinterpret_cast<char*>(&p->volumeCoveragePattern_), 2); // 18
is.read(reinterpret_cast<char*>(&p->sequenceNumber_), 2); // 19
is.read(reinterpret_cast<char*>(&p->volumeScanNumber_), 2); // 20
is.read(reinterpret_cast<char*>(&p->volumeScanDate_), 2); // 21
is.read(reinterpret_cast<char*>(&p->volumeScanStartTime_), 4); // 22-23
is.read(reinterpret_cast<char*>(&p->generationDateOfProduct_), 2); // 24
is.read(reinterpret_cast<char*>(&p->generationTimeOfProduct_), 4); // 25-26
is.read(reinterpret_cast<char*>(&p->parameters_[0]), 2 * 2); // 27-28
is.read(reinterpret_cast<char*>(&p->elevationNumber_), 2); // 29
is.read(reinterpret_cast<char*>(&p->parameters_[2]), 2); // 30
is.read(reinterpret_cast<char*>(&p->halfword31_), 2); // 31
is.read(reinterpret_cast<char*>(&p->halfword32_), 2); // 32
is.read(reinterpret_cast<char*>(&p->halfword33_), 2); // 33
is.read(reinterpret_cast<char*>(&p->halfword34_), 2); // 34
is.read(reinterpret_cast<char*>(&p->halfword35_), 2); // 35
is.seekg(11 * 2, std::ios_base::cur); // 36-46
is.read(reinterpret_cast<char*>(&p->parameters_[3]), 7 * 2); // 47-53
is.read(reinterpret_cast<char*>(&p->version_), 1); // 54
is.read(reinterpret_cast<char*>(&p->spotBlank_), 1); // 54
is.read(reinterpret_cast<char*>(&p->offsetToSymbology_), 4); // 55-56
is.read(reinterpret_cast<char*>(&p->offsetToGraphic_), 4); // 57-58
is.read(reinterpret_cast<char*>(&p->offsetToTabular_), 4); // 59-60
p->blockDivider_ = ntohs(p->blockDivider_);
p->latitudeOfRadar_ = ntohl(p->latitudeOfRadar_);
p->longitudeOfRadar_ = ntohl(p->longitudeOfRadar_);
p->heightOfRadar_ = ntohs(p->heightOfRadar_);
p->productCode_ = ntohs(p->productCode_);
p->operationalMode_ = ntohs(p->operationalMode_);
p->volumeCoveragePattern_ = ntohs(p->volumeCoveragePattern_);
p->sequenceNumber_ = ntohs(p->sequenceNumber_);
p->volumeScanNumber_ = ntohs(p->volumeScanNumber_);
p->volumeScanDate_ = ntohs(p->volumeScanDate_);
p->volumeScanStartTime_ = ntohl(p->volumeScanStartTime_);
p->generationDateOfProduct_ = ntohs(p->generationDateOfProduct_);
p->generationTimeOfProduct_ = ntohl(p->generationTimeOfProduct_);
p->elevationNumber_ = ntohs(p->elevationNumber_);
p->halfword31_ = ntohs(p->halfword31_);
p->halfword32_ = ntohs(p->halfword32_);
p->halfword33_ = ntohs(p->halfword33_);
p->halfword34_ = ntohs(p->halfword34_);
p->halfword35_ = ntohs(p->halfword35_);
p->offsetToSymbology_ = ntohl(p->offsetToSymbology_);
p->offsetToGraphic_ = ntohl(p->offsetToGraphic_);
p->offsetToTabular_ = ntohl(p->offsetToTabular_);
SwapArray(p->parameters_);
if (is.eof())
{
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Reached end of file";
blockValid = false;
}
else
{
if (p->blockDivider_ != -1)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid block divider: " << p->blockDivider_;
blockValid = false;
}
if (p->productCode_ < -299 ||
(p->productCode_ > -16 && p->productCode_ < 16) ||
p->productCode_ > 299)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid product code: " << p->productCode_;
blockValid = false;
}
}
if (blockValid)
{
BOOST_LOG_TRIVIAL(trace)
<< logPrefix_ << "Product code: " << p->productCode_;
}
return blockValid;
}
} // namespace rpg
} // namespace wsr88d
} // namespace scwx

View file

@ -0,0 +1,212 @@
#include <scwx/wsr88d/rpg/wmo_header.hpp>
#include <scwx/util/streams.hpp>
#include <istream>
#include <sstream>
#include <string>
#include <boost/log/trivial.hpp>
#ifdef WIN32
# include <WinSock2.h>
#else
# include <arpa/inet.h>
#endif
namespace scwx
{
namespace wsr88d
{
namespace rpg
{
static const std::string logPrefix_ = "[scwx::wsr88d::rpg::wmo_header] ";
class WmoHeaderImpl
{
public:
explicit WmoHeaderImpl() :
dataType_ {},
geographicDesignator_ {},
bulletinId_ {},
icao_ {},
dateTime_ {},
bbbIndicator_ {},
productCategory_ {},
productDesignator_ {} {};
~WmoHeaderImpl() = default;
std::string dataType_;
std::string geographicDesignator_;
std::string bulletinId_;
std::string icao_;
std::string dateTime_;
std::string bbbIndicator_;
std::string productCategory_;
std::string productDesignator_;
};
WmoHeader::WmoHeader() : p(std::make_unique<WmoHeaderImpl>()) {}
WmoHeader::~WmoHeader() = default;
WmoHeader::WmoHeader(WmoHeader&&) noexcept = default;
WmoHeader& WmoHeader::operator=(WmoHeader&&) noexcept = default;
const std::string& WmoHeader::data_type() const
{
return p->dataType_;
}
const std::string& WmoHeader::geographic_designator() const
{
return p->geographicDesignator_;
}
const std::string& WmoHeader::bulletin_id() const
{
return p->bulletinId_;
}
const std::string& WmoHeader::icao() const
{
return p->icao_;
}
const std::string& WmoHeader::date_time() const
{
return p->dateTime_;
}
const std::string& WmoHeader::bbb_indicator() const
{
return p->bbbIndicator_;
}
const std::string& WmoHeader::product_category() const
{
return p->productCategory_;
}
const std::string& WmoHeader::product_designator() const
{
return p->productDesignator_;
}
bool WmoHeader::Parse(std::istream& is)
{
bool headerValid = true;
std::string wmoLine;
std::string awipsLine;
std::getline(is, wmoLine);
std::getline(is, awipsLine);
if (is.eof())
{
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Reached end of file";
headerValid = false;
}
else if (!wmoLine.ends_with("\r\r"))
{
BOOST_LOG_TRIVIAL(debug)
<< logPrefix_ << "WMO Abbreviated Heading Line is malformed";
headerValid = false;
}
else if (!awipsLine.ends_with("\r\r"))
{
BOOST_LOG_TRIVIAL(debug)
<< logPrefix_ << "AWIPS Identifier Line is malformed";
headerValid = false;
}
else
{
// Remove delimiters from the end of the line
wmoLine.erase(wmoLine.end() - 2);
awipsLine.erase(awipsLine.end() - 2);
}
// WMO Abbreviated Heading Line:
// T1T2A1A2ii CCCC YYGGgg (BBB)
if (headerValid)
{
std::string token;
std::istringstream wmoTokens(wmoLine);
std::vector<std::string> wmoTokenList;
while (wmoTokens >> token)
{
wmoTokenList.push_back(std::move(token));
}
if (wmoTokenList.size() < 3 || wmoTokenList.size() > 4)
{
BOOST_LOG_TRIVIAL(debug)
<< logPrefix_ << "Invalid number of WMO tokens";
headerValid = false;
}
else if (wmoTokenList[0].size() != 6)
{
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "WMO identifier malformed";
headerValid = false;
}
else if (wmoTokenList[1].size() != 4)
{
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "ICAO malformed";
headerValid = false;
}
else if (wmoTokenList[2].size() != 6)
{
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Date/time malformed";
headerValid = false;
}
else if (wmoTokenList.size() == 4 && wmoTokenList[3].size() != 3)
{
// BBB indicator is optional
BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "BBB indicator malformed";
headerValid = false;
}
else
{
p->dataType_ = wmoTokenList[0].substr(0, 2);
p->geographicDesignator_ = wmoTokenList[0].substr(2, 2);
p->bulletinId_ = wmoTokenList[0].substr(4, 2);
p->icao_ = wmoTokenList[1];
p->dateTime_ = wmoTokenList[2];
if (wmoTokenList.size() == 4)
{
p->bbbIndicator_ = wmoTokenList[3];
}
else
{
p->bbbIndicator_ = "";
}
}
}
// AWIPS Identifer Line:
// NNNxxx
if (headerValid)
{
if (awipsLine.size() != 6)
{
BOOST_LOG_TRIVIAL(debug)
<< logPrefix_ << "AWIPS Identifier Line bad size";
headerValid = false;
}
else
{
p->productCategory_ = awipsLine.substr(0, 3);
p->productDesignator_ = awipsLine.substr(3, 3);
}
}
return headerValid;
}
} // namespace rpg
} // namespace wsr88d
} // namespace scwx