The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

508 lines
24KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce::midi_ci::detail
  19. {
  20. Parser::Status Responder::processCompleteMessage (BufferOutput& output,
  21. ump::BytesOnGroup message,
  22. Span<ResponderDelegate* const> listeners)
  23. {
  24. auto status = Parser::Status::noError;
  25. const auto parsed = Parser::parse (output.getMuid(), message.bytes, &status);
  26. if (! parsed.has_value())
  27. return Parser::Status::malformed;
  28. class Output : public ResponderOutput
  29. {
  30. public:
  31. Output (BufferOutput& o, Message::Header h, uint8_t g)
  32. : innerOutput (o), header (h), group (g) {}
  33. MUID getMuid() const override { return innerOutput.getMuid(); }
  34. Message::Header getIncomingHeader() const override { return header; }
  35. uint8_t getIncomingGroup() const override { return group; }
  36. std::vector<std::byte>& getOutputBuffer() override { return innerOutput.getOutputBuffer(); }
  37. void send (uint8_t g) override { innerOutput.send (g); }
  38. private:
  39. BufferOutput& innerOutput;
  40. Message::Header header;
  41. uint8_t group{};
  42. };
  43. Output responderOutput { output, parsed->header, message.group };
  44. if (status != Parser::Status::noError)
  45. {
  46. switch (status)
  47. {
  48. case Parser::Status::collidingMUID:
  49. {
  50. const Message::Header header { ChannelInGroup::wholeBlock,
  51. MessageMeta::Meta<Message::InvalidateMUID>::subID2,
  52. MessageMeta::implementationVersion,
  53. output.getMuid(),
  54. MUID::getBroadcast() };
  55. const Message::InvalidateMUID body { output.getMuid() };
  56. MessageTypeUtils::send (responderOutput, responderOutput.getIncomingGroup(), header, body);
  57. break;
  58. }
  59. case Parser::Status::unrecognisedMessage:
  60. MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x01 });
  61. break;
  62. case Parser::Status::reservedVersion:
  63. MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x02 });
  64. break;
  65. case Parser::Status::malformed:
  66. MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x41 });
  67. break;
  68. case Parser::Status::mismatchedMUID:
  69. case Parser::Status::noError:
  70. break;
  71. }
  72. return status;
  73. }
  74. for (auto* listener : listeners)
  75. if (listener != nullptr && listener->tryRespond (responderOutput, *parsed))
  76. return Parser::Status::noError;
  77. MessageTypeUtils::BaseCaseDelegate base;
  78. if (base.tryRespond (responderOutput, *parsed))
  79. return Parser::Status::noError;
  80. return Parser::Status::unrecognisedMessage;
  81. }
  82. //==============================================================================
  83. //==============================================================================
  84. #if JUCE_UNIT_TESTS
  85. class ResponderTests : public UnitTest
  86. {
  87. public:
  88. ResponderTests() : UnitTest ("Responder", UnitTestCategories::midi) {}
  89. void runTest() override
  90. {
  91. auto random = getRandom();
  92. std::vector<std::byte> outgoing;
  93. const auto makeOutput = [&]
  94. {
  95. struct Output : public BufferOutput
  96. {
  97. Output (Random& r, std::vector<std::byte>& b)
  98. : muid (MUID::makeRandom (r)), buf (b) {}
  99. MUID getMuid() const override { return muid; }
  100. std::vector<std::byte>& getOutputBuffer() override { return buf; }
  101. void send (uint8_t) override { sent.push_back (buf); }
  102. MUID muid;
  103. std::vector<std::byte>& buf;
  104. std::vector<std::vector<std::byte>> sent;
  105. };
  106. return Output { random, outgoing };
  107. };
  108. beginTest ("An endpoint message with a matching MUID provokes an endpoint response");
  109. {
  110. constexpr auto version = MessageMeta::implementationVersion;
  111. auto output = makeOutput();
  112. const auto initialMUID = output.getMuid();
  113. const auto bytes = makeByteArray (0x7e,
  114. /* to function block */ 0x7f,
  115. /* midi CI */ 0x0d,
  116. /* endpoint message */ 0x72,
  117. /* version */ version,
  118. /* source MUID */ 0x01,
  119. /* ... */ 0x02,
  120. /* ... */ 0x03,
  121. /* ... */ 0x04,
  122. /* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
  123. /* ... */ (initialMUID.get() >> 0x07) & 0x7f,
  124. /* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
  125. /* ... */ (initialMUID.get() >> 0x15) & 0x7f,
  126. /* status, product instance ID */ 0x00);
  127. const Message::Parsed expectedInput { Message::Header { ChannelInGroup::wholeBlock,
  128. std::byte { 0x72 },
  129. version,
  130. MUID::makeUnchecked (0x80c101),
  131. initialMUID },
  132. Message::EndpointInquiry { std::byte { 0x00 } } };
  133. EndpointResponderListener listener;
  134. processCompleteMessage (output, { 0, bytes }, listener);
  135. expect (listener == SilentResponderListener (expectedInput));
  136. const auto expectedOutputBytes = makeByteArray (0x7e,
  137. /* to function block */ 0x7f,
  138. /* midi CI */ 0x0d,
  139. /* endpoint reply */ 0x73,
  140. /* version */ version,
  141. /* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
  142. /* ... */ (initialMUID.get() >> 0x07) & 0x7f,
  143. /* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
  144. /* ... */ (initialMUID.get() >> 0x15) & 0x7f,
  145. /* destination MUID */ 0x01,
  146. /* ... */ 0x02,
  147. /* ... */ 0x03,
  148. /* ... */ 0x04,
  149. /* status */ 0x00,
  150. /* 16-bit length of following data */ 0x04,
  151. /* ... */ 0x00,
  152. /* info */ 0x00,
  153. /* ... */ 0x00,
  154. /* ... */ 0x00,
  155. /* ... */ 0x00);
  156. expect (rangesEqual (output.sent.front(), expectedOutputBytes));
  157. }
  158. beginTest ("An endpoint message directed at a different MUID does not provoke a response");
  159. {
  160. const auto destMUID = MUID::makeRandom (random);
  161. constexpr auto version = MessageMeta::implementationVersion;
  162. const auto bytes = makeByteArray (0x7e,
  163. /* to function block */ 0x7f,
  164. /* midi CI */ 0x0d,
  165. /* endpoint message */ 0x72,
  166. /* version */ version,
  167. /* source MUID */ 0x01,
  168. /* ... */ 0x02,
  169. /* ... */ 0x03,
  170. /* ... */ 0x04,
  171. /* destination MUID */ (destMUID.get() >> 0x00) & 0x7f,
  172. /* ... */ (destMUID.get() >> 0x07) & 0x7f,
  173. /* ... */ (destMUID.get() >> 0x0e) & 0x7f,
  174. /* ... */ (destMUID.get() >> 0x15) & 0x7f,
  175. /* status, product instance ID */ 0x00);
  176. auto output = makeOutput();
  177. EndpointResponderListener listener;
  178. processCompleteMessage (output, { 0, bytes }, listener);
  179. expect (listener == SilentResponderListener());
  180. expect (output.sent.empty());
  181. }
  182. beginTest ("If the listener fails to compose an endpoint response, a NAK is emitted");
  183. {
  184. auto output = makeOutput();
  185. const auto initialMUID = output.getMuid();
  186. SilentResponderListener listener;
  187. constexpr auto version = MessageMeta::implementationVersion;
  188. const auto bytes = makeByteArray (0x7e,
  189. /* to function block */ 0x7f,
  190. /* midi CI */ 0x0d,
  191. /* endpoint message */ 0x72,
  192. /* version */ version,
  193. /* source MUID */ 0x01,
  194. /* ... */ 0x02,
  195. /* ... */ 0x03,
  196. /* ... */ 0x04,
  197. /* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
  198. /* ... */ (initialMUID.get() >> 0x07) & 0x7f,
  199. /* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
  200. /* ... */ (initialMUID.get() >> 0x15) & 0x7f,
  201. /* status, product instance ID */ 0x00);
  202. processCompleteMessage (output, { 0, bytes }, listener);
  203. const auto expectedOutputBytes = makeByteArray (0x7e,
  204. /* to function block */ 0x7f,
  205. /* midi CI */ 0x0d,
  206. /* nak */ 0x7f,
  207. /* version */ version,
  208. /* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
  209. /* ... */ (initialMUID.get() >> 0x07) & 0x7f,
  210. /* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
  211. /* ... */ (initialMUID.get() >> 0x15) & 0x7f,
  212. /* destination MUID */ 0x01,
  213. /* ... */ 0x02,
  214. /* ... */ 0x03,
  215. /* ... */ 0x04,
  216. /* original transaction sub-id #2 */ 0x72,
  217. /* nak status code */ 0x00,
  218. /* nak status data */ 0x00,
  219. /* details */ 0x00,
  220. /* ... */ 0x00,
  221. /* ... */ 0x00,
  222. /* ... */ 0x00,
  223. /* ... */ 0x00,
  224. /* message text length */ 0x00,
  225. /* ... */ 0x00);
  226. expect (rangesEqual (output.sent.front(), expectedOutputBytes));
  227. }
  228. beginTest ("If a message is sent with reserved bits set in the Message Format Version, a NAK is emitted");
  229. {
  230. auto output = makeOutput();
  231. const auto initialMUID = output.getMuid();
  232. const auto bytes = makeByteArray (0x7e,
  233. /* to function block */ 0x7f,
  234. /* midi CI */ 0x0d,
  235. /* endpoint message */ 0x72,
  236. /* version, reserved bit set */ 0x12,
  237. /* source MUID */ 0x01,
  238. /* ... */ 0x02,
  239. /* ... */ 0x03,
  240. /* ... */ 0x04,
  241. /* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
  242. /* ... */ (initialMUID.get() >> 0x07) & 0x7f,
  243. /* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
  244. /* ... */ (initialMUID.get() >> 0x15) & 0x7f,
  245. /* status, product instance ID */ 0x00);
  246. SilentResponderListener listener;
  247. processCompleteMessage (output, { 0, bytes }, listener);
  248. expect (listener == SilentResponderListener{});
  249. const auto expectedOutputBytes = makeByteArray (0x7e,
  250. /* to function block */ 0x7f,
  251. /* midi CI */ 0x0d,
  252. /* nak */ 0x7f,
  253. /* version */ MessageMeta::implementationVersion,
  254. /* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
  255. /* ... */ (initialMUID.get() >> 0x07) & 0x7f,
  256. /* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
  257. /* ... */ (initialMUID.get() >> 0x15) & 0x7f,
  258. /* destination MUID */ 0x01,
  259. /* ... */ 0x02,
  260. /* ... */ 0x03,
  261. /* ... */ 0x04,
  262. /* original transaction sub-id #2 */ 0x72,
  263. /* nak status code */ 0x02,
  264. /* nak status data */ 0x00,
  265. /* details */ 0x00,
  266. /* ... */ 0x00,
  267. /* ... */ 0x00,
  268. /* ... */ 0x00,
  269. /* ... */ 0x00,
  270. /* message text length */ 0x00,
  271. /* ... */ 0x00);
  272. expect (rangesEqual (output.sent.front(), expectedOutputBytes));
  273. }
  274. beginTest ("If the message body is malformed, a NAK with a status of 0x41 is emitted");
  275. {
  276. const auto sourceMUID = MUID::makeRandom (random);
  277. Message::Header header;
  278. header.deviceID = ChannelInGroup::wholeBlock;
  279. header.category = std::byte { 0x7e };
  280. header.version = MessageMeta::implementationVersion;
  281. header.source = sourceMUID;
  282. header.destination = MUID::getBroadcast();
  283. Message::InvalidateMUID invalidate;
  284. invalidate.target = MUID::makeRandom (random);
  285. std::vector<std::byte> message;
  286. Marshalling::Writer { message } (header, invalidate);
  287. // Remove a byte from the end of the message
  288. message.pop_back();
  289. auto output = makeOutput();
  290. const auto ourMUID = output.getMuid();
  291. SilentResponderListener listener;
  292. processCompleteMessage (output, { 0, message }, listener);
  293. const auto expectedOutputBytes = makeByteArray (0x7e,
  294. /* to function block */ 0x7f,
  295. /* midi CI */ 0x0d,
  296. /* nak */ 0x7f,
  297. /* version */ MessageMeta::implementationVersion,
  298. /* source MUID */ (ourMUID.get() >> 0x00) & 0x7f,
  299. /* ... */ (ourMUID.get() >> 0x07) & 0x7f,
  300. /* ... */ (ourMUID.get() >> 0x0e) & 0x7f,
  301. /* ... */ (ourMUID.get() >> 0x15) & 0x7f,
  302. /* destination MUID */ (sourceMUID.get() >> 0x00) & 0x7f,
  303. /* ... */ (sourceMUID.get() >> 0x07) & 0x7f,
  304. /* ... */ (sourceMUID.get() >> 0x0e) & 0x7f,
  305. /* ... */ (sourceMUID.get() >> 0x15) & 0x7f,
  306. /* original transaction sub-id #2 */ 0x7e,
  307. /* nak status code */ 0x41,
  308. /* nak status data */ 0x00,
  309. /* details */ 0x00,
  310. /* ... */ 0x00,
  311. /* ... */ 0x00,
  312. /* ... */ 0x00,
  313. /* ... */ 0x00,
  314. /* message text length */ 0x00,
  315. /* ... */ 0x00);
  316. expect (rangesEqual (output.sent.front(), expectedOutputBytes));
  317. }
  318. beginTest ("If an unrecognised message is received, a NAK with a status of 0x01 is emitted");
  319. {
  320. const auto sourceMUID = MUID::makeRandom (random);
  321. Message::Header header;
  322. header.deviceID = ChannelInGroup::wholeBlock;
  323. header.category = std::byte { 0x50 }; // reserved
  324. header.version = MessageMeta::implementationVersion;
  325. header.source = sourceMUID;
  326. header.destination = MUID::getBroadcast();
  327. std::vector<std::byte> message;
  328. Marshalling::Writer { message } (header);
  329. message.emplace_back();
  330. auto output = makeOutput();
  331. const auto ourMUID = output.getMuid();
  332. SilentResponderListener listener;
  333. processCompleteMessage (output, { 0, message }, listener);
  334. const auto expectedOutputBytes = makeByteArray (0x7e,
  335. /* to function block */ 0x7f,
  336. /* midi CI */ 0x0d,
  337. /* nak */ 0x7f,
  338. /* version */ MessageMeta::implementationVersion,
  339. /* source MUID */ (ourMUID.get() >> 0x00) & 0x7f,
  340. /* ... */ (ourMUID.get() >> 0x07) & 0x7f,
  341. /* ... */ (ourMUID.get() >> 0x0e) & 0x7f,
  342. /* ... */ (ourMUID.get() >> 0x15) & 0x7f,
  343. /* destination MUID */ (sourceMUID.get() >> 0x00) & 0x7f,
  344. /* ... */ (sourceMUID.get() >> 0x07) & 0x7f,
  345. /* ... */ (sourceMUID.get() >> 0x0e) & 0x7f,
  346. /* ... */ (sourceMUID.get() >> 0x15) & 0x7f,
  347. /* original transaction sub-id #2 */ 0x50,
  348. /* nak status code */ 0x01,
  349. /* nak status data */ 0x00,
  350. /* details */ 0x00,
  351. /* ... */ 0x00,
  352. /* ... */ 0x00,
  353. /* ... */ 0x00,
  354. /* ... */ 0x00,
  355. /* message text length */ 0x00,
  356. /* ... */ 0x00);
  357. expect (rangesEqual (output.sent.front(), expectedOutputBytes));
  358. }
  359. }
  360. private:
  361. template <typename... Ts>
  362. static std::array<std::byte, sizeof... (Ts)> makeByteArray (Ts&&... ts)
  363. {
  364. jassert (((0 <= (int) ts && (int) ts <= std::numeric_limits<uint8_t>::max()) && ...));
  365. return { std::byte (ts)... };
  366. }
  367. struct SilentResponderListener : public ResponderDelegate
  368. {
  369. SilentResponderListener() = default;
  370. explicit SilentResponderListener (const Message::Parsed& p) : parsed (p) {}
  371. bool tryRespond (ResponderOutput&, const Message::Parsed& p) override
  372. {
  373. parsed = p;
  374. return false;
  375. }
  376. // Returning false indicates that the message was not handled
  377. bool operator== (const SilentResponderListener& other) const { return parsed == other.parsed; }
  378. bool operator!= (const SilentResponderListener& other) const { return ! operator== (other); }
  379. std::optional<Message::Parsed> parsed;
  380. };
  381. struct EndpointResponderListener : public SilentResponderListener
  382. {
  383. bool tryRespond (ResponderOutput& output, const Message::Parsed& message) override
  384. {
  385. parsed = message;
  386. if (std::holds_alternative<Message::EndpointInquiry> (message.body))
  387. {
  388. std::array<std::byte, 4> data{};
  389. Message::EndpointInquiryResponse response;
  390. response.status = std::byte{};
  391. response.data = data;
  392. MessageTypeUtils::send (output, output.getIncomingGroup(), output.getReplyHeader (std::byte { 0x73 }), response);
  393. return true;
  394. }
  395. return SilentResponderListener::tryRespond (output, message);
  396. }
  397. using SilentResponderListener::operator==, SilentResponderListener::operator!=;
  398. };
  399. struct OutputCallback
  400. {
  401. void operator() (Span<const std::byte> bytes)
  402. {
  403. output = std::vector<std::byte> (bytes.begin(), bytes.end());
  404. }
  405. std::vector<std::byte> output;
  406. };
  407. template <typename A, typename B>
  408. static bool rangesEqual (A&& a, B&& b)
  409. {
  410. using std::begin, std::end;
  411. return std::equal (begin (a), end (a), begin (b), end (b));
  412. }
  413. static Parser::Status processCompleteMessage (BufferOutput& output,
  414. ump::BytesOnGroup message,
  415. ResponderDelegate& listener)
  416. {
  417. ResponderDelegate* const listeners[] { &listener };
  418. return Responder::processCompleteMessage (output, message, listeners);
  419. }
  420. };
  421. static ResponderTests responderTests;
  422. #endif
  423. } // namespace juce::midi_ci::detail