iPXE
peerblk.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or (at your option) any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stdlib.h>
00027 #include <stdio.h>
00028 #include <errno.h>
00029 #include <ipxe/http.h>
00030 #include <ipxe/iobuf.h>
00031 #include <ipxe/xfer.h>
00032 #include <ipxe/uri.h>
00033 #include <ipxe/timer.h>
00034 #include <ipxe/profile.h>
00035 #include <ipxe/fault.h>
00036 #include <ipxe/pccrr.h>
00037 #include <ipxe/peerblk.h>
00038 
00039 /** @file
00040  *
00041  * Peer Content Caching and Retrieval (PeerDist) protocol block downloads
00042  *
00043  */
00044 
00045 /** PeerDist decryption chunksize
00046  *
00047  * This is a policy decision.
00048  */
00049 #define PEERBLK_DECRYPT_CHUNKSIZE 2048
00050 
00051 /** PeerDist maximum number of concurrent raw block downloads
00052  *
00053  * Raw block downloads are expensive if the origin server uses HTTPS,
00054  * since each concurrent download will require local TLS resources
00055  * (including potentially large received encrypted data buffers).
00056  *
00057  * Raw block downloads may also be prohibitively slow to initiate when
00058  * the origin server is using HTTPS and client certificates.  Origin
00059  * servers for PeerDist downloads are likely to be running IIS, which
00060  * has a bug that breaks session resumption and requires each
00061  * connection to go through the full client certificate verification.
00062  *
00063  * Limit the total number of concurrent raw block downloads to
00064  * ameliorate these problems.
00065  *
00066  * This is a policy decision.
00067  */
00068 #define PEERBLK_RAW_MAX 2
00069 
00070 /** PeerDist raw block download attempt initial progress timeout
00071  *
00072  * This is a policy decision.
00073  */
00074 #define PEERBLK_RAW_OPEN_TIMEOUT ( 10 * TICKS_PER_SEC )
00075 
00076 /** PeerDist raw block download attempt ongoing progress timeout
00077  *
00078  * This is a policy decision.
00079  */
00080 #define PEERBLK_RAW_RX_TIMEOUT ( 15 * TICKS_PER_SEC )
00081 
00082 /** PeerDist retrieval protocol block download attempt initial progress timeout
00083  *
00084  * This is a policy decision.
00085  */
00086 #define PEERBLK_RETRIEVAL_OPEN_TIMEOUT ( 3 * TICKS_PER_SEC )
00087 
00088 /** PeerDist retrieval protocol block download attempt ongoing progress timeout
00089  *
00090  * This is a policy decision.
00091  */
00092 #define PEERBLK_RETRIEVAL_RX_TIMEOUT ( 5 * TICKS_PER_SEC )
00093 
00094 /** PeerDist maximum number of full download attempt cycles
00095  *
00096  * This is the maximum number of times that we will try a full cycle
00097  * of download attempts (i.e. a retrieval protocol download attempt
00098  * from each discovered peer plus a raw download attempt from the
00099  * origin server).
00100  *
00101  * This is a policy decision.
00102  */
00103 #define PEERBLK_MAX_ATTEMPT_CYCLES 4
00104 
00105 /** PeerDist block download profiler */
00106 static struct profiler peerblk_download_profiler __profiler =
00107         { .name = "peerblk.download" };
00108 
00109 /** PeerDist block download attempt success profiler */
00110 static struct profiler peerblk_attempt_success_profiler __profiler =
00111         { .name = "peerblk.attempt.success" };
00112 
00113 /** PeerDist block download attempt failure profiler */
00114 static struct profiler peerblk_attempt_failure_profiler __profiler =
00115         { .name = "peerblk.attempt.failure" };
00116 
00117 /** PeerDist block download attempt timeout profiler */
00118 static struct profiler peerblk_attempt_timeout_profiler __profiler =
00119         { .name = "peerblk.attempt.timeout" };
00120 
00121 /** PeerDist block download discovery success profiler */
00122 static struct profiler peerblk_discovery_success_profiler __profiler =
00123         { .name = "peerblk.discovery.success" };
00124 
00125 /** PeerDist block download discovery timeout profiler */
00126 static struct profiler peerblk_discovery_timeout_profiler __profiler =
00127         { .name = "peerblk.discovery.timeout" };
00128 
00129 static void peerblk_dequeue ( struct peerdist_block *peerblk );
00130 
00131 /**
00132  * Get profiling timestamp
00133  *
00134  * @ret timestamp       Timestamp
00135  */
00136 static inline __attribute__ (( always_inline )) unsigned long
00137 peerblk_timestamp ( void ) {
00138 
00139         if ( PROFILING ) {
00140                 return currticks();
00141         } else {
00142                 return 0;
00143         }
00144 }
00145 
00146 /**
00147  * Free PeerDist block download
00148  *
00149  * @v refcnt            Reference count
00150  */
00151 static void peerblk_free ( struct refcnt *refcnt ) {
00152         struct peerdist_block *peerblk =
00153                 container_of ( refcnt, struct peerdist_block, refcnt );
00154 
00155         uri_put ( peerblk->uri );
00156         free ( peerblk->cipherctx );
00157         free ( peerblk );
00158 }
00159 
00160 /**
00161  * Reset PeerDist block download attempt
00162  *
00163  * @v peerblk           PeerDist block download
00164  * @v rc                Reason for reset
00165  */
00166 static void peerblk_reset ( struct peerdist_block *peerblk, int rc ) {
00167 
00168         /* Stop decryption process */
00169         process_del ( &peerblk->process );
00170 
00171         /* Stop timer */
00172         stop_timer ( &peerblk->timer );
00173 
00174         /* Abort any current download attempt */
00175         intf_restart ( &peerblk->raw, rc );
00176         intf_restart ( &peerblk->retrieval, rc );
00177 
00178         /* Remove from download queue, if applicable */
00179         if ( peerblk->queue )
00180                 peerblk_dequeue ( peerblk );
00181 
00182         /* Empty received data buffer */
00183         xferbuf_free ( &peerblk->buffer );
00184         peerblk->pos = 0;
00185 
00186         /* Reset digest and free cipher context */
00187         digest_init ( peerblk->digest, peerblk->digestctx );
00188         free ( peerblk->cipherctx );
00189         peerblk->cipherctx = NULL;
00190         peerblk->cipher = NULL;
00191 
00192         /* Reset trim thresholds */
00193         peerblk->start = ( peerblk->trim.start - peerblk->range.start );
00194         peerblk->end = ( peerblk->trim.end - peerblk->range.start );
00195         assert ( peerblk->start <= peerblk->end );
00196 }
00197 
00198 /**
00199  * Close PeerDist block download
00200  *
00201  * @v peerblk           PeerDist block download
00202  * @v rc                Reason for close
00203  */
00204 static void peerblk_close ( struct peerdist_block *peerblk, int rc ) {
00205         unsigned long now = peerblk_timestamp();
00206 
00207         /* Profile overall block download */
00208         profile_custom ( &peerblk_download_profiler,
00209                          ( now - peerblk->started ) );
00210 
00211         /* Reset download attempt */
00212         peerblk_reset ( peerblk, rc );
00213 
00214         /* Close discovery */
00215         peerdisc_close ( &peerblk->discovery );
00216 
00217         /* Shut down all interfaces */
00218         intf_shutdown ( &peerblk->retrieval, rc );
00219         intf_shutdown ( &peerblk->raw, rc );
00220         intf_shutdown ( &peerblk->xfer, rc );
00221 }
00222 
00223 /**
00224  * Calculate offset within overall download
00225  *
00226  * @v peerblk           PeerDist block download
00227  * @v pos               Position within incoming data stream
00228  * @ret offset          Offset within overall download
00229  */
00230 static inline __attribute__ (( always_inline )) size_t
00231 peerblk_offset ( struct peerdist_block *peerblk, size_t pos ) {
00232 
00233         return ( ( pos - peerblk->start ) + peerblk->offset );
00234 }
00235 
00236 /**
00237  * Deliver download attempt data block
00238  *
00239  * @v peerblk           PeerDist block download
00240  * @v iobuf             I/O buffer
00241  * @v meta              Original data transfer metadata
00242  * @v pos               Position within incoming data stream
00243  * @ret rc              Return status code
00244  */
00245 static int peerblk_deliver ( struct peerdist_block *peerblk,
00246                              struct io_buffer *iobuf,
00247                              struct xfer_metadata *meta, size_t pos ) {
00248         struct xfer_metadata xfer_meta;
00249         size_t len = iob_len ( iobuf );
00250         size_t start = pos;
00251         size_t end = ( pos + len );
00252         int rc;
00253 
00254         /* Discard zero-length packets and packets which lie entirely
00255          * outside the trimmed range.
00256          */
00257         if ( ( start >= peerblk->end ) || ( end <= peerblk->start ) ||
00258              ( len == 0 ) ) {
00259                 free_iob ( iobuf );
00260                 return 0;
00261         }
00262 
00263         /* Truncate data to within trimmed range */
00264         if ( start < peerblk->start ) {
00265                 iob_pull ( iobuf, ( peerblk->start - start ) );
00266                 start = peerblk->start;
00267         }
00268         if ( end > peerblk->end ) {
00269                 iob_unput ( iobuf, ( end - peerblk->end ) );
00270                 end = peerblk->end;
00271         }
00272 
00273         /* Construct metadata */
00274         memcpy ( &xfer_meta, meta, sizeof ( xfer_meta ) );
00275         xfer_meta.flags |= XFER_FL_ABS_OFFSET;
00276         xfer_meta.offset = peerblk_offset ( peerblk, start );
00277 
00278         /* Deliver data */
00279         if ( ( rc = xfer_deliver ( &peerblk->xfer, iob_disown ( iobuf ),
00280                                    &xfer_meta ) ) != 0 ) {
00281                 DBGC ( peerblk, "PEERBLK %p %d.%d could not deliver data: %s\n",
00282                        peerblk, peerblk->segment, peerblk->block,
00283                        strerror ( rc ) );
00284                 return rc;
00285         }
00286 
00287         return 0;
00288 }
00289 
00290 /**
00291  * Finish PeerDist block download attempt
00292  *
00293  * @v peerblk           PeerDist block download
00294  * @v rc                Reason for close
00295  */
00296 static void peerblk_done ( struct peerdist_block *peerblk, int rc ) {
00297         struct digest_algorithm *digest = peerblk->digest;
00298         struct peerdisc_segment *segment = peerblk->discovery.segment;
00299         struct peerdisc_peer *head;
00300         struct peerdisc_peer *peer;
00301         uint8_t hash[digest->digestsize];
00302         unsigned long now = peerblk_timestamp();
00303 
00304         /* Check for errors on completion */
00305         if ( rc != 0 ) {
00306                 DBGC ( peerblk, "PEERBLK %p %d.%d attempt failed: %s\n",
00307                        peerblk, peerblk->segment, peerblk->block,
00308                        strerror ( rc ) );
00309                 goto err;
00310         }
00311 
00312         /* Check digest */
00313         digest_final ( digest, peerblk->digestctx, hash );
00314         if ( memcmp ( hash, peerblk->hash, peerblk->digestsize ) != 0 ) {
00315                 DBGC ( peerblk, "PEERBLK %p %d.%d digest mismatch:\n",
00316                        peerblk, peerblk->segment, peerblk->block );
00317                 DBGC_HDA ( peerblk, 0, hash, peerblk->digestsize );
00318                 DBGC_HDA ( peerblk, 0, peerblk->hash, peerblk->digestsize );
00319                 rc = -EIO;
00320                 goto err;
00321         }
00322 
00323         /* Profile successful attempt */
00324         profile_custom ( &peerblk_attempt_success_profiler,
00325                          ( now - peerblk->attempted ) );
00326 
00327         /* Report peer statistics */
00328         head = list_entry ( &segment->peers, struct peerdisc_peer, list );
00329         peer = ( ( peerblk->peer == head ) ? NULL : peerblk->peer );
00330         peerdisc_stat ( &peerblk->xfer, peer, &segment->peers );
00331 
00332         /* Close download */
00333         peerblk_close ( peerblk, 0 );
00334         return;
00335 
00336  err:
00337         /* Record failure reason and schedule a retry attempt */
00338         profile_custom ( &peerblk_attempt_failure_profiler,
00339                          ( now - peerblk->attempted ) );
00340         peerblk_reset ( peerblk, rc );
00341         peerblk->rc = rc;
00342         start_timer_nodelay ( &peerblk->timer );
00343 }
00344 
00345 /******************************************************************************
00346  *
00347  * Raw block download attempts (using an HTTP range request)
00348  *
00349  ******************************************************************************
00350  */
00351 
00352 /**
00353  * Open PeerDist raw block download attempt
00354  *
00355  * @v peerblk           PeerDist block download
00356  * @ret rc              Return status code
00357  */
00358 static int peerblk_raw_open ( struct peerdist_block *peerblk ) {
00359         struct http_request_range range;
00360         int rc;
00361 
00362         DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting raw range request\n",
00363                 peerblk, peerblk->segment, peerblk->block );
00364 
00365         /* Construct HTTP range */
00366         memset ( &range, 0, sizeof ( range ) );
00367         range.start = peerblk->range.start;
00368         range.len = ( peerblk->range.end - peerblk->range.start );
00369 
00370         /* Initiate range request to retrieve block */
00371         if ( ( rc = http_open ( &peerblk->raw, &http_get, peerblk->uri,
00372                                 &range, NULL ) ) != 0 ) {
00373                 DBGC ( peerblk, "PEERBLK %p %d.%d could not create range "
00374                        "request: %s\n", peerblk, peerblk->segment,
00375                        peerblk->block, strerror ( rc ) );
00376                 return rc;
00377         }
00378 
00379         /* Annul HTTP connection (for testing) if applicable.  Do not
00380          * report as an immediate error, in order to test our ability
00381          * to recover from a totally unresponsive HTTP server.
00382          */
00383         if ( inject_fault ( PEERBLK_ANNUL_RATE ) )
00384                 intf_restart ( &peerblk->raw, 0 );
00385 
00386         /* Start download attempt timer */
00387         peerblk->rc = -ETIMEDOUT;
00388         start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_OPEN_TIMEOUT );
00389 
00390         return 0;
00391 }
00392 
00393 /**
00394  * Receive PeerDist raw data
00395  *
00396  * @v peerblk           PeerDist block download
00397  * @v iobuf             I/O buffer
00398  * @v meta              Data transfer metadata
00399  * @ret rc              Return status code
00400  */
00401 static int peerblk_raw_rx ( struct peerdist_block *peerblk,
00402                             struct io_buffer *iobuf,
00403                             struct xfer_metadata *meta ) {
00404         size_t len = iob_len ( iobuf );
00405         size_t pos = peerblk->pos;
00406         size_t mid = ( ( peerblk->range.end - peerblk->range.start ) / 2 );
00407         int rc;
00408 
00409         /* Corrupt received data (for testing) if applicable */
00410         inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len );
00411 
00412         /* Fail if data is delivered out of order, since the streaming
00413          * digest requires strict ordering.
00414          */
00415         if ( ( rc = xfer_check_order ( meta, &peerblk->pos, len ) ) != 0 )
00416                 goto err;
00417 
00418         /* Add data to digest */
00419         digest_update ( peerblk->digest, peerblk->digestctx, iobuf->data, len );
00420 
00421         /* Deliver data */
00422         if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta,
00423                                       pos ) ) != 0 )
00424                 goto err;
00425 
00426         /* Extend download attempt timer */
00427         start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_RX_TIMEOUT );
00428 
00429         /* Stall download attempt (for testing) if applicable */
00430         if ( ( pos < mid ) && ( ( pos + len ) >= mid ) &&
00431              ( ( rc = inject_fault ( PEERBLK_STALL_RATE ) ) != 0 ) ) {
00432                 intf_restart ( &peerblk->raw, rc );
00433         }
00434 
00435         return 0;
00436 
00437  err:
00438         free_iob ( iobuf );
00439         peerblk_done ( peerblk, rc );
00440         return rc;
00441 }
00442 
00443 /**
00444  * Close PeerDist raw block download attempt
00445  *
00446  * @v peerblk           PeerDist block download
00447  * @v rc                Reason for close
00448  */
00449 static void peerblk_raw_close ( struct peerdist_block *peerblk, int rc ) {
00450 
00451         /* Restart interface */
00452         intf_restart ( &peerblk->raw, rc );
00453 
00454         /* Fail immediately if we have an error */
00455         if ( rc != 0 )
00456                 goto done;
00457 
00458         /* Abort download attempt (for testing) if applicable */
00459         if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 )
00460                 goto done;
00461 
00462  done:
00463         /* Complete download attempt */
00464         peerblk_done ( peerblk, rc );
00465 }
00466 
00467 /******************************************************************************
00468  *
00469  * Block download queue
00470  *
00471  ******************************************************************************
00472  */
00473 
00474 /**
00475  * PeerDist block download queue process
00476  *
00477  * @v queue             Block download queue
00478  */
00479 static void peerblk_step ( struct peerdist_block_queue *queue ) {
00480         struct peerdist_block *peerblk;
00481         int rc;
00482 
00483         /* Do nothing yet if we have too many open block downloads */
00484         if ( queue->count >= queue->max )
00485                 return;
00486 
00487         /* Do nothing unless there are queued block downloads */
00488         peerblk = list_first_entry ( &queue->list, struct peerdist_block,
00489                                      queued );
00490         if ( ! peerblk )
00491                 return;
00492 
00493         /* Reschedule queue process */
00494         process_add ( &queue->process );
00495 
00496         /* Remove block from queue */
00497         list_del ( &peerblk->queued );
00498         INIT_LIST_HEAD ( &peerblk->queued );
00499 
00500         /* Attempt download */
00501         if ( ( rc = queue->open ( peerblk ) ) != 0 ) {
00502                 peerblk_close ( peerblk, rc );
00503                 return;
00504         }
00505 
00506         /* Increment open block download count */
00507         queue->count++;
00508 }
00509 
00510 /**
00511  * Add block to download queue
00512  *
00513  * @v peerblk           PeerDist block download
00514  * @v queue             Block download queue
00515  */
00516 static void peerblk_enqueue ( struct peerdist_block *peerblk,
00517                               struct peerdist_block_queue *queue ) {
00518 
00519         /* Sanity checks */
00520         assert ( peerblk->queue == NULL );
00521         assert ( list_empty ( &peerblk->queued ) );
00522 
00523         /* Add block to queue */
00524         peerblk->queue = queue;
00525         list_add_tail ( &peerblk->queued, &queue->list );
00526 
00527         /* Schedule queue process */
00528         process_add ( &queue->process );
00529 }
00530 
00531 /**
00532  * Remove block from download queue
00533  *
00534  * @v peerblk           PeerDist block download
00535  */
00536 static void peerblk_dequeue ( struct peerdist_block *peerblk ) {
00537         struct peerdist_block_queue *queue = peerblk->queue;
00538 
00539         /* Sanity checks */
00540         assert ( queue != NULL );
00541 
00542         /* Remove block from queue */
00543         peerblk->queue = NULL;
00544         if ( list_empty ( &peerblk->queued ) ) {
00545 
00546                 /* Open download: decrement count and reschedule queue */
00547                 queue->count--;
00548                 process_add ( &queue->process );
00549 
00550         } else {
00551 
00552                 /* Queued download: remove from queue */
00553                 list_del ( &peerblk->queued );
00554                 INIT_LIST_HEAD ( &peerblk->queued );
00555         }
00556 }
00557 
00558 /** PeerDist block download queue process descriptor */
00559 static struct process_descriptor peerblk_queue_desc =
00560         PROC_DESC_ONCE ( struct peerdist_block_queue, process, peerblk_step );
00561 
00562 /** Raw block download queue */
00563 static struct peerdist_block_queue peerblk_raw_queue = {
00564         .process = PROC_INIT ( peerblk_raw_queue.process, &peerblk_queue_desc ),
00565         .list = LIST_HEAD_INIT ( peerblk_raw_queue.list ),
00566         .max = PEERBLK_RAW_MAX,
00567         .open = peerblk_raw_open,
00568 };
00569 
00570 /******************************************************************************
00571  *
00572  * Retrieval protocol block download attempts (using HTTP POST)
00573  *
00574  ******************************************************************************
00575  */
00576 
00577 /**
00578  * Construct PeerDist retrieval protocol URI
00579  *
00580  * @v location          Peer location
00581  * @ret uri             Retrieval URI, or NULL on error
00582  */
00583 static struct uri * peerblk_retrieval_uri ( const char *location ) {
00584         char uri_string[ 7 /* "http://" */ + strlen ( location ) +
00585                          sizeof ( PEERDIST_MAGIC_PATH /* includes NUL */ ) ];
00586 
00587         /* Construct URI string */
00588         snprintf ( uri_string, sizeof ( uri_string ),
00589                    ( "http://%s" PEERDIST_MAGIC_PATH ), location );
00590 
00591         /* Parse URI string */
00592         return parse_uri ( uri_string );
00593 }
00594 
00595 /**
00596  * Open PeerDist retrieval protocol block download attempt
00597  *
00598  * @v peerblk           PeerDist block download
00599  * @v location          Peer location
00600  * @ret rc              Return status code
00601  */
00602 static int peerblk_retrieval_open ( struct peerdist_block *peerblk,
00603                                     const char *location ) {
00604         size_t digestsize = peerblk->digestsize;
00605         peerdist_msg_getblks_t ( digestsize, 1, 0 ) req;
00606         peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *rsp;
00607         struct http_request_content content;
00608         struct uri *uri;
00609         int rc;
00610 
00611         DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting retrieval from %s\n",
00612                 peerblk, peerblk->segment, peerblk->block, location );
00613 
00614         /* Construct block fetch request */
00615         memset ( &req, 0, sizeof ( req ) );
00616         req.getblks.hdr.version.raw = htonl ( PEERDIST_MSG_GETBLKS_VERSION );
00617         req.getblks.hdr.type = htonl ( PEERDIST_MSG_GETBLKS_TYPE );
00618         req.getblks.hdr.len = htonl ( sizeof ( req ) );
00619         req.getblks.hdr.algorithm = htonl ( PEERDIST_MSG_AES_128_CBC );
00620         req.segment.segment.digestsize = htonl ( digestsize );
00621         memcpy ( req.segment.id, peerblk->id, digestsize );
00622         req.ranges.ranges.count = htonl ( 1 );
00623         req.ranges.range[0].first = htonl ( peerblk->block );
00624         req.ranges.range[0].count = htonl ( 1 );
00625 
00626         /* Construct POST request content */
00627         memset ( &content, 0, sizeof ( content ) );
00628         content.data = &req;
00629         content.len = sizeof ( req );
00630 
00631         /* Construct URI */
00632         if ( ( uri = peerblk_retrieval_uri ( location ) ) == NULL ) {
00633                 rc = -ENOMEM;
00634                 goto err_uri;
00635         }
00636 
00637         /* Update trim thresholds */
00638         peerblk->start += offsetof ( typeof ( *rsp ), msg.vrf );
00639         peerblk->end += offsetof ( typeof ( *rsp ), msg.vrf );
00640 
00641         /* Initiate HTTP POST to retrieve block */
00642         if ( ( rc = http_open ( &peerblk->retrieval, &http_post, uri,
00643                                 NULL, &content ) ) != 0 ) {
00644                 DBGC ( peerblk, "PEERBLK %p %d.%d could not create retrieval "
00645                        "request: %s\n", peerblk, peerblk->segment,
00646                        peerblk->block, strerror ( rc ) );
00647                 goto err_open;
00648         }
00649 
00650         /* Annul HTTP connection (for testing) if applicable.  Do not
00651          * report as an immediate error, in order to test our ability
00652          * to recover from a totally unresponsive HTTP server.
00653          */
00654         if ( inject_fault ( PEERBLK_ANNUL_RATE ) )
00655                 intf_restart ( &peerblk->retrieval, 0 );
00656 
00657         /* Start download attempt timer */
00658         peerblk->rc = -ETIMEDOUT;
00659         start_timer_fixed ( &peerblk->timer, PEERBLK_RETRIEVAL_OPEN_TIMEOUT );
00660 
00661  err_open:
00662         uri_put ( uri );
00663  err_uri:
00664         return rc;
00665 }
00666 
00667 /**
00668  * Receive PeerDist retrieval protocol data
00669  *
00670  * @v peerblk           PeerDist block download
00671  * @v iobuf             I/O buffer
00672  * @v meta              Data transfer metadata
00673  * @ret rc              Return status code
00674  */
00675 static int peerblk_retrieval_rx ( struct peerdist_block *peerblk,
00676                                   struct io_buffer *iobuf,
00677                                   struct xfer_metadata *meta ) {
00678         size_t len = iob_len ( iobuf );
00679         size_t start;
00680         size_t end;
00681         size_t before;
00682         size_t after;
00683         size_t cut;
00684         int rc;
00685 
00686         /* Some genius at Microsoft thought it would be a great idea
00687          * to place the AES-CBC initialisation vector *after* the
00688          * encrypted data, thereby making it logically impossible to
00689          * decrypt each packet as it arrives.
00690          *
00691          * To work around this mindless stupidity, we deliver the
00692          * ciphertext as-is and later use xfer_buffer() to obtain
00693          * access to the underlying data transfer buffer in order to
00694          * perform the decryption.
00695          *
00696          * There will be some data both before and after the bytes
00697          * corresponding to the trimmed plaintext: a MSG_BLK
00698          * header/footer, some block padding for the AES-CBC cipher,
00699          * and a possibly large quantity of unwanted ciphertext which
00700          * is excluded from the trimmed content range.  We store this
00701          * data in a local data transfer buffer.  If the amount of
00702          * data to be stored is too large, we will fail allocation and
00703          * so eventually fall back to using a range request (which
00704          * does not require this kind of temporary storage
00705          * allocation).
00706          */
00707 
00708         /* Corrupt received data (for testing) if applicable */
00709         inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len );
00710 
00711         /* Calculate start and end positions of this buffer */
00712         start = peerblk->pos;
00713         if ( meta->flags & XFER_FL_ABS_OFFSET )
00714                 start = 0;
00715         start += meta->offset;
00716         end = ( start + len );
00717 
00718         /* Buffer any data before the trimmed content */
00719         if ( ( start < peerblk->start ) && ( len > 0 ) ) {
00720 
00721                 /* Calculate length of data before the trimmed content */
00722                 before = ( peerblk->start - start );
00723                 if ( before > len )
00724                         before = len;
00725 
00726                 /* Buffer data before the trimmed content */
00727                 if ( ( rc = xferbuf_write ( &peerblk->buffer, start,
00728                                             iobuf->data, before ) ) != 0 ) {
00729                         DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer "
00730                                "data: %s\n", peerblk, peerblk->segment,
00731                                peerblk->block, strerror ( rc ) );
00732                         goto err;
00733                 }
00734         }
00735 
00736         /* Buffer any data after the trimmed content */
00737         if ( ( end > peerblk->end ) && ( len > 0 ) ) {
00738 
00739                 /* Calculate length of data after the trimmed content */
00740                 after = ( end - peerblk->end );
00741                 if ( after > len )
00742                         after = len;
00743 
00744                 /* Buffer data after the trimmed content */
00745                 cut = ( peerblk->end - peerblk->start );
00746                 if ( ( rc = xferbuf_write ( &peerblk->buffer,
00747                                             ( end - after - cut ),
00748                                             ( iobuf->data + len - after ),
00749                                             after ) ) != 0 ) {
00750                         DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer "
00751                                "data: %s\n", peerblk, peerblk->segment,
00752                                peerblk->block, strerror ( rc ) );
00753                         goto err;
00754                 }
00755         }
00756 
00757         /* Deliver any remaining data */
00758         if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta,
00759                                       start ) ) != 0 )
00760                 goto err;
00761 
00762         /* Update position */
00763         peerblk->pos = end;
00764 
00765         /* Extend download attempt timer */
00766         start_timer_fixed ( &peerblk->timer, PEERBLK_RETRIEVAL_RX_TIMEOUT );
00767 
00768         /* Stall download attempt (for testing) if applicable */
00769         if ( ( start < peerblk->end ) && ( end >= peerblk->end ) &&
00770              ( ( rc = inject_fault ( PEERBLK_STALL_RATE ) ) != 0 ) ) {
00771                 intf_restart ( &peerblk->retrieval, rc );
00772         }
00773 
00774         return 0;
00775 
00776  err:
00777         free_iob ( iobuf );
00778         peerblk_done ( peerblk, rc );
00779         return rc;
00780 }
00781 
00782 /**
00783  * Parse retrieval protocol message header
00784  *
00785  * @v peerblk           PeerDist block download
00786  * @ret rc              Return status code
00787  */
00788 static int peerblk_parse_header ( struct peerdist_block *peerblk ) {
00789         struct {
00790                 struct peerdist_msg_transport_header hdr;
00791                 struct peerdist_msg_header msg;
00792         } __attribute__ (( packed )) *msg = peerblk->buffer.data;
00793         struct cipher_algorithm *cipher;
00794         size_t len = peerblk->buffer.len;
00795         size_t keylen = 0;
00796         int rc;
00797 
00798         /* Check message length */
00799         if ( len < sizeof ( *msg ) ) {
00800                 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for header "
00801                        "(%zd bytes)\n", peerblk, peerblk->segment,
00802                        peerblk->block, len );
00803                 return -ERANGE;
00804         }
00805 
00806         /* Check message type */
00807         if ( msg->msg.type != htonl ( PEERDIST_MSG_BLK_TYPE ) ) {
00808                 DBGC ( peerblk, "PEERBLK %p %d.%d unexpected message type "
00809                        "%#08x\n", peerblk, peerblk->segment, peerblk->block,
00810                        ntohl ( msg->msg.type ) );
00811                 return -EPROTO;
00812         }
00813 
00814         /* Determine cipher algorithm and key length */
00815         cipher = &aes_cbc_algorithm;
00816         switch ( msg->msg.algorithm ) {
00817         case htonl ( PEERDIST_MSG_PLAINTEXT ) :
00818                 cipher = NULL;
00819                 break;
00820         case htonl ( PEERDIST_MSG_AES_128_CBC ) :
00821                 keylen = ( 128 / 8 );
00822                 break;
00823         case htonl ( PEERDIST_MSG_AES_192_CBC ) :
00824                 keylen = ( 192 / 8 );
00825                 break;
00826         case htonl ( PEERDIST_MSG_AES_256_CBC ) :
00827                 keylen = ( 256 / 8 );
00828                 break;
00829         default:
00830                 DBGC ( peerblk, "PEERBLK %p %d.%d unrecognised algorithm "
00831                        "%#08x\n", peerblk, peerblk->segment, peerblk->block,
00832                        ntohl ( msg->msg.algorithm ) );
00833                 return -ENOTSUP;
00834         }
00835         DBGC2 ( peerblk, "PEERBLK %p %d.%d using %s with %zd-bit key\n",
00836                 peerblk, peerblk->segment, peerblk->block,
00837                 ( cipher ? cipher->name : "plaintext" ), ( 8 * keylen ) );
00838 
00839         /* Sanity check key length against maximum secret length */
00840         if ( keylen > peerblk->digestsize ) {
00841                 DBGC ( peerblk, "PEERBLK %p %d.%d %zd-byte secret too short "
00842                        "for %zd-bit key\n", peerblk, peerblk->segment,
00843                        peerblk->block, peerblk->digestsize, ( 8 * keylen ) );
00844                 return -EPROTO;
00845         }
00846 
00847         /* Allocate cipher context, if applicable.  Freeing the cipher
00848          * context (on error or otherwise) is handled by peerblk_reset().
00849          */
00850         peerblk->cipher = cipher;
00851         assert ( peerblk->cipherctx == NULL );
00852         if ( cipher ) {
00853                 peerblk->cipherctx = malloc ( cipher->ctxsize );
00854                 if ( ! peerblk->cipherctx )
00855                         return -ENOMEM;
00856         }
00857 
00858         /* Initialise cipher, if applicable */
00859         if ( cipher &&
00860              ( rc = cipher_setkey ( cipher, peerblk->cipherctx, peerblk->secret,
00861                                     keylen ) ) != 0 ) {
00862                 DBGC ( peerblk, "PEERBLK %p %d.%d could not set key: %s\n",
00863                        peerblk, peerblk->segment, peerblk->block,
00864                        strerror ( rc ) );
00865                 return rc;
00866         }
00867 
00868         return 0;
00869 }
00870 
00871 /**
00872  * Parse retrieval protocol message segment and block details
00873  *
00874  * @v peerblk           PeerDist block download
00875  * @v buf_len           Length of buffered data to fill in
00876  * @ret rc              Return status code
00877  */
00878 static int peerblk_parse_block ( struct peerdist_block *peerblk,
00879                                  size_t *buf_len ) {
00880         size_t digestsize = peerblk->digestsize;
00881         peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *msg = peerblk->buffer.data;
00882         size_t len = peerblk->buffer.len;
00883         size_t data_len;
00884         size_t total;
00885 
00886         /* Check message length */
00887         if ( len < offsetof ( typeof ( *msg ), msg.block.data ) ) {
00888                 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
00889                        "zero-length data (%zd bytes)\n", peerblk,
00890                        peerblk->segment, peerblk->block, len );
00891                 return -ERANGE;
00892         }
00893 
00894         /* Check digest size */
00895         if ( ntohl ( msg->msg.segment.segment.digestsize ) != digestsize ) {
00896                 DBGC ( peerblk, "PEERBLK %p %d.%d incorrect digest size %d\n",
00897                        peerblk, peerblk->segment, peerblk->block,
00898                        ntohl ( msg->msg.segment.segment.digestsize ) );
00899                 return -EPROTO;
00900         }
00901 
00902         /* Check segment ID */
00903         if ( memcmp ( msg->msg.segment.id, peerblk->id, digestsize ) != 0 ) {
00904                 DBGC ( peerblk, "PEERBLK %p %d.%d segment ID mismatch\n",
00905                        peerblk, peerblk->segment, peerblk->block );
00906                 return -EPROTO;
00907         }
00908 
00909         /* Check block ID */
00910         if ( ntohl ( msg->msg.index ) != peerblk->block ) {
00911                 DBGC ( peerblk, "PEERBLK %p %d.%d block ID mismatch (got %d)\n",
00912                        peerblk, peerblk->segment, peerblk->block,
00913                        ntohl ( msg->msg.index ) );
00914                 return -EPROTO;
00915         }
00916 
00917         /* Check for missing blocks */
00918         data_len = be32_to_cpu ( msg->msg.block.block.len );
00919         if ( ! data_len ) {
00920                 DBGC ( peerblk, "PEERBLK %p %d.%d block not found\n",
00921                        peerblk, peerblk->segment, peerblk->block );
00922                 return -ENOENT;
00923         }
00924 
00925         /* Check for underlength blocks */
00926         if ( data_len < ( peerblk->range.end - peerblk->range.start ) ) {
00927                 DBGC ( peerblk, "PEERBLK %p %d.%d underlength block (%zd "
00928                        "bytes)\n", peerblk, peerblk->segment, peerblk->block,
00929                        data_len );
00930                 return -ERANGE;
00931         }
00932 
00933         /* Calculate buffered data length (i.e. excluding data which
00934          * was delivered to the final data transfer buffer).
00935          */
00936         *buf_len = ( data_len - ( peerblk->end - peerblk->start ) );
00937 
00938         /* Describe data before the trimmed content */
00939         peerblk->decrypt[PEERBLK_BEFORE].xferbuf = &peerblk->buffer;
00940         peerblk->decrypt[PEERBLK_BEFORE].offset =
00941                 offsetof ( typeof ( *msg ), msg.block.data );
00942         peerblk->decrypt[PEERBLK_BEFORE].len =
00943                 ( peerblk->start -
00944                   offsetof ( typeof ( *msg ), msg.block.data ) );
00945         total = peerblk->decrypt[PEERBLK_BEFORE].len;
00946 
00947         /* Describe data within the trimmed content */
00948         peerblk->decrypt[PEERBLK_DURING].offset =
00949                 peerblk_offset ( peerblk, peerblk->start );
00950         peerblk->decrypt[PEERBLK_DURING].len =
00951                 ( peerblk->end - peerblk->start );
00952         total += peerblk->decrypt[PEERBLK_DURING].len;
00953 
00954         /* Describe data after the trimmed content */
00955         peerblk->decrypt[PEERBLK_AFTER].xferbuf = &peerblk->buffer;
00956         peerblk->decrypt[PEERBLK_AFTER].offset = peerblk->start;
00957         peerblk->decrypt[PEERBLK_AFTER].len =
00958                 ( offsetof ( typeof ( *msg ), msg.block.data )
00959                   + *buf_len - peerblk->start );
00960         total += peerblk->decrypt[PEERBLK_AFTER].len;
00961 
00962         /* Sanity check */
00963         assert ( total == be32_to_cpu ( msg->msg.block.block.len ) );
00964 
00965         /* Initialise cipher and digest lengths */
00966         peerblk->cipher_remaining = total;
00967         peerblk->digest_remaining =
00968                 ( peerblk->range.end - peerblk->range.start );
00969         assert ( peerblk->cipher_remaining >= peerblk->digest_remaining );
00970 
00971         return 0;
00972 }
00973 
00974 /**
00975  * Parse retrieval protocol message useless details
00976  *
00977  * @v peerblk           PeerDist block download
00978  * @v buf_len           Length of buffered data
00979  * @v vrf_len           Length of uselessness to fill in
00980  * @ret rc              Return status code
00981  */
00982 static int peerblk_parse_useless ( struct peerdist_block *peerblk,
00983                                    size_t buf_len, size_t *vrf_len ) {
00984         size_t digestsize = peerblk->digestsize;
00985         peerblk_msg_blk_t ( digestsize, buf_len, 0, 0 ) *msg =
00986                 peerblk->buffer.data;
00987         size_t len = peerblk->buffer.len;
00988 
00989         /* Check message length */
00990         if ( len < offsetof ( typeof ( *msg ), msg.vrf.data ) ) {
00991                 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
00992                        "zero-length uselessness (%zd bytes)\n", peerblk,
00993                        peerblk->segment, peerblk->block, len );
00994                 return -ERANGE;
00995         }
00996 
00997         /* Extract length of uselessness */
00998         *vrf_len = be32_to_cpu ( msg->msg.vrf.vrf.len );
00999 
01000         return 0;
01001 }
01002 
01003 /**
01004  * Parse retrieval protocol message initialisation vector details
01005  *
01006  * @v peerblk           PeerDist block download
01007  * @v buf_len           Length of buffered data
01008  * @v vrf_len           Length of uselessness
01009  * @ret rc              Return status code
01010  */
01011 static int peerblk_parse_iv ( struct peerdist_block *peerblk, size_t buf_len,
01012                               size_t vrf_len ) {
01013         size_t digestsize = peerblk->digestsize;
01014         size_t blksize = peerblk->cipher->blocksize;
01015         peerblk_msg_blk_t ( digestsize, buf_len, vrf_len, blksize ) *msg =
01016                 peerblk->buffer.data;
01017         size_t len = peerblk->buffer.len;
01018 
01019         /* Check message length */
01020         if ( len < sizeof ( *msg ) ) {
01021                 DBGC ( peerblk, "PEERBLK %p %d.%d message too short for "
01022                        "initialisation vector (%zd bytes)\n", peerblk,
01023                        peerblk->segment, peerblk->block, len );
01024                 return -ERANGE;
01025         }
01026 
01027         /* Check initialisation vector size */
01028         if ( ntohl ( msg->msg.iv.iv.blksize ) != blksize ) {
01029                 DBGC ( peerblk, "PEERBLK %p %d.%d incorrect IV size %d\n",
01030                        peerblk, peerblk->segment, peerblk->block,
01031                        ntohl ( msg->msg.iv.iv.blksize ) );
01032                 return -EPROTO;
01033         }
01034 
01035         /* Set initialisation vector */
01036         cipher_setiv ( peerblk->cipher, peerblk->cipherctx, msg->msg.iv.data );
01037 
01038         return 0;
01039 }
01040 
01041 /**
01042  * Read from decryption buffers
01043  *
01044  * @v peerblk           PeerDist block download
01045  * @v data              Data buffer
01046  * @v len               Length to read
01047  * @ret rc              Return status code
01048  */
01049 static int peerblk_decrypt_read ( struct peerdist_block *peerblk,
01050                                   void *data, size_t len ) {
01051         struct peerdist_block_decrypt *decrypt = peerblk->decrypt;
01052         size_t frag_len;
01053         int rc;
01054 
01055         /* Read from each decryption buffer in turn */
01056         for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) {
01057 
01058                 /* Calculate length to use from this buffer */
01059                 frag_len = decrypt->len;
01060                 if ( frag_len > len )
01061                         frag_len = len;
01062                 if ( ! frag_len )
01063                         continue;
01064 
01065                 /* Read from this buffer */
01066                 if ( ( rc = xferbuf_read ( decrypt->xferbuf, decrypt->offset,
01067                                            data, frag_len ) ) != 0 )
01068                         return rc;
01069         }
01070 
01071         return 0;
01072 }
01073 
01074 /**
01075  * Write to decryption buffers and update offsets and lengths
01076  *
01077  * @v peerblk           PeerDist block download
01078  * @v data              Data buffer
01079  * @v len               Length to read
01080  * @ret rc              Return status code
01081  */
01082 static int peerblk_decrypt_write ( struct peerdist_block *peerblk,
01083                                    const void *data, size_t len ) {
01084         struct peerdist_block_decrypt *decrypt = peerblk->decrypt;
01085         size_t frag_len;
01086         int rc;
01087 
01088         /* Write to each decryption buffer in turn */
01089         for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) {
01090 
01091                 /* Calculate length to use from this buffer */
01092                 frag_len = decrypt->len;
01093                 if ( frag_len > len )
01094                         frag_len = len;
01095                 if ( ! frag_len )
01096                         continue;
01097 
01098                 /* Write to this buffer */
01099                 if ( ( rc = xferbuf_write ( decrypt->xferbuf, decrypt->offset,
01100                                             data, frag_len ) ) != 0 )
01101                         return rc;
01102 
01103                 /* Update offset and length */
01104                 decrypt->offset += frag_len;
01105                 decrypt->len -= frag_len;
01106         }
01107 
01108         return 0;
01109 }
01110 
01111 /**
01112  * Decrypt one chunk of PeerDist retrieval protocol data
01113  *
01114  * @v peerblk           PeerDist block download
01115  */
01116 static void peerblk_decrypt ( struct peerdist_block *peerblk ) {
01117         struct cipher_algorithm *cipher = peerblk->cipher;
01118         struct digest_algorithm *digest = peerblk->digest;
01119         struct xfer_buffer *xferbuf;
01120         size_t cipher_len;
01121         size_t digest_len;
01122         void *data;
01123         int rc;
01124 
01125         /* Sanity check */
01126         assert ( ( PEERBLK_DECRYPT_CHUNKSIZE % cipher->blocksize ) == 0 );
01127 
01128         /* Get the underlying data transfer buffer */
01129         xferbuf = xfer_buffer ( &peerblk->xfer );
01130         if ( ! xferbuf ) {
01131                 DBGC ( peerblk, "PEERBLK %p %d.%d has no underlying data "
01132                        "transfer buffer\n", peerblk, peerblk->segment,
01133                        peerblk->block );
01134                 rc = -ENOTSUP;
01135                 goto err_xfer_buffer;
01136         }
01137         peerblk->decrypt[PEERBLK_DURING].xferbuf = xferbuf;
01138 
01139         /* Calculate cipher and digest lengths */
01140         cipher_len = PEERBLK_DECRYPT_CHUNKSIZE;
01141         if ( cipher_len > peerblk->cipher_remaining )
01142                 cipher_len = peerblk->cipher_remaining;
01143         digest_len = cipher_len;
01144         if ( digest_len > peerblk->digest_remaining )
01145                 digest_len = peerblk->digest_remaining;
01146         assert ( ( cipher_len & ( cipher->blocksize - 1 ) ) == 0 );
01147 
01148         /* Allocate temporary data buffer */
01149         data = malloc ( cipher_len );
01150         if ( ! data ) {
01151                 rc = -ENOMEM;
01152                 goto err_alloc_data;
01153         }
01154 
01155         /* Read ciphertext */
01156         if ( ( rc = peerblk_decrypt_read ( peerblk, data, cipher_len ) ) != 0 ){
01157                 DBGC ( peerblk, "PEERBLK %p %d.%d could not read ciphertext: "
01158                        "%s\n", peerblk, peerblk->segment, peerblk->block,
01159                        strerror ( rc ) );
01160                 goto err_read;
01161         }
01162 
01163         /* Decrypt data */
01164         cipher_decrypt ( cipher, peerblk->cipherctx, data, data, cipher_len );
01165 
01166         /* Add data to digest */
01167         digest_update ( digest, peerblk->digestctx, data, digest_len );
01168 
01169         /* Write plaintext */
01170         if ( ( rc = peerblk_decrypt_write ( peerblk, data, cipher_len ) ) != 0){
01171                 DBGC ( peerblk, "PEERBLK %p %d.%d could not write plaintext: "
01172                        "%s\n", peerblk, peerblk->segment, peerblk->block,
01173                        strerror ( rc ) );
01174                 goto err_write;
01175         }
01176 
01177         /* Consume input */
01178         peerblk->cipher_remaining -= cipher_len;
01179         peerblk->digest_remaining -= digest_len;
01180 
01181         /* Free temporary data buffer */
01182         free ( data );
01183 
01184         /* Continue processing until all input is consumed */
01185         if ( peerblk->cipher_remaining )
01186                 return;
01187 
01188         /* Complete download attempt */
01189         peerblk_done ( peerblk, 0 );
01190         return;
01191 
01192  err_write:
01193  err_read:
01194         free ( data );
01195  err_alloc_data:
01196  err_xfer_buffer:
01197         peerblk_done ( peerblk, rc );
01198 }
01199 
01200 /**
01201  * Close PeerDist retrieval protocol block download attempt
01202  *
01203  * @v peerblk           PeerDist block download
01204  * @v rc                Reason for close
01205  */
01206 static void peerblk_retrieval_close ( struct peerdist_block *peerblk, int rc ) {
01207         size_t buf_len;
01208         size_t vrf_len;
01209 
01210         /* Restart interface */
01211         intf_restart ( &peerblk->retrieval, rc );
01212 
01213         /* Fail immediately if we have an error */
01214         if ( rc != 0 )
01215                 goto done;
01216 
01217         /* Abort download attempt (for testing) if applicable */
01218         if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 )
01219                 goto done;
01220 
01221         /* Parse message header */
01222         if ( ( rc = peerblk_parse_header ( peerblk ) ) != 0 )
01223                 goto done;
01224 
01225         /* Parse message segment and block details */
01226         if ( ( rc = peerblk_parse_block ( peerblk, &buf_len ) ) != 0 )
01227                 goto done;
01228 
01229         /* If the block was plaintext, then there is nothing more to do */
01230         if ( ! peerblk->cipher )
01231                 goto done;
01232 
01233         /* Parse message useless details */
01234         if ( ( rc = peerblk_parse_useless ( peerblk, buf_len, &vrf_len ) ) != 0)
01235                 goto done;
01236 
01237         /* Parse message initialisation vector details */
01238         if ( ( rc = peerblk_parse_iv ( peerblk, buf_len, vrf_len ) ) != 0 )
01239                 goto done;
01240 
01241         /* Fail if decryption length is not aligned to the cipher block size */
01242         if ( peerblk->cipher_remaining & ( peerblk->cipher->blocksize - 1 ) ) {
01243                 DBGC ( peerblk, "PEERBLK %p %d.%d unaligned data length %zd\n",
01244                        peerblk, peerblk->segment, peerblk->block,
01245                        peerblk->cipher_remaining );
01246                 rc = -EPROTO;
01247                 goto done;
01248         }
01249 
01250         /* Stop the download attempt timer: there is no point in
01251          * timing out while decrypting.
01252          */
01253         stop_timer ( &peerblk->timer );
01254 
01255         /* Start decryption process */
01256         process_add ( &peerblk->process );
01257         return;
01258 
01259  done:
01260         /* Complete download attempt */
01261         peerblk_done ( peerblk, rc );
01262 }
01263 
01264 /******************************************************************************
01265  *
01266  * Retry policy
01267  *
01268  ******************************************************************************
01269  */
01270 
01271 /**
01272  * Handle PeerDist retry timer expiry
01273  *
01274  * @v timer             Retry timer
01275  * @v over              Failure indicator
01276  */
01277 static void peerblk_expired ( struct retry_timer *timer, int over __unused ) {
01278         struct peerdist_block *peerblk =
01279                 container_of ( timer, struct peerdist_block, timer );
01280         struct peerdisc_segment *segment = peerblk->discovery.segment;
01281         struct peerdisc_peer *head;
01282         unsigned long now = peerblk_timestamp();
01283         const char *location;
01284         int rc;
01285 
01286         /* Profile discovery timeout, if applicable */
01287         if ( ( peerblk->peer == NULL ) && ( timer->timeout != 0 ) ) {
01288                 profile_custom ( &peerblk_discovery_timeout_profiler,
01289                                  ( now - peerblk->started ) );
01290                 DBGC ( peerblk, "PEERBLK %p %d.%d discovery timed out after "
01291                        "%ld ticks\n", peerblk, peerblk->segment,
01292                        peerblk->block, timer->timeout );
01293         }
01294 
01295         /* Profile download timeout, if applicable */
01296         if ( ( peerblk->peer != NULL ) && ( timer->timeout != 0 ) ) {
01297                 profile_custom ( &peerblk_attempt_timeout_profiler,
01298                                  ( now - peerblk->attempted ) );
01299                 DBGC ( peerblk, "PEERBLK %p %d.%d timed out after %ld ticks\n",
01300                        peerblk, peerblk->segment, peerblk->block,
01301                        timer->timeout );
01302         }
01303 
01304         /* Abort any current download attempt */
01305         peerblk_reset ( peerblk, -ETIMEDOUT );
01306 
01307         /* Record attempt start time */
01308         peerblk->attempted = now;
01309 
01310         /* If we have exceeded our maximum number of attempt cycles
01311          * (each cycle comprising a retrieval protocol download from
01312          * each peer in the list followed by a raw download from the
01313          * origin server), then abort the overall download.
01314          */
01315         head = list_entry ( &segment->peers, struct peerdisc_peer, list );
01316         if ( ( peerblk->peer == head ) &&
01317              ( ++peerblk->cycles >= PEERBLK_MAX_ATTEMPT_CYCLES ) ) {
01318                 rc = peerblk->rc;
01319                 assert ( rc != 0 );
01320                 goto err;
01321         }
01322 
01323         /* If we have not yet made any download attempts, then move to
01324          * the start of the peer list.
01325          */
01326         if ( peerblk->peer == NULL )
01327                 peerblk->peer = head;
01328 
01329         /* Attempt retrieval protocol download from next usable peer */
01330         list_for_each_entry_continue ( peerblk->peer, &segment->peers, list ) {
01331 
01332                 /* Attempt retrieval protocol download from this peer */
01333                 location = peerblk->peer->location;
01334                 if ( ( rc = peerblk_retrieval_open ( peerblk,
01335                                                      location ) ) != 0 ) {
01336                         /* Non-fatal: continue to try next peer */
01337                         continue;
01338                 }
01339 
01340                 /* Peer download started */
01341                 return;
01342         }
01343 
01344         /* Add to raw download queue */
01345         peerblk_enqueue ( peerblk, &peerblk_raw_queue );
01346 
01347         return;
01348 
01349  err:
01350         peerblk_close ( peerblk, rc );
01351 }
01352 
01353 /**
01354  * Handle PeerDist peer discovery
01355  *
01356  * @v discovery         PeerDist discovery client
01357  */
01358 static void peerblk_discovered ( struct peerdisc_client *discovery ) {
01359         struct peerdist_block *peerblk =
01360                 container_of ( discovery, struct peerdist_block, discovery );
01361         unsigned long now = peerblk_timestamp();
01362 
01363         /* Do nothing unless we are still waiting for the initial
01364          * discovery timeout.
01365          */
01366         if ( ( peerblk->peer != NULL ) || ( peerblk->timer.timeout == 0 ) )
01367                 return;
01368 
01369         /* Schedule an immediate retry */
01370         start_timer_nodelay ( &peerblk->timer );
01371 
01372         /* Profile discovery success */
01373         profile_custom ( &peerblk_discovery_success_profiler,
01374                          ( now - peerblk->started ) );
01375 }
01376 
01377 /******************************************************************************
01378  *
01379  * Opener
01380  *
01381  ******************************************************************************
01382  */
01383 
01384 /** PeerDist block download data transfer interface operations */
01385 static struct interface_operation peerblk_xfer_operations[] = {
01386         INTF_OP ( intf_close, struct peerdist_block *, peerblk_close ),
01387 };
01388 
01389 /** PeerDist block download data transfer interface descriptor */
01390 static struct interface_descriptor peerblk_xfer_desc =
01391         INTF_DESC ( struct peerdist_block, xfer, peerblk_xfer_operations );
01392 
01393 /** PeerDist block download raw data interface operations */
01394 static struct interface_operation peerblk_raw_operations[] = {
01395         INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_raw_rx ),
01396         INTF_OP ( intf_close, struct peerdist_block *, peerblk_raw_close ),
01397 };
01398 
01399 /** PeerDist block download raw data interface descriptor */
01400 static struct interface_descriptor peerblk_raw_desc =
01401         INTF_DESC ( struct peerdist_block, raw, peerblk_raw_operations );
01402 
01403 /** PeerDist block download retrieval protocol interface operations */
01404 static struct interface_operation peerblk_retrieval_operations[] = {
01405         INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_retrieval_rx ),
01406         INTF_OP ( intf_close, struct peerdist_block *, peerblk_retrieval_close),
01407 };
01408 
01409 /** PeerDist block download retrieval protocol interface descriptor */
01410 static struct interface_descriptor peerblk_retrieval_desc =
01411         INTF_DESC ( struct peerdist_block, retrieval,
01412                     peerblk_retrieval_operations );
01413 
01414 /** PeerDist block download decryption process descriptor */
01415 static struct process_descriptor peerblk_process_desc =
01416         PROC_DESC ( struct peerdist_block, process, peerblk_decrypt );
01417 
01418 /** PeerDist block download discovery operations */
01419 static struct peerdisc_client_operations peerblk_discovery_operations = {
01420         .discovered = peerblk_discovered,
01421 };
01422 
01423 /**
01424  * Open PeerDist block download
01425  *
01426  * @v xfer              Data transfer interface
01427  * @v uri               Original URI
01428  * @v info              Content information block
01429  * @ret rc              Return status code
01430  */
01431 int peerblk_open ( struct interface *xfer, struct uri *uri,
01432                    struct peerdist_info_block *block ) {
01433         const struct peerdist_info_segment *segment = block->segment;
01434         const struct peerdist_info *info = segment->info;
01435         struct digest_algorithm *digest = info->digest;
01436         struct peerdist_block *peerblk;
01437         unsigned long timeout;
01438         size_t digestsize;
01439         int rc;
01440 
01441         /* Allocate and initialise structure */
01442         peerblk = zalloc ( sizeof ( *peerblk ) + digest->ctxsize );
01443         if ( ! peerblk ) {
01444                 rc = -ENOMEM;
01445                 goto err_alloc;
01446         }
01447         ref_init ( &peerblk->refcnt, peerblk_free );
01448         intf_init ( &peerblk->xfer, &peerblk_xfer_desc, &peerblk->refcnt );
01449         intf_init ( &peerblk->raw, &peerblk_raw_desc, &peerblk->refcnt );
01450         intf_init ( &peerblk->retrieval, &peerblk_retrieval_desc,
01451                     &peerblk->refcnt );
01452         peerblk->uri = uri_get ( uri );
01453         memcpy ( &peerblk->range, &block->range, sizeof ( peerblk->range ) );
01454         memcpy ( &peerblk->trim, &block->trim, sizeof ( peerblk->trim ) );
01455         peerblk->offset = ( block->trim.start - info->trim.start );
01456         peerblk->digest = info->digest;
01457         peerblk->digestsize = digestsize = info->digestsize;
01458         peerblk->digestctx = ( ( ( void * ) peerblk ) + sizeof ( *peerblk ) );
01459         peerblk->segment = segment->index;
01460         memcpy ( peerblk->id, segment->id, sizeof ( peerblk->id ) );
01461         memcpy ( peerblk->secret, segment->secret, sizeof ( peerblk->secret ) );
01462         peerblk->block = block->index;
01463         memcpy ( peerblk->hash, block->hash, sizeof ( peerblk->hash ) );
01464         xferbuf_malloc_init ( &peerblk->buffer );
01465         process_init_stopped ( &peerblk->process, &peerblk_process_desc,
01466                                &peerblk->refcnt );
01467         peerdisc_init ( &peerblk->discovery, &peerblk_discovery_operations );
01468         INIT_LIST_HEAD ( &peerblk->queued );
01469         timer_init ( &peerblk->timer, peerblk_expired, &peerblk->refcnt );
01470         DBGC2 ( peerblk, "PEERBLK %p %d.%d id %02x%02x%02x%02x%02x..."
01471                 "%02x%02x%02x [%08zx,%08zx)", peerblk, peerblk->segment,
01472                 peerblk->block, peerblk->id[0], peerblk->id[1], peerblk->id[2],
01473                 peerblk->id[3], peerblk->id[4], peerblk->id[ digestsize - 3 ],
01474                 peerblk->id[ digestsize - 2 ], peerblk->id[ digestsize - 1 ],
01475                 peerblk->range.start, peerblk->range.end );
01476         if ( ( peerblk->trim.start != peerblk->range.start ) ||
01477              ( peerblk->trim.end != peerblk->range.end ) ) {
01478                 DBGC2 ( peerblk, " covers [%08zx,%08zx)",
01479                         peerblk->trim.start, peerblk->trim.end );
01480         }
01481         DBGC2 ( peerblk, "\n" );
01482 
01483         /* Open discovery */
01484         if ( ( rc = peerdisc_open ( &peerblk->discovery, peerblk->id,
01485                                     peerblk->digestsize ) ) != 0 )
01486                 goto err_open_discovery;
01487 
01488         /* Schedule a retry attempt either immediately (if we already
01489          * have some peers) or after the discovery timeout.
01490          */
01491         timeout = ( list_empty ( &peerblk->discovery.segment->peers ) ?
01492                     ( peerdisc_timeout_secs * TICKS_PER_SEC ) : 0 );
01493         start_timer_fixed ( &peerblk->timer, timeout );
01494 
01495         /* Record start time */
01496         peerblk->started = peerblk_timestamp();
01497 
01498         /* Attach to parent interface, mortalise self, and return */
01499         intf_plug_plug ( xfer, &peerblk->xfer );
01500         ref_put ( &peerblk->refcnt );
01501         return 0;
01502 
01503  err_open_discovery:
01504         peerblk_close ( peerblk, rc );
01505  err_alloc:
01506         return rc;
01507 }