1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "TestMIDIPlatformService.h"
6 #include "mozilla/dom/MIDIPort.h"
7 #include "mozilla/dom/MIDITypes.h"
8 #include "mozilla/dom/MIDIPortInterface.h"
9 #include "mozilla/dom/MIDIPortParent.h"
10 #include "mozilla/dom/MIDIPlatformRunnables.h"
11 #include "mozilla/dom/MIDIUtils.h"
12 #include "mozilla/ipc/BackgroundParent.h"
13 #include "mozilla/Unused.h"
14 #include "nsIThread.h"
15 
16 using namespace mozilla;
17 using namespace mozilla::dom;
18 using namespace mozilla::ipc;
19 
20 /**
21  * Runnable used for making sure ProcessMessages only happens on the IO thread.
22  *
23  */
24 class ProcessMessagesRunnable : public mozilla::Runnable {
25  public:
ProcessMessagesRunnable(const nsAString & aPortID)26   explicit ProcessMessagesRunnable(const nsAString& aPortID)
27       : Runnable("ProcessMessagesRunnable"), mPortID(aPortID) {}
28   ~ProcessMessagesRunnable() = default;
Run()29   NS_IMETHOD Run() {
30     // If service is no longer running, just exist without processing.
31     if (!MIDIPlatformService::IsRunning()) {
32       return NS_OK;
33     }
34     TestMIDIPlatformService* srv =
35         static_cast<TestMIDIPlatformService*>(MIDIPlatformService::Get());
36     srv->ProcessMessages(mPortID);
37     return NS_OK;
38   }
39 
40  private:
41   nsString mPortID;
42 };
43 
44 /**
45  * Runnable used for allowing IO thread to queue more messages for processing,
46  * since it can't access the service object directly.
47  *
48  */
49 class QueueMessagesRunnable : public MIDIBackgroundRunnable {
50  public:
QueueMessagesRunnable(const nsAString & aPortID,const nsTArray<MIDIMessage> & aMsgs)51   QueueMessagesRunnable(const nsAString& aPortID,
52                         const nsTArray<MIDIMessage>& aMsgs)
53       : MIDIBackgroundRunnable("QueueMessagesRunnable"),
54         mPortID(aPortID),
55         mMsgs(aMsgs.Clone()) {}
56   ~QueueMessagesRunnable() = default;
RunInternal()57   virtual void RunInternal() {
58     AssertIsOnBackgroundThread();
59     MIDIPlatformService::Get()->QueueMessages(mPortID, mMsgs);
60   }
61 
62  private:
63   nsString mPortID;
64   nsTArray<MIDIMessage> mMsgs;
65 };
66 
TestMIDIPlatformService()67 TestMIDIPlatformService::TestMIDIPlatformService()
68     : mBackgroundThread(NS_GetCurrentThread()),
69       mControlInputPort(u"b744eebe-f7d8-499b-872b-958f63c8f522"_ns,
70                         u"Test Control MIDI Device Input Port"_ns,
71                         u"Test Manufacturer"_ns, u"1.0.0"_ns,
72                         static_cast<uint32_t>(MIDIPortType::Input)),
73       mControlOutputPort(u"ab8e7fe8-c4de-436a-a960-30898a7c9a3d"_ns,
74                          u"Test Control MIDI Device Output Port"_ns,
75                          u"Test Manufacturer"_ns, u"1.0.0"_ns,
76                          static_cast<uint32_t>(MIDIPortType::Output)),
77       mStateTestInputPort(u"a9329677-8588-4460-a091-9d4a7f629a48"_ns,
78                           u"Test State MIDI Device Input Port"_ns,
79                           u"Test Manufacturer"_ns, u"1.0.0"_ns,
80                           static_cast<uint32_t>(MIDIPortType::Input)),
81       mStateTestOutputPort(u"478fa225-b5fc-4fa6-a543-d32d9cb651e7"_ns,
82                            u"Test State MIDI Device Output Port"_ns,
83                            u"Test Manufacturer"_ns, u"1.0.0"_ns,
84                            static_cast<uint32_t>(MIDIPortType::Output)),
85       mAlwaysClosedTestOutputPort(u"f87d0c76-3c68-49a9-a44f-700f1125c07a"_ns,
86                                   u"Always Closed MIDI Device Output Port"_ns,
87                                   u"Test Manufacturer"_ns, u"1.0.0"_ns,
88                                   static_cast<uint32_t>(MIDIPortType::Output)),
89       mIsInitialized(false) {
90   AssertIsOnBackgroundThread();
91 }
92 
~TestMIDIPlatformService()93 TestMIDIPlatformService::~TestMIDIPlatformService() {
94   AssertIsOnBackgroundThread();
95 }
96 
Init()97 void TestMIDIPlatformService::Init() {
98   AssertIsOnBackgroundThread();
99 
100   if (mIsInitialized) {
101     return;
102   }
103   mIsInitialized = true;
104 
105   // Treat all of our special ports as always connected. When the service comes
106   // up, prepopulate the port list with them.
107   MIDIPlatformService::Get()->AddPortInfo(mControlInputPort);
108   MIDIPlatformService::Get()->AddPortInfo(mControlOutputPort);
109   MIDIPlatformService::Get()->AddPortInfo(mAlwaysClosedTestOutputPort);
110   nsCOMPtr<nsIRunnable> r(new SendPortListRunnable());
111 
112   // Start the IO Thread.
113   NS_DispatchToCurrentThread(r);
114 }
115 
Open(MIDIPortParent * aPort)116 void TestMIDIPlatformService::Open(MIDIPortParent* aPort) {
117   MOZ_ASSERT(aPort);
118   MIDIPortConnectionState s = MIDIPortConnectionState::Open;
119   if (aPort->MIDIPortInterface::Id() == mAlwaysClosedTestOutputPort.id()) {
120     // If it's the always closed testing port, act like it's already opened
121     // exclusively elsewhere.
122     s = MIDIPortConnectionState::Closed;
123   }
124   // Connection events are just simulated on the background thread, no need to
125   // push to IO thread.
126   nsCOMPtr<nsIRunnable> r(new SetStatusRunnable(aPort->MIDIPortInterface::Id(),
127                                                 aPort->DeviceState(), s));
128   NS_DispatchToCurrentThread(r);
129 }
130 
ScheduleClose(MIDIPortParent * aPort)131 void TestMIDIPlatformService::ScheduleClose(MIDIPortParent* aPort) {
132   MOZ_ASSERT(aPort);
133   if (aPort->ConnectionState() == MIDIPortConnectionState::Open) {
134     // Connection events are just simulated on the background thread, no need to
135     // push to IO thread.
136     nsCOMPtr<nsIRunnable> r(new SetStatusRunnable(
137         aPort->MIDIPortInterface::Id(), aPort->DeviceState(),
138         MIDIPortConnectionState::Closed));
139     NS_DispatchToCurrentThread(r);
140   }
141 }
142 
Stop()143 void TestMIDIPlatformService::Stop() { AssertIsOnBackgroundThread(); }
144 
ScheduleSend(const nsAString & aPortId)145 void TestMIDIPlatformService::ScheduleSend(const nsAString& aPortId) {
146   nsCOMPtr<nsIRunnable> r(new ProcessMessagesRunnable(aPortId));
147   NS_DispatchToCurrentThread(r);
148 }
149 
ProcessMessages(const nsAString & aPortId)150 void TestMIDIPlatformService::ProcessMessages(const nsAString& aPortId) {
151   nsTArray<MIDIMessage> msgs;
152   GetMessagesBefore(aPortId, TimeStamp::Now(), msgs);
153 
154   for (MIDIMessage msg : msgs) {
155     // receiving message from test control port
156     if (aPortId == mControlOutputPort.id()) {
157       switch (msg.data()[0]) {
158         // Hit a note, get a test!
159         case 0x90:
160           switch (msg.data()[1]) {
161             // Echo data/timestamp back through output port
162             case 0x00: {
163               nsCOMPtr<nsIRunnable> r(
164                   new ReceiveRunnable(mControlInputPort.id(), msg));
165               mBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL);
166               break;
167             }
168             // Cause control test ports to connect
169             case 0x01: {
170               nsCOMPtr<nsIRunnable> r1(
171                   new AddPortRunnable(mStateTestInputPort));
172               mBackgroundThread->Dispatch(r1, NS_DISPATCH_NORMAL);
173               break;
174             }
175             // Cause control test ports to disconnect
176             case 0x02: {
177               nsCOMPtr<nsIRunnable> r1(
178                   new RemovePortRunnable(mStateTestInputPort));
179               mBackgroundThread->Dispatch(r1, NS_DISPATCH_NORMAL);
180               break;
181             }
182             // Test for packet timing
183             case 0x03: {
184               // Append a few echo command packets in reverse timing order,
185               // should come out in correct order on other end.
186               nsTArray<MIDIMessage> newMsgs;
187               nsTArray<uint8_t> msg;
188               msg.AppendElement(0x90);
189               msg.AppendElement(0x00);
190               msg.AppendElement(0x00);
191               // PR_Now() returns nanosecods, and we need a double with
192               // fractional milliseconds.
193               TimeStamp currentTime = TimeStamp::Now();
194               for (int i = 0; i <= 5; ++i) {
195                 // Insert messages with timestamps in reverse order, to make
196                 // sure we're sorting correctly.
197                 newMsgs.AppendElement(MIDIMessage(
198                     msg, currentTime - TimeDuration::FromMilliseconds(i * 2)));
199               }
200               nsCOMPtr<nsIRunnable> r(
201                   new QueueMessagesRunnable(aPortId, newMsgs));
202               mBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL);
203               break;
204             }
205             default:
206               NS_WARNING("Unknown Test MIDI message received!");
207           }
208           break;
209           // Sysex tests
210         case 0xF0:
211           switch (msg.data()[1]) {
212               // Echo data/timestamp back through output port
213             case 0x00: {
214               nsCOMPtr<nsIRunnable> r(
215                   new ReceiveRunnable(mControlInputPort.id(), msg));
216               mBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL);
217               break;
218             }
219             // Test for system real time messages in the middle of sysex
220             // messages.
221             case 0x01: {
222               nsTArray<uint8_t> msgs;
223               const uint8_t msg[] = {0xF0, 0x01, 0xF8, 0x02, 0x03,
224                                      0x04, 0xF9, 0x05, 0xF7};
225               // Can't use AppendElements on an array here, so just do range
226               // based loading.
227               for (auto& s : msg) {
228                 msgs.AppendElement(s);
229               }
230               nsTArray<MIDIMessage> newMsgs;
231               MIDIUtils::ParseMessages(msgs, TimeStamp::Now(), newMsgs);
232               nsCOMPtr<nsIRunnable> r(
233                   new ReceiveRunnable(mControlInputPort.id(), newMsgs));
234               mBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL);
235               break;
236             }
237             default:
238               NS_WARNING("Unknown Test Sysex MIDI message received!");
239           }
240           break;
241       }
242     }
243   }
244 }
245