Russian Qt Forum

Qt => Общие вопросы => Тема начата: kuzulis от Октябрь 19, 2014, 19:06



Название: С++ парсинг бинарных структур из байтового массива
Отправлено: kuzulis от Октябрь 19, 2014, 19:06
Всем доброго времени.

Какой самый "лучший" способ проверки принадлежности того или иного принятого QByteArray к тому или иному типу структуры?

Например, принимаю пакет/структуру с полями типа:

Цитировать

opcode=2 byte, len=1 byte, params = x bytes

где,
opcode - код операции;
len - размер поля params;
params - вариабельное поле, которое имеет разную длину и содержимое в зависимости от opcode


Я пока знаю два метода:

1) "Конструирования" заданной структуры из QByteArray
Код
C++ (Qt)
struct BasePacket
{
   enum { OpCodeOffset = 0, LenOffset = 2, ParamsOffset = 3; }
 
   BasePacket (QByteArray &packet): packet(packet)
   {
   }
 
   quint16 opCode() const
   {
       quint16 opcode = 0;
       // read the opcode field from packet from OpCodeOffset
       return opcode;
   }
 
   QByteArray packet;
};
 
struct FooPacket : public BasePacket
{
   enum { Foo_OpCode = 0x1234, Foo_PacketSize = xxx };
 
   bool isValid() const
   {
       if (packet.size() != Foo_PacketSize)
           return false;
       if (packet.at(LenOffset ) != (packet.size() - ParamsOffset))
           return false;
       if (opCode() != Foo_OpCode)
           return false;
       return true;
   }
};
 
struct BarPacket : public BasePacket
{
   enum { Bar_OpCode = 0x5678, Bar_PacketSize = yyy };
 
   bool isValid() const
   {
       if (packet.size() != Bar_PacketSize)
           return false;
       if (packet.at(LenOffset ) != (packet.size() - ParamsOffset))
           return false;
       if (opCode() != Bar_OpCode)
           return false;
       return true;
   }
};
 
 

Тогда в обработчиках для каждого пакета можно сделать проверки:
Код
C++ (Qt)
class BasePacketHandler
{
public:
   virtual bool canHandle(const QByteArray &packet) const = 0;
}
 
class FooPacketHandler : public BasePacketHandler
{
public:
   bool canHandle(const QByteArray &packet) const
   {
       return FooPacket(packet).isValid();
   }
}
 
class BarPacketHandler : public BasePacketHandler
{
public:
   bool canHandle(const QByteArray &packet) const
   {
       return BarPacket(packet).isValid();
   }
}
 

2) Создания набора статических методов:

Код
C++ (Qt)
struct BasePacket
{
   enum { OpCodeOffset = 0, LenOffset = 2, ParamsOffset = 3; }
 
   BasePacket (QByteArray &packet): packet(packet)
   {
   }
 
   static quint16 opCode(QByteArray &packet) {
       quint16 opcode = 0;
       // read the opcode field from packet from OpCodeOffset
       return opcode;
   }
 
   QByteArray packet;
};
 
struct FooPacket : public BasePacket
{
   enum { Foo_OpCode = 0x1234, Foo_PacketSize = xxx };
 
   static bool isValid(QByteArray &packet)
   {
       if (packet.size() != Foo_PacketSize)
           return false;
       if (packet.at(LenOffset ) != (packet.size() - ParamsOffset))
           return false;
       if (opCode(packet) != Foo_OpCode)
           return false;
       return true;
   }
};
 
struct BarPacket : public BasePacket
{
   enum { Bar_OpCode = 0x5678, Bar_PacketSize = yyy };
 
   static bool isValid(QByteArray &packet)
   {
       if (packet.size() != Bar_PacketSize)
           return false;
       if (packet.at(LenOffset ) != (packet.size() - ParamsOffset))
           return false;
       if (opCode(packet) != Bar_OpCode)
           return false;
       return true;
   }
};
 

Тогда в обработчиках для каждого пакета можно сделать проверки:
Код
C++ (Qt)
class BasePacketHandler
{
public:
   virtual bool canHandle(const QByteArray &packet) const = 0;
};
 
class FooPacketHandler : public BasePacketHandler
{
public:
   bool canHandle(const QByteArray &packet) const
   {
       return FooPacket::isValid(packet);
   }
};
 
class BarPacketHandler : public BasePacketHandler
{
public:
   bool canHandle(const QByteArray &packet) const
   {
       return BarPacket::isValid(packet);
   }
};
 

Что предпочтительнее? или есть иной простой способ как сделать желаемое ? :)

ЗЫ: естественно интересуют всякие там паттерны; чисто Си-шное решение с switch/case  и прочим не предлагать.

ЗЫЗЫ: вроде как использование статических методов не дает оверхеда (нет копирования), но с другой стороны метод isValid() можно сделать inline...

ЗЫЗЫЗЫ: уже ломаю мозг неделю :)


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: vulko от Октябрь 20, 2014, 09:38
Зачем изобретаешь велосипед?

Цитировать
opcode - код операции;
len - размер поля params;
params - вариабельное поле, которое имеет разную длину и содержимое в зависимости от opcode

len тут совершенно не нужен. по opcode можно точно определить размер структуры упакованной в массив байт.

Зачем такая дикая имплементация со структурой внутри которой QByteArray не понимаю.

