A2B: Add A2BMessage transmit support

add-device-sharing
Yasser Yassine 2022-12-02 12:13:25 -05:00
parent ed1632c652
commit 78465e0f20
9 changed files with 405 additions and 30 deletions

View File

@ -394,6 +394,7 @@ if(LIBICSNEO_BUILD_TESTS)
test/eventmanagertest.cpp test/eventmanagertest.cpp
test/ethernetpacketizertest.cpp test/ethernetpacketizertest.cpp
test/i2cencoderdecodertest.cpp test/i2cencoderdecodertest.cpp
test/a2bencoderdecodertest.cpp
) )
target_link_libraries(libicsneo-tests gtest gtest_main) target_link_libraries(libicsneo-tests gtest gtest_main)

View File

@ -112,6 +112,7 @@ static constexpr const char* ATOMIC_OPERATION_RETRIED = "An operation failed to
static constexpr const char* ATOMIC_OPERATION_COMPLETED_NONATOMICALLY = "An ideally-atomic operation was completed nonatomically."; static constexpr const char* ATOMIC_OPERATION_COMPLETED_NONATOMICALLY = "An ideally-atomic operation was completed nonatomically.";
static constexpr const char* WIVI_STACK_REFRESH_FAILED = "The Wireless neoVI stack encountered a communication error."; static constexpr const char* WIVI_STACK_REFRESH_FAILED = "The Wireless neoVI stack encountered a communication error.";
static constexpr const char* WIVI_UPLOAD_STACK_OVERFLOW = "The Wireless neoVI upload stack has encountered an overflow condition."; static constexpr const char* WIVI_UPLOAD_STACK_OVERFLOW = "The Wireless neoVI upload stack has encountered an overflow condition.";
static constexpr const char* A2B_MESSAGE_INCOMPLETE_FRAME = "At least one of the frames of the A2B message does not contain samples for each channel and stream.";
// Transport Errors // Transport Errors
static constexpr const char* FAILED_TO_READ = "A read operation failed."; static constexpr const char* FAILED_TO_READ = "A read operation failed.";
@ -241,6 +242,8 @@ const char* APIEvent::DescriptionForType(Type type) {
return WIVI_STACK_REFRESH_FAILED; return WIVI_STACK_REFRESH_FAILED;
case Type::WiVIUploadStackOverflow: case Type::WiVIUploadStackOverflow:
return WIVI_UPLOAD_STACK_OVERFLOW; return WIVI_UPLOAD_STACK_OVERFLOW;
case Type::A2BMessageIncompleteFrame:
return A2B_MESSAGE_INCOMPLETE_FRAME;
// Transport Errors // Transport Errors
case Type::FailedToRead: case Type::FailedToRead:

View File

@ -8,6 +8,8 @@
#include "icsneo/communication/message/ethphymessage.h" #include "icsneo/communication/message/ethphymessage.h"
#include "icsneo/communication/packet/i2cpacket.h" #include "icsneo/communication/packet/i2cpacket.h"
#include "icsneo/communication/message/i2cmessage.h" #include "icsneo/communication/message/i2cmessage.h"
#include "icsneo/communication/packet/a2bpacket.h"
using namespace icsneo; using namespace icsneo;
@ -70,6 +72,18 @@ bool Encoder::encode(const Packetizer& packetizer, std::vector<uint8_t>& result,
// packets to the device. This function just encodes them back to back into `result` // packets to the device. This function just encodes them back to back into `result`
return HardwareISO9141Packet::EncodeFromMessage(*isomsg, result, report, packetizer); return HardwareISO9141Packet::EncodeFromMessage(*isomsg, result, report, packetizer);
} // End of Network::Type::ISO9141 } // End of Network::Type::ISO9141
case Network::Type::A2B: {
auto a2bmsg = std::dynamic_pointer_cast<A2BMessage>(message);
if(!a2bmsg) {
report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error);
return false;
}
buffer = &result;
if(!HardwareA2BPacket::EncodeFromMessage(*a2bmsg, result, report)) {
return false;
}
break;
} // End of Network::Type::A2B
case Network::Type::I2C: { case Network::Type::I2C: {
auto i2cmsg = std::dynamic_pointer_cast<I2CMessage>(message); auto i2cmsg = std::dynamic_pointer_cast<I2CMessage>(message);
if(!i2cmsg) { if(!i2cmsg) {
@ -81,7 +95,7 @@ bool Encoder::encode(const Packetizer& packetizer, std::vector<uint8_t>& result,
return false; return false;
} }
break; break;
} } // End of Network::Type::I2C
default: default:
report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error); report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
return false; return false;

