/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TestSerialPlatformService.h"
#include "SerialLogging.h"
#include "nsIPipe.h"
namespace mozilla::dom {
TestSerialPlatformService::TestSerialPlatformService() {
AddDefaultMockPorts();
}
TestSerialPlatformService* TestSerialPlatformService::AsTestService() {
return this;
}
void TestSerialPlatformService::Shutdown() {
if (IsShutdown()) {
return;
}
MOZ_LOG(gWebSerialLog, LogLevel::Info,
(
"TestSerialPlatformService[%p]::Shutdown", this));
mMockPorts.Clear();
SerialPlatformService::Shutdown();
}
void TestSerialPlatformService::AddDefaultMockPorts() {
AddMockDevice(u
"test-device-1"_ns, u
"/dev/ttyUSB0"_ns,
0x2341,
0x0043);
AddMockDevice(u
"test-device-2"_ns, u
"/dev/ttyUSB1"_ns,
0x0403,
0x6002);
AddMockDevice(u
"test-device-3"_ns, u
"/dev/ttyACM0"_ns,
0x1a86,
0x7523);
}
nsresult TestSerialPlatformService::EnumeratePortsImpl(
SerialPortList& aPorts,
bool* aLikelyAccessDenied) {
aPorts.Clear();
for (
const auto& port : mMockPorts) {
aPorts.AppendElement(port.mInfo);
}
return NS_OK;
}
nsresult TestSerialPlatformService::OpenImpl(
const nsString& aPortId,
const IPCSerialOptions& aOptions) {
MockSerialPort* port = FindPort(aPortId);
if (!port) {
return NS_ERROR_NOT_AVAILABLE;
}
if (port->mIsOpen) {
return NS_ERROR_ALREADY_INITIALIZED;
}
nsCOMPtr<nsIAsyncInputStream> reader;
nsCOMPtr<nsIAsyncOutputStream> writer;
NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer),
/* nonBlockingInput */ true, /* nonBlockingOutput */ true, 4096,
UINT32_MAX);
port->mPipeReadStream = reader;
port->mPipeWriteStream = writer;
port->mIsOpen = true;
port->mOptions = aOptions;
return NS_OK;
}
nsresult TestSerialPlatformService::CloseImpl(
const nsString& aPortId) {
MockSerialPort* port = FindPort(aPortId);
if (!port) {
return NS_ERROR_NOT_AVAILABLE;
}
if (!port->mIsOpen) {
return NS_OK;
}
port->mIsOpen =
false;
if (port->mPipeWriteStream) {
port->mPipeWriteStream->Close();
port->mPipeWriteStream = nullptr;
}
port->mPipeReadStream = nullptr;
return NS_OK;
}
nsresult TestSerialPlatformService::WriteImpl(
const nsString& aPortId,
Span<
const uint8_t> aData) {
MockSerialPort* port = FindPort(aPortId);
if (!port || !port->mIsOpen) {
return NS_ERROR_NOT_AVAILABLE;
}
if (aData.IsEmpty()) {
return NS_OK;
}
if (!port->mPipeWriteStream) {
return NS_ERROR_NOT_AVAILABLE;
}
uint32_t totalWritten =
0;
while (totalWritten < aData.Length()) {
uint32_t written =
0;
nsresult rv = port->mPipeWriteStream->Write(
reinterpret_cast<
const char*>(aData.Elements() + totalWritten),
aData.Length() - totalWritten, &written);
if (NS_FAILED(rv)) {
return rv;
}
if (written ==
0) {
return NS_ERROR_FAILURE;
}
totalWritten += written;
}
return NS_OK;
}
nsresult TestSerialPlatformService::DrainImpl(
const nsString& aPortId) {
MockSerialPort* port = FindPort(aPortId);
if (!port || !port->mIsOpen) {
return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
}
nsresult TestSerialPlatformService::FlushImpl(
const nsString& aPortId,
bool aReceive) {
MockSerialPort* port = FindPort(aPortId);
if (!port || !port->mIsOpen) {
return NS_ERROR_NOT_AVAILABLE;
}
// Don't recreate the pipe pair on flush. PlatformPipeReader holds a
// duplicate of the read end and persists across flush/reattach cycles;
// recreating the pair would leave it reading from a stale pipe.
// Any buffered data is drained by SerialPortParent::StartReadPump.
return NS_OK;
}
nsresult TestSerialPlatformService::SetSignalsImpl(
const nsString& aPortId,
const IPCSerialOutputSignals& aSignals) {
MockSerialPort* port = FindPort(aPortId);
if (!port || !port->mIsOpen) {
return NS_ERROR_NOT_AVAILABLE;
}
if (aSignals.dataTerminalReady().isSome()) {
port->mOutputSignals.dataTerminalReady() = aSignals.dataTerminalReady();
}
if (aSignals.requestToSend().isSome()) {
port->mOutputSignals.requestToSend() = aSignals.requestToSend();
}
if (aSignals.breakSignal().isSome()) {
port->mOutputSignals.breakSignal() = aSignals.breakSignal();
}
return NS_OK;
}
nsresult TestSerialPlatformService::GetSignalsImpl(
const nsString& aPortId, IPCSerialInputSignals& aSignals) {
MockSerialPort* port = FindPort(aPortId);
if (!port || !port->mIsOpen) {
return NS_ERROR_NOT_AVAILABLE;
}
aSignals = IPCSerialInputSignals{
port->mOutputSignals.dataTerminalReady().valueOr(
false),
// dataCarrierDetect
port->mOutputSignals.requestToSend().valueOr(
false),
// clearToSend
false,
// ringIndicator
port->mOutputSignals.dataTerminalReady().valueOr(
false)
// dataSetReady
};
return NS_OK;
}
nsresult TestSerialPlatformService::GetReadStreamImpl(
const nsString& aPortId, uint32_t aBufferSize,
nsIAsyncInputStream** aStream) {
MockSerialPort* port = FindPort(aPortId);
if (!port || !port->mIsOpen) {
return NS_ERROR_NOT_AVAILABLE;
}
if (!port->mPipeReadStream) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsIAsyncInputStream> stream = port->mPipeReadStream;
stream.forget(aStream);
return NS_OK;
}
MockSerialPort TestSerialPlatformService::CreateMockPort(
const nsString& aId,
const nsString& aPath,
uint16_t aVendorId,
uint16_t aProductId) {
MockSerialPort port;
port.mInfo.id() = aId;
port.mInfo.path() = aPath;
port.mInfo.friendlyName() = aId;
port.mInfo.usbVendorId() = Some(aVendorId);
port.mInfo.usbProductId() = Some(aProductId);
return port;
}
void TestSerialPlatformService::AddMockDevice(
const nsString& aId,
const nsString& aPath,
uint16_t aVendorId,
uint16_t aProductId) {
mMockPorts.AppendElement(CreateMockPort(aId, aPath, aVendorId, aProductId));
}
MockSerialPort* TestSerialPlatformService::FindPort(
const nsString& aPortId) {
for (
auto& port : mMockPorts) {
if (port.mInfo.id() == aPortId) {
return &port;
}
}
return nullptr;
}
void TestSerialPlatformService::SimulateDeviceConnection(
const nsString& aId,
const nsString& aPath,
uint16_t aVendorId,
uint16_t aProductId) {
MockSerialPort port = CreateMockPort(aId, aPath, aVendorId, aProductId);
IPCSerialPortInfo info = port.mInfo;
mMockPorts.AppendElement(std::move(port));
NotifyPortConnected(info);
}
void TestSerialPlatformService::SimulateDeviceDisconnection(
const nsString& aId) {
mMockPorts.RemoveElementsBy(
[&aId](
const MockSerialPort& port) {
return port.mInfo.id() == aId
; });
NotifyPortDisconnected(aId);
}
void TestSerialPlatformService::RemoveAllMockDevices() {
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("TestSerialPlatformService::RemoveAllMockDevices removing %zu "
"devices",
mMockPorts.Length()));
// Notify disconnection for each device before removing
for (const auto& mockPort : mMockPorts) {
NotifyPortDisconnected(mockPort.mInfo.id());
}
mMockPorts.Clear();
}
void TestSerialPlatformService::ResetToDefaultMockDevices() {
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("TestSerialPlatformService::ResetToDefaultMockDevices"));
RemoveAllMockDevices();
AddDefaultMockPorts();
}
} // namespace mozilla::dom