1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/modules/juce_midi_ci/detail/juce_CIResponder.cpp
2023-10-31 11:46:49 +00:00

507 lines
24 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce::midi_ci::detail
{
Parser::Status Responder::processCompleteMessage (BufferOutput& output,
ump::BytesOnGroup message,
Span<ResponderDelegate* const> listeners)
{
auto status = Parser::Status::noError;
const auto parsed = Parser::parse (output.getMuid(), message.bytes, &status);
if (! parsed.has_value())
return Parser::Status::malformed;
class Output : public ResponderOutput
{
public:
Output (BufferOutput& o, Message::Header h, uint8_t g)
: innerOutput (o), header (h), group (g) {}
MUID getMuid() const override { return innerOutput.getMuid(); }
Message::Header getIncomingHeader() const override { return header; }
uint8_t getIncomingGroup() const override { return group; }
std::vector<std::byte>& getOutputBuffer() override { return innerOutput.getOutputBuffer(); }
void send (uint8_t g) override { innerOutput.send (g); }
private:
BufferOutput& innerOutput;
Message::Header header;
uint8_t group{};
};
Output responderOutput { output, parsed->header, message.group };
if (status != Parser::Status::noError)
{
switch (status)
{
case Parser::Status::collidingMUID:
{
const Message::Header header { ChannelInGroup::wholeBlock,
MessageMeta::Meta<Message::InvalidateMUID>::subID2,
MessageMeta::implementationVersion,
output.getMuid(),
MUID::getBroadcast() };
const Message::InvalidateMUID body { output.getMuid() };
MessageTypeUtils::send (responderOutput, responderOutput.getIncomingGroup(), header, body);
break;
}
case Parser::Status::unrecognisedMessage:
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x01 });
break;
case Parser::Status::reservedVersion:
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x02 });
break;
case Parser::Status::malformed:
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x41 });
break;
case Parser::Status::mismatchedMUID:
case Parser::Status::noError:
break;
}
return status;
}
for (auto* listener : listeners)
if (listener != nullptr && listener->tryRespond (responderOutput, *parsed))
return Parser::Status::noError;
MessageTypeUtils::BaseCaseDelegate base;
if (base.tryRespond (responderOutput, *parsed))
return Parser::Status::noError;
return Parser::Status::unrecognisedMessage;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class ResponderTests : public UnitTest
{
public:
ResponderTests() : UnitTest ("Responder", UnitTestCategories::midi) {}
void runTest() override
{
auto random = getRandom();
std::vector<std::byte> outgoing;
const auto makeOutput = [&]
{
struct Output : public BufferOutput
{
Output (Random& r, std::vector<std::byte>& b)
: muid (MUID::makeRandom (r)), buf (b) {}
MUID getMuid() const override { return muid; }
std::vector<std::byte>& getOutputBuffer() override { return buf; }
void send (uint8_t) override { sent.push_back (buf); }
MUID muid;
std::vector<std::byte>& buf;
std::vector<std::vector<std::byte>> sent;
};
return Output { random, outgoing };
};
beginTest ("An endpoint message with a matching MUID provokes an endpoint response");
{
constexpr auto version = MessageMeta::implementationVersion;
auto output = makeOutput();
const auto initialMUID = output.getMuid();
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint message */ 0x72,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* status, product instance ID */ 0x00);
const Message::Parsed expectedInput { Message::Header { ChannelInGroup::wholeBlock,
std::byte { 0x72 },
version,
MUID::makeUnchecked (0x80c101),
initialMUID },
Message::EndpointInquiry { std::byte { 0x00 } } };
EndpointResponderListener listener;
processCompleteMessage (output, { 0, bytes }, listener);
expect (listener == SilentResponderListener (expectedInput));
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint reply */ 0x73,
/* version */ version,
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* status */ 0x00,
/* 16-bit length of following data */ 0x04,
/* ... */ 0x00,
/* info */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
beginTest ("An endpoint message directed at a different MUID does not provoke a response");
{
const auto destMUID = MUID::makeRandom (random);
constexpr auto version = MessageMeta::implementationVersion;
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint message */ 0x72,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* destination MUID */ (destMUID.get() >> 0x00) & 0x7f,
/* ... */ (destMUID.get() >> 0x07) & 0x7f,
/* ... */ (destMUID.get() >> 0x0e) & 0x7f,
/* ... */ (destMUID.get() >> 0x15) & 0x7f,
/* status, product instance ID */ 0x00);
auto output = makeOutput();
EndpointResponderListener listener;
processCompleteMessage (output, { 0, bytes }, listener);
expect (listener == SilentResponderListener());
expect (output.sent.empty());
}
beginTest ("If the listener fails to compose an endpoint response, a NAK is emitted");
{
auto output = makeOutput();
const auto initialMUID = output.getMuid();
SilentResponderListener listener;
constexpr auto version = MessageMeta::implementationVersion;
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint message */ 0x72,
/* version */ version,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* status, product instance ID */ 0x00);
processCompleteMessage (output, { 0, bytes }, listener);
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* nak */ 0x7f,
/* version */ version,
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* original transaction sub-id #2 */ 0x72,
/* nak status code */ 0x00,
/* nak status data */ 0x00,
/* details */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* message text length */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
beginTest ("If a message is sent with reserved bits set in the Message Format Version, a NAK is emitted");
{
auto output = makeOutput();
const auto initialMUID = output.getMuid();
const auto bytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* endpoint message */ 0x72,
/* version, reserved bit set */ 0x12,
/* source MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* status, product instance ID */ 0x00);
SilentResponderListener listener;
processCompleteMessage (output, { 0, bytes }, listener);
expect (listener == SilentResponderListener{});
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* nak */ 0x7f,
/* version */ MessageMeta::implementationVersion,
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ 0x01,
/* ... */ 0x02,
/* ... */ 0x03,
/* ... */ 0x04,
/* original transaction sub-id #2 */ 0x72,
/* nak status code */ 0x02,
/* nak status data */ 0x00,
/* details */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* message text length */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
beginTest ("If the message body is malformed, a NAK with a status of 0x41 is emitted");
{
const auto sourceMUID = MUID::makeRandom (random);
Message::Header header;
header.deviceID = ChannelInGroup::wholeBlock;
header.category = std::byte { 0x7e };
header.version = MessageMeta::implementationVersion;
header.source = sourceMUID;
header.destination = MUID::getBroadcast();
Message::InvalidateMUID invalidate;
invalidate.target = MUID::makeRandom (random);
std::vector<std::byte> message;
Marshalling::Writer { message } (header, invalidate);
// Remove a byte from the end of the message
message.pop_back();
auto output = makeOutput();
const auto ourMUID = output.getMuid();
SilentResponderListener listener;
processCompleteMessage (output, { 0, message }, listener);
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* nak */ 0x7f,
/* version */ MessageMeta::implementationVersion,
/* source MUID */ (ourMUID.get() >> 0x00) & 0x7f,
/* ... */ (ourMUID.get() >> 0x07) & 0x7f,
/* ... */ (ourMUID.get() >> 0x0e) & 0x7f,
/* ... */ (ourMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ (sourceMUID.get() >> 0x00) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x07) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x0e) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x15) & 0x7f,
/* original transaction sub-id #2 */ 0x7e,
/* nak status code */ 0x41,
/* nak status data */ 0x00,
/* details */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* message text length */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
beginTest ("If an unrecognised message is received, a NAK with a status of 0x01 is emitted");
{
const auto sourceMUID = MUID::makeRandom (random);
Message::Header header;
header.deviceID = ChannelInGroup::wholeBlock;
header.category = std::byte { 0x50 }; // reserved
header.version = MessageMeta::implementationVersion;
header.source = sourceMUID;
header.destination = MUID::getBroadcast();
std::vector<std::byte> message;
Marshalling::Writer { message } (header);
message.emplace_back();
auto output = makeOutput();
const auto ourMUID = output.getMuid();
SilentResponderListener listener;
processCompleteMessage (output, { 0, message }, listener);
const auto expectedOutputBytes = makeByteArray (0x7e,
/* to function block */ 0x7f,
/* midi CI */ 0x0d,
/* nak */ 0x7f,
/* version */ MessageMeta::implementationVersion,
/* source MUID */ (ourMUID.get() >> 0x00) & 0x7f,
/* ... */ (ourMUID.get() >> 0x07) & 0x7f,
/* ... */ (ourMUID.get() >> 0x0e) & 0x7f,
/* ... */ (ourMUID.get() >> 0x15) & 0x7f,
/* destination MUID */ (sourceMUID.get() >> 0x00) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x07) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x0e) & 0x7f,
/* ... */ (sourceMUID.get() >> 0x15) & 0x7f,
/* original transaction sub-id #2 */ 0x50,
/* nak status code */ 0x01,
/* nak status data */ 0x00,
/* details */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* ... */ 0x00,
/* message text length */ 0x00,
/* ... */ 0x00);
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
}
}
private:
template <typename... Ts>
static std::array<std::byte, sizeof... (Ts)> makeByteArray (Ts&&... ts)
{
jassert (((0 <= (int) ts && (int) ts <= std::numeric_limits<uint8_t>::max()) && ...));
return { std::byte (ts)... };
}
struct SilentResponderListener : public ResponderDelegate
{
SilentResponderListener() = default;
explicit SilentResponderListener (const Message::Parsed& p) : parsed (p) {}
bool tryRespond (ResponderOutput&, const Message::Parsed& p) override
{
parsed = p;
return false;
}
// Returning false indicates that the message was not handled
bool operator== (const SilentResponderListener& other) const { return parsed == other.parsed; }
bool operator!= (const SilentResponderListener& other) const { return ! operator== (other); }
std::optional<Message::Parsed> parsed;
};
struct EndpointResponderListener : public SilentResponderListener
{
bool tryRespond (ResponderOutput& output, const Message::Parsed& message) override
{
parsed = message;
if (std::holds_alternative<Message::EndpointInquiry> (message.body))
{
std::array<std::byte, 4> data{};
Message::EndpointInquiryResponse response;
response.status = std::byte{};
response.data = data;
MessageTypeUtils::send (output, output.getIncomingGroup(), output.getReplyHeader (std::byte { 0x73 }), response);
return true;
}
return SilentResponderListener::tryRespond (output, message);
}
using SilentResponderListener::operator==, SilentResponderListener::operator!=;
};
struct OutputCallback
{
void operator() (Span<const std::byte> bytes)
{
output = std::vector<std::byte> (bytes.begin(), bytes.end());
}
std::vector<std::byte> output;
};
template <typename A, typename B>
static bool rangesEqual (A&& a, B&& b)
{
using std::begin, std::end;
return std::equal (begin (a), end (a), begin (b), end (b));
}
static Parser::Status processCompleteMessage (BufferOutput& output,
ump::BytesOnGroup message,
ResponderDelegate& listener)
{
ResponderDelegate* const listeners[] { &listener };
return Responder::processCompleteMessage (output, message, listeners);
}
};
static ResponderTests responderTests;
#endif
} // namespace juce::midi_ci::detail