Add support for devices supporting the Logitech bulk controller protocol

This commit is contained in:
Sanjay Sheth 2021-08-06 01:20:40 -07:00 committed by Richard Hughes
parent ce6da002aa
commit b6ff1ea40e
30 changed files with 2448 additions and 0 deletions

View File

@ -7,6 +7,13 @@ export DESTDIR=${root}/dist
build=$root/build-win32
rm -rf $DESTDIR $build
# For logitech bulk controller being disabled (-Dplugin_logitech_bulkcontroller=false):
# See https://bugzilla.redhat.com/show_bug.cgi?id=1991749
# When fixed need to do the following to enable:
# 1. need to add mingw64-protobuf mingw64-protobuf-tools to CI build deps
# 2. add protoc = /path/to/protoc-c.exe in mingw64.cross
# 3. Only enable when not a tagged release (Unsupported by Logitech)
#build
mkdir -p $build $DESTDIR && cd $build
meson .. \
@ -22,6 +29,7 @@ meson .. \
-Dplugin_redfish=false \
-Dplugin_altos=false \
-Dplugin_dell=false \
-Dplugin_logitech_bulkcontroller=false \
-Dplugin_nvme=false \
-Dplugin_parade_lspcon=false \
-Dplugin_realtek_mst=false \

View File

@ -1553,4 +1553,41 @@
<package>ShellCheck</package>
</distro>
</dependency>
<dependency type="build" id="libprotobuf-c-dev">
<distro id="arch">
<package>protobuf-c</package>
</distro>
<distro id="debian">
<control />
<package variant="x86_64" />
<package variant="i386" />
</distro>
<distro id="ubuntu">
<control />
<package variant="x86_64" />
</distro>
<distro id="fedora">
<package>protobuf-c-devel</package>
</distro>
<distro id="void">
<package>protobuf-c-devel</package>
</distro>
</dependency>
<dependency type="build" id="protobuf-c-compiler">
<distro id="debian">
<control />
<package variant="x86_64" />
<package variant="i386" />
</distro>
<distro id="ubuntu">
<control />
<package variant="x86_64" />
</distro>
<distro id="fedora">
<package />
</distro>
<distro id="void">
<package>protobuf</package>
</distro>
</dependency>
</dependencies>

View File

@ -26,6 +26,7 @@ LIB_DEPENDS= libcurl.so:ftp/curl \
libgusb.so:devel/libgusb \
libjcat.so:textproc/libjcat \
libjson-glib-1.0.so:devel/json-glib \
libprotobuf-c.so:devel/protobuf-c \
libxmlb.so:textproc/libxmlb \
libefiboot.so:devel/gnu-efi

View File

@ -481,6 +481,7 @@ done
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_uefi_recovery.so
%endif
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logind.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logitech_bulkcontroller.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logitech_hidpp.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_upower.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_vli.so

View File

@ -544,6 +544,12 @@ if get_option('libarchive')
plugin_deps += libarchive
endif
if get_option('plugin_logitech_bulkcontroller')
protobufc = dependency('libprotobuf-c')
protoc = find_program('protoc', 'protoc-c')
plugin_deps += protobufc
endif
root_incdir = include_directories('.')
if get_option('docs') == 'gtkdoc'

View File