Мой совет таков, не городи фигню и сделай switch/case по opcode.
Причины просты:
1. это работает и работает быстро
2. не нужно плодить сто миллионов классов для каждого типа сообщения
3. меньше писать кода, меньше потенциальных ошибок, меньше проблем

Ну и наконец в каком-то месте все равно будет switch/case. Ну или if {} else if {} else if...


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Igors от Октябрь 20, 2014, 09:39
Код
C++ (Qt)
template <qint32 packID, qint32 packSize>
struct CPacket {
CPacket( QByteArray & ba ) : mError(false), mBA(ba)
{
  int id = 0, size = 0;
  QDataStream ds(ba);
  ds >> id >> size;
  mError = (id != packID) ||  (size != packSize) || (mBA.size() - sizeof(qint32) * 2 != packSize);
}
 
bool mError;
QByteArray & mBA;
};
 
int main(int argc, char *argv[])
{
typedef CPacket <'PAC1', 32> MyPacket;
QByteArray ba;
MyPacket p(ba);
if (!p.mError) return 1;
return 0;
}


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Igors от Октябрь 20, 2014, 11:02
Дальше насаживаем этот template на виртуальный базовый

Код
C++ (Qt)
template <qint32 packID, qint32 packSize>
struct CPacket : public BasePacket {
...
// BasePacket virtuals
virtual int OpCode( void ) const { return packID; }
virtual int DataSize( void ) const { return packSize; }
...
};
 
Создаем хвабрику..

Код
C++ (Qt)
typedef std::map <int, BasePacket *> TPacketMap;
TPacketMap mPacketMap;
 
void Add2Factory( BasePacket * p )
{
mPacketMap[p->OpCode()] = p;
}
 
Add2Factory(new CPacket <'PAC1', 32> ());
Add2Factory(new CPacket <'PAC2', 15> ());
Add2Factory(new CPacket <'PAC3', 20> ());
...
Использование: по коду операции находим нужный класс
Код
C++ (Qt)
BasePacket * Lookup( int opCode )
{
TPacketMap::iterator it = mPacketMap.find(opCode);
return (it == mPacketMap.end()) ? 0 : it->second;
}
Т.е. классов все равно "много", но их не надо размазывать каждый, один раз на создании и все


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: kuzulis от Октябрь 20, 2014, 11:12
2 vulko,

Цитата: vulko
len тут совершенно не нужен. по opcode можно точно определить размер структуры упакованной в массив байт.

Не, это не от меня зависит. Этот формат описан в спецификации.

Цитата: vulko
Зачем такая дикая имплементация со структурой внутри которой QByteArray не понимаю.

Чтобы можно было потом сделать:

Код
C++ (Qt)
 
QByteArray data1; // приняли пакет FooPacket
FooPacket foo(data1);
QString name = foo.name();
 
QByteArray data2; // приняли пакет BarPacket
BarPacket bar(data2);
quint8 type = bar.type();
Address address = bar.address();
 
 

Дело в том, что у каждого пакета разное содержимое поля params.
Например, у пакета FooPacket это поле содержит параметр Name; у ракета BarPacket два параметра: тип и адрес и т.п.

Т.е. эти все разбиения на разные классы для того, чтобы можно было "тупо" наложить класс на пакет, проверить его
(пакета валидность, т.е. тот факт, что пакет имеет именно текущий тип), а потом изъять (распарсить) необходимые параметры из пакета.

Т.е. идея в том, что только конкретные классы пакетов знают как извлекать свои параметры из RAW пакета. :)


2 Igors,

Я не могу использовать  QDataStream, т.к. принимаю пакеты не из Qt-шного приложения. Но должен их парсить методами Qt. :)

Цитата: Igors
Т.е. классов все равно "много", но их не надо размазывать каждый, один раз на создании и все

В смысле? Мне не нужно ничего создавать. Мне нужно просто "распарсить" принятый пакет (QByteArray) и в зависимости от его принадлежности
извлечь необходимые параметры. :)

Тут по идее должна быть список с конкретными обработчиками, каждый из которых "реагирует" только на определенный пакет.

Код
C++ (Qt)
 
class BasePacketHandler
{
public:
   virtual bool canHandle(const QByteArray &packet) const = 0;
};
 
class FooPacketHandler : public BasePacketHandler
{
public:
   bool canHandle(const QByteArray &packet) const
   {
       // как тут правильнее парсить?
       return true/false;
   }
};
 
class BarPacketHandler : public BasePacketHandler
{
public:
   bool canHandle(const QByteArray &packet) const
   {
       // как тут правильнее парсить?
       return true/false;
   }
};
 
QList<BasePacketHandler *> handlers = QList<BasePacketHandler *>() << new FooPacketHandler << new BarPacketHandler;
 
