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
;