xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/Protocol/LdapQueue.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
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