Skip to content

Commit cdc6d47

Browse files
committed
add http protocol support
Signed-off-by: Gerd Hoffmann <[email protected]>
1 parent 6ab3d14 commit cdc6d47

File tree

2 files changed

+363
-0
lines changed

2 files changed

+363
-0
lines changed

uefi/src/proto/network/http.rs

+362
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
#![cfg(target_os = "uefi")]
2+
3+
// SPDX-License-Identifier: MIT OR Apache-2.0
4+
5+
//! HTTP Protocol.
6+
//!
7+
//! See [`Http`].
8+
9+
extern crate alloc;
10+
use alloc::string::String;
11+
use alloc::vec;
12+
use alloc::vec::Vec;
13+
use core::ffi::{c_char, c_void, CStr};
14+
use core::ptr;
15+
use log::debug;
16+
17+
use uefi::boot::ScopedProtocol;
18+
use uefi::prelude::*;
19+
use uefi::proto::unsafe_protocol;
20+
use uefi_raw::protocol::driver::ServiceBindingProtocol;
21+
use uefi_raw::protocol::network::http::{
22+
HttpAccessPoint, HttpConfigData, HttpHeader, HttpMessage, HttpMethod, HttpProtocol,
23+
HttpRequestData, HttpResponseData, HttpStatusCode, HttpToken, HttpV4AccessPoint, HttpVersion,
24+
};
25+
26+
/// HTTP [`Protocol`]. Send HTTP Requests.
27+
///
28+
/// [`Protocol`]: uefi::proto::Protocol
29+
#[derive(Debug)]
30+
#[unsafe_protocol(HttpProtocol::GUID)]
31+
pub struct Http(HttpProtocol);
32+
33+
impl Http {
34+
/// Configure HTTP Protocol. Must be called before sending HTTP requests.
35+
pub fn configure(&mut self, config_data: &HttpConfigData) -> uefi::Result<()> {
36+
let status = unsafe { (self.0.configure)(&mut self.0, config_data) };
37+
match status {
38+
Status::SUCCESS => Ok(()),
39+
_ => Err(status.into()),
40+
}
41+
}
42+
43+
/// Send HTTP request.
44+
pub fn request(&mut self, token: &mut HttpToken) -> uefi::Result<()> {
45+
let status = unsafe { (self.0.request)(&mut self.0, token) };
46+
match status {
47+
Status::SUCCESS => Ok(()),
48+
_ => Err(status.into()),
49+
}
50+
}
51+
52+
/// Receive HTTP response.
53+
pub fn response(&mut self, token: &mut HttpToken) -> uefi::Result<()> {
54+
let status = unsafe { (self.0.response)(&mut self.0, token) };
55+
match status {
56+
Status::SUCCESS => Ok(()),
57+
_ => Err(status.into()),
58+
}
59+
}
60+
61+
/// Poll network stack for updates.
62+
pub fn poll(&mut self) -> uefi::Result<()> {
63+
let status = unsafe { (self.0.poll)(&mut self.0) };
64+
match status {
65+
Status::SUCCESS => Ok(()),
66+
_ => Err(status.into()),
67+
}
68+
}
69+
}
70+
71+
/// HTTP Service Binding Protocol.
72+
#[derive(Debug)]
73+
#[unsafe_protocol(HttpProtocol::SERVICE_BINDING_GUID)]
74+
pub struct HttpBinding(ServiceBindingProtocol);
75+
76+
impl HttpBinding {
77+
/// Create HTTP Protocol Handle.
78+
pub fn create_child(&mut self) -> uefi::Result<Handle> {
79+
let mut c_handle = ptr::null_mut();
80+
let status;
81+
let handle;
82+
unsafe {
83+
status = (self.0.create_child)(&mut self.0, &mut c_handle);
84+
handle = Handle::from_ptr(c_handle);
85+
};
86+
match status {
87+
Status::SUCCESS => Ok(handle.unwrap()),
88+
_ => Err(status.into()),
89+
}
90+
}
91+
92+
/// Destroy HTTP Protocol Handle.
93+
pub fn destroy_child(&mut self, handle: Handle) -> uefi::Result<()> {
94+
let status = unsafe { (self.0.destroy_child)(&mut self.0, handle.as_ptr()) };
95+
match status {
96+
Status::SUCCESS => Ok(()),
97+
_ => Err(status.into()),
98+
}
99+
}
100+
}
101+
102+
/// HTTP Response data
103+
#[derive(Debug)]
104+
pub struct HttpHelperResponse {
105+
/// HTTP Status
106+
pub status: HttpStatusCode,
107+
/// HTTP Response Headers
108+
pub headers: Vec<(String, String)>,
109+
/// HTTP Body
110+
pub body: Vec<u8>,
111+
}
112+
113+
/// HTTP Helper, makes using the HTTP protocol more convinient.
114+
#[derive(Debug)]
115+
pub struct HttpHelper {
116+
child_handle: Handle,
117+
binding: ScopedProtocol<HttpBinding>,
118+
protocol: ScopedProtocol<Http>,
119+
}
120+
121+
impl HttpHelper {
122+
/// Create new HTTP helper instance for the given NIC handle.
123+
pub fn new(nic_handle: Handle) -> uefi::Result<HttpHelper> {
124+
let mut binding = unsafe {
125+
boot::open_protocol::<HttpBinding>(
126+
boot::OpenProtocolParams {
127+
handle: nic_handle,
128+
agent: boot::image_handle(),
129+
controller: None,
130+
},
131+
boot::OpenProtocolAttributes::GetProtocol,
132+
)?
133+
};
134+
debug!("http: binding proto ok");
135+
136+
let child_handle = binding.create_child()?;
137+
debug!("http: child handle ok");
138+
139+
let protocol_res = unsafe {
140+
boot::open_protocol::<Http>(
141+
boot::OpenProtocolParams {
142+
handle: child_handle,
143+
agent: boot::image_handle(),
144+
controller: None,
145+
},
146+
boot::OpenProtocolAttributes::GetProtocol,
147+
)
148+
};
149+
if let Err(e) = protocol_res {
150+
let _ = binding.destroy_child(child_handle);
151+
return Err(e);
152+
}
153+
debug!("http: protocol ok");
154+
155+
Ok(HttpHelper {
156+
child_handle,
157+
binding,
158+
protocol: protocol_res.unwrap(),
159+
})
160+
}
161+
162+
/// Configure the HTTP Protocol with some sane defaults.
163+
pub fn configure(&mut self) -> uefi::Result<()> {
164+
let ip4 = HttpV4AccessPoint {
165+
use_default_addr: true.into(),
166+
..Default::default()
167+
};
168+
169+
let config = HttpConfigData {
170+
http_version: HttpVersion::HTTP_VERSION_10,
171+
time_out_millisec: 10_000,
172+
local_addr_is_ipv6: false.into(),
173+
access_point: HttpAccessPoint { ipv4_node: &ip4 },
174+
};
175+
176+
self.protocol.configure(&config)?;
177+
debug!("http: configure ok");
178+
179+
Ok(())
180+
}
181+
182+
/// Send HTTP request
183+
pub fn request(
184+
&mut self,
185+
method: HttpMethod,
186+
url: &str,
187+
body: Option<&mut [u8]>,
188+
) -> uefi::Result<()> {
189+
let url16 = uefi::CString16::try_from(url).unwrap();
190+
191+
let Some(hostname) = url.split('/').nth(2) else {
192+
return Err(Status::INVALID_PARAMETER.into());
193+
};
194+
let mut c_hostname = String::from(hostname);
195+
c_hostname.push('\0');
196+
debug!("http: host: {}", hostname);
197+
198+
let mut tx_req = HttpRequestData {
199+
method,
200+
url: url16.as_ptr() as *const u16,
201+
};
202+
203+
let mut tx_hdr = Vec::new();
204+
tx_hdr.push(HttpHeader {
205+
field_name: c"Host".as_ptr() as *const u8,
206+
field_value: c_hostname.as_ptr(),
207+
});
208+
209+
let mut tx_msg = HttpMessage::default();
210+
tx_msg.data.request = &mut tx_req;
211+
tx_msg.header_count = tx_hdr.len();
212+
tx_msg.header = tx_hdr.as_mut_ptr();
213+
if body.is_some() {
214+
let b = body.unwrap();
215+
tx_msg.body_length = b.len();
216+
tx_msg.body = b.as_mut_ptr() as *mut c_void;
217+
}
218+
219+
let mut tx_token = HttpToken {
220+
status: Status::NOT_READY,
221+
message: &mut tx_msg,
222+
..Default::default()
223+
};
224+
225+
self.protocol.request(&mut tx_token)?;
226+
debug!("http: request sent ok");
227+
228+
loop {
229+
if tx_token.status != Status::NOT_READY {
230+
break;
231+
}
232+
self.protocol.poll()?;
233+
}
234+
235+
if tx_token.status != Status::SUCCESS {
236+
return Err(tx_token.status.into());
237+
};
238+
239+
debug!("http: request status ok");
240+
241+
Ok(())
242+
}
243+
244+
/// Send HTTP GET request
245+
pub fn request_get(&mut self, url: &str) -> uefi::Result<()> {
246+
self.request(HttpMethod::GET, url, None)?;
247+
Ok(())
248+
}
249+
250+
/// Send HTTP HEAD request
251+
pub fn request_head(&mut self, url: &str) -> uefi::Result<()> {
252+
self.request(HttpMethod::HEAD, url, None)?;
253+
Ok(())
254+
}
255+
256+
/// Receive the start of the http response, the headers and (parts of) the body.
257+
pub fn response_first(&mut self, expect_body: bool) -> uefi::Result<HttpHelperResponse> {
258+
let mut rx_rsp = HttpResponseData {
259+
status_code: HttpStatusCode::STATUS_UNSUPPORTED,
260+
};
261+
262+
let mut body = vec![0; if expect_body { 16 * 1024 } else { 0 }];
263+
let mut rx_msg = HttpMessage::default();
264+
rx_msg.data.response = &mut rx_rsp;
265+
rx_msg.body_length = body.len();
266+
rx_msg.body = if !body.is_empty() {
267+
body.as_mut_ptr()
268+
} else {
269+
ptr::null()
270+
} as *mut c_void;
271+
272+
let mut rx_token = HttpToken {
273+
status: Status::NOT_READY,
274+
message: &mut rx_msg,
275+
..Default::default()
276+
};
277+
278+
self.protocol.response(&mut rx_token)?;
279+
280+
loop {
281+
if rx_token.status != Status::NOT_READY {
282+
break;
283+
}
284+
self.protocol.poll()?;
285+
}
286+
287+
debug!(
288+
"http: response: {} / {:?}",
289+
rx_token.status, rx_rsp.status_code
290+
);
291+
292+
if rx_token.status != Status::SUCCESS && rx_token.status != Status::HTTP_ERROR {
293+
return Err(rx_token.status.into());
294+
};
295+
296+
debug!("http: headers: {}", rx_msg.header_count);
297+
let mut headers: Vec<(String, String)> = Vec::new();
298+
for i in 0..rx_msg.header_count {
299+
let n;
300+
let v;
301+
unsafe {
302+
n = CStr::from_ptr((*rx_msg.header.add(i)).field_name as *const c_char);
303+
v = CStr::from_ptr((*rx_msg.header.add(i)).field_value as *const c_char);
304+
}
305+
headers.push((
306+
n.to_str().unwrap().to_lowercase(),
307+
String::from(v.to_str().unwrap()),
308+
));
309+
}
310+
311+
debug!("http: body: {}/{}", rx_msg.body_length, body.len());
312+
313+
let rsp = HttpHelperResponse {
314+
status: rx_rsp.status_code,
315+
headers,
316+
body: body[0..rx_msg.body_length].to_vec(),
317+
};
318+
Ok(rsp)
319+
}
320+
321+
/// Receive more body data.
322+
pub fn response_more(&mut self) -> uefi::Result<Vec<u8>> {
323+
let mut body = vec![0; 16 * 1024];
324+
let mut rx_msg = HttpMessage {
325+
body_length: body.len(),
326+
body: body.as_mut_ptr() as *mut c_void,
327+
..Default::default()
328+
};
329+
330+
let mut rx_token = HttpToken {
331+
status: Status::NOT_READY,
332+
message: &mut rx_msg,
333+
..Default::default()
334+
};
335+
336+
self.protocol.response(&mut rx_token)?;
337+
338+
loop {
339+
if rx_token.status != Status::NOT_READY {
340+
break;
341+
}
342+
self.protocol.poll()?;
343+
}
344+
345+
debug!("http: response: {}", rx_token.status);
346+
347+
if rx_token.status != Status::SUCCESS {
348+
return Err(rx_token.status.into());
349+
};
350+
351+
debug!("http: body: {}/{}", rx_msg.body_length, body.len());
352+
353+
Ok(body[0..rx_msg.body_length].to_vec())
354+
}
355+
}
356+
357+
impl Drop for HttpHelper {
358+
fn drop(&mut self) {
359+
debug!("http: drop");
360+
let _ = self.binding.destroy_child(self.child_handle);
361+
}
362+
}

uefi/src/proto/network/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//!
55
//! These protocols can be used to interact with network resources.
66
7+
pub mod http;
78
pub mod pxe;
89
pub mod snp;
910

0 commit comments

Comments
 (0)