@ -17,6 +17,7 @@ option('plugin_amt', type : 'boolean', value : true, description : 'enable Intel
option('plugin_dell', type : 'boolean', value : true, description : 'enable Dell-specific support')
option('plugin_dummy', type : 'boolean', value : false, description : 'enable the dummy device')
option('plugin_emmc', type : 'boolean', value : true, description : 'enable eMMC support')
option('plugin_logitech_bulkcontroller', type : 'boolean', value : true, description : 'enable Logitech bulk controller support')
option('plugin_parade_lspcon', type : 'boolean', value : true, description : 'enable Parade LSPCON support')
option('plugin_realtek_mst', type : 'boolean', value : true, description : 'enable Realtek MST hub support')
option('plugin_synaptics_mst', type: 'boolean', value: true, description : 'enable Synaptics MST hub support')

View File

@ -0,0 +1,40 @@
# Logitech Video Collaboration
## Introduction
This plugin can upgrade the firmware on Logitech Video Collaboration products (Rally Bar and Rally Bar Mini), using USB bulk transfer.
## Firmware Format
The daemon will decompress the cabinet archive and extract a firmware blob in
a packed binary file format.
This plugin supports the following protocol ID:
* com.logitech.vc.proto
## GUID Generation
These devices use the standard USB DeviceInstanceId values, e.g.
* `USB\VID_046D&PID_089B`
* `USB\VID_046D&PID_08D3`
## Quirk Use
This plugin uses the following plugin-specific quirks:
## Update Behavior
The peripheral firmware is deployed when the device is in normal runtime mode,
and the device will reset when the new firmware has been written.
## Design Notes
## Vendor ID Security
The vendor ID is set from the USB vendor, in this instance set to `USB:0x046D`
## External Interface Access
This plugin requires read/write access to `/dev/bus/usb`.

View File

@ -0,0 +1,208 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-logitech-bulkcontroller-common.h"
#include "usb_msg.pb-c.h"
static void
proto_manager_set_header(Logi__Device__Proto__Header *header_msg)
{
gint64 timestamp_tv;
g_return_if_fail(header_msg != NULL);
timestamp_tv = g_get_real_time();
header_msg->id = g_uuid_string_random();
header_msg->timestamp = g_strdup_printf("%" G_GINT64_FORMAT, timestamp_tv / 1000);
}
GByteArray *
proto_manager_generate_get_device_info_request(void)
{
GByteArray *buf = g_byte_array_new();
Logi__Device__Proto__Header header_msg = LOGI__DEVICE__PROTO__HEADER__INIT;
Logi__Device__Proto__GetDeviceInfoRequest get_deviceinfo_msg =
LOGI__DEVICE__PROTO__GET_DEVICE_INFO_REQUEST__INIT;
Logi__Device__Proto__Request request_msg = {
PROTOBUF_C_MESSAGE_INIT(&logi__device__proto__request__descriptor),
LOGI__DEVICE__PROTO__REQUEST__PAYLOAD_GET_DEVICE_INFO_REQUEST,
{&get_deviceinfo_msg}};
Logi__Device__Proto__UsbMsg usb_msg = LOGI__DEVICE__PROTO__USB_MSG__INIT;
proto_manager_set_header(&header_msg);
usb_msg.header = &header_msg;
usb_msg.message_case = LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_REQUEST;
usb_msg.request = &request_msg;
fu_byte_array_set_size(buf, logi__device__proto__usb_msg__get_packed_size(&usb_msg));
logi__device__proto__usb_msg__pack(&usb_msg, (unsigned char *)buf->data);
return buf;
}
GByteArray *
proto_manager_generate_transition_to_device_mode_request(void)
{
GByteArray *buf = g_byte_array_new();
Logi__Device__Proto__Header header_msg = LOGI__DEVICE__PROTO__HEADER__INIT;
Logi__Device__Proto__TransitionToDeviceModeRequest transition_to_device_mode_msg =
LOGI__DEVICE__PROTO__TRANSITION_TO_DEVICE_MODE_REQUEST__INIT;
Logi__Device__Proto__Request request_msg = {
PROTOBUF_C_MESSAGE_INIT(&logi__device__proto__request__descriptor),
LOGI__DEVICE__PROTO__REQUEST__PAYLOAD_TRANSITION_TO_DEVICEMODE_REQUEST,
{(Logi__Device__Proto__GetDeviceInfoRequest *)&transition_to_device_mode_msg}};
Logi__Device__Proto__UsbMsg usb_msg = LOGI__DEVICE__PROTO__USB_MSG__INIT;
proto_manager_set_header(&header_msg);
usb_msg.header = &header_msg;
usb_msg.message_case = LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_REQUEST;
usb_msg.request = &request_msg;
fu_byte_array_set_size(buf, logi__device__proto__usb_msg__get_packed_size(&usb_msg));
logi__device__proto__usb_msg__pack(&usb_msg, (unsigned char *)buf->data);
return buf;
}
GByteArray *
proto_manager_decode_message(const guint8 *data,
guint32 len,
FuLogitechBulkcontrollerProtoId *proto_id,
GError **error)
{
g_autoptr(GByteArray) buf_decoded = g_byte_array_new();
Logi__Device__Proto__UsbMsg *usb_msg =
logi__device__proto__usb_msg__unpack(NULL, len, (const unsigned char *)data);
if (usb_msg == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"unable to unpack data");
return NULL;
}
switch (usb_msg->message_case) {
case LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_ACK:
*proto_id = kProtoId_Ack;
break;
case LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_RESPONSE:
if (!usb_msg->response) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"no USB response");
return NULL;
}
switch (usb_msg->response->payload_case) {
case LOGI__DEVICE__PROTO__RESPONSE__PAYLOAD_GET_DEVICE_INFO_RESPONSE:
if (usb_msg->response->get_device_info_response) {
const gchar *tmp =
usb_msg->response->get_device_info_response->payload;
*proto_id = kProtoId_GetDeviceInfoResponse;
if (tmp != NULL)
g_byte_array_append(buf_decoded,
(const guint8 *)tmp,
strlen(tmp));
}
break;
case LOGI__DEVICE__PROTO__RESPONSE__PAYLOAD_TRANSITION_TO_DEVICEMODE_RESPONSE:
if (usb_msg->response->transition_to_devicemode_response) {
*proto_id = kProtoId_TransitionToDeviceModeResponse;
fu_byte_array_append_uint8(
buf_decoded,
usb_msg->response->transition_to_devicemode_response->success);
}
break;
default:
break;
};
break;
case LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_EVENT:
if (!usb_msg->response) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"no USB event");
return NULL;
}
switch (usb_msg->event->payload_case) {
case LOGI__DEVICE__PROTO__EVENT__PAYLOAD_KONG_EVENT:
if (usb_msg->event->kong_event) {
const gchar *tmp = usb_msg->event->kong_event->mqtt_event;
*proto_id = kProtoId_KongEvent;
if (tmp != NULL)
g_byte_array_append(buf_decoded,
(const guint8 *)tmp,
strlen(tmp));
}
break;
case LOGI__DEVICE__PROTO__EVENT__PAYLOAD_HANDSHAKE_EVENT:
if (usb_msg->event->handshake_event) {
*proto_id = kProtoId_HandshakeEvent;
}
break;
case LOGI__DEVICE__PROTO__EVENT__PAYLOAD_CRASH_DUMP_AVAILABLE_EVENT:
*proto_id = kProtoId_CrashDumpAvailableEvent;
break;
default:
break;
};
break;
default:
break;
};
logi__device__proto__usb_msg__free_unpacked(usb_msg, NULL);
return g_steal_pointer(&buf_decoded);
}
const gchar *
fu_logitech_bulkcontroller_device_status_to_string(FuLogitechBulkcontrollerDeviceStatus status)
{
if (status == kDeviceStateUnknown)
return "Unknown";
if (status == kDeviceStateOffline)
return "Offline";
if (status == kDeviceStateOnline)
return "Online";
if (status == kDeviceStateIdle)
return "Idle";
if (status == kDeviceStateInUse)
return "InUse";
if (status == kDeviceStateAudioOnly)
return "AudioOnly";
if (status == kDeviceStateEnumerating)
return "Enumerating";
return NULL;
}
const gchar *
fu_logitech_bulkcontroller_device_update_state_to_string(
FuLogitechBulkcontrollerDeviceUpdateState update_state)
{
if (update_state == kUpdateStateUnknown)
return "Unknown";
if (update_state == kUpdateStateCurrent)
return "Current";
if (update_state == kUpdateStateAvailable)
return "Available";
if (update_state == kUpdateStateStarting)
return "Starting";
if (update_state == kUpdateStateDownloading)
return "Downloading";
if (update_state == kUpdateStateReady)
return "Ready";
if (update_state == kUpdateStateUpdating)
return "Updating";
if (update_state == kUpdateStateScheduled)
return "Scheduled";
if (update_state == kUpdateStateError)
return "Error";
return NULL;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <glib.h>
#include "usb_msg.pb-c.h"
typedef enum {
kDeviceStateUnknown = -1,
kDeviceStateOffline,
kDeviceStateOnline,
kDeviceStateIdle,
kDeviceStateInUse,
kDeviceStateAudioOnly,
kDeviceStateEnumerating
} FuLogitechBulkcontrollerDeviceStatus;
typedef enum {
kUpdateStateUnknown = -1,
kUpdateStateCurrent,
kUpdateStateAvailable,
kUpdateStateStarting = 3,
kUpdateStateDownloading,
kUpdateStateReady,
kUpdateStateUpdating,
kUpdateStateScheduled,
kUpdateStateError
} FuLogitechBulkcontrollerDeviceUpdateState;
typedef enum {
kProtoId_UnknownId,
kProtoId_GetDeviceInfoResponse,
kProtoId_TransitionToDeviceModeResponse,
kProtoId_Ack,
kProtoId_KongEvent,
kProtoId_HandshakeEvent,
kProtoId_CrashDumpAvailableEvent
} FuLogitechBulkcontrollerProtoId;
const gchar *
fu_logitech_bulkcontroller_device_status_to_string(FuLogitechBulkcontrollerDeviceStatus status);
const gchar *
fu_logitech_bulkcontroller_device_update_state_to_string(
FuLogitechBulkcontrollerDeviceUpdateState update_state);
GByteArray *
proto_manager_generate_get_device_info_request(void);
GByteArray *
proto_manager_generate_transition_to_device_mode_request(void);
GByteArray *
proto_manager_decode_message(const guint8 *data,
guint32 len,
FuLogitechBulkcontrollerProtoId *proto_id,
GError **error);

View File

@ -0,0 +1,704 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include <json-glib/json-glib.h>
#include <string.h>
#include "fu-logitech-bulkcontroller-common.h"
#include "fu-logitech-bulkcontroller-device.h"
/* SYNC interface follows TLSV (Type, Length, SequenceID, Value) protocol */
/* UPD interface follows TLV (Type, Length, Value) protocol */
/* Payload size limited to 8k for both interfaces */
#define UPD_PACKET_HEADER_SIZE (2 * sizeof(guint32))
#define SYNC_PACKET_HEADER_SIZE (3 * sizeof(guint32))
#define WRITE_TIME_OUT 100
#define HASH_TIMEOUT 30000
#define MAX_DATA_SIZE 8192 /* 8k */
#define PAYLOAD_SIZE MAX_DATA_SIZE - UPD_PACKET_HEADER_SIZE
#define UPD_INTERFACE_SUBPROTOCOL_ID 117
#define SYNC_INTERFACE_SUBPROTOCOL_ID 118
#define BULK_TRANSFER_TIMEOUT 1000
#define HASH_VALUE_SIZE 16
#define LENGTH_OFFSET 0x4
#define COMMAND_OFFSET 0x0
#define SYNC_ACK_PAYLOAD_LENGTH 5
enum { SHA_256, SHA_512, MD5 };
enum { EP_OUT, EP_IN, EP_LAST };
enum { BULK_INTERFACE_UPD, BULK_INTERFACE_SYNC };
typedef enum {
CMD_CHECK_BUFFERSIZE = 0xCC00,
CMD_INIT = 0xCC01,
CMD_START_TRANSFER = 0xCC02,
CMD_DATA_TRANSFER = 0xCC03,
CMD_END_TRANSFER = 0xCC04,
CMD_UNINIT = 0xCC05,
CMD_BUFFER_READ = 0xCC06,
CMD_BUFFER_WRITE = 0xCC07,
CMD_UNINIT_BUFFER = 0xCC08,
CMD_ACK = 0xFF01,
CMD_TIMEOUT = 0xFF02,
CMD_NACK = 0xFF03
} UsbCommands;
struct _FuLogitechBulkcontrollerDevice {
FuUsbDevice parent_instance;
guint sync_ep[EP_LAST];
guint update_ep[EP_LAST];
guint sync_iface;
guint update_iface;
FuLogitechBulkcontrollerDeviceStatus status;
FuLogitechBulkcontrollerDeviceUpdateState update_status;
};
G_DEFINE_TYPE(FuLogitechBulkcontrollerDevice, fu_logitech_bulkcontroller_device, FU_TYPE_USB_DEVICE)
static void
fu_logitech_bulkcontroller_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device);
fu_common_string_append_kx(str, idt, "SyncIface", self->sync_iface);
fu_common_string_append_kx(str, idt, "UpdateIface", self->update_iface);
fu_common_string_append_kv(
str,
idt,
"Status",
fu_logitech_bulkcontroller_device_status_to_string(self->status));
fu_common_string_append_kv(
str,
idt,
"UpdateState",
fu_logitech_bulkcontroller_device_update_state_to_string(self->update_status));
}
static gboolean
fu_logitech_bulkcontroller_device_probe(FuDevice *device, GError **error)
{
FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device);
g_autoptr(GPtrArray) intfs = NULL;
intfs = g_usb_device_get_interfaces(fu_usb_device_get_dev(FU_USB_DEVICE(self)), error);
if (intfs == NULL)
return FALSE;
for (guint i = 0; i < intfs->len; i++) {
GUsbInterface *intf = g_ptr_array_index(intfs, i);
if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_VENDOR_SPECIFIC &&
g_usb_interface_get_protocol(intf) == 0x1) {
if (g_usb_interface_get_subclass(intf) == SYNC_INTERFACE_SUBPROTOCOL_ID) {
g_autoptr(GPtrArray) endpoints =
g_usb_interface_get_endpoints(intf);
self->sync_iface = g_usb_interface_get_number(intf);
if (endpoints == NULL)
continue;
for (guint j = 0; j < endpoints->len; j++) {
GUsbEndpoint *ep = g_ptr_array_index(endpoints, j);
if (j == EP_OUT)
self->sync_ep[EP_OUT] =
g_usb_endpoint_get_address(ep);
else
self->sync_ep[EP_IN] =
g_usb_endpoint_get_address(ep);
}
} else if (g_usb_interface_get_subclass(intf) ==
UPD_INTERFACE_SUBPROTOCOL_ID) {
g_autoptr(GPtrArray) endpoints =
g_usb_interface_get_endpoints(intf);
self->sync_iface = g_usb_interface_get_number(intf);
if (endpoints == NULL)
continue;
for (guint j = 0; j < endpoints->len; j++) {
GUsbEndpoint *ep = g_ptr_array_index(endpoints, j);
if (j == EP_OUT)
self->update_ep[EP_OUT] =
g_usb_endpoint_get_address(ep);
else
self->update_ep[EP_IN] =
g_usb_endpoint_get_address(ep);
}
}
}
}
return TRUE;
}
static gboolean
fu_logitech_bulkcontroller_device_send(FuLogitechBulkcontrollerDevice *self,
GByteArray *buf,
gint interface_id,
GError **error)
{
gsize transferred = 0;
gint ep;
GCancellable *cancellable = NULL;
g_return_val_if_fail(buf != NULL, FALSE);
if (interface_id == BULK_INTERFACE_SYNC) {
ep = self->sync_ep[EP_OUT];
} else if (interface_id == BULK_INTERFACE_UPD) {
ep = self->update_ep[EP_OUT];
} else {
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "interface is invalid");
return FALSE;
}
if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)),
ep,
(guint8 *)buf->data,
buf->len,
&transferred,
WRITE_TIME_OUT,
cancellable,
error)) {
g_prefix_error(error, "bulk transfer failed: ");
return FALSE;
}
return TRUE;
}
static gboolean
fu_logitech_bulkcontroller_device_recv(FuLogitechBulkcontrollerDevice *self,
GByteArray *buf,
gint interface_id,
guint timeout,
GError **error)
{
gsize received_length = 0;
gint ep;
g_return_val_if_fail(buf != NULL, FALSE);
if (interface_id == BULK_INTERFACE_SYNC) {
ep = self->sync_ep[EP_IN];
} else if (interface_id == BULK_INTERFACE_UPD) {
ep = self->update_ep[EP_IN];
} else {
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "interface is invalid");
return FALSE;
}
if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)),
ep,
buf->data,
buf->len,
&received_length,
timeout,
NULL,
error)) {
g_prefix_error(error, "bulk transfer failed: ");
return FALSE;
}
return TRUE;
}
static gboolean
fu_logitech_bulkcontroller_device_send_upd_cmd(FuLogitechBulkcontrollerDevice *self,
guint32 cmd,
GByteArray *buf,
GError **error)
{
guint32 cmd_tmp = 0x0;
guint timeout = BULK_TRANSFER_TIMEOUT;
g_autoptr(GByteArray) buf_pkt = g_byte_array_new();
g_autoptr(GByteArray) buf_ack = g_byte_array_new();
fu_byte_array_append_uint32(buf_pkt, cmd, G_LITTLE_ENDIAN); /* Type(T) : Command type */
fu_byte_array_append_uint32(buf_pkt,
buf != NULL ? buf->len : 0,
G_LITTLE_ENDIAN); /*Length(L) : Length of payload */
if (buf != NULL) {
g_byte_array_append(buf_pkt,
buf->data,
buf->len); /* Value(V) : Actual payload data */
}
if (!fu_logitech_bulkcontroller_device_send(self, buf_pkt, BULK_INTERFACE_UPD, error))
return FALSE;
/* receiving INIT ACK */
fu_byte_array_set_size(buf_ack, MAX_DATA_SIZE);
/* Extending the bulk transfer timeout value, as android device takes some time to
calculate Hash and respond */
if (CMD_END_TRANSFER == cmd)
timeout = HASH_TIMEOUT;
if (!fu_logitech_bulkcontroller_device_recv(self,
buf_ack,
BULK_INTERFACE_UPD,
timeout,
error))
return FALSE;
if (!fu_common_read_uint32_safe(buf_ack->data,
buf_ack->len,
COMMAND_OFFSET,
&cmd_tmp,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (cmd_tmp != CMD_ACK) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "not CMD_ACK, got %x", cmd);
return FALSE;
}
if (!fu_common_read_uint32_safe(buf_ack->data,
buf_ack->len,
UPD_PACKET_HEADER_SIZE,
&cmd_tmp,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (cmd_tmp != cmd) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"invalid upd message received, expected %x, got %x",
cmd,
cmd_tmp);
return FALSE;
}
return TRUE;
}
static gboolean
fu_logitech_bulkcontroller_device_send_sync_cmd(FuLogitechBulkcontrollerDevice *self,
guint32 cmd,
GByteArray *buf,
GError **error)
{
guint32 cmd_tmp = 0x0;
guint64 cmd_tmp_64 = 0x0;
guint32 verify_cmd = 0x0;
gchar ack_payload[SYNC_ACK_PAYLOAD_LENGTH] = {0x0};
g_autoptr(GByteArray) buf_pkt = g_byte_array_new();
g_autoptr(GByteArray) buf_ack = g_byte_array_new();
fu_byte_array_append_uint32(buf_pkt, cmd, G_LITTLE_ENDIAN); /* Type(T) : Command type */
fu_byte_array_append_uint32(buf_pkt,
buf != NULL ? buf->len : 0,
G_LITTLE_ENDIAN); /*Length(L) : Length of payload */
fu_byte_array_append_uint32(buf_pkt,
g_random_int_range(0, G_MAXUINT16),
G_LITTLE_ENDIAN); /*Sequence(S) : Sequence ID of the data */
if (buf != NULL) {
g_byte_array_append(buf_pkt,
buf->data,
buf->len); /* Value(V) : Actual payload data */
}
if (!fu_logitech_bulkcontroller_device_send(self, buf_pkt, BULK_INTERFACE_SYNC, error))
return FALSE;
/* receiving ACK */
fu_byte_array_set_size(buf_ack, MAX_DATA_SIZE);
if (!fu_logitech_bulkcontroller_device_recv(self,
buf_ack,
BULK_INTERFACE_SYNC,
BULK_TRANSFER_TIMEOUT,
error))
return FALSE;
if (!fu_common_read_uint32_safe(buf_ack->data,
buf_ack->len,
COMMAND_OFFSET,
&cmd_tmp,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (cmd_tmp != CMD_ACK) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"not sync CMD_ACK, got %x",
cmd_tmp);
return FALSE;
}
if (!fu_common_read_uint64_safe(buf_ack->data,
buf_ack->len,
SYNC_PACKET_HEADER_SIZE,
&cmd_tmp_64,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (!fu_memcpy_safe((guint8 *)ack_payload,
sizeof(ack_payload),
0x0,
(guint8 *)&cmd_tmp_64,
sizeof(cmd_tmp_64),
0x0,
SYNC_ACK_PAYLOAD_LENGTH,
error)) {
g_prefix_error(error, "failed to retrieve payload data: ");
return FALSE;
}
verify_cmd = fu_common_strtoull(ack_payload);
if (verify_cmd != cmd) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"invalid sync message payload received, expected %x, got %x",
cmd,
verify_cmd);
return FALSE;
}
return TRUE;
}
static gboolean
fu_logitech_bulkcontroller_device_recv_sync_cmd(FuLogitechBulkcontrollerDevice *self,
guint32 cmd,
GByteArray *buf,
GError **error)
{
guint32 cmd_tmp = 0x0;
guint32 response_length = 0;
g_autoptr(GByteArray) buf_pkt = g_byte_array_new();
g_autoptr(GByteArray) buf_ack = g_byte_array_new();
fu_byte_array_set_size(buf_pkt, MAX_DATA_SIZE);
if (!fu_logitech_bulkcontroller_device_recv(self,
buf_pkt,
BULK_INTERFACE_SYNC,
BULK_TRANSFER_TIMEOUT,
error))
return FALSE;
if (!fu_common_read_uint32_safe(buf_pkt->data,
buf_pkt->len,
COMMAND_OFFSET,
&cmd_tmp,
G_LITTLE_ENDIAN,
error))
return FALSE;
if (cmd_tmp != cmd) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"invalid sync message received, expected %x, got %x",
cmd,
cmd_tmp);
return FALSE;
}
if (!fu_common_read_uint32_safe(buf_pkt->data,
buf_pkt->len,
LENGTH_OFFSET,
&response_length,
G_LITTLE_ENDIAN,
error))
return FALSE;
fu_byte_array_append_uint32(buf_ack, CMD_ACK, G_LITTLE_ENDIAN); /* ACK message */
fu_byte_array_append_uint32(buf_ack, sizeof(guint32), G_LITTLE_ENDIAN); /* Length */
fu_byte_array_append_uint32(buf_ack, 0, G_LITTLE_ENDIAN); /* Sequence ID */
fu_byte_array_append_uint32(buf_ack, cmd, G_LITTLE_ENDIAN); /* Payload */
if (!fu_logitech_bulkcontroller_device_send(self, buf_ack, BULK_INTERFACE_SYNC, error))
return FALSE;
if (buf != NULL)
g_byte_array_append(buf, buf_pkt->data + SYNC_PACKET_HEADER_SIZE, response_length);
return TRUE;
}
static gchar *
fu_logitech_bulkcontroller_device_compute_hash(GBytes *data)
{
guint8 md5buf[HASH_VALUE_SIZE] = {0};
gsize data_len = sizeof(md5buf);
GChecksum *checksum = g_checksum_new(G_CHECKSUM_MD5);
g_checksum_update(checksum, g_bytes_get_data(data, NULL), g_bytes_get_size(data));
g_checksum_get_digest(checksum, (guint8 *)&md5buf, &data_len);
return g_base64_encode(md5buf, sizeof(md5buf));
}
static gboolean
fu_logitech_bulkcontroller_device_write_firmware(FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device);
g_autofree gchar *base64hash = NULL;
g_autoptr(GByteArray) end_pkt = g_byte_array_new();
g_autoptr(GByteArray) start_pkt = g_byte_array_new();
g_autoptr(GBytes) fw = NULL;
g_autoptr(GPtrArray) chunks = NULL;
/* get default image */
fw = fu_firmware_get_bytes(firmware, error);
if (fw == NULL)
return FALSE;
/* Sending INIT */
if (!fu_logitech_bulkcontroller_device_send_upd_cmd(self, CMD_INIT, NULL, error)) {
g_prefix_error(error, "error in writing init transfer packet: ");
return FALSE;
}
/* transfer sent */
fu_device_set_status(device, FWUPD_STATUS_DEVICE_WRITE);
fu_byte_array_append_uint64(start_pkt, g_bytes_get_size(fw), G_LITTLE_ENDIAN);
if (!fu_logitech_bulkcontroller_device_send_upd_cmd(self,
CMD_START_TRANSFER,
start_pkt,
error)) {
g_prefix_error(error, "error in writing start transfer packet: ");
return FALSE;
}
/* each block */
chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, PAYLOAD_SIZE);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
g_autoptr(GByteArray) data_pkt = g_byte_array_new();
g_byte_array_append(data_pkt, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk));
if (!fu_logitech_bulkcontroller_device_send_upd_cmd(self,
CMD_DATA_TRANSFER,
data_pkt,
error)) {
g_prefix_error(error, "failed to send data packet 0x%x: ", i);
return FALSE;
}
fu_device_set_progress_full(FU_DEVICE(self), i + 1, chunks->len);
}
/* sending end transfer */
base64hash = fu_logitech_bulkcontroller_device_compute_hash(fw);
fu_byte_array_append_uint32(end_pkt, 1, G_LITTLE_ENDIAN); /* update */
fu_byte_array_append_uint32(end_pkt, 0, G_LITTLE_ENDIAN); /* force */
fu_byte_array_append_uint32(end_pkt, MD5, G_LITTLE_ENDIAN); /* checksum type */
g_byte_array_append(end_pkt, (const guint8 *)base64hash, strlen(base64hash));
if (!fu_logitech_bulkcontroller_device_send_upd_cmd(self,
CMD_END_TRANSFER,
end_pkt,
error)) {
g_prefix_error(error, "error in writing end transfer transfer packet: ");
return FALSE;
}
/* send uninit */
if (!fu_logitech_bulkcontroller_device_send_upd_cmd(self, CMD_UNINIT, NULL, error)) {
g_prefix_error(error, "error in writing finish transfer packet: ");
return FALSE;
}
/* success! */
return TRUE;
}
static gboolean
fu_logitech_bulkcontroller_device_open(FuDevice *device, GError **error)
{
FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device);
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
/* FuUsbDevice->open */
if (!FU_DEVICE_CLASS(fu_logitech_bulkcontroller_device_parent_class)->open(device, error))
return FALSE;
/* claim both interfaces */
if (!g_usb_device_claim_interface(usb_device,
self->update_iface,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error(error, "failed to claim update interface: ");
return FALSE;
}
if (!g_usb_device_claim_interface(usb_device,
self->sync_iface,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error(error, "failed to claim sync interface: ");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_logitech_bulkcontroller_device_close(FuDevice *device, GError **error)
{
FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device);
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
if (!g_usb_device_release_interface(usb_device,
self->update_iface,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error(error, "failed to release update interface: ");
return FALSE;
}
if (!g_usb_device_release_interface(usb_device,
self->sync_iface,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error(error, "failed to release sync interface: ");
return FALSE;
}
/* FuUsbDevice->close */
return FU_DEVICE_CLASS(fu_logitech_bulkcontroller_device_parent_class)
->close(device, error);
}
static gboolean
fu_logitech_bulkcontroller_device_json_parser(FuDevice *device,
GByteArray *decoded_pkt,
GError **error)
{
FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device);
JsonArray *json_devices;
JsonNode *json_root;
JsonObject *json_device;
JsonObject *json_object;
JsonObject *json_payload;
g_autoptr(JsonParser) json_parser = json_parser_new();
/* parse JSON reply */
if (!json_parser_load_from_data(json_parser,
(const gchar *)decoded_pkt->data,
decoded_pkt->len,
error)) {
g_prefix_error(error, "error in parsing json data: ");
return FALSE;
}
json_root = json_parser_get_root(json_parser);
if (json_root == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"did not get JSON root");
return FALSE;
}
json_object = json_node_get_object(json_root);
json_payload = json_object_get_object_member(json_object, "payload");
if (json_payload == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"did not get JSON payload");
return FALSE;
}
json_devices = json_object_get_array_member(json_payload, "devices");
if (json_devices == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"did not get JSON devices");
return FALSE;
}
json_device = json_array_get_object_element(json_devices, 0);
if (json_device == NULL) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"did not get JSON device");
return FALSE;
}
if (json_object_has_member(json_device, "name"))
fu_device_set_name(device, json_object_get_string_member(json_device, "name"));
if (json_object_has_member(json_device, "sw"))
fu_device_set_version(device, json_object_get_string_member(json_device, "sw"));
if (json_object_has_member(json_device, "type"))
fu_device_add_instance_id(device,
json_object_get_string_member(json_device, "type"));
if (json_object_has_member(json_device, "status"))
self->status = json_object_get_int_member(json_device, "status");
if (json_object_has_member(json_device, "updateStatus"))
self->update_status = json_object_get_int_member(json_device, "updateStatus");
return TRUE;
}
static gboolean
fu_logitech_bulkcontroller_device_setup(FuDevice *device, GError **error)
{
FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device);
g_autoptr(GByteArray) device_info_request = g_byte_array_new();
g_autoptr(GByteArray) decoded_pkt = g_byte_array_new();
g_autoptr(GByteArray) device_info_response = g_byte_array_new();
FuLogitechBulkcontrollerProtoId proto_id = kProtoId_UnknownId;
/* FuUsbDevice->setup */
if (!FU_DEVICE_CLASS(fu_logitech_bulkcontroller_device_parent_class)->setup(device, error))
return FALSE;
/* sending GetDeviceInfoRequest */
device_info_request = proto_manager_generate_get_device_info_request();
if (!fu_logitech_bulkcontroller_device_send_sync_cmd(self,
CMD_BUFFER_WRITE,
device_info_request,
error)) {
g_prefix_error(error, "error in sending buffer write packet: ");
return FALSE;
}
/* wait for the GetDeviceInfoResponse */
if (!fu_logitech_bulkcontroller_device_recv_sync_cmd(self,
CMD_BUFFER_READ,
device_info_response,
error)) {
g_prefix_error(error, "error in buffer read packet: ");
return FALSE;
}
if (!fu_logitech_bulkcontroller_device_recv_sync_cmd(self,
CMD_UNINIT_BUFFER,
NULL,
error)) {
g_prefix_error(error, "error in buffer read packet: ");
return FALSE;
}
if (!fu_logitech_bulkcontroller_device_send_sync_cmd(self,
CMD_UNINIT_BUFFER,
NULL,
error)) {
g_prefix_error(error, "error in sending buffer uninitialize packet: ");
return FALSE;
}
decoded_pkt = proto_manager_decode_message(device_info_response->data,
device_info_response->len,
&proto_id,
error);
if (decoded_pkt == NULL) {
g_prefix_error(error, "error in unpacking packet: ");
return FALSE;
}
if (proto_id != kProtoId_GetDeviceInfoResponse) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"did not get kProtoId_GetDeviceInfoResponse");
return FALSE;
}
if (!fu_logitech_bulkcontroller_device_json_parser(device, decoded_pkt, error))
return FALSE;
/* success */
return TRUE;
}
static void
fu_logitech_bulkcontroller_device_init(FuLogitechBulkcontrollerDevice *self)
{
fu_device_add_protocol(FU_DEVICE(self), "com.logitech.vc.proto");
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
}
static void
fu_logitech_bulkcontroller_device_class_init(FuLogitechBulkcontrollerDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->to_string = fu_logitech_bulkcontroller_device_to_string;
klass_device->write_firmware = fu_logitech_bulkcontroller_device_write_firmware;
klass_device->probe = fu_logitech_bulkcontroller_device_probe;
klass_device->setup = fu_logitech_bulkcontroller_device_setup;
klass_device->open = fu_logitech_bulkcontroller_device_open;
klass_device->close = fu_logitech_bulkcontroller_device_close;
}

