1<?php 2 3/** 4 * This file is part of the FreeDSx LDAP package. 5 * 6 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace FreeDSx\Ldap\Protocol; 13 14use FreeDSx\Asn1\Encoder\EncoderInterface; 15use FreeDSx\Asn1\Exception\EncoderException; 16use FreeDSx\Ldap\Exception\ProtocolException; 17use FreeDSx\Ldap\Exception\UnsolicitedNotificationException; 18use FreeDSx\Ldap\Operation\Response\ExtendedResponse; 19use FreeDSx\Ldap\Protocol\Queue\MessageWrapperInterface; 20use FreeDSx\Socket\Exception\ConnectionException; 21use FreeDSx\Socket\Queue\Asn1MessageQueue; 22use FreeDSx\Socket\Queue\Buffer; 23use FreeDSx\Socket\Socket; 24use function strlen; 25use function substr; 26 27/** 28 * The LDAP Queue class for sending and receiving messages. 29 * 30 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 31 */ 32class LdapQueue extends Asn1MessageQueue 33{ 34 /** 35 * @var int 36 */ 37 protected const BUFFER_SIZE = 8192; 38 39 /** 40 * @var int 41 */ 42 protected $id = 0; 43 44 /** 45 * @var Socket 46 */ 47 protected $socket; 48 49 /** 50 * @var MessageWrapperInterface|null 51 */ 52 protected $messageWrapper; 53 54 public function __construct(Socket $socket, EncoderInterface $encoder = null) 55 { 56 parent::__construct($socket, $encoder ?? new LdapEncoder()); 57 } 58 59 /** 60 * Encrypt messages sent by the socket for the queue. 61 * 62 * @return $this 63 * @throws ConnectionException 64 */ 65 public function encrypt() 66 { 67 $this->socket->block(true); 68 $this->socket->encrypt(true); 69 $this->socket->block(false); 70 71 return $this; 72 } 73 74 /** 75 * @return bool 76 */ 77 public function isEncrypted(): bool 78 { 79 return ($this->socket->isConnected() && $this->socket->isEncrypted()); 80 } 81 82 /** 83 * Cleanly close the socket and clear buffer contents. 84 */ 85 public function close(): void 86 { 87 $this->socket->close(); 88 $this->buffer = false; 89 $this->id = 0; 90 } 91 92 /** 93 * Generates a message ID to be sent out the queue. 94 */ 95 public function generateId(): int 96 { 97 return ++$this->id; 98 } 99 100 /** 101 * Get the current ID that the queue is on. 102 */ 103 public function currentId(): int 104 { 105 return $this->id; 106 } 107 108 /** 109 * @param MessageWrapperInterface|null $messageWrapper 110 * @return $this 111 */ 112 public function setMessageWrapper(?MessageWrapperInterface $messageWrapper) 113 { 114 $this->messageWrapper = $messageWrapper; 115 116 return $this; 117 } 118 119 /** 120 * {@inheritDoc} 121 */ 122 protected function unwrap($bytes): Buffer 123 { 124 if ($this->messageWrapper === null) { 125 return parent::unwrap($bytes); 126 } 127 128 return $this->messageWrapper->unwrap($bytes); 129 } 130 131 /** 132 * Send LDAP messages out the socket. 133 * 134 * The logic in the loop is to send the messages in chunks of 8192 bytes to lessen the amount of TCP writes we need 135 * to perform if sending out many messages. 136 * 137 * @param LdapMessage ...$messages 138 * @return static 139 * @throws EncoderException 140 */ 141 protected function sendLdapMessage(LdapMessage ...$messages): self 142 { 143 $buffer = ''; 144 145 foreach ($messages as $message) { 146 $encoded = $this->encoder->encode($message->toAsn1()); 147 $buffer .= $this->messageWrapper !== null ? $this->messageWrapper->wrap($encoded) : $encoded; 148 $bufferLen = strlen($buffer); 149 if ($bufferLen >= self::BUFFER_SIZE) { 150 $this->socket->write(substr($buffer, 0, self::BUFFER_SIZE)); 151 $buffer = $bufferLen > self::BUFFER_SIZE ? substr($buffer, self::BUFFER_SIZE) : ''; 152 } 153 } 154 if (strlen($buffer) > 0) { 155 $this->socket->write($buffer); 156 } 157 158 return $this; 159 } 160 161 /** 162 * @return bool 163 */ 164 public function isConnected(): bool 165 { 166 return $this->socket->isConnected(); 167 } 168 169 /** 170 * @throws ConnectionException 171 * @throws ProtocolException 172 * @throws UnsolicitedNotificationException 173 */ 174 protected function getAndValidateMessage(?int $id): LdapMessage 175 { 176 $message = parent::getMessage($id); 177 178 /** 179 * This logic exists in the queue because an unsolicited notification can be received at any time. So we cannot 180 * rely on logic in the handler determined for the initial request / response. 181 */ 182 if ($message->getMessageId() === 0 && $message instanceof LdapMessageResponse && $message->getResponse() instanceof ExtendedResponse) { 183 /** @var ExtendedResponse $response */ 184 $response = $message->getResponse(); 185 throw new UnsolicitedNotificationException( 186 $response->getDiagnosticMessage(), 187 $response->getResultCode(), 188 null, 189 (string) $response->getName() 190 ); 191 } 192 if ($id !== null && $message->getMessageId() !== $id) { 193 throw new ProtocolException(sprintf( 194 'Expected message ID %s, but received %s', 195 $id, 196 $message->getMessageId() 197 )); 198 } 199 200 return $message; 201 } 202} 203