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.

449 lines
26KB

  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
  19. {
  20. std::optional<Message::Parsed> Parser::parse (Span<const std::byte> message, Status* status)
  21. {
  22. const auto setStatus = [&] (Status s)
  23. {
  24. if (status != nullptr)
  25. *status = s;
  26. };
  27. setStatus (Status::noError);
  28. Message::Generic generic;
  29. if (! detail::Marshalling::Reader { message } (generic))
  30. {
  31. // Got a full sysex message, but it didn't contain a well-formed header.
  32. setStatus (Status::malformed);
  33. return {};
  34. }
  35. if ((generic.header.version & std::byte { 0x70 }) != std::byte{})
  36. {
  37. setStatus (Status::reservedVersion);
  38. return Message::Parsed { generic.header, std::monostate{} };
  39. }
  40. const auto index = (uint8_t) generic.header.category;
  41. constexpr auto tables = detail::MessageTypeUtils::getTables();
  42. const auto processFunction = tables.parsers[index];
  43. return Message::Parsed { generic.header, processFunction (generic, status) };
  44. }
  45. std::optional<Message::Parsed> Parser::parse (const MUID ourMUID,
  46. Span<const std::byte> message,
  47. Status* status)
  48. {
  49. const auto setStatus = [&] (Status s)
  50. {
  51. if (status != nullptr)
  52. *status = s;
  53. };
  54. setStatus (Status::noError);
  55. if (const auto parsed = parse (message, status))
  56. {
  57. if (parsed->header.destination != MUID::getBroadcast() && parsed->header.destination != ourMUID)
  58. setStatus (Status::mismatchedMUID);
  59. else if (parsed->header.source == ourMUID)
  60. setStatus (Status::collidingMUID);
  61. else if ((parsed->header.version & std::byte { 0x70 }) != std::byte{})
  62. setStatus (Status::reservedVersion);
  63. return parsed;
  64. }
  65. return {};
  66. }
  67. class DescriptionVisitor : public detail::MessageTypeUtils::MessageVisitor
  68. {
  69. public:
  70. DescriptionVisitor (const Message::Parsed* m, String* str) : msg (m), result (str) {}
  71. void visit (const std::monostate&) const override { *result = "!! Unrecognised !!"; }
  72. void visit (const Message::Discovery& body) const override { visitImpl (body); }
  73. void visit (const Message::DiscoveryResponse& body) const override { visitImpl (body); }
  74. void visit (const Message::InvalidateMUID& body) const override { visitImpl (body); }
  75. void visit (const Message::EndpointInquiry& body) const override { visitImpl (body); }
  76. void visit (const Message::EndpointInquiryResponse& body) const override { visitImpl (body); }
  77. void visit (const Message::ACK& body) const override { visitImpl (body); }
  78. void visit (const Message::NAK& body) const override { visitImpl (body); }
  79. void visit (const Message::ProfileInquiry& body) const override { visitImpl (body); }
  80. void visit (const Message::ProfileInquiryResponse& body) const override { visitImpl (body); }
  81. void visit (const Message::ProfileAdded& body) const override { visitImpl (body); }
  82. void visit (const Message::ProfileRemoved& body) const override { visitImpl (body); }
  83. void visit (const Message::ProfileDetails& body) const override { visitImpl (body); }
  84. void visit (const Message::ProfileDetailsResponse& body) const override { visitImpl (body); }
  85. void visit (const Message::ProfileOn& body) const override { visitImpl (body); }
  86. void visit (const Message::ProfileOff& body) const override { visitImpl (body); }
  87. void visit (const Message::ProfileEnabledReport& body) const override { visitImpl (body); }
  88. void visit (const Message::ProfileDisabledReport& body) const override { visitImpl (body); }
  89. void visit (const Message::ProfileSpecificData& body) const override { visitImpl (body); }
  90. void visit (const Message::PropertyExchangeCapabilities& body) const override { visitImpl (body); }
  91. void visit (const Message::PropertyExchangeCapabilitiesResponse& body) const override { visitImpl (body); }
  92. void visit (const Message::PropertyGetData& body) const override { visitImpl (body); }
  93. void visit (const Message::PropertyGetDataResponse& body) const override { visitImpl (body); }
  94. void visit (const Message::PropertySetData& body) const override { visitImpl (body); }
  95. void visit (const Message::PropertySetDataResponse& body) const override { visitImpl (body); }
  96. void visit (const Message::PropertySubscribe& body) const override { visitImpl (body); }
  97. void visit (const Message::PropertySubscribeResponse& body) const override { visitImpl (body); }
  98. void visit (const Message::PropertyNotify& body) const override { visitImpl (body); }
  99. void visit (const Message::ProcessInquiry& body) const override { visitImpl (body); }
  100. void visit (const Message::ProcessInquiryResponse& body) const override { visitImpl (body); }
  101. void visit (const Message::ProcessMidiMessageReport& body) const override { visitImpl (body); }
  102. void visit (const Message::ProcessMidiMessageReportResponse& body) const override { visitImpl (body); }
  103. void visit (const Message::ProcessEndMidiMessageReport& body) const override { visitImpl (body); }
  104. private:
  105. static const char* getDescription (const Message::Discovery&) { return "Discovery"; }
  106. static const char* getDescription (const Message::DiscoveryResponse&) { return "Discovery Response"; }
  107. static const char* getDescription (const Message::InvalidateMUID&) { return "Invalidate MUID"; }
  108. static const char* getDescription (const Message::EndpointInquiry&) { return "Endpoint"; }
  109. static const char* getDescription (const Message::EndpointInquiryResponse&) { return "Endpoint Response"; }
  110. static const char* getDescription (const Message::ACK&) { return "ACK"; }
  111. static const char* getDescription (const Message::NAK&) { return "NAK"; }
  112. static const char* getDescription (const Message::ProfileInquiry&) { return "Profile Inquiry"; }
  113. static const char* getDescription (const Message::ProfileInquiryResponse&) { return "Profile Inquiry Response"; }
  114. static const char* getDescription (const Message::ProfileAdded&) { return "Profile Added"; }
  115. static const char* getDescription (const Message::ProfileRemoved&) { return "Profile Removed"; }
  116. static const char* getDescription (const Message::ProfileDetails&) { return "Profile Details"; }
  117. static const char* getDescription (const Message::ProfileDetailsResponse&) { return "Profile Details Response"; }
  118. static const char* getDescription (const Message::ProfileOn&) { return "Profile On"; }
  119. static const char* getDescription (const Message::ProfileOff&) { return "Profile Off"; }
  120. static const char* getDescription (const Message::ProfileEnabledReport&) { return "Profile Enabled Report"; }
  121. static const char* getDescription (const Message::ProfileDisabledReport&) { return "Profile Disabled Report"; }
  122. static const char* getDescription (const Message::ProfileSpecificData&) { return "Profile Specific Data"; }
  123. static const char* getDescription (const Message::PropertyExchangeCapabilities&) { return "Property Exchange Capabilities"; }
  124. static const char* getDescription (const Message::PropertyExchangeCapabilitiesResponse&) { return "Property Exchange Capabilities Response"; }
  125. static const char* getDescription (const Message::PropertyGetData&) { return "Property Get Data"; }
  126. static const char* getDescription (const Message::PropertyGetDataResponse&) { return "Property Get Data Response"; }
  127. static const char* getDescription (const Message::PropertySetData&) { return "Property Set Data"; }
  128. static const char* getDescription (const Message::PropertySetDataResponse&) { return "Property Set Data Response"; }
  129. static const char* getDescription (const Message::PropertySubscribe&) { return "Property Subscribe"; }
  130. static const char* getDescription (const Message::PropertySubscribeResponse&) { return "Property Subscribe Response"; }
  131. static const char* getDescription (const Message::PropertyNotify&) { return "Property Notify"; }
  132. static const char* getDescription (const Message::ProcessInquiry&) { return "Process Inquiry"; }
  133. static const char* getDescription (const Message::ProcessInquiryResponse&) { return "Process Inquiry Response"; }
  134. static const char* getDescription (const Message::ProcessMidiMessageReport&) { return "Process Midi Message Report"; }
  135. static const char* getDescription (const Message::ProcessMidiMessageReportResponse&) { return "Process Midi Message Report Response"; }
  136. static const char* getDescription (const Message::ProcessEndMidiMessageReport&) { return "Process End Midi Message Report"; }
  137. template <typename Body>
  138. void visitImpl (const Body& body) const
  139. {
  140. const auto opts = ToVarOptions{}.withExplicitVersion ((int) msg->header.version)
  141. .withVersionIncluded (false);
  142. const auto json = ToVar::convert (body, opts);
  143. if (json.has_value())
  144. *result = String (getDescription (body)) + ": " + JSON::toString (*json, JSON::FormatOptions{}.withSpacing (JSON::Spacing::none));
  145. }
  146. const Message::Parsed* msg = nullptr;
  147. String* result = nullptr;
  148. };
  149. String Parser::getMessageDescription (const Message::Parsed& message)
  150. {
  151. String result;
  152. detail::MessageTypeUtils::visit (message, DescriptionVisitor { &message, &result });
  153. return result;
  154. }
  155. //==============================================================================
  156. //==============================================================================
  157. #if JUCE_UNIT_TESTS
  158. class ParserTests : public UnitTest
  159. {
  160. public:
  161. ParserTests() : UnitTest ("Parser", UnitTestCategories::midi) {}
  162. void runTest() override
  163. {
  164. auto random = getRandom();
  165. beginTest ("Sending an empty message does nothing");
  166. {
  167. const auto parsed = Parser::parse (MUID::makeRandom (random), {});
  168. expect (parsed == std::nullopt);
  169. }
  170. beginTest ("Sending a garbage message does nothing");
  171. {
  172. const std::vector<std::byte> bytes (128, std::byte { 0x70 });
  173. const auto parsed = Parser::parse (MUID::makeRandom (random), bytes);
  174. expect (parsed == std::nullopt);
  175. }
  176. beginTest ("Sending a message with truncated body produces a malformed status");
  177. {
  178. constexpr auto version1 = 0x01;
  179. const auto truncatedV1 = makeByteArray (0x7e,
  180. /* to function block */ 0x7f,
  181. /* midi CI */ 0x0d,
  182. /* discovery message */ 0x70,
  183. /* version */ version1,
  184. /* source MUID */ 0x01,
  185. /* ... */ 0x02,
  186. /* ... */ 0x03,
  187. /* ... */ 0x04,
  188. /* broadcast MUID */ 0x7f,
  189. /* ... */ 0x7f,
  190. /* ... */ 0x7f,
  191. /* ... */ 0x7f,
  192. /* manufacturer */ 0x10,
  193. /* ... */ 0x11,
  194. /* ... */ 0x12,
  195. /* family */ 0x20,
  196. /* ... */ 0x21,
  197. /* model */ 0x30,
  198. /* ... */ 0x31,
  199. /* revision */ 0x40,
  200. /* ... */ 0x41,
  201. /* ... */ 0x42,
  202. /* ... */ 0x43,
  203. /* CI category supported */ 0x7f,
  204. /* max sysex size */ 0x7f,
  205. /* ... */ 0x7f,
  206. /* ... */ 0x7f);
  207. /* Missing final byte for a version 1 message */
  208. Parser::Status status{};
  209. const auto parsedV1 = Parser::parse (MUID::makeRandom (random), truncatedV1, &status);
  210. expect (status == Parser::Status::malformed);
  211. expect (parsedV1 == Message::Parsed { Message::Header { ChannelInGroup::wholeBlock,
  212. std::byte { 0x70 },
  213. std::byte { version1 },
  214. MUID::makeUnchecked (0x80c101),
  215. MUID::getBroadcast() },
  216. std::monostate{} });
  217. constexpr auto version2 = 0x02;
  218. const auto truncatedV2 = makeByteArray (0x7e,
  219. /* to function block */ 0x7f,
  220. /* midi CI */ 0x0d,
  221. /* discovery message */ 0x70,
  222. /* version */ version2,
  223. /* source MUID */ 0x01,
  224. /* ... */ 0x02,
  225. /* ... */ 0x03,
  226. /* ... */ 0x04,
  227. /* broadcast MUID */ 0x7f,
  228. /* ... */ 0x7f,
  229. /* ... */ 0x7f,
  230. /* ... */ 0x7f,
  231. /* manufacturer */ 0x10,
  232. /* ... */ 0x11,
  233. /* ... */ 0x12,
  234. /* family */ 0x20,
  235. /* ... */ 0x21,
  236. /* model */ 0x30,
  237. /* ... */ 0x31,
  238. /* revision */ 0x40,
  239. /* ... */ 0x41,
  240. /* ... */ 0x42,
  241. /* ... */ 0x43,
  242. /* CI category supported */ 0x7f,
  243. /* max sysex size */ 0x7f,
  244. /* ... */ 0x7f,
  245. /* ... */ 0x7f,
  246. /* ... */ 0x7f);
  247. /* Missing final byte for a version 2 message */
  248. const auto parsedV2 = Parser::parse (MUID::makeRandom (random), truncatedV2);
  249. expect (status == Parser::Status::malformed);
  250. expect (parsedV2 == Message::Parsed { Message::Header { ChannelInGroup::wholeBlock,
  251. std::byte { 0x70 },
  252. std::byte { version2 },
  253. MUID::makeUnchecked (0x80c101),
  254. MUID::getBroadcast() },
  255. std::monostate{} });
  256. }
  257. const auto getExpectedDiscoveryInput = [] (uint8_t version, uint8_t outputPathID)
  258. {
  259. return Message::Parsed { Message::Header { ChannelInGroup::wholeBlock,
  260. std::byte { 0x70 },
  261. std::byte { version },
  262. MUID::makeUnchecked (0x80c101),
  263. MUID::getBroadcast() },
  264. Message::Discovery { { { std::byte { 0x10 }, std::byte { 0x11 }, std::byte { 0x12 } },
  265. { std::byte { 0x20 }, std::byte { 0x21 } },
  266. { std::byte { 0x30 }, std::byte { 0x31 } },
  267. { std::byte { 0x40 }, std::byte { 0x41 }, std::byte { 0x42 }, std::byte { 0x43 } } },
  268. std::byte { 0x7f },
  269. 0xfffffff,
  270. std::byte { outputPathID } } };
  271. };
  272. beginTest ("Sending a V1 discovery message notifies the input listener");
  273. {
  274. const auto initialMUID = MUID::makeRandom (random);
  275. constexpr uint8_t version = 0x01;
  276. const auto bytes = makeByteArray (0x7e,
  277. /* to function block */ 0x7f,
  278. /* midi CI */ 0x0d,
  279. /* discovery message */ 0x70,
  280. /* version */ version,
  281. /* source MUID */ 0x01,
  282. /* ... */ 0x02,
  283. /* ... */ 0x03,
  284. /* ... */ 0x04,
  285. /* broadcast MUID */ 0x7f,
  286. /* ... */ 0x7f,
  287. /* ... */ 0x7f,
  288. /* ... */ 0x7f,
  289. /* manufacturer */ 0x10,
  290. /* ... */ 0x11,
  291. /* ... */ 0x12,
  292. /* family */ 0x20,
  293. /* ... */ 0x21,
  294. /* model */ 0x30,
  295. /* ... */ 0x31,
  296. /* revision */ 0x40,
  297. /* ... */ 0x41,
  298. /* ... */ 0x42,
  299. /* ... */ 0x43,
  300. /* CI category supported */ 0x7f,
  301. /* max sysex size */ 0x7f,
  302. /* ... */ 0x7f,
  303. /* ... */ 0x7f,
  304. /* ... */ 0x7f);
  305. const auto parsed = Parser::parse (initialMUID, bytes);
  306. expect (parsed == getExpectedDiscoveryInput (version, 0));
  307. }
  308. beginTest ("Sending a V2 discovery message notifies the input listener");
  309. {
  310. constexpr uint8_t outputPathID = 5;
  311. const auto initialMUID = MUID::makeRandom (random);
  312. constexpr uint8_t version = 0x02;
  313. const auto bytes = makeByteArray (0x7e,
  314. /* to function block */ 0x7f,
  315. /* midi CI */ 0x0d,
  316. /* discovery message */ 0x70,
  317. /* version */ version,
  318. /* source MUID */ 0x01,
  319. /* ... */ 0x02,
  320. /* ... */ 0x03,
  321. /* ... */ 0x04,
  322. /* broadcast MUID */ 0x7f,
  323. /* ... */ 0x7f,
  324. /* ... */ 0x7f,
  325. /* ... */ 0x7f,
  326. /* manufacturer */ 0x10,
  327. /* ... */ 0x11,
  328. /* ... */ 0x12,
  329. /* family */ 0x20,
  330. /* ... */ 0x21,
  331. /* model */ 0x30,
  332. /* ... */ 0x31,
  333. /* revision */ 0x40,
  334. /* ... */ 0x41,
  335. /* ... */ 0x42,
  336. /* ... */ 0x43,
  337. /* CI category supported */ 0x7f,
  338. /* max sysex size */ 0x7f,
  339. /* ... */ 0x7f,
  340. /* ... */ 0x7f,
  341. /* ... */ 0x7f,
  342. /* output path ID */ outputPathID);
  343. const auto parsed = Parser::parse (initialMUID, bytes);
  344. expect (parsed == getExpectedDiscoveryInput (version, outputPathID));
  345. }
  346. beginTest ("Sending a discovery message with a future version notifies the input listener and ignores trailing fields");
  347. {
  348. constexpr uint8_t outputPathID = 10;
  349. const auto initialMUID = MUID::makeRandom (random);
  350. constexpr auto version = (uint8_t) detail::MessageMeta::implementationVersion + 1;
  351. const auto bytes = makeByteArray (0x7e,
  352. /* to function block */ 0x7f,
  353. /* midi CI */ 0x0d,
  354. /* discovery message */ 0x70,
  355. /* version */ version,
  356. /* source MUID */ 0x01,
  357. /* ... */ 0x02,
  358. /* ... */ 0x03,
  359. /* ... */ 0x04,
  360. /* broadcast MUID */ 0x7f,
  361. /* ... */ 0x7f,
  362. /* ... */ 0x7f,
  363. /* ... */ 0x7f,
  364. /* manufacturer */ 0x10,
  365. /* ... */ 0x11,
  366. /* ... */ 0x12,
  367. /* family */ 0x20,
  368. /* ... */ 0x21,
  369. /* model */ 0x30,
  370. /* ... */ 0x31,
  371. /* revision */ 0x40,
  372. /* ... */ 0x41,
  373. /* ... */ 0x42,
  374. /* ... */ 0x43,
  375. /* CI category supported */ 0x7f,
  376. /* max sysex size */ 0x7f,
  377. /* ... */ 0x7f,
  378. /* ... */ 0x7f,
  379. /* ... */ 0x7f,
  380. /* output path ID */ outputPathID,
  381. /* extra bytes */ 0x00,
  382. /* ... */ 0x00,
  383. /* ... */ 0x00,
  384. /* ... */ 0x00);
  385. const auto parsed = Parser::parse (initialMUID, bytes);
  386. expect (parsed == getExpectedDiscoveryInput (version, outputPathID));
  387. }
  388. }
  389. private:
  390. template <typename... Ts>
  391. static std::array<std::byte, sizeof... (Ts)> makeByteArray (Ts&&... ts)
  392. {
  393. jassert (((0 <= (int) ts && (int) ts <= std::numeric_limits<uint8_t>::max()) && ...));
  394. return { std::byte (ts)... };
  395. }
  396. };
  397. static ParserTests parserTests;
  398. #endif
  399. } // namespace juce::midi_ci