void Manager::handleIncomingPacket(const QByteArray &packet)
{
   foreach (BasePacketHandler *handler, handlers) {
       if (handler->canHandle(packet) {
           // do something
       }
   }
}
 
 

Но тут главный вопрос: как красиво парсить QByteArray и приводить к определенному типу чтобы можно было
легко изъять нужные параметры из QByteArray.


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Fregloin от Октябрь 20, 2014, 12:03
С таким сталкивался при реализации чтения/записи в universal binary json.
Можете посмотреть как это сделано у меня.


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Igors от Октябрь 20, 2014, 12:26
Код
C++ (Qt)
class BasePacketHandler
{
public:
   virtual bool canHandle(const QByteArray &packet) const = 0;
};
 
class FooPacketHandler : public BasePacketHandler
{
public:
   bool canHandle(const QByteArray &packet) const
   {
       // как тут правильнее парсить?
       return true/false;
   }
};
 
Полагаем что canHandle решает "мой пакет или нет", а валидный ли он - уже др вопрос. Тогда сваливаем этот метод в базовый класс
Код
C++ (Qt)
class BasePacketHandler
{
public:
   virtual bool canHandle(const QByteArray &packet) const
   {
       return (packet.size() >= 3) && (*(unsigned short *) &packet[0] == OpCode());
   }
};
 
А OpCode - подскоком из темплейта (см выше)


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: vulko от Октябрь 20, 2014, 15:33
Чтобы можно было потом сделать:

Код
C++ (Qt)
QByteArray data1; // приняли пакет FooPacket
FooPacket foo(data1);
QString name = foo.name();
 
QByteArray data2; // приняли пакет BarPacket
BarPacket bar(data2);
quint8 type = bar.type();
Address address = bar.address();
 

Дело в том, что у каждого пакета разное содержимое поля params.
Например, у пакета FooPacket это поле содержит параметр Name; у ракета BarPacket два параметра: тип и адрес и т.п.

Т.е. эти все разбиения на разные классы для того, чтобы можно было "тупо" наложить класс на пакет, проверить его
(пакета валидность, т.е. тот факт, что пакет имеет именно текущий тип), а потом изъять (распарсить) необходимые параметры из пакета.

Т.е. идея в том, что только конкретные классы пакетов знают как извлекать свои параметры из RAW пакета. :)

Я понял в чем идея. Но я не вижу ни одной причины чтобы заниматься такой фигней...
Проверить пакет на валидность можно ведь и без излишней ООПтимизации.
Ну есть у вас 20 классов, по одному на каждый тип пакета. Один фиг каждый из них при проверке на валидность проверять будет тупо ID пакета (опкод он у вас называется).
При этом у вас куча однообразного кода, который будет занимать много места.

Да, типо ООП. Да, типа шаблоны применил.
Только профита никакого. Если нужно красивое внешнее АПИ, не проблема его сделать. Но зачем изобретать такой велосипед для простой задачи, которая где-то внутри проекта находится, я не понимаю.

Это ведь не просто много времени на копи паст и переделывание классов наследников - это потенциальный гемор при тестировании.


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: vulko от Октябрь 20, 2014, 15:37
Но тут главный вопрос: как красиво парсить QByteArray и приводить к определенному типу чтобы можно было
легко изъять нужные параметры из QByteArray.

QByteArray data;
QDataStream in(&data, QDataStream::ReadOnly);
int opcode;
int len;
char* data;
in >> opcode;
in >> len;
in.readRawData(data, len)); (или readRawBytes, не помню точно...)

далее MyStruct *struct = (MyStruct*) data;
либо парсить поочередно, в зависимости от того кто и как писал данные в QByteArray.


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: kuzulis от Октябрь 20, 2014, 16:16
2 vulko,

Да елки, ну не применить в моем случае QDataStream, ну никак. :)

Цитировать
далее MyStruct *struct = (MyStruct*) data;

Тоже не вариант: нужно делать "pragma pack" и прочую фигню. Гемор еще больший.

Цитировать
При этом у вас куча однообразного кода, который будет занимать много места.

Зато каждый на своем месте :).

2 Igors,

Эмм.. я привел только маленькую часть задачи.. Поэтому наверное оно как-то кажется непонятным..

На самом деле оно гораздо шире: имеются пакеты - команды (которые отправляются в у-во), и пакеты - события (которые принимаются из у-ва).
На каждую из команд (запрос), приходит соответствующий евент (ответ). Также евенты от у-ва могут быть и спонтанными.

То что идет привязка к OpCode - это просто так получилось из частного случая (просто для примера).
В реальности я не могу привязаться к OpCode т.к. пакет содержит еще дополнительные поля.. т.е.:

Команда: [opcode = 2byte] + [len = 1 byte] + [params = xxx bytes]
Евент:     [eventcode = 1 byte] + [len = 1 byte] + [params = xxx bytes]

При этом, event поле params будет содержать: [num = 1 byte] + [opcode = 2 byte] + [return params = xxx bytes]

далее, содержимое под-поля [return params] зависит от opcode и может иметь разный тип данных.


Например,

команда Reset:

Req  : 030c00 - зарос
Resp : 0e0401030c00 - ответ

где: 030c00:

- 030c - opcode
- 00 - len (нет параметров)

где: 0e0401030c00

- 0e - код евента что это евент завершения команды
- 04 - len длина поля параметры
- 01030c00 - параметры:
    - 01 - некий код (неважно)
    - 030c - opcod команды на которую этот евент
    - 00 - статус выполнения команды

т.е. сейчас реализовано по такому типу:

