1 /*
2 * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
3 *
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.
8 *
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.
13 *
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
17 * 02110-1301, USA.
18 *
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.
22 */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <ipxe/http.h>
30 #include <ipxe/iobuf.h>
31 #include <ipxe/xfer.h>
32 #include <ipxe/uri.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>
38
39 /** @file
40 *
41 * Peer Content Caching and Retrieval (PeerDist) protocol block downloads
42 *
43 */
44
45 /** PeerDist decryption chunksize
46 *
47 * This is a policy decision.
48 */
49 #define PEERBLK_DECRYPT_CHUNKSIZE 2048
50
51 /** PeerDist raw block download attempt initial progress timeout
52 *
53 * This is a policy decision.
54 */
55 #define PEERBLK_RAW_OPEN_TIMEOUT ( 10 * TICKS_PER_SEC )
56
57 /** PeerDist raw block download attempt ongoing progress timeout
58 *
59 * This is a policy decision.
60 */
61 #define PEERBLK_RAW_RX_TIMEOUT ( 15 * TICKS_PER_SEC )
62
63 /** PeerDist retrieval protocol block download attempt initial progress timeout
64 *
65 * This is a policy decision.
66 */
67 #define PEERBLK_RETRIEVAL_OPEN_TIMEOUT ( 3 * TICKS_PER_SEC )
68
69 /** PeerDist retrieval protocol block download attempt ongoing progress timeout
70 *
71 * This is a policy decision.
72 */
73 #define PEERBLK_RETRIEVAL_RX_TIMEOUT ( 5 * TICKS_PER_SEC )
74
75 /** PeerDist maximum number of full download attempt cycles
76 *
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
80 * origin server).
81 *
82 * This is a policy decision.
83 */
84 #define PEERBLK_MAX_ATTEMPT_CYCLES 4
85
86 /** PeerDist block download profiler */
87 static struct profiler peerblk_download_profiler __profiler =
88 { .name = "peerblk.download" };
89
90 /** PeerDist block download attempt success profiler */
91 static struct profiler peerblk_attempt_success_profiler __profiler =
92 { .name = "peerblk.attempt.success" };
93
94 /** PeerDist block download attempt failure profiler */
95 static struct profiler peerblk_attempt_failure_profiler __profiler =
96 { .name = "peerblk.attempt.failure" };
97
98 /** PeerDist block download attempt timeout profiler */
99 static struct profiler peerblk_attempt_timeout_profiler __profiler =
100 { .name = "peerblk.attempt.timeout" };
101
102 /** PeerDist block download discovery success profiler */
103 static struct profiler peerblk_discovery_success_profiler __profiler =
104 { .name = "peerblk.discovery.success" };
105
106 /** PeerDist block download discovery timeout profiler */
107 static struct profiler peerblk_discovery_timeout_profiler __profiler =
108 { .name = "peerblk.discovery.timeout" };
109
110 /**
111 * Get profiling timestamp
112 *
113 * @ret timestamp Timestamp
114 */
115 static inline __attribute__ (( always_inline )) unsigned long
peerblk_timestamp(void)116 peerblk_timestamp ( void ) {
117
118 if ( PROFILING ) {
119 return currticks();
120 } else {
121 return 0;
122 }
123 }
124
125 /**
126 * Free PeerDist block download
127 *
128 * @v refcnt Reference count
129 */
peerblk_free(struct refcnt * refcnt)130 static void peerblk_free ( struct refcnt *refcnt ) {
131 struct peerdist_block *peerblk =
132 container_of ( refcnt, struct peerdist_block, refcnt );
133
134 uri_put ( peerblk->uri );
135 free ( peerblk->cipherctx );
136 free ( peerblk );
137 }
138
139 /**
140 * Reset PeerDist block download attempt
141 *
142 * @v peerblk PeerDist block download
143 * @v rc Reason for reset
144 */
peerblk_reset(struct peerdist_block * peerblk,int rc)145 static void peerblk_reset ( struct peerdist_block *peerblk, int rc ) {
146
147 /* Stop decryption process */
148 process_del ( &peerblk->process );
149
150 /* Stop timer */
151 stop_timer ( &peerblk->timer );
152
153 /* Abort any current download attempt */
154 intf_restart ( &peerblk->raw, rc );
155 intf_restart ( &peerblk->retrieval, rc );
156
157 /* Empty received data buffer */
158 xferbuf_free ( &peerblk->buffer );
159 peerblk->pos = 0;
160
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;
166
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 );
171 }
172
173 /**
174 * Close PeerDist block download
175 *
176 * @v peerblk PeerDist block download
177 * @v rc Reason for close
178 */
peerblk_close(struct peerdist_block * peerblk,int rc)179 static void peerblk_close ( struct peerdist_block *peerblk, int rc ) {
180 unsigned long now = peerblk_timestamp();
181
182 /* Profile overall block download */
183 profile_custom ( &peerblk_download_profiler,
184 ( now - peerblk->started ) );
185
186 /* Reset download attempt */
187 peerblk_reset ( peerblk, rc );
188
189 /* Close discovery */
190 peerdisc_close ( &peerblk->discovery );
191
192 /* Shut down all interfaces */
193 intf_shutdown ( &peerblk->retrieval, rc );
194 intf_shutdown ( &peerblk->raw, rc );
195 intf_shutdown ( &peerblk->xfer, rc );
196 }
197
198 /**
199 * Calculate offset within overall download
200 *
201 * @v peerblk PeerDist block download
202 * @v pos Position within incoming data stream
203 * @ret offset Offset within overall download
204 */
205 static inline __attribute__ (( always_inline )) size_t
peerblk_offset(struct peerdist_block * peerblk,size_t pos)206 peerblk_offset ( struct peerdist_block *peerblk, size_t pos ) {
207
208 return ( ( pos - peerblk->start ) + peerblk->offset );
209 }
210
211 /**
212 * Deliver download attempt data block
213 *
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
219 */
peerblk_deliver(struct peerdist_block * peerblk,struct io_buffer * iobuf,struct xfer_metadata * meta,size_t pos)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 );
225 size_t start = pos;
226 size_t end = ( pos + len );
227 int rc;
228
229 /* Discard zero-length packets and packets which lie entirely
230 * outside the trimmed range.
231 */
232 if ( ( start >= peerblk->end ) || ( end <= peerblk->start ) ||
233 ( len == 0 ) ) {
234 free_iob ( iobuf );
235 return 0;
236 }
237
238 /* Truncate data to within trimmed range */
239 if ( start < peerblk->start ) {
240 iob_pull ( iobuf, ( peerblk->start - start ) );
241 start = peerblk->start;
242 }
243 if ( end > peerblk->end ) {
244 iob_unput ( iobuf, ( end - peerblk->end ) );
245 end = peerblk->end;
246 }
247
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 );
252
253 /* Deliver data */
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,
258 strerror ( rc ) );
259 return rc;
260 }
261
262 return 0;
263 }
264
265 /**
266 * Finish PeerDist block download attempt
267 *
268 * @v peerblk PeerDist block download
269 * @v rc Reason for close
270 */
peerblk_done(struct peerdist_block * peerblk,int rc)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();
275
276 /* Check for errors on completion */
277 if ( rc != 0 ) {
278 DBGC ( peerblk, "PEERBLK %p %d.%d attempt failed: %s\n",
279 peerblk, peerblk->segment, peerblk->block,
280 strerror ( rc ) );
281 goto err;
282 }
283
284 /* Check digest */
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 );
291 rc = -EIO;
292 goto err;
293 }
294
295 /* Profile successful attempt */
296 profile_custom ( &peerblk_attempt_success_profiler,
297 ( now - peerblk->attempted ) );
298
299 /* Close download */
300 peerblk_close ( peerblk, 0 );
301 return;
302
303 err:
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 );
308 peerblk->rc = rc;
309 start_timer_nodelay ( &peerblk->timer );
310 }
311
312 /******************************************************************************
313 *
314 * Raw block download attempts (using an HTTP range request)
315 *
316 ******************************************************************************
317 */
318
319 /**
320 * Open PeerDist raw block download attempt
321 *
322 * @v peerblk PeerDist block download
323 * @ret rc Return status code
324 */
peerblk_raw_open(struct peerdist_block * peerblk)325 static int peerblk_raw_open ( struct peerdist_block *peerblk ) {
326 struct http_request_range range;
327 int rc;
328
329 DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting raw range request\n",
330 peerblk, peerblk->segment, peerblk->block );
331
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 );
336
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 ) );
343 return rc;
344 }
345
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.
349 */
350 if ( inject_fault ( PEERBLK_ANNUL_RATE ) )
351 intf_restart ( &peerblk->raw, 0 );
352
353 return 0;
354 }
355
356 /**
357 * Receive PeerDist raw data
358 *
359 * @v peerblk PeerDist block download
360 * @v iobuf I/O buffer
361 * @v meta Data transfer metadata
362 * @ret rc Return status code
363 */
peerblk_raw_rx(struct peerdist_block * peerblk,struct io_buffer * iobuf,struct xfer_metadata * meta)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 );
370 int rc;
371
372 /* Corrupt received data (for testing) if applicable */
373 inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len );
374
375 /* Fail if data is delivered out of order, since the streaming
376 * digest requires strict ordering.
377 */
378 if ( ( rc = xfer_check_order ( meta, &peerblk->pos, len ) ) != 0 )
379 goto err;
380
381 /* Add data to digest */
382 digest_update ( peerblk->digest, peerblk->digestctx, iobuf->data, len );
383
384 /* Deliver data */
385 if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta,
386 pos ) ) != 0 )
387 goto err;
388
389 /* Extend download attempt timer */
390 start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_RX_TIMEOUT );
391
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 );
396 }
397
398 return 0;
399
400 err:
401 free_iob ( iobuf );
402 peerblk_done ( peerblk, rc );
403 return rc;
404 }
405
406 /**
407 * Close PeerDist raw block download attempt
408 *
409 * @v peerblk PeerDist block download
410 * @v rc Reason for close
411 */
peerblk_raw_close(struct peerdist_block * peerblk,int rc)412 static void peerblk_raw_close ( struct peerdist_block *peerblk, int rc ) {
413
414 /* Restart interface */
415 intf_restart ( &peerblk->raw, rc );
416
417 /* Fail immediately if we have an error */
418 if ( rc != 0 )
419 goto done;
420
421 /* Abort download attempt (for testing) if applicable */
422 if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 )
423 goto done;
424
425 done:
426 /* Complete download attempt */
427 peerblk_done ( peerblk, rc );
428 }
429
430 /******************************************************************************
431 *
432 * Retrieval protocol block download attempts (using HTTP POST)
433 *
434 ******************************************************************************
435 */
436
437 /**
438 * Construct PeerDist retrieval protocol URI
439 *
440 * @v location Peer location
441 * @ret uri Retrieval URI, or NULL on error
442 */
peerblk_retrieval_uri(const char * location)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 */ ) ];
446
447 /* Construct URI string */
448 snprintf ( uri_string, sizeof ( uri_string ),
449 ( "http://%s" PEERDIST_MAGIC_PATH ), location );
450
451 /* Parse URI string */
452 return parse_uri ( uri_string );
453 }
454
455 /**
456 * Open PeerDist retrieval protocol block download attempt
457 *
458 * @v peerblk PeerDist block download
459 * @v location Peer location
460 * @ret rc Return status code
461 */
peerblk_retrieval_open(struct peerdist_block * peerblk,const char * location)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;
468 struct uri *uri;
469 int rc;
470
471 DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting retrieval from %s\n",
472 peerblk, peerblk->segment, peerblk->block, location );
473
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 );
485
486 /* Construct POST request content */
487 memset ( &content, 0, sizeof ( content ) );
488 content.data = &req;
489 content.len = sizeof ( req );
490
491 /* Construct URI */
492 if ( ( uri = peerblk_retrieval_uri ( location ) ) == NULL ) {
493 rc = -ENOMEM;
494 goto err_uri;
495 }
496
497 /* Update trim thresholds */
498 peerblk->start += offsetof ( typeof ( *rsp ), msg.vrf );
499 peerblk->end += offsetof ( typeof ( *rsp ), msg.vrf );
500
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 ) );
507 goto err_open;
508 }
509
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.
513 */
514 if ( inject_fault ( PEERBLK_ANNUL_RATE ) )
515 intf_restart ( &peerblk->retrieval, 0 );
516
517 err_open:
518 uri_put ( uri );
519 err_uri:
520 return rc;
521 }
522
523 /**
524 * Receive PeerDist retrieval protocol data
525 *
526 * @v peerblk PeerDist block download
527 * @v iobuf I/O buffer
528 * @v meta Data transfer metadata
529 * @ret rc Return status code
530 */
peerblk_retrieval_rx(struct peerdist_block * peerblk,struct io_buffer * iobuf,struct xfer_metadata * meta)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 );
535 size_t start;
536 size_t end;
537 size_t before;
538 size_t after;
539 size_t cut;
540 int rc;
541
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.
546 *
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.
551 *
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
561 * allocation).
562 */
563
564 /* Corrupt received data (for testing) if applicable */
565 inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len );
566
567 /* Calculate start and end positions of this buffer */
568 start = peerblk->pos;
569 if ( meta->flags & XFER_FL_ABS_OFFSET )
570 start = 0;
571 start += meta->offset;
572 end = ( start + len );
573
574 /* Buffer any data before the trimmed content */
575 if ( ( start < peerblk->start ) && ( len > 0 ) ) {
576
577 /* Calculate length of data before the trimmed content */
578 before = ( peerblk->start - start );
579 if ( before > len )
580 before = len;
581
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 ) );
588 goto err;
589 }
590 }
591
592 /* Buffer any data after the trimmed content */
593 if ( ( end > peerblk->end ) && ( len > 0 ) ) {
594
595 /* Calculate length of data after the trimmed content */
596 after = ( end - peerblk->end );
597 if ( after > len )
598 after = len;
599
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 ),
605 after ) ) != 0 ) {
606 DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer "
607 "data: %s\n", peerblk, peerblk->segment,
608 peerblk->block, strerror ( rc ) );
609 goto err;
610 }
611 }
612
613 /* Deliver any remaining data */
614 if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta,
615 start ) ) != 0 )
616 goto err;
617
618 /* Update position */
619 peerblk->pos = end;
620
621 /* Extend download attempt timer */
622 start_timer_fixed ( &peerblk->timer, PEERBLK_RETRIEVAL_RX_TIMEOUT );
623
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 );
628 }
629
630 return 0;
631
632 err:
633 free_iob ( iobuf );
634 peerblk_done ( peerblk, rc );
635 return rc;
636 }
637
638 /**
639 * Parse retrieval protocol message header
640 *
641 * @v peerblk PeerDist block download
642 * @ret rc Return status code
643 */
peerblk_parse_header(struct peerdist_block * peerblk)644 static int peerblk_parse_header ( struct peerdist_block *peerblk ) {
645 struct {
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;
651 size_t keylen = 0;
652 int rc;
653
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 );
659 return -ERANGE;
660 }
661
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 ) );
667 return -EPROTO;
668 }
669
670 /* Determine cipher algorithm and key length */
671 cipher = &aes_cbc_algorithm;
672 switch ( msg->msg.algorithm ) {
673 case htonl ( PEERDIST_MSG_PLAINTEXT ) :
674 cipher = NULL;
675 break;
676 case htonl ( PEERDIST_MSG_AES_128_CBC ) :
677 keylen = ( 128 / 8 );
678 break;
679 case htonl ( PEERDIST_MSG_AES_192_CBC ) :
680 keylen = ( 192 / 8 );
681 break;
682 case htonl ( PEERDIST_MSG_AES_256_CBC ) :
683 keylen = ( 256 / 8 );
684 break;
685 default:
686 DBGC ( peerblk, "PEERBLK %p %d.%d unrecognised algorithm "
687 "%#08x\n", peerblk, peerblk->segment, peerblk->block,
688 ntohl ( msg->msg.algorithm ) );
689 return -ENOTSUP;
690 }
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 ) );
694
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 ) );
700 return -EPROTO;
701 }
702
703 /* Allocate cipher context, if applicable. Freeing the cipher
704 * context (on error or otherwise) is handled by peerblk_reset().
705 */
706 peerblk->cipher = cipher;
707 assert ( peerblk->cipherctx == NULL );
708 if ( cipher ) {
709 peerblk->cipherctx = malloc ( cipher->ctxsize );
710 if ( ! peerblk->cipherctx )
711 return -ENOMEM;
712 }
713
714 /* Initialise cipher, if applicable */
715 if ( cipher &&
716 ( rc = cipher_setkey ( cipher, peerblk->cipherctx, peerblk->secret,
717 keylen ) ) != 0 ) {
718 DBGC ( peerblk, "PEERBLK %p %d.%d could not set key: %s\n",
719 peerblk, peerblk->segment, peerblk->block,
720 strerror ( rc ) );
721 return rc;
722 }
723
724 return 0;
725 }
726
727 /**
728 * Parse retrieval protocol message segment and block details
729 *
730 * @v peerblk PeerDist block download
731 * @v buf_len Length of buffered data to fill in
732 * @ret rc Return status code
733 */
peerblk_parse_block(struct peerdist_block * peerblk,size_t * buf_len)734 static int peerblk_parse_block ( struct peerdist_block *peerblk,
735 size_t *buf_len ) {
736 size_t digestsize = peerblk->digestsize;
737 peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *msg = peerblk->buffer.data;
738 size_t len = peerblk->buffer.len;
739 size_t data_len;
740 size_t total;
741
742 /* Check message length */
743 if ( len < offsetof ( typeof ( *msg ), msg.block.data ) ) {
744 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
745 "zero-length data (%zd bytes)\n", peerblk,
746 peerblk->segment, peerblk->block, len );
747 return -ERANGE;
748 }
749
750 /* Check digest size */
751 if ( ntohl ( msg->msg.segment.segment.digestsize ) != digestsize ) {
752 DBGC ( peerblk, "PEERBLK %p %d.%d incorrect digest size %d\n",
753 peerblk, peerblk->segment, peerblk->block,
754 ntohl ( msg->msg.segment.segment.digestsize ) );
755 return -EPROTO;
756 }
757
758 /* Check segment ID */
759 if ( memcmp ( msg->msg.segment.id, peerblk->id, digestsize ) != 0 ) {
760 DBGC ( peerblk, "PEERBLK %p %d.%d segment ID mismatch\n",
761 peerblk, peerblk->segment, peerblk->block );
762 return -EPROTO;
763 }
764
765 /* Check block ID */
766 if ( ntohl ( msg->msg.index ) != peerblk->block ) {
767 DBGC ( peerblk, "PEERBLK %p %d.%d block ID mismatch (got %d)\n",
768 peerblk, peerblk->segment, peerblk->block,
769 ntohl ( msg->msg.index ) );
770 return -EPROTO;
771 }
772
773 /* Check for missing blocks */
774 data_len = be32_to_cpu ( msg->msg.block.block.len );
775 if ( ! data_len ) {
776 DBGC ( peerblk, "PEERBLK %p %d.%d block not found\n",
777 peerblk, peerblk->segment, peerblk->block );
778 return -ENOENT;
779 }
780
781 /* Check for underlength blocks */
782 if ( data_len < ( peerblk->range.end - peerblk->range.start ) ) {
783 DBGC ( peerblk, "PEERBLK %p %d.%d underlength block (%zd "
784 "bytes)\n", peerblk, peerblk->segment, peerblk->block,
785 data_len );
786 return -ERANGE;
787 }
788
789 /* Calculate buffered data length (i.e. excluding data which
790 * was delivered to the final data transfer buffer).
791 */
792 *buf_len = ( data_len - ( peerblk->end - peerblk->start ) );
793
794 /* Describe data before the trimmed content */
795 peerblk->decrypt[PEERBLK_BEFORE].xferbuf = &peerblk->buffer;
796 peerblk->decrypt[PEERBLK_BEFORE].offset =
797 offsetof ( typeof ( *msg ), msg.block.data );
798 peerblk->decrypt[PEERBLK_BEFORE].len =
799 ( peerblk->start -
800 offsetof ( typeof ( *msg ), msg.block.data ) );
801 total = peerblk->decrypt[PEERBLK_BEFORE].len;
802
803 /* Describe data within the trimmed content */
804 peerblk->decrypt[PEERBLK_DURING].offset =
805 peerblk_offset ( peerblk, peerblk->start );
806 peerblk->decrypt[PEERBLK_DURING].len =
807 ( peerblk->end - peerblk->start );
808 total += peerblk->decrypt[PEERBLK_DURING].len;
809
810 /* Describe data after the trimmed content */
811 peerblk->decrypt[PEERBLK_AFTER].xferbuf = &peerblk->buffer;
812 peerblk->decrypt[PEERBLK_AFTER].offset = peerblk->start;
813 peerblk->decrypt[PEERBLK_AFTER].len =
814 ( offsetof ( typeof ( *msg ), msg.block.data )
815 + *buf_len - peerblk->start );
816 total += peerblk->decrypt[PEERBLK_AFTER].len;
817
818 /* Sanity check */
819 assert ( total == be32_to_cpu ( msg->msg.block.block.len ) );
820
821 /* Initialise cipher and digest lengths */
822 peerblk->cipher_remaining = total;
823 peerblk->digest_remaining =
824 ( peerblk->range.end - peerblk->range.start );
825 assert ( peerblk->cipher_remaining >= peerblk->digest_remaining );
826
827 return 0;
828 }
829
830 /**
831 * Parse retrieval protocol message useless details
832 *
833 * @v peerblk PeerDist block download
834 * @v buf_len Length of buffered data
835 * @v vrf_len Length of uselessness to fill in
836 * @ret rc Return status code
837 */
peerblk_parse_useless(struct peerdist_block * peerblk,size_t buf_len,size_t * vrf_len)838 static int peerblk_parse_useless ( struct peerdist_block *peerblk,
839 size_t buf_len, size_t *vrf_len ) {
840 size_t digestsize = peerblk->digestsize;
841 peerblk_msg_blk_t ( digestsize, buf_len, 0, 0 ) *msg =
842 peerblk->buffer.data;
843 size_t len = peerblk->buffer.len;
844
845 /* Check message length */
846 if ( len < offsetof ( typeof ( *msg ), msg.vrf.data ) ) {
847 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
848 "zero-length uselessness (%zd bytes)\n", peerblk,
849 peerblk->segment, peerblk->block, len );
850 return -ERANGE;
851 }
852
853 /* Extract length of uselessness */
854 *vrf_len = be32_to_cpu ( msg->msg.vrf.vrf.len );
855
856 return 0;
857 }
858
859 /**
860 * Parse retrieval protocol message initialisation vector details
861 *
862 * @v peerblk PeerDist block download
863 * @v buf_len Length of buffered data
864 * @v vrf_len Length of uselessness
865 * @ret rc Return status code
866 */
peerblk_parse_iv(struct peerdist_block * peerblk,size_t buf_len,size_t vrf_len)867 static int peerblk_parse_iv ( struct peerdist_block *peerblk, size_t buf_len,
868 size_t vrf_len ) {
869 size_t digestsize = peerblk->digestsize;
870 size_t blksize = peerblk->cipher->blocksize;
871 peerblk_msg_blk_t ( digestsize, buf_len, vrf_len, blksize ) *msg =
872 peerblk->buffer.data;
873 size_t len = peerblk->buffer.len;
874
875 /* Check message length */
876 if ( len < sizeof ( *msg ) ) {
877 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
878 "initialisation vector (%zd bytes)\n", peerblk,
879 peerblk->segment, peerblk->block, len );
880 return -ERANGE;
881 }
882
883 /* Check initialisation vector size */
884 if ( ntohl ( msg->msg.iv.iv.blksize ) != blksize ) {
885 DBGC ( peerblk, "PEERBLK %p %d.%d incorrect IV size %d\n",
886 peerblk, peerblk->segment, peerblk->block,
887 ntohl ( msg->msg.iv.iv.blksize ) );
888 return -EPROTO;
889 }
890
891 /* Set initialisation vector */
892 cipher_setiv ( peerblk->cipher, peerblk->cipherctx, msg->msg.iv.data );
893
894 return 0;
895 }
896
897 /**
898 * Read from decryption buffers
899 *
900 * @v peerblk PeerDist block download
901 * @v data Data buffer
902 * @v len Length to read
903 * @ret rc Return status code
904 */
peerblk_decrypt_read(struct peerdist_block * peerblk,void * data,size_t len)905 static int peerblk_decrypt_read ( struct peerdist_block *peerblk,
906 void *data, size_t len ) {
907 struct peerdist_block_decrypt *decrypt = peerblk->decrypt;
908 size_t frag_len;
909 int rc;
910
911 /* Read from each decryption buffer in turn */
912 for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) {
913
914 /* Calculate length to use from this buffer */
915 frag_len = decrypt->len;
916 if ( frag_len > len )
917 frag_len = len;
918 if ( ! frag_len )
919 continue;
920
921 /* Read from this buffer */
922 if ( ( rc = xferbuf_read ( decrypt->xferbuf, decrypt->offset,
923 data, frag_len ) ) != 0 )
924 return rc;
925 }
926
927 return 0;
928 }
929
930 /**
931 * Write to decryption buffers and update offsets and lengths
932 *
933 * @v peerblk PeerDist block download
934 * @v data Data buffer
935 * @v len Length to read
936 * @ret rc Return status code
937 */
peerblk_decrypt_write(struct peerdist_block * peerblk,const void * data,size_t len)938 static int peerblk_decrypt_write ( struct peerdist_block *peerblk,
939 const void *data, size_t len ) {
940 struct peerdist_block_decrypt *decrypt = peerblk->decrypt;
941 size_t frag_len;
942 int rc;
943
944 /* Write to each decryption buffer in turn */
945 for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) {
946
947 /* Calculate length to use from this buffer */
948 frag_len = decrypt->len;
949 if ( frag_len > len )
950 frag_len = len;
951 if ( ! frag_len )
952 continue;
953
954 /* Write to this buffer */
955 if ( ( rc = xferbuf_write ( decrypt->xferbuf, decrypt->offset,
956 data, frag_len ) ) != 0 )
957 return rc;
958
959 /* Update offset and length */
960 decrypt->offset += frag_len;
961 decrypt->len -= frag_len;
962 }
963
964 return 0;
965 }
966
967 /**
968 * Decrypt one chunk of PeerDist retrieval protocol data
969 *
970 * @v peerblk PeerDist block download
971 */
peerblk_decrypt(struct peerdist_block * peerblk)972 static void peerblk_decrypt ( struct peerdist_block *peerblk ) {
973 struct cipher_algorithm *cipher = peerblk->cipher;
974 struct digest_algorithm *digest = peerblk->digest;
975 struct xfer_buffer *xferbuf;
976 size_t cipher_len;
977 size_t digest_len;
978 void *data;
979 int rc;
980
981 /* Sanity check */
982 assert ( ( PEERBLK_DECRYPT_CHUNKSIZE % cipher->blocksize ) == 0 );
983
984 /* Get the underlying data transfer buffer */
985 xferbuf = xfer_buffer ( &peerblk->xfer );
986 if ( ! xferbuf ) {
987 DBGC ( peerblk, "PEERBLK %p %d.%d has no underlying data "
988 "transfer buffer\n", peerblk, peerblk->segment,
989 peerblk->block );
990 rc = -ENOTSUP;
991 goto err_xfer_buffer;
992 }
993 peerblk->decrypt[PEERBLK_DURING].xferbuf = xferbuf;
994
995 /* Calculate cipher and digest lengths */
996 cipher_len = PEERBLK_DECRYPT_CHUNKSIZE;
997 if ( cipher_len > peerblk->cipher_remaining )
998 cipher_len = peerblk->cipher_remaining;
999 digest_len = cipher_len;
1000 if ( digest_len > peerblk->digest_remaining )
1001 digest_len = peerblk->digest_remaining;
1002 assert ( ( cipher_len & ( cipher->blocksize - 1 ) ) == 0 );
1003
1004 /* Allocate temporary data buffer */
1005 data = malloc ( cipher_len );
1006 if ( ! data ) {
1007 rc = -ENOMEM;
1008 goto err_alloc_data;
1009 }
1010
1011 /* Read ciphertext */
1012 if ( ( rc = peerblk_decrypt_read ( peerblk, data, cipher_len ) ) != 0 ){
1013 DBGC ( peerblk, "PEERBLK %p %d.%d could not read ciphertext: "
1014 "%s\n", peerblk, peerblk->segment, peerblk->block,
1015 strerror ( rc ) );
1016 goto err_read;
1017 }
1018
1019 /* Decrypt data */
1020 cipher_decrypt ( cipher, peerblk->cipherctx, data, data, cipher_len );
1021
1022 /* Add data to digest */
1023 digest_update ( digest, peerblk->digestctx, data, digest_len );
1024
1025 /* Write plaintext */
1026 if ( ( rc = peerblk_decrypt_write ( peerblk, data, cipher_len ) ) != 0){
1027 DBGC ( peerblk, "PEERBLK %p %d.%d could not write plaintext: "
1028 "%s\n", peerblk, peerblk->segment, peerblk->block,
1029 strerror ( rc ) );
1030 goto err_write;
1031 }
1032
1033 /* Consume input */
1034 peerblk->cipher_remaining -= cipher_len;
1035 peerblk->digest_remaining -= digest_len;
1036
1037 /* Free temporary data buffer */
1038 free ( data );
1039
1040 /* Continue processing until all input is consumed */
1041 if ( peerblk->cipher_remaining )
1042 return;
1043
1044 /* Complete download attempt */
1045 peerblk_done ( peerblk, 0 );
1046 return;
1047
1048 err_write:
1049 err_read:
1050 free ( data );
1051 err_alloc_data:
1052 err_xfer_buffer:
1053 peerblk_done ( peerblk, rc );
1054 }
1055
1056 /**
1057 * Close PeerDist retrieval protocol block download attempt
1058 *
1059 * @v peerblk PeerDist block download
1060 * @v rc Reason for close
1061 */
peerblk_retrieval_close(struct peerdist_block * peerblk,int rc)1062 static void peerblk_retrieval_close ( struct peerdist_block *peerblk, int rc ) {
1063 size_t buf_len;
1064 size_t vrf_len;
1065
1066 /* Restart interface */
1067 intf_restart ( &peerblk->retrieval, rc );
1068
1069 /* Fail immediately if we have an error */
1070 if ( rc != 0 )
1071 goto done;
1072
1073 /* Abort download attempt (for testing) if applicable */
1074 if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 )
1075 goto done;
1076
1077 /* Parse message header */
1078 if ( ( rc = peerblk_parse_header ( peerblk ) ) != 0 )
1079 goto done;
1080
1081 /* Parse message segment and block details */
1082 if ( ( rc = peerblk_parse_block ( peerblk, &buf_len ) ) != 0 )
1083 goto done;
1084
1085 /* If the block was plaintext, then there is nothing more to do */
1086 if ( ! peerblk->cipher )
1087 goto done;
1088
1089 /* Parse message useless details */
1090 if ( ( rc = peerblk_parse_useless ( peerblk, buf_len, &vrf_len ) ) != 0)
1091 goto done;
1092
1093 /* Parse message initialisation vector details */
1094 if ( ( rc = peerblk_parse_iv ( peerblk, buf_len, vrf_len ) ) != 0 )
1095 goto done;
1096
1097 /* Fail if decryption length is not aligned to the cipher block size */
1098 if ( peerblk->cipher_remaining & ( peerblk->cipher->blocksize - 1 ) ) {
1099 DBGC ( peerblk, "PEERBLK %p %d.%d unaligned data length %zd\n",
1100 peerblk, peerblk->segment, peerblk->block,
1101 peerblk->cipher_remaining );
1102 rc = -EPROTO;
1103 goto done;
1104 }
1105
1106 /* Stop the download attempt timer: there is no point in
1107 * timing out while decrypting.
1108 */
1109 stop_timer ( &peerblk->timer );
1110
1111 /* Start decryption process */
1112 process_add ( &peerblk->process );
1113 return;
1114
1115 done:
1116 /* Complete download attempt */
1117 peerblk_done ( peerblk, rc );
1118 }
1119
1120 /******************************************************************************
1121 *
1122 * Retry policy
1123 *
1124 ******************************************************************************
1125 */
1126
1127 /**
1128 * Handle PeerDist retry timer expiry
1129 *
1130 * @v timer Retry timer
1131 * @v over Failure indicator
1132 */
peerblk_expired(struct retry_timer * timer,int over __unused)1133 static void peerblk_expired ( struct retry_timer *timer, int over __unused ) {
1134 struct peerdist_block *peerblk =
1135 container_of ( timer, struct peerdist_block, timer );
1136 struct peerdisc_segment *segment = peerblk->discovery.segment;
1137 struct peerdisc_peer *head;
1138 unsigned long now = peerblk_timestamp();
1139 const char *location;
1140 int rc;
1141
1142 /* Profile discovery timeout, if applicable */
1143 if ( ( peerblk->peer == NULL ) && ( timer->timeout != 0 ) ) {
1144 profile_custom ( &peerblk_discovery_timeout_profiler,
1145 ( now - peerblk->started ) );
1146 DBGC ( peerblk, "PEERBLK %p %d.%d discovery timed out after "
1147 "%ld ticks\n", peerblk, peerblk->segment,
1148 peerblk->block, timer->timeout );
1149 }
1150
1151 /* Profile download timeout, if applicable */
1152 if ( ( peerblk->peer != NULL ) && ( timer->timeout != 0 ) ) {
1153 profile_custom ( &peerblk_attempt_timeout_profiler,
1154 ( now - peerblk->attempted ) );
1155 DBGC ( peerblk, "PEERBLK %p %d.%d timed out after %ld ticks\n",
1156 peerblk, peerblk->segment, peerblk->block,
1157 timer->timeout );
1158 }
1159
1160 /* Abort any current download attempt */
1161 peerblk_reset ( peerblk, -ETIMEDOUT );
1162
1163 /* Record attempt start time */
1164 peerblk->attempted = now;
1165
1166 /* If we have exceeded our maximum number of attempt cycles
1167 * (each cycle comprising a retrieval protocol download from
1168 * each peer in the list followed by a raw download from the
1169 * origin server), then abort the overall download.
1170 */
1171 head = list_entry ( &segment->peers, struct peerdisc_peer, list );
1172 if ( ( peerblk->peer == head ) &&
1173 ( ++peerblk->cycles >= PEERBLK_MAX_ATTEMPT_CYCLES ) ) {
1174 rc = peerblk->rc;
1175 assert ( rc != 0 );
1176 goto err;
1177 }
1178
1179 /* If we have not yet made any download attempts, then move to
1180 * the start of the peer list.
1181 */
1182 if ( peerblk->peer == NULL )
1183 peerblk->peer = head;
1184
1185 /* Attempt retrieval protocol download from next usable peer */
1186 list_for_each_entry_continue ( peerblk->peer, &segment->peers, list ) {
1187
1188 /* Attempt retrieval protocol download from this peer */
1189 location = peerblk->peer->location;
1190 if ( ( rc = peerblk_retrieval_open ( peerblk,
1191 location ) ) != 0 ) {
1192 /* Non-fatal: continue to try next peer */
1193 continue;
1194 }
1195
1196 /* Start download attempt timer */
1197 peerblk->rc = -ETIMEDOUT;
1198 start_timer_fixed ( &peerblk->timer,
1199 PEERBLK_RETRIEVAL_OPEN_TIMEOUT );
1200 return;
1201 }
1202
1203 /* Attempt raw download */
1204 if ( ( rc = peerblk_raw_open ( peerblk ) ) != 0 )
1205 goto err;
1206
1207 /* Start download attempt timer */
1208 peerblk->rc = -ETIMEDOUT;
1209 start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_OPEN_TIMEOUT );
1210 return;
1211
1212 err:
1213 peerblk_close ( peerblk, rc );
1214 }
1215
1216 /**
1217 * Handle PeerDist peer discovery
1218 *
1219 * @v discovery PeerDist discovery client
1220 */
peerblk_discovered(struct peerdisc_client * discovery)1221 static void peerblk_discovered ( struct peerdisc_client *discovery ) {
1222 struct peerdist_block *peerblk =
1223 container_of ( discovery, struct peerdist_block, discovery );
1224 unsigned long now = peerblk_timestamp();
1225
1226 /* Do nothing unless we are still waiting for the initial
1227 * discovery timeout.
1228 */
1229 if ( ( peerblk->peer != NULL ) || ( peerblk->timer.timeout == 0 ) )
1230 return;
1231
1232 /* Schedule an immediate retry */
1233 start_timer_nodelay ( &peerblk->timer );
1234
1235 /* Profile discovery success */
1236 profile_custom ( &peerblk_discovery_success_profiler,
1237 ( now - peerblk->started ) );
1238 }
1239
1240 /******************************************************************************
1241 *
1242 * Opener
1243 *
1244 ******************************************************************************
1245 */
1246
1247 /** PeerDist block download data transfer interface operations */
1248 static struct interface_operation peerblk_xfer_operations[] = {
1249 INTF_OP ( intf_close, struct peerdist_block *, peerblk_close ),
1250 };
1251
1252 /** PeerDist block download data transfer interface descriptor */
1253 static struct interface_descriptor peerblk_xfer_desc =
1254 INTF_DESC ( struct peerdist_block, xfer, peerblk_xfer_operations );
1255
1256 /** PeerDist block download raw data interface operations */
1257 static struct interface_operation peerblk_raw_operations[] = {
1258 INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_raw_rx ),
1259 INTF_OP ( intf_close, struct peerdist_block *, peerblk_raw_close ),
1260 };
1261
1262 /** PeerDist block download raw data interface descriptor */
1263 static struct interface_descriptor peerblk_raw_desc =
1264 INTF_DESC ( struct peerdist_block, raw, peerblk_raw_operations );
1265
1266 /** PeerDist block download retrieval protocol interface operations */
1267 static struct interface_operation peerblk_retrieval_operations[] = {
1268 INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_retrieval_rx ),
1269 INTF_OP ( intf_close, struct peerdist_block *, peerblk_retrieval_close),
1270 };
1271
1272 /** PeerDist block download retrieval protocol interface descriptor */
1273 static struct interface_descriptor peerblk_retrieval_desc =
1274 INTF_DESC ( struct peerdist_block, retrieval,
1275 peerblk_retrieval_operations );
1276
1277 /** PeerDist block download decryption process descriptor */
1278 static struct process_descriptor peerblk_process_desc =
1279 PROC_DESC ( struct peerdist_block, process, peerblk_decrypt );
1280
1281 /** PeerDist block download discovery operations */
1282 static struct peerdisc_client_operations peerblk_discovery_operations = {
1283 .discovered = peerblk_discovered,
1284 };
1285
1286 /**
1287 * Open PeerDist block download
1288 *
1289 * @v xfer Data transfer interface
1290 * @v uri Original URI
1291 * @v info Content information block
1292 * @ret rc Return status code
1293 */
peerblk_open(struct interface * xfer,struct uri * uri,struct peerdist_info_block * block)1294 int peerblk_open ( struct interface *xfer, struct uri *uri,
1295 struct peerdist_info_block *block ) {
1296 const struct peerdist_info_segment *segment = block->segment;
1297 const struct peerdist_info *info = segment->info;
1298 struct digest_algorithm *digest = info->digest;
1299 struct peerdist_block *peerblk;
1300 unsigned long timeout;
1301 size_t digestsize;
1302 int rc;
1303
1304 /* Allocate and initialise structure */
1305 peerblk = zalloc ( sizeof ( *peerblk ) + digest->ctxsize );
1306 if ( ! peerblk ) {
1307 rc = -ENOMEM;
1308 goto err_alloc;
1309 }
1310 ref_init ( &peerblk->refcnt, peerblk_free );
1311 intf_init ( &peerblk->xfer, &peerblk_xfer_desc, &peerblk->refcnt );
1312 intf_init ( &peerblk->raw, &peerblk_raw_desc, &peerblk->refcnt );
1313 intf_init ( &peerblk->retrieval, &peerblk_retrieval_desc,
1314 &peerblk->refcnt );
1315 peerblk->uri = uri_get ( uri );
1316 memcpy ( &peerblk->range, &block->range, sizeof ( peerblk->range ) );
1317 memcpy ( &peerblk->trim, &block->trim, sizeof ( peerblk->trim ) );
1318 peerblk->offset = ( block->trim.start - info->trim.start );
1319 peerblk->digest = info->digest;
1320 peerblk->digestsize = digestsize = info->digestsize;
1321 peerblk->digestctx = ( ( ( void * ) peerblk ) + sizeof ( *peerblk ) );
1322 peerblk->segment = segment->index;
1323 memcpy ( peerblk->id, segment->id, sizeof ( peerblk->id ) );
1324 memcpy ( peerblk->secret, segment->secret, sizeof ( peerblk->secret ) );
1325 peerblk->block = block->index;
1326 memcpy ( peerblk->hash, block->hash, sizeof ( peerblk->hash ) );
1327 xferbuf_malloc_init ( &peerblk->buffer );
1328 process_init_stopped ( &peerblk->process, &peerblk_process_desc,
1329 &peerblk->refcnt );
1330 peerdisc_init ( &peerblk->discovery, &peerblk_discovery_operations );
1331 timer_init ( &peerblk->timer, peerblk_expired, &peerblk->refcnt );
1332 DBGC2 ( peerblk, "PEERBLK %p %d.%d id %02x%02x%02x%02x%02x..."
1333 "%02x%02x%02x [%08zx,%08zx)", peerblk, peerblk->segment,
1334 peerblk->block, peerblk->id[0], peerblk->id[1], peerblk->id[2],
1335 peerblk->id[3], peerblk->id[4], peerblk->id[ digestsize - 3 ],
1336 peerblk->id[ digestsize - 2 ], peerblk->id[ digestsize - 1 ],
1337 peerblk->range.start, peerblk->range.end );
1338 if ( ( peerblk->trim.start != peerblk->range.start ) ||
1339 ( peerblk->trim.end != peerblk->range.end ) ) {
1340 DBGC2 ( peerblk, " covers [%08zx,%08zx)",
1341 peerblk->trim.start, peerblk->trim.end );
1342 }
1343 DBGC2 ( peerblk, "\n" );
1344
1345 /* Open discovery */
1346 if ( ( rc = peerdisc_open ( &peerblk->discovery, peerblk->id,
1347 peerblk->digestsize ) ) != 0 )
1348 goto err_open_discovery;
1349
1350 /* Schedule a retry attempt either immediately (if we already
1351 * have some peers) or after the discovery timeout.
1352 */
1353 timeout = ( list_empty ( &peerblk->discovery.segment->peers ) ?
1354 ( peerdisc_timeout_secs * TICKS_PER_SEC ) : 0 );
1355 start_timer_fixed ( &peerblk->timer, timeout );
1356
1357 /* Record start time */
1358 peerblk->started = peerblk_timestamp();
1359
1360 /* Attach to parent interface, mortalise self, and return */
1361 intf_plug_plug ( xfer, &peerblk->xfer );
1362 ref_put ( &peerblk->refcnt );
1363 return 0;
1364
1365 err_open_discovery:
1366 peerblk_close ( peerblk, rc );
1367 err_alloc:
1368 return rc;
1369 }
1370