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