Код
C++ (Qt)
const ResetCmd cmd;
Reply *reply = manager->exec(cmd);
connect(reply, SIGNAL(finished(), this, SLOT(onResetComplete()));
 
Foo::onResetComplete()
{
   Reply *reply = qobject_cast<Reply *>(sender());
 
   QByteArray req = reply->request(); // RAW команда которая была послана
   QByteArray resp = reply->response(); // RAW евент (ответ), который был получен на команду
 
   // естественно, что этот слот - это уже готовый (обработанный результат), который обрабатывает Manager.
   // и в этом Manager вся логика и обработчики
 
   // но теперь я хочу получить параметры ответа resp. сразу и без проблем, т.к. я точно знаю его тип
   ResetCompleteEvent ev(resp);
 
   quint status = ev.status();
   QString name = ev.name();
 
   // далее могу обработать status (или иные параметры) как мне надо
   if (status == xxx) {
       // команда успешна
   } else if (status == yyy) {
       // команда сфейлилась
   } else ...
 
   emit deviceDiscovered(name);
   // и прочее
 
}
 

т.е. по-любому нужно плодить классы для каждой из команд/события. Может быть, ДА, где -то применить шаблоны.

т.е. не прокатит просто так switch/case и прочее..

PS: В общем, портирую Bluez на Windows (HCI стек) для своего проекта :D


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Igors от Октябрь 20, 2014, 18:05
т.е. по-любому нужно плодить классы для каждой из команд/события. Может быть, ДА, где -то применить шаблоны.
Да понятно, случай-то нередкий. Создается по экземпляру каждого обработчика и складывается в контейнер. Когда пришел QByteArray - перебираем все обработчики контейнера, какой-то ответит "да, это мой пакет". Если никто не ответит - ну инвалидный пакет. Да, каждый обработчик свой класс, ничего страшного.

То есть все сводится к тому как удобно реализовать эти классы. Напр создаем базовый класс с чистыми виртуалами и наследуемся. Это вполне нормально, но общности между различными обработчиками может оказаться мало. Напр поместив поле OpCode в базовый класс - что тогда с пакетами Event?

Поэтому делают "просто классы" - с темплейтами/наследованием или без, как получится. Структуры данных все равно надо где-то определять. А потом пристегивают общую виртуальную базу (иначе не выскочить на нужный обработчик). Напр

где: 0e0401030c00

- 0e - код евента что это евент завершения команды
- 04 - len длина поля параметры
- 01030c00 - параметры:
    - 01 - некий код (неважно)
    - 030c - opcod команды на которую этот евент
    - 00 - статус выполнения команды
Ну и рисуете такую структуру, удастся сэкономить на template или наследованием на подобной - хорошо, нет - переживем. Создаете методы чтения/записи из QByteArray. А дальше добавляете базовые виртуалы, напр canHandle который просто вызывает метод чтения и возвращает код ошибки. Обычно удается все на хедерах (без .cpp файлов)


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: kuzulis от Октябрь 20, 2014, 19:20
Цитата: Igors
Да понятно, случай-то нередкий. Создается по экземпляру каждого обработчика и складывается в контейнер. Когда пришел QByteArray - перебираем все обработчики контейнера, какой-то ответит "да, это мой пакет". Если никто не ответит - ну инвалидный пакет. Да, каждый обработчик свой класс, ничего страшного.

Ага.

Цитата: Igors
То есть все сводится к тому как удобно реализовать эти классы. Напр создаем базовый класс с чистыми виртуалами и наследуемся. Это вполне нормально, но общности между различными обработчиками может оказаться мало. Напр поместив поле OpCode в базовый класс - что тогда с пакетами Event?
 

Сейчас обработчики имеют по два абстрактных метода:

Код
C++ (Qt)
class HciReplyHandler
{
public:
   virtual bool canHandleRequest(const QByteArray &request) const = 0;
   virtual bool canHandleResponse(const QByteArray &response) const = 0;
};
 

метод exec() менеджера такой:

Код
C++ (Qt)
HciReply *HciAccessManager::execute(const QByteArray &request)
{
   foreach (HciReplyHandler *handler, m_handlers) { // перебираем зарегистрированные обработчики
       Q_ASSERT(handler);
 
       if (!handler->canHandleRequest(request)) // проверяем - а чей, собственно запрос?
           continue;
 
       HciReply *reply = new HciReply(handler->replyType(), this);
       if (reply->type() == HciReply::Pending) {
           reply->setRequest(request);
           m_pendingReplies.insert(reply, handler); // засовываем Reply и его обработчик в хеш
           m_transport->writeCommandPacket(request);
       }
 
       return reply;
   }
   return 0;
}
 

Где m_pendingReplies - это хеш, где для каждого созданного Reply присваивается соответствующий ему обработчик.
Для того чтобы в слоте обработки (см. далее), можно было найти Reply по его обработчику (ничего лучше я пка не придумал)..

Слот отбаботки ответа менеджера такой (т.е. этот слот дергается по приходу события):

Код
C++ (Qt)
void HciAccessManager::response(const QByteArray &packet)
{
   foreach (HciReplyHandler *handler, m_handlers) { // перебираем обработчики и ищем подходяший
       Q_ASSERT(handler);
 
       if (!handler->canHandleResponse(packet)) // а чей, собственно ответ?
           continue;
 
       // ищем Reply по обработчику (тут, конечно, косячно все т.к. к одному обработчику могут принадлежать несколько Reply)
       // но в данном случае - пофик, т.к. оно найдет первый попавшийся и нам этого достаточно, т.к. без разницы :)
       HciReply *reply = m_pendingReplies.key(handler);
       Q_ASSERT(reply);
 
       reply->setResponse(packet);
       if (handler->replyType() == HciReply::Pending)
           m_pendingReplies.take(reply);
 
       finalizeEventReply(reply);
       return;
   }
}
 

Реализация команды/события/обработчика Reset для примера такая:

Код
C++ (Qt)
HciResetCommand::HciResetCommand()
   : HciCommand(HciCommand::ControllerBasebandGroup, HciCommand::ResetCode)
{
   packet.resize(packet.size() + TotalParametersLength);
   setTotalParametersLength(TotalParametersLength);
}
 
HciResetCommand::HciResetCommand(const QByteArray &packet)
   : HciCommand(packet)
{
}
 
bool HciResetCommand::isValid() const
{
   return isComplete()
           && (opCode() == HciCommand::opCode(HciCommand::ControllerBasebandGroup, HciCommand::ResetCode));
}
 
HciResetCommandCompleteEvent::HciResetCommandCompleteEvent(const QByteArray &packet)
   : HciCommandCompleteEvent(packet)
{
}
 
quint8 HciResetCommandCompleteEvent::status() const
{
   return packet.at(parametersOffset() + ReturnParametersOffset + StatusOffset);
}
 
bool HciResetCommandCompleteEvent::isValid() const
{
   return isComplete()
           && (packet.size() == TotalPackeSize)
           && (opCode() == HciCommand::opCode(HciCommand::ControllerBasebandGroup, HciCommand::ResetCode));
}
 
bool HciResetReplyHandler::canHandleRequest(const QByteArray &request) const
{
   return HciResetCommand(request).isValid();
}
 
bool HciResetReplyHandler::canHandleResponse(const QByteArray &response) const
{
   return HciResetCommandCompleteEvent(response).isValid();
}
 

Блин, больше ничего толковее я не придумал. :(


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: vulko от Октябрь 21, 2014, 08:17
2 vulko,

Да елки, ну не применить в моем случае QDataStream, ну никак. :)

Удивительно, что QByteArray есть, а QDataStream нельзя применить...)))
Это всего лишь удобный wrapper для QByteArray (и не только), созданный для удобной сериализации/десереализации данных.


