<?php
/**
* @license Silk Software House Sp. Z O. O.
*/
declare(strict_types=1);
namespace App\EventSubscriber;
use App\Decoder\Interfaces\DecoderInterface;
use App\Entity\DeviceCommand;
use App\Repository\RecipeToSyncRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class ResponseEncryptingSubscriber.
*/
class ResponseEncryptingSubscriber implements EventSubscriberInterface
{
const MAX_BUFFER_SIZE = 400;
/**
* Decoder.
*
* @var DecoderInterface
*/
private $decoder;
/**
* Device ping path.
*
* @var string[]
*/
private $encryptedPaths;
/**
* @var RecipeToSyncRepository
*/
private $recipeToSyncRepository;
/**
* @var LoggerInterface
*/
private $logger;
/**
* ResponseEncryptingSubscriber constructor.
*
* @param DecoderInterface $decoder
* @param string[] $encryptedPaths Array of encrypted paths.
*/
public function __construct(
DecoderInterface $decoder,
array $encryptedPaths,
RecipeToSyncRepository $recipeToSyncRepository,
LoggerInterface $logger
)
{
$this->decoder = $decoder;
$this->encryptedPaths = $encryptedPaths;
$this->recipeToSyncRepository = $recipeToSyncRepository;
$this->logger = $logger;
}
/**
* Get subscribed events.
*
* @return array
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => [
['onKernelResponse', -9999],
],
];
}
/**
* On kernel response.
*
* @param ResponseEvent $event
*
* @throws AccessDeniedHttpException
*/
public function onKernelResponse(ResponseEvent $event)
{
if (!$event->isMasterRequest() || !in_array($event->getRequest()->getPathInfo(), $this->encryptedPaths)) {
return;
}
$serial = $event->getResponse()->headers->get('serial');
$payload = json_decode($event->getResponse()->getContent(), true) ?? [];
$command = $payload[DeviceCommand::COMMAND_KEY] ?? DeviceCommand::NO_COMMAND;
$plainContent = $event->getResponse()->getContent();
if ($serial) {
$event->getResponse()->headers->remove('serial');
}
$encrypted = $this->decoder->encrypt($plainContent, $serial);
$response = $event->getResponse();
$response->setContent($encrypted);
$response->headers->remove('Content-Type');
$response->headers->add(['Content-Type' => 'text/plain']);
if (strlen($encrypted) > self::MAX_BUFFER_SIZE) {
$this->logger->warning('Encrypted device response exceeds modem buffer.', [
'path' => $event->getRequest()->getPathInfo(),
'serial' => $serial,
'command' => $command,
'plain_length' => strlen($plainContent),
'encrypted_length' => strlen($encrypted),
'status' => $response->getStatusCode(),
'payload' => $payload,
]);
$response->setStatusCode(Response::HTTP_REQUEST_ENTITY_TOO_LARGE);
$contentWhereSizeFailed = json_encode(['cmd' => DeviceCommand::NO_COMMAND]);
$response->setContent($this->decoder->encrypt($contentWhereSizeFailed, $serial));
} elseif (
$response->getStatusCode() >= Response::HTTP_OK
&& $response->getStatusCode() < Response::HTTP_MULTIPLE_CHOICES
&& $command === DeviceCommand::SET_RECIPE
&& !empty($serial)
) {
$this->recipeToSyncRepository->findFirstBySerialNumberAndRemove($serial);
}
$event->setResponse($response);
}
}