Предыдущая статья.
В продолжение темы WeChat. В данной статье мы покажем как сериализуются и десериализуются объекты, а так же зашифруем сообщение и получим СМС с кодом подтверждения. Кроме того мы приведем весь необходимый код на PHP, чтобы вы могли попробовать и убедиться что все работает
В приложении используется библиотека ProtoBuf.
Существует базовый объект WXPBGeneratedMessage, все остальные наследуются от него.
Изначально Protobuf умеет сериализовать только простые типы(строки и числа). Чтобы сериализовать сложные объекты, для каждого объекта создается поле, которое мы назвали classInfo. В нем хранятся имена и описания всех полей.
Для запроса СМС необходимо создать объект BindOpMobileRequest.
Как можно увидеть, classInfo состоит из двух массивов(имена полей и их описания). ObjectDefinition состоит из пяти чисел и строки:
Объект сериализуется рекурсивно по алгоритму прямого обхода дерева в глубину. Перед каждым полем сначала ставиться тег, который вычисляется (c использованием словаря) следующим образом:
Если поле является объектом, то после тега ставиться его длинна.
Создаем объект BindOpMobileRequest, заполняем поля и сериализуем:
В результате получаем:
Теперь осталось, составить заголовок, зашифровать данные и отправить на сервер.
Если все сделано правильно, сервер пришлет нам ответ, который нужно расшифровать AES-ключом переданным в запросе и десериализовать:
Десериализовать необходимо в объект BindOpMobileResponse.
Здесь приведен код на php, реализующий все выше сказанное. Дерзайте…
PS. SMS не приходят на номера одной «жужжащей» компании. Не понятно почему…
В продолжение темы WeChat. В данной статье мы покажем как сериализуются и десериализуются объекты, а так же зашифруем сообщение и получим СМС с кодом подтверждения. Кроме того мы приведем весь необходимый код на PHP, чтобы вы могли попробовать и убедиться что все работает
В приложении используется библиотека ProtoBuf.
Существует базовый объект WXPBGeneratedMessage, все остальные наследуются от него.
Изначально Protobuf умеет сериализовать только простые типы(строки и числа). Чтобы сериализовать сложные объекты, для каждого объекта создается поле, которое мы назвали classInfo. В нем хранятся имена и описания всех полей.
Для запроса СМС необходимо создать объект BindOpMobileRequest.
public function __construct()
{
$this->classInfo = new \wechat\PBClassInfo();
$this->classInfo->nameProperty[] = 'baseRequest';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x1, 0x2, 0xB, 0x0, 0x0, 'BaseRequest');
$this->classInfo->nameProperty[] = 'userName';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x2, 0x1, 0x9, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'mobile';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x3, 0x1, 0x9, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'opcode';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x4, 0x2, 0x5, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'verifycode';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x5, 0x1, 0x9, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'dialFlag';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x6, 0x1, 0x5, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'dialLang';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x7, 0x1, 0x9, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'authTicket';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x8, 0x1, 0x9, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'forceReg';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x9, 0x1, 0xD, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'safeDeviceName';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0xA, 0x1, 0x9, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'safeDeviceType';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0xB, 0x1, 0x9, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'randomEncryKey';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0xC, 0x1, 0xB, 0x0, 0x0, 'SKBuiltinBuffer_t');
$this->classInfo->nameProperty[] = 'language';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0xD, 0x1, 0x9, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'inputMobileRetrys';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0xE, 0x1, 0xD, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'adjustRet';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0xF, 0x1, 0xD, 0x0, 0x0, '');
$this->classInfo->nameProperty[] = 'clientSeqId';
$this->classInfo->objectDefinition[] = new \wechat\ObjectDefinition(0x10, 0x1, 0x9, 0x0, 0x0, '');
parent::__construct();
}
Как можно увидеть, classInfo состоит из двух массивов(имена полей и их описания). ObjectDefinition состоит из пяти чисел и строки:
- Порядковый номер поля
- Идентификатор массива. Ставится равным трем, если поле является массивом. В остальных случаях ни на что не влияет
- Тип поля (например 0x9 — string, 0x8 — bool, 0x7 — unsigned int, 0xB — объект итд )
- Используется при сериализации массивов вместе со вторым пунктом, в остальных случаях равен нулю
- Назначение этого числа осталось загадкой (оно всегда равно нулю и нигде не применяется)
- В случае если поле является объектом, указывается тип объекта, в противном случае указывается пустая строка.
Объект сериализуется рекурсивно по алгоритму прямого обхода дерева в глубину. Перед каждым полем сначала ставиться тег, который вычисляется (c использованием словаря) следующим образом:
$FieldTypeDictionary = array(-1, 1, 5, 0, 0, 0, 1, 5, 0, 2, 3, 2, 2, 0, 0, 5, 1, 0, 0);
$fieldType = $FieldTypeDictionary[$fieldType];
$tag = $fieldNumber << 3 | $fieldType;
Если поле является объектом, то после тега ставиться его длинна.
Готовим сообщение для отправки на сервер
Создаем объект BindOpMobileRequest, заполняем поля и сериализуем:
public function getVerifyCode()
{
$bindOpMobileRequest = new \wechat\Request\BindOpMobileRequest();
$baseRequest = $this->createBaseRequest();
$this->aesKey = random_bytes(0x10);
$baseRequest->sessionKey = '';
$baseRequest->scene = 0;
$bindOpMobileRequest->baseRequest = $baseRequest;
$bindOpMobileRequest->mobile = $this->phoneNumber;
$bindOpMobileRequest->opcode = 14;
$bindOpMobileRequest->safeDeviceName = $this->deviceName;
$bindOpMobileRequest->safeDeviceType = 'iPhone';
$bindOpMobileRequest->randomEncryKey = new \wechat\Object\SKBuiltinBuffer_t();
$bindOpMobileRequest->randomEncryKey->iLen = strlen($this->aesKey);
$bindOpMobileRequest->randomEncryKey->buffer = $this->aesKey;
$bindOpMobileRequest->language = $this->language;
$bindOpMobileRequest->inputMobileRetrys = 5;
$bindOpMobileRequest->adjustRet = 0;
$bindOpMobileRequest->clientSeqId = $this->clientSeqId;
$serializedData = $bindOpMobileRequest->serializedData();
}
В результате получаем:
Теперь осталось, составить заголовок, зашифровать данные и отправить на сервер.
$header = $this->computeHeader($serializedData, 0x91, 2);
$dataToSend = $header . $this->client->RSAEncrypt($serializedData);
$response = $this->client->request($this, $dataToSend, 'bindopmobileforreg');
Если все сделано правильно, сервер пришлет нам ответ, который нужно расшифровать AES-ключом переданным в запросе и десериализовать:
$response = deleteHeaderFromResponse($response);
$response = $this->client->AESDecrypt($response, $this->aesKey);
$bindOpMobileResponse = new \wechat\Response\BindOpMobileResponse();
$bindOpMobileResponse->mergeFromData($response);
Десериализовать необходимо в объект BindOpMobileResponse.
Здесь приведен код на php, реализующий все выше сказанное. Дерзайте…
PS. SMS не приходят на номера одной «жужжащей» компании. Не понятно почему…