Цитировать
далее MyStruct *struct = (MyStruct*) data;

Тоже не вариант: нужно делать "pragma pack" и прочую фигню. Гемор еще больший.

Совсем не обязательно. Зависит от того как паковали.

А даже если нужно будь изменить паковку структуры, то никакого гемора нет.
Гемор это как раз плодить никому не нужные классы в низкоуровневом АПИ.

Код:
#pragma pack(push, 1)
struct MyStruct
{
    // ...
};
#pragma pack(pop)


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Igors от Октябрь 21, 2014, 09:16
По поводу хеша. Чаще не берут обработчик из контейнера, а делают его копию, т.к. он может быть задействован 2 и более раз. Тогда мапируйтесть как хотите - можно и 2 хеша, прямой и обратный

Насчет размазывания классов обработчиков. Ну да, выглядит неск занудно, но вполне цивильно. Все же не лучше ли выделить Command и Event отдельно? Ну будет 2 контейнера, ничего плохого. Зато многие операции можно слить в базовый класс(ы) и схлопнуть текст (хоть с виртуалами, хоть с темплейтпми).


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: kuzulis от Октябрь 21, 2014, 11:09
2 vulko,

Цитата: vulko
Удивительно, что QByteArray есть, а QDataStream нельзя применить...)))
Это всего лишь удобный wrapper для QByteArray (и не только), созданный для удобной сериализации/десереализации данных.

Ничего удивительного, почитайте для чего нужен QDataStream и когда можно, а когда нельзя его использовать.

Цитата: vulko
Совсем не обязательно. Зависит от того как паковали.

Кого как паковали? Работа идет с массивами данных / пакетами. Никого никуда не паковали.

Цитата: vulko
Гемор это как раз плодить никому не нужные классы в низкоуровневом АПИ.
Код:
#pragma pack(push, 1)
struct MyStruct
{
    // ...
};
#pragma pack(pop)

А вот это уже как раз-таки гемор.


2 Igors ,

Цитата: Igors
По поводу хеша. Чаще не берут обработчик из контейнера, а делают его копию, т.к. он может быть задействован 2 и более раз. Тогда мапируйтесть как хотите - можно и 2 хеша, прямой и обратный

Тогда уж слишком много new/delete да и дополнительный код нужен. Сейчас также любой обработчик можно задействовать сколько угодно раз.
Просто есть небольшой нюанс в обратном нахождении нужного Reply к которому относится Response (т.к. при прямом нахождении сопоставлении Request-а и Reply нет проблем - там Reply создается).

Цитировать
Все же не лучше ли выделить Command и Event отдельно? Ну будет 2 контейнера, ничего плохого. Зато многие операции можно слить в базовый класс(ы) и схлопнуть текст (хоть с виртуалами, хоть с темплейтпми).

Эмм.. так Command и Event и так отдельно. Или что имеется ввиду?


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Igors от Октябрь 21, 2014, 11:44
Тогда уж слишком много new/delete да и дополнительный код нужен. Сейчас также любой обработчик можно задействовать сколько угодно раз.
При таком подходе обработчик не может ничего хранить в себе. Если известен момент "окончания сессии" - то просто грохнуть "контейнер копий". Ну здесь конечно по задаче

Эмм.. так Command и Event и так отдельно. Или что имеется ввиду?
Сейчас они наследуются от одного класса с виртуалами, сделать от разных. Тогда можно иметь больше общих методов в базовых классах. И контейнеры обработчиков разные, тогда можно их мапами


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: kuzulis от Октябрь 21, 2014, 13:07
Цитата:  Igors
Сейчас они наследуются от одного класса с виртуалами, сделать от разных. Тогда можно иметь больше общих методов в базовых классах. И контейнеры обработчиков разные, тогда можно их мапами

