1/*
2 * Copyright (C) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15#include "host_usb.h"
16
17#include <thread>
18#include "usb_util.h"
19
20namespace Hdc {
21constexpr uint16_t DEVICE_CHECK_INTERVAL = 3000;  // ms
22constexpr uint16_t BUF_SIZE_MEDIUM = 512;
23constexpr uint16_t BUF_SIZE_TINY = 64;
24constexpr uint8_t GLOBAL_TIMEOUT = 30;
25constexpr uint16_t TIME_BASE = 1000;
26constexpr uint16_t MAX_SIZE_IOBUF = 61440;
27
28uint8_t *g_bufPtr = nullptr;
29
30const std::string StringFormat(const char * const formater, va_list &vaArgs)
31{
32    std::vector<char> args(MAX_SIZE_IOBUF);
33    const int retSize = vsnprintf_s(args.data(), MAX_SIZE_IOBUF, MAX_SIZE_IOBUF - 1, formater, vaArgs);
34    if (retSize < 0) {
35        return std::string("");
36    } else {
37        return std::string(args.data(), retSize);
38    }
39}
40
41const std::string StringFormat(const char * const formater, ...)
42{
43    va_list vaArgs;
44    va_start(vaArgs, formater);
45    std::string ret = StringFormat(formater, vaArgs);
46    va_end(vaArgs);
47    return ret;
48}
49
50HostUsb::HostUsb()
51{
52    if (libusb_init((libusb_context **)&ctxUSB) != 0) {
53        ctxUSB = nullptr;
54    }
55    running = false;
56}
57
58HostUsb::~HostUsb()
59{
60    if (running) {
61        Stop();
62    }
63}
64
65void HostUsb::Stop()
66{
67    if (!ctxUSB) {
68        return;
69    }
70    timer->Stop();
71    libusb_exit((libusb_context *)ctxUSB);
72    running = false;
73
74    if (g_bufPtr != nullptr) {
75        delete[] g_bufPtr;
76        g_bufPtr = nullptr;
77    }
78}
79
80// Main thread USB operates in this thread
81void HostUsb::UsbWorkThread(void *arg)
82{
83    HostUsb *thisClass = (HostUsb *)arg;
84    constexpr uint8_t usbHandleTimeout = 30;  // second
85    while (thisClass->running) {
86        struct timeval zerotime;
87        zerotime.tv_sec = usbHandleTimeout;
88        zerotime.tv_usec = 0;  // if == 0,windows will be high CPU load
89        libusb_handle_events_timeout(thisClass->ctxUSB, &zerotime);
90    }
91}
92
93void HostUsb::WatchUsbNodeChange(void *arg)
94{
95    HostUsb *thisClass = (HostUsb *)arg;
96    libusb_device **devs = nullptr;
97    libusb_device *dev = nullptr;
98    ssize_t cnt = libusb_get_device_list(thisClass->ctxUSB, &devs);
99    if (cnt < 0) {
100        return;
101    }
102    int i = 0;
103    // linux replug devid increment,windows will be not
104    while ((dev = devs[i++]) != nullptr) {  // must postfix++
105        std::string szTmpKey = StringFormat("%d-%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
106        // check is in ignore list
107        UsbCheckStatus statusCheck = thisClass->mapIgnoreDevice[szTmpKey];
108        if (statusCheck == HOST_USB_IGNORE || statusCheck == HOST_USB_REGISTER) {
109            continue;
110        }
111        std::string sn = szTmpKey;
112        if (thisClass->HasValidDevice(dev) && !thisClass->DetectMyNeed(dev, sn)) {
113            thisClass->ReviewUsbNodeLater(szTmpKey);
114        }
115    }
116    libusb_free_device_list(devs, 1);
117}
118
119void HostUsb::ReviewUsbNodeLater(string &nodeKey)
120{
121    // add to ignore list
122    mapIgnoreDevice[nodeKey] = HOST_USB_IGNORE;
123    RemoveIgnoreDevice(nodeKey);
124}
125
126bool HostUsb::HasValidDevice(libusb_device *device)
127{
128    struct libusb_config_descriptor *descConfig = nullptr;
129    int ret = libusb_get_active_config_descriptor(device, &descConfig);
130    if (ret != 0) {
131        return false;
132    }
133    bool hasValid = false;
134    for (unsigned int j = 0; j < descConfig->bNumInterfaces; ++j) {
135        const struct libusb_interface *interface = &descConfig->interface[j];
136        if (interface->num_altsetting < 1) {
137            continue;
138        }
139        const struct libusb_interface_descriptor *ifDescriptor = &interface->altsetting[0];
140        if (!IsDebuggableDev(ifDescriptor)) {
141            continue;
142        }
143        hasValid = true;
144        break;
145    }
146    return hasValid;
147}
148
149bool HostUsb::IsDebuggableDev(const struct libusb_interface_descriptor *ifDescriptor)
150{
151    constexpr uint8_t harmonyEpNum = 2;
152    constexpr uint8_t harmonyClass = 0xff;
153    constexpr uint8_t harmonySubClass = 0x50;
154    constexpr uint8_t harmonyProtocol = 0x01;
155
156    if (ifDescriptor->bInterfaceClass != harmonyClass || ifDescriptor->bInterfaceSubClass != harmonySubClass ||
157        ifDescriptor->bInterfaceProtocol != harmonyProtocol) {
158        return false;
159    }
160    if (ifDescriptor->bNumEndpoints != harmonyEpNum) {
161        return false;
162    }
163    return true;
164}
165
166bool HostUsb::DetectMyNeed(libusb_device *device, string &sn)
167{
168    HUSB hUSB = new(std::nothrow) HdcUSB();
169    if (hUSB == nullptr) {
170        return false;
171    }
172    hUSB->device = device;
173    // just get usb SN, close handle immediately
174    int childRet = OpenDeviceMyNeed(hUSB);
175    if (childRet < 0) {
176        delete hUSB;
177        return false;
178    }
179    UpdateUSBDaemonInfo(hUSB, STATUS_READY);
180    mapIgnoreDevice[sn] = HOST_USB_REGISTER;
181    mapUsbDevice[hUSB->serialNumber] = hUSB;
182    return true;
183}
184
185void HostUsb::UpdateUSBDaemonInfo(HUSB hUSB, uint8_t connStatus)
186{
187    // add to list
188    HdcDaemonInformation di;
189    di.connectKey = hUSB->serialNumber;
190    di.connType = CONN_USB;
191    di.connStatus = connStatus;
192    di.usbMountPoint = "";
193    di.usbMountPoint = StringFormat("%d-%d", hUSB->busId, hUSB->devId);
194
195    HDaemonInfo pDi = nullptr;
196    HDaemonInfo hdiNew = &di;
197    AdminDaemonMap(OP_QUERY, hUSB->serialNumber, pDi);
198    if (!pDi) {
199        AdminDaemonMap(OP_ADD, hUSB->serialNumber, hdiNew);
200    } else {
201        AdminDaemonMap(OP_UPDATE, hUSB->serialNumber, hdiNew);
202        if (connStatus == STATUS_OFFLINE) {
203            RemoveIgnoreDevice(di.usbMountPoint);
204        }
205    }
206}
207
208// ==0 Represents new equipment and is what we need,<0  my need
209int HostUsb::OpenDeviceMyNeed(HUSB hUSB)
210{
211    libusb_device *device = hUSB->device;
212    int ret = -1;
213    int openRet = libusb_open(device, &hUSB->devHandle);
214    if (openRet != LIBUSB_SUCCESS) {
215        return -1;
216    }
217    while (running) {
218        libusb_device_handle *handle = hUSB->devHandle;
219        struct libusb_device_descriptor desc;
220        if (CheckDescriptor(hUSB, desc)) {
221            break;
222        }
223#ifdef HOST_MAC
224        if (CheckActiveConfig(device, hUSB, desc)) {
225#else
226        if (CheckActiveConfig(device, hUSB)) {
227#endif
228            break;
229        }
230
231        // USB filter rules are set according to specific device pedding device
232        ret = libusb_claim_interface(handle, hUSB->interfaceNumber);
233        break;
234    }
235    if (ret) {
236        // not my need device, release the device
237        libusb_close(hUSB->devHandle);
238        hUSB->devHandle = nullptr;
239    }
240    return ret;
241}
242
243int HostUsb::CheckDescriptor(HUSB hUSB, libusb_device_descriptor& desc)
244{
245    char serialNum[BUF_SIZE_MEDIUM] = "";
246    int childRet = 0;
247    uint8_t curBus = libusb_get_bus_number(hUSB->device);
248    uint8_t curDev = libusb_get_device_address(hUSB->device);
249    hUSB->busId = curBus;
250    hUSB->devId = curDev;
251    if (libusb_get_device_descriptor(hUSB->device, &desc)) {
252        return -1;
253    }
254    // Get the serial number of the device, if there is no serial number, use the ID number to replace
255    // If the device is not in time, occasionally can't get it, this is determined by the external factor, cannot be
256    // changed. LIBUSB_SUCCESS
257    childRet = libusb_get_string_descriptor_ascii(hUSB->devHandle, desc.iSerialNumber, (uint8_t *)serialNum,
258                                                  sizeof(serialNum));
259    if (childRet < 0) {
260        return -1;
261    } else {
262        hUSB->serialNumber = serialNum;
263    }
264    return 0;
265}
266
267#ifdef HOST_MAC
268int HostUsb::CheckActiveConfig(libusb_device *device, HUSB hUSB, libusb_device_descriptor& desc)
269#else
270int HostUsb::CheckActiveConfig(libusb_device *device, HUSB hUSB)
271#endif
272{
273    struct libusb_config_descriptor *descConfig = nullptr;
274    int ret = libusb_get_active_config_descriptor(device, &descConfig);
275    if (ret != 0) {
276#ifdef HOST_MAC
277        if ((desc.bDeviceClass == 0xFF)
278            && (desc.bDeviceSubClass == 0xFF)
279            && (desc.bDeviceProtocol == 0xFF)) {
280            ret = libusb_set_configuration(hUSB->devHandle, 1);
281            if (ret != 0) {
282                return -1;
283            }
284        }
285
286        ret = libusb_get_active_config_descriptor(device, &descConfig);
287        if (ret != 0) {
288#endif
289            return -1;
290        }
291#ifdef HOST_MAC
292    }
293#endif
294
295    ret = -1;
296    CheckUsbEndpoint(ret, hUSB, descConfig);
297    libusb_free_config_descriptor(descConfig);
298    return ret;
299}
300
301void HostUsb::CheckUsbEndpoint(int& ret, HUSB hUSB, libusb_config_descriptor *descConfig)
302{
303    unsigned int j = 0;
304    for (j = 0; j < descConfig->bNumInterfaces; ++j) {
305        const struct libusb_interface *interface = &descConfig->interface[j];
306        if (interface->num_altsetting < 1) {
307            continue;
308        }
309        const struct libusb_interface_descriptor *ifDescriptor = &interface->altsetting[0];
310        if (!IsDebuggableDev(ifDescriptor)) {
311            continue;
312        }
313        hUSB->interfaceNumber = ifDescriptor->bInterfaceNumber;
314        unsigned int k = 0;
315        for (k = 0; k < ifDescriptor->bNumEndpoints; ++k) {
316            const struct libusb_endpoint_descriptor *ep_desc = &ifDescriptor->endpoint[k];
317            if ((ep_desc->bmAttributes & 0x03) != LIBUSB_TRANSFER_TYPE_BULK) {
318                continue;
319            }
320            if (ep_desc->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
321                hUSB->hostBulkIn.endpoint = ep_desc->bEndpointAddress;
322                hUSB->hostBulkIn.bulkInOut = true;
323            } else {
324                hUSB->hostBulkOut.endpoint = ep_desc->bEndpointAddress;
325                hUSB->wMaxPacketSizeSend = ep_desc->wMaxPacketSize;
326                hUSB->hostBulkOut.bulkInOut = false;
327            }
328        }
329        if (hUSB->hostBulkIn.endpoint == 0 || hUSB->hostBulkOut.endpoint == 0) {
330            break;
331        }
332        ret = 0;
333    }
334}
335
336bool HostUsb::FindDeviceByID(HUSB hUSB, const char *usbMountPoint, libusb_context *ctxUSB)
337{
338    libusb_device **listDevices = nullptr;
339    bool ret = false;
340    char tmpStr[BUF_SIZE_TINY] = "";
341    int busNum = 0;
342    int devNum = 0;
343    int curBus = 0;
344    int curDev = 0;
345
346    int deviceNum = libusb_get_device_list(ctxUSB, &listDevices);
347    if (deviceNum <= 0) {
348        libusb_free_device_list(listDevices, 1);
349        return false;
350    }
351    if (strchr(usbMountPoint, '-') && EOK == strcpy_s(tmpStr, sizeof(tmpStr), usbMountPoint)) {
352        *strchr(tmpStr, '-') = '\0';
353        busNum = atoi(tmpStr);
354        devNum = atoi(tmpStr + strlen(tmpStr) + 1);
355    } else {
356        return false;
357    }
358
359    int i = 0;
360    for (i = 0; i < deviceNum; ++i) {
361        struct libusb_device_descriptor desc;
362        if (LIBUSB_SUCCESS != libusb_get_device_descriptor(listDevices[i], &desc)) {
363            continue;
364        }
365        curBus = libusb_get_bus_number(listDevices[i]);
366        curDev = libusb_get_device_address(listDevices[i]);
367        if ((curBus == busNum && curDev == devNum)) {
368            hUSB->device = listDevices[i];
369            int childRet = OpenDeviceMyNeed(hUSB);
370            if (!childRet) {
371                ret = true;
372            } else {
373                string key = string(usbMountPoint);
374                RemoveIgnoreDevice(key);
375            }
376            break;
377        }
378    }
379    libusb_free_device_list(listDevices, 1);
380    return ret;
381}
382
383// multi-thread calll
384void HostUsb::CancelUsbIo(HUSB hUSB)
385{
386    std::unique_lock<std::mutex> lock(hUSB->lockDeviceHandle);
387    if (!hUSB->hostBulkIn.isShutdown) {
388        if (!hUSB->hostBulkIn.isComplete) {
389            libusb_cancel_transfer(hUSB->hostBulkIn.transfer);
390            hUSB->hostBulkIn.cv.notify_one();
391        } else {
392            hUSB->hostBulkIn.isShutdown = true;
393        }
394    }
395    if (!hUSB->hostBulkOut.isShutdown) {
396        if (!hUSB->hostBulkOut.isComplete) {
397            libusb_cancel_transfer(hUSB->hostBulkOut.transfer);
398            hUSB->hostBulkOut.cv.notify_one();
399        } else {
400            hUSB->hostBulkOut.isShutdown = true;
401        }
402    }
403}
404
405void HostUsb::RemoveIgnoreDevice(string &mountInfo)
406{
407    if (mapIgnoreDevice.count(mountInfo)) {
408        mapIgnoreDevice.erase(mountInfo);
409    }
410}
411
412void LIBUSB_CALL HostUsb::USBBulkCallback(struct libusb_transfer *transfer)
413{
414    auto *ep = reinterpret_cast<HostUSBEndpoint *>(transfer->user_data);
415    std::unique_lock<std::mutex> lock(ep->mutexIo);
416    bool retrySumit = false;
417    int childRet = 0;
418    do {
419        if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
420            break;
421        }
422        if (!ep->bulkInOut && transfer->actual_length != transfer->length) {
423            transfer->length -= transfer->actual_length;
424            transfer->buffer += transfer->actual_length;
425            retrySumit = true;
426            break;
427        }
428    } while (false);
429    while (retrySumit) {
430        childRet = libusb_submit_transfer(transfer);
431        if (childRet != 0) {
432            transfer->status = LIBUSB_TRANSFER_ERROR;
433            break;
434        }
435        return;
436    }
437    ep->isComplete = true;
438    ep->cv.notify_one();
439}
440
441PersistBuffer HostUsb::ReadUsbIO(HUSB hUSB, int exceptedSize)
442{
443    int timeout = 0;
444    int childRet = 0;
445    int ret = 0;
446
447    HostUSBEndpoint* ep = &hUSB->hostBulkIn;
448
449    if (g_bufPtr == nullptr) {
450        g_bufPtr = new uint8_t[MAX_SIZE_IOBUF];
451    }
452
453    hUSB->lockDeviceHandle.lock();
454    ep->isComplete = false;
455    do {
456        std::unique_lock<std::mutex> lock(ep->mutexIo);
457        libusb_fill_bulk_transfer(ep->transfer, hUSB->devHandle, ep->endpoint, g_bufPtr, exceptedSize,
458            USBBulkCallback, ep, timeout);
459        childRet = libusb_submit_transfer(ep->transfer);
460        hUSB->lockDeviceHandle.unlock();
461        if (childRet < 0) {
462            break;
463        }
464        ep->cv.wait(lock, [ep]() { return ep->isComplete; });
465        if (ep->transfer->status != 0) {
466            break;
467        }
468        ret = ep->transfer->actual_length;
469    } while (false);
470    return PersistBuffer{reinterpret_cast<char *>(g_bufPtr), static_cast<uint64_t>(ret)};
471}
472
473HUSB HostUsb::GetUsbDevice(std::string connectKey)
474{
475    return mapUsbDevice[connectKey];
476}
477
478int HostUsb::WriteUsbIO(HUSB hUSB, SerializedBuffer buf)
479{
480    int childRet = 0;
481    int ret = -14000;
482    int timeout = GLOBAL_TIMEOUT * TIME_BASE;
483    HostUSBEndpoint *ep = &hUSB->hostBulkOut;
484
485    hUSB->lockDeviceHandle.lock();
486    ep->isComplete = false;
487    uint8_t* ptr = reinterpret_cast<uint8_t *>(buf.ptr);
488    size_t size = static_cast<size_t>(buf.size);
489    do {
490        std::unique_lock<std::mutex> lock(ep->mutexIo);
491        libusb_fill_bulk_transfer(ep->transfer, hUSB->devHandle, ep->endpoint, ptr, size, USBBulkCallback, ep,
492                                  timeout);
493        childRet = libusb_submit_transfer(ep->transfer);
494        hUSB->lockDeviceHandle.unlock();
495        if (childRet < 0) {
496            break;
497        }
498        ep->cv.wait(lock, [ep]() { return ep->isComplete; });
499        if (ep->transfer->status != 0) {
500            break;
501        }
502        ret = ep->transfer->actual_length;
503    } while (false);
504    return ret;
505}
506
507int HostUsb::Initial()
508{
509    if (!ctxUSB) {
510        return -1;
511    }
512    running = true;
513    auto WatchUsbNodeChangeFunc = [this]() { WatchUsbNodeChange(this); };
514    timer = std::make_unique<CTimer>(WatchUsbNodeChangeFunc);
515    timer->Start(DEVICE_CHECK_INTERVAL);
516    std::thread([this]() {
517        UsbWorkThread(this);
518    }).detach();
519    return 0;
520}
521
522static void BuildDaemonVisableLine(HDaemonInfo hdi, bool fullDisplay, string &out)
523{
524    if (fullDisplay) {
525        string sConn;
526        string sStatus;
527        switch (hdi->connType) {
528            case CONN_TCP:
529                sConn = "TCP";
530                break;
531            case CONN_USB:
532                sConn = "USB";
533                break;
534#ifdef HDC_SUPPORT_UART
535            case CONN_SERIAL:
536                sConn = "UART";
537                break;
538#endif
539            case CONN_BT:
540                sConn = "BT";
541                break;
542            default:
543                sConn = "UNKNOW";
544                break;
545        }
546        switch (hdi->connStatus) {
547            case STATUS_READY:
548                sStatus = "Ready";
549                break;
550            case STATUS_CONNECTED:
551                sStatus = "Connected";
552                break;
553            case STATUS_OFFLINE:
554                sStatus = "Offline";
555                break;
556            default:
557                sStatus = "UNKNOW";
558                break;
559        }
560        out = StringFormat("%s\t\t%s\t%s\t%s\n", hdi->connectKey.c_str(), sConn.c_str(), sStatus.c_str(),
561                  hdi->devName.c_str());
562    } else {
563        if (hdi->connStatus == STATUS_CONNECTED) {
564            out = StringFormat("%s\n", hdi->connectKey.c_str());
565        }
566    }
567}
568
569string HostUsb::GetDaemonMapList(uint8_t opType)
570{
571    string ret;
572    bool fullDisplay = false;
573    if (opType == OP_GET_STRLIST_FULL) {
574        fullDisplay = true;
575    }
576    lockMapDaemon.lock();
577    map<string, HDaemonInfo>::iterator iter;
578    string echoLine;
579    for (iter = mapDaemon.begin(); iter != mapDaemon.end(); ++iter) {
580        HDaemonInfo di = iter->second;
581        if (!di) {
582            continue;
583        }
584        echoLine = "";
585        if (opType == OP_GET_READY_STRLIST) {
586            if (di->connStatus == STATUS_READY) {
587                echoLine = StringFormat("%s ", di->connectKey.c_str());
588                ret += echoLine;
589            }
590            continue;
591        }
592        BuildDaemonVisableLine(di, fullDisplay, echoLine);
593        ret += echoLine;
594    }
595    lockMapDaemon.unlock();
596    return ret;
597}
598
599string HostUsb::AdminDaemonMap(uint8_t opType, const string &connectKey, HDaemonInfo &hDaemonInfoInOut)
600{
601    string sRet;
602    switch (opType) {
603        case OP_ADD: {
604            HDaemonInfo pdiNew = new(std::nothrow) HdcDaemonInformation();
605            if (pdiNew == nullptr) {
606                break;
607            }
608            *pdiNew = *hDaemonInfoInOut;
609            lockMapDaemon.lock();
610            if (!mapDaemon[hDaemonInfoInOut->connectKey]) {
611                mapDaemon[hDaemonInfoInOut->connectKey] = pdiNew;
612            }
613            lockMapDaemon.unlock();
614            break;
615        }
616        case OP_GET_READY_STRLIST:
617            sRet = GetDaemonMapList(opType);
618            break;
619        case OP_GET_STRLIST:
620        case OP_GET_STRLIST_FULL: {
621            sRet = GetDaemonMapList(opType);
622            break;
623        }
624        case OP_QUERY: {
625            lockMapDaemon.lock();
626            if (mapDaemon.count(connectKey)) {
627                hDaemonInfoInOut = mapDaemon[connectKey];
628            }
629            lockMapDaemon.unlock();
630            break;
631        }
632        case OP_REMOVE: {
633            lockMapDaemon.lock();
634            if (mapDaemon.count(connectKey)) {
635                mapDaemon.erase(connectKey);
636            }
637            lockMapDaemon.unlock();
638            break;
639        }
640        case OP_GET_ANY: {
641            lockMapDaemon.lock();
642            map<string, HDaemonInfo>::iterator iter;
643            for (iter = mapDaemon.begin(); iter != mapDaemon.end(); ++iter) {
644                HDaemonInfo di = iter->second;
645                // usb will be auto connected
646                if (di->connStatus == STATUS_READY || di->connStatus == STATUS_CONNECTED) {
647                    hDaemonInfoInOut = di;
648                    break;
649                }
650            }
651            lockMapDaemon.unlock();
652            break;
653        }
654        case OP_WAIT_FOR_ANY: {
655            lockMapDaemon.lock();
656            map<string, HDaemonInfo>::iterator iter;
657            for (iter = mapDaemon.begin(); iter != mapDaemon.end(); ++iter) {
658                HDaemonInfo di = iter->second;
659                if (di->connStatus == STATUS_CONNECTED) {
660                    hDaemonInfoInOut = di;
661                    break;
662                }
663            }
664            lockMapDaemon.unlock();
665            break;
666        }
667        case OP_UPDATE: {  // Cannot update the Object HDi lower key value by direct value
668            lockMapDaemon.lock();
669            HDaemonInfo hdi = mapDaemon[hDaemonInfoInOut->connectKey];
670            if (hdi) {
671                *mapDaemon[hDaemonInfoInOut->connectKey] = *hDaemonInfoInOut;
672            }
673            lockMapDaemon.unlock();
674            break;
675        }
676        default:
677            break;
678    }
679    return sRet;
680}
681}