Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  Win32SerialPlatformService.cpp

  Sprache: C
 

/* 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 "Win32SerialPlatformService.h"

#include <cfgmgr32.h>
// Including initguid.h needs to come before including devpkey.h, so
// disable clang-format here.

// clang-format off
#include <initguid.h>
#include <devpkey.h>
// clang-format on

#include <ntddser.h>
#include <setupapi.h>

#include "SerialLogging.h"
#include "mozilla/AsyncPlatformPipes.h"
#include "mozilla/ScopeExit.h"
#include "nsString.h"
#include "nsThreadUtils.h"

namespace mozilla::dom {

namespace {
constexpr size_t kPropertyBufferSize = 256;
constexpr unsigned int kDeviceChangeDelayMs = 200;
constexpr wchar_t kDevicePathPrefix[] = L"\\\\.\\";
}  // namespace

Win32SerialPlatformService::Win32SerialPlatformService()
    : mIOCapability(IOThread()) {
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService[%p] created", this));
  MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
      "SerialMonitorQueue", getter_AddRefs(mMonitorThread)));
}

nsresult Win32SerialPlatformService::Init() {
  return StartMonitoringDeviceChanges();
}

void Win32SerialPlatformService::Shutdown() {
  AssertIsOnMainThread();
  if (IsShutdown()) {
    return;
  }
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService[%p]::Shutdown", this));
  StopMonitoringDeviceChanges();
  mMonitorThread = nullptr;
  SerialPlatformService::Shutdown();
}

Win32SerialPlatformService::~Win32SerialPlatformService() {
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService[%p] destroyed (closing %u ports)", this,
           mOpenPorts.Count()));
  for (auto iter = mOpenPorts.Iter(); !iter.Done(); iter.Next()) {
    HANDLE handle = iter.Data();
    if (handle != INVALID_HANDLE_VALUE) {
      CloseHandle(handle);
    }
  }
  mOpenPorts.Clear();
}

namespace {
nsresult EnumeratePortsWin32(SerialPortList& aPorts) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService::EnumeratePorts"));
  aPorts.Clear();

  HDEVINFO deviceInfoSet =
      SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, nullptr, nullptr,
                          DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

  if (deviceInfoSet == INVALID_HANDLE_VALUE) {
    DWORD error = GetLastError();
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService::EnumeratePorts "
             "SetupDiGetClassDevs failed: 0x%08lx",
             error));
    return NS_ERROR_FAILURE;
  }
  auto cleanupDeviceInfoSet =
      MakeScopeExit([&]() { SetupDiDestroyDeviceInfoList(deviceInfoSet); });

  SP_DEVINFO_DATA deviceInfo;
  deviceInfo.cbSize = sizeof(SP_DEVINFO_DATA);

  for (DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, &deviceInfo); ++i) {
    HKEY hKey = SetupDiOpenDevRegKey(deviceInfoSet, &deviceInfo,
                                     DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

    if (hKey == INVALID_HANDLE_VALUE) {
      continue;
    }
    wchar_t portName[kPropertyBufferSize];
    {
      auto cleanupRegKey = MakeScopeExit([&]() { RegCloseKey(hKey); });

      DWORD portNameSize = sizeof(portName);
      LONG result =
          RegGetValueW(hKey, nullptr, L"PortName", RRF_RT_REG_SZ, nullptr,
                       reinterpret_cast<LPBYTE>(portName), &portNameSize);
      if (result != ERROR_SUCCESS) {
        continue;
      }
    }

    wchar_t friendlyName[kPropertyBufferSize] = {0};
    DWORD friendlyNameSize = sizeof(friendlyName);
    // The bus reported device description is usually more descriptive than
    // the friendly name (for LEGO Spike, Flipper Zero, etc.)
    auto deviceDescriptionPropKey = DEVPKEY_Device_BusReportedDeviceDesc;
    DEVPROPTYPE unusedPropertyType;
    BOOL succeeded = SetupDiGetDevicePropertyW(
        deviceInfoSet, &deviceInfo, &deviceDescriptionPropKey,
        &unusedPropertyType, reinterpret_cast<PBYTE>(friendlyName),
        friendlyNameSize, nullptr, 0);
    if (!(succeeded && *friendlyName)) {
      deviceDescriptionPropKey = DEVPKEY_Device_FriendlyName;
      succeeded = SetupDiGetDevicePropertyW(
          deviceInfoSet, &deviceInfo, &deviceDescriptionPropKey,
          &unusedPropertyType, reinterpret_cast<PBYTE>(friendlyName),
          friendlyNameSize, nullptr, 0);
    }
    if (!(succeeded && *friendlyName)) {
      wcscpy_s(friendlyName, portName);
    }
    // SPDRP_HARDWAREID is of type REG_MULTI_SZ. We just are interested
    // in the first one, and since each string is null-terminated, we'll
    // just treat it as a regular string.
    wchar_t hardwareId[kPropertyBufferSize] = {0};
    SetupDiGetDeviceRegistryPropertyW(
        deviceInfoSet, &deviceInfo, SPDRP_HARDWAREID, nullptr,
        reinterpret_cast<PBYTE>(hardwareId), sizeof(hardwareId), nullptr);

    IPCSerialPortInfo info;
    info.id() = nsString(portName);
    info.friendlyName() = nsString(friendlyName);

    {
      nsString path(kDevicePathPrefix);
      path.Append(portName);
      info.path() = std::move(path);
    }

    uint16_t vendorId = 0;
    uint16_t productId = 0;
    wchar_t* vidLocation = wcsstr(hardwareId, L"VID_");
    if (vidLocation) {
      swscanf_s(vidLocation + 4, L"%4hx", &vendorId);
    }
    wchar_t* pidLocation = wcsstr(hardwareId, L"PID_");
    if (pidLocation) {
      swscanf_s(pidLocation + 4, L"%4hx", &productId);
    }

    if (vendorId != 0 && productId != 0) {
      info.usbVendorId() = Some(vendorId);
      info.usbProductId() = Some(productId);
    }

    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("Win32SerialPlatformService::EnumeratePorts found port '%s' (%s) "
             "VID:0x%04x PID:0x%04x",
             NS_ConvertUTF16toUTF8(info.id()).get(),
             NS_ConvertUTF16toUTF8(info.friendlyName()).get(), vendorId,
             productId));
    aPorts.AppendElement(info);
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService::EnumeratePorts found %zu ports",
           aPorts.Length()));
  return NS_OK;
}
}  // namespace

nsresult Win32SerialPlatformService::EnumeratePortsImpl(
    SerialPortList& aPorts, bool* aLikelyAccessDenied) {
  return EnumeratePortsWin32(aPorts);
}

HANDLE Win32SerialPlatformService::FindPortHandle(const nsString& aPortId) {
  mIOCapability.AssertOnCurrentThread();
  return mOpenPorts.MaybeGet(aPortId).valueOr(INVALID_HANDLE_VALUE);
}

nsresult Win32SerialPlatformService::ConfigurePort(
    HANDLE aHandle, const IPCSerialOptions& aOptions) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::ConfigurePort (baudRate=%u, "
           "dataBits=%u, stopBits=%u, parity=%u, flowControl=%u)",
           this, aOptions.baudRate(), aOptions.dataBits(), aOptions.stopBits(),
           static_cast<unsigned>(aOptions.parity()),
           static_cast<unsigned>(aOptions.flowControl())));
  mIOCapability.AssertOnCurrentThread();

  DCB dcb = {0};
  dcb.DCBlength = sizeof(DCB);

  if (!GetCommState(aHandle, &dcb)) {
    DWORD error = GetLastError();
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("Win32SerialPlatformService[%p]::ConfigurePort GetCommState failed: "
         "0x%08lx",
         this, error));
    return NS_ERROR_FAILURE;
  }

  // These options are not configurable
  dcb.fBinary = TRUE;
  dcb.fParity = TRUE;
  dcb.fAbortOnError = FALSE;
  dcb.fOutxDsrFlow = FALSE;
  dcb.fDtrControl = DTR_CONTROL_ENABLE;
  dcb.fDsrSensitivity = FALSE;
  dcb.fOutX = FALSE;
  dcb.fInX = FALSE;

  dcb.BaudRate = aOptions.baudRate();

  switch (aOptions.dataBits()) {
    case 7:
      dcb.ByteSize = 7;
      break;
    case 8:
      dcb.ByteSize = 8;
      break;
    default:
      return NS_ERROR_INVALID_ARG;
  }

  switch (aOptions.stopBits()) {
    case 1:
      dcb.StopBits = ONESTOPBIT;
      break;
    case 2:
      dcb.StopBits = TWOSTOPBITS;
      break;
    default:
      return NS_ERROR_INVALID_ARG;
  }

  switch (aOptions.parity()) {
    case ParityType::None:
      dcb.Parity = NOPARITY;
      break;
    case ParityType::Even:
      dcb.Parity = EVENPARITY;
      break;
    case ParityType::Odd:
      dcb.Parity = ODDPARITY;
      break;
    default:
      return NS_ERROR_INVALID_ARG;
  }

  switch (aOptions.flowControl()) {
    case FlowControlType::None:
      dcb.fOutxCtsFlow = FALSE;
      dcb.fRtsControl = RTS_CONTROL_ENABLE;
      break;
    case FlowControlType::Hardware:
      dcb.fOutxCtsFlow = TRUE;
      dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
      break;
    default:
      return NS_ERROR_INVALID_ARG;
  }

  if (!SetCommState(aHandle, &dcb)) {
    DWORD error = GetLastError();
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("Win32SerialPlatformService[%p]::ConfigurePort SetCommState failed: "
         "0x%08lx",
         this, error));
    return NS_ERROR_FAILURE;
  }

  COMMTIMEOUTS timeouts = {0};
  // When both ReadIntervalTimeout and ReadTotalTimeoutMultiplier are MAXDWORD,
  // ReadFile returns immediately with buffered data, or waits up to
  // ReadTotalTimeoutConstant ms for the first byte. We use a very large
  // constant (MAXDWORD-1 ~= 49 days) so the overlapped ReadFile effectively
  // waits forever for data, ensuring bytesRead > 0 on every completion.
  // PlatformPipeReader treats 0-byte reads as EOF, so we must never time out
  // with 0 bytes. Using MAXDWORD for all three would mean "return immediately
  // even with 0 bytes" which we must avoid.
  timeouts.ReadIntervalTimeout = MAXDWORD;
  timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
  timeouts.ReadTotalTimeoutConstant = MAXDWORD - 1;
  // Write timeout as a safety net.
  timeouts.WriteTotalTimeoutMultiplier = 0;
  timeouts.WriteTotalTimeoutConstant = 5000;

  if (!SetCommTimeouts(aHandle, &timeouts)) {
    DWORD error = GetLastError();
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::ConfigurePort SetCommTimeouts "
             "failed: 0x%08lx",
             this, error));
    return NS_ERROR_FAILURE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::ConfigurePort succeeded", this));
  return NS_OK;
}

nsresult Win32SerialPlatformService::OpenImpl(
    const nsString& aPortId, const IPCSerialOptions& aOptions) {
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService[%p]::Open port '%s'", this,
           NS_ConvertUTF16toUTF8(aPortId).get()));
  mIOCapability.AssertOnCurrentThread();

  // Validate portId format: must be "COM" followed by one or more digits.
  // This prevents a compromised content process from using a crafted portId
  // to open arbitrary devices in the \\.\ namespace (e.g. PhysicalDrive0).
  if (aPortId.Length() < 4 || !StringBeginsWith(aPortId, u"COM"_ns)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::Open rejected invalid portId "
             "'%s': bad prefix",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_INVALID_ARG;
  }
  for (uint32_t i = 3; i < aPortId.Length(); i++) {
    if (!iswdigit(aPortId.CharAt(i))) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("Win32SerialPlatformService[%p]::Open rejected invalid portId "
               "'%s': non-digit character",
               this, NS_ConvertUTF16toUTF8(aPortId).get()));
      return NS_ERROR_INVALID_ARG;
    }
  }

  if (mOpenPorts.Contains(aPortId)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("Win32SerialPlatformService[%p]::Open port '%s' already open",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_FILE_IS_LOCKED;
  }

  nsString devicePath(kDevicePathPrefix);
  devicePath.Append(aPortId);

  HANDLE handle =
      CreateFileW(devicePath.get(), GENERIC_READ | GENERIC_WRITE, 0, nullptr,
                  OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);

  if (handle == INVALID_HANDLE_VALUE) {
    DWORD error = GetLastError();
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::Open CreateFileW failed for port "
             "'%s' at path '%s': 0x%08lx",
             this, NS_ConvertUTF16toUTF8(aPortId).get(),
             NS_ConvertUTF16toUTF8(devicePath).get(), error));
    if (error == ERROR_ACCESS_DENIED) {
      return NS_ERROR_FILE_ACCESS_DENIED;
    }
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsresult rv = ConfigurePort(handle, aOptions);
  if (NS_FAILED(rv)) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("Win32SerialPlatformService[%p]::Open ConfigurePort failed for port "
         "'%s': 0x%08x",
         this, NS_ConvertUTF16toUTF8(aPortId).get(),
         static_cast<uint32_t>(rv)));
    CloseHandle(handle);
    return rv;
  }

  PurgeComm(handle, PURGE_RXCLEAR | PURGE_TXCLEAR);

  mOpenPorts.InsertOrUpdate(aPortId, handle);
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService[%p]::Open succeeded for port '%s'", this,
           NS_ConvertUTF16toUTF8(aPortId).get()));
  return NS_OK;
}

nsresult Win32SerialPlatformService::CloseImpl(const nsString& aPortId) {
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService[%p]::Close port '%s'", this,
           NS_ConvertUTF16toUTF8(aPortId).get()));
  mIOCapability.AssertOnCurrentThread();

  HANDLE handle = FindPortHandle(aPortId);
  if (handle == INVALID_HANDLE_VALUE) {
    MOZ_LOG(gWebSerialLog, LogLevel::Warning,
            ("Win32SerialPlatformService[%p]::Close port '%s' not found", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  mOpenPorts.Remove(aPortId);
  CloseHandle(handle);
  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService[%p]::Close succeeded for port '%s'",
           this, NS_ConvertUTF16toUTF8(aPortId).get()));
  return NS_OK;
}

nsresult Win32SerialPlatformService::WriteImpl(const nsString& aPortId,
                                               Span<const uint8_t> aData) {
  mIOCapability.AssertOnCurrentThread();
  HANDLE handle = FindPortHandle(aPortId);
  if (handle == INVALID_HANDLE_VALUE) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::Write port '%s' not found", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (aData.IsEmpty()) {
    MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
            ("Win32SerialPlatformService[%p]::Write empty data for port '%s'",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_OK;
  }

  MOZ_LOG(
      gWebSerialLog, LogLevel::Verbose,
      ("Win32SerialPlatformService[%p]::Write writing %zu bytes to port '%s'",
       this, aData.Length(), NS_ConvertUTF16toUTF8(aPortId).get()));

  DWORD totalWritten = 0;
  const uint8_t* buffer = aData.Elements();
  DWORD remaining = static_cast<DWORD>(aData.Length());

  auto event = UniqueFileHandle(CreateEvent(nullptr, TRUE, FALSE, nullptr));
  if (!event) {
    return NS_ERROR_FAILURE;
  }
  OVERLAPPED ov = {};
  // Setting the low-order bit of hEvent prevents the I/O completion from
  // being queued to the IOCP. This is necessary because PlatformPipeReader
  // may have registered a DuplicateHandle of this port with the IOCP, and
  // completions from our local OVERLAPPED would corrupt the IOCP handler
  // lookup. GetOverlappedResult still works via the event.
  HANDLE rawEvent = event.get();
  ov.hEvent =
      reinterpret_cast<HANDLE>(reinterpret_cast<uintptr_t>(rawEvent) | 1);

  while (remaining > 0) {
    ResetEvent(event.get());

    if (!WriteFile(handle, buffer + totalWritten, remaining, nullptr, &ov)) {
      DWORD error = GetLastError();
      if (error != ERROR_IO_PENDING) {
        MOZ_LOG(
            gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::Write WriteFile failed for port "
             "'%s': 0x%08lx",
             this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
        return NS_ERROR_FAILURE;
      }
    }
    DWORD bytesWritten = 0;
    if (!GetOverlappedResult(handle, &ov, &bytesWritten, TRUE)) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("Win32SerialPlatformService[%p]::Write GetOverlappedResult "
               "failed for port '%s': 0x%08lx",
               this, NS_ConvertUTF16toUTF8(aPortId).get(), GetLastError()));
      return NS_ERROR_FAILURE;
    }

    if (bytesWritten == 0) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("Win32SerialPlatformService[%p]::Write WriteFile returned 0 for "
               "port '%s'",
               this, NS_ConvertUTF16toUTF8(aPortId).get()));
      return NS_ERROR_FAILURE;
    }

    totalWritten += bytesWritten;
    remaining -= bytesWritten;

    if (remaining > 0) {
      MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
              ("Win32SerialPlatformService[%p]::Write partial write for port "
               "'%s': %lu bytes, %lu remaining",
               this, NS_ConvertUTF16toUTF8(aPortId).get(), bytesWritten,
               remaining));
    }
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::Write wrote %lu bytes to port '%s'",
           this, totalWritten, NS_ConvertUTF16toUTF8(aPortId).get()));
  return NS_OK;
}

nsresult Win32SerialPlatformService::DrainImpl(const nsString& aPortId) {
  mIOCapability.AssertOnCurrentThread();
  HANDLE handle = FindPortHandle(aPortId);
  if (handle == INVALID_HANDLE_VALUE) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::Drain port not found: %s", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("Win32SerialPlatformService[%p]::Drain draining transmit buffers for "
       "port '%s'",
       this, NS_ConvertUTF16toUTF8(aPortId).get()));

  if (!FlushFileBuffers(handle)) {
    DWORD error = GetLastError();
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("Win32SerialPlatformService[%p]::Drain FlushFileBuffers failed for "
         "port '%s': error=%lu",
         this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
    return NS_ERROR_FAILURE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::Drain successfully drained buffers "
           "for port '%s'",
           this, NS_ConvertUTF16toUTF8(aPortId).get()));
  return NS_OK;
}

nsresult Win32SerialPlatformService::FlushImpl(const nsString& aPortId,
                                               bool aReceive) {
  mIOCapability.AssertOnCurrentThread();
  HANDLE handle = FindPortHandle(aPortId);
  if (handle == INVALID_HANDLE_VALUE) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::Flush port not found: %s", this,
             NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  DWORD flags = aReceive ? PURGE_RXCLEAR : PURGE_TXCLEAR;
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::Flush discarding %s buffers "
           "for port '%s'",
           this, aReceive ? "receive" : "transmit",
           NS_ConvertUTF16toUTF8(aPortId).get()));

  if (!PurgeComm(handle, flags)) {
    DWORD error = GetLastError();
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::Flush PurgeComm failed for port "
             "'%s': error=%lu",
             this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
    return NS_ERROR_FAILURE;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::Flush successfully flushed %s "
           "buffers for port '%s'",
           this, aReceive ? "receive" : "transmit",
           NS_ConvertUTF16toUTF8(aPortId).get()));
  return NS_OK;
}

nsresult Win32SerialPlatformService::SetSignalsImpl(
    const nsString& aPortId, const IPCSerialOutputSignals& aSignals) {
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::SetSignals for port '%s' (DTR=%s, "
           "RTS=%s, Break=%s)",
           this, NS_ConvertUTF16toUTF8(aPortId).get(),
           aSignals.dataTerminalReady().isSome()
               ? (aSignals.dataTerminalReady().value() ? "true" : "false")
               : "unset",
           aSignals.requestToSend().isSome()
               ? (aSignals.requestToSend().value() ? "true" : "false")
               : "unset",
           aSignals.breakSignal().isSome()
               ? (aSignals.breakSignal().value() ? "true" : "false")
               : "unset"));
  mIOCapability.AssertOnCurrentThread();

  HANDLE handle = FindPortHandle(aPortId);
  if (handle == INVALID_HANDLE_VALUE) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::SetSignals port '%s' not found",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (aSignals.dataTerminalReady().isSome()) {
    if (!EscapeCommFunction(
            handle, aSignals.dataTerminalReady().value() ? SETDTR : CLRDTR)) {
      DWORD error = GetLastError();
      MOZ_LOG(
          gWebSerialLog, LogLevel::Error,
          ("Win32SerialPlatformService[%p]::SetSignals EscapeCommFunction DTR "
           "failed for port '%s': 0x%08lx",
           this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
      return NS_ERROR_FAILURE;
    }
  }

  if (aSignals.requestToSend().isSome()) {
    if (!EscapeCommFunction(
            handle, aSignals.requestToSend().value() ? SETRTS : CLRRTS)) {
      DWORD error = GetLastError();
      MOZ_LOG(
          gWebSerialLog, LogLevel::Error,
          ("Win32SerialPlatformService[%p]::SetSignals EscapeCommFunction RTS "
           "failed for port '%s': 0x%08lx",
           this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
      return NS_ERROR_FAILURE;
    }
  }

  if (aSignals.breakSignal().isSome()) {
    if (!EscapeCommFunction(
            handle, aSignals.breakSignal().value() ? SETBREAK : CLRBREAK)) {
      DWORD error = GetLastError();
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("Win32SerialPlatformService[%p]::SetSignals EscapeCommFunction "
               "Break failed for port '%s': 0x%08lx",
               this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
      return NS_ERROR_FAILURE;
    }
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::SetSignals succeeded for port '%s'",
           this, NS_ConvertUTF16toUTF8(aPortId).get()));
  return NS_OK;
}

nsresult Win32SerialPlatformService::GetSignalsImpl(
    const nsString& aPortId, IPCSerialInputSignals& aSignals) {
  mIOCapability.AssertOnCurrentThread();
  HANDLE handle = FindPortHandle(aPortId);
  if (handle == INVALID_HANDLE_VALUE) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::GetSignals port '%s' not found",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }

  DWORD status = 0;
  if (!GetCommModemStatus(handle, &status)) {
    DWORD error = GetLastError();
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("Win32SerialPlatformService[%p]::GetSignals GetCommModemStatus failed "
         "for port '%s': 0x%08lx",
         this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
    return NS_ERROR_FAILURE;
  }

  aSignals = IPCSerialInputSignals{
      (status & MS_RLSD_ON) != 0,  // dataCarrierDetect (DCD)
      (status & MS_CTS_ON) != 0,   // clearToSend (CTS)
      (status & MS_RING_ON) != 0,  // ringIndicator (RI)
      (status & MS_DSR_ON) != 0    // dataSetReady (DSR)
  };

  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::GetSignals for port '%s': DCD=%s, "
           "CTS=%s, RI=%s, DSR=%s",
           this, NS_ConvertUTF16toUTF8(aPortId).get(),
           aSignals.dataCarrierDetect() ? "true" : "false",
           aSignals.clearToSend() ? "true" : "false",
           aSignals.ringIndicator() ? "true" : "false",
           aSignals.dataSetReady() ? "true" : "false"));

  return NS_OK;
}

nsresult Win32SerialPlatformService::GetReadStreamImpl(
    const nsString& aPortId, uint32_t aBufferSize,
    nsIAsyncInputStream** aStream) {
  mIOCapability.AssertOnCurrentThread();
  HANDLE handle = FindPortHandle(aPortId);
  if (handle == INVALID_HANDLE_VALUE) {
    MOZ_LOG(
        gWebSerialLog, LogLevel::Error,
        ("Win32SerialPlatformService[%p]::GetReadStream port '%s' not found",
         this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_NOT_AVAILABLE;
  }
  UniqueFileHandle readHandle = DuplicateFileHandle(handle);
  if (!readHandle) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::GetReadStream DuplicateHandle "
             "failed for port '%s'",
             this, NS_ConvertUTF16toUTF8(aPortId).get()));
    return NS_ERROR_FAILURE;
  }
  RefPtr<PlatformPipeReader> reader =
      MakeRefPtr<PlatformPipeReader>(std::move(readHandle), aBufferSize);
  reader.forget(aStream);
  return NS_OK;
}

nsresult Win32SerialPlatformService::StartMonitoringDeviceChanges() {
  if (mMonitoring) {
    return NS_OK;
  }
  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("Win32SerialPlatformService[%p]::StartMonitoringDeviceChanges", this));

  CM_NOTIFY_FILTER filter = {};
  filter.cbSize = sizeof(CM_NOTIFY_FILTER);
  filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE;
  filter.u.DeviceInterface.ClassGuid = GUID_DEVINTERFACE_COMPORT;

  CONFIGRET cr = CM_Register_Notification(
      &filter, this, DeviceNotificationCallback, &mDeviceNotification);

  if (cr != CR_SUCCESS) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::StartMonitoringDeviceChanges "
             "CM_Register_Notification failed: 0x%08lx",
             this, cr));
    return NS_ERROR_FAILURE;
  }

  mMonitoring = true;

  auto cachedPortList = mCachedPortList.Lock();
  nsresult rv = EnumeratePortsWin32(*cachedPortList);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::StartMonitoringDeviceChanges "
             "EnumeratePorts failed: 0x%08x",
             this, static_cast<unsigned>(rv)));
    return rv;
  }

  MOZ_LOG(gWebSerialLog, LogLevel::Info,
          ("Win32SerialPlatformService[%p]::StartMonitoringDeviceChanges "
           "succeeded, "
           "monitoring %zu ports",
           this, cachedPortList->Length()));
  return NS_OK;
}

void Win32SerialPlatformService::StopMonitoringDeviceChanges() {
  AssertIsOnMainThread();
  if (!mMonitoring) {
    return;
  }

  MOZ_LOG(
      gWebSerialLog, LogLevel::Debug,
      ("Win32SerialPlatformService[%p]::StopMonitoringDeviceChanges", this));

  if (mDeviceNotification) {
    CM_Unregister_Notification(mDeviceNotification);
    mDeviceNotification = nullptr;
  }

  mMonitoring = false;
}

DWORD CALLBACK Win32SerialPlatformService::DeviceNotificationCallback(
    HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action,
    PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize) {
  RefPtr<SerialPlatformService> service = SerialPlatformService::GetInstance();
  if (!service) {
    return ERROR_SUCCESS;
  }
  auto* winService = static_cast<Win32SerialPlatformService*>(service.get());

  if (!(Action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ||
        Action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL)) {
    return ERROR_SUCCESS;
  }
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::DeviceNotificationCallback "
           "action=%d",
           winService, Action));

  // Only schedule a check if one isn't already pending
  bool expected = false;
  if (winService->mCheckPending.compareExchange(expected, true)) {
    // Dispatch to the monitor thread (separate from the I/O queue so device
    // change detection isn't blocked by hanging serial I/O calls).
    nsresult rv = winService->mMonitorThread->DelayedDispatch(
        NS_NewRunnableFunction(
            "Win32SerialPlatformService::CheckForDeviceChanges",
            [self = RefPtr{winService}]() {
              self->mCheckPending = false;
              self->CheckForDeviceChanges();
            }),
        kDeviceChangeDelayMs);
    if (NS_FAILED(rv)) {
      MOZ_LOG(gWebSerialLog, LogLevel::Error,
              ("Win32SerialPlatformService[%p]::"
               "DeviceNotificationCallback DelayedDispatch failed: 0x%08x",
               winService, static_cast<uint32_t>(rv)));
      winService->mCheckPending = false;
    } else {
      MOZ_LOG(gWebSerialLog, LogLevel::Debug,
              ("Win32SerialPlatformService[%p]::"
               "DeviceNotificationCallback scheduled CheckForDeviceChanges",
               winService));
    }
  } else {
    MOZ_LOG(gWebSerialLog, LogLevel::Debug,
            ("Win32SerialPlatformService[%p]::"
             "DeviceNotificationCallback check already pending, skipping",
             winService));
  }

  return ERROR_SUCCESS;
}

void Win32SerialPlatformService::CheckForDeviceChanges() {
  if (IsShutdown()) {
    return;
  }
  MOZ_ASSERT(mMonitorThread->IsOnCurrentThread());
  MOZ_LOG(gWebSerialLog, LogLevel::Debug,
          ("Win32SerialPlatformService[%p]::CheckForDeviceChanges", this));

  SerialPortList newPortList;
  nsresult rv = EnumeratePortsWin32(newPortList);
  if (NS_FAILED(rv)) {
    MOZ_LOG(gWebSerialLog, LogLevel::Error,
            ("Win32SerialPlatformService[%p]::CheckForDeviceChanges "
             "EnumeratePorts failed: 0x%08x",
             this, static_cast<unsigned>(rv)));
    return;
  }

  auto cachedPortList = mCachedPortList.Lock();

  // Find newly connected ports
  for (const auto& newPort : newPortList) {
    bool found = false;
    for (const auto& oldPort : *cachedPortList) {
      if (oldPort.id() == newPort.id()) {
        found = true;
        break;
      }
    }
    if (!found) {
      MOZ_LOG(gWebSerialLog, LogLevel::Info,
              ("Win32SerialPlatformService[%p]::CheckForDeviceChanges port "
               "connected: '%s'",
               this, NS_ConvertUTF16toUTF8(newPort.id()).get()));
      NotifyPortConnected(newPort);
    }
  }

  // Find disconnected ports
  for (const auto& oldPort : *cachedPortList) {
    bool found = false;
    for (const auto& newPort : newPortList) {
      if (oldPort.id() == newPort.id()) {
        found = true;
        break;
      }
    }
    if (!found) {
      MOZ_LOG(gWebSerialLog, LogLevel::Info,
              ("Win32SerialPlatformService[%p]::CheckForDeviceChanges port "
               "disconnected: '%s'",
               this, NS_ConvertUTF16toUTF8(oldPort.id()).get()));
      // Since we're just using synchonous I/O for now, if the OS detects
      // a device gets disconnected any pending writes will immediately
      // fail, so we don't need to do any sort of cancelling here.
      NotifyPortDisconnected(oldPort.id());
    }
  }

  *cachedPortList = std::move(newPortList);
}

already_AddRefed<SerialPlatformService>
SerialPlatformService::GetInstanceImpl() {
  return MakeAndAddRef<Win32SerialPlatformService>();
}

}  // namespace mozilla::dom

Messung V0.5 in Prozent
C=92 H=95 G=93

¤ Dauer der Verarbeitung: 0.8 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002