mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-29 13:20:39 +00:00
remove signature from att_write callback, add connection handle to att read and write callbacks, update examples
This commit is contained in:
parent
baecf7ef51
commit
7763678c43
@ -160,7 +160,7 @@ static void app_packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *
|
||||
#include "profile.h"
|
||||
|
||||
// write requests
|
||||
static int att_write_callback(uint16_t handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size, signature_t * signature){
|
||||
static int att_write_callback(uint16_t con_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){
|
||||
printf("WRITE Callback, handle %04x\n", handle);
|
||||
switch(handle){
|
||||
case ATT_CHARACTERISTIC_FFF1_01_VALUE_HANDLE:
|
||||
|
60
ble/att.c
60
ble/att.c
@ -164,18 +164,18 @@ uint16_t att_uuid_for_handle(uint16_t handle){
|
||||
}
|
||||
// end of client API
|
||||
|
||||
static void att_update_value_len(att_iterator_t *it){
|
||||
static void att_update_value_len(att_iterator_t *it, uint16_t con_handle){
|
||||
if ((it->flags & ATT_PROPERTY_DYNAMIC) == 0 || !att_read_callback) return;
|
||||
it->value_len = (*att_read_callback)(it->handle, 0, NULL, 0);
|
||||
it->value_len = (*att_read_callback)(con_handle, it->handle, 0, NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// copy attribute value from offset into buffer with given size
|
||||
static int att_copy_value(att_iterator_t *it, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
static int att_copy_value(att_iterator_t *it, uint16_t offset, uint8_t * buffer, uint16_t buffer_size, uint16_t con_handle){
|
||||
|
||||
// DYNAMIC
|
||||
if ((it->flags & ATT_PROPERTY_DYNAMIC) && att_read_callback) {
|
||||
return (*att_read_callback)(it->handle, offset, buffer, buffer_size);
|
||||
return (*att_read_callback)(con_handle, it->handle, offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
// STATIC
|
||||
@ -498,7 +498,7 @@ static uint16_t handle_read_by_type_request2(att_connection_t * att_connection,
|
||||
error_code = att_validate_security(att_connection, &it);
|
||||
if (error_code) break;
|
||||
|
||||
att_update_value_len(&it);
|
||||
att_update_value_len(&it, att_connection->con_handle);
|
||||
|
||||
// check if value has same len as last one
|
||||
uint16_t this_pair_len = 2 + it.value_len;
|
||||
@ -525,7 +525,7 @@ static uint16_t handle_read_by_type_request2(att_connection_t * att_connection,
|
||||
// store
|
||||
bt_store_16(response_buffer, offset, it.handle);
|
||||
offset += 2;
|
||||
uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len);
|
||||
uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len, att_connection->con_handle);
|
||||
offset += bytes_copied;
|
||||
}
|
||||
|
||||
@ -585,7 +585,7 @@ static uint16_t handle_read_request2(att_connection_t * att_connection, uint8_t
|
||||
return setup_error(response_buffer, request_type, handle, error_code);
|
||||
}
|
||||
|
||||
att_update_value_len(&it);
|
||||
att_update_value_len(&it, att_connection->con_handle);
|
||||
|
||||
uint16_t offset = 1;
|
||||
// limit data
|
||||
@ -594,7 +594,7 @@ static uint16_t handle_read_request2(att_connection_t * att_connection, uint8_t
|
||||
}
|
||||
|
||||
// store
|
||||
uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len);
|
||||
uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len, att_connection->con_handle);
|
||||
offset += bytes_copied;
|
||||
|
||||
response_buffer[0] = ATT_READ_RESPONSE;
|
||||
@ -630,7 +630,7 @@ static uint16_t handle_read_blob_request2(att_connection_t * att_connection, uin
|
||||
return setup_error(response_buffer, request_type, handle, error_code);
|
||||
}
|
||||
|
||||
att_update_value_len(&it);
|
||||
att_update_value_len(&it, att_connection->con_handle);
|
||||
|
||||
if (value_offset > it.value_len){
|
||||
return setup_error_invalid_offset(response_buffer, request_type, handle);
|
||||
@ -643,7 +643,7 @@ static uint16_t handle_read_blob_request2(att_connection_t * att_connection, uin
|
||||
}
|
||||
|
||||
// store
|
||||
uint16_t bytes_copied = att_copy_value(&it, value_offset, response_buffer + offset, it.value_len - value_offset);
|
||||
uint16_t bytes_copied = att_copy_value(&it, value_offset, response_buffer + offset, it.value_len - value_offset, att_connection->con_handle);
|
||||
offset += bytes_copied;
|
||||
|
||||
response_buffer[0] = ATT_READ_BLOB_RESPONSE;
|
||||
@ -696,7 +696,7 @@ static uint16_t handle_read_multiple_request2(att_connection_t * att_connection,
|
||||
error_code = att_validate_security(att_connection, &it);
|
||||
if (error_code) break;
|
||||
|
||||
att_update_value_len(&it);
|
||||
att_update_value_len(&it, att_connection->con_handle);
|
||||
|
||||
// limit data
|
||||
if (offset + it.value_len > response_buffer_size) {
|
||||
@ -704,7 +704,7 @@ static uint16_t handle_read_multiple_request2(att_connection_t * att_connection,
|
||||
}
|
||||
|
||||
// store
|
||||
uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len);
|
||||
uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len, att_connection->con_handle);
|
||||
offset += bytes_copied;
|
||||
}
|
||||
|
||||
@ -866,7 +866,7 @@ static uint16_t handle_write_request(att_connection_t * att_connection, uint8_t
|
||||
if (error_code) {
|
||||
return setup_error(response_buffer, request_type, handle, error_code);
|
||||
}
|
||||
error_code = (*att_write_callback)(handle, ATT_TRANSACTION_MODE_NONE, 0, request_buffer + 3, request_len - 3, NULL);
|
||||
error_code = (*att_write_callback)(att_connection->con_handle, handle, ATT_TRANSACTION_MODE_NONE, 0, request_buffer + 3, request_len - 3);
|
||||
if (error_code) {
|
||||
return setup_error(response_buffer, request_type, handle, error_code);
|
||||
}
|
||||
@ -903,7 +903,7 @@ static uint16_t handle_prepare_write_request(att_connection_t * att_connection,
|
||||
return setup_error(response_buffer, request_type, handle, error_code);
|
||||
}
|
||||
|
||||
error_code = (*att_write_callback)(handle, ATT_TRANSACTION_MODE_ACTIVE, offset, request_buffer + 5, request_len - 5, NULL);
|
||||
error_code = (*att_write_callback)(att_connection->con_handle, handle, ATT_TRANSACTION_MODE_ACTIVE, offset, request_buffer + 5, request_len - 5);
|
||||
switch (error_code){
|
||||
case 0:
|
||||
break;
|
||||
@ -925,9 +925,9 @@ static uint16_t handle_prepare_write_request(att_connection_t * att_connection,
|
||||
/*
|
||||
* @brief transcation queue of prepared writes, e.g., after disconnect
|
||||
*/
|
||||
void att_clear_transaction_queue(){
|
||||
void att_clear_transaction_queue(att_connection_t * att_connection){
|
||||
if (!att_write_callback) return;
|
||||
(*att_write_callback)(0, ATT_TRANSACTION_MODE_CANCEL, 0, NULL, 0, NULL);
|
||||
(*att_write_callback)(att_connection->con_handle, 0, ATT_TRANSACTION_MODE_CANCEL, 0, NULL, 0);
|
||||
}
|
||||
|
||||
// MARK: ATT_EXECUTE_WRITE_REQUEST 0x18
|
||||
@ -943,15 +943,15 @@ static uint16_t handle_execute_write_request(att_connection_t * att_connection,
|
||||
if (request_buffer[1]) {
|
||||
// deliver queued errors
|
||||
if (att_prepare_write_error_code){
|
||||
att_clear_transaction_queue();
|
||||
att_clear_transaction_queue(att_connection);
|
||||
uint8_t error_code = att_prepare_write_error_code;
|
||||
uint16_t handle = att_prepare_write_error_handle;
|
||||
att_prepare_write_reset();
|
||||
return setup_error(response_buffer, request_type, handle, error_code);
|
||||
}
|
||||
(*att_write_callback)(0, ATT_TRANSACTION_MODE_EXECUTE, 0, NULL, 0, NULL);
|
||||
(*att_write_callback)(att_connection->con_handle, 0, ATT_TRANSACTION_MODE_EXECUTE, 0, NULL, 0);
|
||||
} else {
|
||||
att_clear_transaction_queue();
|
||||
att_clear_transaction_queue(att_connection);
|
||||
}
|
||||
response_buffer[0] = ATT_EXECUTE_WRITE_RESPONSE;
|
||||
return 1;
|
||||
@ -971,25 +971,7 @@ static void handle_write_command(att_connection_t * att_connection, uint8_t * re
|
||||
if ((it.flags & ATT_PROPERTY_DYNAMIC) == 0) return;
|
||||
if ((it.flags & ATT_PROPERTY_WRITE_WITHOUT_RESPONSE) == 0) return;
|
||||
if (att_validate_security(att_connection, &it)) return;
|
||||
(*att_write_callback)(handle, ATT_TRANSACTION_MODE_NONE, 0, request_buffer + 3, request_len - 3, NULL);
|
||||
}
|
||||
|
||||
// MARK: ATT_SIGNED_WRITE_COMAND 0xD2
|
||||
// Core 4.0, vol 3, part F, 3.4.5.4
|
||||
// "No Error Response or Write Response shall be sent in response to this command"
|
||||
static void handle_signed_write_command(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len,
|
||||
uint8_t * response_buffer, uint16_t response_buffer_size){
|
||||
|
||||
if (request_len < 15) return;
|
||||
if (!att_write_callback) return;
|
||||
uint16_t handle = READ_BT_16(request_buffer, 1);
|
||||
att_iterator_t it;
|
||||
int ok = att_find_handle(&it, handle);
|
||||
if (!ok) return;
|
||||
if ((it.flags & ATT_PROPERTY_DYNAMIC) == 0) return;
|
||||
if ((it.flags & ATT_PROPERTY_AUTHENTICATED_SIGNED_WRITE) == 0) return;
|
||||
if (att_validate_security(att_connection, &it)) return;
|
||||
(*att_write_callback)(handle, ATT_TRANSACTION_MODE_NONE, 0, request_buffer + 3, request_len - 3 - 12, (signature_t *) request_buffer + request_len - 12);
|
||||
(*att_write_callback)(att_connection->con_handle, handle, ATT_TRANSACTION_MODE_NONE, 0, request_buffer + 3, request_len - 3);
|
||||
}
|
||||
|
||||
// MARK: helper for ATT_HANDLE_VALUE_NOTIFICATION and ATT_HANDLE_VALUE_INDICATION
|
||||
@ -1074,7 +1056,7 @@ uint16_t att_handle_request(att_connection_t * att_connection,
|
||||
handle_write_command(att_connection, request_buffer, request_len, response_buffer, response_buffer_size);
|
||||
break;
|
||||
case ATT_SIGNED_WRITE_COMAND:
|
||||
handle_signed_write_command(att_connection, request_buffer, request_len, response_buffer, response_buffer_size);
|
||||
printf("handle_signed_write_command preprocessed by att_server.c\n");
|
||||
break;
|
||||
default:
|
||||
printf("Unhandled ATT Command: %02X, DATA: ", request_buffer[0]);
|
||||
|
14
ble/att.h
14
ble/att.h
@ -148,29 +148,33 @@ extern "C" {
|
||||
|
||||
|
||||
typedef struct att_connection {
|
||||
uint16_t con_handle;
|
||||
uint16_t mtu;
|
||||
uint8_t encryption_key_size;
|
||||
uint8_t authenticated;
|
||||
uint8_t authorized;
|
||||
} att_connection_t;
|
||||
|
||||
typedef uint8_t signature_t[12];
|
||||
|
||||
// ATT Client Read Callback for Dynamic Data
|
||||
// - if buffer == NULL, don't copy data, just return size of value
|
||||
// - if buffer != NULL, copy data and return number bytes copied
|
||||
// @param con_handle of hci le connection
|
||||
// @param attribute_handle to be read
|
||||
// @param offset defines start of attribute value
|
||||
typedef uint16_t (*att_read_callback_t)(uint16_t handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
// @param buffer
|
||||
// @param buffer_size
|
||||
typedef uint16_t (*att_read_callback_t)(uint16_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
|
||||
// ATT Client Write Callback for Dynamic Data
|
||||
// @param handle to be written
|
||||
// @param con_handle of hci le connection
|
||||
// @param attribute_handle to be written
|
||||
// @param transaction - ATT_TRANSACTION_MODE_NONE for regular writes, ATT_TRANSACTION_MODE_ACTIVE for prepared writes and ATT_TRANSACTION_MODE_EXECUTE
|
||||
// @param offset into the value - used for queued writes and long attributes
|
||||
// @param buffer
|
||||
// @param buffer_size
|
||||
// @param signature used for signed write commmands
|
||||
// @returns 0 if write was ok, ATT_ERROR_PREPARE_QUEUE_FULL if no space in queue, ATT_ERROR_INVALID_OFFSET if offset is larger than max buffer
|
||||
typedef int (*att_write_callback_t)(uint16_t handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size, signature_t * signature);
|
||||
typedef int (*att_write_callback_t)(uint16_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size);
|
||||
|
||||
// MARK: ATT Operations
|
||||
|
||||
|
@ -75,7 +75,6 @@ static att_server_state_t att_server_state;
|
||||
|
||||
static uint8_t att_client_addr_type;
|
||||
static bd_addr_t att_client_address;
|
||||
static uint16_t att_request_handle = 0;
|
||||
static uint16_t att_request_size = 0;
|
||||
static uint8_t att_request_buffer[28];
|
||||
|
||||
@ -103,7 +102,7 @@ static void att_handle_value_indication_notify_client(uint8_t status, uint16_t c
|
||||
|
||||
static void att_handle_value_indication_timeout(timer_source_t *ts){
|
||||
uint16_t att_handle = att_handle_value_indication_handle;
|
||||
att_handle_value_indication_notify_client(ATT_HANDLE_VALUE_INDICATION_TIMEOUT, att_request_handle, att_handle);
|
||||
att_handle_value_indication_notify_client(ATT_HANDLE_VALUE_INDICATION_TIMEOUT, att_connection.con_handle, att_handle);
|
||||
}
|
||||
|
||||
static void att_event_packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
@ -121,10 +120,10 @@ static void att_event_packet_handler (uint8_t packet_type, uint16_t channel, uin
|
||||
switch (packet[2]) {
|
||||
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
|
||||
// store connection info
|
||||
att_request_handle = READ_BT_16(packet, 4);
|
||||
att_client_addr_type = packet[7];
|
||||
bt_flip_addr(att_client_address, &packet[8]);
|
||||
// reset connection properties
|
||||
att_connection.con_handle = READ_BT_16(packet, 4);
|
||||
att_connection.mtu = 23;
|
||||
att_connection.encryption_key_size = 0;
|
||||
att_connection.authenticated = 0;
|
||||
@ -138,18 +137,18 @@ static void att_event_packet_handler (uint8_t packet_type, uint16_t channel, uin
|
||||
|
||||
case HCI_EVENT_ENCRYPTION_CHANGE:
|
||||
// check handle
|
||||
if (att_request_handle != READ_BT_16(packet, 3)) break;
|
||||
if (att_connection.con_handle != READ_BT_16(packet, 3)) break;
|
||||
att_connection.encryption_key_size = sm_encryption_key_size(att_client_addr_type, att_client_address);
|
||||
att_connection.authenticated = sm_authenticated(att_client_addr_type, att_client_address);
|
||||
break;
|
||||
|
||||
case HCI_EVENT_DISCONNECTION_COMPLETE:
|
||||
att_clear_transaction_queue(&att_connection);
|
||||
att_connection.con_handle = 0;
|
||||
att_handle_value_indication_handle = 0; // reset error state
|
||||
// restart advertising if we have been connected before
|
||||
// -> avoid sending advertise enable a second time before command complete was received
|
||||
att_server_state = ATT_SERVER_IDLE;
|
||||
att_request_handle = 0;
|
||||
att_handle_value_indication_handle = 0; // reset error state
|
||||
att_clear_transaction_queue();
|
||||
break;
|
||||
|
||||
case SM_IDENTITY_RESOLVING_STARTED:
|
||||
@ -284,7 +283,7 @@ static void att_run(void){
|
||||
return;
|
||||
}
|
||||
|
||||
l2cap_send_prepared_connectionless(att_request_handle, L2CAP_CID_ATTRIBUTE_PROTOCOL, att_response_size);
|
||||
l2cap_send_prepared_connectionless(att_connection.con_handle, L2CAP_CID_ATTRIBUTE_PROTOCOL, att_response_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -297,7 +296,7 @@ static void att_packet_handler(uint8_t packet_type, uint16_t handle, uint8_t *pa
|
||||
run_loop_remove_timer(&att_handle_value_indication_timer);
|
||||
uint16_t att_handle = att_handle_value_indication_handle;
|
||||
att_handle_value_indication_handle = 0;
|
||||
att_handle_value_indication_notify_client(0, att_request_handle, att_handle);
|
||||
att_handle_value_indication_notify_client(0, att_connection.con_handle, att_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -333,7 +332,7 @@ void att_server_register_packet_handler(btstack_packet_handler_t handler){
|
||||
}
|
||||
|
||||
int att_server_can_send(){
|
||||
if (att_request_handle == 0) return 0;
|
||||
if (att_connection.con_handle == 0) return 0;
|
||||
return l2cap_can_send_connectionless_packet_now();
|
||||
}
|
||||
|
||||
@ -343,7 +342,7 @@ int att_server_notify(uint16_t handle, uint8_t *value, uint16_t value_len){
|
||||
l2cap_reserve_packet_buffer();
|
||||
uint8_t * packet_buffer = l2cap_get_outgoing_buffer();
|
||||
uint16_t size = att_prepare_handle_value_notification(&att_connection, handle, value, value_len, packet_buffer);
|
||||
return l2cap_send_prepared_connectionless(att_request_handle, L2CAP_CID_ATTRIBUTE_PROTOCOL, size);
|
||||
return l2cap_send_prepared_connectionless(att_connection.con_handle, L2CAP_CID_ATTRIBUTE_PROTOCOL, size);
|
||||
}
|
||||
|
||||
int att_server_indicate(uint16_t handle, uint8_t *value, uint16_t value_len){
|
||||
@ -359,6 +358,6 @@ int att_server_indicate(uint16_t handle, uint8_t *value, uint16_t value_len){
|
||||
l2cap_reserve_packet_buffer();
|
||||
uint8_t * packet_buffer = l2cap_get_outgoing_buffer();
|
||||
uint16_t size = att_prepare_handle_value_indication(&att_connection, handle, value, value_len, packet_buffer);
|
||||
l2cap_send_prepared_connectionless(att_request_handle, L2CAP_CID_ATTRIBUTE_PROTOCOL, size);
|
||||
l2cap_send_prepared_connectionless(att_connection.con_handle, L2CAP_CID_ATTRIBUTE_PROTOCOL, size);
|
||||
return 0;
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ static void app_run(){
|
||||
// - if buffer == NULL, don't copy data, just return size of value
|
||||
// - if buffer != NULL, copy data and return number bytes copied
|
||||
// @param offset defines start of attribute value
|
||||
static uint16_t att_read_callback(uint16_t handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
static uint16_t att_read_callback(uint16_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
|
||||
printf("READ Callback, handle %04x, offset %u, buffer size %u\n", handle, offset, buffer_size);
|
||||
uint16_t att_value_len;
|
||||
@ -374,7 +374,7 @@ static uint16_t att_read_callback(uint16_t handle, uint16_t offset, uint8_t * bu
|
||||
}
|
||||
|
||||
// write requests
|
||||
static int att_write_callback(uint16_t handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size, signature_t * signature){
|
||||
static int att_write_callback(uint16_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){
|
||||
printf("WRITE Callback, handle %04x, mode %u, offset %u, data: ", handle, transaction_mode, offset);
|
||||
hexdump(buffer, buffer_size);
|
||||
|
||||
|
@ -165,8 +165,8 @@ static void packet_handler (void * connection, uint8_t packet_type, uint16_t cha
|
||||
// - if buffer == NULL, don't copy data, just return size of value
|
||||
// - if buffer != NULL, copy data and return number bytes copied
|
||||
// @param offset defines start of attribute value
|
||||
static uint16_t att_read_callback(uint16_t handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
if (handle == ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_VALUE_HANDLE){
|
||||
static uint16_t att_read_callback(uint16_t con_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
if (att_handle == ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_VALUE_HANDLE){
|
||||
if (buffer){
|
||||
memcpy(buffer, &counter_string[offset], counter_string_len - offset);
|
||||
}
|
||||
@ -176,10 +176,10 @@ static uint16_t att_read_callback(uint16_t handle, uint16_t offset, uint8_t * bu
|
||||
}
|
||||
|
||||
// write requests
|
||||
static int att_write_callback(uint16_t handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size, signature_t * signature){
|
||||
static int att_write_callback(uint16_t con_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){
|
||||
// printf("WRITE Callback, handle %04x, mode %u, offset %u, data: ", handle, transaction_mode, offset);
|
||||
// hexdump(buffer, buffer_size);
|
||||
if (handle != ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_CLIENT_CONFIGURATION_HANDLE) return 0;
|
||||
if (att_handle != ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_CLIENT_CONFIGURATION_HANDLE) return 0;
|
||||
le_notification_enabled = READ_BT_16(buffer, 0) == GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION;
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user