blob: b8e4bb50f2c219f586fd7f2eb16c220e13260ca1 [file]
/*
* Copyright (C) 2026 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ipxe/uri.h>
#include <ipxe/open.h>
#include <ipxe/http.h>
#include <ipxe/base16.h>
#include <ipxe/pccrc.h>
#include <ipxe/pccrr.h>
/** @file
*
* Peer Content Caching and Retrieval: Retrieval Protocol [MS-PCCRR]
*
* The MS-PCCRR specification defines an HTTP POST request/response
* pair for retrieving an encrypted block from a peer (with the
* decryption keys provided separately via the content information).
*
* The HTTP POST request parameters serve only to identify the block:
* the actual response body is effectively a static encrypted blob.
*
* We define an additional (non-standard) retrieval protocol in which
* the block is identified solely using the request URI, with the
* response being the same content that would be returned as the HTTP
* POST response body. This allows for encrypted blocks to be
* retrieved from a static source such as AWS S3 or a local FAT
* filesystem, without requiring a peer that can understand the
* retrieval protocol HTTP POST request format.
*
* We define the static request URI format as:
*
* <base>/xx/xxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-b.blk
*
* where
*
* - `xxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy` is the segment ID (HoHoDK)
* as a lower-case hexadecimal string
*
* - `xx` is the first two characters (i.e. the first byte) of the
* segment ID
*
* - `b` is the block index within the segment (which will always
* be zero when using MS-PCCRC version 2 content information), as
* an unpadded decimal string
*
* - the extension `.blk` represents a block file
*
* This path format is designed to allow for efficient storage in a
* local FAT filesystem (by limiting both the number of directories
* and the number of entries within each directory).
*
*/
/**
* Open retrieval protocol connection using HTTP POST
*
* @v xfer Data transfer interface
* @v location Peer location
* @v digestsize Digest size
* @v id Segment identifier
* @v block Block index
* @ret rc Return status code
*/
static int peerdist_open_post ( struct interface *xfer, const char *location,
size_t digestsize, const uint8_t *id,
unsigned int block ) {
peerdist_msg_getblks_t ( digestsize, 1, 0 ) req;
struct http_request_content content;
struct uri *uri;
char *uri_string;
int rc;
/* Construct block fetch request */
memset ( &req, 0, sizeof ( req ) );
req.getblks.hdr.version.raw = htonl ( PEERDIST_MSG_GETBLKS_VERSION );
req.getblks.hdr.type = htonl ( PEERDIST_MSG_GETBLKS_TYPE );
req.getblks.hdr.len = htonl ( sizeof ( req ) );
req.getblks.hdr.algorithm = htonl ( PEERDIST_MSG_AES_128_CBC );
req.segment.segment.digestsize = htonl ( digestsize );
memcpy ( req.segment.id, id, digestsize );
req.ranges.ranges.count = htonl ( 1 );
req.ranges.range[0].first = htonl ( block );
req.ranges.range[0].count = htonl ( 1 );
/* Construct POST request content */
memset ( &content, 0, sizeof ( content ) );
content.data = &req;
content.len = sizeof ( req );
/* Construct URI string */
if ( asprintf ( &uri_string, "http://%s" PEERDIST_MAGIC_PATH,
location ) < 0 ) {
rc = -ENOMEM;
goto err_string;
}
/* Parse URI */
uri = parse_uri ( uri_string );
if ( ! uri ) {
rc = -ENOMEM;
goto err_uri;
}
/* Initiate HTTP POST to retrieve block */
if ( ( rc = http_open ( xfer, &http_post, uri, NULL,
&content ) ) != 0 ) {
DBGC ( xfer, "PCCRR %p could not open %s: %s\n",
xfer, uri_string, strerror ( rc ) );
goto err_open;
}
err_open:
uri_put ( uri );
err_uri:
free ( uri_string );
err_string:
return rc;
}
/** PeerDist retrieval protocol using HTTP POST */
struct peerdist_retrieval peerdist_post = {
.name = "POST",
.open = peerdist_open_post,
};
/**
* Open retrieval protocol connection using HTTP GET or local file
*
* @v xfer Data transfer interface
* @v location Peer location
* @v digestsize Digest size
* @v id Segment identifier
* @v block Block index
* @ret rc Return status code
*/
static int peerdist_open_get ( struct interface *xfer, const char *location,
size_t digestsize, const uint8_t *id,
unsigned int block ) {
char encoded[ base16_encoded_len ( PEERDIST_DIGEST_MAX_SIZE ) +
1 /* NUL */ ];
char *uri_string;
int rc;
/* Construct URI string */
assert ( digestsize <= PEERDIST_DIGEST_MAX_SIZE );
base16_encode ( id, digestsize, encoded, sizeof ( encoded ) );
if ( asprintf ( &uri_string, "%s/%02x/%s-%d.blk", location, id[0],
encoded, block ) < 0 ) {
rc = -ENOMEM;
goto err_string;
}
/* Open URI */
if ( ( rc = xfer_open_uri_string ( xfer, uri_string ) ) != 0 ) {
DBGC ( xfer, "PCCRR %p could not open %s: %s\n",
xfer, uri_string, strerror ( rc ) );
goto err_open;
}
err_open:
free ( uri_string );
err_string:
return rc;
}
/** PeerDist retrieval protocol using HTTP GET or local file */
struct peerdist_retrieval peerdist_get = {
.name = "GET",
.open = peerdist_open_get,
};