[prefix] Use garbage-collectable section names
[ipxe.git] / src / net / pccrd.c
1 /*
2 * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
3 *
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.
8 *
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.
13 *
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
17 * 02110-1301, USA.
18 *
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.
22 */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 #include <stddef.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <assert.h>
33 #include <ipxe/pccrd.h>
34
35 /** @file
36 *
37 * Peer Content Caching and Retrieval: Discovery Protocol [MS-PCCRD]
38 *
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
43 * given segment.
44 *
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".
52 *
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.
58 *
59 * I hereby announce this specification to be the 2015 winner of the
60 * prestigious "UEFI HII API" award for incompetent design.
61 */
62
63 /** Discovery request format */
64 #define PEERDIST_DISCOVERY_REQUEST \
65 "<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
66 "<soap:Envelope " \
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\">" \
72 "<soap:Header>" \
73 "<wsa:To>" \
74 "urn:schemas-xmlsoap-org:ws:2005:04:discovery" \
75 "</wsa:To>" \
76 "<wsa:Action>" \
77 "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe" \
78 "</wsa:Action>" \
79 "<wsa:MessageID>" \
80 "urn:uuid:%s" \
81 "</wsa:MessageID>" \
82 "</soap:Header>" \
83 "<soap:Body>" \
84 "<wsd:Probe>" \
85 "<wsd:Types>" \
86 "PeerDist:PeerDistData" \
87 "</wsd:Types>" \
88 "<wsd:Scopes MatchBy=\"http://schemas.xmlsoap.org/ws/" \
89 "2005/04/discovery/strcmp0\">" \
90 "%s" \
91 "</wsd:Scopes>" \
92 "</wsd:Probe>" \
93 "</soap:Body>" \
94 "</soap:Envelope>"
95
96 /**
97 * Construct discovery request
98 *
99 * @v uuid Message UUID string
100 * @v id Segment identifier string
101 * @ret request Discovery request, or NULL on failure
102 *
103 * The request is dynamically allocated; the caller must eventually
104 * free() the request.
105 */
106 char * peerdist_discovery_request ( const char *uuid, const char *id ) {
107 char *request;
108 int len;
109
110 /* Construct request */
111 len = asprintf ( &request, PEERDIST_DISCOVERY_REQUEST, uuid, id );
112 if ( len < 0 )
113 return NULL;
114
115 return request;
116 }
117
118 /**
119 * Locate discovery reply tag
120 *
121 * @v data Reply data (not NUL-terminated)
122 * @v len Length of reply data
123 * @v tag XML tag
124 * @ret found Found tag (or NULL if not found)
125 */
126 static char * peerdist_discovery_reply_tag ( char *data, size_t len,
127 const char *tag ) {
128 size_t tag_len = strlen ( tag );
129
130 /* Search, allowing for the fact that the reply data is not
131 * cleanly NUL-terminated and may contain embedded NULs due to
132 * earlier parsing.
133 */
134 for ( ; len >= tag_len ; data++, len-- ) {
135 if ( strncmp ( data, tag, tag_len ) == 0 )
136 return data;
137 }
138 return NULL;
139 }
140
141 /**
142 * Locate discovery reply values
143 *
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)
148 *
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.
152 *
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.
156 */
157 static char * peerdist_discovery_reply_values ( char *data, size_t len,
158 const char *name ) {
159 char buf[ 2 /* "</" */ + strlen ( name ) + 1 /* ">" */ + 1 /* NUL */ ];
160 char *open;
161 char *close;
162 char *start;
163 char *end;
164 char *in;
165 char *out;
166 char c;
167
168 /* Locate opening tag */
169 snprintf ( buf, sizeof ( buf ), "<%s>", name );
170 open = peerdist_discovery_reply_tag ( data, len, buf );
171 if ( ! open )
172 return NULL;
173 start = ( open + strlen ( buf ) );
174 len -= ( start - data );
175 data = start;
176
177 /* Locate closing tag */
178 snprintf ( buf, sizeof ( buf ), "</%s>", name );
179 close = peerdist_discovery_reply_tag ( data, len, buf );
180 if ( ! close )
181 return NULL;
182 assert ( close >= open );
183 end = close;
184
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.
188 */
189 for ( in = start, out = start ; in < end ; in++ ) {
190 c = *in;
191 if ( isspace ( c ) ) {
192 if ( ( out > start ) && ( out[-1] != '\0' ) )
193 *(out++) = '\0';
194 } else {
195 *(out++) = c;
196 }
197 }
198 *(out++) = '\0';
199 *(out++) = '\0';
200 assert ( out < ( close + strlen ( buf ) ) );
201
202 return start;
203 }
204
205 /**
206 * Parse discovery reply
207 *
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
212 *
213 * The discovery reply includes pointers to strings within the
214 * modified reply data.
215 */
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 = {
219 .hex = "00000000",
220 };
221 struct peerdist_discovery_block_count *count;
222 unsigned int max;
223 unsigned int i;
224 char *scopes;
225 char *xaddrs;
226 char *blockcount;
227 char *in;
228 char *out;
229 size_t skip;
230
231 /* Find <wsd:Scopes> tag */
232 scopes = peerdist_discovery_reply_values ( data, len, "wsd:Scopes" );
233 if ( ! scopes ) {
234 DBGC ( reply, "PCCRD %p missing <wsd:Scopes> tag\n", reply );
235 return -ENOENT;
236 }
237
238 /* Find <wsd:XAddrs> tag */
239 xaddrs = peerdist_discovery_reply_values ( data, len, "wsd:XAddrs" );
240 if ( ! xaddrs ) {
241 DBGC ( reply, "PCCRD %p missing <wsd:XAddrs> tag\n", reply );
242 return -ENOENT;
243 }
244
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",
250 reply );
251 return -ENOENT;
252 }
253
254 /* Determine maximum number of segments (according to number
255 * of entries in the block count list).
256 */
257 max = ( strlen ( blockcount ) / sizeof ( *count ) );
258 count = container_of ( blockcount,
259 struct peerdist_discovery_block_count, hex[0] );
260
261 /* Eliminate any segments with a zero block count */
262 for ( i = 0, in = scopes, out = scopes ; *in ; i++, in += skip ) {
263
264 /* Fail if we have overrun the maximum number of segments */
265 if ( i >= max ) {
266 DBGC ( reply, "PCCRD %p too many segment IDs\n",
267 reply );
268 return -EPROTO;
269 }
270
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 )
275 continue;
276 strcpy ( out, in );
277 out += skip;
278 }
279 out[0] = '\0'; /* Ensure list is terminated with a zero-length string */
280
281 /* Fill in discovery reply */
282 reply->ids = scopes;
283 reply->locations = xaddrs;
284
285 return 0;
286 }