From b9198d99e990ffeb3e94563f1d1d2d165eccbcf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 2 Jan 2026 18:25:47 +0100 Subject: [PATCH] [App] Send diagnostics on server connection Took 32 seconds Took 22 seconds --- cockatrice/CMakeLists.txt | 1 + .../src/client/settings/cache_settings.cpp | 7 ++ .../src/client/settings/cache_settings.h | 6 ++ .../dialogs/dlg_prompt_send_diagnostics.cpp | 55 +++++++++++++++ .../dialogs/dlg_prompt_send_diagnostics.h | 34 +++++++++ .../widgets/dialogs/dlg_settings.cpp | 69 ++++++++++++++++--- .../interface/widgets/dialogs/dlg_settings.h | 3 + cockatrice/src/interface/window_main.cpp | 17 +++++ cockatrice/src/interface/window_main.h | 1 + .../interface_network_settings_provider.h | 8 +++ .../network/client/remote/remote_client.cpp | 44 ++++++++++++ .../network/client/remote/remote_client.h | 6 ++ .../protocol/pb/session_commands.proto | 16 +++++ 13 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.cpp create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 8ebd177db..60319ed32 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -42,6 +42,7 @@ set(cockatrice_SOURCES src/interface/widgets/dialogs/dlg_manage_sets.cpp src/interface/widgets/dialogs/dlg_register.cpp src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp + src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.cpp src/interface/widgets/dialogs/dlg_settings.cpp src/interface/widgets/dialogs/dlg_startup_card_check.cpp src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 2ad1f1ece..a890da3cf 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -212,6 +212,7 @@ SettingsCache::SettingsCache() cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt(); lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate(); notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); + sendDiagnostics = settings->value("personal/senddiagnostics", 0).toInt(); notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool(); if (settings->contains("personal/updatereleasechannel")) { @@ -1496,6 +1497,12 @@ void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate) settings->setValue("personal/updatenotification", notifyAboutUpdates); } +void SettingsCache::setSendDiagnostics(int _sendDiagnostics) +{ + sendDiagnostics = _sendDiagnostics; + settings->setValue("personal/senddiagnostics", sendDiagnostics); +} + void SettingsCache::setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion) { notifyAboutNewVersion = static_cast(_notifyaboutnewversion); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index ca9a2a39b..ab364feee 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -218,6 +218,7 @@ private: int cardUpdateCheckInterval; QDate lastCardUpdateCheck; bool notifyAboutUpdates; + int sendDiagnostics; bool notifyAboutNewVersion; bool showTipsOnStartup; QList seenTips; @@ -498,6 +499,10 @@ public: { return notifyAboutUpdates; } + [[nodiscard]] int getSendDiagnostics() const override + { + return sendDiagnostics; + } [[nodiscard]] bool getNotifyAboutNewVersion() const { return notifyAboutNewVersion; @@ -1105,6 +1110,7 @@ public slots: void setCardUpdateCheckInterval(int value); void setLastCardUpdateCheck(QDate value); void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate); + void setSendDiagnostics(int _sendDiagnostics); void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion); void setUpdateReleaseChannelIndex(int value); void setMaxFontSize(int _max); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.cpp new file mode 100644 index 000000000..1914aa317 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.cpp @@ -0,0 +1,55 @@ +#include "dlg_prompt_send_diagnostics.h" + +#include +#include +#include + +DlgPromptSendDiagnostics::DlgPromptSendDiagnostics(QWidget *parent) : QDialog(parent), choice(SendDiagnosticsDisabled) +{ + auto *layout = new QVBoxLayout(this); + + messageLabel = new QLabel(this); + messageLabel->setWordWrap(true); + layout->addWidget(messageLabel); + + auto *buttonLayout = new QHBoxLayout; + + noButton = new QPushButton(this); + minimalButton = new QPushButton(this); + fullButton = new QPushButton(this); + + buttonLayout->addWidget(noButton); + buttonLayout->addWidget(minimalButton); + buttonLayout->addWidget(fullButton); + + layout->addLayout(buttonLayout); + + // Connect buttons + connect(noButton, &QPushButton::clicked, this, [this]() { + choice = SendDiagnosticsDisabled; + accept(); + }); + connect(minimalButton, &QPushButton::clicked, this, [this]() { + choice = SendDiagnosticsBasic; + accept(); + }); + connect(fullButton, &QPushButton::clicked, this, [this]() { + choice = SendDiagnosticsFull; + accept(); + }); + + retranslateUi(); +} + +void DlgPromptSendDiagnostics::retranslateUi() +{ + setWindowTitle(tr("Help us improve Cockatrice")); + + messageLabel->setText(tr("Hi there! Cockatrice is made by a small team, and we want to make it better for you.\n\n" + "We’d love to collect a tiny bit of anonymous technical data about how you use the app — " + "nothing personal, just usage info so we know what works and what needs improvement.")); + + noButton->setText(tr("No, thank you")); + minimalButton->setText(tr("Minimal")); + fullButton->setText(tr("Full")); +} diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.h b/cockatrice/src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.h new file mode 100644 index 000000000..0f62a1463 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_prompt_send_diagnostics.h @@ -0,0 +1,34 @@ +#ifndef COCKATRICE_DLG_PROMPT_SEND_DIAGNOSTICS_H +#define COCKATRICE_DLG_PROMPT_SEND_DIAGNOSTICS_H + +#include "dlg_settings.h" + +#include +#include +#include + +class QPushButton; + +class DlgPromptSendDiagnostics : public QDialog +{ + Q_OBJECT +public: + explicit DlgPromptSendDiagnostics(QWidget *parent = nullptr); + + SendDiagnosticsMode selectedChoice() const + { + return choice; + } + + void retranslateUi(); + +private: + SendDiagnosticsMode choice; + + QLabel *messageLabel; + QPushButton *noButton; + QPushButton *minimalButton; + QPushButton *fullButton; +}; + +#endif // COCKATRICE_DLG_PROMPT_SEND_DIAGNOSTICS_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index fe8840a41..9453756e8 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -13,6 +13,7 @@ #include "../interface/widgets/utility/get_text_with_max.h" #include "../interface/widgets/utility/sequence_edit.h" #include "../main.h" +#include "dlg_prompt_send_diagnostics.h" #include <../../client/settings/card_counter_settings.h> #include @@ -101,6 +102,27 @@ GeneralSettingsPage::GeneralSettingsPage() advertiseTranslationPageLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); advertiseTranslationPageLabel.setOpenExternalLinks(true); + // Add items with the enum as userData + populateSendDiagnosticsCombo(SettingsCache::instance().getSendDiagnostics()); + + connect(&sendDiagnosticsSelector, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + int mode = sendDiagnosticsSelector.itemData(index).toInt(); + + if (mode == SendDiagnosticsUnprompted) { + DlgPromptSendDiagnostics dlg(this); + if (dlg.exec() == QDialog::Accepted) { + mode = dlg.selectedChoice(); + } else { + mode = SendDiagnosticsDisabled; + } + } + + SettingsCache::instance().setSendDiagnostics(mode); + + // Rebuild combo if we transitioned out of Unprompted + populateSendDiagnosticsCombo(mode); + }); + connect(&languageBox, qOverload(&QComboBox::currentIndexChanged), this, &GeneralSettingsPage::languageBoxChanged); connect(&startupUpdateCheckCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, @@ -125,15 +147,17 @@ GeneralSettingsPage::GeneralSettingsPage() personalGrid->addWidget(&advertiseTranslationPageLabel, 1, 1, Qt::AlignRight); personalGrid->addWidget(&updateReleaseChannelLabel, 2, 0); personalGrid->addWidget(&updateReleaseChannelBox, 2, 1); - personalGrid->addWidget(&startupUpdateCheckCheckBox, 4, 0, 1, 2); - personalGrid->addWidget(&startupCardUpdateCheckBehaviorLabel, 5, 0); - personalGrid->addWidget(&startupCardUpdateCheckBehaviorSelector, 5, 1); - personalGrid->addWidget(&cardUpdateCheckIntervalLabel, 6, 0); - personalGrid->addWidget(&cardUpdateCheckIntervalSpinBox, 6, 1); - personalGrid->addWidget(&lastCardUpdateCheckDateLabel, 7, 1); - personalGrid->addWidget(&updateNotificationCheckBox, 8, 0, 1, 2); - personalGrid->addWidget(&newVersionOracleCheckBox, 9, 0, 1, 2); - personalGrid->addWidget(&showTipsOnStartup, 10, 0, 1, 2); + personalGrid->addWidget(&sendDiagnosticsLabel, 3, 0); + personalGrid->addWidget(&sendDiagnosticsSelector, 3, 1); + personalGrid->addWidget(&startupUpdateCheckCheckBox, 5, 0, 1, 2); + personalGrid->addWidget(&startupCardUpdateCheckBehaviorLabel, 6, 0); + personalGrid->addWidget(&startupCardUpdateCheckBehaviorSelector, 6, 1); + personalGrid->addWidget(&cardUpdateCheckIntervalLabel, 7, 0); + personalGrid->addWidget(&cardUpdateCheckIntervalSpinBox, 7, 1); + personalGrid->addWidget(&lastCardUpdateCheckDateLabel, 8, 1); + personalGrid->addWidget(&updateNotificationCheckBox, 9, 0, 1, 2); + personalGrid->addWidget(&newVersionOracleCheckBox, 10, 0, 1, 2); + personalGrid->addWidget(&showTipsOnStartup, 11, 0, 1, 2); personalGroupBox = new QGroupBox; personalGroupBox->setLayout(personalGrid); @@ -268,6 +292,32 @@ QString GeneralSettingsPage::languageName(const QString &lang) return qTranslator.translate("i18n", DEFAULT_LANG_NAME); } +void GeneralSettingsPage::populateSendDiagnosticsCombo(int mode) +{ + QSignalBlocker blocker(sendDiagnosticsSelector); + + sendDiagnosticsSelector.clear(); + + if (mode == SendDiagnosticsUnprompted) { + sendDiagnosticsSelector.addItem(QString(), SendDiagnosticsUnprompted); + } + + sendDiagnosticsSelector.addItem(tr("Disabled"), SendDiagnosticsDisabled); + sendDiagnosticsSelector.addItem(tr("Minimal"), SendDiagnosticsBasic); + sendDiagnosticsSelector.addItem(tr("Full"), SendDiagnosticsFull); + + // Restore selection + for (int i = 0; i < sendDiagnosticsSelector.count(); ++i) { + if (sendDiagnosticsSelector.itemData(i).toInt() == mode) { + sendDiagnosticsSelector.setCurrentIndex(i); + return; + } + } + + // Fallback: if unprompted was removed, default to Disabled + sendDiagnosticsSelector.setCurrentIndex(0); +} + void GeneralSettingsPage::deckPathButtonClicked() { QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), deckPathEdit->text()); @@ -376,6 +426,7 @@ void GeneralSettingsPage::retranslateUi() customCardDatabasePathLabel.setText(tr("Custom database directory:")); tokenDatabasePathLabel.setText(tr("Token database:")); updateReleaseChannelLabel.setText(tr("Update channel")); + sendDiagnosticsLabel.setText(tr("Send usage statistics:")); startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup")); startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup")); startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index 06ad3601c..5a6447e14 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -63,6 +63,7 @@ private slots: private: QStringList findQmFiles(); QString languageName(const QString &lang); + void populateSendDiagnosticsCombo(int mode); QLineEdit *deckPathEdit; QLineEdit *filtersPathEdit; QLineEdit *replaysPathEdit; @@ -84,6 +85,7 @@ private: QCheckBox updateNotificationCheckBox; QCheckBox newVersionOracleCheckBox; QComboBox updateReleaseChannelBox; + QComboBox sendDiagnosticsSelector; QLabel languageLabel; QLabel deckPathLabel; QLabel filtersPathLabel; @@ -93,6 +95,7 @@ private: QLabel customCardDatabasePathLabel; QLabel tokenDatabasePathLabel; QLabel updateReleaseChannelLabel; + QLabel sendDiagnosticsLabel; QLabel advertiseTranslationPageLabel; QCheckBox showTipsOnStartup; }; diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 2e135d170..c7f2eafe1 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -28,6 +28,7 @@ #include "../interface/widgets/dialogs/dlg_forgot_password_request.h" #include "../interface/widgets/dialogs/dlg_forgot_password_reset.h" #include "../interface/widgets/dialogs/dlg_manage_sets.h" +#include "../interface/widgets/dialogs/dlg_prompt_send_diagnostics.h" #include "../interface/widgets/dialogs/dlg_register.h" #include "../interface/widgets/dialogs/dlg_settings.h" #include "../interface/widgets/dialogs/dlg_startup_card_check.h" @@ -851,6 +852,7 @@ MainWindow::MainWindow(QWidget *parent) connect(client, &RemoteClient::protocolVersionMismatch, this, &MainWindow::protocolVersionMismatch); connect(client, &RemoteClient::userInfoChanged, this, &MainWindow::userInfoReceived, Qt::BlockingQueuedConnection); connect(client, &RemoteClient::notifyUserAboutUpdate, this, &MainWindow::notifyUserAboutUpdate); + connect(client, &RemoteClient::notifyUserAboutDiagnostics, this, &MainWindow::notifyUserAboutDiagnostics); connect(client, &RemoteClient::registerAccepted, this, &MainWindow::registerAccepted); connect(client, &RemoteClient::registerAcceptedNeedsActivate, this, &MainWindow::registerAcceptedNeedsActivate); connect(client, &RemoteClient::registerError, this, &MainWindow::registerError); @@ -1385,6 +1387,21 @@ void MainWindow::notifyUserAboutUpdate() "running a custom or pre-release version.\n\nTo update your client, go to Help -> Check for Updates.")); } +void MainWindow::notifyUserAboutDiagnostics() +{ + int mode = SendDiagnosticsDisabled; + DlgPromptSendDiagnostics dlg(this); + if (dlg.exec() == QDialog::Accepted) { + mode = dlg.selectedChoice(); + } + + SettingsCache::instance().setSendDiagnostics(mode); + + if (mode == SendDiagnosticsBasic || mode == SendDiagnosticsFull) { + client->sendDiagnostics(); + } +} + void MainWindow::actOpenCustomFolder() { QString dir = SettingsCache::instance().getCustomPicsPath(); diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 75b0f5062..7a82c2568 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -81,6 +81,7 @@ private slots: void localGameEnded(); void pixmapCacheSizeChanged(int newSizeInMBs); void notifyUserAboutUpdate(); + void notifyUserAboutDiagnostics(); void actDisconnect(); void actSinglePlayer(); void actWatchReplay(); diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_network_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_network_settings_provider.h index bd136513d..1378c7242 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_network_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_network_settings_provider.h @@ -2,6 +2,13 @@ #define COCKATRICE_INETWORKSETTINGSPROVIDER_H #include +enum SendDiagnosticsMode { + SendDiagnosticsUnprompted = 0, // never asked + SendDiagnosticsDisabled = 1, + SendDiagnosticsBasic = 2, + SendDiagnosticsFull = 3, +}; + class INetworkSettingsProvider { public: @@ -12,6 +19,7 @@ public: [[nodiscard]] virtual int getTimeOut() const = 0; [[nodiscard]] virtual int getKeepAlive() const = 0; [[nodiscard]] virtual bool getNotifyAboutUpdates() const = 0; + [[nodiscard]] virtual int getSendDiagnostics() const = 0; virtual void setKnownMissingFeatures(const QString &_knownMissingFeatures) = 0; virtual QString getKnownMissingFeatures() = 0; diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index 4b5d3f1b8..083edcb0d 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -278,6 +278,34 @@ void RemoteClient::passwordSaltResponse(const Response &response) } } +Command_ClientDiagnostics RemoteClient::generateClientDiagnostics() +{ + Command_ClientDiagnostics diag; + + diag.set_clientver(VERSION_STRING); + +#if defined(Q_OS_WIN) + diag.set_os("windows"); +#elif defined(Q_OS_MAC) + diag.set_os("macos"); +#elif defined(Q_OS_LINUX) + diag.set_os("linux"); +#else + diag.set_os("unknown"); +#endif + + diag.set_arch(QSysInfo::currentCpuArchitecture().toStdString()); + + return diag; +} + +void RemoteClient::addDiagnosticsFeature(Command_ClientDiagnostics *diag, const QString &name, const QString &value) +{ + FeatureFlag *flag = diag->add_feature_flags(); + flag->set_name(name.toStdString()); + flag->set_value(value.toStdString()); +} + void RemoteClient::loginResponse(const Response &response) { const Response_Login &resp = response.GetExtension(Response_Login::ext); @@ -302,6 +330,15 @@ void RemoteClient::loginResponse(const Response &response) ignoreList.append(resp.ignore_list(i)); emit ignoreListReceived(ignoreList); + // send diagnostics if enabled + SendDiagnosticsMode mode = SendDiagnosticsMode(networkSettingsProvider->getSendDiagnostics()); + + if (mode == SendDiagnosticsUnprompted) { + emit notifyUserAboutDiagnostics(); + } else if (mode == SendDiagnosticsBasic || mode == SendDiagnosticsFull) { + sendDiagnostics(); + } + if (newMissingFeatureFound(possibleMissingFeatures) && resp.missing_features_size() > 0 && networkSettingsProvider->getNotifyAboutUpdates()) { networkSettingsProvider->setKnownMissingFeatures(possibleMissingFeatures); @@ -320,6 +357,13 @@ void RemoteClient::loginResponse(const Response &response) } } +void RemoteClient::sendDiagnostics() +{ + Command_ClientDiagnostics diag = generateClientDiagnostics(); + PendingCommand *pend = prepareSessionCommand(diag); + sendCommand(pend); +} + void RemoteClient::registerResponse(const Response &response) { const Response_Register &resp = response.GetExtension(Response_Register::ext); diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h index 15e3e8ef5..0dd642bdc 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h @@ -39,6 +39,7 @@ signals: const QString &_realname); void sigActivateToServer(const QString &_token); void sigDisconnectFromServer(); + void notifyUserAboutDiagnostics(); void notifyUserAboutUpdate(); void sigRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName); void sigForgotPasswordSuccess(); @@ -80,6 +81,8 @@ private slots: void doLogin(); void doHashedLogin(); Command_Login generateCommandLogin(); + Command_ClientDiagnostics generateClientDiagnostics(); + void addDiagnosticsFeature(Command_ClientDiagnostics *diag, const QString &name, const QString &value); void doDisconnectFromServer(); void doActivateToServer(const QString &_token); void doRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName); @@ -120,6 +123,9 @@ private: protected slots: void sendCommandContainer(const CommandContainer &cont) override; +public slots: + void sendDiagnostics(); + public: explicit RemoteClient(QObject *parent = nullptr, INetworkSettingsProvider *networkSettingsProvider = nullptr); ~RemoteClient() override; diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto index cecf87370..be423bed8 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto @@ -55,6 +55,22 @@ message Command_Login { optional string hashed_password = 6; } +message FeatureFlag { + optional string name = 1; + optional string value = 2; +} + +message Command_ClientDiagnostics { + extend SessionCommand { + optional Command_ClientDiagnostics ext = 1051; + } + + optional string clientver = 1; + repeated FeatureFlag feature_flags = 2; + optional string os = 3; + optional string arch = 4; +} + message Command_Message { extend SessionCommand { optional Command_Message ext = 1002;