10b3fd2d3SAndreas Gohr<?php 2*dad993c5SAndreas Gohr 30b3fd2d3SAndreas Gohr/** 40b3fd2d3SAndreas Gohr * This file is part of the FreeDSx LDAP package. 50b3fd2d3SAndreas Gohr * 60b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 70b3fd2d3SAndreas Gohr * 80b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE 90b3fd2d3SAndreas Gohr * file that was distributed with this source code. 100b3fd2d3SAndreas Gohr */ 110b3fd2d3SAndreas Gohr 120b3fd2d3SAndreas Gohrnamespace FreeDSx\Ldap\Protocol; 130b3fd2d3SAndreas Gohr 140b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Encoder\EncoderInterface; 15*dad993c5SAndreas Gohruse FreeDSx\Asn1\Exception\EncoderException; 160b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException; 170b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\UnsolicitedNotificationException; 180b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response\ExtendedResponse; 190b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\Queue\MessageWrapperInterface; 200b3fd2d3SAndreas Gohruse FreeDSx\Socket\Exception\ConnectionException; 210b3fd2d3SAndreas Gohruse FreeDSx\Socket\Queue\Asn1MessageQueue; 220b3fd2d3SAndreas Gohruse FreeDSx\Socket\Queue\Buffer; 230b3fd2d3SAndreas Gohruse FreeDSx\Socket\Socket; 24*dad993c5SAndreas Gohruse function strlen; 25*dad993c5SAndreas Gohruse function substr; 260b3fd2d3SAndreas Gohr 270b3fd2d3SAndreas Gohr/** 280b3fd2d3SAndreas Gohr * The LDAP Queue class for sending and receiving messages. 290b3fd2d3SAndreas Gohr * 300b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 310b3fd2d3SAndreas Gohr */ 320b3fd2d3SAndreas Gohrclass LdapQueue extends Asn1MessageQueue 330b3fd2d3SAndreas Gohr{ 340b3fd2d3SAndreas Gohr /** 350b3fd2d3SAndreas Gohr * @var int 360b3fd2d3SAndreas Gohr */ 370b3fd2d3SAndreas Gohr protected const BUFFER_SIZE = 8192; 380b3fd2d3SAndreas Gohr 390b3fd2d3SAndreas Gohr /** 400b3fd2d3SAndreas Gohr * @var int 410b3fd2d3SAndreas Gohr */ 420b3fd2d3SAndreas Gohr protected $id = 0; 430b3fd2d3SAndreas Gohr 440b3fd2d3SAndreas Gohr /** 450b3fd2d3SAndreas Gohr * @var Socket 460b3fd2d3SAndreas Gohr */ 470b3fd2d3SAndreas Gohr protected $socket; 480b3fd2d3SAndreas Gohr 490b3fd2d3SAndreas Gohr /** 500b3fd2d3SAndreas Gohr * @var MessageWrapperInterface|null 510b3fd2d3SAndreas Gohr */ 520b3fd2d3SAndreas Gohr protected $messageWrapper; 530b3fd2d3SAndreas Gohr 540b3fd2d3SAndreas Gohr public function __construct(Socket $socket, EncoderInterface $encoder = null) 550b3fd2d3SAndreas Gohr { 560b3fd2d3SAndreas Gohr parent::__construct($socket, $encoder ?? new LdapEncoder()); 570b3fd2d3SAndreas Gohr } 580b3fd2d3SAndreas Gohr 590b3fd2d3SAndreas Gohr /** 600b3fd2d3SAndreas Gohr * Encrypt messages sent by the socket for the queue. 610b3fd2d3SAndreas Gohr * 620b3fd2d3SAndreas Gohr * @return $this 630b3fd2d3SAndreas Gohr * @throws ConnectionException 640b3fd2d3SAndreas Gohr */ 650b3fd2d3SAndreas Gohr public function encrypt() 660b3fd2d3SAndreas Gohr { 670b3fd2d3SAndreas Gohr $this->socket->block(true); 680b3fd2d3SAndreas Gohr $this->socket->encrypt(true); 690b3fd2d3SAndreas Gohr $this->socket->block(false); 700b3fd2d3SAndreas Gohr 710b3fd2d3SAndreas Gohr return $this; 720b3fd2d3SAndreas Gohr } 730b3fd2d3SAndreas Gohr 740b3fd2d3SAndreas Gohr /** 750b3fd2d3SAndreas Gohr * @return bool 760b3fd2d3SAndreas Gohr */ 770b3fd2d3SAndreas Gohr public function isEncrypted(): bool 780b3fd2d3SAndreas Gohr { 790b3fd2d3SAndreas Gohr return ($this->socket->isConnected() && $this->socket->isEncrypted()); 800b3fd2d3SAndreas Gohr } 810b3fd2d3SAndreas Gohr 820b3fd2d3SAndreas Gohr /** 830b3fd2d3SAndreas Gohr * Cleanly close the socket and clear buffer contents. 840b3fd2d3SAndreas Gohr */ 850b3fd2d3SAndreas Gohr public function close(): void 860b3fd2d3SAndreas Gohr { 870b3fd2d3SAndreas Gohr $this->socket->close(); 880b3fd2d3SAndreas Gohr $this->buffer = false; 890b3fd2d3SAndreas Gohr $this->id = 0; 900b3fd2d3SAndreas Gohr } 910b3fd2d3SAndreas Gohr 920b3fd2d3SAndreas Gohr /** 930b3fd2d3SAndreas Gohr * Generates a message ID to be sent out the queue. 940b3fd2d3SAndreas Gohr */ 950b3fd2d3SAndreas Gohr public function generateId(): int 960b3fd2d3SAndreas Gohr { 970b3fd2d3SAndreas Gohr return ++$this->id; 980b3fd2d3SAndreas Gohr } 990b3fd2d3SAndreas Gohr 1000b3fd2d3SAndreas Gohr /** 1010b3fd2d3SAndreas Gohr * Get the current ID that the queue is on. 1020b3fd2d3SAndreas Gohr */ 1030b3fd2d3SAndreas Gohr public function currentId(): int 1040b3fd2d3SAndreas Gohr { 1050b3fd2d3SAndreas Gohr return $this->id; 1060b3fd2d3SAndreas Gohr } 1070b3fd2d3SAndreas Gohr 1080b3fd2d3SAndreas Gohr /** 1090b3fd2d3SAndreas Gohr * @param MessageWrapperInterface|null $messageWrapper 1100b3fd2d3SAndreas Gohr * @return $this 1110b3fd2d3SAndreas Gohr */ 1120b3fd2d3SAndreas Gohr public function setMessageWrapper(?MessageWrapperInterface $messageWrapper) 1130b3fd2d3SAndreas Gohr { 1140b3fd2d3SAndreas Gohr $this->messageWrapper = $messageWrapper; 1150b3fd2d3SAndreas Gohr 1160b3fd2d3SAndreas Gohr return $this; 1170b3fd2d3SAndreas Gohr } 1180b3fd2d3SAndreas Gohr 1190b3fd2d3SAndreas Gohr /** 1200b3fd2d3SAndreas Gohr * {@inheritDoc} 1210b3fd2d3SAndreas Gohr */ 1220b3fd2d3SAndreas Gohr protected function unwrap($bytes): Buffer 1230b3fd2d3SAndreas Gohr { 1240b3fd2d3SAndreas Gohr if ($this->messageWrapper === null) { 1250b3fd2d3SAndreas Gohr return parent::unwrap($bytes); 1260b3fd2d3SAndreas Gohr } 1270b3fd2d3SAndreas Gohr 1280b3fd2d3SAndreas Gohr return $this->messageWrapper->unwrap($bytes); 1290b3fd2d3SAndreas Gohr } 1300b3fd2d3SAndreas Gohr 1310b3fd2d3SAndreas Gohr /** 1320b3fd2d3SAndreas Gohr * Send LDAP messages out the socket. 1330b3fd2d3SAndreas Gohr * 1340b3fd2d3SAndreas Gohr * The logic in the loop is to send the messages in chunks of 8192 bytes to lessen the amount of TCP writes we need 1350b3fd2d3SAndreas Gohr * to perform if sending out many messages. 136*dad993c5SAndreas Gohr * 137*dad993c5SAndreas Gohr * @param LdapMessage ...$messages 138*dad993c5SAndreas Gohr * @return static 139*dad993c5SAndreas Gohr * @throws EncoderException 1400b3fd2d3SAndreas Gohr */ 1410b3fd2d3SAndreas Gohr protected function sendLdapMessage(LdapMessage ...$messages): self 1420b3fd2d3SAndreas Gohr { 1430b3fd2d3SAndreas Gohr $buffer = ''; 1440b3fd2d3SAndreas Gohr 1450b3fd2d3SAndreas Gohr foreach ($messages as $message) { 1460b3fd2d3SAndreas Gohr $encoded = $this->encoder->encode($message->toAsn1()); 1470b3fd2d3SAndreas Gohr $buffer .= $this->messageWrapper !== null ? $this->messageWrapper->wrap($encoded) : $encoded; 148*dad993c5SAndreas Gohr $bufferLen = strlen($buffer); 1490b3fd2d3SAndreas Gohr if ($bufferLen >= self::BUFFER_SIZE) { 150*dad993c5SAndreas Gohr $this->socket->write(substr($buffer, 0, self::BUFFER_SIZE)); 151*dad993c5SAndreas Gohr $buffer = $bufferLen > self::BUFFER_SIZE ? substr($buffer, self::BUFFER_SIZE) : ''; 1520b3fd2d3SAndreas Gohr } 1530b3fd2d3SAndreas Gohr } 154*dad993c5SAndreas Gohr if (strlen($buffer) > 0) { 1550b3fd2d3SAndreas Gohr $this->socket->write($buffer); 1560b3fd2d3SAndreas Gohr } 1570b3fd2d3SAndreas Gohr 1580b3fd2d3SAndreas Gohr return $this; 1590b3fd2d3SAndreas Gohr } 1600b3fd2d3SAndreas Gohr 1610b3fd2d3SAndreas Gohr /** 1620b3fd2d3SAndreas Gohr * @return bool 1630b3fd2d3SAndreas Gohr */ 1640b3fd2d3SAndreas Gohr public function isConnected(): bool 1650b3fd2d3SAndreas Gohr { 1660b3fd2d3SAndreas Gohr return $this->socket->isConnected(); 1670b3fd2d3SAndreas Gohr } 1680b3fd2d3SAndreas Gohr 1690b3fd2d3SAndreas Gohr /** 1700b3fd2d3SAndreas Gohr * @throws ConnectionException 1710b3fd2d3SAndreas Gohr * @throws ProtocolException 1720b3fd2d3SAndreas Gohr * @throws UnsolicitedNotificationException 1730b3fd2d3SAndreas Gohr */ 1740b3fd2d3SAndreas Gohr protected function getAndValidateMessage(?int $id): LdapMessage 1750b3fd2d3SAndreas Gohr { 1760b3fd2d3SAndreas Gohr $message = parent::getMessage($id); 1770b3fd2d3SAndreas Gohr 1780b3fd2d3SAndreas Gohr /** 1790b3fd2d3SAndreas Gohr * This logic exists in the queue because an unsolicited notification can be received at any time. So we cannot 1800b3fd2d3SAndreas Gohr * rely on logic in the handler determined for the initial request / response. 1810b3fd2d3SAndreas Gohr */ 1820b3fd2d3SAndreas Gohr if ($message->getMessageId() === 0 && $message instanceof LdapMessageResponse && $message->getResponse() instanceof ExtendedResponse) { 1830b3fd2d3SAndreas Gohr /** @var ExtendedResponse $response */ 1840b3fd2d3SAndreas Gohr $response = $message->getResponse(); 1850b3fd2d3SAndreas Gohr throw new UnsolicitedNotificationException( 1860b3fd2d3SAndreas Gohr $response->getDiagnosticMessage(), 1870b3fd2d3SAndreas Gohr $response->getResultCode(), 1880b3fd2d3SAndreas Gohr null, 1890b3fd2d3SAndreas Gohr (string) $response->getName() 1900b3fd2d3SAndreas Gohr ); 1910b3fd2d3SAndreas Gohr } 1920b3fd2d3SAndreas Gohr if ($id !== null && $message->getMessageId() !== $id) { 1930b3fd2d3SAndreas Gohr throw new ProtocolException(sprintf( 1940b3fd2d3SAndreas Gohr 'Expected message ID %s, but received %s', 1950b3fd2d3SAndreas Gohr $id, 1960b3fd2d3SAndreas Gohr $message->getMessageId() 1970b3fd2d3SAndreas Gohr )); 1980b3fd2d3SAndreas Gohr } 1990b3fd2d3SAndreas Gohr 2000b3fd2d3SAndreas Gohr return $message; 2010b3fd2d3SAndreas Gohr } 2020b3fd2d3SAndreas Gohr} 203