2 * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL
);
29 #include <ipxe/http.h>
30 #include <ipxe/iobuf.h>
31 #include <ipxe/xfer.h>
33 #include <ipxe/timer.h>
34 #include <ipxe/profile.h>
35 #include <ipxe/fault.h>
36 #include <ipxe/pccrr.h>
37 #include <ipxe/peerblk.h>
41 * Peer Content Caching and Retrieval (PeerDist) protocol block downloads
45 /** PeerDist decryption chunksize
47 * This is a policy decision.
49 #define PEERBLK_DECRYPT_CHUNKSIZE 2048
51 /** PeerDist raw block download attempt initial progress timeout
53 * This is a policy decision.
55 #define PEERBLK_RAW_OPEN_TIMEOUT ( 10 * TICKS_PER_SEC )
57 /** PeerDist raw block download attempt ongoing progress timeout
59 * This is a policy decision.
61 #define PEERBLK_RAW_RX_TIMEOUT ( 15 * TICKS_PER_SEC )
63 /** PeerDist retrieval protocol block download attempt initial progress timeout
65 * This is a policy decision.
67 #define PEERBLK_RETRIEVAL_OPEN_TIMEOUT ( 3 * TICKS_PER_SEC )
69 /** PeerDist retrieval protocol block download attempt ongoing progress timeout
71 * This is a policy decision.
73 #define PEERBLK_RETRIEVAL_RX_TIMEOUT ( 5 * TICKS_PER_SEC )
75 /** PeerDist maximum number of full download attempt cycles
77 * This is the maximum number of times that we will try a full cycle
78 * of download attempts (i.e. a retrieval protocol download attempt
79 * from each discovered peer plus a raw download attempt from the
82 * This is a policy decision.
84 #define PEERBLK_MAX_ATTEMPT_CYCLES 4
86 /** PeerDist block download profiler */
87 static struct profiler peerblk_download_profiler __profiler
=
88 { .name
= "peerblk.download" };
90 /** PeerDist block download attempt success profiler */
91 static struct profiler peerblk_attempt_success_profiler __profiler
=
92 { .name
= "peerblk.attempt.success" };
94 /** PeerDist block download attempt failure profiler */
95 static struct profiler peerblk_attempt_failure_profiler __profiler
=
96 { .name
= "peerblk.attempt.failure" };
98 /** PeerDist block download attempt timeout profiler */
99 static struct profiler peerblk_attempt_timeout_profiler __profiler
=
100 { .name
= "peerblk.attempt.timeout" };
102 /** PeerDist block download discovery success profiler */
103 static struct profiler peerblk_discovery_success_profiler __profiler
=
104 { .name
= "peerblk.discovery.success" };
106 /** PeerDist block download discovery timeout profiler */
107 static struct profiler peerblk_discovery_timeout_profiler __profiler
=
108 { .name
= "peerblk.discovery.timeout" };
111 * Get profiling timestamp
113 * @ret timestamp Timestamp
115 static inline __attribute__ (( always_inline
)) unsigned long
116 peerblk_timestamp ( void ) {
126 * Free PeerDist block download
128 * @v refcnt Reference count
130 static void peerblk_free ( struct refcnt
*refcnt
) {
131 struct peerdist_block
*peerblk
=
132 container_of ( refcnt
, struct peerdist_block
, refcnt
);
134 uri_put ( peerblk
->uri
);
135 free ( peerblk
->cipherctx
);
140 * Reset PeerDist block download attempt
142 * @v peerblk PeerDist block download
143 * @v rc Reason for reset
145 static void peerblk_reset ( struct peerdist_block
*peerblk
, int rc
) {
147 /* Stop decryption process */
148 process_del ( &peerblk
->process
);
151 stop_timer ( &peerblk
->timer
);
153 /* Abort any current download attempt */
154 intf_restart ( &peerblk
->raw
, rc
);
155 intf_restart ( &peerblk
->retrieval
, rc
);
157 /* Empty received data buffer */
158 xferbuf_free ( &peerblk
->buffer
);
161 /* Reset digest and free cipher context */
162 digest_init ( peerblk
->digest
, peerblk
->digestctx
);
163 free ( peerblk
->cipherctx
);
164 peerblk
->cipherctx
= NULL
;
165 peerblk
->cipher
= NULL
;
167 /* Reset trim thresholds */
168 peerblk
->start
= ( peerblk
->trim
.start
- peerblk
->range
.start
);
169 peerblk
->end
= ( peerblk
->trim
.end
- peerblk
->range
.start
);
170 assert ( peerblk
->start
<= peerblk
->end
);
174 * Close PeerDist block download
176 * @v peerblk PeerDist block download
177 * @v rc Reason for close
179 static void peerblk_close ( struct peerdist_block
*peerblk
, int rc
) {
180 unsigned long now
= peerblk_timestamp();
182 /* Profile overall block download */
183 profile_custom ( &peerblk_download_profiler
,
184 ( now
- peerblk
->started
) );
186 /* Reset download attempt */
187 peerblk_reset ( peerblk
, rc
);
189 /* Close discovery */
190 peerdisc_close ( &peerblk
->discovery
);
192 /* Shut down all interfaces */
193 intf_shutdown ( &peerblk
->retrieval
, rc
);
194 intf_shutdown ( &peerblk
->raw
, rc
);
195 intf_shutdown ( &peerblk
->xfer
, rc
);
199 * Calculate offset within overall download
201 * @v peerblk PeerDist block download
202 * @v pos Position within incoming data stream
203 * @ret offset Offset within overall download
205 static inline __attribute__ (( always_inline
)) size_t
206 peerblk_offset ( struct peerdist_block
*peerblk
, size_t pos
) {
208 return ( ( pos
- peerblk
->start
) + peerblk
->offset
);
212 * Deliver download attempt data block
214 * @v peerblk PeerDist block download
215 * @v iobuf I/O buffer
216 * @v meta Original data transfer metadata
217 * @v pos Position within incoming data stream
218 * @ret rc Return status code
220 static int peerblk_deliver ( struct peerdist_block
*peerblk
,
221 struct io_buffer
*iobuf
,
222 struct xfer_metadata
*meta
, size_t pos
) {
223 struct xfer_metadata xfer_meta
;
224 size_t len
= iob_len ( iobuf
);
226 size_t end
= ( pos
+ len
);
229 /* Discard zero-length packets and packets which lie entirely
230 * outside the trimmed range.
232 if ( ( start
>= peerblk
->end
) || ( end
<= peerblk
->start
) ||
238 /* Truncate data to within trimmed range */
239 if ( start
< peerblk
->start
) {
240 iob_pull ( iobuf
, ( peerblk
->start
- start
) );
241 start
= peerblk
->start
;
243 if ( end
> peerblk
->end
) {
244 iob_unput ( iobuf
, ( end
- peerblk
->end
) );
248 /* Construct metadata */
249 memcpy ( &xfer_meta
, meta
, sizeof ( xfer_meta
) );
250 xfer_meta
.flags
|= XFER_FL_ABS_OFFSET
;
251 xfer_meta
.offset
= peerblk_offset ( peerblk
, start
);
254 if ( ( rc
= xfer_deliver ( &peerblk
->xfer
, iob_disown ( iobuf
),
255 &xfer_meta
) ) != 0 ) {
256 DBGC ( peerblk
, "PEERBLK %p %d.%d could not deliver data: %s\n",
257 peerblk
, peerblk
->segment
, peerblk
->block
,
266 * Finish PeerDist block download attempt
268 * @v peerblk PeerDist block download
269 * @v rc Reason for close
271 static void peerblk_done ( struct peerdist_block
*peerblk
, int rc
) {
272 struct digest_algorithm
*digest
= peerblk
->digest
;
273 uint8_t hash
[digest
->digestsize
];
274 unsigned long now
= peerblk_timestamp();
276 /* Check for errors on completion */
278 DBGC ( peerblk
, "PEERBLK %p %d.%d attempt failed: %s\n",
279 peerblk
, peerblk
->segment
, peerblk
->block
,
285 digest_final ( digest
, peerblk
->digestctx
, hash
);
286 if ( memcmp ( hash
, peerblk
->hash
, peerblk
->digestsize
) != 0 ) {
287 DBGC ( peerblk
, "PEERBLK %p %d.%d digest mismatch:\n",
288 peerblk
, peerblk
->segment
, peerblk
->block
);
289 DBGC_HDA ( peerblk
, 0, hash
, peerblk
->digestsize
);
290 DBGC_HDA ( peerblk
, 0, peerblk
->hash
, peerblk
->digestsize
);
295 /* Profile successful attempt */
296 profile_custom ( &peerblk_attempt_success_profiler
,
297 ( now
- peerblk
->attempted
) );
300 peerblk_close ( peerblk
, 0 );
304 /* Record failure reason and schedule a retry attempt */
305 profile_custom ( &peerblk_attempt_failure_profiler
,
306 ( now
- peerblk
->attempted
) );
307 peerblk_reset ( peerblk
, rc
);
309 start_timer_nodelay ( &peerblk
->timer
);
312 /******************************************************************************
314 * Raw block download attempts (using an HTTP range request)
316 ******************************************************************************
320 * Open PeerDist raw block download attempt
322 * @v peerblk PeerDist block download
323 * @ret rc Return status code
325 static int peerblk_raw_open ( struct peerdist_block
*peerblk
) {
326 struct http_request_range range
;
329 DBGC2 ( peerblk
, "PEERBLK %p %d.%d attempting raw range request\n",
330 peerblk
, peerblk
->segment
, peerblk
->block
);
332 /* Construct HTTP range */
333 memset ( &range
, 0, sizeof ( range
) );
334 range
.start
= peerblk
->range
.start
;
335 range
.len
= ( peerblk
->range
.end
- peerblk
->range
.start
);
337 /* Initiate range request to retrieve block */
338 if ( ( rc
= http_open ( &peerblk
->raw
, &http_get
, peerblk
->uri
,
339 &range
, NULL
) ) != 0 ) {
340 DBGC ( peerblk
, "PEERBLK %p %d.%d could not create range "
341 "request: %s\n", peerblk
, peerblk
->segment
,
342 peerblk
->block
, strerror ( rc
) );
346 /* Annul HTTP connection (for testing) if applicable. Do not
347 * report as an immediate error, in order to test our ability
348 * to recover from a totally unresponsive HTTP server.
350 if ( inject_fault ( PEERBLK_ANNUL_RATE
) )
351 intf_restart ( &peerblk
->raw
, 0 );
357 * Receive PeerDist raw data
359 * @v peerblk PeerDist block download
360 * @v iobuf I/O buffer
361 * @v meta Data transfer metadata
362 * @ret rc Return status code
364 static int peerblk_raw_rx ( struct peerdist_block
*peerblk
,
365 struct io_buffer
*iobuf
,
366 struct xfer_metadata
*meta
) {
367 size_t len
= iob_len ( iobuf
);
368 size_t pos
= peerblk
->pos
;
369 size_t mid
= ( ( peerblk
->range
.end
- peerblk
->range
.start
) / 2 );
372 /* Corrupt received data (for testing) if applicable */
373 inject_corruption ( PEERBLK_CORRUPT_RATE
, iobuf
->data
, len
);
375 /* Fail if data is delivered out of order, since the streaming
376 * digest requires strict ordering.
378 if ( ( rc
= xfer_check_order ( meta
, &peerblk
->pos
, len
) ) != 0 )
381 /* Add data to digest */
382 digest_update ( peerblk
->digest
, peerblk
->digestctx
, iobuf
->data
, len
);
385 if ( ( rc
= peerblk_deliver ( peerblk
, iob_disown ( iobuf
), meta
,
389 /* Extend download attempt timer */
390 start_timer_fixed ( &peerblk
->timer
, PEERBLK_RAW_RX_TIMEOUT
);
392 /* Stall download attempt (for testing) if applicable */
393 if ( ( pos
< mid
) && ( ( pos
+ len
) >= mid
) &&
394 ( ( rc
= inject_fault ( PEERBLK_STALL_RATE
) ) != 0 ) ) {
395 intf_restart ( &peerblk
->raw
, rc
);
402 peerblk_done ( peerblk
, rc
);
407 * Close PeerDist raw block download attempt
409 * @v peerblk PeerDist block download
410 * @v rc Reason for close
412 static void peerblk_raw_close ( struct peerdist_block
*peerblk
, int rc
) {
414 /* Restart interface */
415 intf_restart ( &peerblk
->raw
, rc
);
417 /* Fail immediately if we have an error */
421 /* Abort download attempt (for testing) if applicable */
422 if ( ( rc
= inject_fault ( PEERBLK_ABORT_RATE
) ) != 0 )
426 /* Complete download attempt */
427 peerblk_done ( peerblk
, rc
);
430 /******************************************************************************
432 * Retrieval protocol block download attempts (using HTTP POST)
434 ******************************************************************************
438 * Construct PeerDist retrieval protocol URI
440 * @v location Peer location
441 * @ret uri Retrieval URI, or NULL on error
443 static struct uri
* peerblk_retrieval_uri ( const char *location
) {
444 char uri_string
[ 7 /* "http://" */ + strlen ( location
) +
445 sizeof ( PEERDIST_MAGIC_PATH
/* includes NUL */ ) ];
447 /* Construct URI string */
448 snprintf ( uri_string
, sizeof ( uri_string
),
449 ( "http://%s" PEERDIST_MAGIC_PATH
), location
);
451 /* Parse URI string */
452 return parse_uri ( uri_string
);
456 * Open PeerDist retrieval protocol block download attempt
458 * @v peerblk PeerDist block download
459 * @v location Peer location
460 * @ret rc Return status code
462 static int peerblk_retrieval_open ( struct peerdist_block
*peerblk
,
463 const char *location
) {
464 size_t digestsize
= peerblk
->digestsize
;
465 peerdist_msg_getblks_t ( digestsize
, 1, 0 ) req
;
466 peerblk_msg_blk_t ( digestsize
, 0, 0, 0 ) *rsp
;
467 struct http_request_content content
;
471 DBGC2 ( peerblk
, "PEERBLK %p %d.%d attempting retrieval from %s\n",
472 peerblk
, peerblk
->segment
, peerblk
->block
, location
);
474 /* Construct block fetch request */
475 memset ( &req
, 0, sizeof ( req
) );
476 req
.getblks
.hdr
.version
.raw
= htonl ( PEERDIST_MSG_GETBLKS_VERSION
);
477 req
.getblks
.hdr
.type
= htonl ( PEERDIST_MSG_GETBLKS_TYPE
);
478 req
.getblks
.hdr
.len
= htonl ( sizeof ( req
) );
479 req
.getblks
.hdr
.algorithm
= htonl ( PEERDIST_MSG_AES_128_CBC
);
480 req
.segment
.segment
.digestsize
= htonl ( digestsize
);
481 memcpy ( req
.segment
.id
, peerblk
->id
, digestsize
);
482 req
.ranges
.ranges
.count
= htonl ( 1 );
483 req
.ranges
.range
[0].first
= htonl ( peerblk
->block
);
484 req
.ranges
.range
[0].count
= htonl ( 1 );
486 /* Construct POST request content */
487 memset ( &content
, 0, sizeof ( content
) );
489 content
.len
= sizeof ( req
);
492 if ( ( uri
= peerblk_retrieval_uri ( location
) ) == NULL
) {
497 /* Update trim thresholds */
498 peerblk
->start
+= offsetof ( typeof ( *rsp
), msg
.vrf
);
499 peerblk
->end
+= offsetof ( typeof ( *rsp
), msg
.vrf
);
501 /* Initiate HTTP POST to retrieve block */
502 if ( ( rc
= http_open ( &peerblk
->retrieval
, &http_post
, uri
,
503 NULL
, &content
) ) != 0 ) {
504 DBGC ( peerblk
, "PEERBLK %p %d.%d could not create retrieval "
505 "request: %s\n", peerblk
, peerblk
->segment
,
506 peerblk
->block
, strerror ( rc
) );
510 /* Annul HTTP connection (for testing) if applicable. Do not
511 * report as an immediate error, in order to test our ability
512 * to recover from a totally unresponsive HTTP server.
514 if ( inject_fault ( PEERBLK_ANNUL_RATE
) )
515 intf_restart ( &peerblk
->retrieval
, 0 );
524 * Receive PeerDist retrieval protocol data
526 * @v peerblk PeerDist block download
527 * @v iobuf I/O buffer
528 * @v meta Data transfer metadata
529 * @ret rc Return status code
531 static int peerblk_retrieval_rx ( struct peerdist_block
*peerblk
,
532 struct io_buffer
*iobuf
,
533 struct xfer_metadata
*meta
) {
534 size_t len
= iob_len ( iobuf
);
542 /* Some genius at Microsoft thought it would be a great idea
543 * to place the AES-CBC initialisation vector *after* the
544 * encrypted data, thereby making it logically impossible to
545 * decrypt each packet as it arrives.
547 * To work around this mindless stupidity, we deliver the
548 * ciphertext as-is and later use xfer_buffer() to obtain
549 * access to the underlying data transfer buffer in order to
550 * perform the decryption.
552 * There will be some data both before and after the bytes
553 * corresponding to the trimmed plaintext: a MSG_BLK
554 * header/footer, some block padding for the AES-CBC cipher,
555 * and a possibly large quantity of unwanted ciphertext which
556 * is excluded from the trimmed content range. We store this
557 * data in a local data transfer buffer. If the amount of
558 * data to be stored is too large, we will fail allocation and
559 * so eventually fall back to using a range request (which
560 * does not require this kind of temporary storage
564 /* Corrupt received data (for testing) if applicable */
565 inject_corruption ( PEERBLK_CORRUPT_RATE
, iobuf
->data
, len
);
567 /* Calculate start and end positions of this buffer */
568 start
= peerblk
->pos
;
569 if ( meta
->flags
& XFER_FL_ABS_OFFSET
)
571 start
+= meta
->offset
;
572 end
= ( start
+ len
);
574 /* Buffer any data before the trimmed content */
575 if ( ( start
< peerblk
->start
) && ( len
> 0 ) ) {
577 /* Calculate length of data before the trimmed content */
578 before
= ( peerblk
->start
- start
);
582 /* Buffer data before the trimmed content */
583 if ( ( rc
= xferbuf_write ( &peerblk
->buffer
, start
,
584 iobuf
->data
, before
) ) != 0 ) {
585 DBGC ( peerblk
, "PEERBLK %p %d.%d could not buffer "
586 "data: %s\n", peerblk
, peerblk
->segment
,
587 peerblk
->block
, strerror ( rc
) );
592 /* Buffer any data after the trimmed content */
593 if ( ( end
> peerblk
->end
) && ( len
> 0 ) ) {
595 /* Calculate length of data after the trimmed content */
596 after
= ( end
- peerblk
->end
);
600 /* Buffer data after the trimmed content */
601 cut
= ( peerblk
->end
- peerblk
->start
);
602 if ( ( rc
= xferbuf_write ( &peerblk
->buffer
,
603 ( end
- after
- cut
),
604 ( iobuf
->data
+ len
- after
),
606 DBGC ( peerblk
, "PEERBLK %p %d.%d could not buffer "
607 "data: %s\n", peerblk
, peerblk
->segment
,
608 peerblk
->block
, strerror ( rc
) );
613 /* Deliver any remaining data */
614 if ( ( rc
= peerblk_deliver ( peerblk
, iob_disown ( iobuf
), meta
,
618 /* Update position */
621 /* Extend download attempt timer */
622 start_timer_fixed ( &peerblk
->timer
, PEERBLK_RETRIEVAL_RX_TIMEOUT
);
624 /* Stall download attempt (for testing) if applicable */
625 if ( ( start
< peerblk
->end
) && ( end
>= peerblk
->end
) &&
626 ( ( rc
= inject_fault ( PEERBLK_STALL_RATE
) ) != 0 ) ) {
627 intf_restart ( &peerblk
->retrieval
, rc
);
634 peerblk_done ( peerblk
, rc
);
639 * Parse retrieval protocol message header
641 * @v peerblk PeerDist block download
642 * @ret rc Return status code
644 static int peerblk_parse_header ( struct peerdist_block
*peerblk
) {
646 struct peerdist_msg_transport_header hdr
;
647 struct peerdist_msg_header msg
;
648 } __attribute__ (( packed
)) *msg
= peerblk
->buffer
.data
;
649 struct cipher_algorithm
*cipher
;
650 size_t len
= peerblk
->buffer
.len
;
654 /* Check message length */
655 if ( len
< sizeof ( *msg
) ) {
656 DBGC ( peerblk
, "PEERBLK %p %d.%d message too short for header "
657 "(%zd bytes)\n", peerblk
, peerblk
->segment
,
658 peerblk
->block
, len
);
662 /* Check message type */
663 if ( msg
->msg
.type
!= htonl ( PEERDIST_MSG_BLK_TYPE
) ) {
664 DBGC ( peerblk
, "PEERBLK %p %d.%d unexpected message type "
665 "%#08x\n", peerblk
, peerblk
->segment
, peerblk
->block
,
666 ntohl ( msg
->msg
.type
) );
670 /* Determine cipher algorithm and key length */
671 cipher
= &aes_cbc_algorithm
;
672 switch ( msg
->msg
.algorithm
) {
673 case htonl ( PEERDIST_MSG_PLAINTEXT
) :
676 case htonl ( PEERDIST_MSG_AES_128_CBC
) :
677 keylen
= ( 128 / 8 );
679 case htonl ( PEERDIST_MSG_AES_192_CBC
) :
680 keylen
= ( 192 / 8 );
682 case htonl ( PEERDIST_MSG_AES_256_CBC
) :
683 keylen
= ( 256 / 8 );
686 DBGC ( peerblk
, "PEERBLK %p %d.%d unrecognised algorithm "
687 "%#08x\n", peerblk
, peerblk
->segment
, peerblk
->block
,
688 ntohl ( msg
->msg
.algorithm
) );
691 DBGC2 ( peerblk
, "PEERBLK %p %d.%d using %s with %zd-bit key\n",
692 peerblk
, peerblk
->segment
, peerblk
->block
,
693 ( cipher ? cipher
->name
: "plaintext" ), ( 8 * keylen
) );
695 /* Sanity check key length against maximum secret length */
696 if ( keylen
> peerblk
->digestsize
) {
697 DBGC ( peerblk
, "PEERBLK %p %d.%d %zd-byte secret too short "
698 "for %zd-bit key\n", peerblk
, peerblk
->segment
,
699 peerblk
->block
, peerblk
->digestsize
, ( 8 * keylen
) );
703 /* Allocate cipher context. Freeing the cipher context (on
704 * error or otherwise) is handled by peerblk_reset().
706 peerblk
->cipher
= cipher
;
707 assert ( peerblk
->cipherctx
== NULL
);
708 peerblk
->cipherctx
= malloc ( cipher
->ctxsize
);
709 if ( ! peerblk
->cipherctx
)
712 /* Initialise cipher */
713 if ( ( rc
= cipher_setkey ( cipher
, peerblk
->cipherctx
, peerblk
->secret
,
715 DBGC ( peerblk
, "PEERBLK %p %d.%d could not set key: %s\n",
716 peerblk
, peerblk
->segment
, peerblk
->block
,
725 * Parse retrieval protocol message segment and block details
727 * @v peerblk PeerDist block download
728 * @v buf_len Length of buffered data to fill in
729 * @ret rc Return status code
731 static int peerblk_parse_block ( struct peerdist_block
*peerblk
,
733 size_t digestsize
= peerblk
->digestsize
;
734 peerblk_msg_blk_t ( digestsize
, 0, 0, 0 ) *msg
= peerblk
->buffer
.data
;
735 size_t len
= peerblk
->buffer
.len
;
739 /* Check message length */
740 if ( len
< offsetof ( typeof ( *msg
), msg
.block
.data
) ) {
741 DBGC ( peerblk
, "PEERBLK %p %d.%d message too short for "
742 "zero-length data (%zd bytes)\n", peerblk
,
743 peerblk
->segment
, peerblk
->block
, len
);
747 /* Check digest size */
748 if ( ntohl ( msg
->msg
.segment
.segment
.digestsize
) != digestsize
) {
749 DBGC ( peerblk
, "PEERBLK %p %d.%d incorrect digest size %d\n",
750 peerblk
, peerblk
->segment
, peerblk
->block
,
751 ntohl ( msg
->msg
.segment
.segment
.digestsize
) );
755 /* Check segment ID */
756 if ( memcmp ( msg
->msg
.segment
.id
, peerblk
->id
, digestsize
) != 0 ) {
757 DBGC ( peerblk
, "PEERBLK %p %d.%d segment ID mismatch\n",
758 peerblk
, peerblk
->segment
, peerblk
->block
);
763 if ( ntohl ( msg
->msg
.index
) != peerblk
->block
) {
764 DBGC ( peerblk
, "PEERBLK %p %d.%d block ID mismatch (got %d)\n",
765 peerblk
, peerblk
->segment
, peerblk
->block
,
766 ntohl ( msg
->msg
.index
) );
770 /* Check for missing blocks */
771 data_len
= be32_to_cpu ( msg
->msg
.block
.block
.len
);
773 DBGC ( peerblk
, "PEERBLK %p %d.%d block not found\n",
774 peerblk
, peerblk
->segment
, peerblk
->block
);
778 /* Check for underlength blocks */
779 if ( data_len
< ( peerblk
->range
.end
- peerblk
->range
.start
) ) {
780 DBGC ( peerblk
, "PEERBLK %p %d.%d underlength block (%zd "
781 "bytes)\n", peerblk
, peerblk
->segment
, peerblk
->block
,
786 /* Calculate buffered data length (i.e. excluding data which
787 * was delivered to the final data transfer buffer).
789 *buf_len
= ( data_len
- ( peerblk
->end
- peerblk
->start
) );
791 /* Describe data before the trimmed content */
792 peerblk
->decrypt
[PEERBLK_BEFORE
].xferbuf
= &peerblk
->buffer
;
793 peerblk
->decrypt
[PEERBLK_BEFORE
].offset
=
794 offsetof ( typeof ( *msg
), msg
.block
.data
);
795 peerblk
->decrypt
[PEERBLK_BEFORE
].len
=
797 offsetof ( typeof ( *msg
), msg
.block
.data
) );
798 total
= peerblk
->decrypt
[PEERBLK_BEFORE
].len
;
800 /* Describe data within the trimmed content */
801 peerblk
->decrypt
[PEERBLK_DURING
].offset
=
802 peerblk_offset ( peerblk
, peerblk
->start
);
803 peerblk
->decrypt
[PEERBLK_DURING
].len
=
804 ( peerblk
->end
- peerblk
->start
);
805 total
+= peerblk
->decrypt
[PEERBLK_DURING
].len
;
807 /* Describe data after the trimmed content */
808 peerblk
->decrypt
[PEERBLK_AFTER
].xferbuf
= &peerblk
->buffer
;
809 peerblk
->decrypt
[PEERBLK_AFTER
].offset
= peerblk
->start
;
810 peerblk
->decrypt
[PEERBLK_AFTER
].len
=
811 ( offsetof ( typeof ( *msg
), msg
.block
.data
)
812 + *buf_len
- peerblk
->start
);
813 total
+= peerblk
->decrypt
[PEERBLK_AFTER
].len
;
816 assert ( total
== be32_to_cpu ( msg
->msg
.block
.block
.len
) );
818 /* Initialise cipher and digest lengths */
819 peerblk
->cipher_remaining
= total
;
820 peerblk
->digest_remaining
=
821 ( peerblk
->range
.end
- peerblk
->range
.start
);
822 assert ( peerblk
->cipher_remaining
>= peerblk
->digest_remaining
);
828 * Parse retrieval protocol message useless details
830 * @v peerblk PeerDist block download
831 * @v buf_len Length of buffered data
832 * @v vrf_len Length of uselessness to fill in
833 * @ret rc Return status code
835 static int peerblk_parse_useless ( struct peerdist_block
*peerblk
,
836 size_t buf_len
, size_t *vrf_len
) {
837 size_t digestsize
= peerblk
->digestsize
;
838 peerblk_msg_blk_t ( digestsize
, buf_len
, 0, 0 ) *msg
=
839 peerblk
->buffer
.data
;
840 size_t len
= peerblk
->buffer
.len
;
842 /* Check message length */
843 if ( len
< offsetof ( typeof ( *msg
), msg
.vrf
.data
) ) {
844 DBGC ( peerblk
, "PEERBLK %p %d.%d message too short for "
845 "zero-length uselessness (%zd bytes)\n", peerblk
,
846 peerblk
->segment
, peerblk
->block
, len
);
850 /* Extract length of uselessness */
851 *vrf_len
= be32_to_cpu ( msg
->msg
.vrf
.vrf
.len
);
857 * Parse retrieval protocol message initialisation vector details
859 * @v peerblk PeerDist block download
860 * @v buf_len Length of buffered data
861 * @v vrf_len Length of uselessness
862 * @ret rc Return status code
864 static int peerblk_parse_iv ( struct peerdist_block
*peerblk
, size_t buf_len
,
866 size_t digestsize
= peerblk
->digestsize
;
867 size_t blksize
= peerblk
->cipher
->blocksize
;
868 peerblk_msg_blk_t ( digestsize
, buf_len
, vrf_len
, blksize
) *msg
=
869 peerblk
->buffer
.data
;
870 size_t len
= peerblk
->buffer
.len
;
872 /* Check message length */
873 if ( len
< sizeof ( *msg
) ) {
874 DBGC ( peerblk
, "PEERBLK %p %d.%d message too short for "
875 "initialisation vector (%zd bytes)\n", peerblk
,
876 peerblk
->segment
, peerblk
->block
, len
);
880 /* Check initialisation vector size */
881 if ( ntohl ( msg
->msg
.iv
.iv
.blksize
) != blksize
) {
882 DBGC ( peerblk
, "PEERBLK %p %d.%d incorrect IV size %d\n",
883 peerblk
, peerblk
->segment
, peerblk
->block
,
884 ntohl ( msg
->msg
.iv
.iv
.blksize
) );
888 /* Set initialisation vector */
889 cipher_setiv ( peerblk
->cipher
, peerblk
->cipherctx
, msg
->msg
.iv
.data
);
895 * Read from decryption buffers
897 * @v peerblk PeerDist block download
898 * @v data Data buffer
899 * @v len Length to read
900 * @ret rc Return status code
902 static int peerblk_decrypt_read ( struct peerdist_block
*peerblk
,
903 void *data
, size_t len
) {
904 struct peerdist_block_decrypt
*decrypt
= peerblk
->decrypt
;
908 /* Read from each decryption buffer in turn */
909 for ( ; len
; decrypt
++, data
+= frag_len
, len
-= frag_len
) {
911 /* Calculate length to use from this buffer */
912 frag_len
= decrypt
->len
;
913 if ( frag_len
> len
)
918 /* Read from this buffer */
919 if ( ( rc
= xferbuf_read ( decrypt
->xferbuf
, decrypt
->offset
,
920 data
, frag_len
) ) != 0 )
928 * Write to decryption buffers and update offsets and lengths
930 * @v peerblk PeerDist block download
931 * @v data Data buffer
932 * @v len Length to read
933 * @ret rc Return status code
935 static int peerblk_decrypt_write ( struct peerdist_block
*peerblk
,
936 const void *data
, size_t len
) {
937 struct peerdist_block_decrypt
*decrypt
= peerblk
->decrypt
;
941 /* Write to each decryption buffer in turn */
942 for ( ; len
; decrypt
++, data
+= frag_len
, len
-= frag_len
) {
944 /* Calculate length to use from this buffer */
945 frag_len
= decrypt
->len
;
946 if ( frag_len
> len
)
951 /* Write to this buffer */
952 if ( ( rc
= xferbuf_write ( decrypt
->xferbuf
, decrypt
->offset
,
953 data
, frag_len
) ) != 0 )
956 /* Update offset and length */
957 decrypt
->offset
+= frag_len
;
958 decrypt
->len
-= frag_len
;
965 * Decrypt one chunk of PeerDist retrieval protocol data
967 * @v peerblk PeerDist block download
969 static void peerblk_decrypt ( struct peerdist_block
*peerblk
) {
970 struct cipher_algorithm
*cipher
= peerblk
->cipher
;
971 struct digest_algorithm
*digest
= peerblk
->digest
;
972 struct xfer_buffer
*xferbuf
;
979 assert ( ( PEERBLK_DECRYPT_CHUNKSIZE
% cipher
->blocksize
) == 0 );
981 /* Get the underlying data transfer buffer */
982 xferbuf
= xfer_buffer ( &peerblk
->xfer
);
984 DBGC ( peerblk
, "PEERBLK %p %d.%d has no underlying data "
985 "transfer buffer\n", peerblk
, peerblk
->segment
,
988 goto err_xfer_buffer
;
990 peerblk
->decrypt
[PEERBLK_DURING
].xferbuf
= xferbuf
;
992 /* Calculate cipher and digest lengths */
993 cipher_len
= PEERBLK_DECRYPT_CHUNKSIZE
;
994 if ( cipher_len
> peerblk
->cipher_remaining
)
995 cipher_len
= peerblk
->cipher_remaining
;
996 digest_len
= cipher_len
;
997 if ( digest_len
> peerblk
->digest_remaining
)
998 digest_len
= peerblk
->digest_remaining
;
999 assert ( ( cipher_len
& ( cipher
->blocksize
- 1 ) ) == 0 );
1001 /* Allocate temporary data buffer */
1002 data
= malloc ( cipher_len
);
1005 goto err_alloc_data
;
1008 /* Read ciphertext */
1009 if ( ( rc
= peerblk_decrypt_read ( peerblk
, data
, cipher_len
) ) != 0 ){
1010 DBGC ( peerblk
, "PEERBLK %p %d.%d could not read ciphertext: "
1011 "%s\n", peerblk
, peerblk
->segment
, peerblk
->block
,
1017 cipher_decrypt ( cipher
, peerblk
->cipherctx
, data
, data
, cipher_len
);
1019 /* Add data to digest */
1020 digest_update ( digest
, peerblk
->digestctx
, data
, digest_len
);
1022 /* Write plaintext */
1023 if ( ( rc
= peerblk_decrypt_write ( peerblk
, data
, cipher_len
) ) != 0){
1024 DBGC ( peerblk
, "PEERBLK %p %d.%d could not write plaintext: "
1025 "%s\n", peerblk
, peerblk
->segment
, peerblk
->block
,
1031 peerblk
->cipher_remaining
-= cipher_len
;
1032 peerblk
->digest_remaining
-= digest_len
;
1034 /* Free temporary data buffer */
1037 /* Continue processing until all input is consumed */
1038 if ( peerblk
->cipher_remaining
)
1041 /* Complete download attempt */
1042 peerblk_done ( peerblk
, 0 );
1050 peerblk_done ( peerblk
, rc
);
1054 * Close PeerDist retrieval protocol block download attempt
1056 * @v peerblk PeerDist block download
1057 * @v rc Reason for close
1059 static void peerblk_retrieval_close ( struct peerdist_block
*peerblk
, int rc
) {
1063 /* Restart interface */
1064 intf_restart ( &peerblk
->retrieval
, rc
);
1066 /* Fail immediately if we have an error */
1070 /* Abort download attempt (for testing) if applicable */
1071 if ( ( rc
= inject_fault ( PEERBLK_ABORT_RATE
) ) != 0 )
1074 /* Parse message header */
1075 if ( ( rc
= peerblk_parse_header ( peerblk
) ) != 0 )
1078 /* Parse message segment and block details */
1079 if ( ( rc
= peerblk_parse_block ( peerblk
, &buf_len
) ) != 0 )
1082 /* If the block was plaintext, then there is nothing more to do */
1083 if ( ! peerblk
->cipher
)
1086 /* Parse message useless details */
1087 if ( ( rc
= peerblk_parse_useless ( peerblk
, buf_len
, &vrf_len
) ) != 0)
1090 /* Parse message initialisation vector details */
1091 if ( ( rc
= peerblk_parse_iv ( peerblk
, buf_len
, vrf_len
) ) != 0 )
1094 /* Fail if decryption length is not aligned to the cipher block size */
1095 if ( peerblk
->cipher_remaining
& ( peerblk
->cipher
->blocksize
- 1 ) ) {
1096 DBGC ( peerblk
, "PEERBLK %p %d.%d unaligned data length %zd\n",
1097 peerblk
, peerblk
->segment
, peerblk
->block
,
1098 peerblk
->cipher_remaining
);
1103 /* Stop the download attempt timer: there is no point in
1104 * timing out while decrypting.
1106 stop_timer ( &peerblk
->timer
);
1108 /* Start decryption process */
1109 process_add ( &peerblk
->process
);
1113 /* Complete download attempt */
1114 peerblk_done ( peerblk
, rc
);
1117 /******************************************************************************
1121 ******************************************************************************
1125 * Handle PeerDist retry timer expiry
1127 * @v timer Retry timer
1128 * @v over Failure indicator
1130 static void peerblk_expired ( struct retry_timer
*timer
, int over __unused
) {
1131 struct peerdist_block
*peerblk
=
1132 container_of ( timer
, struct peerdist_block
, timer
);
1133 struct peerdisc_segment
*segment
= peerblk
->discovery
.segment
;
1134 struct peerdisc_peer
*head
;
1135 unsigned long now
= peerblk_timestamp();
1136 const char *location
;
1139 /* Profile discovery timeout, if applicable */
1140 if ( ( peerblk
->peer
== NULL
) && ( timer
->timeout
!= 0 ) ) {
1141 profile_custom ( &peerblk_discovery_timeout_profiler
,
1142 ( now
- peerblk
->started
) );
1143 DBGC ( peerblk
, "PEERBLK %p %d.%d discovery timed out after "
1144 "%ld ticks\n", peerblk
, peerblk
->segment
,
1145 peerblk
->block
, timer
->timeout
);
1148 /* Profile download timeout, if applicable */
1149 if ( ( peerblk
->peer
!= NULL
) && ( timer
->timeout
!= 0 ) ) {
1150 profile_custom ( &peerblk_attempt_timeout_profiler
,
1151 ( now
- peerblk
->attempted
) );
1152 DBGC ( peerblk
, "PEERBLK %p %d.%d timed out after %ld ticks\n",
1153 peerblk
, peerblk
->segment
, peerblk
->block
,
1157 /* Abort any current download attempt */
1158 peerblk_reset ( peerblk
, -ETIMEDOUT
);
1160 /* Record attempt start time */
1161 peerblk
->attempted
= now
;
1163 /* If we have exceeded our maximum number of attempt cycles
1164 * (each cycle comprising a retrieval protocol download from
1165 * each peer in the list followed by a raw download from the
1166 * origin server), then abort the overall download.
1168 head
= list_entry ( &segment
->peers
, struct peerdisc_peer
, list
);
1169 if ( ( peerblk
->peer
== head
) &&
1170 ( ++peerblk
->cycles
>= PEERBLK_MAX_ATTEMPT_CYCLES
) ) {
1176 /* If we have not yet made any download attempts, then move to
1177 * the start of the peer list.
1179 if ( peerblk
->peer
== NULL
)
1180 peerblk
->peer
= head
;
1182 /* Attempt retrieval protocol download from next usable peer */
1183 list_for_each_entry_continue ( peerblk
->peer
, &segment
->peers
, list
) {
1185 /* Attempt retrieval protocol download from this peer */
1186 location
= peerblk
->peer
->location
;
1187 if ( ( rc
= peerblk_retrieval_open ( peerblk
,
1188 location
) ) != 0 ) {
1189 /* Non-fatal: continue to try next peer */
1193 /* Start download attempt timer */
1194 peerblk
->rc
= -ETIMEDOUT
;
1195 start_timer_fixed ( &peerblk
->timer
,
1196 PEERBLK_RETRIEVAL_OPEN_TIMEOUT
);
1200 /* Attempt raw download */
1201 if ( ( rc
= peerblk_raw_open ( peerblk
) ) != 0 )
1204 /* Start download attempt timer */
1205 peerblk
->rc
= -ETIMEDOUT
;
1206 start_timer_fixed ( &peerblk
->timer
, PEERBLK_RAW_OPEN_TIMEOUT
);
1210 peerblk_close ( peerblk
, rc
);
1214 * Handle PeerDist peer discovery
1216 * @v discovery PeerDist discovery client
1218 static void peerblk_discovered ( struct peerdisc_client
*discovery
) {
1219 struct peerdist_block
*peerblk
=
1220 container_of ( discovery
, struct peerdist_block
, discovery
);
1221 unsigned long now
= peerblk_timestamp();
1223 /* Do nothing unless we are still waiting for the initial
1224 * discovery timeout.
1226 if ( ( peerblk
->peer
!= NULL
) || ( peerblk
->timer
.timeout
== 0 ) )
1229 /* Schedule an immediate retry */
1230 start_timer_nodelay ( &peerblk
->timer
);
1232 /* Profile discovery success */
1233 profile_custom ( &peerblk_discovery_success_profiler
,
1234 ( now
- peerblk
->started
) );
1237 /******************************************************************************
1241 ******************************************************************************
1244 /** PeerDist block download data transfer interface operations */
1245 static struct interface_operation peerblk_xfer_operations
[] = {
1246 INTF_OP ( intf_close
, struct peerdist_block
*, peerblk_close
),
1249 /** PeerDist block download data transfer interface descriptor */
1250 static struct interface_descriptor peerblk_xfer_desc
=
1251 INTF_DESC ( struct peerdist_block
, xfer
, peerblk_xfer_operations
);
1253 /** PeerDist block download raw data interface operations */
1254 static struct interface_operation peerblk_raw_operations
[] = {
1255 INTF_OP ( xfer_deliver
, struct peerdist_block
*, peerblk_raw_rx
),
1256 INTF_OP ( intf_close
, struct peerdist_block
*, peerblk_raw_close
),
1259 /** PeerDist block download raw data interface descriptor */
1260 static struct interface_descriptor peerblk_raw_desc
=
1261 INTF_DESC ( struct peerdist_block
, raw
, peerblk_raw_operations
);
1263 /** PeerDist block download retrieval protocol interface operations */
1264 static struct interface_operation peerblk_retrieval_operations
[] = {
1265 INTF_OP ( xfer_deliver
, struct peerdist_block
*, peerblk_retrieval_rx
),
1266 INTF_OP ( intf_close
, struct peerdist_block
*, peerblk_retrieval_close
),
1269 /** PeerDist block download retrieval protocol interface descriptor */
1270 static struct interface_descriptor peerblk_retrieval_desc
=
1271 INTF_DESC ( struct peerdist_block
, retrieval
,
1272 peerblk_retrieval_operations
);
1274 /** PeerDist block download decryption process descriptor */
1275 static struct process_descriptor peerblk_process_desc
=
1276 PROC_DESC ( struct peerdist_block
, process
, peerblk_decrypt
);
1278 /** PeerDist block download discovery operations */
1279 static struct peerdisc_client_operations peerblk_discovery_operations
= {
1280 .discovered
= peerblk_discovered
,
1284 * Open PeerDist block download
1286 * @v xfer Data transfer interface
1287 * @v uri Original URI
1288 * @v info Content information block
1289 * @ret rc Return status code
1291 int peerblk_open ( struct interface
*xfer
, struct uri
*uri
,
1292 struct peerdist_info_block
*block
) {
1293 const struct peerdist_info_segment
*segment
= block
->segment
;
1294 const struct peerdist_info
*info
= segment
->info
;
1295 struct digest_algorithm
*digest
= info
->digest
;
1296 struct peerdist_block
*peerblk
;
1297 unsigned long timeout
;
1301 /* Allocate and initialise structure */
1302 peerblk
= zalloc ( sizeof ( *peerblk
) + digest
->ctxsize
);
1307 ref_init ( &peerblk
->refcnt
, peerblk_free
);
1308 intf_init ( &peerblk
->xfer
, &peerblk_xfer_desc
, &peerblk
->refcnt
);
1309 intf_init ( &peerblk
->raw
, &peerblk_raw_desc
, &peerblk
->refcnt
);
1310 intf_init ( &peerblk
->retrieval
, &peerblk_retrieval_desc
,
1312 peerblk
->uri
= uri_get ( uri
);
1313 memcpy ( &peerblk
->range
, &block
->range
, sizeof ( peerblk
->range
) );
1314 memcpy ( &peerblk
->trim
, &block
->trim
, sizeof ( peerblk
->trim
) );
1315 peerblk
->offset
= ( block
->trim
.start
- info
->trim
.start
);
1316 peerblk
->digest
= info
->digest
;
1317 peerblk
->digestsize
= digestsize
= info
->digestsize
;
1318 peerblk
->digestctx
= ( ( ( void * ) peerblk
) + sizeof ( *peerblk
) );
1319 peerblk
->segment
= segment
->index
;
1320 memcpy ( peerblk
->id
, segment
->id
, sizeof ( peerblk
->id
) );
1321 memcpy ( peerblk
->secret
, segment
->secret
, sizeof ( peerblk
->secret
) );
1322 peerblk
->block
= block
->index
;
1323 memcpy ( peerblk
->hash
, block
->hash
, sizeof ( peerblk
->hash
) );
1324 xferbuf_malloc_init ( &peerblk
->buffer
);
1325 process_init_stopped ( &peerblk
->process
, &peerblk_process_desc
,
1327 peerdisc_init ( &peerblk
->discovery
, &peerblk_discovery_operations
);
1328 timer_init ( &peerblk
->timer
, peerblk_expired
, &peerblk
->refcnt
);
1329 DBGC2 ( peerblk
, "PEERBLK %p %d.%d id %02x%02x%02x%02x%02x..."
1330 "%02x%02x%02x [%08zx,%08zx)", peerblk
, peerblk
->segment
,
1331 peerblk
->block
, peerblk
->id
[0], peerblk
->id
[1], peerblk
->id
[2],
1332 peerblk
->id
[3], peerblk
->id
[4], peerblk
->id
[ digestsize
- 3 ],
1333 peerblk
->id
[ digestsize
- 2 ], peerblk
->id
[ digestsize
- 1 ],
1334 peerblk
->range
.start
, peerblk
->range
.end
);
1335 if ( ( peerblk
->trim
.start
!= peerblk
->range
.start
) ||
1336 ( peerblk
->trim
.end
!= peerblk
->range
.end
) ) {
1337 DBGC2 ( peerblk
, " covers [%08zx,%08zx)",
1338 peerblk
->trim
.start
, peerblk
->trim
.end
);
1340 DBGC2 ( peerblk
, "\n" );
1342 /* Open discovery */
1343 if ( ( rc
= peerdisc_open ( &peerblk
->discovery
, peerblk
->id
,
1344 peerblk
->digestsize
) ) != 0 )
1345 goto err_open_discovery
;
1347 /* Schedule a retry attempt either immediately (if we already
1348 * have some peers) or after the discovery timeout.
1350 timeout
= ( list_empty ( &peerblk
->discovery
.segment
->peers
) ?
1351 ( peerdisc_timeout_secs
* TICKS_PER_SEC
) : 0 );
1352 start_timer_fixed ( &peerblk
->timer
, timeout
);
1354 /* Record start time */
1355 peerblk
->started
= peerblk_timestamp();
1357 /* Attach to parent interface, mortalise self, and return */
1358 intf_plug_plug ( xfer
, &peerblk
->xfer
);
1359 ref_put ( &peerblk
->refcnt
);
1363 peerblk_close ( peerblk
, rc
);