View File

@ -0,0 +1,16 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#define FU_TYPE_LOGITECH_BULKCONTROLLER_DEVICE (fu_logitech_bulkcontroller_device_get_type())
G_DECLARE_FINAL_TYPE(FuLogitechBulkcontrollerDevice,
fu_logitech_bulkcontroller_device,
FU,
LOGITECH_BULKCONTROLLER_DEVICE,
FuUsbDevice)

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-logitech-bulkcontroller-device.h"
void
fu_plugin_init(FuPlugin *plugin)
{
fu_plugin_set_build_hash(plugin, FU_BUILD_HASH);
fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_BULKCONTROLLER_DEVICE);
}

View File

@ -0,0 +1,10 @@
# TODO: revisit InstallDuration
[USB\VID_046D&PID_089B]
Plugin = logitech_bulkcontroller
InstallDuration = 1500
[USB\VID_046D&PID_08D3]
Plugin = logitech_bulkcontroller
InstallDuration = 1500
Flags = is-mini

View File

@ -0,0 +1,37 @@
if get_option('plugin_logitech_bulkcontroller')
if not get_option('gusb')
error('gusb is required for plugin_logitech_bulkcontroller')
endif
cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechBulkController"']
install_data(['logitech-bulkcontroller.quirk'],
install_dir: join_paths(get_option('datadir'), 'fwupd', 'quirks.d')
)
subdir('proto')
shared_module('fu_plugin_logitech_bulkcontroller',
fu_hash,
sources : [
generated,
'fu-logitech-bulkcontroller-common.c',
'fu-logitech-bulkcontroller-device.c',
'fu-plugin-logitech-bulkcontroller.c',
],
include_directories : [
root_incdir,
fwupd_incdir,
fwupdplugin_incdir,
],
install : true,
install_dir: plugin_dir,
link_with : [
fwupd,
fwupdplugin,
],
c_args : cargs,
dependencies : [
plugin_deps,
],
)
endif

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
import "device_common.proto";
/**
* This message data structure holds information about the
* current AntiFlicker configuration.
*
*/
message AntiFlickerConfiguration
{
enum Mode {
NTSC_60HZ = 0;
PAL_50HZ = 1;
}
Mode mode = 1;
}
message SetAntiFlickerConfigurationRequest
{
AntiFlickerConfiguration.Mode mode = 1;
}
message SetAntiFlickerConfigurationResponse
{
bool success = 1;
repeated Error errors = 2;
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
import "device_common.proto";
message SetBLECfgRequest
{
/**
* (REQUIRED) If true, BLE is enabled and active otherwise disabled
*/
bool BLE_ON = 1;
}
message SetBLECfgResponse
{
bool success = 1;
repeated Error errors = 2;
}

View File

@ -0,0 +1,285 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* Kong as an Android device can accumulate
* crash debug information during its operation.
* When Kong is running in device mode, those
* crash dump files need to be copied over to
* PC and uploaded to S3.
* Note, if Kong is running in host mode, uploaded
* files, and then moved to device mode, will it
* copy the same files over?
*
* This message requests that crash dump files be
* copied over to PC
*
* EXPECTED RESPONSE
* SendCrashDumpResponse
*
*/
message SendCrashDumpRequest
{
/**
* Unused. Reserved for future use.
*/
bool reserved = 1;
}
/**
* Crash dump information. Most of these
* are supplied by the crash analytics service, so lets
* pass this information along.
*/
message CrashDumpInfo
{
/**
* the filename
*/
string file_name = 1;
/**
* the serial number
*/
string device_id = 2;
/**
* the software version
*/
string software_version = 3;
/**
* the file size
*/
uint64 file_size = 4;
/**
* timestamp
*/
uint64 timestamp = 5;
/**
* md5 for file
*/
string md5 = 6;
/**
* the device type . Kong|Diddy
*/
string device_type = 7;
/**
* the device mode. Hosted|Appliance
*/
string device_mode = 8;
/**
* the report type. BugReport|EventLog,Diagnostics
*/
string report_type = 9;
/**
* the content type. application/zip | text/plain | application/json
*/
string content_type = 10;
}
/**
* Response which contains the crash dump file name
* information and bool value to indicate will send
* file
*/
message SendCrashDumpResponse
{
/**
* (OPTIONAL)
* If crash dump exists, this variable
* contains the file name of crash dump
* that will be copied over.
*/
string crash_dump_file = 1;
/**
* (REQUIRED)
* bool value to indicate will send file
* true if sending file over.
* false if no file to send.
* If true, caller will look at CrashDumpInfo
*/
bool will_send_file = 2;
/**
* (OPTIONAL)
* Crash dump info
*/
CrashDumpInfo crash_dump_info = 3;
}
message SendCrashDumpRequestv2
{
/**
* The attestation challenge.
* (REQUIRED)
*/
string challenge = 1;
/**
* Time to live
* (REQUIRED)
*/
int32 ttl = 2;
}
/**
* Response which contains the crash dump file name
* information, bool value to indicate will send
* file, body of the request and signature
*/
message SendCrashDumpResponsev2
{
/**
* (OPTIONAL)
* If crash dump exists, this variable
* contains the file name of crash dump
* that will be copied over.
*/
string crash_dump_file = 1;
/**
* (REQUIRED)
* bool value to indicate will send file
* true if sending file over.
* false if no file to send.
* If true, caller will look at CrashDumpInfo
*/
bool will_send_file = 2;
/**
* (OPTIONAL)
* The get upload url body. This is a json string
*/
string body = 3;
/**
* (OPTIONAL)
* The get upload url body signature.
*/
string signature = 4;
}
/**
* This is event sent from PC or Kong to indicate
* Success
*/
message SendCrashDumpEvent
{
/**
* (REQUIRED)
* Contains the file name of crash dump
* that is being sent or in process of being
* received
*/
string crash_dump_file = 1;
/**
* (REQUIRED)
* Transfer state.
* true indicates file was received without errors and bug report file was
* uploaded false means an error occurred
*/
bool success = 2;
}
/**
* Place holder for Android requesting that a crash dump copy
* get initiated from PC side
*/
message CrashDumpAvailableEvent
{
/**
* Unused. Reserved for future use.
*/
bool reserved = 1;
}
/**
* Ask device to generate a bug report. This could be
* for gathering logcat, system logs, etc.
* Similar to SendCrashDumpRequestv2, but bug report generation is on
* demand.
* EXPECTED RESPONSE:
* GenerateCrashDumpResponse
* It should follow the same flow as described here
* https://docs.google.com/document/d/1D5nx1nenDu9ucZbYPXlNNxFEN1tx3W7k044mvi74x28/edit#heading=h.a9wyfbpb2282
*/
message GenerateCrashDumpRequest
{
/**
* The attestation challenge.
* (REQUIRED)
*/
string challenge = 1;
/**
* Time to live
* (REQUIRED)
*/
int32 ttl = 2;
/**
* The note to include in the bug report. This could be empty.
* (OPTIONAL)
*/
string note = 3;
}
/**
* Response which contains the
* crash dump file name information,
* bool value to indicate will send file,
* body of the request and signature.
* Similar to SendCrashDumpResponsev2, but bug report generation is on
* demand.
* It should follow the same flow as described here
* https://docs.google.com/document/d/1D5nx1nenDu9ucZbYPXlNNxFEN1tx3W7k044mvi74x28/edit#heading=h.a9wyfbpb2282
*/
message GenerateCrashDumpResponse
{
/**
* (OPTIONAL)
* If crash dump exists, this variable
* contains the file name of crash dump
* that will be copied over.
*/
string crash_dump_file = 1;
/**
* (REQUIRED)
* bool value to indicate will send file
* true if sending file over.
* false if no file to send.
*/
bool will_send_file = 2;
/**
* (OPTIONAL)
* The get upload url body. This is a json string
*/
string body = 3;
/**
* (OPTIONAL)
* The get upload url body signature.
*/
string signature = 4;
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* Request for certificate chain
* This is to be included in UsbMsg
* EXPECTED RESPONSE
* GetCertificateChainResponse
*/
message GetCertificateChainRequest
{
/**
* attestation challenge
*/
string attestation = 1;
/**
* time to live
*/
int32 ttl = 2;
}
/**
* Get certificate chain response
*/
message GetCertificateChainResponse
{
/**
* array of certs
*/
repeated string certchain = 1;
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* This error messages describe a failure that was encountered
* by the Sync service and primarily consist of an error code
* and a short, human-readable message. Therefore, if a client
* receives a message with a field reserved for Error messages,
* it is prudent that the application first check if there are
* errors before doing any further processing of the message.
*/
message Error
{
/**
* (REQUIRED) Error code.
*/
uint32 error_code = 1;
/**
* (OPTIONAL) Short, human-readable error message. If no
* message is available, then this will be an empty string.
*/
string error_message = 2;
/**
* (OPTIONAL) A URI to a log file or some other document
* that contains more detailed information about the error.
* If such a file is not available, this will be an empty
* string.
*/
string error_log_uri = 3;
/**
* (OPTIONAL) An optional JSON string with additional
* metadata that may be useful to the client.
*/
string json_metadata = 4;
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* Request Device information
* This is to be included in UsbMsg
* EXPECTED RESPONSE
* GetDeviceInfoResponse
*/
message GetDeviceInfoRequest
{
/**
* Unused. Reserved for future use.
*/
bool reserved = 1;
}
/**
* Get device information response
*/
message GetDeviceInfoResponse
{
/**
* payload contains actual mqtt message
*/
string payload = 1;
}

View File

@ -0,0 +1,183 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* Behavior change as of 1/28/2021 EE
* Kong sync-agent should not deprovision when this message is
* received. If would just start forwarding events to PC when message is
* received.
*
* (Legacy)
* Request to transition to device mode
* Kong could be provisioned in Host mode. This message
* will ask Kong to deprovisioned/remove host mode provisioning
* data.
* This is to be included in UsbMsg
* EXPECTED RESPONSE
* TransitionToDeviceModeResponse
*/
message TransitionToDeviceModeRequest
{
/**
* Unused. Reserved for future use.
*/
bool reserved = 1;
}
/**
* Request to transition to device mode response
*/
message TransitionToDeviceModeResponse
{
/**
* boolean value to indicate Kong was able to transition to
* device mode. If Kong is not provisioned, should just respond
* with true value.
* set to false if error was encountered during transition, and Kong
* wasn't able to transition (is this possible?)
*/
bool success = 1;
/**
* the error in integer if success was false
*/
int32 error = 2;
/**
* the error description
*/
string error_description = 3;
}
/**
* Added 1/28/2021 EE
* Request to deprovision Kong
* This request is sent by PC sync-agent when PC
* is provisioned.
* Kong sync-agent should deprovision (if provisioned)
*
* EXPECTED RESPONSE
* SetDeprovisionResponse
*/
message SetDeprovisionRequest
{
/**
* Unused. Reserved for future use.
*/
bool reserved = 1;
}
/**
* Response to deprovision request
*/
message SetDeprovisionResponse
{
/**
* boolean value to indicate Kong was able to deprovision Kong.
* If Kong is not provisioned, should just respond
* with true value.
* set to false if error was encountered during deprovisioning.
*/
bool success = 1;
/**
* the error in integer if success was false
*/
int32 error = 2;
/**
* the error description
*/
string error_description = 3;
}
/**
* Added 3/22/2021 EE
* For sending a certificate as data. There are currently
* 2 known certificate that will be transferred - Root CA, and 802.1x cert.
* Upon receipt, sync-agent should verify using the supplied hash
* and write the data to the file system.
*
* EXPECTED RESPONSE
* SendCertificateDataResponse
*/
message SendCertificateDataRequest
{
/**
* The certificate type
*/
enum CertType {
/**
* Reserved. Do not use.
*/
RESERVED = 0;
/**
* Root CA
*/
ROOT_CA = 1;
/**
* 802.1x cert
*/
NET_CONFIG = 2;
}
/**
* (REQUIRED)
* The certificate type
*/
CertType cert_type = 1;
/**
* (REQUIRED)
* the certificate file name
*/
string file_name = 2;
/**
* (REQUIRED)
* the certificate data
*/
bytes cert_data = 3;
/**
* (REQUIRED)
* the certificate md5 hash
*/
string md5 = 4;
}
/**
* Response to SendCertificateData Request
*/
message SendCertificateDataResponse
{
/**
* (REQUIRED)
* boolean value to indicate data was received, hash verified .
* set to false if error was encountered during transfer and verification.
*/
bool success = 1;
/**
* (OPTIONAL)
* the error in integer if success was false
*/
int32 error = 2;
/**
* (OPTIONAL)
* the error description if there are errors
*/
string error_description = 3;
}

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* Request to reboot device
* This is to be included in UsbMsg
* EXPECTED RESPONSE
* RebootDeviceResponse
*/
message RebootDeviceRequest
{
/**
* Unused. Reserved for future use.
*/
bool reserved = 1;
/**
* A timestamp indicating when the reboot request
* was initiated.
* The device should include this entry as part of the event information
* it sends back to PC during a reboot request.
*/
uint64 iat = 2;
}
/**
* Reboot device response
*/
message RebootDeviceResponse
{
/**
* bool value to indicate reboot was requested. If there are errors
* while requesting a device to reboot, should set the value to false
*/
bool success = 1;
}
/**
* This message requests that the speaker boost audio setting be changed.
* The device should send a device info event after this setting request are
* handled.
*
* EXPECTED RESPONSE
* SetSpeakerBoostResponse
*
*/
message SetSpeakerBoostRequest
{
/**
* (REQUIRED) The speaker boost setting to be set
*
* If value is 0, the request is to disable. If 1,
* the request is to enable.
*/
int32 speaker_boost = 1;
}
message SetSpeakerBoostResponse
{
/**
* (REQUIRED) set to true if the audio setting request was successfully sent,
* false otherwise
*/
bool success = 1;
}
/**
* This message requests that the noise reduction audio setting be changed.
* The device should send a device info event after this setting request are
* handled.
*
* EXPECTED RESPONSE
* SetNoiseReductionResponse
*
*/
message SetNoiseReductionRequest
{
/**
* (REQUIRED) The noise reduction setting to be set
*
* If value is 0, the request is to disable. If 1,
* the request is to enable.
*/
int32 noise_reduction = 1;
}
message SetNoiseReductionResponse
{
/**
* (REQUIRED) set to true if the audio setting request was successfully sent,
* false otherwise
*/
bool success = 1;
}
/**
* This message requests that the reverb mode audio setting be changed.
* The device should send a device info event after this setting request are
* handled.
*
* EXPECTED RESPONSE
* SetReverbModeResponse
*
*/
message SetReverbModeRequest
{
/**
* Reverb mode enumeration
*/
enum ReverbMode {
DISABLED = 0;
MILD = 1;
NORMAL = 2;
AGGRESSIVE = 3;
}
/**
* (REQUIRED) The reverb mode setting to be set
*
* see Reverb mode enumeration
*/
ReverbMode reverb_mode = 1;
}
message SetReverbModeResponse
{
/**
* (REQUIRED) set to true if the setting request was successfully sent, false
* otherwise
*/
bool success = 1;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* Request for setting device time
* This is to be included in UsbMsg
*/
message SetDeviceTimeRequest
{
/**
* utc timestamp.
*/
uint64 ts = 1;
/**
* the time zone.
*/
string time_zone = 2;
}
/**
* Send an ack as the response
*/

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* Request to start update
* This is to be included in UsbMsg
* EXPECTED RESPONSE
* UpdateNowResponse
*/
message UpdateNowRequest
{
/**
* Unused. Reserved for future use.
*/
bool reserved = 1;
}
/**
* Update now response
*/
message UpdateNowResponse
{
/**
* bool value to indicate update was started
*/
bool started = 1;
}

View File

@ -0,0 +1,21 @@
gen = generator(protoc, \
output : ['@BASENAME@.pb-c.c', '@BASENAME@.pb-c.h'],
arguments : ['--proto_path=@CURRENT_SOURCE_DIR@', '--c_out=@BUILD_DIR@', '@INPUT@'])
src = [
'antiflicker.proto',
'ble_cfg.proto',
'crash_info.proto',
'device_attestation.proto',
'device_common.proto',
'device_info.proto',
'device_mode.proto',
'device_request.proto',
'device_time.proto',
'firmware_update.proto',
'rightsight.proto',
'ota_manifest.proto',
'usb_msg.proto',
]
generated = gen.process(src)

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
/**
* Request device to create a GetManifestv2 body. See
* https://docs.google.com/document/d/1l31A1TWhtJC0xR8GwuNtiGN4vPLURRsj5ZcC1uEIwVQ/edit#heading=h.ctbthi1iyxw1
*
*
* This is to be included in UsbMsg
*
* EXPECTED RESPONSE
* GetManifestBodyResponse
*/
message GetManifestBodyRequest
{
/**
* The attestation challenge.
* (REQUIRED)
*/
string challenge = 1;
/**
* The manifest version.
* (REQUIRED)
*/
string version = 2;
/**
* The channel. Dont use if empty or null
* (OPTIONAL)
*/
string channel = 3;
/**
* The meta info in json format. This
* field usually comes from PC.
* (OPTIONAL)
*/
string meta_info = 4;
/**
* Time to live
* (REQUIRED)
*/
int32 ttl = 5;
}
/**
* GetManifestv2 body response
*/
message GetManifestBodyResponse
{
/**
* The get manifest body. This is a json string
*/
string body = 1;
/**
* The get manifest body signature.
*/
string signature = 2;
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
import "device_common.proto";
/**
* This message data structure holds information about the
* current RightSight configuration.
*
*/
message RightSightConfiguration
{
/**
* Enumeration of modes that the RightSight service can be in.
*/
enum Mode {
/**
* This does not indicate a default value.
*
*/
DO_NOT_USE = 0;
/**
* The camera will continually pan, tilt, and zoom
* to properly frame everyone during a meeting.
*/
DYNAMIC = 1;
/**
* The camera will pan, tilt, and zoom to properly in
* the meeting only when the call starts.
*/
ON_CALL_START = 2;
}
/**
* (REQUIRED) If true, RightSight is enabled and active.
*/
bool enabled = 1;
/**
* (REQUIRED) The current mode that RightSight is in.
*/
Mode mode = 2;
/**
* (REQUIRED) A timestamp indicating when the RightSight
* settings were last modified. This is the number of
* milliseconds since the epoch.
*/
uint64 last_modified = 3;
}
/**
* RightSight is an auto-framing feature that is available in Kong.
* With RightSight enabled, your device will automatically pan, tilt, and zoom
* the camera lens in order to capture all meeting participants
* within the image frame. This feature can be set to one of two
* modes: dynamic and on call start. When in dynamic mode, the
* device will actively pan, tilt, and zoom the camera lens when
* appropriate in order to keep all participants in frame during
* the entire course of the meeting. When in on call start mode,
* the camera lens will pan, tilt, and zoom to capture everybody
* in frame only when the meeting starts.
*
* When RightSight is enabled, it is set
* to dynamic mode by default.
*
* This message requests that the RightSight configuration
* settings be changed.
*
* EXPECTED RESPONSE
* SetRightSightConfigurationResponse
*
*/
message SetRightSightConfigurationRequest
{
/**
* (REQUIRED) If true, requests that RightSight be
* turned on. If false, indicates that
* RightSight should be turned off.
*/
bool enabled = 1;
/**
* (REQUIRED) The mode for RightSight to be in. A value is
* required, but if none is provided, then this will
* default to DYNAMIC mode.
*
* If enabled is set to false, then this will effectively
* do nothing as RightSight is turned off.
*/
RightSightConfiguration.Mode mode = 2;
}
/**
* Response which contains the RightSight configuration that was
* set as a result of the request.
*/
message SetRightSightConfigurationResponse
{
/**
* (OPTIONAL) If any errors occurred while processing the
* request, then this field should be set accordingly.
*/
repeated Error errors = 1;
/**
* (REQUIRED) The RightSight configuration that was set on
* the product.
*/
RightSightConfiguration right_sight_configuration = 2;
}

View File

@ -0,0 +1,204 @@
/*
* Copyright (c) 1999-2021 Logitech, Inc.
* All Rights Reserved
*
* SPDX-License-Identifier: LGPL-2.1+
*/
syntax = "proto3";
package logi.device.proto;
option java_package = "com.logitech.vc.proto";
import "device_info.proto";
import "firmware_update.proto";
import "crash_info.proto";
import "device_mode.proto";
import "device_attestation.proto";
import "rightsight.proto";
import "ota_manifest.proto";
import "device_time.proto";
import "ble_cfg.proto";
import "antiflicker.proto";
import "device_request.proto";
/**
*
* Header message to be included in UsbMsg. This contains
* message metadata that aids in processing of messages
*/
message Header
{
/**
* A unique id of the message. If responding after receiving
* data, the value stored in this field should be used in the ack message
* msgId field
*/
string id = 1;
/**
* A timestamp indicating when the message was
* sent. This is the number of milliseconds that have
* elapsed since the epoch, in string format
*/
string timestamp = 2;
}
/**
* The Ack message.
* This is to be included in UsbMsg
*/
message Acknowledge
{
/**
* The message Id. This should be the same value
* in UsbMsg.Header.id field
*/
string msgId = 1;
/**
* The message processing result. true indicates message was
* successfully processed, false otherwise.
*/
bool success = 2;
}
/**
* The Kong Event message.
* Anything that is not part of
* Request/Response messaging, but is being sent to mqtt distributor
* should be considered as a KongEvent, and forwarded to device host.
* This is to be included in UsbMsg
*/
message KongEvent
{
/**
* mqtt_event contains actual mqtt message
*/
string mqtt_event = 1;
}
/**
* Sent by Kong sync-agent.
* If Kong sync-agent starts-up and it is in Device mode, then
* it can send this event. When PC sync-agent receives this event,
* it should send a TransitionToDeviceModeRequest.
* This is to be included in UsbMsg
*/
message HandshakeEvent
{
/**
* Unused. Reserved for future use.
*/
bool reserved = 1;
}
/**
* The enclosing message.
* This is the root message of all messagesszx
*/
message UsbMsg
{
/**
* Header for the message containing additional
* message metadata.
*/
Header header = 1;
/**
* The actual message being sent. One of these must be
* included
*/
oneof message
{
/**
* Ack message
*/
Acknowledge ack = 2;
/**
* Request message
*/
Request request = 3;
/**
* Response message
*/
Response response = 4;
/**
* Event
*/
Event event = 5;
}
}
/**
* The Request message.
* This is to be included in UsbMsg
*/
message Request
{
oneof payload
{
GetDeviceInfoRequest get_device_info_request = 2;
UpdateNowRequest update_now_request = 3;
SendCrashDumpRequest crash_dump_request = 4;
TransitionToDeviceModeRequest transition_to_devicemode_request = 5;
GetCertificateChainRequest get_certificate_chain_request = 6;
SetRightSightConfigurationRequest set_right_sight_configuration_request = 7;
GetManifestBodyRequest get_manifest_body_request = 8;
SendCrashDumpRequestv2 crash_dump_request_v2 = 9;
SetDeviceTimeRequest set_device_time_request = 10;
SetAntiFlickerConfigurationRequest set_anti_flicker_configuration_request = 11;
SetBLECfgRequest set_ble_cfg_request = 12;
SetDeprovisionRequest set_deprovision_request = 13;
RebootDeviceRequest reboot_device_request = 14;
SetSpeakerBoostRequest speaker_boost_request = 15;
SetNoiseReductionRequest noise_reduction_request = 16;
SetReverbModeRequest reverb_mode_request = 17;
GenerateCrashDumpRequest generate_bug_report_request = 18;
SendCertificateDataRequest send_certificate_data_request = 19;
}
}
/**
* The Response message.
* This is to be included in UsbMsg
*/
message Response
{
oneof payload
{
GetDeviceInfoResponse get_device_info_response = 2;
UpdateNowResponse update_now_response = 3;
SendCrashDumpResponse crash_dump_response = 4;
TransitionToDeviceModeResponse transition_to_devicemode_response = 5;
GetCertificateChainResponse get_certificate_chain_response = 6;
SetRightSightConfigurationResponse set_right_sight_configuration_response = 7;
GetManifestBodyResponse get_manifest_body_response = 8;
SendCrashDumpResponsev2 crash_dump_response_v2 = 9;
SetAntiFlickerConfigurationResponse set_anti_flicker_configuration_response = 11;
SetBLECfgResponse set_ble_cfg_response = 12;
SetDeprovisionResponse set_deprovision_response = 13;
RebootDeviceResponse reboot_device_response = 14;
SetSpeakerBoostResponse speaker_boost_response = 15;
SetNoiseReductionResponse noise_reduction_response = 16;
SetReverbModeResponse reverb_mode_response = 17;
GenerateCrashDumpResponse generate_bug_report_response = 18;
SendCertificateDataResponse send_certificate_data_response = 19;
}
}
/**
* The Event message.
* This is to be included in UsbMsg
*/
message Event
{
oneof payload
{
KongEvent kong_event = 1;
SendCrashDumpEvent send_crash_dump_event = 2;
CrashDumpAvailableEvent crash_dump_available_event = 3;
HandshakeEvent handshake_event = 4;
}
}

View File

@ -35,6 +35,7 @@ subdir('linux-swap')
subdir('linux-tainted')
subdir('logind')
subdir('logitech-hidpp')
subdir('logitech-bulkcontroller')
subdir('modem-manager')
subdir('msr')
subdir('nitrokey')

View File

@ -211,11 +211,13 @@ parts:
- libpango1.0-dev
- libpci-dev
- libpolkit-gobject-1-dev
- libprotobuf-c-dev
- libsmbios-dev
- libsqlite3-dev
- libsystemd-dev
- locales
- pkg-config
- protobuf-c-compiler
- systemd
- uuid-dev
stage-packages: