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.

2888 lines
156KB

  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 Device::Impl : private SubscriptionManagerDelegate
  21. {
  22. template <typename This>
  23. static auto getProfileHostImpl (This& t) { return t.profileHost.has_value() ? &*t.profileHost : nullptr; }
  24. template <typename This>
  25. static auto getPropertyHostImpl (This& t) { return t.propertyHost.has_value() ? &*t.propertyHost : nullptr; }
  26. public:
  27. explicit Impl (const Options& opt)
  28. : options (getValidated (opt)),
  29. muid (getReallyRandomMuid())
  30. {
  31. if (options.getFeatures().isProfileConfigurationSupported())
  32. profileHost.emplace (options.getFunctionBlock(), profileDelegate, concreteBufferOutput);
  33. if (options.getFeatures().isPropertyExchangeSupported())
  34. propertyHost.emplace (options.getFunctionBlock(), propertyDelegate, concreteBufferOutput, cacheProvider);
  35. outgoing.reserve (options.getMaxSysExSize());
  36. }
  37. ~Impl() override
  38. {
  39. if (concreteBufferOutput.hasSentMuid())
  40. {
  41. detail::MessageTypeUtils::send (concreteBufferOutput,
  42. options.getFunctionBlock().firstGroup,
  43. MUID::getBroadcast(),
  44. ChannelInGroup::wholeBlock,
  45. Message::InvalidateMUID { muid });
  46. }
  47. }
  48. void sendDiscovery()
  49. {
  50. {
  51. const auto aboutToRemove = std::move (discovered);
  52. for (const auto& pair : aboutToRemove)
  53. listeners.call ([&] (auto& l) { l.deviceRemoved (pair.first); });
  54. }
  55. const Message::Header header
  56. {
  57. ChannelInGroup::wholeBlock,
  58. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  59. detail::MessageMeta::implementationVersion,
  60. muid,
  61. MUID::getBroadcast(),
  62. };
  63. jassert (options.getOutputs().size() < 128);
  64. for (size_t i = 0; i < options.getOutputs().size(); ++i)
  65. {
  66. const Message::Discovery discovery
  67. {
  68. options.getDeviceInfo(),
  69. options.getFeatures().getSupportedCapabilities(),
  70. uint32_t (options.getMaxSysExSize()),
  71. std::byte (i % 128),
  72. };
  73. outgoing.clear();
  74. detail::Marshalling::Writer { outgoing } (header, discovery);
  75. options.getOutputs()[i]->processMessage ({ options.getFunctionBlock().firstGroup, outgoing });
  76. }
  77. }
  78. void sendEndpointInquiry (MUID destination, Message::EndpointInquiry endpoint)
  79. {
  80. detail::MessageTypeUtils::send (concreteBufferOutput,
  81. options.getFunctionBlock().firstGroup,
  82. destination,
  83. ChannelInGroup::wholeBlock,
  84. endpoint);
  85. }
  86. void sendProfileInquiry (MUID receiver, ChannelInGroup address)
  87. {
  88. if (! supportsProfiles (receiver))
  89. return;
  90. detail::MessageTypeUtils::send (concreteBufferOutput,
  91. options.getFunctionBlock().firstGroup,
  92. receiver,
  93. address,
  94. Message::ProfileInquiry{});
  95. }
  96. void sendProfileDetailsInquiry (MUID receiver, ChannelInGroup address, Profile profile, std::byte target)
  97. {
  98. if (! supportsProfiles (receiver))
  99. return;
  100. detail::MessageTypeUtils::send (concreteBufferOutput,
  101. options.getFunctionBlock().firstGroup,
  102. receiver,
  103. address,
  104. Message::ProfileDetails { profile, target });
  105. }
  106. void sendProfileSpecificData (MUID receiver, ChannelInGroup address, Profile profile, Span<const std::byte> data)
  107. {
  108. if (! supportsProfiles (receiver))
  109. return;
  110. detail::MessageTypeUtils::send (concreteBufferOutput,
  111. options.getFunctionBlock().firstGroup,
  112. receiver,
  113. address,
  114. Message::ProfileSpecificData { profile, data });
  115. }
  116. void sendProfileEnablement (MUID m, ChannelInGroup address, Profile profile, int numChannels)
  117. {
  118. if (! supportsProfiles (m))
  119. return;
  120. // There are only 256 channels on a UMP endpoint, so requesting more probably doesn't make sense!
  121. jassert (numChannels <= 256);
  122. if (numChannels > 0)
  123. {
  124. const auto channelsToSend = address == ChannelInGroup::wholeBlock || address == ChannelInGroup::wholeGroup
  125. ? 0
  126. : numChannels;
  127. detail::MessageTypeUtils::send (concreteBufferOutput,
  128. options.getFunctionBlock().firstGroup,
  129. m,
  130. address,
  131. Message::ProfileOn { profile, (uint16_t) channelsToSend });
  132. }
  133. else
  134. {
  135. detail::MessageTypeUtils::send (concreteBufferOutput,
  136. options.getFunctionBlock().firstGroup,
  137. m,
  138. address,
  139. Message::ProfileOff { profile });
  140. }
  141. }
  142. void sendPropertyCapabilitiesInquiry (MUID m)
  143. {
  144. if (! supportsProperties (m))
  145. return;
  146. detail::MessageTypeUtils::send (concreteBufferOutput,
  147. options.getFunctionBlock().firstGroup,
  148. m,
  149. ChannelInGroup::wholeBlock,
  150. Message::PropertyExchangeCapabilities { std::byte { propertyDelegate.getNumSimultaneousRequestsSupported() }, {}, {} });
  151. }
  152. std::optional<RequestKey> sendPropertyGetInquiry (MUID m,
  153. const PropertyRequestHeader& header,
  154. std::function<void (const PropertyExchangeResult&)> onResult)
  155. {
  156. const auto iter = discovered.find (m);
  157. if (iter == discovered.end() || ! Features { iter->second.discovery.capabilities }.isPropertyExchangeSupported())
  158. return {};
  159. const auto primed = iter->second.initiatorPropertyCaches.primeCache (propertyDelegate.getNumSimultaneousRequestsSupported(),
  160. std::move (onResult));
  161. if (! primed.has_value())
  162. return {};
  163. const auto id = iter->second.initiatorPropertyCaches.getRequestIdForToken (*primed);
  164. jassert (id.has_value());
  165. detail::MessageTypeUtils::send (concreteBufferOutput,
  166. options.getFunctionBlock().firstGroup,
  167. m,
  168. ChannelInGroup::wholeBlock,
  169. Message::PropertyGetData { { id->asByte(), Encodings::jsonTo7BitText (header.toVarCondensed()) } });
  170. return RequestKey { m, *primed };
  171. }
  172. std::optional<RequestKey> sendPropertySetInquiry (MUID m,
  173. const PropertyRequestHeader& header,
  174. Span<const std::byte> body,
  175. std::function<void (const PropertyExchangeResult&)> onResult)
  176. {
  177. const auto encoded = Encodings::tryEncode (body, header.mutualEncoding);
  178. if (! encoded.has_value())
  179. return {};
  180. const auto iter = discovered.find (m);
  181. if (iter == discovered.end() || ! Features { iter->second.discovery.capabilities }.isPropertyExchangeSupported())
  182. return {};
  183. const auto primed = iter->second.initiatorPropertyCaches.primeCache (propertyDelegate.getNumSimultaneousRequestsSupported(),
  184. std::move (onResult));
  185. if (! primed.has_value())
  186. return {};
  187. const auto id = iter->second.initiatorPropertyCaches.getRequestIdForToken (*primed);
  188. jassert (id.has_value());
  189. detail::PropertyHostUtils::send (concreteBufferOutput,
  190. options.getFunctionBlock().firstGroup,
  191. detail::MessageMeta::Meta<Message::PropertySetData>::subID2,
  192. m,
  193. id->asByte(),
  194. Encodings::jsonTo7BitText (header.toVarCondensed()),
  195. *encoded,
  196. cacheProvider.getMaxSysexSizeForMuid (m));
  197. return RequestKey { m, *primed };
  198. }
  199. void abortPropertyRequest (RequestKey k) override
  200. {
  201. const auto iter = discovered.find (k.getMuid());
  202. if (iter == discovered.end())
  203. return;
  204. const auto id = iter->second.initiatorPropertyCaches.getRequestIdForToken (k.getKey());
  205. if (! id.has_value() || ! iter->second.initiatorPropertyCaches.terminate (k.getKey()))
  206. return;
  207. const Message::Header notifyHeader
  208. {
  209. ChannelInGroup::wholeBlock,
  210. detail::MessageMeta::Meta<Message::PropertyNotify>::subID2,
  211. detail::MessageMeta::implementationVersion,
  212. muid,
  213. k.getMuid(),
  214. };
  215. const auto jsonHeader = Encodings::jsonTo7BitText (JSONUtils::makeObjectWithKeyFirst ({ { "status", 144 } }, "status"));
  216. detail::MessageTypeUtils::send (concreteBufferOutput,
  217. options.getFunctionBlock().firstGroup,
  218. notifyHeader,
  219. Message::PropertyNotify { { id->asByte(), jsonHeader, 1, 1, {} } });
  220. }
  221. std::optional<RequestID> getIdForRequestKey (RequestKey key) const
  222. {
  223. const auto iter = discovered.find (key.getMuid());
  224. if (iter == discovered.end())
  225. return {};
  226. return iter->second.initiatorPropertyCaches.getRequestIdForToken (key.getKey());
  227. }
  228. std::vector<RequestKey> getOngoingRequests() const
  229. {
  230. std::vector<RequestKey> result;
  231. for (auto& i : discovered)
  232. for (const auto& token : i.second.initiatorPropertyCaches.getOngoingTransactions())
  233. result.emplace_back (i.first, token);
  234. return result;
  235. }
  236. SubscriptionKey beginSubscription (MUID m, const PropertySubscriptionHeader& header)
  237. {
  238. return subscriptionManager.beginSubscription (m, header);
  239. }
  240. void endSubscription (SubscriptionKey key)
  241. {
  242. subscriptionManager.endSubscription (key);
  243. }
  244. std::vector<SubscriptionKey> getOngoingSubscriptions() const
  245. {
  246. return subscriptionManager.getOngoingSubscriptions();
  247. }
  248. std::optional<String> getSubscribeIdForKey (SubscriptionKey key) const
  249. {
  250. return subscriptionManager.getSubscribeIdForKey (key);
  251. }
  252. std::optional<String> getResourceForKey (SubscriptionKey key) const
  253. {
  254. return subscriptionManager.getResourceForKey (key);
  255. }
  256. bool sendPendingMessages()
  257. {
  258. return subscriptionManager.sendPendingMessages();
  259. }
  260. void processMessage (ump::BytesOnGroup msg)
  261. {
  262. // Queried before the property host to unconditionally register capabilities of property exchange hosts.
  263. FirstListener firstListener { this };
  264. LastListener lastListener { this };
  265. ResponderDelegate* const l[] { &firstListener,
  266. getProfileHostImpl (*this),
  267. getPropertyHostImpl (*this),
  268. &lastListener };
  269. const auto status = detail::Responder::processCompleteMessage (concreteBufferOutput, msg, l);
  270. if (status == Parser::Status::collidingMUID)
  271. {
  272. muid = getReallyRandomMuid();
  273. concreteBufferOutput.resetSentMuid();
  274. sendDiscovery();
  275. }
  276. }
  277. void addListener (Listener& l)
  278. {
  279. listeners.add (&l);
  280. }
  281. void removeListener (Listener& l)
  282. {
  283. listeners.remove (&l);
  284. }
  285. std::vector<MUID> getDiscoveredMuids() const
  286. {
  287. std::vector<MUID> result (discovered.size(), MUID::makeUnchecked (0));
  288. std::transform (discovered.begin(), discovered.end(), result.begin(), [] (const auto& p) { return p.first; });
  289. return result;
  290. }
  291. std::optional<Message::Discovery> getDiscoveryInfoForMuid (MUID m) const
  292. {
  293. const auto iter = discovered.find (m);
  294. return iter != discovered.end()
  295. ? std::optional<Message::Discovery> (iter->second.discovery)
  296. : std::nullopt;
  297. }
  298. std::optional<int> getNumPropertyExchangeRequestsSupportedForMuid (MUID m) const
  299. {
  300. const auto iter = discovered.find (m);
  301. return iter != discovered.end()
  302. ? std::optional<int> ((int) iter->second.propertyExchangeResponse->numSimultaneousRequestsSupported)
  303. : std::nullopt;
  304. }
  305. const ChannelProfileStates* getProfileStateForMuid (MUID m, ChannelAddress address) const
  306. {
  307. const auto iter = discovered.find (m);
  308. return iter != discovered.end() ? iter->second.profileStates.getStateForDestination (address) : nullptr;
  309. }
  310. var getResourceListForMuid (MUID x) const
  311. {
  312. const auto iter = discovered.find (x);
  313. return iter != discovered.end() ? iter->second.resourceList : var();
  314. }
  315. var getDeviceInfoForMuid (MUID x) const
  316. {
  317. const auto iter = discovered.find (x);
  318. return iter != discovered.end() ? iter->second.deviceInfo : var();
  319. }
  320. var getChannelListForMuid (MUID x) const
  321. {
  322. const auto iter = discovered.find (x);
  323. return iter != discovered.end() ? iter->second.channelList : var();
  324. }
  325. MUID getMuid() const { return muid; }
  326. Options getOptions() const { return options; }
  327. ProfileHost* getProfileHost() { return getProfileHostImpl (*this); }
  328. const ProfileHost* getProfileHost() const { return getProfileHostImpl (*this); }
  329. PropertyHost* getPropertyHost() { return getPropertyHostImpl (*this); }
  330. const PropertyHost* getPropertyHost() const { return getPropertyHostImpl (*this); }
  331. private:
  332. class FirstListener : public ResponderDelegate
  333. {
  334. public:
  335. explicit FirstListener (Impl* d) : device (d) {}
  336. bool tryRespond (ResponderOutput& output, const Message::Parsed& message) override
  337. {
  338. detail::MessageTypeUtils::visit (message, Visitor { device, &output });
  339. return false;
  340. }
  341. private:
  342. class Visitor : public detail::MessageTypeUtils::MessageVisitor
  343. {
  344. public:
  345. Visitor (Impl* d, ResponderOutput* o)
  346. : device (d), output (o) {}
  347. void visit (const Message::PropertyExchangeCapabilities& caps) const override { visitImpl (caps); }
  348. void visit (const Message::PropertyExchangeCapabilitiesResponse& caps) const override { visitImpl (caps); }
  349. using MessageVisitor::visit;
  350. private:
  351. template <typename Body>
  352. void visitImpl (const Body& t) const
  353. {
  354. const auto responderMUID = output->getIncomingHeader().source;
  355. const auto iter = device->discovered.find (responderMUID);
  356. if (iter == device->discovered.end())
  357. return;
  358. iter->second.propertyExchangeResponse = Message::PropertyExchangeCapabilitiesResponse { t.numSimultaneousRequestsSupported,
  359. t.majorVersion,
  360. t.minorVersion };
  361. }
  362. Impl* device = nullptr;
  363. ResponderOutput* output = nullptr;
  364. };
  365. Impl* device = nullptr;
  366. };
  367. class LastListener : public ResponderDelegate
  368. {
  369. public:
  370. explicit LastListener (Impl* d) : device (d) {}
  371. bool tryRespond (ResponderOutput& output, const Message::Parsed& message) override
  372. {
  373. bool result = false;
  374. detail::MessageTypeUtils::visit (message, Visitor { device, &output, &result });
  375. return result;
  376. }
  377. private:
  378. class Visitor : public detail::MessageTypeUtils::MessageVisitor
  379. {
  380. public:
  381. Visitor (Impl* d, ResponderOutput* o, bool* b)
  382. : device (d), output (o), handled (b) {}
  383. void visit (const Message::Discovery& x) const override { visitImpl (x); }
  384. void visit (const Message::DiscoveryResponse& x) const override { visitImpl (x); }
  385. void visit (const Message::InvalidateMUID& x) const override { visitImpl (x); }
  386. void visit (const Message::EndpointInquiry& x) const override { visitImpl (x); }
  387. void visit (const Message::EndpointInquiryResponse& x) const override { visitImpl (x); }
  388. void visit (const Message::NAK& x) const override { visitImpl (x); }
  389. void visit (const Message::ProfileInquiryResponse& x) const override { visitImpl (x); }
  390. void visit (const Message::ProfileAdded& x) const override { visitImpl (x); }
  391. void visit (const Message::ProfileRemoved& x) const override { visitImpl (x); }
  392. void visit (const Message::ProfileEnabledReport& x) const override { visitImpl (x); }
  393. void visit (const Message::ProfileDisabledReport& x) const override { visitImpl (x); }
  394. void visit (const Message::ProfileDetailsResponse& x) const override { visitImpl (x); }
  395. void visit (const Message::ProfileSpecificData& x) const override { visitImpl (x); }
  396. void visit (const Message::PropertyExchangeCapabilitiesResponse& x) const override { visitImpl (x); }
  397. void visit (const Message::PropertyGetDataResponse& x) const override { visitImpl (x); }
  398. void visit (const Message::PropertySetDataResponse& x) const override { visitImpl (x); }
  399. void visit (const Message::PropertySubscribe& x) const override { visitImpl (x); }
  400. void visit (const Message::PropertySubscribeResponse& x) const override { visitImpl (x); }
  401. void visit (const Message::PropertyNotify& x) const override { visitImpl (x); }
  402. using MessageVisitor::visit;
  403. private:
  404. template <typename Body>
  405. void visitImpl (const Body& body) const { *handled = messageReceived (body); }
  406. bool messageReceived (const Message::Discovery& body) const
  407. {
  408. const auto replyPath = uint8_t (output->getIncomingHeader().version) >= 0x02 ? body.outputPathID : std::byte { 0x00 };
  409. detail::MessageTypeUtils::send (*output, Message::DiscoveryResponse
  410. {
  411. device->options.getDeviceInfo(),
  412. device->options.getFeatures().getSupportedCapabilities(),
  413. uint32_t (device->options.getMaxSysExSize()),
  414. replyPath,
  415. device->options.getFunctionBlock().identifier,
  416. });
  417. // TODO(reuk) rather than sending a new discovery inquiry, we should store the details from the incoming message
  418. const auto iter = device->discovered.find (output->getIncomingHeader().source);
  419. if (iter == device->discovered.end())
  420. {
  421. const auto initiator = output->getIncomingHeader().source;
  422. device->discovered.emplace (initiator, Discovered { body });
  423. device->listeners.call ([&] (auto& l) { l.deviceAdded (initiator); });
  424. device->sendEndpointInquiry (initiator, Message::EndpointInquiry { std::byte{} });
  425. }
  426. return true;
  427. }
  428. bool messageReceived (const Message::DiscoveryResponse& response) const
  429. {
  430. const auto responderMUID = output->getIncomingHeader().source;
  431. const auto iter = device->discovered.find (responderMUID);
  432. if (iter != device->discovered.end())
  433. {
  434. device->discovered.erase (iter);
  435. device->listeners.call ([&] (auto& l) { l.deviceRemoved (responderMUID); });
  436. const Message::Header header
  437. {
  438. ChannelInGroup::wholeBlock,
  439. detail::MessageMeta::Meta<Message::InvalidateMUID>::subID2,
  440. detail::MessageMeta::implementationVersion,
  441. device->muid,
  442. MUID::getBroadcast(),
  443. };
  444. detail::MessageTypeUtils::send (*output, output->getIncomingGroup(), header, Message::InvalidateMUID { responderMUID });
  445. }
  446. else
  447. {
  448. const Message::Discovery discovery { response.device,
  449. response.capabilities,
  450. response.maximumSysexSize,
  451. response.outputPathID };
  452. device->discovered.emplace (responderMUID, Discovered { discovery });
  453. device->listeners.call ([&] (auto& l) { l.deviceAdded (responderMUID); });
  454. device->sendEndpointInquiry (output->getIncomingHeader().source, Message::EndpointInquiry { std::byte{} });
  455. }
  456. return true;
  457. }
  458. bool messageReceived (const Message::InvalidateMUID& invalidate) const
  459. {
  460. const auto targetMuid = invalidate.target;
  461. const auto iter = device->discovered.find (targetMuid);
  462. if (iter != device->discovered.end())
  463. {
  464. device->subscriptionManager.endSubscriptionsFromResponder (targetMuid);
  465. device->discovered.erase (iter);
  466. device->listeners.call ([&] (auto& l) { l.deviceRemoved (targetMuid); });
  467. }
  468. if (invalidate.target != device->muid)
  469. return false;
  470. device->muid = getReallyRandomMuid();
  471. device->concreteBufferOutput.resetSentMuid();
  472. device->sendDiscovery();
  473. return true;
  474. }
  475. bool messageReceived (const Message::EndpointInquiry& endpoint) const
  476. {
  477. // Only status 0 is defined at time of writing
  478. if (endpoint.status == std::byte{})
  479. {
  480. const auto& id = device->options.getProductInstanceId();
  481. const auto length = std::distance (id.begin(), std::find (id.begin(), id.end(), 0));
  482. if (length <= 0)
  483. return false;
  484. Message::EndpointInquiryResponse response;
  485. response.status = endpoint.status;
  486. response.data = Span<const std::byte> (reinterpret_cast<const std::byte*> (id.data()), (size_t) length);
  487. detail::MessageTypeUtils::send (*output, response);
  488. return true;
  489. }
  490. return false;
  491. }
  492. bool messageReceived (const Message::EndpointInquiryResponse& endpoint) const
  493. {
  494. const auto responderMUID = output->getIncomingHeader().source;
  495. const auto iter = device->discovered.find (responderMUID);
  496. if (iter == device->discovered.end())
  497. return false; // Got an endpoint response for a device we haven't discovered
  498. device->listeners.call ([&] (auto& l) { l.endpointReceived (responderMUID, endpoint); });
  499. return true;
  500. }
  501. bool messageReceived (const Message::NAK& nak) const
  502. {
  503. const auto responderMUID = output->getIncomingHeader().source;
  504. device->listeners.call ([&] (auto& l) { l.messageNotAcknowledged (responderMUID, nak); });
  505. return true;
  506. }
  507. bool messageReceived (const Message::ProfileInquiryResponse& response) const
  508. {
  509. const auto responderMUID = output->getIncomingHeader().source;
  510. const auto iter = device->discovered.find (responderMUID);
  511. if (iter == device->discovered.end())
  512. return false;
  513. const auto destination = output->getIncomingHeader().deviceID;
  514. auto* state = iter->second.profileStates.getStateForDestination (output->getChannelAddress());
  515. if (state == nullptr)
  516. return false;
  517. ChannelProfileStates newState;
  518. for (auto& enabled : response.enabledProfiles)
  519. newState.set (enabled, { 1, 1 });
  520. for (auto& disabled : response.disabledProfiles)
  521. newState.set (disabled, { 1, 0 });
  522. *state = newState;
  523. device->listeners.call ([&] (auto& l) { l.profileStateReceived (responderMUID, destination); });
  524. return true;
  525. }
  526. bool messageReceived (const Message::ProfileAdded& added) const
  527. {
  528. const auto responderMUID = output->getIncomingHeader().source;
  529. const auto iter = device->discovered.find (responderMUID);
  530. if (iter == device->discovered.end())
  531. return false;
  532. const auto address = output->getChannelAddress();
  533. auto* state = iter->second.profileStates.getStateForDestination (address);
  534. if (state == nullptr)
  535. return false;
  536. state->set (added.profile, { 1, 0 });
  537. device->listeners.call ([&] (auto& l) { l.profilePresenceChanged (responderMUID, address.getChannel(), added.profile, true); });
  538. return true;
  539. }
  540. bool messageReceived (const Message::ProfileRemoved& removed) const
  541. {
  542. const auto responderMUID = output->getIncomingHeader().source;
  543. const auto iter = device->discovered.find (responderMUID);
  544. if (iter == device->discovered.end())
  545. return false;
  546. const auto address = output->getChannelAddress();
  547. auto* state = iter->second.profileStates.getStateForDestination (address);
  548. if (state == nullptr)
  549. return false;
  550. state->erase (removed.profile);
  551. device->listeners.call ([&] (auto& l) { l.profilePresenceChanged (responderMUID, address.getChannel(), removed.profile, false); });
  552. return true;
  553. }
  554. bool messageReceived (const Message::ProfileEnabledReport& x) const
  555. {
  556. const auto responderMUID = output->getIncomingHeader().source;
  557. const auto iter = device->discovered.find (responderMUID);
  558. if (iter == device->discovered.end())
  559. return false;
  560. const auto address = output->getChannelAddress();
  561. auto* state = iter->second.profileStates.getStateForDestination (address);
  562. if (state == nullptr)
  563. return false;
  564. const auto numChannels = jmax ((uint16_t) 1, x.numChannels);
  565. state->set (x.profile, { state->get (x.profile).supported, numChannels });
  566. device->listeners.call ([&] (auto& l) { l.profileEnablementChanged (responderMUID, address.getChannel(), x.profile, numChannels); });
  567. return true;
  568. }
  569. bool messageReceived (const Message::ProfileDisabledReport& x) const
  570. {
  571. const auto responderMUID = output->getIncomingHeader().source;
  572. const auto iter = device->discovered.find (responderMUID);
  573. if (iter == device->discovered.end())
  574. return false;
  575. const auto address = output->getChannelAddress();
  576. auto* state = iter->second.profileStates.getStateForDestination (address);
  577. if (state == nullptr)
  578. return false;
  579. state->set (x.profile, { state->get (x.profile).supported, 0 });
  580. device->listeners.call ([&] (auto& l) { l.profileEnablementChanged (responderMUID, address.getChannel(), x.profile, 0); });
  581. return true;
  582. }
  583. bool messageReceived (const Message::ProfileDetailsResponse& response) const
  584. {
  585. const auto responderMUID = output->getIncomingHeader().source;
  586. const auto destination = output->getIncomingHeader().deviceID;
  587. device->listeners.call ([&] (auto& l) { l.profileDetailsReceived (responderMUID, destination, response.profile, response.target, response.data); });
  588. return true;
  589. }
  590. bool messageReceived (const Message::ProfileSpecificData& data) const
  591. {
  592. const auto responderMUID = output->getIncomingHeader().source;
  593. const auto destination = output->getIncomingHeader().deviceID;
  594. device->listeners.call ([&] (auto& l) { l.profileSpecificDataReceived (responderMUID, destination, data.profile, data.data); });
  595. return true;
  596. }
  597. bool messageReceived (const Message::PropertyExchangeCapabilitiesResponse&) const
  598. {
  599. const auto source = output->getIncomingHeader().source;
  600. const auto iter = device->discovered.find (source);
  601. constexpr auto hasResource = [] (var obj, auto resource)
  602. {
  603. if (auto* array = obj.getArray())
  604. for (const auto& item : *array)
  605. if (item.isObject() && item.getProperty ("resource", {}) == var (resource))
  606. return true;
  607. return false;
  608. };
  609. const auto onResourceListReceived = [this, iter, source, hasResource] (const PropertyExchangeResult& result)
  610. {
  611. const auto validateResponse = [] (const PropertyExchangeResult& r)
  612. {
  613. const auto parsed = r.getHeaderAsReplyHeader();
  614. return ! r.getError().has_value()
  615. && parsed.mediaType == PropertySubscriptionHeader().mediaType
  616. && parsed.status == 200;
  617. };
  618. const auto allDone = [this, source]
  619. {
  620. device->listeners.call ([source] (auto& l) { l.propertyExchangeCapabilitiesReceived (source); });
  621. };
  622. if (! validateResponse (result))
  623. {
  624. jassertfalse;
  625. allDone();
  626. return;
  627. }
  628. const auto bodyAsObj = Encodings::jsonFrom7BitText (result.getBody());
  629. iter->second.resourceList = bodyAsObj;
  630. const auto onChannelListReceived = [iter, allDone, validateResponse] (const PropertyExchangeResult& r)
  631. {
  632. if (validateResponse (r))
  633. iter->second.channelList = Encodings::jsonFrom7BitText (r.getBody());
  634. allDone();
  635. return;
  636. };
  637. const auto getChannelList = [this, bodyAsObj, source, allDone, hasResource, onChannelListReceived]
  638. {
  639. if (hasResource (bodyAsObj, "ChannelList"))
  640. {
  641. PropertyRequestHeader header;
  642. header.resource = "ChannelList";
  643. device->sendPropertyGetInquiry (source, header, onChannelListReceived);
  644. return;
  645. }
  646. allDone();
  647. return;
  648. };
  649. if (hasResource (bodyAsObj, "DeviceInfo"))
  650. {
  651. PropertyRequestHeader header;
  652. header.resource = "DeviceInfo";
  653. device->sendPropertyGetInquiry (source,
  654. header,
  655. [iter, getChannelList, validateResponse] (const PropertyExchangeResult& r)
  656. {
  657. if (validateResponse (r))
  658. iter->second.deviceInfo = Encodings::jsonFrom7BitText (r.getBody());
  659. getChannelList();
  660. });
  661. return;
  662. }
  663. return getChannelList();
  664. };
  665. PropertyRequestHeader header;
  666. header.resource = "ResourceList";
  667. device->sendPropertyGetInquiry (source, header, onResourceListReceived);
  668. return true;
  669. }
  670. bool handlePropertyDataResponse (const Message::DynamicSizePropertyExchange& response) const
  671. {
  672. const auto responderMUID = output->getIncomingHeader().source;
  673. const auto iter = device->discovered.find (responderMUID);
  674. if (iter == device->discovered.end())
  675. return false;
  676. const auto request = RequestID::create (response.requestID);
  677. if (! request.has_value())
  678. return false;
  679. iter->second.initiatorPropertyCaches.addChunk (*request, response);
  680. return true;
  681. }
  682. bool messageReceived (const Message::PropertyGetDataResponse& response) const
  683. {
  684. handlePropertyDataResponse (response);
  685. return true;
  686. }
  687. bool messageReceived (const Message::PropertySetDataResponse& response) const
  688. {
  689. handlePropertyDataResponse (Message::DynamicSizePropertyExchange { response.requestID,
  690. response.header,
  691. 1,
  692. 1,
  693. {} });
  694. return true;
  695. }
  696. bool messageReceived (const Message::PropertySubscribe& subscription) const
  697. {
  698. const auto responderMUID = output->getIncomingHeader().source;
  699. const auto iter = device->discovered.find (responderMUID);
  700. if (iter == device->discovered.end())
  701. return false;
  702. const auto request = subscription.requestID;
  703. const auto source = output->getIncomingHeader().source;
  704. const auto jsonHeader = Encodings::jsonFrom7BitText (subscription.header);
  705. const auto typedHeader = PropertySubscriptionHeader::parseCondensed (jsonHeader);
  706. const auto subscribeId = typedHeader.subscribeId;
  707. const auto callback = [this, request, source, subscribeId] (const PropertyExchangeResult& result)
  708. {
  709. if (result.getError().has_value())
  710. return;
  711. PropertySubscriptionData data;
  712. data.header = result.getHeaderAsSubscriptionHeader();
  713. data.body = result.getBody();
  714. if (data.header.command == PropertySubscriptionCommand::end)
  715. device->subscriptionManager.endSubscriptionFromResponder (source, subscribeId);
  716. if (data.header.command != PropertySubscriptionCommand::start)
  717. device->listeners.call ([source, &data] (auto& l) { l.propertySubscriptionDataReceived (source, data); });
  718. PropertyReplyHeader header;
  719. const auto headerBytes = Encodings::jsonTo7BitText (header.toVarCondensed());
  720. detail::MessageTypeUtils::send (device->concreteBufferOutput,
  721. device->options.getFunctionBlock().firstGroup,
  722. source,
  723. ChannelInGroup::wholeBlock,
  724. Message::PropertySubscribeResponse { { request, headerBytes, 1, 1, {} } });
  725. };
  726. const auto requestID = RequestID::create (subscription.requestID);
  727. if (! requestID.has_value())
  728. return false;
  729. // Subscription events may be sent at any time by the responder, so there may not be
  730. // an existing transaction ID for new subscription messages.
  731. iter->second.responderPropertyCaches.primeCache (device->propertyDelegate.getNumSimultaneousRequestsSupported(),
  732. callback,
  733. *requestID);
  734. iter->second.responderPropertyCaches.addChunk (*requestID, subscription);
  735. return true;
  736. }
  737. bool messageReceived (const Message::PropertySubscribeResponse& response) const
  738. {
  739. handlePropertyDataResponse (response);
  740. return true;
  741. }
  742. bool messageReceived (const Message::PropertyNotify& notify) const
  743. {
  744. const auto responderMUID = output->getIncomingHeader().source;
  745. const auto iter = device->discovered.find (responderMUID);
  746. if (iter == device->discovered.end())
  747. return false;
  748. const auto requestID = RequestID::create (notify.requestID);
  749. if (! requestID.has_value())
  750. return false;
  751. iter->second.initiatorPropertyCaches.notify (*requestID, notify.header);
  752. iter->second.responderPropertyCaches.notify (*requestID, notify.header);
  753. return true;
  754. }
  755. Impl* device = nullptr;
  756. ResponderOutput* output = nullptr;
  757. bool* handled = nullptr;
  758. };
  759. Impl* device = nullptr;
  760. };
  761. struct Discovered
  762. {
  763. explicit Discovered (Message::Discovery r) : discovery (r) {}
  764. Message::Discovery discovery;
  765. std::optional<Message::PropertyExchangeCapabilitiesResponse> propertyExchangeResponse;
  766. BlockProfileStates profileStates;
  767. InitiatorPropertyExchangeCache initiatorPropertyCaches;
  768. ResponderPropertyExchangeCache responderPropertyCaches;
  769. var resourceList, deviceInfo, channelList;
  770. };
  771. class ConcreteBufferOutput : public BufferOutput
  772. {
  773. public:
  774. explicit ConcreteBufferOutput (Impl& d) : device (d) {}
  775. MUID getMuid() const override { return device.muid; }
  776. std::vector<std::byte>& getOutputBuffer() override { return device.outgoing; }
  777. void send (uint8_t group) override
  778. {
  779. sentMuid = true;
  780. for (auto* o : device.options.getOutputs())
  781. o->processMessage ({ group, getOutputBuffer() });
  782. }
  783. bool hasSentMuid() const { return sentMuid; }
  784. void resetSentMuid() { sentMuid = false; }
  785. private:
  786. Impl& device;
  787. bool sentMuid = false;
  788. };
  789. class CacheProviderImpl : public CacheProvider
  790. {
  791. public:
  792. explicit CacheProviderImpl (Impl& d) : device (d) {}
  793. std::set<MUID> getDiscoveredMuids() const override
  794. {
  795. std::set<MUID> result;
  796. for (const auto& d : device.discovered)
  797. result.insert (d.first);
  798. return result;
  799. }
  800. InitiatorPropertyExchangeCache* getCacheForMuidAsInitiator (MUID m) override
  801. {
  802. const auto iter = device.discovered.find (m);
  803. return iter != device.discovered.end() ? &iter->second.initiatorPropertyCaches : nullptr;
  804. }
  805. ResponderPropertyExchangeCache* getCacheForMuidAsResponder (MUID m) override
  806. {
  807. const auto iter = device.discovered.find (m);
  808. return iter != device.discovered.end() ? &iter->second.responderPropertyCaches : nullptr;
  809. }
  810. int getMaxSysexSizeForMuid (MUID m) const override
  811. {
  812. constexpr auto defaultResult = 1 << 16;
  813. const auto iter = device.discovered.find (m);
  814. return iter != device.discovered.end() ? jmin (defaultResult, (int) iter->second.discovery.maximumSysexSize) : defaultResult;
  815. }
  816. private:
  817. Impl& device;
  818. };
  819. class ProfileDelegateImpl : public ProfileDelegate
  820. {
  821. public:
  822. explicit ProfileDelegateImpl (Impl& d) : device (d) {}
  823. void profileEnablementRequested (MUID x, ProfileAtAddress profileAtAddress, int numChannels, bool enabled) override
  824. {
  825. if (auto* d = device.options.getProfileDelegate())
  826. return d->profileEnablementRequested (x, profileAtAddress, numChannels, enabled);
  827. if (! device.profileHost.has_value())
  828. return;
  829. device.profileHost->setProfileEnablement (profileAtAddress, enabled ? jmax (1, numChannels) : 0);
  830. }
  831. private:
  832. Impl& device;
  833. };
  834. class PropertyDelegateImpl : public PropertyDelegate
  835. {
  836. public:
  837. explicit PropertyDelegateImpl (Impl& d) : device (d) {}
  838. uint8_t getNumSimultaneousRequestsSupported() const override
  839. {
  840. if (auto* d = device.options.getPropertyDelegate())
  841. return d->getNumSimultaneousRequestsSupported();
  842. return 127;
  843. }
  844. PropertyReplyData propertyGetDataRequested (MUID m, const PropertyRequestHeader& header) override
  845. {
  846. if (auto* d = device.options.getPropertyDelegate())
  847. return d->propertyGetDataRequested (m, header);
  848. PropertyReplyData result;
  849. result.header.status = 404; // Resource not found, do not retry
  850. result.header.message = TRANS ("Handling for \"Inquiry: Get Property Data\" is not implemented.");
  851. return result;
  852. }
  853. PropertyReplyHeader propertySetDataRequested (MUID m, const PropertyRequestData& data) override
  854. {
  855. if (auto* d = device.options.getPropertyDelegate())
  856. return d->propertySetDataRequested (m, data);
  857. PropertyReplyHeader result;
  858. result.status = 404; // Resource not found, do not retry
  859. result.message = TRANS ("Handling for \"Inquiry: Set Property Data\" is not implemented.");
  860. return result;
  861. }
  862. bool subscriptionStartRequested (MUID m, const PropertySubscriptionHeader& data) override
  863. {
  864. if (auto* d = device.options.getPropertyDelegate())
  865. return d->subscriptionStartRequested (m, data);
  866. return false;
  867. }
  868. void subscriptionDidStart (MUID m, const String& id, const PropertySubscriptionHeader& data) override
  869. {
  870. if (auto* d = device.options.getPropertyDelegate())
  871. d->subscriptionDidStart (m, id, data);
  872. }
  873. void subscriptionWillEnd (MUID m, const ci::Subscription& subscription) override
  874. {
  875. if (auto* d = device.options.getPropertyDelegate())
  876. d->subscriptionWillEnd (m, subscription);
  877. }
  878. private:
  879. Impl& device;
  880. };
  881. std::optional<RequestKey> sendPropertySubscribe (MUID m,
  882. const PropertySubscriptionHeader& header,
  883. std::function<void (const PropertyExchangeResult&)> onResult) override
  884. {
  885. const auto iter = discovered.find (m);
  886. if (iter == discovered.end())
  887. return {};
  888. const auto primed = iter->second.initiatorPropertyCaches.primeCache (propertyDelegate.getNumSimultaneousRequestsSupported(),
  889. std::move (onResult));
  890. if (! primed.has_value())
  891. return {};
  892. const auto id = iter->second.initiatorPropertyCaches.getRequestIdForToken (*primed);
  893. jassert (id.has_value());
  894. detail::PropertyHostUtils::send (concreteBufferOutput,
  895. options.getFunctionBlock().firstGroup,
  896. detail::MessageMeta::Meta<Message::PropertySubscribe>::subID2,
  897. m,
  898. id->asByte(),
  899. Encodings::jsonTo7BitText (header.toVarCondensed()),
  900. {},
  901. cacheProvider.getMaxSysexSizeForMuid (m));
  902. return RequestKey (m, *primed);
  903. }
  904. void propertySubscriptionChanged (SubscriptionKey key, const std::optional<String>& subscribeId) override
  905. {
  906. listeners.call ([&] (auto& l) { l.propertySubscriptionChanged (key, subscribeId); });
  907. }
  908. static MUID getReallyRandomMuid()
  909. {
  910. Random random;
  911. random.setSeedRandomly();
  912. return MUID::makeRandom (random);
  913. }
  914. static DeviceOptions getValidated (DeviceOptions opt)
  915. {
  916. opt = opt.withMaxSysExSize (jmax ((size_t) 128, opt.getMaxSysExSize()));
  917. if (opt.getFeatures().isPropertyExchangeSupported())
  918. opt = opt.withMaxSysExSize (jmax ((size_t) 512, opt.getMaxSysExSize()));
  919. opt = opt.withFeatures (opt.getFeatures().withProcessInquirySupported (false));
  920. // You'll need to provide some outputs if you want the device to talk to the outside world!
  921. jassert (! opt.getOutputs().empty());
  922. return opt;
  923. }
  924. template <typename Member>
  925. bool supportsFlag (MUID m, Member member) const
  926. {
  927. const auto iter = discovered.find (m);
  928. return iter != discovered.end() && (Features (iter->second.discovery.capabilities).*member)();
  929. }
  930. bool supportsProfiles (MUID m) const
  931. {
  932. return supportsFlag (m, &Features::isProfileConfigurationSupported);
  933. }
  934. bool supportsProperties (MUID m) const
  935. {
  936. return supportsFlag (m, &Features::isPropertyExchangeSupported);
  937. }
  938. DeviceOptions options;
  939. MUID muid;
  940. std::vector<std::byte> outgoing;
  941. std::map<MUID, Discovered> discovered;
  942. SubscriptionManager subscriptionManager { *this };
  943. ListenerList<Listener> listeners;
  944. ConcreteBufferOutput concreteBufferOutput { *this };
  945. CacheProviderImpl cacheProvider { *this };
  946. ProfileDelegateImpl profileDelegate { *this };
  947. PropertyDelegateImpl propertyDelegate { *this };
  948. std::optional<ProfileHost> profileHost;
  949. std::optional<PropertyHost> propertyHost;
  950. };
  951. //==============================================================================
  952. Device::Device (const Options& opt) : pimpl (std::make_unique<Impl> (opt)) {}
  953. Device::~Device() = default;
  954. Device::Device (Device&&) noexcept = default;
  955. Device& Device::operator= (Device&&) noexcept = default;
  956. void Device::processMessage (ump::BytesOnGroup msg) { pimpl->processMessage (msg); }
  957. void Device::sendDiscovery() { pimpl->sendDiscovery(); }
  958. void Device::sendEndpointInquiry (MUID destination, Message::EndpointInquiry endpoint) { pimpl->sendEndpointInquiry (destination, endpoint); }
  959. void Device::sendProfileInquiry (MUID destination, ChannelInGroup address) { pimpl->sendProfileInquiry (destination, address); }
  960. void Device::sendProfileDetailsInquiry (MUID destination, ChannelInGroup address, Profile profile, std::byte target)
  961. {
  962. pimpl->sendProfileDetailsInquiry (destination, address, profile, target);
  963. }
  964. void Device::sendProfileSpecificData (MUID destination, ChannelInGroup address, Profile profile, Span<const std::byte> data)
  965. {
  966. pimpl->sendProfileSpecificData (destination, address, profile, data);
  967. }
  968. void Device::sendProfileEnablement (MUID destination, ChannelInGroup address, Profile profile, int numChannels)
  969. {
  970. pimpl->sendProfileEnablement (destination, address, profile, numChannels);
  971. }
  972. void Device::sendPropertyCapabilitiesInquiry (MUID destination)
  973. {
  974. pimpl->sendPropertyCapabilitiesInquiry (destination);
  975. }
  976. std::optional<RequestKey> Device::sendPropertyGetInquiry (MUID m,
  977. const PropertyRequestHeader& header,
  978. std::function<void (const PropertyExchangeResult&)> onResult)
  979. {
  980. return pimpl->sendPropertyGetInquiry (m, header, std::move (onResult));
  981. }
  982. std::optional<RequestKey> Device::sendPropertySetInquiry (MUID m,
  983. const PropertyRequestHeader& header,
  984. Span<const std::byte> body,
  985. std::function<void (const PropertyExchangeResult&)> onResult)
  986. {
  987. return pimpl->sendPropertySetInquiry (m, header, body, std::move (onResult));
  988. }
  989. void Device::abortPropertyRequest (RequestKey key) { pimpl->abortPropertyRequest (key); }
  990. std::optional<RequestID> Device::getIdForRequestKey (RequestKey key) const { return pimpl->getIdForRequestKey (key); }
  991. std::vector<RequestKey> Device::getOngoingRequests() const { return pimpl->getOngoingRequests(); }
  992. SubscriptionKey Device::beginSubscription (MUID m, const PropertySubscriptionHeader& header) { return pimpl->beginSubscription (m, header); }
  993. void Device::endSubscription (SubscriptionKey key) { pimpl->endSubscription (key); }
  994. std::vector<SubscriptionKey> Device::getOngoingSubscriptions() const { return pimpl->getOngoingSubscriptions(); }
  995. std::optional<String> Device::getSubscribeIdForKey (SubscriptionKey key) const { return pimpl->getSubscribeIdForKey (key); }
  996. std::optional<String> Device::getResourceForKey (SubscriptionKey key) const { return pimpl->getResourceForKey (key); }
  997. bool Device::sendPendingMessages() { return pimpl->sendPendingMessages(); }
  998. void Device::addListener (Listener& l) { pimpl->addListener (l); }
  999. void Device::removeListener (Listener& l) { pimpl->removeListener (l); }
  1000. MUID Device::getMuid() const { return pimpl->getMuid(); }
  1001. DeviceOptions Device::getOptions() const { return pimpl->getOptions(); }
  1002. std::vector<MUID> Device::getDiscoveredMuids() const { return pimpl->getDiscoveredMuids(); }
  1003. const ProfileHost* Device::getProfileHost() const { return pimpl->getProfileHost(); }
  1004. ProfileHost* Device::getProfileHost() { return pimpl->getProfileHost(); }
  1005. const PropertyHost* Device::getPropertyHost() const { return pimpl->getPropertyHost(); }
  1006. PropertyHost* Device::getPropertyHost() { return pimpl->getPropertyHost(); }
  1007. std::optional<Message::Discovery> Device::getDiscoveryInfoForMuid (MUID m) const { return pimpl->getDiscoveryInfoForMuid (m); }
  1008. const ChannelProfileStates* Device::getProfileStateForMuid (MUID m, ChannelAddress address) const { return pimpl->getProfileStateForMuid (m, address); }
  1009. std::optional<int> Device::getNumPropertyExchangeRequestsSupportedForMuid (MUID m) const
  1010. {
  1011. return pimpl->getNumPropertyExchangeRequestsSupportedForMuid (m);
  1012. }
  1013. var Device::getResourceListForMuid (MUID x) const { return pimpl->getResourceListForMuid (x); }
  1014. var Device::getDeviceInfoForMuid (MUID x) const { return pimpl->getDeviceInfoForMuid (x); }
  1015. var Device::getChannelListForMuid (MUID x) const { return pimpl->getChannelListForMuid (x); }
  1016. //==============================================================================
  1017. //==============================================================================
  1018. #if JUCE_UNIT_TESTS
  1019. class DeviceTests : public UnitTest
  1020. {
  1021. public:
  1022. DeviceTests() : UnitTest ("Device", UnitTestCategories::midi) {}
  1023. void runTest() override
  1024. {
  1025. auto random = getRandom();
  1026. struct GroupOutput
  1027. {
  1028. uint8_t group;
  1029. std::vector<std::byte> bytes;
  1030. bool operator== (const GroupOutput& other) const
  1031. {
  1032. const auto tie = [] (const auto& x) { return std::tie (x.group, x.bytes); };
  1033. return tie (*this) == tie (other);
  1034. }
  1035. bool operator!= (const GroupOutput& other) const { return ! operator== (other); }
  1036. };
  1037. struct Output : public DeviceMessageHandler
  1038. {
  1039. void processMessage (ump::BytesOnGroup msg) override
  1040. {
  1041. messages.push_back ({ msg.group, std::vector<std::byte> (msg.bytes.begin(), msg.bytes.end()) });
  1042. }
  1043. std::vector<GroupOutput> messages;
  1044. };
  1045. const ump::DeviceInfo deviceInfo { { std::byte { 0x01 }, std::byte { 0x02 }, std::byte { 0x03 } },
  1046. { std::byte { 0x11 }, std::byte { 0x12 } },
  1047. { std::byte { 0x21 }, std::byte { 0x22 } },
  1048. { std::byte { 0x31 }, std::byte { 0x32 }, std::byte { 0x33 }, std::byte { 0x34 } } };
  1049. FunctionBlock functionBlock;
  1050. beginTest ("When receiving Discovery from a MUID that matches the Device MUID, reply with InvalidateMUID and initiate discovery");
  1051. {
  1052. Output output;
  1053. const auto options = DeviceOptions().withOutputs ({ &output })
  1054. .withFunctionBlock (functionBlock)
  1055. .withDeviceInfo (deviceInfo)
  1056. .withMaxSysExSize (512);
  1057. Device device { options };
  1058. const auto commonMUID = device.getMuid();
  1059. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1060. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  1061. detail::MessageMeta::implementationVersion,
  1062. commonMUID,
  1063. MUID::getBroadcast() },
  1064. Message::Discovery { ump::DeviceInfo { { std::byte { 0x05 }, std::byte { 0x06 }, std::byte { 0x07 } },
  1065. { std::byte { 0x15 }, std::byte { 0x16 } },
  1066. { std::byte { 0x25 }, std::byte { 0x26 } },
  1067. { std::byte { 0x35 }, std::byte { 0x36 }, std::byte { 0x37 }, std::byte { 0x38 } } },
  1068. std::byte{},
  1069. 1024,
  1070. std::byte{} }) });
  1071. expect (device.getMuid() != commonMUID);
  1072. const std::vector<GroupOutput> responses
  1073. {
  1074. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1075. detail::MessageMeta::Meta<Message::InvalidateMUID>::subID2,
  1076. detail::MessageMeta::implementationVersion,
  1077. commonMUID,
  1078. MUID::getBroadcast() },
  1079. Message::InvalidateMUID { commonMUID }) },
  1080. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1081. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  1082. detail::MessageMeta::implementationVersion,
  1083. device.getMuid(),
  1084. MUID::getBroadcast() },
  1085. Message::Discovery { deviceInfo, std::byte{}, 512, std::byte{} }) },
  1086. };
  1087. expect (output.messages == responses);
  1088. }
  1089. beginTest ("When receiving Discovery from a MUID that does not match the Device MUID, reply with DiscoveryResponse and EndpointInquiry");
  1090. {
  1091. Output output;
  1092. const auto options = DeviceOptions().withOutputs ({ &output })
  1093. .withFunctionBlock (functionBlock)
  1094. .withDeviceInfo (deviceInfo)
  1095. .withMaxSysExSize (512);
  1096. Device device { options };
  1097. const auto responderMUID = device.getMuid();
  1098. const auto initiatorMUID = MUID::makeRandom (random);
  1099. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1100. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  1101. detail::MessageMeta::implementationVersion,
  1102. initiatorMUID,
  1103. MUID::getBroadcast() },
  1104. Message::Discovery { ump::DeviceInfo { { std::byte { 0x05 }, std::byte { 0x06 }, std::byte { 0x07 } },
  1105. { std::byte { 0x15 }, std::byte { 0x16 } },
  1106. { std::byte { 0x25 }, std::byte { 0x26 } },
  1107. { std::byte { 0x35 }, std::byte { 0x36 }, std::byte { 0x37 }, std::byte { 0x38 } } },
  1108. std::byte{},
  1109. 1024,
  1110. std::byte{} }) });
  1111. expect (device.getMuid() == responderMUID);
  1112. const std::vector<GroupOutput> responses
  1113. {
  1114. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1115. detail::MessageMeta::Meta<Message::DiscoveryResponse>::subID2,
  1116. detail::MessageMeta::implementationVersion,
  1117. responderMUID,
  1118. initiatorMUID },
  1119. Message::DiscoveryResponse { deviceInfo, std::byte{}, 512, std::byte{}, std::byte { 0x7f } }) },
  1120. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1121. detail::MessageMeta::Meta<Message::EndpointInquiry>::subID2,
  1122. detail::MessageMeta::implementationVersion,
  1123. responderMUID,
  1124. initiatorMUID },
  1125. Message::EndpointInquiry { std::byte{} }) },
  1126. };
  1127. expect (output.messages == responses);
  1128. }
  1129. beginTest ("Sending a V1 discovery message notifies the listener");
  1130. {
  1131. Output output;
  1132. const auto options = DeviceOptions().withOutputs ({ &output })
  1133. .withFunctionBlock (functionBlock)
  1134. .withDeviceInfo (deviceInfo)
  1135. .withMaxSysExSize (512);
  1136. Device device { options };
  1137. const auto responderMUID = device.getMuid();
  1138. const auto initiatorMUID = MUID::makeRandom (random);
  1139. constexpr uint8_t version = 0x01;
  1140. auto bytes = getMessageBytes ({ ChannelInGroup::wholeBlock,
  1141. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  1142. std::byte { version },
  1143. initiatorMUID,
  1144. MUID::getBroadcast() },
  1145. Message::Discovery { ump::DeviceInfo { { std::byte { 0x05 }, std::byte { 0x06 }, std::byte { 0x07 } },
  1146. { std::byte { 0x15 }, std::byte { 0x16 } },
  1147. { std::byte { 0x25 }, std::byte { 0x26 } },
  1148. { std::byte { 0x35 }, std::byte { 0x36 }, std::byte { 0x37 }, std::byte { 0x38 } } },
  1149. std::byte{},
  1150. 1024,
  1151. std::byte{} });
  1152. // V1 message doesn't have an output path
  1153. bytes.pop_back();
  1154. device.processMessage ({ 0, bytes });
  1155. expect (device.getMuid() == responderMUID);
  1156. const std::vector<GroupOutput> responses
  1157. {
  1158. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1159. detail::MessageMeta::Meta<Message::DiscoveryResponse>::subID2,
  1160. detail::MessageMeta::implementationVersion,
  1161. responderMUID,
  1162. initiatorMUID },
  1163. Message::DiscoveryResponse { deviceInfo, std::byte{}, 512, std::byte{}, std::byte { 0x7f } }) },
  1164. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1165. detail::MessageMeta::Meta<Message::EndpointInquiry>::subID2,
  1166. detail::MessageMeta::implementationVersion,
  1167. responderMUID,
  1168. initiatorMUID },
  1169. Message::EndpointInquiry { std::byte{} }) },
  1170. };
  1171. expect (output.messages == responses);
  1172. }
  1173. beginTest ("Sending a V2 discovery message notifies the input listener");
  1174. {
  1175. constexpr std::byte outputPathID { 5 };
  1176. const auto initiatorMUID = MUID::makeRandom (random);
  1177. constexpr std::byte version { 0x02 };
  1178. Output output;
  1179. const auto options = DeviceOptions().withOutputs ({ &output })
  1180. .withFunctionBlock (functionBlock)
  1181. .withDeviceInfo (deviceInfo)
  1182. .withMaxSysExSize (512);
  1183. Device device { options };
  1184. const auto responderMUID = device.getMuid();
  1185. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1186. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  1187. version,
  1188. initiatorMUID,
  1189. MUID::getBroadcast() },
  1190. Message::Discovery { ump::DeviceInfo { { std::byte { 0x05 }, std::byte { 0x06 }, std::byte { 0x07 } },
  1191. { std::byte { 0x15 }, std::byte { 0x16 } },
  1192. { std::byte { 0x25 }, std::byte { 0x26 } },
  1193. { std::byte { 0x35 }, std::byte { 0x36 }, std::byte { 0x37 }, std::byte { 0x38 } } },
  1194. std::byte{},
  1195. 1024,
  1196. outputPathID }) });
  1197. expect (device.getMuid() == responderMUID);
  1198. const std::vector<GroupOutput> responses
  1199. {
  1200. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1201. detail::MessageMeta::Meta<Message::DiscoveryResponse>::subID2,
  1202. detail::MessageMeta::implementationVersion,
  1203. responderMUID,
  1204. initiatorMUID },
  1205. Message::DiscoveryResponse { deviceInfo, std::byte{}, 512, outputPathID, std::byte { 0x7f } }) },
  1206. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1207. detail::MessageMeta::Meta<Message::EndpointInquiry>::subID2,
  1208. detail::MessageMeta::implementationVersion,
  1209. responderMUID,
  1210. initiatorMUID },
  1211. Message::EndpointInquiry { std::byte{} }) },
  1212. };
  1213. expect (output.messages == responses);
  1214. }
  1215. beginTest ("Sending a discovery message with a future version notifies the input listener and ignores trailing fields");
  1216. {
  1217. constexpr std::byte outputPathID { 10 };
  1218. const auto initiatorMUID = MUID::makeRandom (random);
  1219. constexpr std::byte version { 0x03 };
  1220. Output output;
  1221. const auto options = DeviceOptions().withOutputs ({ &output })
  1222. .withFunctionBlock (functionBlock)
  1223. .withDeviceInfo (deviceInfo)
  1224. .withMaxSysExSize (512);
  1225. Device device { options };
  1226. const auto responderMUID = device.getMuid();
  1227. auto bytes = getMessageBytes ({ ChannelInGroup::wholeBlock,
  1228. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  1229. version,
  1230. initiatorMUID,
  1231. MUID::getBroadcast() },
  1232. Message::Discovery { ump::DeviceInfo { { std::byte { 0x05 }, std::byte { 0x06 }, std::byte { 0x07 } },
  1233. { std::byte { 0x15 }, std::byte { 0x16 } },
  1234. { std::byte { 0x25 }, std::byte { 0x26 } },
  1235. { std::byte { 0x35 }, std::byte { 0x36 }, std::byte { 0x37 }, std::byte { 0x38 } } },
  1236. std::byte{},
  1237. 1024,
  1238. outputPathID });
  1239. // Future versions might have more trailing bytes
  1240. bytes.insert (bytes.end(), { std::byte{}, std::byte{} });
  1241. device.processMessage ({ 0, bytes });
  1242. expect (device.getMuid() == responderMUID);
  1243. const std::vector<GroupOutput> responses
  1244. {
  1245. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1246. detail::MessageMeta::Meta<Message::DiscoveryResponse>::subID2,
  1247. detail::MessageMeta::implementationVersion,
  1248. responderMUID,
  1249. initiatorMUID },
  1250. Message::DiscoveryResponse { deviceInfo, std::byte{}, 512, outputPathID, std::byte { 0x7f } }) },
  1251. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1252. detail::MessageMeta::Meta<Message::EndpointInquiry>::subID2,
  1253. detail::MessageMeta::implementationVersion,
  1254. responderMUID,
  1255. initiatorMUID },
  1256. Message::EndpointInquiry { std::byte{} }) },
  1257. };
  1258. expect (output.messages == responses);
  1259. }
  1260. beginTest ("When receiving an InvalidateMUID that matches the Device MUID, initiate discovery using a new MUID");
  1261. {
  1262. Output output;
  1263. const auto options = DeviceOptions().withOutputs ({ &output })
  1264. .withFunctionBlock (functionBlock)
  1265. .withDeviceInfo (deviceInfo)
  1266. .withMaxSysExSize (512);
  1267. Device device { options };
  1268. const auto deviceMUID = device.getMuid();
  1269. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1270. detail::MessageMeta::Meta<Message::InvalidateMUID>::subID2,
  1271. detail::MessageMeta::implementationVersion,
  1272. MUID::makeRandom (random),
  1273. MUID::getBroadcast() },
  1274. Message::InvalidateMUID { deviceMUID }) });
  1275. expect (device.getMuid() != deviceMUID);
  1276. expect (Parser::parse (MUID::makeRandom (random), output.messages.front().bytes) == Message::Parsed { { ChannelInGroup::wholeBlock,
  1277. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  1278. detail::MessageMeta::implementationVersion,
  1279. device.getMuid(),
  1280. MUID::getBroadcast() },
  1281. Message::Discovery { deviceInfo,
  1282. {},
  1283. 512,
  1284. {} } });
  1285. }
  1286. struct Listener : public DeviceListener
  1287. {
  1288. void deviceAdded (MUID x) override { added .push_back (x); }
  1289. void deviceRemoved (MUID x) override { removed.push_back (x); }
  1290. std::vector<MUID> added, removed;
  1291. };
  1292. beginTest ("When receiving a DiscoveryResponse, update the set of known devices, notify outputs, and request endpoint info");
  1293. {
  1294. Listener delegate;
  1295. Output output;
  1296. const auto options = DeviceOptions().withOutputs ({ &output })
  1297. .withFunctionBlock (functionBlock)
  1298. .withDeviceInfo (deviceInfo)
  1299. .withMaxSysExSize (512);
  1300. Device device { options };
  1301. device.addListener (delegate);
  1302. expect (device.getDiscoveredMuids().empty());
  1303. const auto deviceMUID = device.getMuid();
  1304. const auto responderMUID = MUID::makeRandom (random);
  1305. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1306. detail::MessageMeta::Meta<Message::DiscoveryResponse>::subID2,
  1307. detail::MessageMeta::implementationVersion,
  1308. responderMUID,
  1309. deviceMUID },
  1310. Message::DiscoveryResponse { deviceInfo, std::byte{}, 512, std::byte{}, std::byte { 0x7f } }) });
  1311. expect (device.getDiscoveredMuids() == std::vector { responderMUID });
  1312. expect (delegate.added == std::vector { responderMUID });
  1313. std::vector<GroupOutput> responses
  1314. {
  1315. { 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1316. detail::MessageMeta::Meta<Message::EndpointInquiry>::subID2,
  1317. detail::MessageMeta::implementationVersion,
  1318. deviceMUID,
  1319. responderMUID },
  1320. Message::EndpointInquiry { std::byte{} }) },
  1321. };
  1322. expect (output.messages == responses);
  1323. beginTest ("When receiving a DiscoveryResponse with a MUID that matches a known device, invalidate that MUID");
  1324. {
  1325. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1326. detail::MessageMeta::Meta<Message::DiscoveryResponse>::subID2,
  1327. detail::MessageMeta::implementationVersion,
  1328. responderMUID,
  1329. deviceMUID },
  1330. Message::DiscoveryResponse { deviceInfo, std::byte{}, 512, std::byte{}, std::byte { 0x7f } }) });
  1331. expect (device.getDiscoveredMuids().empty());
  1332. expect (delegate.removed == std::vector { responderMUID });
  1333. responses.push_back ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1334. detail::MessageMeta::Meta<Message::InvalidateMUID>::subID2,
  1335. detail::MessageMeta::implementationVersion,
  1336. deviceMUID,
  1337. MUID::getBroadcast() },
  1338. Message::InvalidateMUID { responderMUID }) });
  1339. expect (output.messages == responses);
  1340. }
  1341. }
  1342. beginTest ("After receiving an EndpointResponse, the listener is notified");
  1343. {
  1344. static constexpr std::byte dataBytes[] { std::byte { 0x01 }, std::byte { 0x7f }, std::byte { 0x41 } };
  1345. struct EndpointListener : public DeviceListener
  1346. {
  1347. EndpointListener (UnitTest& t, Device& d) : test (t), device (d) {}
  1348. void endpointReceived (MUID, Message::EndpointInquiryResponse) override { called = true; }
  1349. UnitTest& test;
  1350. Device& device;
  1351. bool called = false;
  1352. };
  1353. Output output;
  1354. const auto options = DeviceOptions().withOutputs ({ &output })
  1355. .withFunctionBlock (functionBlock)
  1356. .withDeviceInfo (deviceInfo)
  1357. .withMaxSysExSize (512);
  1358. Device device { options };
  1359. EndpointListener delegate { *this, device };
  1360. device.addListener (delegate);
  1361. const auto responderMUID = MUID::makeRandom (random);
  1362. const auto deviceMUID = device.getMuid();
  1363. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1364. detail::MessageMeta::Meta<Message::DiscoveryResponse>::subID2,
  1365. detail::MessageMeta::implementationVersion,
  1366. responderMUID,
  1367. deviceMUID },
  1368. Message::DiscoveryResponse { deviceInfo, std::byte{}, 512, std::byte{}, std::byte { 0x7f } }) });
  1369. expect (! delegate.called);
  1370. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1371. detail::MessageMeta::Meta<Message::EndpointInquiryResponse>::subID2,
  1372. detail::MessageMeta::implementationVersion,
  1373. responderMUID,
  1374. deviceMUID },
  1375. Message::EndpointInquiryResponse { std::byte{}, dataBytes }) });
  1376. expect (delegate.called);
  1377. }
  1378. beginTest ("If a device has not previously acted as a responder, modifying profiles does not emit events");
  1379. {
  1380. Output output;
  1381. const auto options = DeviceOptions().withOutputs ({ &output })
  1382. .withFunctionBlock (functionBlock)
  1383. .withDeviceInfo (deviceInfo)
  1384. .withMaxSysExSize (512)
  1385. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true));
  1386. Device device { options };
  1387. expect (device.getProfileHost() != nullptr);
  1388. const Profile profile { std::byte { 0x01 },
  1389. std::byte { 0x02 },
  1390. std::byte { 0x03 },
  1391. std::byte { 0x04 },
  1392. std::byte { 0x05 } };
  1393. device.getProfileHost()->addProfile ({ profile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) });
  1394. expect (output.messages.empty());
  1395. beginTest ("The device reports profiles accurately");
  1396. {
  1397. const auto inquiryMUID = MUID::makeRandom (random);
  1398. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1399. detail::MessageMeta::Meta<Message::ProfileInquiry>::subID2,
  1400. detail::MessageMeta::implementationVersion,
  1401. inquiryMUID,
  1402. device.getMuid() },
  1403. Message::ProfileInquiry{}) });
  1404. const Profile disabledProfiles[] { profile };
  1405. expect (output.messages.size() == 1);
  1406. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1407. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1408. detail::MessageMeta::implementationVersion,
  1409. device.getMuid(),
  1410. inquiryMUID },
  1411. Message::ProfileInquiryResponse { {}, disabledProfiles }));
  1412. }
  1413. beginTest ("If a device has previously acted as a responder to profile inquiry, then modifying profiles emits events");
  1414. {
  1415. device.getProfileHost()->setProfileEnablement ({ profile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) }, 1);
  1416. expect (output.messages.size() == 2);
  1417. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1418. detail::MessageMeta::Meta<Message::ProfileEnabledReport>::subID2,
  1419. detail::MessageMeta::implementationVersion,
  1420. device.getMuid(),
  1421. MUID::getBroadcast() },
  1422. Message::ProfileEnabledReport { profile, 0 }));
  1423. }
  1424. }
  1425. beginTest ("If a device receives a details inquiry message addressed to an unsupported profile, a NAK with a code of 0x04 is emitted");
  1426. {
  1427. Output output;
  1428. const auto options = DeviceOptions().withOutputs ({ &output })
  1429. .withFunctionBlock (functionBlock)
  1430. .withDeviceInfo (deviceInfo)
  1431. .withMaxSysExSize (512)
  1432. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true));
  1433. Device device { options };
  1434. expect (device.getProfileHost() != nullptr);
  1435. const auto inquiryMUID = MUID::makeRandom (random);
  1436. const Profile profile { std::byte { 0x01 },
  1437. std::byte { 0x02 },
  1438. std::byte { 0x03 },
  1439. std::byte { 0x04 },
  1440. std::byte { 0x05 } };
  1441. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1442. detail::MessageMeta::Meta<Message::ProfileDetails>::subID2,
  1443. detail::MessageMeta::implementationVersion,
  1444. inquiryMUID,
  1445. device.getMuid() },
  1446. Message::ProfileDetails { profile, std::byte { 0x02 } }) });
  1447. expect (output.messages.size() == 1);
  1448. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1449. detail::MessageMeta::Meta<Message::NAK>::subID2,
  1450. detail::MessageMeta::implementationVersion,
  1451. device.getMuid(),
  1452. inquiryMUID },
  1453. Message::NAK { detail::MessageMeta::Meta<Message::ProfileDetails>::subID2,
  1454. std::byte { 0x04 },
  1455. {},
  1456. {},
  1457. {} }));
  1458. }
  1459. beginTest ("If a device receives a set profile on and enables the profile, profile enabled report is emitted");
  1460. {
  1461. // Note: if there's no explicit profile delegate, the device will toggle profiles as requested.
  1462. Output output;
  1463. const auto options = DeviceOptions().withOutputs ({ &output })
  1464. .withFunctionBlock (functionBlock)
  1465. .withDeviceInfo (deviceInfo)
  1466. .withMaxSysExSize (512)
  1467. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true));
  1468. Device device { options };
  1469. expect (device.getProfileHost() != nullptr);
  1470. const Profile profile { std::byte { 0x01 },
  1471. std::byte { 0x02 },
  1472. std::byte { 0x03 },
  1473. std::byte { 0x04 },
  1474. std::byte { 0x05 } };
  1475. device.getProfileHost()->addProfile ({ profile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) });
  1476. const auto inquiryMUID = MUID::makeRandom (random);
  1477. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1478. detail::MessageMeta::Meta<Message::ProfileOn>::subID2,
  1479. detail::MessageMeta::implementationVersion,
  1480. inquiryMUID,
  1481. device.getMuid() },
  1482. Message::ProfileOn { profile, 0 }) });
  1483. expect (output.messages.size() == 1);
  1484. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1485. detail::MessageMeta::Meta<Message::ProfileEnabledReport>::subID2,
  1486. detail::MessageMeta::implementationVersion,
  1487. device.getMuid(),
  1488. MUID::getBroadcast() },
  1489. Message::ProfileEnabledReport { profile, 0 }));
  1490. }
  1491. struct DoNothingProfileDelegate : public ProfileDelegate
  1492. {
  1493. void profileEnablementRequested (MUID, ProfileAtAddress, int, bool) override {}
  1494. };
  1495. beginTest ("If a device receives a set profile on but then doesn't enable the profile, profile disabled report is emitted");
  1496. {
  1497. DoNothingProfileDelegate delegate;
  1498. Output output;
  1499. const auto options = DeviceOptions().withOutputs ({ &output })
  1500. .withFunctionBlock (functionBlock)
  1501. .withDeviceInfo (deviceInfo)
  1502. .withMaxSysExSize (512)
  1503. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true))
  1504. .withProfileDelegate (&delegate);
  1505. Device device { options };
  1506. expect (device.getProfileHost() != nullptr);
  1507. const Profile profile { std::byte { 0x01 },
  1508. std::byte { 0x02 },
  1509. std::byte { 0x03 },
  1510. std::byte { 0x04 },
  1511. std::byte { 0x05 } };
  1512. device.getProfileHost()->addProfile ({ profile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) });
  1513. const auto inquiryMUID = MUID::makeRandom (random);
  1514. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1515. detail::MessageMeta::Meta<Message::ProfileOn>::subID2,
  1516. detail::MessageMeta::implementationVersion,
  1517. inquiryMUID,
  1518. device.getMuid() },
  1519. Message::ProfileOn { profile, 1 }) });
  1520. expect (output.messages.size() == 1);
  1521. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1522. detail::MessageMeta::Meta<Message::ProfileDisabledReport>::subID2,
  1523. detail::MessageMeta::implementationVersion,
  1524. device.getMuid(),
  1525. MUID::getBroadcast() },
  1526. Message::ProfileDisabledReport { profile, {} }));
  1527. }
  1528. beginTest ("If a device receives a set profile on for an unsupported profile, NAK is emitted");
  1529. {
  1530. Output output;
  1531. const auto options = DeviceOptions().withOutputs ({ &output })
  1532. .withFunctionBlock (functionBlock)
  1533. .withDeviceInfo (deviceInfo)
  1534. .withMaxSysExSize (512)
  1535. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true));
  1536. Device device { options };
  1537. expect (device.getProfileHost() != nullptr);
  1538. const Profile profile { std::byte { 0x01 },
  1539. std::byte { 0x02 },
  1540. std::byte { 0x03 },
  1541. std::byte { 0x04 },
  1542. std::byte { 0x05 } };
  1543. const auto inquiryMUID = MUID::makeRandom (random);
  1544. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1545. detail::MessageMeta::Meta<Message::ProfileOn>::subID2,
  1546. detail::MessageMeta::implementationVersion,
  1547. inquiryMUID,
  1548. device.getMuid() },
  1549. Message::ProfileOn { profile, 1 }) });
  1550. expect (output.messages.size() == 1);
  1551. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1552. detail::MessageMeta::Meta<Message::NAK>::subID2,
  1553. detail::MessageMeta::implementationVersion,
  1554. device.getMuid(),
  1555. inquiryMUID },
  1556. Message::NAK { detail::MessageMeta::Meta<Message::ProfileOn>::subID2,
  1557. {},
  1558. {},
  1559. {},
  1560. {} }));
  1561. }
  1562. beginTest ("If a device receives a set profile off and disables the profile, profile disabled report is emitted");
  1563. {
  1564. // Note: if there's no explicit profile delegate, the device will toggle profiles as requested.
  1565. Output output;
  1566. const auto options = DeviceOptions().withOutputs ({ &output })
  1567. .withFunctionBlock (functionBlock)
  1568. .withDeviceInfo (deviceInfo)
  1569. .withMaxSysExSize (512)
  1570. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true));
  1571. Device device { options };
  1572. expect (device.getProfileHost() != nullptr);
  1573. const Profile profile { std::byte { 0x01 },
  1574. std::byte { 0x02 },
  1575. std::byte { 0x03 },
  1576. std::byte { 0x04 },
  1577. std::byte { 0x05 } };
  1578. device.getProfileHost()->addProfile ({ profile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) });
  1579. device.getProfileHost()->setProfileEnablement ({ profile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) }, 0);
  1580. const auto inquiryMUID = MUID::makeRandom (random);
  1581. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1582. detail::MessageMeta::Meta<Message::ProfileOff>::subID2,
  1583. detail::MessageMeta::implementationVersion,
  1584. inquiryMUID,
  1585. device.getMuid() },
  1586. Message::ProfileOff { profile }) });
  1587. expect (output.messages.size() == 1);
  1588. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1589. detail::MessageMeta::Meta<Message::ProfileDisabledReport>::subID2,
  1590. detail::MessageMeta::implementationVersion,
  1591. device.getMuid(),
  1592. MUID::getBroadcast() },
  1593. Message::ProfileDisabledReport { profile, {} }));
  1594. }
  1595. beginTest ("If a device receives a set profile off but then doesn't disable the profile, profile enabled report is emitted");
  1596. {
  1597. Output output;
  1598. DoNothingProfileDelegate delegate;
  1599. const auto options = DeviceOptions().withOutputs ({ &output })
  1600. .withFunctionBlock (functionBlock)
  1601. .withDeviceInfo (deviceInfo)
  1602. .withMaxSysExSize (512)
  1603. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true))
  1604. .withProfileDelegate (&delegate);
  1605. Device device { options };
  1606. expect (device.getProfileHost() != nullptr);
  1607. const Profile profile { std::byte { 0x01 },
  1608. std::byte { 0x02 },
  1609. std::byte { 0x03 },
  1610. std::byte { 0x04 },
  1611. std::byte { 0x05 } };
  1612. device.getProfileHost()->addProfile ({ profile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) });
  1613. device.getProfileHost()->setProfileEnablement ({ profile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) }, 1);
  1614. const auto inquiryMUID = MUID::makeRandom (random);
  1615. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1616. detail::MessageMeta::Meta<Message::ProfileOff>::subID2,
  1617. detail::MessageMeta::implementationVersion,
  1618. inquiryMUID,
  1619. device.getMuid() },
  1620. Message::ProfileOff { profile }) });
  1621. expect (output.messages.size() == 1);
  1622. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1623. detail::MessageMeta::Meta<Message::ProfileEnabledReport>::subID2,
  1624. detail::MessageMeta::implementationVersion,
  1625. device.getMuid(),
  1626. MUID::getBroadcast() },
  1627. Message::ProfileEnabledReport { profile, 0 }));
  1628. }
  1629. beginTest ("If a device receives a set profile off for an unsupported profile, NAK is emitted");
  1630. {
  1631. Output output;
  1632. const auto options = DeviceOptions().withOutputs ({ &output })
  1633. .withFunctionBlock (functionBlock)
  1634. .withDeviceInfo (deviceInfo)
  1635. .withMaxSysExSize (512)
  1636. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true));
  1637. Device device { options };
  1638. expect (device.getProfileHost() != nullptr);
  1639. const Profile profile { std::byte { 0x01 },
  1640. std::byte { 0x02 },
  1641. std::byte { 0x03 },
  1642. std::byte { 0x04 },
  1643. std::byte { 0x05 } };
  1644. const auto inquiryMUID = MUID::makeRandom (random);
  1645. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1646. detail::MessageMeta::Meta<Message::ProfileOff>::subID2,
  1647. detail::MessageMeta::implementationVersion,
  1648. inquiryMUID,
  1649. device.getMuid() },
  1650. Message::ProfileOff { profile }) });
  1651. expect (output.messages.size() == 1);
  1652. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeBlock,
  1653. detail::MessageMeta::Meta<Message::NAK>::subID2,
  1654. detail::MessageMeta::implementationVersion,
  1655. device.getMuid(),
  1656. inquiryMUID },
  1657. Message::NAK { detail::MessageMeta::Meta<Message::ProfileOff>::subID2,
  1658. {},
  1659. {},
  1660. {},
  1661. {} }));
  1662. }
  1663. const FunctionBlock realBlock { std::byte{}, 0, 3 };
  1664. beginTest ("If a device receives a profile inquiry addressed to a channel, that channel's profiles are emitted");
  1665. {
  1666. Output output;
  1667. const auto options = DeviceOptions().withOutputs ({ &output })
  1668. .withFunctionBlock (realBlock)
  1669. .withDeviceInfo (deviceInfo)
  1670. .withMaxSysExSize (512)
  1671. .withFeatures (DeviceFeatures{}.withProfileConfigurationSupported (true));
  1672. Device device { options };
  1673. auto& profileHost = *device.getProfileHost();
  1674. Profile channel0Profile { std::byte { 0x01 } };
  1675. Profile channel1Profile { std::byte { 0x02 } };
  1676. profileHost.addProfile ({ channel0Profile, ChannelAddress{}.withChannel (ChannelInGroup::channel0) });
  1677. profileHost.addProfile ({ channel1Profile, ChannelAddress{}.withChannel (ChannelInGroup::channel1) });
  1678. const auto inquiryMUID = MUID::makeRandom (random);
  1679. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::channel0,
  1680. detail::MessageMeta::Meta<Message::ProfileInquiry>::subID2,
  1681. detail::MessageMeta::implementationVersion,
  1682. inquiryMUID,
  1683. device.getMuid() },
  1684. Message::ProfileInquiry{}) });
  1685. const Profile channel0Profiles[] { channel0Profile };
  1686. const Profile channel1Profiles[] { channel1Profile };
  1687. expect (output.messages.size() == 1);
  1688. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::channel0,
  1689. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1690. detail::MessageMeta::implementationVersion,
  1691. device.getMuid(),
  1692. inquiryMUID },
  1693. Message::ProfileInquiryResponse { {}, channel0Profiles }));
  1694. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::channel2,
  1695. detail::MessageMeta::Meta<Message::ProfileInquiry>::subID2,
  1696. detail::MessageMeta::implementationVersion,
  1697. inquiryMUID,
  1698. device.getMuid() },
  1699. Message::ProfileInquiry{}) });
  1700. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::channel2,
  1701. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1702. detail::MessageMeta::implementationVersion,
  1703. device.getMuid(),
  1704. inquiryMUID },
  1705. Message::ProfileInquiryResponse { {}, {} }));
  1706. Profile group0Profile { std::byte { 0x05 } };
  1707. Profile group1Profile { std::byte { 0x06 } };
  1708. const Profile group0Profiles[] { group0Profile };
  1709. const Profile group1Profiles[] { group1Profile };
  1710. beginTest ("If a device receives a profile inquiry addressed to a group, that group's profiles are emitted");
  1711. {
  1712. profileHost.addProfile ({ group0Profile, ChannelAddress{}.withGroup (0).withChannel (ChannelInGroup::wholeGroup) });
  1713. profileHost.addProfile ({ group1Profile, ChannelAddress{}.withGroup (1).withChannel (ChannelInGroup::wholeGroup) });
  1714. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeGroup,
  1715. detail::MessageMeta::Meta<Message::ProfileInquiry>::subID2,
  1716. detail::MessageMeta::implementationVersion,
  1717. inquiryMUID,
  1718. device.getMuid() },
  1719. Message::ProfileInquiry{}) });
  1720. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeGroup,
  1721. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1722. detail::MessageMeta::implementationVersion,
  1723. device.getMuid(),
  1724. inquiryMUID },
  1725. Message::ProfileInquiryResponse { {}, group0Profiles }));
  1726. device.processMessage ({ 2, getMessageBytes ({ ChannelInGroup::wholeGroup,
  1727. detail::MessageMeta::Meta<Message::ProfileInquiry>::subID2,
  1728. detail::MessageMeta::implementationVersion,
  1729. inquiryMUID,
  1730. device.getMuid() },
  1731. Message::ProfileInquiry{}) });
  1732. expect (output.messages.back().bytes == getMessageBytes ({ ChannelInGroup::wholeGroup,
  1733. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1734. detail::MessageMeta::implementationVersion,
  1735. device.getMuid(),
  1736. inquiryMUID },
  1737. Message::ProfileInquiryResponse { {}, {} }));
  1738. }
  1739. beginTest ("If a device receives a profile inquiry addressed to a block, the profiles for member channels, then member groups, then the block are emitted");
  1740. {
  1741. Profile blockProfile { std::byte { 0x0a } };
  1742. profileHost.addProfile ({ blockProfile, ChannelAddress{}.withChannel (ChannelInGroup::wholeBlock) });
  1743. output.messages.clear();
  1744. device.processMessage ({ 1, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1745. detail::MessageMeta::Meta<Message::ProfileInquiry>::subID2,
  1746. detail::MessageMeta::implementationVersion,
  1747. inquiryMUID,
  1748. device.getMuid() },
  1749. Message::ProfileInquiry{}) });
  1750. const Profile blockProfiles[] { blockProfile };
  1751. expect (output.messages == std::vector<GroupOutput> { { 0, getMessageBytes ({ ChannelInGroup::channel0,
  1752. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1753. detail::MessageMeta::implementationVersion,
  1754. device.getMuid(),
  1755. inquiryMUID },
  1756. Message::ProfileInquiryResponse { {}, channel0Profiles }) },
  1757. { 0, getMessageBytes ({ ChannelInGroup::channel1,
  1758. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1759. detail::MessageMeta::implementationVersion,
  1760. device.getMuid(),
  1761. inquiryMUID },
  1762. Message::ProfileInquiryResponse { {}, channel1Profiles }) },
  1763. { 0, getMessageBytes ({ ChannelInGroup::wholeGroup,
  1764. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1765. detail::MessageMeta::implementationVersion,
  1766. device.getMuid(),
  1767. inquiryMUID },
  1768. Message::ProfileInquiryResponse { {}, group0Profiles }) },
  1769. { 1, getMessageBytes ({ ChannelInGroup::wholeGroup,
  1770. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1771. detail::MessageMeta::implementationVersion,
  1772. device.getMuid(),
  1773. inquiryMUID },
  1774. Message::ProfileInquiryResponse { {}, group1Profiles }) },
  1775. { 1, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1776. detail::MessageMeta::Meta<Message::ProfileInquiryResponse>::subID2,
  1777. detail::MessageMeta::implementationVersion,
  1778. device.getMuid(),
  1779. inquiryMUID },
  1780. Message::ProfileInquiryResponse { {}, blockProfiles }) } });
  1781. }
  1782. }
  1783. // Property exchange
  1784. {
  1785. const auto inquiryMUID = MUID::makeRandom (random);
  1786. struct Delegate : public PropertyDelegate
  1787. {
  1788. uint8_t getNumSimultaneousRequestsSupported() const override { return 1; }
  1789. PropertyReplyData propertyGetDataRequested (MUID, const PropertyRequestHeader&) override { return {}; }
  1790. PropertyReplyHeader propertySetDataRequested (MUID, const PropertyRequestData&) override { return {}; }
  1791. bool subscriptionStartRequested (MUID, const PropertySubscriptionHeader&) override { return true; }
  1792. void subscriptionDidStart (MUID, const String&, const PropertySubscriptionHeader&) override {}
  1793. void subscriptionWillEnd (MUID, const Subscription&) override {}
  1794. };
  1795. Delegate delegate;
  1796. Output output;
  1797. const auto options = DeviceOptions().withOutputs ({ &output })
  1798. .withFunctionBlock (realBlock)
  1799. .withDeviceInfo (deviceInfo)
  1800. .withMaxSysExSize (512)
  1801. .withFeatures (DeviceFeatures{}.withPropertyExchangeSupported (true))
  1802. .withPropertyDelegate (&delegate);
  1803. Device device { options };
  1804. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1805. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  1806. detail::MessageMeta::implementationVersion,
  1807. inquiryMUID,
  1808. MUID::getBroadcast() },
  1809. Message::Discovery { {}, DeviceFeatures{}.withPropertyExchangeSupported (true).getSupportedCapabilities(), 512, {} }) });
  1810. expect (output.messages.size() == 2);
  1811. output.messages.clear();
  1812. beginTest ("If a device receives too many concurrent property exchange requests, it responds with a retry status code.");
  1813. {
  1814. auto obj = std::make_unique<DynamicObject>();
  1815. obj->setProperty ("resource", "X-CustomProp");
  1816. const auto header = Encodings::jsonTo7BitText (obj.release());
  1817. for (const auto& requestID : { std::byte { 0 }, std::byte { 1 } })
  1818. {
  1819. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1820. detail::MessageMeta::Meta<Message::PropertySetData>::subID2,
  1821. detail::MessageMeta::implementationVersion,
  1822. inquiryMUID,
  1823. device.getMuid() },
  1824. Message::PropertySetData { { requestID, header, 0, 1, {} } }) });
  1825. }
  1826. expect (output.messages.size() == 1);
  1827. const auto parsed = Parser::parse (output.messages.back().bytes);
  1828. expect (parsed.has_value());
  1829. expect (parsed->header == Message::Header { ChannelInGroup::wholeBlock,
  1830. detail::MessageMeta::Meta<Message::PropertySetDataResponse>::subID2,
  1831. detail::MessageMeta::implementationVersion,
  1832. device.getMuid(),
  1833. inquiryMUID });
  1834. auto* body = std::get_if<Message::PropertySetDataResponse> (&parsed->body);
  1835. expect (body != nullptr);
  1836. expect (body->requestID == std::byte { 1 });
  1837. auto replyHeader = Encodings::jsonFrom7BitText (body->header);
  1838. expect (replyHeader.getProperty ("status", "") == var (343));
  1839. }
  1840. // Terminate ongoing message
  1841. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1842. detail::MessageMeta::Meta<Message::PropertySetData>::subID2,
  1843. detail::MessageMeta::implementationVersion,
  1844. inquiryMUID,
  1845. device.getMuid() },
  1846. Message::PropertySetData { { {}, {}, 0, 0, {} } }) });
  1847. output.messages.clear();
  1848. beginTest ("If a device receives an unexpectedly-terminated request, it responds with an error status code.");
  1849. {
  1850. auto obj = std::make_unique<DynamicObject>();
  1851. obj->setProperty ("resource", "X-CustomProp");
  1852. const auto header = Encodings::jsonTo7BitText (obj.release());
  1853. const std::byte requestID { 3 };
  1854. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1855. detail::MessageMeta::Meta<Message::PropertySetData>::subID2,
  1856. detail::MessageMeta::implementationVersion,
  1857. inquiryMUID,
  1858. device.getMuid() },
  1859. Message::PropertySetData { { requestID, header, 2, 1, {} } }) });
  1860. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1861. detail::MessageMeta::Meta<Message::PropertySetData>::subID2,
  1862. detail::MessageMeta::implementationVersion,
  1863. inquiryMUID,
  1864. device.getMuid() },
  1865. Message::PropertySetData { { requestID, header, 2, 0, {} } }) });
  1866. expect (output.messages.size() == 1);
  1867. const auto parsed = Parser::parse (output.messages.back().bytes);
  1868. expect (parsed.has_value());
  1869. expect (parsed->header == Message::Header { ChannelInGroup::wholeBlock,
  1870. detail::MessageMeta::Meta<Message::PropertySetDataResponse>::subID2,
  1871. detail::MessageMeta::implementationVersion,
  1872. device.getMuid(),
  1873. inquiryMUID });
  1874. auto* body = std::get_if<Message::PropertySetDataResponse> (&parsed->body);
  1875. expect (body != nullptr);
  1876. expect (body->requestID == requestID);
  1877. auto replyHeader = Encodings::jsonFrom7BitText (body->header);
  1878. expect (replyHeader.getProperty ("status", "") == var (400));
  1879. }
  1880. output.messages.clear();
  1881. const auto makeStatusHeader = [] (int status)
  1882. {
  1883. auto ptr = std::make_unique<DynamicObject>();
  1884. ptr->setProperty ("status", status);
  1885. return Encodings::jsonTo7BitText (ptr.release());
  1886. };
  1887. const auto successHeader = makeStatusHeader (200);
  1888. const auto retryHeader = makeStatusHeader (343);
  1889. const auto cancelHeader = makeStatusHeader (144);
  1890. // Common rules for PE section 10: There is no reply message associated with any Notify message.
  1891. beginTest ("If a request is terminated via notify, the device does not respond");
  1892. {
  1893. auto obj = std::make_unique<DynamicObject>();
  1894. obj->setProperty ("resource", "X-CustomProp");
  1895. const auto header = Encodings::jsonTo7BitText (obj.release());
  1896. const std::byte requestID { 100 };
  1897. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1898. detail::MessageMeta::Meta<Message::PropertySetData>::subID2,
  1899. detail::MessageMeta::implementationVersion,
  1900. inquiryMUID,
  1901. device.getMuid() },
  1902. Message::PropertySetData { { requestID, header, 2, 1, {} } }) });
  1903. expect (device.getPropertyHost()->countOngoingTransactions() == 1);
  1904. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1905. detail::MessageMeta::Meta<Message::PropertyNotify>::subID2,
  1906. detail::MessageMeta::implementationVersion,
  1907. inquiryMUID,
  1908. device.getMuid() },
  1909. Message::PropertyNotify { { requestID, cancelHeader, 1, 1, {} } }) });
  1910. expect (device.getPropertyHost()->countOngoingTransactions() == 0);
  1911. expect (output.messages.empty());
  1912. }
  1913. beginTest ("Sending too many property requests simultaneously fails");
  1914. {
  1915. PropertyRequestHeader header;
  1916. header.resource = "X-CustomProp";
  1917. const auto a = device.sendPropertyGetInquiry (inquiryMUID, header, [] (const PropertyExchangeResult&) {});
  1918. expect (a.has_value());
  1919. expect (device.getOngoingRequests() == std::vector { *a });
  1920. // Our device only supports 1 simultaneous request, so this should fail to send
  1921. const auto b = device.sendPropertyGetInquiry (inquiryMUID, header, [] (const PropertyExchangeResult&) {});
  1922. expect (! b.has_value());
  1923. expect (device.getOngoingRequests() == std::vector { *a });
  1924. // Reply to the first request
  1925. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1926. detail::MessageMeta::Meta<Message::PropertyGetDataResponse>::subID2,
  1927. detail::MessageMeta::implementationVersion,
  1928. inquiryMUID,
  1929. device.getMuid() },
  1930. Message::PropertyGetDataResponse { { device.getIdForRequestKey (*a)->asByte(), successHeader, 1, 1, {} } }) });
  1931. // Now that a response to the first request has been received, there should be no
  1932. // requests in progress.
  1933. expect (device.getOngoingRequests().empty());
  1934. }
  1935. output.messages.clear();
  1936. beginTest ("Aborting a property request sends a property notify");
  1937. {
  1938. PropertyRequestHeader header;
  1939. header.resource = "X-CustomProp";
  1940. bool callbackCalled = false;
  1941. const auto a = device.sendPropertyGetInquiry (inquiryMUID, header, [&] (const PropertyExchangeResult&)
  1942. {
  1943. callbackCalled = true;
  1944. });
  1945. expect (a.has_value());
  1946. expect (device.getOngoingRequests() == std::vector { *a });
  1947. expect (! callbackCalled);
  1948. const auto requestID = device.getIdForRequestKey (*a);
  1949. device.abortPropertyRequest (*a);
  1950. expect (device.getOngoingRequests().empty());
  1951. expect (! callbackCalled);
  1952. expect (output.messages.size() == 2);
  1953. const auto inquiry = Parser::parse (output.messages.front().bytes);
  1954. expect (inquiry.has_value());
  1955. expect (inquiry->header == Message::Header { ChannelInGroup::wholeBlock,
  1956. detail::MessageMeta::Meta<Message::PropertyGetData>::subID2,
  1957. detail::MessageMeta::implementationVersion,
  1958. device.getMuid(),
  1959. inquiryMUID });
  1960. const auto notify = Parser::parse (output.messages.back().bytes);
  1961. expect (notify.has_value());
  1962. expect (notify->header == Message::Header { ChannelInGroup::wholeBlock,
  1963. detail::MessageMeta::Meta<Message::PropertyNotify>::subID2,
  1964. detail::MessageMeta::implementationVersion,
  1965. device.getMuid(),
  1966. inquiryMUID });
  1967. auto* body = std::get_if<Message::PropertyNotify> (&notify->body);
  1968. expect (body != nullptr);
  1969. expect (body->requestID == requestID->asByte());
  1970. expect (body->thisChunkNum == 1);
  1971. expect (body->totalNumChunks == 1);
  1972. const auto replyHeader = Encodings::jsonFrom7BitText (body->header);
  1973. expect (replyHeader.getProperty ("status", "") == var (144));
  1974. }
  1975. output.messages.clear();
  1976. beginTest ("Aborting a completed property request does nothing");
  1977. {
  1978. PropertyRequestHeader header;
  1979. header.resource = "X-CustomProp";
  1980. const auto a = device.sendPropertyGetInquiry (inquiryMUID, header, [&] (const PropertyExchangeResult&) {});
  1981. expect (a.has_value());
  1982. expect (device.getOngoingRequests() == std::vector { *a });
  1983. // Reply to the get data request
  1984. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  1985. detail::MessageMeta::Meta<Message::PropertyGetDataResponse>::subID2,
  1986. detail::MessageMeta::implementationVersion,
  1987. inquiryMUID,
  1988. device.getMuid() },
  1989. Message::PropertyGetDataResponse { { device.getIdForRequestKey (*a)->asByte(), successHeader, 1, 1, {} } }) });
  1990. // After replying, there should be no ongoing requests
  1991. expect (device.getOngoingRequests().empty());
  1992. expect (output.messages.size() == 1);
  1993. // This request has already finished
  1994. device.abortPropertyRequest (*a);
  1995. expect (device.getOngoingRequests().empty());
  1996. expect (output.messages.size() == 1);
  1997. }
  1998. output.messages.clear();
  1999. beginTest ("Beginning a subscription and ending it before the remote device replies causes a property notify to be sent");
  2000. {
  2001. PropertySubscriptionHeader header;
  2002. header.command = PropertySubscriptionCommand::start;
  2003. header.resource = "X-CustomProp";
  2004. const auto a = device.beginSubscription (inquiryMUID, header);
  2005. expect (device.getOngoingSubscriptions() == std::vector { a });
  2006. // Sending a subscription request uses a request slot
  2007. expect (device.getOngoingRequests().size() == 1);
  2008. // subscription id is empty until the responder confirms the subscription
  2009. expect (! device.getSubscribeIdForKey (a).has_value());
  2010. expect (device.getResourceForKey (a) == header.resource);
  2011. expect (output.messages.size() == 1);
  2012. {
  2013. const auto parsed = Parser::parse (output.messages.back().bytes);
  2014. expect (parsed.has_value());
  2015. expect (parsed->header == Message::Header { ChannelInGroup::wholeBlock,
  2016. detail::MessageMeta::Meta<Message::PropertySubscribe>::subID2,
  2017. detail::MessageMeta::implementationVersion,
  2018. device.getMuid(),
  2019. inquiryMUID });
  2020. auto* body = std::get_if<Message::PropertySubscribe> (&parsed->body);
  2021. expect (body != nullptr);
  2022. const auto bodyHeader = Encodings::jsonFrom7BitText (body->header);
  2023. expect (bodyHeader.getProperty ("command", "") == var ("start"));
  2024. }
  2025. output.messages.clear();
  2026. const auto requestID = device.getIdForRequestKey (device.getOngoingRequests().back());
  2027. device.endSubscription (a);
  2028. expect (output.messages.size() == 1);
  2029. {
  2030. const auto parsed = Parser::parse (output.messages.back().bytes);
  2031. expect (parsed.has_value());
  2032. expect (parsed->header == Message::Header { ChannelInGroup::wholeBlock,
  2033. detail::MessageMeta::Meta<Message::PropertyNotify>::subID2,
  2034. detail::MessageMeta::implementationVersion,
  2035. device.getMuid(),
  2036. inquiryMUID });
  2037. auto* body = std::get_if<Message::PropertyNotify> (&parsed->body);
  2038. expect (body != nullptr);
  2039. const auto bodyHeader = Encodings::jsonFrom7BitText (body->header);
  2040. expect (bodyHeader.getProperty ("status", "") == var (144));
  2041. }
  2042. expect (device.getOngoingSubscriptions().empty());
  2043. // The start request is no longer in progress because it was terminated by the notify
  2044. expect (device.getOngoingRequests().empty());
  2045. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2046. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  2047. detail::MessageMeta::implementationVersion,
  2048. inquiryMUID,
  2049. device.getMuid() },
  2050. Message::PropertySubscribeResponse { { requestID->asByte(), successHeader, 1, 1, {} } }) });
  2051. expect (device.getOngoingRequests().empty());
  2052. output.messages.clear();
  2053. // There shouldn't be any queued messages.
  2054. device.sendPendingMessages();
  2055. expect (output.messages.empty());
  2056. expect (device.getOngoingRequests().empty());
  2057. }
  2058. output.messages.clear();
  2059. beginTest ("Starting a new subscription while the device is waiting for a previous subscription to be confirmed queues further requests");
  2060. {
  2061. PropertySubscriptionHeader header;
  2062. header.command = PropertySubscriptionCommand::start;
  2063. header.resource = "X-CustomProp";
  2064. const auto a = device.beginSubscription (inquiryMUID, header);
  2065. const auto b = device.beginSubscription (inquiryMUID, header);
  2066. const auto c = device.beginSubscription (inquiryMUID, header);
  2067. expect (device.getOngoingSubscriptions() == std::vector { a, b, c });
  2068. expect (device.getOngoingRequests().size() == 1);
  2069. // subscription id is empty until the responder confirms the subscription
  2070. expect (device.getResourceForKey (a) == header.resource);
  2071. expect (device.getResourceForKey (b) == header.resource);
  2072. expect (device.getResourceForKey (c) == header.resource);
  2073. expect (output.messages.size() == 1);
  2074. // The device has sent a subscription start for a, but not for c,
  2075. // so it should send a notify to end subscription a, but shouldn't emit any
  2076. // messages related to subscription c.
  2077. device.endSubscription (a);
  2078. device.endSubscription (c);
  2079. expect (device.getOngoingSubscriptions() == std::vector { b });
  2080. expect (output.messages.size() == 2);
  2081. expect (device.getOngoingRequests().empty());
  2082. // There should still be requests related to subscription b pending
  2083. device.sendPendingMessages();
  2084. expect (output.messages.size() == 3);
  2085. expect (device.getOngoingRequests().size() == 1);
  2086. // Now, we should send a terminate request for subscription b
  2087. device.endSubscription (b);
  2088. expect (device.getOngoingSubscriptions().empty());
  2089. expect (output.messages.size() == 4);
  2090. expect (device.getOngoingRequests().empty());
  2091. }
  2092. output.messages.clear();
  2093. beginTest ("If the device receives a retry or notify in response to a subscription start request, the subscription is retried or terminated as necessary");
  2094. {
  2095. PropertySubscriptionHeader header;
  2096. header.command = PropertySubscriptionCommand::start;
  2097. header.resource = "X-CustomProp";
  2098. const auto a = device.beginSubscription (inquiryMUID, header);
  2099. expect (device.getOngoingSubscriptions() == std::vector { a });
  2100. expect (device.getOngoingRequests().size() == 1);
  2101. expect (output.messages.size() == 1);
  2102. const auto request0 = device.getOngoingRequests().back();
  2103. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2104. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  2105. detail::MessageMeta::implementationVersion,
  2106. inquiryMUID,
  2107. device.getMuid() },
  2108. Message::PropertySubscribeResponse { { device.getIdForRequestKey (request0)->asByte(), retryHeader, 1, 1, {} } }) });
  2109. // The subscription is still active from the perspective of the device, but the
  2110. // first request is over and should be retried
  2111. expect (device.getOngoingSubscriptions() == std::vector { a });
  2112. expect (device.getOngoingRequests().empty());
  2113. expect (output.messages.size() == 1);
  2114. device.sendPendingMessages();
  2115. expect (device.getOngoingSubscriptions() == std::vector { a });
  2116. expect (device.getOngoingRequests().size() == 1);
  2117. expect (output.messages.size() == 2);
  2118. const auto request1 = device.getOngoingRequests().back();
  2119. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2120. detail::MessageMeta::Meta<Message::PropertyNotify>::subID2,
  2121. detail::MessageMeta::implementationVersion,
  2122. inquiryMUID,
  2123. device.getMuid() },
  2124. Message::PropertyNotify { { device.getIdForRequestKey (request1)->asByte(), cancelHeader, 1, 1, {} } }) });
  2125. expect (device.getOngoingSubscriptions().empty());
  2126. expect (device.getOngoingRequests().empty());
  2127. expect (output.messages.size() == 2);
  2128. }
  2129. beginTest ("If the device receives a retry or notify in response to a subscription end request, the subscription is retried as necessary");
  2130. {
  2131. PropertySubscriptionHeader header;
  2132. header.command = PropertySubscriptionCommand::start;
  2133. header.resource = "X-CustomProp";
  2134. const auto a = device.beginSubscription (inquiryMUID, header);
  2135. expect (device.getOngoingSubscriptions() == std::vector { a });
  2136. expect (device.getResourceForKey (a) == header.resource);
  2137. const auto subscriptionResponseHeader = Encodings::jsonTo7BitText ([]
  2138. {
  2139. auto ptr = std::make_unique<DynamicObject>();
  2140. ptr->setProperty ("status", 200);
  2141. ptr->setProperty ("subscribeId", "newId");
  2142. return ptr.release();
  2143. }());
  2144. // Accept the subscription
  2145. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2146. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  2147. detail::MessageMeta::implementationVersion,
  2148. inquiryMUID,
  2149. device.getMuid() },
  2150. Message::PropertySubscribeResponse { { device.getIdForRequestKey (device.getOngoingRequests().back())->asByte(), subscriptionResponseHeader, 1, 1, {} } }) });
  2151. // The subscription is still active from the perspective of the device, but the
  2152. // request is over and should be retried
  2153. expect (device.getOngoingSubscriptions() == std::vector { a });
  2154. // Now that the subscription was accepted, the subscription id should be non-empty
  2155. expect (device.getResourceForKey (a) == header.resource);
  2156. expect (device.getSubscribeIdForKey (a) == "newId");
  2157. expect (device.getOngoingRequests().empty());
  2158. device.endSubscription (a);
  2159. expect (device.getOngoingSubscriptions().empty());
  2160. expect (device.getOngoingRequests().size() == 1);
  2161. // The responder is busy, can't process the subscription end
  2162. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2163. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  2164. detail::MessageMeta::implementationVersion,
  2165. inquiryMUID,
  2166. device.getMuid() },
  2167. Message::PropertySubscribeResponse { { device.getIdForRequestKey (device.getOngoingRequests().back())->asByte(), retryHeader, 1, 1, {} } }) });
  2168. expect (device.getOngoingSubscriptions().empty());
  2169. expect (device.getOngoingRequests().empty());
  2170. device.sendPendingMessages();
  2171. expect (device.getOngoingSubscriptions().empty());
  2172. expect (device.getOngoingRequests().size() == 1);
  2173. // The responder told us to immediately terminate our request to end the subscription!
  2174. // It's unclear how this should behave, so we'll just ignore the failure and assume
  2175. // the subscription is really over.
  2176. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2177. detail::MessageMeta::Meta<Message::PropertyNotify>::subID2,
  2178. detail::MessageMeta::implementationVersion,
  2179. inquiryMUID,
  2180. device.getMuid() },
  2181. Message::PropertyNotify { { device.getIdForRequestKey (device.getOngoingRequests().back())->asByte(), cancelHeader, 1, 1, {} } }) });
  2182. expect (device.getOngoingSubscriptions().empty());
  2183. expect (device.getOngoingRequests().empty());
  2184. output.messages.clear();
  2185. device.sendPendingMessages();
  2186. expect (device.getOngoingSubscriptions().empty());
  2187. expect (device.getOngoingRequests().empty());
  2188. expect (output.messages.empty());
  2189. }
  2190. output.messages.clear();
  2191. const auto startResponseHeader = [&]
  2192. {
  2193. auto ptr = std::make_unique<DynamicObject>();
  2194. ptr->setProperty ("status", 200);
  2195. ptr->setProperty ("subscribeId", "newId");
  2196. return Encodings::jsonTo7BitText (ptr.release());
  2197. }();
  2198. beginTest ("The responder can terminate a subscription");
  2199. {
  2200. PropertySubscriptionHeader header;
  2201. header.command = PropertySubscriptionCommand::start;
  2202. header.resource = "X-CustomProp";
  2203. const auto a = device.beginSubscription (inquiryMUID, header);
  2204. expect (device.getOngoingRequests().size() == 1);
  2205. expect (device.getOngoingSubscriptions().size() == 1);
  2206. expect (device.getResourceForKey (a) == "X-CustomProp");
  2207. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2208. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  2209. detail::MessageMeta::implementationVersion,
  2210. inquiryMUID,
  2211. device.getMuid() },
  2212. Message::PropertySubscribeResponse { { device.getIdForRequestKey (device.getOngoingRequests().back())->asByte(),
  2213. startResponseHeader,
  2214. 1,
  2215. 1,
  2216. {} } }) });
  2217. expect (device.getOngoingRequests().empty());
  2218. expect (device.getOngoingSubscriptions().size() == 1);
  2219. expect (output.messages.size() == 1);
  2220. output.messages.clear();
  2221. expect (device.getResourceForKey (a) == "X-CustomProp");
  2222. expect (device.getSubscribeIdForKey (a) == "newId");
  2223. const auto endRequestHeader = [&]
  2224. {
  2225. auto ptr = std::make_unique<DynamicObject>();
  2226. ptr->setProperty ("command", "end");
  2227. ptr->setProperty ("subscribeId", "newId");
  2228. return Encodings::jsonTo7BitText (ptr.release());
  2229. }();
  2230. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2231. detail::MessageMeta::Meta<Message::PropertySubscribe>::subID2,
  2232. detail::MessageMeta::implementationVersion,
  2233. inquiryMUID,
  2234. device.getMuid() },
  2235. Message::PropertySubscribe { { std::byte { 0x42 }, endRequestHeader, 1, 1, {} } }) });
  2236. expect (device.getOngoingRequests().empty());
  2237. expect (device.getOngoingSubscriptions().empty());
  2238. expect (output.messages.size() == 1);
  2239. {
  2240. const auto parsed = Parser::parse (output.messages.back().bytes);
  2241. expect (parsed.has_value());
  2242. expect (parsed->header == Message::Header { ChannelInGroup::wholeBlock,
  2243. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  2244. detail::MessageMeta::implementationVersion,
  2245. device.getMuid(),
  2246. inquiryMUID });
  2247. auto* body = std::get_if<Message::PropertySubscribeResponse> (&parsed->body);
  2248. expect (body != nullptr);
  2249. const auto bodyHeader = Encodings::jsonFrom7BitText (body->header);
  2250. expect (bodyHeader.getProperty ("status", "") == var (200));
  2251. }
  2252. }
  2253. beginTest ("Invalidating a MUID clears subscriptions to that MUID");
  2254. {
  2255. PropertySubscriptionHeader header;
  2256. header.command = PropertySubscriptionCommand::start;
  2257. header.resource = "X-CustomProp";
  2258. const auto a = device.beginSubscription (inquiryMUID, header);
  2259. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2260. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  2261. detail::MessageMeta::implementationVersion,
  2262. inquiryMUID,
  2263. device.getMuid() },
  2264. Message::PropertySubscribeResponse { { device.getIdForRequestKey (device.getOngoingRequests().back())->asByte(),
  2265. startResponseHeader,
  2266. 1,
  2267. 1,
  2268. {} } }) });
  2269. expect (device.getOngoingSubscriptions() == std::vector { a });
  2270. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2271. detail::MessageMeta::Meta<Message::InvalidateMUID>::subID2,
  2272. detail::MessageMeta::implementationVersion,
  2273. inquiryMUID,
  2274. MUID::getBroadcast() },
  2275. Message::InvalidateMUID { inquiryMUID }) });
  2276. expect (device.getOngoingSubscriptions().empty());
  2277. }
  2278. beginTest ("Disconnecting and then connecting with the same MUID doesn't reuse SubscribeKeys");
  2279. {
  2280. expect (device.getDiscoveredMuids().empty());
  2281. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2282. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  2283. detail::MessageMeta::implementationVersion,
  2284. inquiryMUID,
  2285. MUID::getBroadcast() },
  2286. Message::Discovery { {}, DeviceFeatures{}.withPropertyExchangeSupported (true).getSupportedCapabilities(), 512, {} }) });
  2287. expect (device.getDiscoveredMuids().size() == 1);
  2288. PropertySubscriptionHeader header;
  2289. header.command = PropertySubscriptionCommand::start;
  2290. header.resource = "X-CustomProp";
  2291. const auto subscription = device.beginSubscription (inquiryMUID, header);
  2292. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2293. detail::MessageMeta::Meta<Message::PropertySubscribeResponse>::subID2,
  2294. detail::MessageMeta::implementationVersion,
  2295. inquiryMUID,
  2296. device.getMuid() },
  2297. Message::PropertySubscribeResponse { { device.getIdForRequestKey (device.getOngoingRequests().back())->asByte(),
  2298. startResponseHeader,
  2299. 1,
  2300. 1,
  2301. {} } }) });
  2302. expect (device.getSubscribeIdForKey (subscription) == "newId");
  2303. expect (device.getResourceForKey (subscription) == "X-CustomProp");
  2304. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2305. detail::MessageMeta::Meta<Message::InvalidateMUID>::subID2,
  2306. detail::MessageMeta::implementationVersion,
  2307. inquiryMUID,
  2308. MUID::getBroadcast() },
  2309. Message::InvalidateMUID { inquiryMUID }) });
  2310. expect (device.getDiscoveredMuids().empty());
  2311. device.processMessage ({ 0, getMessageBytes ({ ChannelInGroup::wholeBlock,
  2312. detail::MessageMeta::Meta<Message::Discovery>::subID2,
  2313. detail::MessageMeta::implementationVersion,
  2314. inquiryMUID,
  2315. MUID::getBroadcast() },
  2316. Message::Discovery { {}, DeviceFeatures{}.withPropertyExchangeSupported (true).getSupportedCapabilities(), 512, {} }) });
  2317. expect (device.getDiscoveredMuids().size() == 1);
  2318. const auto newSubscription = device.beginSubscription (inquiryMUID, header);
  2319. expect (subscription != newSubscription);
  2320. expect (device.getOngoingSubscriptions() == std::vector { newSubscription });
  2321. }
  2322. }
  2323. }
  2324. private:
  2325. template <typename Msg>
  2326. static std::vector<std::byte> getMessageBytes (const Message::Header& header, const Msg& body)
  2327. {
  2328. std::vector<std::byte> bytes;
  2329. detail::Marshalling::Writer { bytes } (header, body);
  2330. return bytes;
  2331. }
  2332. };
  2333. static DeviceTests deviceTests;
  2334. #endif
  2335. } // namespace juce::midi_ci