Lines
100.00%
28 / 28
Methods
100.00%
7 / 7
Classes
100.00%
1 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| encodeConnect | 100.00% | 16 / 16 | 100.00% | 1 / 1 | 5 | ||
| encodePublish | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 3 | ||
| encodePubAck | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| encodeSubscribe | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| encodeUnsubscribe | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| encodePingRequest | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| encodeDisconnect | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| 12 | final readonly class PacketEncoder | |
| 13 | { | |
| 14 | private const string PROTOCOL_NAME = 'MQTT'; | |
| 15 | ||
| 16 | private const int PROTOCOL_LEVEL = 4; | |
| 17 | ||
| 18 | private const int CLEAN_SESSION_FLAG = 0x02; | |
| 19 | ||
| 20 | private const int WILL_FLAG = 0x04; | |
| 21 | ||
| 22 | private const int WILL_QOS_SHIFT = 3; | |
| 23 | ||
| 24 | private const int WILL_RETAIN_FLAG = 0x20; | |
| 25 | ||
| 26 | private const int PASSWORD_FLAG = 0x40; | |
| 27 | ||
| 28 | private const int USERNAME_FLAG = 0x80; | |
| 29 | ||
| 30 | private const int MAX_KEEPALIVE_SECONDS = 65535; | |
| 31 | ||
| 32 | private const int PUBLISH_RETAIN_FLAG = 0x01; | |
| 33 | ||
| 34 | private const int PUBLISH_QOS_SHIFT = 1; | |
| 35 | ||
| 36 | private const int SUBSCRIPTION_REQUIRED_FLAGS = 0x02; | |
| 37 | ||
| 38 | public function encodeConnect( | |
| 39 | string $clientId, | |
| 40 | Duration $keepalive, | |
| 41 | ?string $username = null, | |
| 42 | #[SensitiveParameter] | |
| 43 | ?string $password = null, | |
| 44 | ?MqttMessage $will = null, | |
| 45 | ): string { | |
| 46 | $flags = self::CLEAN_SESSION_FLAG; | |
| 47 | $payload = PacketBytes::encodeString($clientId); | |
| 48 | ||
| 49 | if ($will !== null) { | |
| 50 | $flags |= self::WILL_FLAG | ($will->qos->value << self::WILL_QOS_SHIFT) | ($will->retain ? self::WILL_RETAIN_FLAG : 0); | |
| 51 | $payload .= PacketBytes::encodeString($will->topic) . PacketBytes::encodeString($will->payload); | |
| 52 | } | |
| 53 | ||
| 54 | // MQTT 3.1.1 allows a password only together with a username. | |
| 55 | if ($username !== null) { | |
| 56 | $flags |= self::USERNAME_FLAG; | |
| 57 | $payload .= PacketBytes::encodeString($username); | |
| 58 | ||
| 59 | if ($password !== null) { | |
| 60 | $flags |= self::PASSWORD_FLAG; | |
| 61 | $payload .= PacketBytes::encodeString($password); | |
| 62 | } | |
| 63 | } | |
| 64 | ||
| 65 | $variableHeader = PacketBytes::encodeString(self::PROTOCOL_NAME) | |
| 66 | . \chr(self::PROTOCOL_LEVEL) | |
| 67 | . \chr($flags) | |
| 68 | . PacketBytes::encodeUint16(min(self::MAX_KEEPALIVE_SECONDS, (int) ceil($keepalive->toSeconds()))); | |
| 69 | ||
| 70 | return PacketBytes::assemblePacket(PacketType::Connect, 0, $variableHeader . $payload); | |
| 71 | } | |
| 72 | ||
| 73 | public function encodePublish(MqttMessage $message, ?int $packetId = null): string | |
| 74 | { | |
| 75 | $flags = ($message->qos->value << self::PUBLISH_QOS_SHIFT) | ($message->retain ? self::PUBLISH_RETAIN_FLAG : 0); | |
| 76 | $body = PacketBytes::encodeString($message->topic); | |
| 77 | ||
| 78 | if ($message->qos !== MqttQos::AtMostOnce) { | |
| 79 | $body .= PacketBytes::encodeUint16($packetId ?? 0); | |
| 80 | } | |
| 81 | ||
| 82 | return PacketBytes::assemblePacket(PacketType::Publish, $flags, $body . $message->payload); | |
| 83 | } | |
| 84 | ||
| 85 | public function encodePubAck(int $packetId): string | |
| 86 | { | |
| 87 | return PacketBytes::assemblePacket(PacketType::PubAck, 0, PacketBytes::encodeUint16($packetId)); | |
| 88 | } | |
| 89 | ||
| 90 | public function encodeSubscribe(int $packetId, string $topicFilter, MqttQos $maximumQos): string | |
| 91 | { | |
| 92 | $body = PacketBytes::encodeUint16($packetId) . PacketBytes::encodeString($topicFilter) . \chr($maximumQos->value); | |
| 93 | ||
| 94 | return PacketBytes::assemblePacket(PacketType::Subscribe, self::SUBSCRIPTION_REQUIRED_FLAGS, $body); | |
| 95 | } | |
| 96 | ||
| 97 | public function encodeUnsubscribe(int $packetId, string $topicFilter): string | |
| 98 | { | |
| 99 | $body = PacketBytes::encodeUint16($packetId) . PacketBytes::encodeString($topicFilter); | |
| 100 | ||
| 101 | return PacketBytes::assemblePacket(PacketType::Unsubscribe, self::SUBSCRIPTION_REQUIRED_FLAGS, $body); | |
| 102 | } | |
| 103 | ||
| 104 | public function encodePingRequest(): string | |
| 105 | { | |
| 106 | return PacketBytes::assemblePacket(PacketType::PingReq, 0, ''); | |
| 107 | } | |
| 108 | ||
| 109 | public function encodeDisconnect(): string | |
| 110 | { | |
| 111 | return PacketBytes::assemblePacket(PacketType::Disconnect, 0, ''); | |
| 112 | } | |
| 113 | } |