Нет, HciCommand и HciEvent  - это два разных базового класса. От HciCommand наследуется ResetCommand, а от HciEvent  наследуется ResetCommandCompleteEvent
 (если я правильно понял твой комментарий).

Есть также две дополнителных сущности:

* Reply - что-то типа хендла операции, к которому можно приконнектится и прочее (по аналогии с QNetworkReply).
Этот Reply наследуется от QObject и содержит QByteArray как от request (HciCommand) так и от response (HciEvent).

* ReplyHandler - это базовый обработчик с виртуалами которые должны обрабатывать (проверять, что могут это делать) как исходяшие запросы так
и входяшие ответы. Для каджого типа запроса/ответа - свой обработчик, который принимает только свои пакеты.

Т.е. алгоритм такой:

1. Создаем команду (например Reset) и отправляем ее в менеджер (делаем exec(cmd))

2. Менеджер ищет нужный обработчик, который успешно вернет canHandleRequest(cmd). Этот обработчик также будет обрабатывать и response (когда оно придет).
Далее, создается Reply в который засовываем команду (на хранение), а далее засоваваем этот Reply с указателем на текущий обработчик в контейнер (QHash<Reply *. Handler *>).
Это надо для того чтобы потом (когда придет response и сработает тот-же обработчик) найти Reply.  

3. Приходит какой-то response, менеджер перечисляет обработчики и находит тот, который успешно делает canHandleResponse(resp).
Далее, в контейнере (QHash<Reply *. Handler *>) ищется первое совпадение с Handler* и возвращается соответствующий ему Reply.

4. Далее в Reply запихивается response, Reply изымается из контейнера (если надо, это не важно), и финализируется (т.е. емитим сигнал finished и прочее - неважно).

5. В методах конкретных обработчиков для проверки (мой пакет / чужой пакет) просто на сырой QByteArray накладывается соответствующий класс HciCommand/HciEvent.
Например, в ResetReplyHandler это будет проверка ResetCommand(data).isValid() и ResetCommandCompleteEvent(data).isValid() (для canHandleRequest/canHandleResponse соответственно).

Т.е. изначальный мой вопрос был как корректнее можно унифицировать эту проверку, т.к. можно ее также делать с помощью статических методов: ResetCommand::isValid(data) / ResetCommandCompleteEvent::isValid(data) вместо "наложения" класса на QByteArray.

Хотя, также пришлось бы делать и другие методы (для извлечения соотв. свойств из QByteArray) для классов - наследников HciCommand и HciEvent также статическими:

Код
C++ (Qt)
static bool FooCommand::isValid(const QByteArray &data);
static QString FooCommand::name(const QByteArray &data);
 
static bool BarCommand::isValid(const QByteArray &data);
static Type BarCommand::type(const QByteArray &data);
 

6. Возможно, что и Reply можно сделать виртуальным, а вместо хранения QByteArray от request/response хранить указатели на конкретные классы от HciCommand / HciEvent ?
Код
C++ (Qt)
class ResetReply : public Reply
{
   ResetCommand *request;
   ResetCommandCompleteEvent *response;
}
 

Возможно, имплементацию получения параметров от Request/Response можно сделать напрямую в каждом конкретном Reply вместо HciCommand / HciEvent
 (где вместо указателей на HciCommand / HciEvent просто хранить принятые сырые массивы).
Код
C++ (Qt)
class FooReply : public Reply
{
   QByteArray request;
   QByteArray response;
 
   QString name() const { return response... }; // извлекаем нужный параметр из какого-то там поля из response
}
 
class BarReply : public Reply
{
   QByteArray request;
   QByteArray response;
 
   QString type() const { return response... }; // извлекаем нужный параметр из какого-то там поля из response
}
 

В этом случае когда Reply завершится достаточно просто скастить его к нужному типу (к FooReply  или BarReply ) и дернуть желаемые методы получения того или иного параметра..
Код
C++ (Qt)
MyClass::onFooComplete(Reply *reply)
{
   // я точно знаю, что слот onFooComplete только для FooReply, поэтому могу кастить
   FooReply *reply = qobject_cast<FooReply *>(reply);
 
   QString name = reply->name();
}
 

вместо того, что есть сейчас:
Код
C++ (Qt)
MyClass::onFooComplete(Reply *reply)
{
   // я точно знаю, что слот onFooComplete только для FooReply, поэтому могу наложить FooCompleteEvent на response
   QByteArray response = reply->response();
 
   FooCompleteEvent ev(response);
 
   QString name = ev.name();
}
 

Но все-равно нужно каким-то образом реализовывать методы canHandleRequest/canHandleResponse для обработчиков..

В общем.. такая вот засада.. Много вопросов, а что выбрать в итоге - непонятно. :)


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: vulko от Октябрь 21, 2014, 13:50
1. Создаем команду (например Reset) и отправляем ее в менеджер (делаем exec(cmd))

Такое ощущение что ты недавно прочитал какую-то статью про шаблоны, и теперь хочешь шаблонизировать свой код...
Ну бог с ним, хочется тебе (new ResetCommand).exec() или что-то подобное, почему бы и нет. В коде конечно это выглядит очень ООПэшненько и современно :)


2. Менеджер ищет нужный обработчик, который успешно вернет canHandleRequest(cmd). Этот обработчик также будет обрабатывать и response (когда оно придет).
Разве менеджер не может выполнить команду? Впрочем если это топ-менеджер, ему нельзя самому выполнять команды. Нужно обязательно найти того кто его сделает.
Гораздо логичнее было бы использовать менеджер не для поиска нужного обработчика, а для непосредственно обработки команды. Обозвать его RequestManager. А вот обработчиком можно сделать ResponceManager, который не будет пытаться брать на себя все обязанности и займется конкретно ответом.


