A super trimmed down TLS stack, GPL licensed
Dependents: MiniTLS-HTTPS-Example
MiniTLS - A super trimmed down TLS/SSL Library for embedded devices Author: Donatien Garnier Copyright (C) 2013-2014 AppNearMe Ltd
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
tls/tls_socket.c@0:35aa5be3b78d, 2014-06-06 (annotated)
- Committer:
- MiniTLS
- Date:
- Fri Jun 06 10:49:02 2014 +0000
- Revision:
- 0:35aa5be3b78d
Initial commit
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
MiniTLS | 0:35aa5be3b78d | 1 | /* |
MiniTLS | 0:35aa5be3b78d | 2 | MuTLS - A super trimmed down TLS/SSL Library for embedded devices |
MiniTLS | 0:35aa5be3b78d | 3 | Author: Donatien Garnier |
MiniTLS | 0:35aa5be3b78d | 4 | Copyright (C) 2013-2014 AppNearMe Ltd |
MiniTLS | 0:35aa5be3b78d | 5 | |
MiniTLS | 0:35aa5be3b78d | 6 | This program is free software; you can redistribute it and/or |
MiniTLS | 0:35aa5be3b78d | 7 | modify it under the terms of the GNU General Public License |
MiniTLS | 0:35aa5be3b78d | 8 | as published by the Free Software Foundation; either version 2 |
MiniTLS | 0:35aa5be3b78d | 9 | of the License, or (at your option) any later version. |
MiniTLS | 0:35aa5be3b78d | 10 | |
MiniTLS | 0:35aa5be3b78d | 11 | This program is distributed in the hope that it will be useful, |
MiniTLS | 0:35aa5be3b78d | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
MiniTLS | 0:35aa5be3b78d | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
MiniTLS | 0:35aa5be3b78d | 14 | GNU General Public License for more details. |
MiniTLS | 0:35aa5be3b78d | 15 | |
MiniTLS | 0:35aa5be3b78d | 16 | You should have received a copy of the GNU General Public License |
MiniTLS | 0:35aa5be3b78d | 17 | along with this program; if not, write to the Free Software |
MiniTLS | 0:35aa5be3b78d | 18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
MiniTLS | 0:35aa5be3b78d | 19 | *//** |
MiniTLS | 0:35aa5be3b78d | 20 | * \file tls_socket.c |
MiniTLS | 0:35aa5be3b78d | 21 | * \copyright Copyright (c) AppNearMe Ltd 2013 |
MiniTLS | 0:35aa5be3b78d | 22 | * \author Donatien Garnier |
MiniTLS | 0:35aa5be3b78d | 23 | */ |
MiniTLS | 0:35aa5be3b78d | 24 | |
MiniTLS | 0:35aa5be3b78d | 25 | #define __DEBUG__ 0 |
MiniTLS | 0:35aa5be3b78d | 26 | #ifndef __MODULE__ |
MiniTLS | 0:35aa5be3b78d | 27 | #define __MODULE__ "tls_socket.c" |
MiniTLS | 0:35aa5be3b78d | 28 | #endif |
MiniTLS | 0:35aa5be3b78d | 29 | |
MiniTLS | 0:35aa5be3b78d | 30 | |
MiniTLS | 0:35aa5be3b78d | 31 | #include "core/fwk.h" |
MiniTLS | 0:35aa5be3b78d | 32 | #include "inc/mutls_errors.h" |
MiniTLS | 0:35aa5be3b78d | 33 | |
MiniTLS | 0:35aa5be3b78d | 34 | #include "tls_socket.h" |
MiniTLS | 0:35aa5be3b78d | 35 | |
MiniTLS | 0:35aa5be3b78d | 36 | mutls_err_t tls_socket_init(tls_socket_t* socket, mutls_t* mutls, |
MiniTLS | 0:35aa5be3b78d | 37 | uint8_t* write_buffer, size_t write_buffer_size, uint8_t* read_buffer, size_t read_buffer_size) |
MiniTLS | 0:35aa5be3b78d | 38 | { |
MiniTLS | 0:35aa5be3b78d | 39 | socket->mutls = mutls; |
MiniTLS | 0:35aa5be3b78d | 40 | |
MiniTLS | 0:35aa5be3b78d | 41 | socket->events = NULL; |
MiniTLS | 0:35aa5be3b78d | 42 | |
MiniTLS | 0:35aa5be3b78d | 43 | socket->read_buffer = NULL; |
MiniTLS | 0:35aa5be3b78d | 44 | buffer_init(&socket->write_buffer, write_buffer, write_buffer_size); |
MiniTLS | 0:35aa5be3b78d | 45 | |
MiniTLS | 0:35aa5be3b78d | 46 | socket->session.session_id_length = 0; |
MiniTLS | 0:35aa5be3b78d | 47 | |
MiniTLS | 0:35aa5be3b78d | 48 | mutls_err_t ret = tls_record_init(&socket->record, socket, read_buffer, read_buffer_size); |
MiniTLS | 0:35aa5be3b78d | 49 | if(ret) |
MiniTLS | 0:35aa5be3b78d | 50 | { |
MiniTLS | 0:35aa5be3b78d | 51 | return ret; |
MiniTLS | 0:35aa5be3b78d | 52 | } |
MiniTLS | 0:35aa5be3b78d | 53 | |
MiniTLS | 0:35aa5be3b78d | 54 | ret = tls_handshake_init(&socket->handshake, socket); |
MiniTLS | 0:35aa5be3b78d | 55 | if(ret) |
MiniTLS | 0:35aa5be3b78d | 56 | { |
MiniTLS | 0:35aa5be3b78d | 57 | return ret; |
MiniTLS | 0:35aa5be3b78d | 58 | } |
MiniTLS | 0:35aa5be3b78d | 59 | |
MiniTLS | 0:35aa5be3b78d | 60 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 61 | } |
MiniTLS | 0:35aa5be3b78d | 62 | |
MiniTLS | 0:35aa5be3b78d | 63 | mutls_err_t tls_socket_connect(tls_socket_t* socket, const char* hostname, uint16_t port, int timeout) |
MiniTLS | 0:35aa5be3b78d | 64 | { |
MiniTLS | 0:35aa5be3b78d | 65 | mutls_err_t ret = tls_record_connect(&socket->record, hostname, port); |
MiniTLS | 0:35aa5be3b78d | 66 | if(ret) |
MiniTLS | 0:35aa5be3b78d | 67 | { |
MiniTLS | 0:35aa5be3b78d | 68 | ERR("Could not connect"); |
MiniTLS | 0:35aa5be3b78d | 69 | return ret; |
MiniTLS | 0:35aa5be3b78d | 70 | } |
MiniTLS | 0:35aa5be3b78d | 71 | |
MiniTLS | 0:35aa5be3b78d | 72 | //TODO we could allocate the handshake structure here to save memory |
MiniTLS | 0:35aa5be3b78d | 73 | |
MiniTLS | 0:35aa5be3b78d | 74 | ret = tls_handshake_start(&socket->handshake); |
MiniTLS | 0:35aa5be3b78d | 75 | if(ret) |
MiniTLS | 0:35aa5be3b78d | 76 | { |
MiniTLS | 0:35aa5be3b78d | 77 | ERR("Could not start handshake"); |
MiniTLS | 0:35aa5be3b78d | 78 | return ret; |
MiniTLS | 0:35aa5be3b78d | 79 | } |
MiniTLS | 0:35aa5be3b78d | 80 | |
MiniTLS | 0:35aa5be3b78d | 81 | //Now wait for handshake to finish processing (or fail) |
MiniTLS | 0:35aa5be3b78d | 82 | while(!tls_handshake_is_done(&socket->handshake)) |
MiniTLS | 0:35aa5be3b78d | 83 | { |
MiniTLS | 0:35aa5be3b78d | 84 | DBG("Handshaking..."); |
MiniTLS | 0:35aa5be3b78d | 85 | ret = tls_record_process(&socket->record); |
MiniTLS | 0:35aa5be3b78d | 86 | if(ret) |
MiniTLS | 0:35aa5be3b78d | 87 | { |
MiniTLS | 0:35aa5be3b78d | 88 | ERR("Processing error"); |
MiniTLS | 0:35aa5be3b78d | 89 | return ret; |
MiniTLS | 0:35aa5be3b78d | 90 | } |
MiniTLS | 0:35aa5be3b78d | 91 | } |
MiniTLS | 0:35aa5be3b78d | 92 | |
MiniTLS | 0:35aa5be3b78d | 93 | DBG("Connected"); |
MiniTLS | 0:35aa5be3b78d | 94 | |
MiniTLS | 0:35aa5be3b78d | 95 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 96 | } |
MiniTLS | 0:35aa5be3b78d | 97 | |
MiniTLS | 0:35aa5be3b78d | 98 | void tls_socket_copy_session(tls_socket_t* to, tls_socket_t* from) |
MiniTLS | 0:35aa5be3b78d | 99 | { |
MiniTLS | 0:35aa5be3b78d | 100 | memcpy(&to->session, &from->session, sizeof(tls_session_t)); |
MiniTLS | 0:35aa5be3b78d | 101 | } |
MiniTLS | 0:35aa5be3b78d | 102 | |
MiniTLS | 0:35aa5be3b78d | 103 | //Events API -- select like |
MiniTLS | 0:35aa5be3b78d | 104 | mutls_err_t tls_socket_event_list_init(tls_socket_event_list_t* list) |
MiniTLS | 0:35aa5be3b78d | 105 | { |
MiniTLS | 0:35aa5be3b78d | 106 | list->head = NULL; |
MiniTLS | 0:35aa5be3b78d | 107 | |
MiniTLS | 0:35aa5be3b78d | 108 | //Create semaphore |
MiniTLS | 0:35aa5be3b78d | 109 | /* list->sem = rtos_sem_create(1, 0);*/ |
MiniTLS | 0:35aa5be3b78d | 110 | /*if(list->sem == NULL) |
MiniTLS | 0:35aa5be3b78d | 111 | { |
MiniTLS | 0:35aa5be3b78d | 112 | return TLS_ERR_MEMORY; |
MiniTLS | 0:35aa5be3b78d | 113 | }*/ //FIXME |
MiniTLS | 0:35aa5be3b78d | 114 | |
MiniTLS | 0:35aa5be3b78d | 115 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 116 | } |
MiniTLS | 0:35aa5be3b78d | 117 | |
MiniTLS | 0:35aa5be3b78d | 118 | mutls_err_t tls_socket_event_init_and_register(tls_socket_event_t* event, tls_socket_event_list_t* list, tls_socket_t* socket, bool read, bool write) |
MiniTLS | 0:35aa5be3b78d | 119 | { |
MiniTLS | 0:35aa5be3b78d | 120 | //Init event |
MiniTLS | 0:35aa5be3b78d | 121 | event->socket = socket; |
MiniTLS | 0:35aa5be3b78d | 122 | event->read = read; |
MiniTLS | 0:35aa5be3b78d | 123 | event->write = write; |
MiniTLS | 0:35aa5be3b78d | 124 | event->list = list; |
MiniTLS | 0:35aa5be3b78d | 125 | event->socket_list_next = NULL; |
MiniTLS | 0:35aa5be3b78d | 126 | event->event_list_next = NULL; |
MiniTLS | 0:35aa5be3b78d | 127 | |
MiniTLS | 0:35aa5be3b78d | 128 | //Add to select list (tail) |
MiniTLS | 0:35aa5be3b78d | 129 | if(list->head != NULL) |
MiniTLS | 0:35aa5be3b78d | 130 | { |
MiniTLS | 0:35aa5be3b78d | 131 | tls_socket_event_t* previous_event = list->head; |
MiniTLS | 0:35aa5be3b78d | 132 | while( previous_event->event_list_next != NULL ) |
MiniTLS | 0:35aa5be3b78d | 133 | { |
MiniTLS | 0:35aa5be3b78d | 134 | previous_event = previous_event->event_list_next; |
MiniTLS | 0:35aa5be3b78d | 135 | } |
MiniTLS | 0:35aa5be3b78d | 136 | previous_event->event_list_next = event; |
MiniTLS | 0:35aa5be3b78d | 137 | } |
MiniTLS | 0:35aa5be3b78d | 138 | else |
MiniTLS | 0:35aa5be3b78d | 139 | { |
MiniTLS | 0:35aa5be3b78d | 140 | list->head = event; |
MiniTLS | 0:35aa5be3b78d | 141 | } |
MiniTLS | 0:35aa5be3b78d | 142 | |
MiniTLS | 0:35aa5be3b78d | 143 | //Add to socket's event list (tail) |
MiniTLS | 0:35aa5be3b78d | 144 | if(socket->events != NULL) |
MiniTLS | 0:35aa5be3b78d | 145 | { |
MiniTLS | 0:35aa5be3b78d | 146 | tls_socket_event_t* previous_event = socket->events; |
MiniTLS | 0:35aa5be3b78d | 147 | while( previous_event->socket_list_next != NULL ) |
MiniTLS | 0:35aa5be3b78d | 148 | { |
MiniTLS | 0:35aa5be3b78d | 149 | previous_event = previous_event->socket_list_next; |
MiniTLS | 0:35aa5be3b78d | 150 | } |
MiniTLS | 0:35aa5be3b78d | 151 | previous_event->socket_list_next = event; |
MiniTLS | 0:35aa5be3b78d | 152 | } |
MiniTLS | 0:35aa5be3b78d | 153 | else |
MiniTLS | 0:35aa5be3b78d | 154 | { |
MiniTLS | 0:35aa5be3b78d | 155 | socket->events = event; |
MiniTLS | 0:35aa5be3b78d | 156 | } |
MiniTLS | 0:35aa5be3b78d | 157 | |
MiniTLS | 0:35aa5be3b78d | 158 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 159 | } |
MiniTLS | 0:35aa5be3b78d | 160 | |
MiniTLS | 0:35aa5be3b78d | 161 | mutls_err_t tls_socket_event_list_wait(tls_socket_event_list_t* list, int timeout) //All events unregistered at the end of wait, timeout in ms, -1 for infinite timeout |
MiniTLS | 0:35aa5be3b78d | 162 | {/* |
MiniTLS | 0:35aa5be3b78d | 163 | if( rtos_sem_get(list->sem, timeout) ) //FIXME |
MiniTLS | 0:35aa5be3b78d | 164 | {*/ |
MiniTLS | 0:35aa5be3b78d | 165 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 166 | /*} |
MiniTLS | 0:35aa5be3b78d | 167 | else |
MiniTLS | 0:35aa5be3b78d | 168 | { |
MiniTLS | 0:35aa5be3b78d | 169 | return TLS_ERR_TIMEOUT; |
MiniTLS | 0:35aa5be3b78d | 170 | }*/ |
MiniTLS | 0:35aa5be3b78d | 171 | } |
MiniTLS | 0:35aa5be3b78d | 172 | |
MiniTLS | 0:35aa5be3b78d | 173 | //These calls are non-blocking |
MiniTLS | 0:35aa5be3b78d | 174 | mutls_err_t tls_socket_read(tls_socket_t* socket, uint8_t* bytes, size_t max_size, size_t* read_size) |
MiniTLS | 0:35aa5be3b78d | 175 | { |
MiniTLS | 0:35aa5be3b78d | 176 | DBG("Reading at most %d bytes", max_size); |
MiniTLS | 0:35aa5be3b78d | 177 | if(socket->read_buffer == NULL) //Socket is not ready |
MiniTLS | 0:35aa5be3b78d | 178 | { |
MiniTLS | 0:35aa5be3b78d | 179 | WARN("Socket is not ready or has been closed"); |
MiniTLS | 0:35aa5be3b78d | 180 | *read_size = 0; |
MiniTLS | 0:35aa5be3b78d | 181 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 182 | } |
MiniTLS | 0:35aa5be3b78d | 183 | |
MiniTLS | 0:35aa5be3b78d | 184 | size_t length = buffer_length(socket->read_buffer); |
MiniTLS | 0:35aa5be3b78d | 185 | *read_size = MIN(max_size, length); |
MiniTLS | 0:35aa5be3b78d | 186 | |
MiniTLS | 0:35aa5be3b78d | 187 | buffer_nbytes_read(socket->read_buffer, bytes, *read_size); |
MiniTLS | 0:35aa5be3b78d | 188 | |
MiniTLS | 0:35aa5be3b78d | 189 | DBG("%d bytes read", *read_size); |
MiniTLS | 0:35aa5be3b78d | 190 | |
MiniTLS | 0:35aa5be3b78d | 191 | //If buffer has been fully read, invalidate read_buffer |
MiniTLS | 0:35aa5be3b78d | 192 | if( buffer_length(socket->read_buffer) == 0 ) |
MiniTLS | 0:35aa5be3b78d | 193 | { |
MiniTLS | 0:35aa5be3b78d | 194 | socket->read_buffer = NULL; |
MiniTLS | 0:35aa5be3b78d | 195 | } |
MiniTLS | 0:35aa5be3b78d | 196 | |
MiniTLS | 0:35aa5be3b78d | 197 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 198 | } |
MiniTLS | 0:35aa5be3b78d | 199 | |
MiniTLS | 0:35aa5be3b78d | 200 | mutls_err_t tls_socket_write(tls_socket_t* socket, uint8_t* bytes, size_t size, size_t* written_size) |
MiniTLS | 0:35aa5be3b78d | 201 | { |
MiniTLS | 0:35aa5be3b78d | 202 | size_t length = buffer_space(&socket->write_buffer); |
MiniTLS | 0:35aa5be3b78d | 203 | *written_size = MIN(size, length); |
MiniTLS | 0:35aa5be3b78d | 204 | |
MiniTLS | 0:35aa5be3b78d | 205 | buffer_nbytes_write(&socket->write_buffer, bytes, *written_size); |
MiniTLS | 0:35aa5be3b78d | 206 | |
MiniTLS | 0:35aa5be3b78d | 207 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 208 | } |
MiniTLS | 0:35aa5be3b78d | 209 | |
MiniTLS | 0:35aa5be3b78d | 210 | mutls_err_t tls_socket_flush_read(tls_socket_t* socket, int timeout) |
MiniTLS | 0:35aa5be3b78d | 211 | { |
MiniTLS | 0:35aa5be3b78d | 212 | DBG("Get new bytes"); |
MiniTLS | 0:35aa5be3b78d | 213 | if( socket->read_buffer != NULL ) //No need to read more bytes |
MiniTLS | 0:35aa5be3b78d | 214 | { |
MiniTLS | 0:35aa5be3b78d | 215 | WARN("Buffer is already non-empty"); |
MiniTLS | 0:35aa5be3b78d | 216 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 217 | } |
MiniTLS | 0:35aa5be3b78d | 218 | |
MiniTLS | 0:35aa5be3b78d | 219 | tls_record_set_read_timeout(&socket->record, timeout); |
MiniTLS | 0:35aa5be3b78d | 220 | |
MiniTLS | 0:35aa5be3b78d | 221 | int ret; |
MiniTLS | 0:35aa5be3b78d | 222 | do |
MiniTLS | 0:35aa5be3b78d | 223 | { |
MiniTLS | 0:35aa5be3b78d | 224 | DBG("Processing TLS packet"); |
MiniTLS | 0:35aa5be3b78d | 225 | ret = tls_record_process(&socket->record); |
MiniTLS | 0:35aa5be3b78d | 226 | if(ret) |
MiniTLS | 0:35aa5be3b78d | 227 | { |
MiniTLS | 0:35aa5be3b78d | 228 | return ret; |
MiniTLS | 0:35aa5be3b78d | 229 | } |
MiniTLS | 0:35aa5be3b78d | 230 | } while( socket->read_buffer == NULL ); |
MiniTLS | 0:35aa5be3b78d | 231 | |
MiniTLS | 0:35aa5be3b78d | 232 | DBG("Buffer has been filled"); |
MiniTLS | 0:35aa5be3b78d | 233 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 234 | } |
MiniTLS | 0:35aa5be3b78d | 235 | |
MiniTLS | 0:35aa5be3b78d | 236 | mutls_err_t tls_socket_flush_write(tls_socket_t* socket, int timeout) |
MiniTLS | 0:35aa5be3b78d | 237 | { |
MiniTLS | 0:35aa5be3b78d | 238 | if(buffer_length(&socket->write_buffer) == 0) |
MiniTLS | 0:35aa5be3b78d | 239 | { |
MiniTLS | 0:35aa5be3b78d | 240 | return MUTLS_OK; //Useless to flush |
MiniTLS | 0:35aa5be3b78d | 241 | } |
MiniTLS | 0:35aa5be3b78d | 242 | |
MiniTLS | 0:35aa5be3b78d | 243 | tls_record_set_write_timeout(&socket->record, timeout); |
MiniTLS | 0:35aa5be3b78d | 244 | |
MiniTLS | 0:35aa5be3b78d | 245 | //Pass buffer to record layer |
MiniTLS | 0:35aa5be3b78d | 246 | mutls_err_t ret = tls_record_send(&socket->record, TLS_APPLICATION_DATA, &socket->write_buffer); |
MiniTLS | 0:35aa5be3b78d | 247 | |
MiniTLS | 0:35aa5be3b78d | 248 | //TODO: Advertise writeable here |
MiniTLS | 0:35aa5be3b78d | 249 | |
MiniTLS | 0:35aa5be3b78d | 250 | if(ret) |
MiniTLS | 0:35aa5be3b78d | 251 | { |
MiniTLS | 0:35aa5be3b78d | 252 | return ret; |
MiniTLS | 0:35aa5be3b78d | 253 | } |
MiniTLS | 0:35aa5be3b78d | 254 | |
MiniTLS | 0:35aa5be3b78d | 255 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 256 | } |
MiniTLS | 0:35aa5be3b78d | 257 | |
MiniTLS | 0:35aa5be3b78d | 258 | //Called from record layer |
MiniTLS | 0:35aa5be3b78d | 259 | mutls_err_t tls_socket_readable_callback(tls_socket_t* socket, buffer_t* buffer) |
MiniTLS | 0:35aa5be3b78d | 260 | { |
MiniTLS | 0:35aa5be3b78d | 261 | socket->read_buffer = buffer; |
MiniTLS | 0:35aa5be3b78d | 262 | |
MiniTLS | 0:35aa5be3b78d | 263 | //Process events |
MiniTLS | 0:35aa5be3b78d | 264 | |
MiniTLS | 0:35aa5be3b78d | 265 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 266 | } |
MiniTLS | 0:35aa5be3b78d | 267 | |
MiniTLS | 0:35aa5be3b78d | 268 | mutls_err_t tls_socket_close(tls_socket_t* socket) |
MiniTLS | 0:35aa5be3b78d | 269 | { |
MiniTLS | 0:35aa5be3b78d | 270 | mutls_err_t ret = tls_record_close(&socket->record); |
MiniTLS | 0:35aa5be3b78d | 271 | if(ret) |
MiniTLS | 0:35aa5be3b78d | 272 | { |
MiniTLS | 0:35aa5be3b78d | 273 | return ret; |
MiniTLS | 0:35aa5be3b78d | 274 | } |
MiniTLS | 0:35aa5be3b78d | 275 | return MUTLS_OK; |
MiniTLS | 0:35aa5be3b78d | 276 | } |