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.

415 lines
15KB

  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. class PropertyHost::Visitor : public detail::MessageTypeUtils::MessageVisitor
  21. {
  22. public:
  23. Visitor (PropertyHost* h, ResponderOutput* o, bool* b)
  24. : host (h), output (o), handled (b) {}
  25. void visit (const Message::PropertyExchangeCapabilities& body) const override { visitImpl (body); }
  26. void visit (const Message::PropertyGetData& body) const override { visitImpl (body); }
  27. void visit (const Message::PropertySetData& body) const override { visitImpl (body); }
  28. void visit (const Message::PropertySubscribe& body) const override { visitImpl (body); }
  29. using MessageVisitor::visit;
  30. private:
  31. template <typename Body>
  32. void visitImpl (const Body& body) const { *handled = messageReceived (body); }
  33. bool messageReceived (const Message::PropertyExchangeCapabilities&) const
  34. {
  35. detail::MessageTypeUtils::send (*output, Message::PropertyExchangeCapabilitiesResponse { std::byte { host->delegate.getNumSimultaneousRequestsSupported() },
  36. {},
  37. {} });
  38. return true;
  39. }
  40. bool messageReceived (const Message::PropertyGetData& data) const
  41. {
  42. // This should always be a single message, so no need to accumulate chunks
  43. const auto reply = host->delegate.propertyGetDataRequested (output->getIncomingHeader().source,
  44. PropertyRequestHeader::parseCondensed (Encodings::jsonFrom7BitText (data.header)));
  45. const auto encoded = Encodings::tryEncode (reply.body, reply.header.mutualEncoding);
  46. if (! encoded.has_value())
  47. {
  48. // If this is hit, the data that was supplied isn't valid for the encoding that was specified
  49. jassertfalse;
  50. return false;
  51. }
  52. detail::PropertyHostUtils::send (*output,
  53. output->getIncomingGroup(),
  54. detail::MessageMeta::Meta<Message::PropertyGetDataResponse>::subID2,
  55. output->getIncomingHeader().source,
  56. data.requestID,
  57. Encodings::jsonTo7BitText (reply.header.toVarCondensed()),
  58. *encoded,
  59. host->cacheProvider.getMaxSysexSizeForMuid (output->getIncomingHeader().source));
  60. return true;
  61. }
  62. bool messageReceived (const Message::PropertySetData& data) const
  63. {
  64. auto* caches = host->cacheProvider.getCacheForMuidAsResponder (output->getIncomingHeader().source);
  65. if (caches == nullptr)
  66. return false;
  67. const auto source = output->getIncomingHeader().source;
  68. const auto dest = output->getIncomingHeader().destination;
  69. const auto group = output->getIncomingGroup();
  70. const auto request = RequestID::create (data.requestID);
  71. if (! request.has_value())
  72. return false;
  73. caches->primeCache (host->delegate.getNumSimultaneousRequestsSupported(), [hostPtr = host, source, dest, group, request] (const PropertyExchangeResult& result)
  74. {
  75. const auto send = [&] (const PropertyReplyHeader& header)
  76. {
  77. detail::MessageTypeUtils::send (hostPtr->output,
  78. group,
  79. Message::Header { ChannelInGroup::wholeBlock,
  80. detail::MessageMeta::Meta<Message::PropertySetDataResponse>::subID2,
  81. detail::MessageMeta::implementationVersion,
  82. dest,
  83. source },
  84. Message::PropertySetDataResponse { { request->asByte(), Encodings::jsonTo7BitText (header.toVarCondensed()) } });
  85. };
  86. const auto sendStatus = [&] (int status, StringRef message)
  87. {
  88. PropertyReplyHeader header;
  89. header.status = status;
  90. header.message = message;
  91. send (header);
  92. };
  93. if (const auto error = result.getError())
  94. {
  95. switch (*error)
  96. {
  97. case PropertyExchangeResult::Error::tooManyTransactions:
  98. sendStatus (343, TRANS ("The device has initiated too many simultaneous requests"));
  99. break;
  100. case PropertyExchangeResult::Error::partial:
  101. sendStatus (400, TRANS ("Request was incomplete"));
  102. break;
  103. case PropertyExchangeResult::Error::notify:
  104. break;
  105. }
  106. return;
  107. }
  108. send (hostPtr->delegate.propertySetDataRequested (source, { result.getHeaderAsRequestHeader(), result.getBody() }));
  109. }, *request);
  110. caches->addChunk (*request, data);
  111. return true;
  112. }
  113. bool messageReceived (const Message::PropertySubscribe& data) const
  114. {
  115. auto* caches = host->cacheProvider.getCacheForMuidAsResponder (output->getIncomingHeader().source);
  116. if (caches == nullptr)
  117. return false;
  118. if (data.header.empty() || data.thisChunkNum != 1 || data.totalNumChunks != 1)
  119. return false;
  120. const auto subHeader = PropertySubscriptionHeader::parseCondensed (Encodings::jsonFrom7BitText (data.header));
  121. const auto tryNotifyInitiator = subHeader.command == PropertySubscriptionCommand::start
  122. || subHeader.command == PropertySubscriptionCommand::end;
  123. if (! tryNotifyInitiator)
  124. return false;
  125. const auto source = output->getIncomingHeader().source;
  126. const auto sendResponse = [&] (const PropertyReplyHeader& header)
  127. {
  128. detail::PropertyHostUtils::send (*output,
  129. output->getIncomingGroup(),
  130. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  131. source,
  132. data.requestID,
  133. Encodings::jsonTo7BitText (header.toVarCondensed()),
  134. {},
  135. host->cacheProvider.getMaxSysexSizeForMuid (source));
  136. };
  137. if (subHeader.command == PropertySubscriptionCommand::start)
  138. {
  139. if (host->delegate.subscriptionStartRequested (source, subHeader))
  140. {
  141. auto& currentSubscribeIds = host->registry[source];
  142. const auto newToken = findUnusedSubscribeId (currentSubscribeIds);
  143. [[maybe_unused]] const auto pair = currentSubscribeIds.emplace (newToken, subHeader.resource);
  144. jassert (pair.second);
  145. const auto subscribeId = subscribeIdFromUid (newToken);
  146. host->delegate.subscriptionDidStart (source, subscribeId, subHeader);
  147. PropertyReplyHeader header;
  148. header.extended["subscribeId"] = subscribeId;
  149. sendResponse (header);
  150. }
  151. else
  152. {
  153. PropertyReplyHeader header;
  154. header.status = 405;
  155. sendResponse (header);
  156. }
  157. return true;
  158. }
  159. if (subHeader.command == PropertySubscriptionCommand::end)
  160. {
  161. const auto token = uidFromSubscribeId (subHeader.subscribeId);
  162. auto& currentSubscribeIds = host->registry[source];
  163. const auto iter = currentSubscribeIds.find (token);
  164. if (iter != currentSubscribeIds.end())
  165. {
  166. host->delegate.subscriptionWillEnd (source, { subHeader.subscribeId, iter->second });
  167. currentSubscribeIds.erase (iter);
  168. sendResponse ({});
  169. return true;
  170. }
  171. return false;
  172. }
  173. return false;
  174. }
  175. PropertyHost* host = nullptr;
  176. ResponderOutput* output = nullptr;
  177. bool* handled = nullptr;
  178. };
  179. //==============================================================================
  180. std::set<Subscription> PropertyHost::findSubscriptionsForDevice (MUID device) const
  181. {
  182. const auto iter = registry.find (device);
  183. if (iter == registry.end())
  184. return {};
  185. std::set<Subscription> result;
  186. for (const auto& [subId, resource] : iter->second)
  187. {
  188. [[maybe_unused]] const auto pair = result.insert ({ subscribeIdFromUid (subId), resource });
  189. jassert (pair.second);
  190. }
  191. return result;
  192. }
  193. int PropertyHost::countOngoingTransactions() const
  194. {
  195. const auto muids = cacheProvider.getDiscoveredMuids();
  196. return std::accumulate (muids.begin(), muids.end(), 0, [&] (auto acc, const auto& m)
  197. {
  198. if (auto* cache = cacheProvider.getCacheForMuidAsResponder (m))
  199. return acc + cache->countOngoingTransactions();
  200. return acc;
  201. });
  202. }
  203. bool PropertyHost::tryRespond (ResponderOutput& responderOutput, const Message::Parsed& message)
  204. {
  205. bool result = false;
  206. detail::MessageTypeUtils::visit (message, Visitor { this, &responderOutput, &result });
  207. return result;
  208. }
  209. std::optional<RequestKey> PropertyHost::sendSubscriptionUpdate (MUID device,
  210. const PropertySubscriptionHeader& header,
  211. Span<const std::byte> body,
  212. std::function<void (const PropertyExchangeResult&)> cb)
  213. {
  214. const auto deviceIter = registry.find (device);
  215. if (deviceIter == registry.end())
  216. {
  217. // That device doesn't have any active subscriptions
  218. jassertfalse;
  219. return {};
  220. }
  221. const auto uid = uidFromSubscribeId (header.subscribeId);
  222. const auto subIter = deviceIter->second.find (uid);
  223. if (subIter == deviceIter->second.end())
  224. {
  225. // That subscribeId isn't currently in use by that device
  226. jassertfalse;
  227. return {};
  228. }
  229. const auto resource = subIter->second;
  230. if (header.resource != resource)
  231. {
  232. // That subscribeId corresponds to a different resource
  233. jassertfalse;
  234. return {};
  235. }
  236. if (header.command == PropertySubscriptionCommand::start)
  237. {
  238. // This function is intended to update ongoing subscriptions. To start a new subscription,
  239. // use CIDevice.
  240. jassertfalse;
  241. return {};
  242. }
  243. auto* caches = cacheProvider.getCacheForMuidAsInitiator (device);
  244. if (caches == nullptr)
  245. return {};
  246. auto wrappedCallback = [&]() -> std::function<void (const PropertyExchangeResult&)>
  247. {
  248. if (header.command != PropertySubscriptionCommand::end)
  249. return cb;
  250. return [this, device, uid, resource, cb] (const PropertyExchangeResult& result)
  251. {
  252. if (! result.getError().has_value())
  253. {
  254. delegate.subscriptionWillEnd (device, { subscribeIdFromUid (uid), resource });
  255. registry[device].erase (uid);
  256. }
  257. NullCheckedInvocation::invoke (cb, result);
  258. };
  259. }();
  260. const auto encoded = Encodings::tryEncode (body, header.mutualEncoding);
  261. if (! encoded.has_value())
  262. {
  263. // The data could not be encoded successfully
  264. jassertfalse;
  265. return {};
  266. }
  267. const auto primed = caches->primeCache (delegate.getNumSimultaneousRequestsSupported(),
  268. std::move (wrappedCallback));
  269. if (! primed.has_value())
  270. return {};
  271. const auto id = caches->getRequestIdForToken (*primed);
  272. if (! id.has_value())
  273. return {};
  274. detail::PropertyHostUtils::send (output,
  275. functionBlock.firstGroup,
  276. detail::MessageMeta::Meta<Message::PropertySubscribe>::subID2,
  277. device,
  278. id->asByte(),
  279. Encodings::jsonTo7BitText (header.toVarCondensed()),
  280. *encoded,
  281. cacheProvider.getMaxSysexSizeForMuid (device));
  282. return RequestKey { device, *primed };
  283. }
  284. void PropertyHost::terminateSubscription (MUID device, const String& subscribeId)
  285. {
  286. const auto deviceIter = registry.find (device);
  287. if (deviceIter == registry.end())
  288. {
  289. // That device doesn't have any active subscriptions
  290. jassertfalse;
  291. return;
  292. }
  293. const auto uid = uidFromSubscribeId (subscribeId);
  294. const auto subIter = deviceIter->second.find (uid);
  295. if (subIter == deviceIter->second.end())
  296. {
  297. // That subscribeId isn't currently in use by that device
  298. jassertfalse;
  299. return;
  300. }
  301. PropertySubscriptionHeader header;
  302. header.command = PropertySubscriptionCommand::end;
  303. header.subscribeId = subscribeId;
  304. header.resource = subIter->second;
  305. sendSubscriptionUpdate (device, header, {}, nullptr);
  306. }
  307. PropertyHost::SubscriptionToken PropertyHost::uidFromSubscribeId (String id)
  308. {
  309. try
  310. {
  311. // from_chars would be better once we no longer need to support older macOS
  312. return { (size_t) std::stoull (id.toStdString(), {}, 36) };
  313. }
  314. catch (...) {}
  315. jassertfalse;
  316. return {};
  317. }
  318. String PropertyHost::subscribeIdFromUid (SubscriptionToken uid)
  319. {
  320. const auto str = std::to_string (uid.uid);
  321. jassert (str.size() <= 8);
  322. return str;
  323. }
  324. PropertyHost::SubscriptionToken PropertyHost::findUnusedSubscribeId (const std::map<SubscriptionToken, String>& used)
  325. {
  326. return ! used.empty() ? SubscriptionToken { std::prev (used.end())->first.uid + 1 } : SubscriptionToken { 0 };
  327. }
  328. } // namespace juce::midi_ci