Приходит какой-то response, менеджер перечисляет обработчики и находит тот, который успешно делает canHandleResponse(resp).
Далее, в контейнере (QHash<Reply *. Handler *>) ищется первое совпадение с Handler* и возвращается соответствующий ему Reply.
По факту тот же switch-case.
Нахрена тут хэш мэп?! Менеджер же уже определил кто успешно делает canHandleResponse(resp).


Далее в Reply запихивается response, Reply изымается из контейнера (если надо, это не важно), и финализируется (т.е. емитим сигнал finished и прочее - неважно).
:o


В методах конкретных обработчиков для проверки (мой пакет / чужой пакет) просто на сырой QByteArray накладывается соответствующий класс HciCommand/HciEvent.
Например, в ResetReplyHandler это будет проверка ResetCommand(data).isValid() и ResetCommandCompleteEvent(data).isValid() (для canHandleRequest/canHandleResponse соответственно).
"Вы повысили скилл в индусский кодинг на +5" :)
А если серьезно, то гораздо логичнее определить тип данных в массиве сперва, а потом уже выбирать нужный класс для респонса или реквеста и создавать из массива объект.
Логичнее это, просто хотя бы потому, что если сторонний разработчик посмотрит на такой код, он вообще ничего не поймет что за фигня тут происходит.
Не по фен шую это, перебирать типы объектов, прежде чем их создавать. Нужно сперва определить какой тип объекта нужен, а потом его создавать.


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: kuzulis от Октябрь 21, 2014, 14:32
Цитата: vulko
Такое ощущение что ты недавно прочитал какую-то статью про шаблоны, и теперь хочешь шаблонизировать свой код...
Ну бог с ним, хочется тебе (new ResetCommand).exec() или что-то подобное, почему бы и нет. В коде конечно это выглядит очень ООПэшненько и современно

Не, ну а в чем проблема то:?

Код
C++ (Qt)
MyClass::sendFooCommand()
{
   FooCommand cmd(param1, param2, param3);
   Reply *reply = manager->exec(cmd);
   connect(reply, SIGNAL(finished()), this, SLOT(onFooCommandFinished()));
}
 
MyClass::onFooCommandFinished()
{
   Reply *reply = qobject_cast<Reply *>(sender());
 
   FooCompleteEvent ev(reply->response());
 
   if (ev.status() == Success) {
       // do something
   }
 
   QString someName = ev.name() == Success) {
       // do something
   }
 
   reply->deleteLater();
}
 
MyClass::sendBarCommand()
{
   BarCommand cmd(param11, param22, param33);
   Reply *reply = manager->exec(cmd);
   connect(reply, SIGNAL(finished()), this, SLOT(onBarCommandFinished()));
}
 
MyClass::onBarCommandFinished()
{
   Reply *reply = qobject_cast<Reply *>(sender());
 
   BarCompleteEvent ev(reply->response());
 
   if (ev.status() == Success) {
       // do something
   }
 
   Type someType = ev.type();
   // do something
 
   reply->deleteLater();
}
 

предлагаешь тут команды вручную и массива клепать?  ;D
Код
C++ (Qt)
MyClass::sendBarCommand()
{
   QByteArray cmd;
   cmd.append(bar_header);
   cmd.append(bar_body);
   cmd.append(bar_tail);
 
   requestManager->write(cmd);
   // И? что дальше?
}
 


Цитата: vulko
Разве менеджер не может выполнить команду? Впрочем если это топ-менеджер, ему нельзя самому выполнять команды. Нужно обязательно найти того кто его сделает.

Ухаха, обхохочешься.
Он и выполняет: шлет ее в у-во. Но также он ищет обработчика чтобы привязать его к Reply чтобы потом обработать ответ и запихнуть его в нужный Reply.

Цитата: vulko
По факту тот же switch-case.

Тот же да не тот же.

Цитата: vulko
Нахрена тут хэш мэп?!

Да пофик, просто для примера.

Цитата: vulko
Менеджер же уже определил кто успешно делает canHandleResponse(resp).

Да, он нажел указатель того обработчика который это сделает. Но теперь нужно найти тот Response которому нужно запихнуть результат.
Это по дизайну.

Цитата: kuzulis
Далее в Reply запихивается response, Reply изымается из контейнера (если надо, это не важно), и финализируется (т.е. емитим сигнал finished и прочее - неважно).

Что вызвало такое удивление?

Цитата: vulko
А если серьезно, то гораздо логичнее определить тип данных в массиве сперва, а потом уже выбирать нужный класс для респонса или реквеста и создавать из массива объект.

Неужели? Каким же образом определять "тип данных" ?

Цитата: vulko
Логичнее это, просто хотя бы потому, что если сторонний разработчик посмотрит на такой код, он вообще ничего не поймет что за фигня тут происходит.
Не по фен шую это, перебирать типы объектов, прежде чем их создавать. Нужно сперва определить какой тип объекта нужен, а потом его создавать.

Никто не создает объектов. Ищется обработчик для соответствующего request/response и "биндится" к Reply.
И все как раз-таки и понятно, в отличии от switch/case


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: vulko от Октябрь 21, 2014, 15:03
Цитировать
предлагаешь тут команды вручную и массива клепать?

