mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-06-07 09:38:25 +02:00
Simplified usb connection, better UI
This commit is contained in:
+2
-2
@@ -8,7 +8,7 @@
|
||||
#product-id = 5408 # 0x1520
|
||||
|
||||
# Requested image resolution
|
||||
#width = 720
|
||||
#width = 1024
|
||||
#height = 576
|
||||
|
||||
# Requested image refresh rate
|
||||
@@ -98,7 +98,7 @@
|
||||
##############################################################################
|
||||
|
||||
# Font size for messgaes on screen. Set to 0 is you do not want any
|
||||
#font-size = 30
|
||||
#font-size = 40
|
||||
|
||||
# Enable vsync. This reduce tearing but can dramatically affect performance on low end systems
|
||||
#vsync = false
|
||||
|
||||
+23
-14
@@ -288,7 +288,7 @@ void Application::loop()
|
||||
{
|
||||
// Prepare home screen
|
||||
Interface interface(_renderer);
|
||||
interface.drawHome(true, PROTOCOL_STATUS_UNKNOWN);
|
||||
interface.drawHome(true, PROTOCOL_STATUS_UNKNOWN, "");
|
||||
|
||||
// Process full screen, do not do this in headless to avoid blinking
|
||||
if (Settings::isFullscreen())
|
||||
@@ -301,7 +301,7 @@ void Application::loop()
|
||||
// Show window, do not do this in headless to avoid blinking
|
||||
if (!Settings::isHeadless())
|
||||
SDL_ShowWindow(_window);
|
||||
interface.drawHome(true, PROTOCOL_STATUS_UNKNOWN);
|
||||
interface.drawHome(true, PROTOCOL_STATUS_UNKNOWN, "");
|
||||
|
||||
Connection protocol;
|
||||
Decoder decoder;
|
||||
@@ -310,7 +310,7 @@ void Application::loop()
|
||||
decoder.start(&protocol.videoStream, AV_CODEC_ID_H264);
|
||||
audioMain.start(&protocol.audioStreamMain);
|
||||
audioAux.start(&protocol.audioStreamAux, &audioMain);
|
||||
protocol.start(&_state.deviceStatus);
|
||||
protocol.start();
|
||||
|
||||
log_v("Loop");
|
||||
Uint32 frameStart = SDL_GetTicks();
|
||||
@@ -322,29 +322,32 @@ void Application::loop()
|
||||
uint32_t dropframes = 0;
|
||||
int skipEvents = 0;
|
||||
int frameTime = 0;
|
||||
Uint32 debugLast = SDL_GetTicks();
|
||||
int debugSpeed = 0;
|
||||
int debugLastCount = 0;
|
||||
while (_active)
|
||||
{
|
||||
bool late = false;
|
||||
|
||||
if (_state.deviceStatus != _state.previousdeviceStatus)
|
||||
if (protocol.state() != _state.latestState)
|
||||
{
|
||||
// On connect/disconnect
|
||||
if (_state.previousdeviceStatus == PROTOCOL_STATUS_CONNECTED || _state.deviceStatus == PROTOCOL_STATUS_CONNECTED)
|
||||
if (protocol.state() == PROTOCOL_STATUS_CONNECTED || _state.latestState == PROTOCOL_STATUS_CONNECTED)
|
||||
{
|
||||
_state.frameRendered = false;
|
||||
_state.dirty = true;
|
||||
_state.requestFrame = 0;
|
||||
}
|
||||
// On connect
|
||||
if (_state.deviceStatus == PROTOCOL_STATUS_CONNECTED)
|
||||
if (protocol.state() == PROTOCOL_STATUS_CONNECTED)
|
||||
{
|
||||
decoder.flush();
|
||||
decoder.buffer.reset();
|
||||
}
|
||||
_state.previousdeviceStatus = _state.deviceStatus;
|
||||
_state.latestState = protocol.state();
|
||||
}
|
||||
|
||||
if (_state.deviceStatus == PROTOCOL_STATUS_CONNECTED && _state.showVideo)
|
||||
if (_state.latestState == PROTOCOL_STATUS_CONNECTED && _state.showVideo)
|
||||
{
|
||||
delay = 0;
|
||||
while (!_state.dirty && decoder.buffer.latestId() == latestFrameid && ++delay < frameTargetTime)
|
||||
@@ -382,7 +385,7 @@ void Application::loop()
|
||||
|
||||
if (!_state.frameRendered || !_state.showVideo)
|
||||
{
|
||||
interface.drawHome(_state.dirty, _state.deviceStatus);
|
||||
interface.drawHome(_state.dirty, _state.latestState, protocol.phoneName());
|
||||
_state.dirty = false;
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e))
|
||||
@@ -403,14 +406,18 @@ void Application::loop()
|
||||
|
||||
if (_debug)
|
||||
{
|
||||
char debugBuffer[512];
|
||||
if (SDL_GetTicks() - debugLast >= 1000)
|
||||
{
|
||||
debugSpeed = (protocol.transfered() - debugLastCount) / (SDL_GetTicks() - debugLast);
|
||||
debugLastCount = protocol.transfered();
|
||||
debugLast = SDL_GetTicks();
|
||||
}
|
||||
char debugBuffer[2048];
|
||||
std::snprintf(debugBuffer, sizeof(debugBuffer),
|
||||
"%s\n"
|
||||
"FRAME: %u / %u [%d] dropped: %d render: %dms / %dms\n"
|
||||
"VIDEO: %u\n"
|
||||
"AUDIO-MAIN: %u\n"
|
||||
"AUDIO-AUX: %u\n"
|
||||
"OUT: %u",
|
||||
"USB: %s ~%dKB/s\n"
|
||||
"BUFF: video [%u] audio[main %u aux %u] out [%u]",
|
||||
status().c_str(),
|
||||
latestFrameid,
|
||||
decoder.buffer.latestId(),
|
||||
@@ -418,6 +425,8 @@ void Application::loop()
|
||||
dropframes,
|
||||
frameTime,
|
||||
delay,
|
||||
protocol.status().c_str(),
|
||||
debugSpeed,
|
||||
protocol.videoStream.count(),
|
||||
protocol.audioStreamMain.count(),
|
||||
protocol.audioStreamAux.count(),
|
||||
|
||||
+1
-2
@@ -27,8 +27,7 @@ private:
|
||||
bool showVideo = true;
|
||||
bool fullscreen = false;
|
||||
bool mouseDown = false;
|
||||
int8_t previousdeviceStatus = PROTOCOL_STATUS_INITIALISING;
|
||||
atomic<int8_t> deviceStatus = PROTOCOL_STATUS_INITIALISING;
|
||||
int8_t latestState = PROTOCOL_STATUS_UNKNOWN;
|
||||
};
|
||||
|
||||
bool setAudioDriver();
|
||||
|
||||
+75
-5
@@ -51,17 +51,87 @@ inline void pushEvent(Uint32 evt, int code)
|
||||
|
||||
inline std::string bytes(uint8_t *data, uint32_t length, uint16_t max)
|
||||
{
|
||||
std::ostringstream out;
|
||||
std::ostringstream oss;
|
||||
|
||||
if (data && length >= 4)
|
||||
if (data && length > 0)
|
||||
{
|
||||
for (size_t i = 0; (i < length) && (i < max); ++i)
|
||||
for (uint32_t i = 0; (i < length) && (i < max); ++i)
|
||||
{
|
||||
out << std::setw(4) << static_cast<uint32_t>(data[i]);
|
||||
oss << std::setw(4) << static_cast<uint32_t>(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
inline std::string ascii(uint8_t *data, uint32_t length)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
if (data && length > 0)
|
||||
{
|
||||
for (uint32_t i = 0; i < length; ++i)
|
||||
{
|
||||
char ch = static_cast<char>(data[i]);
|
||||
if (ch == '\n' || ch == '\r')
|
||||
oss << '.';
|
||||
else
|
||||
oss << (std::isprint(static_cast<unsigned char>(ch)) ? ch : '.');
|
||||
}
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
inline bool jsonFindString(const uint8_t *data, int size, const char *key, char *result, int len)
|
||||
{
|
||||
const char *p = reinterpret_cast<const char *>(data);
|
||||
const char *end = p + size;
|
||||
|
||||
int keyLen = strlen(key);
|
||||
|
||||
while (p < end)
|
||||
{
|
||||
while (p < end && *p != '"')
|
||||
++p;
|
||||
if (p >= end)
|
||||
break;
|
||||
++p;
|
||||
|
||||
if ((end - p) > keyLen &&
|
||||
strncmp(p, key, keyLen) == 0 &&
|
||||
p[keyLen] == '"')
|
||||
{
|
||||
p += keyLen + 1;
|
||||
while (p < end && isspace((unsigned char)*p))
|
||||
++p;
|
||||
if (p >= end || *p != ':')
|
||||
continue;
|
||||
++p;
|
||||
while (p < end && isspace((unsigned char)*p))
|
||||
++p;
|
||||
if (p >= end || *p != '"')
|
||||
continue;
|
||||
++p;
|
||||
|
||||
int i = 0;
|
||||
while (p < end && *p != '"' && i + 1 < len)
|
||||
result[i++] = *p++;
|
||||
result[i] = '\0';
|
||||
return p < end && *p == '"';
|
||||
}
|
||||
|
||||
while (p < end && *p != '"')
|
||||
{
|
||||
if (*p == '\\')
|
||||
++p;
|
||||
++p;
|
||||
}
|
||||
if (p < end)
|
||||
++p;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif /* SRC_COMMON_FUNCTIONS */
|
||||
|
||||
@@ -57,8 +57,10 @@ void Logger::vlog(Level level, const char *context, const char *fmt, std::va_lis
|
||||
char message[LOGGER_MESSAGE_SIZE];
|
||||
std::vsnprintf(message, LOGGER_MESSAGE_SIZE, fmt, args);
|
||||
|
||||
FILE *stream = (level <= Level::Error) ? stderr : stdout;
|
||||
|
||||
std::fprintf(
|
||||
(level <= Level::Warning) ? stderr : stdout,
|
||||
stream,
|
||||
"%s%s %s[%s] %s%s\n",
|
||||
LOGGER_COLOR_GRAY,
|
||||
timebuf,
|
||||
@@ -66,6 +68,8 @@ void Logger::vlog(Level level, const char *context, const char *fmt, std::va_lis
|
||||
className(context).c_str(),
|
||||
message,
|
||||
LOGGER_COLOR_RESET);
|
||||
|
||||
std::fflush(stream);
|
||||
}
|
||||
|
||||
const char *Logger::levelColor(Level level)
|
||||
@@ -95,7 +99,8 @@ std::string Logger::className(const char *context)
|
||||
return "Global";
|
||||
|
||||
std::string signature(context);
|
||||
const std::size_t scopePos = signature.rfind("::");
|
||||
const std::size_t argsPos = signature.find('(');
|
||||
const std::size_t scopePos = signature.rfind("::", argsPos);
|
||||
if (scopePos == std::string::npos)
|
||||
return "Global";
|
||||
|
||||
|
||||
+29
-20
@@ -9,10 +9,7 @@ Interface::Interface(SDL_Renderer *renderer)
|
||||
: Renderer(renderer),
|
||||
_state(0),
|
||||
_debug(false),
|
||||
_textDongle(font, font_len, Settings::fontSize),
|
||||
_textInit(font, font_len, Settings::fontSize),
|
||||
_textConnect(font, font_len, Settings::fontSize),
|
||||
_textLaunch(font, font_len, Settings::fontSize),
|
||||
_textStatus(font, font_len, Settings::fontSize),
|
||||
_textDebug(font, font_len, 15),
|
||||
_mainImage(background, background_len)
|
||||
{
|
||||
@@ -47,7 +44,7 @@ bool Interface::render(AVFrame *frame)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Interface::drawHome(bool force, int state)
|
||||
bool Interface::drawHome(bool force, int state, std::string name)
|
||||
{
|
||||
if (state == _state && !force)
|
||||
return false;
|
||||
@@ -57,22 +54,34 @@ bool Interface::drawHome(bool force, int state)
|
||||
SDL_GetRendererOutputSize(_renderer, &width, &height);
|
||||
|
||||
_mainImage.draw(_renderer, width, height);
|
||||
bool drawText = false;
|
||||
|
||||
if (state == PROTOCOL_STATUS_ERROR)
|
||||
{
|
||||
if (_textDongle.prepare(_renderer, "Connection error", colorError))
|
||||
_textDongle.draw(_renderer, 0.05 * width, 0.2 * height - _textDongle.height / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_textDongle.prepare(_renderer, "Insert dongle", state == PROTOCOL_STATUS_NO_DEVICE ? color1 : color1_inactive))
|
||||
_textDongle.draw(_renderer, 0.05 * width, 0.2 * height - _textDongle.height / 2);
|
||||
}
|
||||
if (_textInit.prepare(_renderer, "Initialising", state == PROTOCOL_STATUS_LINKING ? color2 : color2_inactive))
|
||||
_textInit.draw(_renderer, 0.05 * width, 0.4 * height - _textInit.height / 2);
|
||||
if (_textConnect.prepare(_renderer, "Connect phone", state == PROTOCOL_STATUS_ONLINE ? color3 : color3_inactive))
|
||||
_textConnect.draw(_renderer, 0.05 * width, 0.6 * height - _textConnect.height / 2);
|
||||
if (_textLaunch.prepare(_renderer, "Launching", state == PROTOCOL_STATUS_CONNECTED ? color4 : color4_inactive))
|
||||
_textLaunch.draw(_renderer, 0.05 * width, 0.8 * height - _textLaunch.height / 2);
|
||||
if (_textStatus.prepare(_renderer, "Dongle error", colorError))
|
||||
drawText = true;
|
||||
|
||||
if (state == PROTOCOL_STATUS_NO_DEVICE)
|
||||
if (_textStatus.prepare(_renderer, "Insert dongle", colorError))
|
||||
drawText = true;
|
||||
|
||||
if (state == PROTOCOL_STATUS_INITIALISING)
|
||||
if (_textStatus.prepare(_renderer, "Initialising", color2))
|
||||
drawText = true;
|
||||
|
||||
if (state == PROTOCOL_STATUS_LINKING)
|
||||
if (_textStatus.prepare(_renderer, "Initialising", color2))
|
||||
drawText = true;
|
||||
|
||||
if (state == PROTOCOL_STATUS_ONLINE)
|
||||
if (_textStatus.prepare(_renderer, "Connect phone", color4))
|
||||
drawText = true;
|
||||
|
||||
if (state == PROTOCOL_STATUS_CONNECTED)
|
||||
if (_textStatus.prepare(_renderer, "Connecting to "+name, color3))
|
||||
drawText = true;
|
||||
|
||||
if (drawText)
|
||||
_textStatus.draw(_renderer, (width - _textStatus.width) / 2, height * 0.85 - _textStatus.height);
|
||||
|
||||
SDL_RenderPresent(_renderer);
|
||||
return true;
|
||||
|
||||
+2
-5
@@ -10,7 +10,7 @@ public:
|
||||
Interface(SDL_Renderer *renderer);
|
||||
~Interface();
|
||||
bool render(AVFrame *frame);
|
||||
bool drawHome(bool force, int state);
|
||||
bool drawHome(bool force, int state, std::string name);
|
||||
void debug(const char *text);
|
||||
|
||||
private:
|
||||
@@ -18,10 +18,7 @@ private:
|
||||
|
||||
int _state;
|
||||
bool _debug;
|
||||
RendererText _textDongle;
|
||||
RendererText _textInit;
|
||||
RendererText _textConnect;
|
||||
RendererText _textLaunch;
|
||||
RendererText _textStatus;
|
||||
RendererText _textDebug;
|
||||
RendererImage _mainImage;
|
||||
std::string _debugText;
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
#include "pipe_listener.h"
|
||||
#include "settings.h"
|
||||
|
||||
static const char *title = "Fast Car Play v0.8";
|
||||
static const char *title = "Fast Car Play v0.9";
|
||||
|
||||
void start()
|
||||
{
|
||||
|
||||
@@ -263,7 +263,6 @@ void PcmAudio::loop()
|
||||
|
||||
if (device != 0)
|
||||
{
|
||||
SDL_PauseAudioDevice(device, 1);
|
||||
SDL_ClearQueuedAudio(device);
|
||||
SDL_CloseAudioDevice(device);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
#ifndef SRC_PCM_AUDIO
|
||||
#define SRC_PCM_AUDIO
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
+14
-14
@@ -31,52 +31,52 @@ AESCipher::AESCipher(const std::string &baseKey)
|
||||
_initVec[12] = static_cast<uint8_t>(_seed >> 24);
|
||||
}
|
||||
|
||||
Status AESCipher::Encrypt(uint8_t *data, uint32_t length) const
|
||||
bool AESCipher::encrypt(uint8_t *data, uint32_t length, char *err) const
|
||||
{
|
||||
if (!data || length == 0)
|
||||
return Status::Error("Empty data");
|
||||
return error(err, "Empty data");
|
||||
|
||||
auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
||||
if (!ctx)
|
||||
return Status::Error("Failed to create cipher context");
|
||||
return error(err, "Failed to create cipher context");
|
||||
|
||||
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cfb(), nullptr, _encKey.data(), _initVec.data()) != 1)
|
||||
return Status::Error("Encryption initialization failed");
|
||||
return error(err, "Encryption initialization failed");
|
||||
|
||||
std::unique_ptr<uint8_t[]> temp(new uint8_t[length + AES_BLOCK_SIZE]);
|
||||
int out_len = 0;
|
||||
if (EVP_EncryptUpdate(ctx.get(), temp.get(), &out_len, data, length) != 1)
|
||||
return Status::Error("Encryption failed during update");
|
||||
return error(err, "Encryption failed during update");
|
||||
|
||||
int final_len = 0;
|
||||
if (EVP_EncryptFinal_ex(ctx.get(), temp.get() + out_len, &final_len) != 1)
|
||||
return Status::Error("Encryption failed during final");
|
||||
return error(err, "Encryption failed during final");
|
||||
|
||||
std::copy_n(temp.get(), length, data);
|
||||
return Status::Success();
|
||||
return true;
|
||||
}
|
||||
|
||||
Status AESCipher::Decrypt(uint8_t *data, uint32_t length) const
|
||||
bool AESCipher::decrypt(uint8_t *data, uint32_t length, char *err) const
|
||||
{
|
||||
if (!data || length == 0)
|
||||
return Status::Error("Empty data");
|
||||
return error(err, "Empty data");
|
||||
|
||||
auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
||||
if (!ctx)
|
||||
return Status::Error("Failed to create cipher context");
|
||||
return error(err, "Failed to create cipher context");
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_cfb(), nullptr, _encKey.data(), _initVec.data()) != 1)
|
||||
return Status::Error(" Decryption initialization failed");
|
||||
return error(err, "Decryption initialization failed");
|
||||
|
||||
std::unique_ptr<uint8_t[]> temp(new uint8_t[length + AES_BLOCK_SIZE]);
|
||||
int out_len = 0;
|
||||
if (EVP_DecryptUpdate(ctx.get(), temp.get(), &out_len, data, length) != 1)
|
||||
return Status::Error("Decryption failed during update");
|
||||
return error(err, "Decryption failed during update");
|
||||
|
||||
int final_len = 0;
|
||||
if (EVP_DecryptFinal_ex(ctx.get(), temp.get() + out_len, &final_len) != 1)
|
||||
return Status::Error("Decryption failed during final");
|
||||
return error(err, "Decryption failed during final");
|
||||
|
||||
std::copy_n(temp.get(), length, data);
|
||||
return Status::Success();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "common/status.h"
|
||||
|
||||
class AESCipher
|
||||
{
|
||||
public:
|
||||
@@ -16,11 +15,18 @@ public:
|
||||
AESCipher(const std::string &base_key);
|
||||
~AESCipher() = default;
|
||||
|
||||
Status Encrypt(uint8_t *data, uint32_t length) const;
|
||||
Status Decrypt(uint8_t *data, uint32_t length) const;
|
||||
bool encrypt(uint8_t *data, uint32_t length, char *err) const;
|
||||
bool decrypt(uint8_t *data, uint32_t length, char *err) const;
|
||||
|
||||
uint32_t Seed() const { return _seed; }
|
||||
const std::string& Key() const { return _baseKey; }
|
||||
uint32_t seed() const { return _seed; }
|
||||
const std::string &key() const { return _baseKey; }
|
||||
|
||||
static bool error(char *error, const char *message)
|
||||
{
|
||||
if (error)
|
||||
strcpy(error, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _baseKey;
|
||||
|
||||
+375
-146
@@ -1,8 +1,11 @@
|
||||
#include "connection.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "libavcodec/defs.h"
|
||||
|
||||
#include "protocol/message.h"
|
||||
#include "common/logger.h"
|
||||
@@ -12,19 +15,22 @@
|
||||
|
||||
Connection::Connection()
|
||||
: writeQueue(WRITE_QUEUE_SIZE),
|
||||
videoStream(Settings::videoQueue),
|
||||
audioStreamMain(Settings::audioQueue),
|
||||
audioStreamAux(Settings::audioQueue),
|
||||
_recorder(Settings::audioQueue),
|
||||
videoStream(VIDEO_QUEUE_SIZE),
|
||||
audioStreamMain(AUDIO_QUEUE_SIZE),
|
||||
audioStreamAux(AUDIO_QUEUE_SIZE),
|
||||
_processQueue(Settings::usbBuffer, Settings::usbTransferSize),
|
||||
_transfers(Settings::usbQueue),
|
||||
_statusHandler(nullptr),
|
||||
_cipher(nullptr),
|
||||
_context(nullptr),
|
||||
_active(false),
|
||||
_connected(false),
|
||||
_phoneConnected(false),
|
||||
_ecnrypt(false),
|
||||
_state(PROTOCOL_STATUS_INITIALISING),
|
||||
_failCount(0),
|
||||
_nodeviceCount(0),
|
||||
_statusHandler(nullptr)
|
||||
_method("unknown"),
|
||||
_phoneName("phone"),
|
||||
_transfered(0)
|
||||
{
|
||||
int result = libusb_init(&_context);
|
||||
if (result < 0)
|
||||
@@ -44,6 +50,14 @@ Connection::Connection()
|
||||
_cipher = nullptr;
|
||||
log_w("Can't initialise cypher for encryption > Unknown error");
|
||||
}
|
||||
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
context.owner = this;
|
||||
context.transfer = nullptr;
|
||||
context.slot = nullptr;
|
||||
}
|
||||
|
||||
log_v("Created");
|
||||
}
|
||||
|
||||
@@ -58,6 +72,15 @@ Connection::~Connection()
|
||||
_cipher = nullptr;
|
||||
}
|
||||
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
if (context.transfer)
|
||||
{
|
||||
libusb_free_transfer(context.transfer);
|
||||
context.transfer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (_context)
|
||||
{
|
||||
libusb_exit(_context);
|
||||
@@ -66,17 +89,31 @@ Connection::~Connection()
|
||||
log_v("Destroyed");
|
||||
}
|
||||
|
||||
void Connection::start(atomic<int8_t> *statusHandler)
|
||||
void Connection::start()
|
||||
{
|
||||
_statusHandler = statusHandler;
|
||||
|
||||
if (_active)
|
||||
return;
|
||||
|
||||
_state = PROTOCOL_STATUS_INITIALISING;
|
||||
|
||||
log_v("Starting");
|
||||
|
||||
// Prepare usb transfers
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
if (!context.transfer)
|
||||
{
|
||||
context.transfer = libusb_alloc_transfer(0);
|
||||
if (context.transfer == nullptr)
|
||||
{
|
||||
log_e("Can't allocate usb transfer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_active = true;
|
||||
_thread = std::thread(&Connection::mainLoop, this);
|
||||
_writeThread = std::thread(&Connection::mainLoop, this);
|
||||
}
|
||||
|
||||
void Connection::stop()
|
||||
@@ -87,72 +124,285 @@ void Connection::stop()
|
||||
log_v("Stopping");
|
||||
|
||||
_active = false;
|
||||
_connected = false;
|
||||
_state = PROTOCOL_STATUS_UNKNOWN;
|
||||
|
||||
_processQueue.notify();
|
||||
writeQueue.notify();
|
||||
state(PROTOCOL_STATUS_INITIALISING);
|
||||
|
||||
if (_thread.joinable())
|
||||
_thread.join();
|
||||
if (_writeThread.joinable())
|
||||
_writeThread.join();
|
||||
|
||||
log_v("Stopped");
|
||||
_statusHandler = nullptr;
|
||||
}
|
||||
|
||||
void Connection::onTransfer(libusb_transfer *transfer)
|
||||
{
|
||||
if (!transfer || !transfer->user_data)
|
||||
return;
|
||||
|
||||
Context *c = static_cast<Context *>(transfer->user_data);
|
||||
if (!c->owner->_connected)
|
||||
return;
|
||||
|
||||
c->owner->_transfered.fetch_add(transfer->actual_length, std::memory_order_relaxed);
|
||||
log_p("Transfer %d [%d] > %s", transfer->actual_length, transfer->status, bytes(transfer->buffer, transfer->actual_length, 40).c_str());
|
||||
|
||||
if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
|
||||
return;
|
||||
|
||||
if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE)
|
||||
{
|
||||
c->owner->_connected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (transfer->status == LIBUSB_TRANSFER_COMPLETED)
|
||||
{
|
||||
c->slot->commit(transfer->actual_length);
|
||||
c->slot = c->owner->_processQueue.get();
|
||||
if (!c->slot)
|
||||
{
|
||||
log_e("Can't allocate data slot for next usb transfer, increase usb buffer slots");
|
||||
c->owner->_connected = false;
|
||||
return;
|
||||
}
|
||||
c->transfer->buffer = c->slot->data;
|
||||
}
|
||||
int status = libusb_submit_transfer(c->transfer);
|
||||
if (status != LIBUSB_SUCCESS)
|
||||
{
|
||||
log_w("USB transfer re-submit failed with status %d", status);
|
||||
c->owner->_connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::mainLoop()
|
||||
{
|
||||
// Set thread name
|
||||
setThreadName("usb-write");
|
||||
state(PROTOCOL_STATUS_LINKING);
|
||||
|
||||
log_d("USB writing thread started");
|
||||
|
||||
int connectCount = 0;
|
||||
|
||||
while (_active)
|
||||
{
|
||||
int linkCount = 0;
|
||||
libusb_device_handle *handler = libusb_open_device_with_vid_pid(_context, Settings::vendorid, Settings::productid);
|
||||
if (handler)
|
||||
{
|
||||
uint8_t epIn = 0;
|
||||
uint8_t epOut = 0;
|
||||
int retry = 0;
|
||||
if (_state != PROTOCOL_STATUS_LINKING && _state != PROTOCOL_STATUS_ERROR)
|
||||
connectCount = 0;
|
||||
_state = PROTOCOL_STATUS_LINKING;
|
||||
linkCount = 1;
|
||||
uint8_t endpointIn = 0;
|
||||
uint8_t endpointOut = 0;
|
||||
libusb_device *device = nullptr;
|
||||
_ecnrypt = false;
|
||||
writeQueue.clear();
|
||||
|
||||
while (!device && retry++ < LINK_RETRY)
|
||||
while (!device && linkCount++ < LINK_RETRY)
|
||||
{
|
||||
device = link(handler, &epIn, &epOut);
|
||||
device = link(handler, &endpointIn, &endpointOut);
|
||||
writeQueue.waitFor(_active, LINK_RETRY_TIMEOUT);
|
||||
}
|
||||
|
||||
if (device)
|
||||
{
|
||||
_connected = true;
|
||||
state(PROTOCOL_STATUS_ONLINE);
|
||||
log_i("Device connected %d:%d speed: %d", libusb_get_bus_number(device), libusb_get_device_address(device), libusb_get_device_speed(device));
|
||||
_reader.start(_context, handler, epIn, this);
|
||||
onDeviceConnect();
|
||||
|
||||
writeLoop(handler, epOut);
|
||||
|
||||
_connected = false;
|
||||
_state = PROTOCOL_STATUS_ONLINE;
|
||||
onDeviceConnect(handler, device, endpointIn);
|
||||
writeLoop(handler, endpointOut);
|
||||
_state = PROTOCOL_STATUS_LINKING;
|
||||
onDeviceDisconnect();
|
||||
_reader.stop();
|
||||
}
|
||||
|
||||
libusb_release_interface(handler, 0);
|
||||
libusb_close(handler);
|
||||
}
|
||||
state(PROTOCOL_STATUS_NO_DEVICE);
|
||||
if (linkCount == 0)
|
||||
{
|
||||
if (_state != PROTOCOL_STATUS_NO_DEVICE && connectCount++ > CONNECT_RETRY)
|
||||
_state = PROTOCOL_STATUS_NO_DEVICE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_state != PROTOCOL_STATUS_ERROR && connectCount++ > CONNECT_RETRY)
|
||||
_state = PROTOCOL_STATUS_ERROR;
|
||||
}
|
||||
writeQueue.waitFor(_active, RECONNECT_TIMEOUT);
|
||||
}
|
||||
|
||||
log_v("USB writing thread stopped");
|
||||
}
|
||||
|
||||
void Connection::onDeviceConnect(libusb_device_handle *handler, libusb_device *device, uint8_t endpointIn)
|
||||
{
|
||||
_connected = true;
|
||||
_phoneConnected = false;
|
||||
log_i("Device connected %d:%d speed: %d", libusb_get_bus_number(device), libusb_get_device_address(device), libusb_get_device_speed(device));
|
||||
writeQueue.clear();
|
||||
videoStream.clear();
|
||||
audioStreamMain.clear();
|
||||
audioStreamAux.clear();
|
||||
_processQueue.reset();
|
||||
|
||||
_processThread = std::thread(&Connection::processLoop, this);
|
||||
_readThread = std::thread(&Connection::readLoop, this);
|
||||
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
context.owner = this;
|
||||
context.slot = _processQueue.get();
|
||||
if (context.slot == nullptr)
|
||||
{
|
||||
log_e("Can't allocate data slot for usb transfer, increase usb buffer slots");
|
||||
_connected = false;
|
||||
return;
|
||||
}
|
||||
libusb_fill_bulk_transfer(context.transfer, handler, endpointIn, context.slot->data, context.slot->size, Connection::onTransfer, &context, 0);
|
||||
int status = libusb_submit_transfer(context.transfer);
|
||||
if (status != LIBUSB_SUCCESS)
|
||||
{
|
||||
log_w("USB transfer submit failed with code %d", status);
|
||||
_connected = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendInit();
|
||||
}
|
||||
|
||||
void Connection::onDeviceDisconnect()
|
||||
{
|
||||
onPhoneDisconnect();
|
||||
|
||||
log_i("Device disconnected");
|
||||
_connected = false;
|
||||
_state = PROTOCOL_STATUS_ERROR;
|
||||
_processQueue.notify();
|
||||
|
||||
if (_readThread.joinable())
|
||||
_readThread.join();
|
||||
|
||||
if (_processThread.joinable())
|
||||
_processThread.join();
|
||||
}
|
||||
|
||||
void Connection::onPhoneConnect()
|
||||
{
|
||||
if (_phoneConnected)
|
||||
return;
|
||||
_state = PROTOCOL_STATUS_CONNECTED;
|
||||
log_i("Phone connected");
|
||||
_phoneConnected = true;
|
||||
|
||||
if (Settings::onConnect.value.length() > 1)
|
||||
execute(Settings::onConnect.value.c_str());
|
||||
}
|
||||
|
||||
void Connection::onPhoneDisconnect()
|
||||
{
|
||||
if (!_phoneConnected)
|
||||
return;
|
||||
_state = PROTOCOL_STATUS_ONLINE;
|
||||
log_i("Phone disconnected");
|
||||
_phoneConnected = false;
|
||||
|
||||
_recorder.stop();
|
||||
if (Settings::onDisconnect.value.length() > 1)
|
||||
execute(Settings::onDisconnect.value.c_str());
|
||||
|
||||
_method = "unknown";
|
||||
_phoneName = "phone";
|
||||
}
|
||||
|
||||
void Connection::readLoop()
|
||||
{
|
||||
setThreadName("usb-read");
|
||||
setThreadPriority(ThreadPriority::Realtime);
|
||||
timeval timeout{0, 1000};
|
||||
|
||||
log_d("USB reading thread started");
|
||||
|
||||
while (_connected)
|
||||
{
|
||||
libusb_handle_events_timeout_completed(_context, &timeout, nullptr);
|
||||
}
|
||||
|
||||
log_v("Canceling transfer requests");
|
||||
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
if (context.transfer)
|
||||
libusb_cancel_transfer(context.transfer);
|
||||
libusb_handle_events_timeout_completed(_context, &timeout, nullptr);
|
||||
}
|
||||
|
||||
log_v("USB reading thread stopped");
|
||||
}
|
||||
|
||||
void Connection::processLoop()
|
||||
{
|
||||
setThreadName("usb-process");
|
||||
log_d("USB processing thread started");
|
||||
|
||||
while (_connected)
|
||||
{
|
||||
std::unique_ptr<Message> message = std::make_unique<Message>();
|
||||
|
||||
if (!_processQueue.read(message->header(), message->headerSize(), _connected))
|
||||
break;
|
||||
|
||||
if (message->invalidMagic())
|
||||
{
|
||||
log_w("Header read failed > invalid magic");
|
||||
_processQueue.discard();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message->invalidChecksum())
|
||||
{
|
||||
log_w("Header read failed > invalid checksum");
|
||||
_processQueue.discard();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message->invalidLength())
|
||||
{
|
||||
log_w("Header read failed > invalid length");
|
||||
_processQueue.discard();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message->length() > 0)
|
||||
{
|
||||
uint32_t padding = message->type() == CMD_VIDEO_DATA ? AV_INPUT_BUFFER_PADDING_SIZE : 0;
|
||||
uint8_t *buff = message->allocate(padding);
|
||||
if (!_processQueue.read(buff, message->length(), _connected))
|
||||
continue;
|
||||
if (!buff)
|
||||
{
|
||||
log_w("Message discarded > can't allocate memory %d", message->length() + padding);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(std::move(message));
|
||||
}
|
||||
|
||||
log_v("USB processing thread stopped");
|
||||
}
|
||||
|
||||
void Connection::writeLoop(libusb_device_handle *handler, uint8_t ep)
|
||||
{
|
||||
while (_active && _reader.active())
|
||||
while (_connected)
|
||||
{
|
||||
std::unique_ptr<Message> message = writeQueue.pop();
|
||||
if (!message)
|
||||
{
|
||||
if (!writeQueue.waitFor(_active, PROTOCOL_HEARTBEAT_DELAY))
|
||||
if (!writeQueue.waitFor(_connected, PROTOCOL_HEARTBEAT_DELAY))
|
||||
break;
|
||||
message = writeQueue.pop();
|
||||
}
|
||||
@@ -160,7 +410,7 @@ void Connection::writeLoop(libusb_device_handle *handler, uint8_t ep)
|
||||
if (!message)
|
||||
message = Message::HeartBeat();
|
||||
|
||||
if (!_active || !_reader.active())
|
||||
if (!_connected)
|
||||
break;
|
||||
|
||||
if (!message->allocated())
|
||||
@@ -173,26 +423,26 @@ void Connection::writeLoop(libusb_device_handle *handler, uint8_t ep)
|
||||
|
||||
if (_ecnrypt)
|
||||
{
|
||||
Status s = message->encrypt(_cipher);
|
||||
if (s.failed())
|
||||
char error[256];
|
||||
if (!message->encrypt(_cipher, error))
|
||||
{
|
||||
log_w("Message encryption failed > %s", s.error());
|
||||
log_w("Message encryption failed > %s", error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int transferred;
|
||||
libusb_bulk_transfer(handler, ep, message->header(), message->headerSize(), &transferred, 0);
|
||||
int status = libusb_bulk_transfer(handler, ep, message->header(), message->headerSize(), &transferred, PROTOCOL_HEARTBEAT_DELAY);
|
||||
message->setOffset(0);
|
||||
if (message->length() > 0)
|
||||
libusb_bulk_transfer(handler, ep, message->data(), message->length(), &transferred, 0);
|
||||
if (status == LIBUSB_SUCCESS && message->length() > 0)
|
||||
{
|
||||
libusb_bulk_transfer(handler, ep, message->data(), message->length(), &transferred, PROTOCOL_HEARTBEAT_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libusb_device *Connection::link(libusb_device_handle *handler, uint8_t *epIn, uint8_t *epOut)
|
||||
{
|
||||
state(PROTOCOL_STATUS_LINKING);
|
||||
|
||||
if (fail(libusb_reset_device(handler), " Can't reset device"))
|
||||
return nullptr;
|
||||
|
||||
@@ -242,51 +492,10 @@ bool Connection::fail(int status, const char *msg)
|
||||
if (status == 0)
|
||||
return false;
|
||||
log_w("%s > %s", msg, libusb_error_name(status));
|
||||
state(PROTOCOL_STATUS_ERROR);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Connection::state(u_int8_t state)
|
||||
{
|
||||
if (state == _state)
|
||||
return false;
|
||||
|
||||
if (state == PROTOCOL_STATUS_ERROR && _failCount++ < 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state > _state || state == PROTOCOL_STATUS_INITIALISING)
|
||||
{
|
||||
_nodeviceCount = 0;
|
||||
_failCount = 0;
|
||||
_state = state;
|
||||
if (_statusHandler)
|
||||
*_statusHandler = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state == PROTOCOL_STATUS_NO_DEVICE && (_nodeviceCount++ > 30 || _state >= PROTOCOL_STATUS_ONLINE))
|
||||
{
|
||||
_failCount = 0;
|
||||
_state = state;
|
||||
if (_statusHandler)
|
||||
*_statusHandler = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state == PROTOCOL_STATUS_ONLINE && _state == PROTOCOL_STATUS_CONNECTED)
|
||||
{
|
||||
_state = state;
|
||||
if (_statusHandler)
|
||||
*_statusHandler = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Connection::onDeviceConnect()
|
||||
void Connection::sendInit()
|
||||
{
|
||||
int syncTime = std::time(nullptr);
|
||||
int drivePosition = Settings::leftDrive ? 0 : 1; // 0==left, 1==right
|
||||
@@ -329,7 +538,7 @@ void Connection::onDeviceConnect()
|
||||
if (Settings::encryption)
|
||||
{
|
||||
if (_cipher)
|
||||
send(Message::Encryption(_cipher->Seed()));
|
||||
send(Message::Encryption(_cipher->seed()));
|
||||
else
|
||||
log_w("Can't request encryption > Cypher is not initalised");
|
||||
}
|
||||
@@ -359,79 +568,99 @@ void Connection::onDeviceConnect()
|
||||
send(Message::Control(1002));
|
||||
}
|
||||
|
||||
void Connection::onDeviceDisconnect()
|
||||
{
|
||||
_recorder.stop();
|
||||
if (Settings::onDisconnect.value.length() > 1)
|
||||
execute(Settings::onDisconnect.value.c_str());
|
||||
}
|
||||
|
||||
void Connection::onMessage(std::unique_ptr<Message> message)
|
||||
{
|
||||
Status s = message->decrypt(_cipher);
|
||||
if (s.failed())
|
||||
char error[256];
|
||||
if (!message->decrypt(_cipher, error))
|
||||
{
|
||||
log_w("Can't decrypt message %d > %s", message->type(), s.error());
|
||||
log_w("Can't decrypt message %d > %s", message->type(), error);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message->type())
|
||||
if (message->type() == CMD_VIDEO_DATA && message->setOffset(20))
|
||||
{
|
||||
|
||||
case CMD_CONTROL:
|
||||
if (message->length() == 4)
|
||||
{
|
||||
switch (message->getInt(0))
|
||||
{
|
||||
case 1:
|
||||
_recorder.start(&writeQueue);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
_recorder.stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_PLUGGED:
|
||||
{
|
||||
state(PROTOCOL_STATUS_CONNECTED);
|
||||
if (Settings::onConnect.value.length() > 1)
|
||||
execute(Settings::onConnect.value.c_str());
|
||||
break;
|
||||
if (!videoStream.pushDiscard(std::move(message)))
|
||||
log_w("Discard message > video queue is full");
|
||||
return;
|
||||
}
|
||||
|
||||
case CMD_UNPLUGGED:
|
||||
if (message->type() == CMD_AUDIO_DATA && message->length() > 16)
|
||||
{
|
||||
state(PROTOCOL_STATUS_ONLINE);
|
||||
_recorder.stop();
|
||||
if (Settings::onDisconnect.value.length() > 1)
|
||||
execute(Settings::onDisconnect.value.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_VIDEO_DATA:
|
||||
{
|
||||
if (message->setOffset(20))
|
||||
videoStream.pushDiscard(std::move(message));
|
||||
break;
|
||||
}
|
||||
case CMD_AUDIO_DATA:
|
||||
{
|
||||
if (message->length() <= 16)
|
||||
break;
|
||||
int channel = message->getInt(8);
|
||||
message->setOffset(12);
|
||||
if (channel == 1)
|
||||
audioStreamMain.pushDiscard(std::move(message));
|
||||
{
|
||||
if (!audioStreamMain.pushDiscard(std::move(message)))
|
||||
log_w("Discard message > main audio queue is full");
|
||||
return;
|
||||
}
|
||||
if (channel == 2)
|
||||
audioStreamAux.pushDiscard(std::move(message));
|
||||
break;
|
||||
{
|
||||
if (!audioStreamAux.pushDiscard(std::move(message)))
|
||||
log_w("Discard message > aux audio queue is full");
|
||||
return;
|
||||
}
|
||||
}
|
||||
case CMD_ENCRYPTION:
|
||||
if (message->length() == 0)
|
||||
setEncryption(true);
|
||||
break;
|
||||
|
||||
if (message->type() == CMD_CONTROL && message->length() == 4)
|
||||
{
|
||||
switch (message->getInt(0))
|
||||
{
|
||||
case 1:
|
||||
_recorder.start(&writeQueue);
|
||||
return;
|
||||
|
||||
case 2:
|
||||
_recorder.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (message->type() == CMD_PLUGGED)
|
||||
{
|
||||
onPhoneConnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (message->type() == CMD_UNPLUGGED)
|
||||
{
|
||||
onPhoneDisconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (message->type() == CMD_ENCRYPTION && message->length() == 0)
|
||||
{
|
||||
setEncryption(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message->type() == CMD_JSON_CONTROL)
|
||||
{
|
||||
char buf[64];
|
||||
log_d("Controll message %d [%d] > %s", message->type(), message->length(), ascii(message->data(), message->length()).c_str());
|
||||
if (jsonFindString(message->data(), message->length(), "MDLinkType", buf, 64))
|
||||
_method = buf;
|
||||
if (jsonFindString(message->data(), message->length(), "btName", buf, 64))
|
||||
_phoneName = buf;
|
||||
return;
|
||||
}
|
||||
|
||||
log_v("Unknown message %d [%d] > %s", message->type(), message->length(), bytes(message->data(), message->length(), 40).c_str());
|
||||
}
|
||||
|
||||
const std::string Connection::status() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
|
||||
const libusb_version *version = libusb_get_version();
|
||||
out << "v"
|
||||
<< static_cast<int>(version->major) << '.'
|
||||
<< static_cast<int>(version->minor) << '.'
|
||||
<< static_cast<int>(version->micro) << '.'
|
||||
<< static_cast<int>(version->nano) << " "
|
||||
<< " queue " << _processQueue.count() << " / " << Settings::usbBuffer << " "
|
||||
<< (_ecnrypt.load(std::memory_order_acquire) ? "encrypt" : "simple") << " "
|
||||
<< _phoneName << " via " << _method;
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
+48
-24
@@ -1,72 +1,96 @@
|
||||
#ifndef SRC_CONNECTOR
|
||||
#define SRC_CONNECTOR
|
||||
#ifndef SRC_PROTOCOL_CONNECTION
|
||||
#define SRC_PROTOCOL_CONNECTION
|
||||
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "protocol/imessage_sender.h"
|
||||
#include "struct/atomic_queue.h"
|
||||
#include "struct/usb_buffer.h"
|
||||
#include "protocol/aes_cipher.h"
|
||||
#include "protocol/connection_reader.h"
|
||||
#include "protocol/usb_buffer.h"
|
||||
#include "recorder.h"
|
||||
|
||||
#define LINK_RETRY 5
|
||||
#define LINK_RETRY 3
|
||||
#define CONNECT_RETRY 20
|
||||
#define LINK_RETRY_TIMEOUT 100
|
||||
#define RECONNECT_TIMEOUT 100
|
||||
#define RECONNECT_TIMEOUT 200
|
||||
#define PROTOCOL_HEARTBEAT_DELAY 3000
|
||||
|
||||
#define WRITE_QUEUE_SIZE 256
|
||||
#define WRITE_QUEUE_SIZE 128
|
||||
#define VIDEO_QUEUE_SIZE 128
|
||||
#define AUDIO_QUEUE_SIZE 128
|
||||
#define PROCESS_QUEUE_SIZE 128
|
||||
|
||||
#define ENCRYPTION_BASE "SkBRDy3gmrw1ieH0"
|
||||
|
||||
class Connection : public IMessageReceiver
|
||||
class Connection
|
||||
{
|
||||
|
||||
public:
|
||||
Connection();
|
||||
virtual ~Connection();
|
||||
|
||||
void start(atomic<int8_t> *statusHandler);
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool inline send(std::unique_ptr<Message> message) { return writeQueue.pushDiscard(std::move(message)); }
|
||||
uint32_t transfered() const { return _transfered.load(std::memory_order_acquire); }
|
||||
|
||||
int8_t state() const { return _state.load(); }
|
||||
std::string connectionMethod() const { return _method; }
|
||||
std::string phoneName() const { return _phoneName; }
|
||||
const std::string status() const;
|
||||
|
||||
AtomicQueue<Message> writeQueue;
|
||||
AtomicQueue<Message> videoStream;
|
||||
AtomicQueue<Message> audioStreamMain;
|
||||
AtomicQueue<Message> audioStreamAux;
|
||||
|
||||
virtual void onMessage(std::unique_ptr<Message> message) override;
|
||||
|
||||
private:
|
||||
struct Context
|
||||
{
|
||||
Connection *owner = nullptr;
|
||||
DataSlot *slot = nullptr;
|
||||
libusb_transfer *transfer = nullptr;
|
||||
};
|
||||
|
||||
static void onTransfer(libusb_transfer *transfer);
|
||||
void mainLoop();
|
||||
void readLoop();
|
||||
void processLoop();
|
||||
void writeLoop(libusb_device_handle *handler, uint8_t ep);
|
||||
libusb_device *link(libusb_device_handle *handler, uint8_t *epIn, uint8_t *epOut);
|
||||
void setEncryption(bool enabled);
|
||||
bool fail(int status, const char *msg);
|
||||
bool state(u_int8_t state);
|
||||
void onDeviceConnect();
|
||||
void sendInit();
|
||||
void onDeviceConnect(libusb_device_handle *handler, libusb_device *device, uint8_t endpointIn);
|
||||
void onDeviceDisconnect();
|
||||
void onPhoneConnect();
|
||||
void onPhoneDisconnect();
|
||||
void onMessage(std::unique_ptr<Message> message);
|
||||
|
||||
std::thread _writeThread;
|
||||
std::thread _readThread;
|
||||
std::thread _processThread;
|
||||
|
||||
Recorder _recorder;
|
||||
UsbBuffer _processQueue;
|
||||
std::vector<Context> _transfers;
|
||||
atomic<int8_t> *_statusHandler;
|
||||
AESCipher *_cipher;
|
||||
ConnectionReader _reader;
|
||||
libusb_context *_context;
|
||||
std::thread _thread;
|
||||
|
||||
std::atomic<bool> _active;
|
||||
std::atomic<bool> _connected;
|
||||
std::atomic<bool> _phoneConnected;
|
||||
std::atomic<bool> _ecnrypt;
|
||||
std::atomic<int8_t> _state;
|
||||
|
||||
uint8_t _state;
|
||||
uint8_t _failCount;
|
||||
uint8_t _nodeviceCount;
|
||||
|
||||
atomic<int8_t> *_statusHandler;
|
||||
std::string _method;
|
||||
std::string _phoneName;
|
||||
std::atomic<uint32_t> _transfered;
|
||||
};
|
||||
|
||||
#endif /* SRC_CONNECTOR */
|
||||
#endif /* SRC_PROTOCOL_CONNECTION */
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
#include "protocol/connection_reader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "settings.h"
|
||||
#include "common/logger.h"
|
||||
#include "common/threading.h"
|
||||
#include "protocol/protocol_const.h"
|
||||
#include "libavcodec/defs.h"
|
||||
#include "common/functions.h"
|
||||
|
||||
ConnectionReader::ConnectionReader()
|
||||
: _active(false),
|
||||
_buffer(Settings::usbBuffer, Settings::usbTransferSize),
|
||||
_transfers(Settings::usbQueue),
|
||||
_receiver(nullptr),
|
||||
_usbContext(nullptr)
|
||||
{
|
||||
log_v("Created");
|
||||
}
|
||||
|
||||
ConnectionReader::~ConnectionReader()
|
||||
{
|
||||
stop();
|
||||
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
if (context.transfer)
|
||||
{
|
||||
libusb_free_transfer(context.transfer);
|
||||
context.transfer = nullptr;
|
||||
}
|
||||
}
|
||||
log_v("Destroyed");
|
||||
}
|
||||
|
||||
bool ConnectionReader::start(libusb_context *context, libusb_device_handle *device, uint8_t endpoint, IMessageReceiver *receiver)
|
||||
{
|
||||
if (_active || !context || !device)
|
||||
return false;
|
||||
|
||||
_receiver = receiver;
|
||||
_usbContext = context;
|
||||
|
||||
log_i("Starting to read endpoint %d with %d requests", endpoint, _transfers.size());
|
||||
|
||||
// Prepare usb transfers
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
context.owner = this;
|
||||
if (!context.transfer)
|
||||
{
|
||||
context.transfer = libusb_alloc_transfer(0);
|
||||
if (context.transfer == nullptr)
|
||||
{
|
||||
log_e("Can't allocate usb transfer");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start processing thread
|
||||
_buffer.reset();
|
||||
_active = true;
|
||||
_processThread = std::thread(&ConnectionReader::processLoop, this);
|
||||
_readThread = std::thread(&ConnectionReader::readLoop, this);
|
||||
|
||||
// Start usb reading thread
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
context.slot = _buffer.get();
|
||||
if (context.slot == nullptr)
|
||||
{
|
||||
log_e("Can't allocate data slot for usb transfer");
|
||||
return false;
|
||||
}
|
||||
context.owner = this;
|
||||
libusb_fill_bulk_transfer(context.transfer, device, endpoint, context.slot->data, context.slot->size, ConnectionReader::onUsbRead, &context, 0);
|
||||
int status = libusb_submit_transfer(context.transfer);
|
||||
if (status != LIBUSB_SUCCESS)
|
||||
{
|
||||
log_w("USB transfer submit failed with code %d", status);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionReader::stop()
|
||||
{
|
||||
log_v("Stopping");
|
||||
|
||||
_active = false;
|
||||
|
||||
if (_usbContext)
|
||||
{
|
||||
for (Context &context : _transfers)
|
||||
{
|
||||
if (context.transfer)
|
||||
libusb_cancel_transfer(context.transfer);
|
||||
}
|
||||
|
||||
timeval timeout{0, 100000};
|
||||
libusb_handle_events_timeout_completed(_usbContext, &timeout, nullptr);
|
||||
|
||||
log_v("Events canceled");
|
||||
}
|
||||
|
||||
_buffer.notify();
|
||||
|
||||
if (_readThread.joinable())
|
||||
_readThread.join();
|
||||
|
||||
if (_processThread.joinable())
|
||||
_processThread.join();
|
||||
|
||||
_usbContext = nullptr;
|
||||
|
||||
log_v("Threads stopped");
|
||||
}
|
||||
|
||||
void ConnectionReader::onUsbRead(libusb_transfer *transfer)
|
||||
{
|
||||
if (!transfer || !transfer->user_data)
|
||||
return;
|
||||
|
||||
Context *c = static_cast<Context *>(transfer->user_data);
|
||||
if (!c->owner->_active)
|
||||
return;
|
||||
|
||||
log_p("Read %d [%d]: %s", transfer->actual_length, transfer->status, bytes(transfer->buffer, transfer->actual_length, 40).c_str());
|
||||
|
||||
if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
|
||||
return;
|
||||
|
||||
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
|
||||
{
|
||||
log_w("USB read failed with status %d", transfer->status);
|
||||
c->owner->_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
c->slot->commit(transfer->actual_length);
|
||||
|
||||
c->slot = c->owner->_buffer.get();
|
||||
if (!c->slot)
|
||||
{
|
||||
log_e("Can't allocate data slot for next usb transfer");
|
||||
c->owner->_active = false;
|
||||
return;
|
||||
}
|
||||
c->transfer->buffer = c->slot->data;
|
||||
|
||||
if (!c->owner->_active)
|
||||
return;
|
||||
|
||||
int status = libusb_submit_transfer(c->transfer);
|
||||
if (status != LIBUSB_SUCCESS)
|
||||
{
|
||||
log_w("USB transfer re-submit failed with status %d", status);
|
||||
c->owner->_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionReader::readLoop()
|
||||
{
|
||||
setThreadName("usb-read");
|
||||
setThreadPriority(ThreadPriority::Realtime);
|
||||
timeval timeout{0, 100000};
|
||||
|
||||
log_d("USB reading thread started");
|
||||
|
||||
while (_active)
|
||||
{
|
||||
libusb_handle_events_timeout_completed(_usbContext, &timeout, nullptr);
|
||||
}
|
||||
|
||||
log_v("USB reading thread stopped");
|
||||
}
|
||||
|
||||
void ConnectionReader::processLoop()
|
||||
{
|
||||
setThreadName("usb-process");
|
||||
log_d("USB processing thread started");
|
||||
|
||||
while (_active)
|
||||
{
|
||||
std::unique_ptr<Message> message = std::make_unique<Message>();
|
||||
|
||||
if (!_buffer.read(message->header(), message->headerSize(), _active))
|
||||
break;
|
||||
|
||||
if (!message->valid())
|
||||
{
|
||||
log_w("Mallformed message %s", message->toString(20).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message->length() >= 0)
|
||||
{
|
||||
uint8_t *buff = message->allocate(message->type() == CMD_VIDEO_DATA ? AV_INPUT_BUFFER_PADDING_SIZE : 0);
|
||||
if (!_buffer.read(buff, message->length(), _active))
|
||||
break;
|
||||
}
|
||||
|
||||
if (_receiver && message->allocated())
|
||||
_receiver->onMessage(std::move(message));
|
||||
}
|
||||
|
||||
log_v("USB processing thread stopped");
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#ifndef SRC_PROTOCOL_CONNECTION_READER
|
||||
#define SRC_PROTOCOL_CONNECTION_READER
|
||||
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "struct/usb_buffer.h"
|
||||
#include "protocol/aes_cipher.h"
|
||||
#include "protocol/message.h"
|
||||
|
||||
class IMessageReceiver
|
||||
{
|
||||
public:
|
||||
virtual ~IMessageReceiver() = default;
|
||||
virtual void onMessage(std::unique_ptr<Message> message) = 0;
|
||||
};
|
||||
|
||||
class ConnectionReader
|
||||
{
|
||||
public:
|
||||
ConnectionReader();
|
||||
~ConnectionReader();
|
||||
|
||||
ConnectionReader(const ConnectionReader &) = delete;
|
||||
ConnectionReader &operator=(const ConnectionReader &) = delete;
|
||||
|
||||
bool start(libusb_context *context, libusb_device_handle *device, uint8_t endpoint, IMessageReceiver *receiver);
|
||||
void stop();
|
||||
|
||||
int bufferCount() const { return _buffer.count(); }
|
||||
bool active() const { return _active; }
|
||||
|
||||
private:
|
||||
struct Context
|
||||
{
|
||||
ConnectionReader *owner = nullptr;
|
||||
DataSlot *slot = nullptr;
|
||||
libusb_transfer *transfer = nullptr;
|
||||
};
|
||||
|
||||
static void onUsbRead(libusb_transfer *transfer);
|
||||
void readLoop();
|
||||
void processLoop();
|
||||
|
||||
void cancelTransfers();
|
||||
|
||||
std::atomic<bool> _active;
|
||||
UsbBuffer _buffer;
|
||||
std::vector<Context> _transfers;
|
||||
IMessageReceiver *_receiver;
|
||||
std::thread _readThread;
|
||||
std::thread _processThread;
|
||||
libusb_context *_usbContext;
|
||||
};
|
||||
|
||||
#endif /* SRC_PROTOCOL_CONNECTION_READER */
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef SRC_HELPER_ISENDER
|
||||
#define SRC_HELPER_ISENDER
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "protocol/message.h"
|
||||
|
||||
class IMessageSender
|
||||
{
|
||||
public:
|
||||
virtual ~IMessageSender() = default;
|
||||
virtual bool send(std::unique_ptr<Message> packet) = 0;
|
||||
};
|
||||
|
||||
#endif /* SRC_HELPER_ISENDER */
|
||||
+27
-34
@@ -99,51 +99,40 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool valid() const
|
||||
bool encrypt(AESCipher *cipher, char *err = nullptr)
|
||||
{
|
||||
if (_header.magic != MAGIC_ENC && _header.magic != MAGIC)
|
||||
return false;
|
||||
if (_header.typecheck != ~_header.type)
|
||||
return false;
|
||||
if (_header.length < 0 || _header.length > MESSAGE_MAX_PAYLOAD_SIZE)
|
||||
if (!_encrypt || _header.magic == MAGIC_ENC)
|
||||
return true;
|
||||
|
||||
if (!cipher)
|
||||
return AESCipher::error(err, "Cipher is not initialised");
|
||||
|
||||
if (!allocated())
|
||||
return AESCipher::error(err, "Message data is not allocated");
|
||||
|
||||
if (!cipher->encrypt(_data, _header.length, err))
|
||||
return false;
|
||||
|
||||
_header.magic = MAGIC_ENC;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status encrypt(AESCipher *cipher)
|
||||
{
|
||||
if (!_encrypt)
|
||||
return Status::Success();
|
||||
|
||||
if (_header.magic == MAGIC_ENC)
|
||||
return Status::Success();
|
||||
|
||||
if (!cipher)
|
||||
return Status::Error("Cipher is not initialised");
|
||||
|
||||
if (!allocated())
|
||||
return Status::Error("Message data is not allocated");
|
||||
|
||||
Status result = cipher->Encrypt(_data, _header.length);
|
||||
|
||||
if (result.succeed())
|
||||
_header.magic = MAGIC_ENC;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Status decrypt(AESCipher *cipher)
|
||||
bool decrypt(AESCipher *cipher, char *err = nullptr)
|
||||
{
|
||||
if (_header.magic != MAGIC_ENC)
|
||||
return Status::Success();
|
||||
return true;
|
||||
|
||||
if (!cipher)
|
||||
return Status::Error("Cipher is not initialised");
|
||||
return AESCipher::error(err, "Cipher is not initialised");
|
||||
|
||||
if (!allocated())
|
||||
return Status::Error("Message data is not allocated");
|
||||
return AESCipher::error(err, "Message data is not allocated");
|
||||
|
||||
return cipher->Decrypt(_data, _header.length);
|
||||
if (!cipher->decrypt(_data, _header.length, err))
|
||||
return false;
|
||||
|
||||
_header.magic = MAGIC;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isMotion() const
|
||||
@@ -299,7 +288,11 @@ public:
|
||||
int32_t length() const { return _header.length - _offset; }
|
||||
uint8_t *data() const { return _data ? _data + _offset : nullptr; }
|
||||
|
||||
const std::string toString(int count) const
|
||||
bool invalidMagic() const { return _header.magic != MAGIC_ENC && _header.magic != MAGIC; }
|
||||
bool invalidChecksum() const { return _header.typecheck != ~_header.type; }
|
||||
bool invalidLength() const { return _header.length < 0 || _header.length > MESSAGE_MAX_PAYLOAD_SIZE; }
|
||||
|
||||
const std::string toString(int count) const
|
||||
{
|
||||
const char *cmds = "Unknown";
|
||||
for (size_t i = 0; i < sizeof(protocolCmdList) / sizeof(protocolCmdList[0]); ++i)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "struct/usb_buffer.h"
|
||||
#include "protocol/usb_buffer.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -129,6 +129,17 @@ bool UsbBuffer::read(uint8_t *dst, uint32_t length, std::atomic<bool> &active)
|
||||
return active.load();
|
||||
}
|
||||
|
||||
void UsbBuffer::discard()
|
||||
{
|
||||
if (!_slots[_readSlot].ready.load())
|
||||
return;
|
||||
|
||||
_slots[_readSlot].ready.store(false);
|
||||
_readSlot++;
|
||||
if (_readSlot >= _size)
|
||||
_readSlot = 0;
|
||||
}
|
||||
|
||||
void UsbBuffer::reset()
|
||||
{
|
||||
_readSlot = 0;
|
||||
@@ -40,6 +40,7 @@ public:
|
||||
|
||||
DataSlot *get();
|
||||
bool read(uint8_t *dst, uint32_t length, std::atomic<bool> &active);
|
||||
void discard();
|
||||
|
||||
void reset();
|
||||
void notify();
|
||||
+1
-5
@@ -4,12 +4,8 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "protocol/protocol_const.h"
|
||||
#include "common/functions.h"
|
||||
#include "settings.h"
|
||||
#include "protocol/message.h"
|
||||
|
||||
|
||||
Recorder::Recorder(uint16_t buffSize)
|
||||
Recorder::Recorder()
|
||||
: _queue(nullptr), _active(false), _device(0)
|
||||
{
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,7 +1,6 @@
|
||||
#ifndef SRC_RECORDER
|
||||
#define SRC_RECORDER
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
@@ -12,7 +11,7 @@
|
||||
class Recorder
|
||||
{
|
||||
public:
|
||||
Recorder(uint16_t buffSize);
|
||||
Recorder();
|
||||
~Recorder();
|
||||
|
||||
void start(AtomicQueue<Message> *queue);
|
||||
@@ -20,11 +19,10 @@ public:
|
||||
|
||||
private:
|
||||
static void AudioCallback(void *userdata, Uint8 *stream, int len);
|
||||
void runner();
|
||||
|
||||
AtomicQueue<Message> *_queue;
|
||||
std::atomic<bool> _active;
|
||||
SDL_AudioDeviceID _device;
|
||||
SDL_AudioDeviceID _device;
|
||||
};
|
||||
|
||||
#endif /* SRC_RECORDER */
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 497 KiB |
+4
-3
@@ -1,6 +1,7 @@
|
||||
#include "settings.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
|
||||
#include "common/logger.h"
|
||||
@@ -10,7 +11,7 @@ bool Settings::load(const std::string &filename)
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open())
|
||||
{
|
||||
log_e( "Cannot open file > %s", filename.c_str());
|
||||
log_e("Cannot open file > %s", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ void Settings::print()
|
||||
{
|
||||
for (ISetting *setting : _settings())
|
||||
{
|
||||
log_d("%s = %s",setting->name.c_str(), setting->asString().c_str());
|
||||
log_d("%s = %s", setting->name.c_str(), setting->asString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-9
@@ -14,7 +14,7 @@ public:
|
||||
// General section
|
||||
static inline Setting<int> vendorid{"vendor-id", 4884};
|
||||
static inline Setting<int> productid{"product-id", 5408};
|
||||
static inline Setting<int> width{"width", 720};
|
||||
static inline Setting<int> width{"width", 1024};
|
||||
static inline Setting<int> height{"height", 576};
|
||||
static inline Setting<int> sourceFps{"source-fps", 60};
|
||||
static inline Setting<int> fps{"fps", 60};
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
static inline Setting<int> mediaDelay{"android-media-delay", 300};
|
||||
|
||||
// Application configuration section
|
||||
static inline Setting<int> fontSize{"font-size", 30};
|
||||
static inline Setting<int> fontSize{"font-size", 40};
|
||||
static inline Setting<bool> vsync{"vsync", false};
|
||||
static inline Setting<bool> hwDecode{"hw-decode", true};
|
||||
static inline Setting<int> renderingBuffer{"rendering-buffer", 5};
|
||||
@@ -44,14 +44,12 @@ public:
|
||||
static inline Setting<int> forceRedraw{"force-redraw", 0};
|
||||
static inline Setting<int> forceRedrawTimeout{"force-redraw-timeout", 0};
|
||||
static inline Setting<float> aspectCorrection{"aspect-correction", 1};
|
||||
static inline Setting<std::string> renderDriver{"renderer-driver", ""};
|
||||
static inline Setting<bool> alternativeRendering{"alternative-rendering", false};
|
||||
static inline Setting<std::string> renderDriver{"renderer-driver", ""};
|
||||
static inline Setting<bool> alternativeRendering{"alternative-rendering", false};
|
||||
static inline Setting<bool> fastScale{"fast-render-scale", false};
|
||||
static inline Setting<int> usbQueue{"async-usb-calls", 16};
|
||||
static inline Setting<int> usbTransferSize{"usb-buffer-size", 20480};
|
||||
static inline Setting<int> usbBuffer{"usb-buffer", 64};
|
||||
static inline Setting<int> videoQueue{"video-buffer-size", 64};
|
||||
static inline Setting<int> audioQueue{"audio-buffer-size", 64};
|
||||
static inline Setting<int> usbQueue{"async-usb-calls", 32};
|
||||
static inline Setting<int> usbTransferSize{"usb-buffer-size", 2048};
|
||||
static inline Setting<int> usbBuffer{"usb-buffer", 128};
|
||||
static inline Setting<int> audioDelay{"audio-buffer-wait", 2};
|
||||
static inline Setting<int> audioDelayCall{"audio-buffer-wait-call", 6};
|
||||
static inline Setting<float> audioFade{"audio-fade", 0.3};
|
||||
|
||||
@@ -104,7 +104,7 @@ public:
|
||||
_lock.notify_all();
|
||||
}
|
||||
|
||||
uint16_t count() { return _count.load(std::memory_order_acquire); }
|
||||
uint16_t count() const { return _count.load(std::memory_order_acquire); }
|
||||
|
||||
private:
|
||||
uint16_t _size;
|
||||
|
||||
Reference in New Issue
Block a user