Skip to content

Commit 4eb433f

Browse files
committed
add http protocol support
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
1 parent ff1b91f commit 4eb433f

File tree

2 files changed

+386
-0
lines changed

2 files changed

+386
-0
lines changed

uefi/src/proto/network/http.rs

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

uefi/src/proto/network/mod.rs

Lines changed: 1 addition & 0 deletions
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)