Skip to content

Commit 0bcf707

Browse files
committed
add http protocol support
Signed-off-by: Gerd Hoffmann <[email protected]>
1 parent 19bdcee commit 0bcf707

File tree

2 files changed

+385
-0
lines changed

2 files changed

+385
-0
lines changed

uefi/src/proto/network/http.rs

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

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 ip4config2;
89
pub mod pxe;
910
pub mod snp;

0 commit comments

Comments
 (0)