нет, я предлагаю начать с того чтобы подумать над нормальной архитектурой.

то что ты пытаешься сделать, больше похоже на попытку впихнуть побольше шаблонов и ненужных контейнеров.

архитектура должна быть предельно проста, понятна и удовлетворять задаче.
и вот уже "вокруг" архитектуры, когда она ясна, можно соорудить какие-то шаблоны, контейнеры и т.п.


Цитировать
Ухаха, обхохочешься.
Он и выполняет: шлет ее в у-во. Но также он ищет обработчика чтобы привязать его к Reply чтобы потом обработать ответ и запихнуть его в нужный Reply.
Зачем привязывать обработчика к Reply? Какой отношение к Reply-ю имеет обработчик? Это обработчик на выходе должен выдавать объект типа Reply, а не создавать сперва Reply, на кой то хрен привязывать к нему обработчика, который потом его инициализирует.
Или ты прежде чем в туалет (отдельный от ванной) пойти, берешь сперва мыло в ванной, чтобы потом пойти снова в ванную и помыть там руки?


Цитировать
Неужели? Каким же образом определять "тип данных" ?
читай документацию к устройству, там написано чего оно шлет.
ну а если имеется ввиду "как же мне запихать все в кривую архитектуру?", то я уже не раз говорил. для таких случаев есть switch-case или if else if.


Цитировать
Никто не создает объектов. Ищется обработчик для соответствующего request/response и "биндится" к Reply.
И все как раз-таки и понятно, в отличии от switch/case
ты сперва опиши суть вопроса или проблемы нормально. непонятно что есть, что должно быть.

ну а если все понятно, то к чему вопросы на форуме?)


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Igors от Октябрь 21, 2014, 15:17
5. В методах конкретных обработчиков для проверки (мой пакет / чужой пакет) просто на сырой QByteArray накладывается соответствующий класс HciCommand/HciEvent.
Например, в ResetReplyHandler это будет проверка ResetCommand(data).isValid() и ResetCommandCompleteEvent(data).isValid() (для canHandleRequest/canHandleResponse соответственно).
То есть структура полей не имеет, но "знает" что по такому-то смещению в QByteArray лежит то-то? Ну пока не видно резонов от этого отказываться.

Т.е. изначальный мой вопрос был как корректнее можно унифицировать эту проверку, т.к. можно ее также делать с помощью статических методов: ResetCommand::isValid(data) ...
Так а что статические методы - все равно это руками "позвать каждый". Давайте конкретнее, напр "вот 2 класса которые неприятно "плодятся", отличаются только проверкой(ами)". А то советы будут слишком общими.

[off]Не тратьте слова на того мудака [off]


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: vulko от Октябрь 21, 2014, 15:35
Джедай, ротик прикрой и за языком следи!

Слишком много постов с дурацкими советами! Ничего не знаешь, но пытаешься строить из себя эксперта.


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: kuzulis от Октябрь 21, 2014, 18:18
Цитата: Igors
То есть структура полей не имеет, но "знает" что по такому-то смещению в QByteArray лежит то-то? Ну пока не видно резонов от этого отказываться.

Да, каждая конкретная структура/класс унаследованная от HciCommand или HciEvent содержит только raw QByteArray и знает
только свои специфичные смешения по которым лежат свои специфичные данные.

Поэтому получение нужных параметров (как и проверка валидности) сводится только по дерганью конкретных методов
этих классов которые "тупо" читают из массива по смещениям. И все.

Т.е. "наложив" по очереди каждый класс на QByteArray можно узнать для кого этот QByteArray предназначен.

Цитата: Igors
Так а что статические методы - все равно это руками "позвать каждый".

Да, то-же самое. Просто разнича в том, что нет оверхеда, т.к. нет конструктора копирования как при "наложении" класса на QByteArray.

Цитата: Igors
Давайте конкретнее, напр "вот 2 класса которые неприятно "плодятся", отличаются только проверкой(ами)". А то советы будут слишком общими.

Все классы разные - имеют разное кол-во параметров.

Для HciCommands (и всех их наследников) можно привязаться только к первому opcode, но следующие поля параметров могут быть какими угодно.

Для HciEvents (и всех их наследников) можно привязаться только к первому полю EventCode, но следующие поля параметров могут быть какими угодно.
Например, в евентах, которые являются ответами на команды, в поле params будет содержаться и opcode той команды на которую девайс ответил (см. выше пример с дампом).
Но могут быть и спонтанные евенты, которые отличаются как EventCode так и содержимым.

Т.е. плодятся как-бы все классы :)


Название: Re: С++ парсинг бинарных структур из байтового массива
Отправлено: Igors от Октябрь 21, 2014, 19:51
Если действие уникально, то это просто метод класса, это нормально. Обобщать/схлопывать надо то что повторяется. Тут не пойму: говорите что HciCommand и HciEvent разные классы - тогда почему не слить OpCode/EventCode в базовые классы?  Вообще где возникает проблема повторов/избыточности? И возникает ли она?

Во-вторых, может сделать команды более вумными? Напр сейчас есть Manager. Он получил экземпляр команды, но ему все время надо учитывать "а что это за команда", методы одной не имеют смысла для другой. Может лучше дать эту работу самому классу команды (напр метод exec)? Она-то точно знает что делать. Если ей понадобятся какие-то данные - подать их залив нужные указатели в структуру (часто с именем типа "контекст")