kopia lustrzana https://gitlab.com/sane-project/backends
brother_mfp: start of network driver.
We start with SNMP experiments for button registration and general network driver framework.brother_mfp_backend
rodzic
311a38c76f
commit
45416ffe03
|
@ -383,13 +383,14 @@ EXTRA_DIST += bh.conf.in
|
|||
|
||||
|
||||
libbrother_mfp_la_SOURCES = \
|
||||
brother_mfp/brother_mfp-driver.cpp \
|
||||
brother_mfp/brother_mfp-driver_usb.cpp \
|
||||
brother_mfp/brother_mfp-driver_network.cpp \
|
||||
brother_mfp/brother_mfp-driver.h \
|
||||
brother_mfp/brother_mfp-common.h \
|
||||
brother_mfp/brother_mfp-encoder.cpp \
|
||||
brother_mfp/brother_mfp-encoder.h \
|
||||
brother_mfp/brother_mfp.cpp
|
||||
libbrother_mfp_la_CPPFLAGS = $(AM_CPPFLAGS) -DBACKEND_NAME=brother_mfp
|
||||
libbrother_mfp_la_CPPFLAGS = $(AM_CPPFLAGS) -DBACKEND_NAME=brother_mfp $(SNMP_CFLAGS)
|
||||
|
||||
nodist_libsane_brother_mfp_la_SOURCES = brother_mfp-s.cpp
|
||||
libsane_brother_mfp_la_CPPFLAGS = $(AM_CPPFLAGS) -DBACKEND_NAME=brother_mfp
|
||||
|
@ -400,7 +401,7 @@ libsane_brother_mfp_la_LIBADD = $(COMMON_LIBS) libbrother_mfp.la \
|
|||
../sanei/sanei_usb.lo \
|
||||
../sanei/sanei_config.lo \
|
||||
sane_strstatus.lo \
|
||||
$(USB_LIBS) $(JPEG_LIBS)
|
||||
$(USB_LIBS) $(JPEG_LIBS) $(SNMP_LIBS)
|
||||
EXTRA_DIST += brother_mfp.conf.in
|
||||
|
||||
|
||||
|
|
|
@ -145,8 +145,6 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
static const char *ScanModeToText(BrotherScanMode scan_mode);
|
||||
|
||||
BrotherFamily family;
|
||||
SANE_Word capabilities;
|
||||
BrotherEncoder *encoder;
|
||||
|
@ -197,3 +195,48 @@ private:
|
|||
bool out_of_docs;
|
||||
|
||||
};
|
||||
|
||||
class BrotherNetworkDriver : public BrotherDriver
|
||||
{
|
||||
public:
|
||||
BrotherNetworkDriver (const char *devicename, BrotherFamily family, SANE_Word capabilities);
|
||||
|
||||
~BrotherNetworkDriver ();
|
||||
|
||||
SANE_Status Connect () override;
|
||||
|
||||
SANE_Status Disconnect () override;
|
||||
|
||||
SANE_Status StartScan () override;
|
||||
|
||||
SANE_Status CancelScan () override;
|
||||
|
||||
SANE_Status CheckSensor(BrotherSensor &status) override;
|
||||
|
||||
SANE_Status ReadScanData (SANE_Byte *data, size_t data_len,
|
||||
size_t *bytes_read) override;
|
||||
|
||||
private:
|
||||
SANE_Status Init ();
|
||||
|
||||
SANE_Status SNMPRegisterButtons ();
|
||||
|
||||
// SANE_Status PollForReadFlush (useconds_t max_time);
|
||||
// SANE_Status PollForRead (SANE_Byte *buffer, size_t *buf_len,
|
||||
// useconds_t *max_time);
|
||||
bool is_open;
|
||||
bool in_session;
|
||||
bool is_scanning;
|
||||
bool was_cancelled;
|
||||
char *devicename;
|
||||
SANE_Int next_frame_number;
|
||||
|
||||
SANE_Byte small_buffer[1024];
|
||||
SANE_Byte *data_buffer;
|
||||
size_t data_buffer_bytes;
|
||||
|
||||
bool out_of_docs;
|
||||
|
||||
int sockfd;
|
||||
|
||||
};
|
||||
|
|
|
@ -0,0 +1,318 @@
|
|||
/* sane - Scanner Access Now Easy.
|
||||
|
||||
BACKEND brother_mfp
|
||||
|
||||
Copyright (C) 2022 Ralph Little <skelband@gmail.com>
|
||||
|
||||
This file is part of the SANE package.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU 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 General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
This file implements a SANE backend for Brother Multifunction Devices.
|
||||
*/
|
||||
|
||||
#define DEBUG_DECLARE_ONLY
|
||||
|
||||
#include "../include/sane/config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef HAVE_SYS_SOCKET_H
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NETINET_IN_H
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
|
||||
|
||||
#include "../include/sane/sane.h"
|
||||
#include "../include/sane/sanei.h"
|
||||
#include "../include/sane/saneopts.h"
|
||||
#include "../include/sane/sanei_config.h"
|
||||
#include "../include/sane/sanei_debug.h"
|
||||
|
||||
#include "brother_mfp-common.h"
|
||||
#include "brother_mfp-driver.h"
|
||||
|
||||
/*
|
||||
* Protocol defines.
|
||||
*
|
||||
*/
|
||||
#define BROTHER_USB_REQ_STARTSESSION 1
|
||||
#define BROTHER_USB_REQ_STOPSESSION 2
|
||||
#define BROTHER_USB_REQ_BUTTONSTATE 3
|
||||
|
||||
#define BROTHER_READ_BUFFER_LEN (16 * 1024)
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------*/
|
||||
|
||||
union BrotherSocketAddr
|
||||
{
|
||||
struct sockaddr_storage storage;
|
||||
struct sockaddr addr;
|
||||
struct sockaddr_in ipv4;
|
||||
struct sockaddr_in6 ipv6;
|
||||
};
|
||||
|
||||
SANE_Status BrotherNetworkDriver::Connect ()
|
||||
{
|
||||
int val;
|
||||
|
||||
if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
return SANE_STATUS_IO_ERROR;
|
||||
}
|
||||
|
||||
val = 1;
|
||||
setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
|
||||
|
||||
/*
|
||||
* Using TCP_NODELAY improves responsiveness, especially on systems
|
||||
* with a slow loopback interface...
|
||||
*/
|
||||
|
||||
val = 1;
|
||||
setsockopt (sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
|
||||
|
||||
/*
|
||||
* Close this socket when starting another process...
|
||||
*/
|
||||
|
||||
fcntl (sockfd, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
BrotherSocketAddr adr_inet;
|
||||
memset (&adr_inet, 0, sizeof adr_inet);
|
||||
|
||||
adr_inet.ipv4.sin_family = AF_INET;
|
||||
adr_inet.ipv4.sin_port = htons (54921);
|
||||
|
||||
if (!inet_aton ("192.168.1.19", &adr_inet.ipv4.sin_addr))
|
||||
{
|
||||
return SANE_STATUS_IO_ERROR;
|
||||
}
|
||||
|
||||
if (connect (sockfd, (struct sockaddr*) &adr_inet, sizeof(adr_inet.ipv4)) < 0)
|
||||
{
|
||||
return SANE_STATUS_IO_ERROR;
|
||||
}
|
||||
|
||||
SANE_Status init_res = Init ();
|
||||
|
||||
if (init_res != SANE_STATUS_GOOD)
|
||||
{
|
||||
DBG (DBG_SERIOUS, "BrotherUSBDriver::Connect: failed to send init session: %d\n", init_res);
|
||||
Disconnect ();
|
||||
}
|
||||
|
||||
/*
|
||||
* Register buttons.
|
||||
*
|
||||
*/
|
||||
(void)SNMPRegisterButtons();
|
||||
|
||||
return SANE_STATUS_NO_MEM;
|
||||
}
|
||||
|
||||
|
||||
#if HAVE_LIBSNMP
|
||||
#include <net-snmp/net-snmp-config.h>
|
||||
#include <net-snmp/net-snmp-includes.h>
|
||||
#endif
|
||||
|
||||
SANE_Status BrotherNetworkDriver::SNMPRegisterButtons ()
|
||||
{
|
||||
#if HAVE_LIBSNMP
|
||||
init_snmp ("brother_mfp");
|
||||
|
||||
/*
|
||||
* Initialize a "session" that defines who we're going to talk to
|
||||
*/
|
||||
struct snmp_session session;
|
||||
|
||||
oid anOID[MAX_OID_LEN];
|
||||
size_t anOID_len = MAX_OID_LEN;
|
||||
|
||||
snmp_sess_init (&session); /* set up defaults */
|
||||
session.peername = (char *)"192.168.1.19";
|
||||
|
||||
session.version = SNMP_VERSION_1;
|
||||
|
||||
/* set the SNMPv1 community name used for authentication */
|
||||
session.community = (u_char *)"internal";
|
||||
session.community_len = strlen ("internal");
|
||||
|
||||
SOCK_STARTUP;
|
||||
|
||||
/*
|
||||
* Open the session
|
||||
*/
|
||||
struct snmp_session *ss = snmp_open (&session);
|
||||
|
||||
if (!ss) {
|
||||
snmp_perror("ack");
|
||||
snmp_log(LOG_ERR, "something horrible happened!!!\n");
|
||||
return SANE_STATUS_IO_ERROR;
|
||||
}
|
||||
|
||||
netsnmp_pdu *pdu;
|
||||
oid the_oid[MAX_OID_LEN];
|
||||
size_t oid_len;
|
||||
const char *oid_str = ".1.3.6.1.4.1.2435.2.3.9.2.11.1.1.0";
|
||||
|
||||
pdu = snmp_pdu_create (SNMP_MSG_SET);
|
||||
oid_len = MAX_OID_LEN;
|
||||
|
||||
// Parse the OID
|
||||
if (snmp_parse_oid (oid_str, the_oid, &oid_len) == 0)
|
||||
{
|
||||
snmp_perror (oid_str);
|
||||
return SANE_STATUS_IO_ERROR;
|
||||
}
|
||||
|
||||
struct ButtonApp
|
||||
{
|
||||
const char *function;
|
||||
unsigned int app_num;
|
||||
} button_apps[] =
|
||||
{
|
||||
{"IMAGE", 1} ,
|
||||
{"OCR", 3} ,
|
||||
{"FILE", 5} ,
|
||||
{"EMAIL", 2} ,
|
||||
{ nullptr, 0 }
|
||||
};
|
||||
|
||||
const char *backend_host_addr = "192.168.1.12";
|
||||
unsigned int backend_host_port = 54925;
|
||||
const char *backend_host_name = "BACKEND_HOST";
|
||||
unsigned int reg_lifetime = 360;
|
||||
|
||||
for (const ButtonApp *app = button_apps; app->function; app++)
|
||||
{
|
||||
(void) snprintf ((char*) small_buffer,
|
||||
sizeof(small_buffer),
|
||||
"TYPE=BR;BUTTON=SCAN;USER=\"%s\";FUNC=%s;HOST=%s:%u;APPNUM=%u;DURATION=%u;",
|
||||
backend_host_name,
|
||||
app->function,
|
||||
backend_host_addr,
|
||||
backend_host_port,
|
||||
app->app_num,
|
||||
reg_lifetime);
|
||||
|
||||
// const char *value = "TYPE=BR;BUTTON=SCAN;USER=\"MYTESTSVR\";FUNC=OCR;HOST=192.168.1.12:54925;APPNUM=3;DURATION=360;";
|
||||
if (snmp_pdu_add_variable (pdu,
|
||||
the_oid,
|
||||
oid_len,
|
||||
ASN_OCTET_STR,
|
||||
(const char*) small_buffer,
|
||||
strlen ((const char*) small_buffer)) == nullptr)
|
||||
{
|
||||
snmp_perror ("failed");
|
||||
return SANE_STATUS_IO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the request
|
||||
if (snmp_send (ss, pdu) == 0)
|
||||
{
|
||||
snmp_perror ("SNMP: Error while sending!");
|
||||
snmp_free_pdu (pdu);
|
||||
return SANE_STATUS_IO_ERROR;
|
||||
}
|
||||
|
||||
return SANE_STATUS_NO_MEM;
|
||||
#else
|
||||
return SANE_STATUS_GOOD;
|
||||
#endif
|
||||
}
|
||||
|
||||
SANE_Status BrotherNetworkDriver::Disconnect ()
|
||||
{
|
||||
DBG (DBG_EVENT, "BrotherNetworkDriver::Disconnect: `%s'\n", devicename);
|
||||
|
||||
if (sockfd)
|
||||
{
|
||||
close(sockfd);
|
||||
sockfd = 0;
|
||||
}
|
||||
|
||||
return SANE_STATUS_GOOD;
|
||||
}
|
||||
|
||||
SANE_Status BrotherNetworkDriver::Init ()
|
||||
{
|
||||
return SANE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
SANE_Status BrotherNetworkDriver::CancelScan ()
|
||||
{
|
||||
return SANE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
SANE_Status BrotherNetworkDriver::StartScan ()
|
||||
{
|
||||
return SANE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
SANE_Status BrotherNetworkDriver::CheckSensor (BrotherSensor &status)
|
||||
{
|
||||
return SANE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
SANE_Status BrotherNetworkDriver::ReadScanData (SANE_Byte *data, size_t max_length, size_t *length)
|
||||
{
|
||||
return SANE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
BrotherNetworkDriver::BrotherNetworkDriver (const char *devicename, BrotherFamily family,
|
||||
SANE_Word capabilities) :
|
||||
BrotherDriver (family, capabilities),
|
||||
is_open (false),
|
||||
in_session (false),
|
||||
is_scanning (false),
|
||||
was_cancelled (false),
|
||||
devicename (nullptr),
|
||||
next_frame_number (0),
|
||||
small_buffer { 0 },
|
||||
data_buffer (nullptr),
|
||||
data_buffer_bytes (0),
|
||||
out_of_docs (false),
|
||||
sockfd (0)
|
||||
{
|
||||
this->devicename = strdup(devicename);
|
||||
}
|
||||
|
||||
BrotherNetworkDriver::~BrotherNetworkDriver ()
|
||||
{
|
||||
delete[] data_buffer;
|
||||
free (devicename);
|
||||
}
|
|
@ -241,6 +241,22 @@ static Brother_Model models[] =
|
|||
CAP_BUTTON_HAS_SCAN_IMAGE |
|
||||
CAP_ENCODING_HAS_JPEG },
|
||||
|
||||
{ "Brother", "DCP-1610W", BROTHER_FAMILY_4, 0x04f9, 0x035b,
|
||||
{ 0, SANE_FIX(211.5), 0 },
|
||||
{ 0, SANE_FIX(297), 0 },
|
||||
{ 6, 100, 150, 200, 300, 600, 1200 },
|
||||
{ 7, 100, 150, 200, 300, 600, 1200, 2400 },
|
||||
CAP_MODE_COLOUR |
|
||||
CAP_MODE_GRAY |
|
||||
CAP_MODE_GRAY_DITHER |
|
||||
CAP_MODE_BW |
|
||||
CAP_SOURCE_HAS_FLATBED |
|
||||
CAP_BUTTON_HAS_SCAN_EMAIL |
|
||||
CAP_BUTTON_HAS_SCAN_FILE |
|
||||
CAP_BUTTON_HAS_SCAN_OCR |
|
||||
CAP_BUTTON_HAS_SCAN_IMAGE |
|
||||
CAP_ENCODING_HAS_JPEG },
|
||||
|
||||
{NULL, NULL, BROTHER_FAMILY_NONE, 0, 0, {0, 0, 0}, {0, 0, 0}, {0}, {0}, 0}
|
||||
};
|
||||
|
||||
|
@ -291,7 +307,11 @@ static SANE_Device **devlist = NULL;
|
|||
static const SANE_Range constraint_brightness_contrast = { -50, +50, 1 };
|
||||
|
||||
|
||||
|
||||
enum BrotherDeviceCommsType
|
||||
{
|
||||
BROTHER_DEVICE_COMMS_TYPE_USB,
|
||||
BROTHER_DEVICE_COMMS_TYPE_NETWORK,
|
||||
};
|
||||
|
||||
static SANE_Status
|
||||
attach_with_ret (const char *devicename, BrotherDevice **dev)
|
||||
|
@ -432,7 +452,115 @@ attach_with_ret (const char *devicename, BrotherDevice **dev)
|
|||
return SANE_STATUS_GOOD;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//static SANE_Status
|
||||
//attach_with_ret_network (const char *devicename, Brother_Model *model, BrotherDevice **dev)
|
||||
//{
|
||||
// BrotherDevice *device;
|
||||
// SANE_Status status;
|
||||
//
|
||||
// DBG (DBG_EVENT, "attach_with_ret_network: %s\n", devicename);
|
||||
//
|
||||
// /*
|
||||
// * See if we already know about the device.
|
||||
// *
|
||||
// */
|
||||
// for (device = first_dev; device; device = device->next)
|
||||
// {
|
||||
// if (strcmp (device->sane_device.name, devicename) == 0)
|
||||
// {
|
||||
// if (dev)
|
||||
// {
|
||||
// *dev = device;
|
||||
// }
|
||||
// return SANE_STATUS_GOOD;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /*
|
||||
// * Create a new entry for this device.
|
||||
// *
|
||||
// */
|
||||
// device = new BrotherDevice;
|
||||
// if (!device)
|
||||
// {
|
||||
// DBG (DBG_SERIOUS, "attach_with_ret_network: failed to allocate device entry %zu\n", sizeof(*device));
|
||||
// return SANE_STATUS_NO_MEM;
|
||||
// }
|
||||
//
|
||||
// device->name = strdup (devicename);
|
||||
// device->sane_device.name = device->name;
|
||||
// device->sane_device.vendor = "BROTHER";
|
||||
// device->sane_device.model = model->model;
|
||||
// device->sane_device.type = "multi-function peripheral";
|
||||
// device->model = model;
|
||||
//
|
||||
// /*
|
||||
// * Generate a driver for this device.
|
||||
// *
|
||||
// */
|
||||
// device->driver = new BrotherNetworkDriver(devicename, model->family, model->capabilities);
|
||||
// if (nullptr == device->driver)
|
||||
// {
|
||||
// DBG (DBG_SERIOUS, "attach_with_ret_network: failed to create Brother driver: %s\n", devicename);
|
||||
// return SANE_STATUS_NO_MEM;
|
||||
// }
|
||||
//
|
||||
// /*
|
||||
// * Create the modes list.
|
||||
// *
|
||||
// */
|
||||
// size_t num_modes = 0;
|
||||
// if (model->capabilities & CAP_MODE_COLOUR)
|
||||
// {
|
||||
// device->modes[num_modes++] = SANE_VALUE_SCAN_MODE_COLOR;
|
||||
// }
|
||||
// if (model->capabilities & CAP_MODE_GRAY)
|
||||
// {
|
||||
// device->modes[num_modes++] = SANE_VALUE_SCAN_MODE_GRAY;
|
||||
// }
|
||||
// if (model->capabilities & CAP_MODE_GRAY_DITHER)
|
||||
// {
|
||||
// device->modes[num_modes++] = SANE_VALUE_SCAN_MODE_GRAY_DITHER;
|
||||
// }
|
||||
// if (model->capabilities & CAP_MODE_BW)
|
||||
// {
|
||||
// device->modes[num_modes++] = SANE_VALUE_SCAN_MODE_LINEART;
|
||||
// }
|
||||
//
|
||||
// /*
|
||||
// * Create the sources list.
|
||||
// *
|
||||
// */
|
||||
// size_t num_sources = 0;
|
||||
// if (model->capabilities & CAP_SOURCE_HAS_FLATBED)
|
||||
// {
|
||||
// device->sources[num_sources++] = SANE_VALUE_SOURCE_FLATBED;
|
||||
// }
|
||||
//
|
||||
// if (model->capabilities & CAP_SOURCE_HAS_ADF)
|
||||
// {
|
||||
// device->sources[num_sources++] = SANE_VALUE_SOURCE_ADF;
|
||||
// }
|
||||
// else if (model->capabilities & CAP_SOURCE_HAS_ADF_DUPLEX)
|
||||
// {
|
||||
// device->sources[num_sources++] = SANE_VALUE_SOURCE_ADF_SIMPLEX;
|
||||
// device->sources[num_sources++] = SANE_VALUE_SOURCE_ADF_DUPLEX;
|
||||
// }
|
||||
//
|
||||
// ++num_devices;
|
||||
// device->next = first_dev;
|
||||
// first_dev = device;
|
||||
//
|
||||
// if (dev)
|
||||
// {
|
||||
// *dev = device;
|
||||
// }
|
||||
//
|
||||
// return SANE_STATUS_GOOD;
|
||||
//}
|
||||
//
|
||||
|
||||
static SANE_Status
|
||||
attach_with_no_ret (const char *devicename)
|
||||
|
@ -836,6 +964,19 @@ SANE_Status sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
|
|||
sanei_usb_find_devices (model->usb_vendor, model->usb_product, attach_with_no_ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe for network devices.
|
||||
*
|
||||
*/
|
||||
#if 0
|
||||
BrotherDevice *device;
|
||||
attach_with_ret_network ("NetworkDevice", &models[0], &device);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return SANE_STATUS_GOOD;
|
||||
}
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ being searched (in this order).
|
|||
.B SANE_DEBUG_BROTHER_MFP
|
||||
If the library was compiled with debug support enabled, this environment
|
||||
variable controls the debug level for this backend. Valid values are 1 (IMPORTANT),
|
||||
2 (SERIOUS), 3 (WARNINGS), 5 (DETAIL) and 6 (DEBUG). Selecting 5 or 6 will generate
|
||||
2 (SERIOUS), 3 (WARNINGS), 4 (EVENT), 5 (DETAIL) and 6 (DEBUG). Selecting 5 or 6 will generate
|
||||
a large amount of output.
|
||||
|
||||
|
||||
|
@ -182,7 +182,7 @@ getting a good head start on this process and for inspiring me to write this bac
|
|||
|
||||
.TP
|
||||
.I Various GitLab users
|
||||
For their help in checkout out models of devices that I do not have access to and patiently
|
||||
For their help in checking out models of devices that I do not have access to and patiently
|
||||
making PCAP captures and diag log output for me to look at!
|
||||
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue