Skip to content

Commit b76f9dc

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

File tree

2 files changed

+371
-0
lines changed

2 files changed

+371
-0
lines changed

uefi/src/proto/network/http.rs

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

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)