WeChat. Сериализуем объект — получаем СМС +3

- такой же как Forbes, только лучше.

Предыдущая статья.
В продолжение темы 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 состоит из пяти чисел и строки:

  1. Порядковый номер поля
  2. Идентификатор массива. Ставится равным трем, если поле является массивом. В остальных случаях ни на что не влияет
  3. Тип поля (например 0x9 — string, 0x8 — bool, 0x7 — unsigned int, 0xB — объект итд )
  4. Используется при сериализации массивов вместе со вторым пунктом, в остальных случаях равен нулю
  5. Назначение этого числа осталось загадкой (оно всегда равно нулю и нигде не применяется)
  6. В случае если поле является объектом, указывается тип объекта, в противном случае указывается пустая строка.

Объект сериализуется рекурсивно по алгоритму прямого обхода дерева в глубину. Перед каждым полем сначала ставиться тег, который вычисляется (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 не приходят на номера одной «жужжащей» компании. Не понятно почему…

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (0):