From 0e08bef3bc6adf9fdf3ec210bb5e155c38deeaa4 Mon Sep 17 00:00:00 2001 From: netkas Date: Thu, 6 Feb 2025 15:13:00 -0500 Subject: [PATCH] Added Encrypted channels and communication methods --- .idea/sqldialects.xml | 3 + .../Resources/database/channel_com.sql | 32 + .../Resources/database/encrypted_channels.sql | 36 ++ src/Socialbox/Enums/DatabaseObjects.php | 5 + src/Socialbox/Enums/StandardError.php | 1 + .../Enums/Status/EncryptionChannelState.php | 12 + .../Types/CommunicationRecipientType.php | 9 + .../Managers/EncryptionChannelManager.php | 568 ++++++++++++++++++ .../Objects/Database/ChannelMessageRecord.php | 141 +++++ .../Database/EncryptionChannelRecord.php | 259 ++++++++ 10 files changed, 1066 insertions(+) create mode 100644 src/Socialbox/Classes/Resources/database/channel_com.sql create mode 100644 src/Socialbox/Classes/Resources/database/encrypted_channels.sql create mode 100644 src/Socialbox/Enums/Status/EncryptionChannelState.php create mode 100644 src/Socialbox/Enums/Types/CommunicationRecipientType.php create mode 100644 src/Socialbox/Managers/EncryptionChannelManager.php create mode 100644 src/Socialbox/Objects/Database/ChannelMessageRecord.php create mode 100644 src/Socialbox/Objects/Database/EncryptionChannelRecord.php diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index b4dcf43..8fb5dbe 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -4,8 +4,10 @@ + + @@ -14,6 +16,7 @@ + diff --git a/src/Socialbox/Classes/Resources/database/channel_com.sql b/src/Socialbox/Classes/Resources/database/channel_com.sql new file mode 100644 index 0000000..ab75fd7 --- /dev/null +++ b/src/Socialbox/Classes/Resources/database/channel_com.sql @@ -0,0 +1,32 @@ +create table channel_com +( + uuid varchar(36) default uuid() not null comment 'The UUID of the message', + channel_uuid varchar(36) not null comment 'The UUID of the encryption channel used', + recipient enum ('SENDER', 'RECEIVER') not null comment 'The recipient of the message', + message text not null comment 'The encrypted message content', + signature varchar(64) not null comment 'The signature of the decrypted message', + received tinyint(1) default 0 not null comment 'True if the message was received by the recipient', + timestamp timestamp default current_timestamp() not null comment 'The timestamp of the mssage being sent', + primary key (uuid, channel_uuid) comment 'The Unique Pair Index for the channel UUID and message UUID', + constraint channel_com_uuid_channel_uuid_uindex + unique (uuid, channel_uuid) comment 'The Unique Pair Index for the channel UUID and message UUID', + constraint channel_com_uuid_channel_uuid_uindex_2 + unique (uuid, channel_uuid) comment 'The Unique Index Pair for the channel UUID and message UUID', + constraint channel_com_encryption_channels_uuid_fk + foreign key (channel_uuid) references encryption_channels (uuid) + on update cascade on delete cascade +) + comment 'Table for housing communication messages over encryption channels'; + +create index channel_com_received_index + on channel_com (received) + comment 'The index for the received column'; + +create index channel_com_recipient_index + on channel_com (recipient) + comment 'The index for the recipient column'; + +create index channel_com_timestamp_index + on channel_com (timestamp) + comment 'The index for the Timestamp column'; + diff --git a/src/Socialbox/Classes/Resources/database/encrypted_channels.sql b/src/Socialbox/Classes/Resources/database/encrypted_channels.sql new file mode 100644 index 0000000..f906d93 --- /dev/null +++ b/src/Socialbox/Classes/Resources/database/encrypted_channels.sql @@ -0,0 +1,36 @@ +create table encryption_channels +( + uuid varchar(36) not null comment 'The Unique Universal Identifier for the encryption channel' + primary key comment 'The Unique Index of the encryption channel UUID', + calling_peer varchar(320) not null comment 'The address of the calling peer', + calling_signature_uuid varchar(64) not null comment 'The UUID of the signing key that the calling peer is going to use to sign their messages', + calling_signature_public_key varchar(32) not null, + calling_encryption_public_key varchar(32) not null comment 'The public encryption key of the caller', + receiving_peer varchar(320) not null comment 'The address of the receiving peer', + receiving_signature_uuid varchar(256) null comment 'The UUID of the signature that the receiver peer will use to sign messages with', + receiving_signature_public_key varchar(32) null comment 'The public key of the receiver''s signing key', + receiving_encryption_public_key varchar(32) null comment 'The encryption key of the receiver', + transport_encryption_algorithm enum ('xchacha20', 'chacha20', 'aes256gcm') default 'xchacha20' not null comment 'The transport encryption algorithm used as selected by the caller', + transport_encryption_key varchar(256) null comment 'The transport encryption key encrypted using the caller''s public encryption key', + state enum ('AWAITING_RECEIVER', 'ERROR', 'DECLINED', 'AWAITING_DHE', 'OPENED', 'CLOSED') default 'AWAITING_RECEIVER' not null comment 'The current state of the encryption channel', + created timestamp default current_timestamp() not null comment 'The Timestamp for when this record was created', + constraint encryption_channels_uuid_uindex + unique (uuid) comment 'The Unique Index of the encryption channel UUID' +); + +create index encryption_channels_calling_peer_index + on encryption_channels (calling_peer) + comment 'The index of the calling peer address'; + +create index encryption_channels_created_index + on encryption_channels (created) + comment 'The Index for when the record was created'; + +create index encryption_channels_receiving_peer_index + on encryption_channels (receiving_peer) + comment 'The index of the receiving peer address'; + +create index encryption_channels_state_index + on encryption_channels (state) + comment 'The index for the state column'; + diff --git a/src/Socialbox/Enums/DatabaseObjects.php b/src/Socialbox/Enums/DatabaseObjects.php index 327698b..f2ad497 100644 --- a/src/Socialbox/Enums/DatabaseObjects.php +++ b/src/Socialbox/Enums/DatabaseObjects.php @@ -18,6 +18,9 @@ case SIGNING_KEYS = 'signing_keys.sql'; case EXTERNAL_SESSIONS = 'external_sessions.sql'; + case ENCRYPTED_CHANNELS = 'encrypted_channels.sql'; + case CHANNEL_COM = 'channel_com.sql'; + case CONTACT_KNOWN_KEYS = 'contact_known_keys.sql'; /** @@ -43,6 +46,8 @@ self::SIGNING_KEYS, self::EXTERNAL_SESSIONS => 2, + self::ENCRYPTED_CHANNELS, + self::CHANNEL_COM, self::CONTACT_KNOWN_KEYS => 3, }; } diff --git a/src/Socialbox/Enums/StandardError.php b/src/Socialbox/Enums/StandardError.php index 2184f55..0cf213d 100644 --- a/src/Socialbox/Enums/StandardError.php +++ b/src/Socialbox/Enums/StandardError.php @@ -18,6 +18,7 @@ case CONFLICT = -107; case EXPIRED = -108; case CRYPTOGRAPHIC_ERROR = -109; + case UUID_CONFLICT = -110; // RPC Errors case RPC_METHOD_NOT_FOUND = -1000; diff --git a/src/Socialbox/Enums/Status/EncryptionChannelState.php b/src/Socialbox/Enums/Status/EncryptionChannelState.php new file mode 100644 index 0000000..fd46611 --- /dev/null +++ b/src/Socialbox/Enums/Status/EncryptionChannelState.php @@ -0,0 +1,12 @@ +toRfc4122(); + } + + try + { + $stmt = Database::getConnection()->prepare('INSERT INTO encryption_channels (uuid, calling_peer, calling_signature_uuid, calling_signature_public_key, calling_encryption_public_key, receiving_peer, transport_encryption_algorithm) VALUES (:uuid, :calling_peer, :calling_signature_uuid, :calling_signature_public_key, :calling_encryption_public_key, :receiving_peer, :transport_encryption_algorithm)'); + $stmt->bindParam(':uuid', $uuid); + $callingPeerAddress = $callingPeer->getAddress(); + $stmt->bindParam(':calling_peer', $callingPeerAddress); + $stmt->bindParam(':calling_signature_uuid', $signatureUuid); + $stmt->bindParam(':calling_signature_public_key', $signingPublicKey); + $stmt->bindParam(':calling_encryption_public_key', $encryptionPublicKey); + $receivingPeerAddress = $receivingPeer->getAddress(); + $stmt->bindParam(':receiving_peer', $receivingPeerAddress); + $stmt->bindParam(':transport_encryption_algorithm', $transportEncryptionAlgorithm); + $stmt->execute(); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to create the encryption channel', $e); + } + + return $uuid; + } + + /** + * Retrieves the incoming encryption channels for the specified peer. + * + * @param string|PeerAddress $peerAddress The peer to retrieve the channels for. + * @param int $limit The maximum number of channels to retrieve. + * @param int $page The page of channels to retrieve. + * @return EncryptionChannelRecord[] The incoming channels for the peer. + * @throws DatabaseOperationException If an error occurs while retrieving the channels. + * @throws \DateMalformedStringException If the created date is not a valid date string. + */ + public static function getChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array + { + if($peerAddress instanceof PeerAddress) + { + $peerAddress = $peerAddress->getAddress(); + } + + try + { + $stmt = Database::getConnection()->prepare('SELECT * FROM encryption_channels WHERE calling_peer=:address OR receiving_peer=:address LIMIT :limit OFFSET :offset'); + $stmt->bindParam(':address', $peerAddress); + $stmt->bindParam(':limit', $limit, PDO::PARAM_INT); + $offset = $page * $limit; + $stmt->bindParam(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + $results = $stmt->fetchAll(); + + $channels = []; + foreach($results as $result) + { + $channels[] = new EncryptionChannelRecord($result); + } + + return $channels; + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to retrieve the encryption channels', $e); + } + } + + /** + * Retrieves the incoming encryption channels for the specified peer. + * + * @param string|PeerAddress $peerAddress The peer to retrieve the channels for. + * @param int $limit The maximum number of channels to retrieve. + * @param int $page The page of channels to retrieve. + * @return EncryptionChannelRecord[] The incoming channels for the peer. + * @throws DatabaseOperationException If an error occurs while retrieving the channels. + * @throws \DateMalformedStringException If the created date is not a valid date string. + */ + public static function getRequests(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array + { + if($peerAddress instanceof PeerAddress) + { + $peerAddress = $peerAddress->getAddress(); + } + + try + { + $stmt = Database::getConnection()->prepare('SELECT * FROM encryption_channels WHERE receiving_peer=:address AND state=:state LIMIT :limit OFFSET :offset'); + $stmt->bindParam(':address', $peerAddress); + $state = EncryptionChannelState::AWAITING_RECEIVER->value; + $stmt->bindParam(':state', $state); + $stmt->bindParam(':limit', $limit, PDO::PARAM_INT); + $offset = $page * $limit; + $stmt->bindParam(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + $results = $stmt->fetchAll(); + + $channels = []; + foreach($results as $result) + { + $channels[] = new EncryptionChannelRecord($result); + } + + return $channels; + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to retrieve the encryption channels', $e); + } + } + + /** + * Retrieves the incoming encryption channels for the specified peer. + * + * @param string|PeerAddress $peerAddress The peer to retrieve the channels for. + * @param int $limit The maximum number of channels to retrieve. + * @param int $page The page of channels to retrieve. + * @return EncryptionChannelRecord[] The incoming channels for the peer. + * @throws DatabaseOperationException If an error occurs while retrieving the channels. + * @throws \DateMalformedStringException If the created date is not a valid date string. + */ + public static function getIncomingChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array + { + if($peerAddress instanceof PeerAddress) + { + $peerUuid = $peerAddress->getAddress(); + } + + try + { + $stmt = Database::getConnection()->prepare('SELECT * FROM encryption_channels WHERE receiving_peer=:address LIMIT :limit OFFSET :offset'); + $stmt->bindParam(':address', $peerUuid); + $stmt->bindParam(':limit', $limit, PDO::PARAM_INT); + $offset = $page * $limit; + $stmt->bindParam(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + $results = $stmt->fetchAll(); + + $channels = []; + foreach($results as $result) + { + $channels[] = new EncryptionChannelRecord($result); + } + + return $channels; + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to retrieve the encryption channels', $e); + } + } + + /** + * Retrieves the outgoing channels for the specified peer. + * + * @param string|PeerAddress $peerAddress The peer to retrieve the channels for. + * @param int $limit The maximum number of channels to retrieve. + * @param int $page The page of channels to retrieve. + * @return EncryptionChannelRecord[] The outgoing channels for the specified peer. + * @throws DatabaseOperationException If an error occurs while retrieving the channels. + * @throws \DateMalformedStringException If the created date is not a valid date string. + */ + public static function getOutgoingChannels(string|PeerAddress $peerAddress, int $limit=100, int $page=0): array + { + if($peerAddress instanceof PeerAddress) + { + $peerAddress = $peerAddress->getAddress(); + } + + try + { + $stmt = Database::getConnection()->prepare('SELECT * FROM encryption_channels WHERE calling_peer=:address LIMIT :limit OFFSET :offset'); + $stmt->bindParam(':address', $peerAddress); + $stmt->bindParam(':limit', $limit, PDO::PARAM_INT); + $offset = $page * $limit; + $stmt->bindParam(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + $results = $stmt->fetchAll(); + + $channels = []; + foreach($results as $result) + { + $channels[] = new EncryptionChannelRecord($result); + } + + return $channels; + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to retrieve the encryption channels', $e); + } + } + + /** + * Declines the encryption channel with the specified UUID. + * + * @param string $channelUuid The UUID of the channel to decline. + * @throws DatabaseOperationException If an error occurs while declining the channel. + */ + public static function declineChannel(string $channelUuid): void + { + try + { + $stmt = Database::getConnection()->prepare('UPDATE encryption_channels SET state=:state WHERE uuid=:uuid'); + $state = EncryptionChannelState::DECLINED->value; + $stmt->bindParam(':state', $state); + $stmt->bindParam(':uuid', $channelUuid); + $stmt->execute(); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to decline the encryption channel', $e); + } + } + + /** + * Accepts the encryption channel with the specified UUID. + * + * @param string $channelUuid The UUID of the channel to accept. + * @param string $signatureUuid The UUID of the signature used to create the channel. + * @param string $signaturePublicKey The public key used for signing. + * @param string $encryptionPublicKey The public key used for encryption. + * @param string $transportEncryptionAlgorithm The algorithm used for transport encryption. + * @param string $encryptedTransportEncryptionKey The encrypted transport encryption key. + * @throws DatabaseOperationException If an error occurs while accepting the channel. + */ + public static function acceptChannel(string $channelUuid, string $signatureUuid, string $signaturePublicKey, string $encryptionPublicKey, string $transportEncryptionAlgorithm, string $encryptedTransportEncryptionKey): void + { + try + { + $stmt = Database::getConnection()->prepare('UPDATE encryption_channels SET state=:state, receiving_signature_uuid=:receiving_signature_uuid, receiving_signature_public_key=:receiving_signature_public_key, receiving_encryption_public_key=:receiving_encryption_public_key, transport_encryption_algorithm=:transport_encryption_algorithm, transport_encryption_key=:transport_encryption_key WHERE uuid=:uuid'); + $state = EncryptionChannelState::OPENED->value; + $stmt->bindParam(':state', $state); + $stmt->bindParam(':receiving_signature_uuid', $signatureUuid); + $stmt->bindParam(':receiving_signature_public_key', $signaturePublicKey); + $stmt->bindParam(':receiving_encryption_public_key', $encryptionPublicKey); + $stmt->bindParam(':transport_encryption_algorithm', $transportEncryptionAlgorithm); + $stmt->bindParam(':transport_encryption_key', $encryptedTransportEncryptionKey); + $stmt->bindParam(':uuid', $channelUuid); + $stmt->execute(); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to accept the encryption channel', $e); + } + } + + /** + * Retrieves the encryption channel with the specified UUID. + * + * @param string $channelUuid The UUID of the channel to retrieve. + * @return EncryptionChannelRecord|null The record of the encryption channel. Null if the channel does not exist. + * @throws DatabaseOperationException If an error occurs while retrieving the channel. + * @throws \DateMalformedStringException If the created date is not a valid date string. + */ + public static function getChannel(string $channelUuid): ?EncryptionChannelRecord + { + try + { + $stmt = Database::getConnection()->prepare('SELECT * FROM encryption_channels WHERE uuid=:uuid'); + $stmt->bindParam(':uuid', $channelUuid); + $stmt->execute(); + $result = $stmt->fetch(); + + if($result === false) + { + return null; + } + + return new EncryptionChannelRecord($result); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to retrieve the encryption channel', $e); + } + } + + /** + * Deletes the encryption channel with the specified UUID. + * + * @param string $channelUuid The UUID of the channel to delete. + * @return void + *@throws DatabaseOperationException If an error occurs while deleting the channel. + */ + public static function deleteChannel(string $channelUuid): void + { + try + { + $stmt = Database::getConnection()->prepare('DELETE FROM encryption_channels WHERE uuid=:uuid'); + $stmt->bindParam(':uuid', $channelUuid); + $stmt->execute(); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to delete the encryption channel', $e); + } + } + + /** + * Updates the state of the encryption channel with the specified UUID. + * + * @param string $channelUuid The UUID of the channel to update. + * @return EncryptionChannelState The current state of the channel. + * @throws DatabaseOperationException If an error occurs while updating the channel state. + */ + public static function getChannelState(string $channelUuid): EncryptionChannelState + { + try + { + $stmt = Database::getConnection()->prepare('SELECT state FROM encryption_channels WHERE uuid=:uuid'); + $stmt->bindParam(':uuid', $channelUuid); + $stmt->execute(); + + return EncryptionChannelState::from($stmt->fetchColumn()); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to retrieve the encryption channel state', $e); + } + } + + /** + * Updates the state of the encryption channel with the specified UUID. + * + * @param string $channelUuid The UUID of the channel to update. + * @param EncryptionChannelState $state The new state of the channel. + * @return void The current state of the channel. + * @throws DatabaseOperationException If an error occurs while updating the channel state. + */ + public static function updateChannelState(string $channelUuid, EncryptionChannelState $state): void + { + try + { + $stmt = Database::getConnection()->prepare('UPDATE encryption_channels SET state=:state WHERE uuid=:uuid'); + $state = $state->value; + $stmt->bindParam(':state', $state); + $stmt->bindParam(':uuid', $channelUuid); + $stmt->execute(); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to update the encryption channel state', $e); + } + } + + /** + * Checks if a channel with the provided UUID exists. + * + * @param string $uuid The UUID of the channel to check. + * @return bool True if the channel exists, False otherwise. + * @throws DatabaseOperationException If an error occurs while checking the channel. + */ + public static function channelExists(string $uuid): bool + { + try + { + $stmt = Database::getConnection()->prepare('SELECT COUNT(*) FROM encryption_channels WHERE uuid=:uuid'); + $stmt->bindParam(':uuid', $uuid); + $stmt->execute(); + + return $stmt->fetchColumn() > 0; + } + catch(PDOException $e) + { + throw new DatabaseOperationException('There was an error while trying to check if the channel UUID exists', $e); + } + } + + /** + * Sends data to the specified channel. + * + * @param string $channelUuid The UUID of the channel to send the data to. + * @param string $message The message to send. + * @param string $signature The signature of the message. + * @param CommunicationRecipientType $recipient The recipient type. + * @return string The UUID of the sent message. + * @throws DatabaseOperationException If an error occurs while sending the message. + */ + public static function sendData(string $channelUuid, string $message, string $signature, CommunicationRecipientType $recipient): string + { + $uuid = UuidV4::v4()->toRfc4122(); + + try + { + $stmt = Database::getConnection()->prepare('INSERT INTO channel_com (uuid, channel_uuid, recipient, message, signature) VALUES (:uuid, :channel_uuid, :recipient, :message, :signature)'); + $stmt->bindParam(':uuid', $uuid); + $stmt->bindParam(':channel_uuid', $channelUuid); + $recipient = $recipient->value; + $stmt->bindParam(':recipient', $recipient); + $stmt->bindParam(':message', $message); + $stmt->bindParam(':signature', $signature); + + $stmt->execute(); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to send the message', $e); + } + + return $uuid; + } + + /** + * Retrieves the messages for the specified channel and recipient. + * + * @param string $channelUuid The UUID of the channel to retrieve the messages for. + * @param CommunicationRecipientType $recipient The recipient type to retrieve the messages for. + * @return ChannelMessageRecord[] The messages for the specified channel and recipient. + * @throws DatabaseOperationException If an error occurs while retrieving the messages. + */ + public static function receiveData(string $channelUuid, CommunicationRecipientType $recipient): array + { + try + { + $stmt = Database::getConnection()->prepare('SELECT * FROM channel_com WHERE channel_uuid=:channel_uuid AND recipient=:recipient AND received=0 ORDER BY timestamp'); + $stmt->bindParam(':channel_uuid', $channelUuid); + $recipient = $recipient->value; + $stmt->bindParam(':recipient', $recipient); + $stmt->execute(); + $results = $stmt->fetchAll(); + + $messages = []; + foreach($results as $result) + { + $messages[] = new ChannelMessageRecord($result); + } + + return $messages; + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to retrieve the messages', $e); + } + } + + /** + * Retrieves the message with the specified UUID. + * + * @param string $channelUuid The UUID of the channel to retrieve the message for. + * @param string $messageUuid The UUID of the message to retrieve. + * @return ChannelMessageRecord|null The message with the specified UUID. Null if the message does not exist. + * @throws DatabaseOperationException If an error occurs while retrieving the message. + */ + public static function getData(string $channelUuid, string $messageUuid): ?ChannelMessageRecord + { + try + { + $stmt = Database::getConnection()->prepare('SELECT * FROM channel_com WHERE channel_uuid=:channel_uuid AND uuid=:uuid'); + $stmt->bindParam(':channel_uuid', $channelUuid); + $stmt->bindParam(':uuid', $messageUuid); + $stmt->execute(); + $result = $stmt->fetch(); + + if($result === false) + { + return null; + } + + return new ChannelMessageRecord($result); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to retrieve the message', $e); + } + } + + /** + * Marks the message with the specified UUID as received. + * + * @param string $uuid The UUID of the message to mark as received. + * @throws DatabaseOperationException If an error occurs while marking the message as received. + */ + public static function markDataAsReceived(string $uuid): void + { + try + { + $stmt = Database::getConnection()->prepare('UPDATE channel_com SET received=1 WHERE uuid=:uuid'); + $stmt->bindParam(':uuid', $uuid); + $stmt->execute(); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to mark the message as received', $e); + } + } + + /** + * Deletes the message with the specified UUID. + * + * @param string $uuid The UUID of the message to delete. + * @throws DatabaseOperationException If an error occurs while deleting the message. + */ + public static function deleteData(string $uuid): void + { + try + { + $stmt = Database::getConnection()->prepare('DELETE FROM channel_com WHERE uuid=:uuid'); + $stmt->bindParam(':uuid', $uuid); + $stmt->execute(); + } + catch(PDOException $e) + { + throw new DatabaseOperationException('Failed to delete the message', $e); + } + } + } \ No newline at end of file diff --git a/src/Socialbox/Objects/Database/ChannelMessageRecord.php b/src/Socialbox/Objects/Database/ChannelMessageRecord.php new file mode 100644 index 0000000..aed0925 --- /dev/null +++ b/src/Socialbox/Objects/Database/ChannelMessageRecord.php @@ -0,0 +1,141 @@ +uuid = $data['uuid']; + $this->channelUuid = $data['channel_uuid']; + $this->recipient = CommunicationRecipientType::from($data['recipient']); + $this->message = $data['message']; + $this->signature = $data['signature']; + $this->received = (bool)$data['received']; + + if($data['timestamp'] instanceof \DateTime) + { + $this->timestamp = $data['timestamp']; + } + elseif(is_int($data['timestamp'])) + { + $this->timestamp = (new \DateTime())->setTimestamp($data['timestamp']); + } + elseif(is_string($data['timestamp'])) + { + $this->timestamp = new \DateTime($data['timestamp']); + } + else + { + throw new \InvalidArgumentException('Invalid timestamp type, got ' . gettype($data['timestamp'])); + } + } + + /** + * Returns the unique identifier for the message. + * + * @return string + */ + public function getUuid(): string + { + return $this->uuid; + } + + /** + * Returns the UUID of the channel that the message belongs to. + * + * @return string + */ + public function getChannelUuid(): string + { + return $this->channelUuid; + } + + /** + * Returns the recipient type of the message. + * + * @return CommunicationRecipientType + */ + public function getRecipient(): CommunicationRecipientType + { + return $this->recipient; + } + + /** + * Returns the message content. + * + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * Returns the signature of the message. + * + * @return string + */ + public function getSignature(): string + { + return $this->signature; + } + + public function isReceived(): bool + { + return $this->received; + } + + public function getTimestamp(): \DateTime + { + return $this->timestamp; + } + + /** + * @inheritDoc + */ + public static function fromArray(array $data): ChannelMessageRecord + { + return new self($data); + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return [ + 'uuid' => $this->uuid, + 'channel_uuid' => $this->channelUuid, + 'recipient' => $this->recipient->value, + 'message' => $this->message, + 'signature' => $this->signature, + 'received' => $this->received, + 'timestamp' => $this->timestamp->format('Y-m-d H:i:s') + ]; + } + } \ No newline at end of file diff --git a/src/Socialbox/Objects/Database/EncryptionChannelRecord.php b/src/Socialbox/Objects/Database/EncryptionChannelRecord.php new file mode 100644 index 0000000..c8979f0 --- /dev/null +++ b/src/Socialbox/Objects/Database/EncryptionChannelRecord.php @@ -0,0 +1,259 @@ +uuid = $data['uuid']; + + if(!isset($data['calling_peer'])) + { + throw new InvalidArgumentException('Missing property calling_peer'); + } + else + { + if(is_string($data['calling_peer'])) + { + $this->callingPeer = PeerAddress::fromAddress($data['calling_peer']); + } + elseif($data['calling_peer'] instanceof PeerAddress) + { + $this->callingPeer = $data['calling_peer']; + } + else + { + throw new InvalidArgumentException('Unexpected calling_peer type, got ' . gettype($data['calling_peer'])); + } + } + + $this->callingSignatureUuid = $data['calling_signature_uuid']; + $this->callingEncryptionPublicKey = $data['calling_encryption_public_key']; + + if(!isset($data['receiving_peer'])) + { + throw new InvalidArgumentException('Missing property receiving_peer'); + } + else + { + if(is_string($data['receiving_peer'])) + { + $this->receivingPeer = PeerAddress::fromAddress($data['receiving_peer']); + } + elseif($data['receiving_peer'] instanceof PeerAddress) + { + $this->receivingPeer = $data['receiving_peer']; + } + else + { + throw new InvalidArgumentException('Unexpected receiving_peer type, got ' . gettype($data['receiving_peer'])); + } + } + + $this->receivingSignatureUuid = $data['receiving_signature_uuid'] ?? null; + $this->receivingSignaturePublicKey = $data['receiving_signature_public_key'] ?? null; + $this->receivingEncryptionPublicKey = $data['receiving_encryption_public_key'] ?? null; + $this->transportEncryptionAlgorithm = $data['transport_encryption_algorithm']; + $this->transportEncryptionKey = $data['transport_encryption_key'] ?? null; + $this->state = EncryptionChannelState::tryFrom($data['state']) ?? EncryptionChannelState::ERROR; + + if(!isset($data['created'])) + { + throw new InvalidArgumentException('Missing property created'); + } + else + { + if(is_string($data['created'])) + { + $this->created = new DateTime($data['created']); + } + elseif(is_int($data['created'])) + { + $this->created = (new DateTime())->setTimestamp($data['created']); + } + elseif($data['created'] instanceof DateTime) + { + $this->created = $data['created']; + } + else + { + throw new InvalidArgumentException('Unexpected created type, got ' . gettype($data['created'])); + } + } + } + + /** + * Returns the Unique Universal Identifier for the encryption record + * + * @return string + */ + public function getUuid(): string + { + return $this->uuid; + } + + /** + * Returns the address of the calling peer + * + * @return PeerAddress + */ + public function getCallingPeer(): PeerAddress + { + return $this->callingPeer; + } + + /** + * Returns the UUID of the signing keypair that the caller is using + * + * @return string + */ + public function getCallingSignatureUuid(): string + { + return $this->callingSignatureUuid; + } + + /** + * Returns the public key of the encryption keypair that the caller is using + * + * @return string + */ + public function getCallingEncryptionPublicKey(): string + { + return $this->callingEncryptionPublicKey; + } + + /** + * Returns the address of the receiving peer + * + * @return PeerAddress + */ + public function getReceivingPeer(): PeerAddress + { + return $this->receivingPeer; + } + + /** + * Returns the UUID of the signing keypair that the receiver is using + * + * @return string|null + */ + public function getReceivingSignatureUuid(): ?string + { + return $this->receivingSignatureUuid; + } + + /** + * Returns the public key of the signing keypair that the receiver is using + * + * @return string|null + */ + public function getReceivingSignaturePublicKey(): ?string + { + return $this->receivingSignaturePublicKey; + } + + /** + * Returns the public key of the encryption keypair that the receiver is using + * + * @return string|null + */ + public function getReceivingEncryptionPublicKey(): ?string + { + return $this->receivingEncryptionPublicKey; + } + + /** + * Returns the algorithm used for transport encryption + * + * @return string + */ + public function getTransportEncryptionAlgorithm(): string + { + return $this->transportEncryptionAlgorithm; + } + + /** + * Returns the key used for transport encryption + * + * @return string|null + */ + public function getTransportEncryptionKey(): ?string + { + return $this->transportEncryptionKey; + } + + /** + * Returns the current state of the encryption channel + * + * @return EncryptionChannelState + */ + public function getState(): EncryptionChannelState + { + return $this->state; + } + + /** + * Returns the creation date of the encryption channel + * + * @return DateTime + */ + public function getCreated(): DateTime + { + return $this->created; + } + + /** + * @inheritDoc + */ + public static function fromArray(array $data): EncryptionChannelRecord + { + return new self($data); + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return [ + 'uuid' => $this->uuid, + 'calling_peer' => $this->callingPeer->getAddress(), + 'calling_signature_uuid' => $this->callingSignatureUuid, + 'calling_encryption_public_key' => $this->callingEncryptionPublicKey, + 'receiving_peer' => $this->receivingPeer->getAddress(), + 'receiving_signature_uuid' => $this->receivingSignatureUuid, + 'receiving_signature_public_key' => $this->receivingSignaturePublicKey, + 'receiving_encryption_public_key' => $this->receivingEncryptionPublicKey, + 'transport_encryption_algorithm' => $this->transportEncryptionAlgorithm, + 'transport_encryption_key' => $this->transportEncryptionKey, + 'state' => $this->state->value, + 'created' => $this->created->format('Y-m-d H:i:s') + ]; + } + } \ No newline at end of file