View File

@ -70,11 +70,11 @@ bool A2BWAVOutput::writeSamples(const std::shared_ptr<A2BMessage>& msg, A2BMessa
uint8_t numChannels = msg->getNumChannels(); uint8_t numChannels = msg->getNumChannels();
uint8_t channel = 0; uint8_t channel = 0;
uint32_t sampleIndex = 0; uint32_t frame = 0;
uint8_t bitDepth = msg->getBitDepth(); uint8_t bitDepth = msg->getBitDepth();
while(true) { while(true) {
auto sample = msg->getSample(dir, channel, sampleIndex); auto sample = msg->getSample(dir, channel, frame);
if(!sample) { if(!sample) {
if(channel == 0) { if(channel == 0) {
@ -90,7 +90,7 @@ bool A2BWAVOutput::writeSamples(const std::shared_ptr<A2BMessage>& msg, A2BMessa
channel = (channel + 1) % numChannels; channel = (channel + 1) % numChannels;
if(channel == 0) { if(channel == 0) {
sampleIndex++; frame++;
} }
} }

View File

@ -1,13 +1,15 @@
#include "icsneo/communication/packet/a2bpacket.h" #include "icsneo/communication/packet/a2bpacket.h"
#include "icsneo/communication/message/a2bmessage.h"
#include <cstring> #include <cstring>
using namespace icsneo;
std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<uint8_t> &bytestream) { namespace icsneo {
constexpr uint8_t coreMiniMessageHeaderSize = 28; const size_t HardwareA2BPacket::coreMiniMessageHeaderSize = 28;
const size_t HardwareA2BPacket::a2bMessageMaxLength = (size_t)HardwareA2BPacket::coreMiniMessageHeaderSize + 1024;
const size_t HardwareA2BPacket::a2bHeaderSize = 6;
std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<uint8_t>& bytestream) {
if(bytestream.size() < coreMiniMessageHeaderSize) if(bytestream.size() < coreMiniMessageHeaderSize)
{ {
return nullptr; return nullptr;
@ -31,9 +33,14 @@ std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<ui
uint8_t bytesPerChannel = data->header.channelSize16 ? 2 : 4; uint8_t bytesPerChannel = data->header.channelSize16 ? 2 : 4;
uint8_t numChannels = data->header.channelNum; uint8_t numChannels = data->header.channelNum;
uint8_t bitDepth = data->header.channelSize16 ? A2BPCM_L16 : A2BPCM_L24; uint8_t bitDepth = data->header.channelSize16 ? A2BPCM_L16 : A2BPCM_L24;
bool monitor = data->header.monitor;
std::shared_ptr<A2BMessage> msg = std::make_shared<A2BMessage>(bitDepth, bytesPerChannel, numChannels, monitor); std::shared_ptr<A2BMessage> msg = std::make_shared<A2BMessage>(bitDepth, bytesPerChannel, numChannels);
msg->channelSize16 = data->header.channelSize16;
msg->monitor = data->header.monitor;
msg->txmsg = data->header.txmsg;
msg->errIndicator = data->header.errIndicator;
msg->syncFrame = data->header.syncFrame;
msg->rfu2 = data->header.rfu2;
const uint8_t *bytes = bytestream.data(); const uint8_t *bytes = bytestream.data();
bytes+=coreMiniMessageHeaderSize; bytes+=coreMiniMessageHeaderSize;
@ -58,3 +65,99 @@ std::shared_ptr<Message> HardwareA2BPacket::DecodeToMessage(const std::vector<ui
return msg; return msg;
} }
bool HardwareA2BPacket::EncodeFromMessage(const A2BMessage& message, std::vector<uint8_t>& bytestream, const device_eventhandler_t& report) {
if(message.getBytesPerSample() != 2 && message.getBytesPerSample() != 4) {
report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error);
return false;
}
size_t sampleBytes = message.getNumSamples() * static_cast<size_t>(message.getBytesPerSample());
size_t totalSize = coreMiniMessageHeaderSize + sampleBytes;
if(totalSize > a2bMessageMaxLength) {
report(APIEvent::Type::MessageMaxLengthExceeded, APIEvent::Severity::Error);
return false;
}
bytestream.reserve(totalSize);
bytestream.push_back(message.getNumChannels());
bytestream.push_back(message.channelSize16 ? 1 : 0);
uint8_t a2b2Bits = 0;
if(message.monitor) {
a2b2Bits = a2b2Bits | 1;
}
if(message.txmsg) {
a2b2Bits = a2b2Bits | (1 << 1);
}
if(message.errIndicator) {
a2b2Bits = a2b2Bits | (1 << 2);
}
if(message.syncFrame) {
a2b2Bits = a2b2Bits | (1 << 3);
}
bytestream.push_back(a2b2Bits);
bytestream.push_back(0);
bytestream.push_back(static_cast<uint8_t>(message.rfu2));
bytestream.push_back(static_cast<uint8_t>(message.rfu2 >> 8));
for(size_t i = 0; i < (coreMiniMessageHeaderSize - a2bHeaderSize); i++)
bytestream.push_back(0);
uint8_t numChannels = message.getNumChannels();
uint8_t channel = 0;
uint32_t frame = 0;
auto writeSample = [&](A2BPCMSample&& sample) {
for(uint32_t i = 0; i < static_cast<uint32_t>(message.getBytesPerSample()); i++) {
bytestream.push_back(static_cast<uint8_t>((sample >> (i*8))));
}
};
while(true) {
auto dsSample = message.getSample(A2BMessage::A2BDirection::DownStream, channel, frame);
auto usSample = message.getSample(A2BMessage::A2BDirection::UpStream, channel, frame);
// Check if getSample failed for both downstream and upstream
if(!dsSample && !usSample) {
if(channel != 0) {
//Incomplete frame, the frame we are currently on does not contain all channel samples
report(APIEvent::Type::A2BMessageIncompleteFrame, APIEvent::Severity::Error);
return false;
}
// Since no samples have been written for the current frame yet and there are no more
// samples in both upstream and downstream, we can break and end parsing.
break;
}
// Since the first case failed, at least one of the streams still has samples.
// This case checks to see if the other stream does not have a sample.
else if(!dsSample || !usSample) {
// Report an error since we must have a one to one correspondence between upstream
// and downstream.
report(APIEvent::Type::A2BMessageIncompleteFrame, APIEvent::Severity::Error);
return false;
}
writeSample(std::move(dsSample.value()));
writeSample(std::move(usSample.value()));
channel = (channel + 1) % numChannels;
if(channel == 0)
frame++;
}
return true;
}
}

View File

@ -90,6 +90,7 @@ public:
WiVIStackRefreshFailed = 0x2036, WiVIStackRefreshFailed = 0x2036,
WiVIUploadStackOverflow = 0x2037, WiVIUploadStackOverflow = 0x2037,
I2CMessageExceedsMaxLength = 0x2038, I2CMessageExceedsMaxLength = 0x2038,
A2BMessageIncompleteFrame = 0x2039,
// Transport Events // Transport Events
FailedToRead = 0x3000, FailedToRead = 0x3000,

View File

@ -31,16 +31,15 @@ public:
A2BMessage() = delete; A2BMessage() = delete;
A2BMessage(uint8_t bitDepth, uint8_t bytesPerSample, uint8_t numChannels, bool monitor) : A2BMessage(uint8_t bitDepth, uint8_t bytesPerSample, uint8_t numChannels) :
mBitDepth(bitDepth), bDepth(bitDepth),
mBytesPerSample(bytesPerSample), bps(bytesPerSample)
mMonitor(monitor)
{ {
downstream.resize(numChannels); downstream.resize(numChannels);
upstream.resize(numChannels); upstream.resize(numChannels);
} }
void addSample(A2BPCMSample &&sample, A2BDirection dir, uint8_t channel) { void addSample(A2BPCMSample&& sample, A2BDirection dir, uint8_t channel) {
if(dir == A2BDirection::DownStream) { if(dir == A2BDirection::DownStream) {
downstream[channel].push_back(std::move(sample)); downstream[channel].push_back(std::move(sample));
} }
@ -61,18 +60,18 @@ public:
return upstream[channel].data(); return upstream[channel].data();
} }
std::optional<A2BPCMSample> getSample(A2BDirection dir, uint8_t channel, uint32_t sampleIndex) const { std::optional<A2BPCMSample> getSample(A2BDirection dir, uint8_t channel, uint32_t frame) const {
const A2BPCMSample* samples = getSamples(dir, channel); const A2BPCMSample* samples = getSamples(dir, channel);
auto numSamplesInChannel = getNumSamplesInChannel(dir, channel); auto numSamplesInChannel = getNumSamplesInChannel(dir, channel);
if( if(
samples == nullptr || samples == nullptr ||
sampleIndex >= numSamplesInChannel.value_or(0) frame >= numSamplesInChannel.value_or(0)
) { ) {
return std::nullopt; return std::nullopt;
} }
return samples[sampleIndex]; return samples[frame];
} }
std::optional<size_t> getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const { std::optional<size_t> getNumSamplesInChannel(A2BDirection dir, uint8_t channel) const {
@ -104,25 +103,41 @@ public:
} }
uint8_t getBitDepth() const { uint8_t getBitDepth() const {
return mBitDepth; return bDepth;
} }
uint8_t getBytesPerSample() const { uint8_t getBytesPerSample() const {
return mBytesPerSample; return bps;
} }
bool isMonitor() const { // Equals operation for testing.
return mMonitor; bool operator==(const A2BMessage& rhs) const {
return channelSize16 == rhs.channelSize16 &&
monitor == rhs.monitor &&
txmsg == rhs.txmsg &&
errIndicator == rhs.errIndicator &&
syncFrame == rhs.syncFrame &&
rfu2 == rhs.rfu2 &&
downstream == rhs.downstream &&
upstream == rhs.upstream &&
totalSamples == rhs.totalSamples &&
bDepth == rhs.bDepth &&
bps == rhs.bps;
} }
bool channelSize16;
bool monitor;
bool txmsg;
bool errIndicator;
bool syncFrame;
uint16_t rfu2;
private: private:
std::vector<ChannelBuffer> downstream; std::vector<ChannelBuffer> downstream;
std::vector<ChannelBuffer> upstream; std::vector<ChannelBuffer> upstream;
size_t totalSamples = 0; size_t totalSamples = 0;
uint8_t mBitDepth; uint8_t bDepth;
uint8_t mBytesPerSample; uint8_t bps;
bool mMonitor;
}; };
} }

View File

@ -7,9 +7,7 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include "icsneo/communication/message/message.h" #include "icsneo/communication/message/a2bmessage.h"
namespace icsneo { namespace icsneo {
@ -18,6 +16,7 @@ typedef uint16_t icscm_bitfield;
struct HardwareA2BPacket { struct HardwareA2BPacket {
static std::shared_ptr<Message> DecodeToMessage(const std::vector<uint8_t>& bytestream); static std::shared_ptr<Message> DecodeToMessage(const std::vector<uint8_t>& bytestream);
static bool EncodeFromMessage(const A2BMessage& message, std::vector<uint8_t>& bytestream, const device_eventhandler_t& report);
struct { struct {
// CxA2B // CxA2B
@ -34,6 +33,10 @@ struct HardwareA2BPacket {
icscm_bitfield : 11; icscm_bitfield : 11;
icscm_bitfield rfu2; icscm_bitfield rfu2;
} header; } header;
static const size_t coreMiniMessageHeaderSize;
static const size_t a2bMessageMaxLength;
static const size_t a2bHeaderSize;
}; };
} }

View File

@ -0,0 +1,235 @@
#include "icsneo/icsneocpp.h"
#include "icsneo/communication/encoder.h"
#include "icsneo/communication/packet/a2bpacket.h"
#include "icsneo/communication/packetizer.h"
#include "icsneo/api/eventmanager.h"
#include "gtest/gtest.h"
#include <vector>
using namespace icsneo;
class A2BEncoderDecoderTest : public ::testing::Test {
protected:
void SetUp() override {
report = [](APIEvent::Type, APIEvent::Severity) {
// Unless caught by the test, the packetizer should not throw errors
EXPECT_TRUE(false);
};
packetizer.emplace([this](APIEvent::Type t, APIEvent::Severity s) {
report(t, s);
});
packetEncoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) {
report(t, s);
});
packetDecoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) {
report(t, s);
});
A2BPCMSample initialSample = 10 << 8;
constructTest(
msg1,
msg1Encoded,
{1,1,0,0,0,0},
1,
true,
false,
false,
false,
false,
0,
3,
initialSample
);
initialSample = (29 << 16) | (10 << 8);
constructTest(
msg2,
msg2Encoded,
{1,0,9,0,0,0},
1,
false,
true,
false,
false,
true,
0,
1,
initialSample
);
initialSample = (29 << 16) | (10 << 8);
constructTest(
msg3,
msg3Encoded,
{2,0,6,0,0xAA,0xFF},
2,
false,
false,
true,
true,
false,
0xFFAA,
3,
initialSample
);
constructTest(
msg4,
msg4Encoded,
{4,0,6,0,0xAA,0xFF},
4,
false,
false,
true,
true,
false,
0xFFAA,
0,
initialSample
);
}
void constructTest(
std::shared_ptr<A2BMessage>& testMsg,
std::vector<uint8_t>& testMsgEncoded,
std::array<uint8_t, 6> encodedHeader,
uint8_t numChannels,
bool channelSize16,
bool monitor,
bool txmsg,
bool errIndicator,
bool syncFrame,
uint16_t rfu2,
uint32_t numFrames,
A2BPCMSample initialSample
)
{
testMsg = std::make_shared<A2BMessage>(
(uint8_t)(channelSize16 ? 16 : 24),
(uint8_t)(channelSize16 ? 2 : 4),
numChannels
);
auto addSample = [&](
uint8_t channel,
A2BMessage::A2BDirection dir
) {
testMsg->addSample(std::move(initialSample), dir, channel);
for(size_t i = 0; i < static_cast<size_t>(testMsg->getBytesPerSample()); i++) {
testMsgEncoded.push_back(static_cast<uint8_t>(initialSample >> (i * 8)));
}
initialSample++;
};
testMsg->network = Network(Network::NetID::A2B1);
testMsg->channelSize16 = channelSize16;
testMsg->monitor = monitor;
testMsg->txmsg = txmsg;
testMsg->errIndicator = errIndicator;
testMsg->syncFrame = syncFrame;
testMsg->rfu2 = rfu2;
testMsgEncoded.reserve(
HardwareA2BPacket::coreMiniMessageHeaderSize + numFrames * static_cast<size_t>(testMsg->getBytesPerSample())
);
testMsgEncoded.insert(
testMsgEncoded.end(),
encodedHeader.begin(),
encodedHeader.end()
); // Insert header
appendCoreMiniHeaderOffset(testMsgEncoded);
for(unsigned int frame = 0; frame < numFrames; frame++) {
for(uint8_t channel = 0; channel < testMsg->getNumChannels(); channel++) {
addSample(channel, A2BMessage::A2BDirection::DownStream);
addSample(channel, A2BMessage::A2BDirection::UpStream);
}
}
}
void appendCoreMiniHeaderOffset(std::vector<uint8_t>& buf) {
for(
size_t i = 0;
i < (HardwareA2BPacket::coreMiniMessageHeaderSize - HardwareA2BPacket::a2bHeaderSize);
i++
) {
buf.push_back(0);
}
}
void decrementEncodedMessageSize(std::vector<uint8_t>& bytestream) {
if(bytestream.size() < 6)
return;
uint16_t size = (bytestream[3] << 8) | bytestream[2];
if(size == 0)
return;
size--;
bytestream[2] = (uint8_t)size;
bytestream[3] = (uint8_t)(size >> 8);
}
void testMessage(std::shared_ptr<A2BMessage>& msg, std::vector<uint8_t>& msgEncoded) {
std::vector<uint8_t> bytestream;
std::vector<uint8_t> rawPacketBytes;
std::shared_ptr<Message> decodeMsg;
std::shared_ptr<A2BMessage> decodeA2BMsg;
EXPECT_TRUE(packetEncoder->encode(*packetizer, bytestream, msg));
rawPacketBytes = std::vector<uint8_t>(bytestream.begin() + 6, bytestream.end());
EXPECT_EQ(rawPacketBytes, msgEncoded);
decrementEncodedMessageSize(bytestream);
EXPECT_TRUE(packetizer->input(bytestream));
auto packets = packetizer->output();
if(packets.empty()) { EXPECT_TRUE(false);}
EXPECT_TRUE(packetDecoder->decode(decodeMsg, packets.back()));
decodeA2BMsg = std::static_pointer_cast<A2BMessage>(decodeMsg);
EXPECT_EQ(*msg, *decodeA2BMsg);
}
device_eventhandler_t report;
std::optional<Encoder> packetEncoder;
std::optional<Packetizer> packetizer;
std::optional<Decoder> packetDecoder;
std::shared_ptr<A2BMessage> msg1;
std::vector<uint8_t> msg1Encoded;
std::shared_ptr<A2BMessage> msg2;
std::vector<uint8_t> msg2Encoded;
std::shared_ptr<A2BMessage> msg3;
std::vector<uint8_t> msg3Encoded;
std::shared_ptr<A2BMessage> msg4;
std::vector<uint8_t> msg4Encoded;
};
TEST_F(A2BEncoderDecoderTest, ChannelSize16SingleChannel) {
testMessage(msg1, msg1Encoded);
}
TEST_F(A2BEncoderDecoderTest, ChannelSize24MultiChannel) {
testMessage(msg2, msg2Encoded);
}
TEST_F(A2BEncoderDecoderTest, ChannelSize24MultiChannelMultiFrame) {
testMessage(msg3, msg3Encoded);
}
TEST_F(A2BEncoderDecoderTest, NoFrames) {
testMessage(msg4, msg4Encoded);
}