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