opennx/UsbIp.cpp
2025-08-08 20:34:09 +02:00

540 lines
15 KiB
C++

// $Id: UsbIp.cpp 451 2010-01-27 12:24:56Z felfert $
//
// Copyright (C) 2006 The OpenNX Team
// Author: Fritz Elfert
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
#pragma implementation "UsbIp.h"
#endif
#ifdef SUPPORT_USBIP
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "UsbIp.h"
#include <wx/log.h>
#include <wx/socket.h>
#include <wx/stopwatch.h>
#include <wx/tokenzr.h>
#include <wx/regex.h>
#include <wx/arrimpl.cpp>
#ifdef APP_OPENNX
# include "opennxApp.h"
#endif
#ifdef APP_WATCHUSBIP
# include "watchUsbIpApp.h"
#endif
#include "trace.h"
ENABLE_TRACE;
IMPLEMENT_DYNAMIC_CLASS(HotplugEvent, wxEvent)
DEFINE_LOCAL_EVENT_TYPE(wxEVT_HOTPLUG)
WX_DEFINE_OBJARRAY(ArrayOfUsbIpDevices);
wxString UsbIpDevice::toString() const {
wxString ret = wxString::Format(wxT("%s %d-%d %04X/%04X %s"),
m_sUsbIpBusId.c_str(), m_iUsbBusnum, m_iUsbDevnum,
m_iVendorID, m_iProductID, VMB(m_sDriver));
return ret;
}
IMPLEMENT_CLASS(UsbIp, wxEvtHandler);
#define SOCKET_ID 55555
BEGIN_EVENT_TABLE(UsbIp, wxEvtHandler)
EVT_SOCKET(SOCKET_ID, UsbIp::OnSocketEvent)
END_EVENT_TABLE()
UsbIp::UsbIp()
: m_pEvtHandler(NULL)
{
m_pSocketClient = new wxSocketClient();
m_pSocketClient->SetEventHandler(*this, SOCKET_ID);
m_pSocketClient->SetNotify(
wxSOCKET_INPUT_FLAG|wxSOCKET_CONNECTION_FLAG|wxSOCKET_LOST_FLAG);
m_pSocketClient->Notify(true);
m_pSocketClient->SetTimeout(10); // 10 sec.
m_bConnected = false;
m_bError = false;
m_sSid = wxEmptyString;
m_sLineBuffer = wxEmptyString;
m_eState = None;
}
UsbIp::~UsbIp()
{
if (m_pSocketClient) {
if (m_bConnected) {
m_eState = Terminating;
if (send(wxT("quit\n")))
waitforstate(None);
}
m_pSocketClient->Destroy();
}
m_pSocketClient = NULL;
}
bool UsbIp::HasError()
{
return m_pSocketClient->Error();
}
bool UsbIp::Wait(long sec = -1, long msec = 0)
{
return m_pSocketClient->Wait(sec, msec);
}
bool UsbIp::Connect(const wxString &socketPath)
{
#ifdef __WXMSW__
m_bConnected = false;
# warning win32 implementation missing
#else
wxUNIXaddress addr;
addr.Filename(socketPath);
myLogTrace(MYTRACETAG, wxT("Connecting to %s"), VMB(socketPath));
m_pSocketClient->Connect(addr, false);
// It's a local unix socket and the server must be running already,
// so 5 secs should be more than enough.
m_pSocketClient->WaitOnConnect(5);
m_bConnected = m_pSocketClient->IsConnected();
myLogTrace(MYTRACETAG, wxT("m_bConnected = %d"), (int)m_bConnected);
if (m_bConnected)
m_eState = Initializing;
else
m_pSocketClient->Close();
#endif
return m_bConnected;
}
void UsbIp::SetSession(const wxString &sid)
{
m_sSid = sid;
}
bool UsbIp::WaitForSession(int secs /* = 10 */)
{
long timeout = secs * 1000;
if (m_sSid.IsEmpty())
return false;
if (Initializing > m_eState)
return false;
if (!waitforstate(Idle))
return false;
wxStopWatch sw;
myLogTrace(MYTRACETAG, wxT("waiting for session ..."));
while (!findsession(m_sSid)) {
::wxGetApp().Yield(true);
wxLog::FlushActive();
m_pSocketClient->Wait(0, 1000);
if (0 < timeout) {
if (sw.Time() > timeout) {
myLogTrace(MYTRACETAG, wxT("waitforsession timed out"));
return false;
}
}
}
return true;
}
bool UsbIp::ExportDevice(const wxString &busid)
{
if (m_sSid.IsEmpty())
return false;
if (Initializing > m_eState)
return false;
if (!waitforstate(Idle))
return false;
myLogTrace(MYTRACETAG, wxT("Exporting ..."));
if (!findsession(m_sSid)) {
myLogTrace(MYTRACETAG, wxT("Session not found"));
return false;
}
m_eState = Exporting;
if (!send(wxT("export %s %s\n"), busid.c_str(), m_sSid.c_str())) {
m_eState = Idle;
return false;
}
if (!waitforstate(Exported)) {
m_eState = Idle;
if (404 == m_iLastError) {
wxLogWarning(_("USB device is already exported in another session"));
return true; // Prevent calling app from showing an error.
}
return false;
}
m_eState = Idle;
return true;
}
bool UsbIp::UnexportDevice(const wxString &busid)
{
if (m_sSid.IsEmpty())
return false;
if (Initializing > m_eState)
return false;
if (!waitforstate(Idle))
return false;
myLogTrace(MYTRACETAG, wxT("Unexporting ..."));
if (!findsession(m_sSid)) {
myLogTrace(MYTRACETAG, wxT("Session not found"));
return false;
}
if (!send(wxT("unexport %s %s\n"), busid.c_str(), m_sSid.c_str()))
return false;
return true;
}
bool UsbIp::RegisterHotplug()
{
if (m_sSid.IsEmpty())
return false;
if (Initializing > m_eState)
return false;
if (!waitforstate(Idle))
return false;
m_eState = Registering;
myLogTrace(MYTRACETAG, wxT("Registering for hotplug ..."));
if (!send(wxT("setsid %s\n"), m_sSid.c_str())) {
m_eState = Idle;
return false;
}
if (!waitforstate(Registered)) {
m_eState = Idle;
return false;
}
m_eState = Idle;
return true;
}
bool UsbIp::SendHotplugResponse(const wxString &cookie)
{
if (m_sSid.IsEmpty())
return false;
if (Initializing > m_eState)
return false;
if (!waitforstate(Idle))
return false;
m_eState = Responding;
myLogTrace(MYTRACETAG, wxT("Sending hotplug response ..."));
if (!send(wxT("handled %s\n"), cookie.c_str())) {
m_eState = Idle;
return false;
}
if (!waitforstate(Responded)) {
m_eState = Idle;
return false;
}
m_eState = Idle;
return true;
}
ArrayOfUsbIpDevices UsbIp::GetDevices() {
m_aDevices.Empty();
if (Initializing > m_eState)
return m_aDevices;
if (!waitforstate(Idle))
return m_aDevices;
myLogTrace(MYTRACETAG, wxT("Fetching device list ..."));
m_eState = ListDevices;
if (!send(wxT("list\n"))) {
m_eState = Idle;
return m_aDevices;
}
if (!waitforstate(GotDevices)) {
m_eState = Idle;
return m_aDevices;
}
m_eState = Idle;
return m_aDevices;
}
bool UsbIp::findsession(const wxString &sid)
{
if (Initializing > m_eState)
return false;
if (!waitforstate(Idle))
return false;
m_aSessions.Empty();
m_eState = ListSessions;
if (!send(wxT("sessions\n"))) {
m_eState = Idle;
return false;
}
if (!waitforstate(GotSessions)) {
m_eState = Idle;
return false;
}
bool found = (m_aSessions.Index(sid, true) != wxNOT_FOUND);
m_eState = Idle;
return found;
}
bool UsbIp::send(const wxChar *fmt, ...)
{
wxString buf;
va_list args;
va_start(args, fmt);
if (0 > buf.PrintfV(fmt, args))
return false;
m_bError = false;
m_pSocketClient->Write(buf.mb_str(wxConvUTF8), buf.Len());
return ((m_pSocketClient->LastCount() == buf.Len()) && (!HasError()));
}
bool UsbIp::waitforstate(tStates state, long timeout /* = 5000 */)
{
wxStopWatch watch;
while (m_eState != state) {
::wxGetApp().Yield(true);
wxLog::FlushActive();
m_pSocketClient->Wait(0, 100);
if (0 < timeout) {
if (watch.Time() > timeout) {
return false;
}
}
}
return !m_bError;
}
void UsbIp::parsehev(const wxString &line)
{
wxRegEx re(wxT("([a-f\\d]+)\\s+(\\d+-[\\d\\.]+)\\s+(\\d+)\\s+(\\d+)\\s+([a-f\\d]+)\\s+([a-f\\d]+)"),
wxRE_ADVANCED);
if (!re.IsValid()) {
wxLogFatalError(_("Invalid regular expression in %s %d"), __FILE__, __LINE__);
return;
}
if (re.Matches(line)) {
HotplugEvent ev;
long lval;
ev.SetCookie(re.GetMatch(line, 1));
ev.SetBusID(re.GetMatch(line, 2));
if (!re.GetMatch(line, 3).ToLong(&lval, 10))
return;
ev.SetBusNum(lval);
if (!re.GetMatch(line, 4).ToLong(&lval, 10))
return;
ev.SetDevNum(lval);
if (!re.GetMatch(line, 5).ToLong(&lval, 16))
return;
ev.SetProduct(lval);
if (!re.GetMatch(line, 6).ToLong(&lval, 16))
return;
ev.SetVendor(lval);
ev.SetEventObject(this);
if (m_pEvtHandler)
m_pEvtHandler->AddPendingEvent(ev);
} else
myLogTrace(MYTRACETAG, wxT("hev not matched"));
}
void UsbIp::parsesession(const wxString &line)
{
wxRegEx re(wxT("\\[\\d+\\.\\d+\\.\\d+\\.\\d+\\]:\\d+\\s+\\(SID:\\s+([0-9,A-F]+)\\)"),
wxRE_ADVANCED);
if (!re.IsValid()) {
wxLogFatalError(_("Invalid regular expression %s %d"), __FILE__, __LINE__);
return;
}
if (re.Matches(line))
m_aSessions.Add(re.GetMatch(line, 1));
}
void UsbIp::parsedevice(const wxString &line)
{
wxStringTokenizer tkz(line, wxT(" "));
if (6 == tkz.CountTokens()) {
UsbIpDevice dev;
long lval;
dev.m_sUsbIpBusId = tkz.GetNextToken();
dev.m_sConfig = tkz.GetNextToken();
if (!tkz.GetNextToken().ToLong(&lval))
return;
dev.m_iUsbBusnum = lval;
if (!tkz.GetNextToken().ToLong(&lval))
return;
dev.m_iUsbDevnum = lval;
wxStringTokenizer tkz2(tkz.GetNextToken(), wxT(":"));
dev.m_sDriver = tkz.GetNextToken();
if (2 != tkz2.CountTokens())
return;
if (!tkz2.GetNextToken().ToLong(&lval, 16))
return;
dev.m_iVendorID = lval;
if (!tkz2.GetNextToken().ToLong(&lval, 16))
return;
dev.m_iProductID = lval;
m_aDevices.Add(dev);
}
}
void UsbIp::parse(const wxString &line)
{
wxString cs = line.Left(4).Trim();
if (cs.Len() == 3) {
long code;
if (cs.ToLong(&code)) {
if (200 != code)
myLogTrace(MYTRACETAG, wxT("Got Line: '%s'"), VMB(line));
switch (code) {
case 100:
if (m_eState == Initializing)
m_bError = !send(wxT("\n"));
break;
case 101:
if (m_eState == Initializing)
m_eState = Idle;
break;
case 200:
m_bError = false;
switch (m_eState) {
case Exporting:
m_eState = Exported;
break;
case UnExporting:
m_eState = UnExported;
break;
case ListSessions:
m_eState = GotSessions;
break;
case ListDevices:
m_eState = GotDevices;
break;
case Registering:
m_eState = Registered;
break;
case Responding:
m_eState = Responded;
break;
default:
break;
}
break;
case 201:
if (m_eState == Terminating)
m_eState = None;
break;
case 202:
switch (m_eState) {
case ListSessions:
parsesession(line.Mid(4));
break;
case ListDevices:
parsedevice(line.Mid(4));
break;
default:
break;
}
break;
case 203:
parsehev(line.Mid(4));
break;
case 404:
case 400:
case 401:
case 402:
case 500:
case 501:
m_iLastError = code;
m_bError = true;
switch (m_eState) {
case Initializing:
m_eState = Idle;
break;
case Exporting:
m_eState = Exported;
break;
case UnExporting:
m_eState = UnExported;
break;
case ListSessions:
m_eState = GotSessions;
break;
case ListDevices:
m_eState = GotDevices;
break;
case Registering:
m_eState = Registered;
break;
case Responding:
m_eState = Responded;
break;
default:
break;
}
break;
}
}
}
}
void UsbIp::OnSocketEvent(wxSocketEvent &event)
{
char *p;
char *q;
char buf[128];
myLogTrace(MYTRACETAG, wxT("SocketEvent"));
switch (event.GetSocketEvent()) {
case wxSOCKET_OUTPUT:
break;
case wxSOCKET_INPUT:
m_pSocketClient->Read(buf, sizeof(buf) - 1);
buf[m_pSocketClient->LastCount()] = '\0';
q = buf;
while ((p = strchr(q, '\n'))) {
*p++ = '\0';
parse(m_sLineBuffer.Append(wxString(q, wxConvUTF8)));
m_sLineBuffer = wxEmptyString;
q = p;
}
m_sLineBuffer.Append(wxString(q, wxConvUTF8));
break;
case wxSOCKET_CONNECTION:
myLogTrace(MYTRACETAG, wxT("OnConnection"));
m_bConnected = true;
break;
case wxSOCKET_LOST:
myLogTrace(MYTRACETAG, wxT("OnLost"));
m_bConnected = false;
break;
}
}
#endif
// SUPPORT_USBIP