2 * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
33 #include <ipxe/pccrd.h>
37 * Peer Content Caching and Retrieval: Discovery Protocol [MS-PCCRD]
39 * This protocol manages to ingeniously combine the excessive
40 * verbosity of XML with a paucity of actual information. For
41 * example: even in version 2.0 of the protocol it is still not
42 * possible to discover which peers hold a specific block within a
45 * For added bonus points, version 1.0 of the protocol is specified to
46 * use a case-sensitive string comparison (for SHA2 digest values) but
47 * nothing specifies whether the strings in question should be in
48 * upper or lower case. There are example strings given in the
49 * specification, but the author skilfully manages to leave the issue
50 * unresolved by using the somewhat implausible digest value of
51 * "0200000000000000000000000000000000000000000000000000000000000000".
53 * Just in case you were thinking that the silver lining of the choice
54 * to use an XML-based protocol would be the ability to generate and
55 * process messages with standard tools, version 2.0 of the protocol
56 * places most of the critical information inside a Base64-encoded
57 * custom binary data structure. Within an XML element, naturally.
59 * I hereby announce this specification to be the 2015 winner of the
60 * prestigious "UEFI HII API" award for incompetent design.
63 /** Discovery request format */
64 #define PEERDIST_DISCOVERY_REQUEST \
65 "<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
67 "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" " \
68 "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " \
69 "xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" " \
70 "xmlns:PeerDist=\"http://schemas.microsoft.com/p2p/" \
71 "2007/09/PeerDistributionDiscovery\">" \
74 "urn:schemas-xmlsoap-org:ws:2005:04:discovery" \
77 "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe" \
86 "PeerDist:PeerDistData" \
88 "<wsd:Scopes MatchBy=\"http://schemas.xmlsoap.org/ws/" \
89 "2005/04/discovery/strcmp0\">" \
97 * Construct discovery request
99 * @v uuid Message UUID string
100 * @v id Segment identifier string
101 * @ret request Discovery request, or NULL on failure
103 * The request is dynamically allocated; the caller must eventually
104 * free() the request.
106 char * peerdist_discovery_request ( const char *uuid, const char *id ) {
110 /* Construct request */
111 len = asprintf ( &request, PEERDIST_DISCOVERY_REQUEST, uuid, id );
119 * Locate discovery reply tag
121 * @v data Reply data (not NUL-terminated)
122 * @v len Length of reply data
124 * @ret found Found tag (or NULL if not found)
126 static char * peerdist_discovery_reply_tag ( char *data, size_t len,
128 size_t tag_len = strlen ( tag );
130 /* Search, allowing for the fact that the reply data is not
131 * cleanly NUL-terminated and may contain embedded NULs due to
134 for ( ; len >= tag_len ; data++, len-- ) {
135 if ( strncmp ( data, tag, tag_len ) == 0 )
142 * Locate discovery reply values
144 * @v data Reply data (not NUL-terminated, will be modified)
145 * @v len Length of reply data
146 * @v name XML tag name
147 * @ret values Tag values (or NULL if not found)
149 * The reply data is modified by adding NULs and moving characters as
150 * needed to produce a NUL-separated list of values, terminated with a
151 * zero-length string.
153 * This is not supposed to be a full XML parser; it's supposed to
154 * include just enough functionality to allow PeerDist discovery to
155 * work with existing implementations.
157 static char * peerdist_discovery_reply_values ( char *data, size_t len,
159 char buf[ 2 /* "</" */ + strlen ( name ) + 1 /* ">" */ + 1 /* NUL */ ];
168 /* Locate opening tag */
169 snprintf ( buf, sizeof ( buf ), "<%s>", name );
170 open = peerdist_discovery_reply_tag ( data, len, buf );
173 start = ( open + strlen ( buf ) );
174 len -= ( start - data );
177 /* Locate closing tag */
178 snprintf ( buf, sizeof ( buf ), "</%s>", name );
179 close = peerdist_discovery_reply_tag ( data, len, buf );
182 assert ( close >= open );
185 /* Strip initial whitespace, convert other whitespace
186 * sequences to single NULs, add terminating pair of NULs.
187 * This will probably overwrite part of the closing tag.
189 for ( in = start, out = start ; in < end ; in++ ) {
191 if ( isspace ( c ) ) {
192 if ( ( out > start ) && ( out[-1] != '\0' ) )
200 assert ( out < ( close + strlen ( buf ) ) );
206 * Parse discovery reply
208 * @v data Reply data (not NUL-terminated, will be modified)
209 * @v len Length of reply data
210 * @v reply Discovery reply to fill in
211 * @ret rc Return status code
213 * The discovery reply includes pointers to strings within the
214 * modified reply data.
216 int peerdist_discovery_reply ( char *data, size_t len,
217 struct peerdist_discovery_reply *reply ) {
218 static const struct peerdist_discovery_block_count zcount = {
221 struct peerdist_discovery_block_count *count;
231 /* Find <wsd:Scopes> tag */
232 scopes = peerdist_discovery_reply_values ( data, len, "wsd:Scopes" );
234 DBGC ( reply, "PCCRD %p missing <wsd:Scopes> tag\n", reply );
238 /* Find <wsd:XAddrs> tag */
239 xaddrs = peerdist_discovery_reply_values ( data, len, "wsd:XAddrs" );
241 DBGC ( reply, "PCCRD %p missing <wsd:XAddrs> tag\n", reply );
245 /* Find <PeerDist:BlockCount> tag */
246 blockcount = peerdist_discovery_reply_values ( data, len,
247 "PeerDist:BlockCount" );
248 if ( ! blockcount ) {
249 DBGC ( reply, "PCCRD %p missing <PeerDist:BlockCount> tag\n",
254 /* Determine maximum number of segments (according to number
255 * of entries in the block count list).
257 max = ( strlen ( blockcount ) / sizeof ( *count ) );
258 count = container_of ( blockcount,
259 struct peerdist_discovery_block_count, hex[0] );
261 /* Eliminate any segments with a zero block count */
262 for ( i = 0, in = scopes, out = scopes ; *in ; i++, in += skip ) {
264 /* Fail if we have overrun the maximum number of segments */
266 DBGC ( reply, "PCCRD %p too many segment IDs\n",
271 /* Delete segment if block count is zero */
272 skip = ( strlen ( in ) + 1 /* NUL */ );
273 if ( memcmp ( count[i].hex, zcount.hex,
274 sizeof ( zcount.hex ) ) == 0 )
279 out[0] = '\0'; /* Ensure list is terminated with a zero-length string */
281 /* Fill in discovery reply */
283 reply->locations = xaddrs;