Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Service Binding, DHCP4, IP4 Config2, HTTP, and TLS Config Protocols #952

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 119 additions & 26 deletions uefi-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,30 @@ use proc_macro::TokenStream;

use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned, TokenStreamExt};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{
parse_macro_input, parse_quote, Error, Expr, ExprLit, ExprPath, FnArg, Ident, ItemFn,
ItemStruct, Lit, LitStr, Pat, Visibility,
parse_macro_input, parse_quote, token, Expr, ExprLit, ExprPath, FnArg, Ident, ItemFn,
ItemStruct, Lit, LitStr, Pat, Type, TypePath, Visibility,
};

macro_rules! err {
($span:expr, $message:expr $(,)?) => {
Error::new($span.span(), $message).to_compile_error()
syn::Error::new($span.span(), $message).to_compile_error()
};
($span:expr, $message:expr, $($args:expr),*) => {
Error::new($span.span(), format!($message, $($args),*)).to_compile_error()
syn::Error::new($span.span(), format!($message, $($args),*)).to_compile_error()
};
}

/// Attribute macro for marking structs as UEFI protocols.
///
/// The macro takes one argument, either a GUID string or the path to a `Guid`
/// constant.
/// The macro takes one, two, or 3 arguments. The first two are GUIDs
/// or the path to a `Guid` constant. The first argument is always the
/// GUID of the protocol, while the optional second argument is the
/// GUID of the service binding protocol, when applicable.
///
/// The third argument is a struct
///
/// The macro can only be applied to a struct. It implements the
/// [`Protocol`] trait and the `unsafe` [`Identify`] trait for the
Expand All @@ -49,38 +54,47 @@ macro_rules! err {
/// #[unsafe_protocol(PROTO_GUID)]
/// struct ExampleProtocol2 {}
///
/// const SERVICE_GUID: Guid = guid!("12345678-9abc-def0-1234-56789abcdef1");
/// #[unsafe_protocol(PROTO_GUID, SERVICE_GUID)]
/// struct ExampleProtocol3 {}
///
/// assert_eq!(ExampleProtocol1::GUID, PROTO_GUID);
/// assert_eq!(ExampleProtocol2::GUID, PROTO_GUID);
/// assert_eq!(ExampleProtocol3::GUID, PROTO_GUID);
///
/// assert_eq!(ExampleProtocol1::SERVICE_BINDING, None);
/// assert_eq!(ExampleProtocol2::SERVICE_BINDING, None);
/// assert_eq!(ExampleProtocol3::SERVICE_BINDING, Some(SERVICE_GUID));
/// ```
///
/// [`Identify`]: https://docs.rs/uefi/latest/uefi/trait.Identify.html
/// [`Protocol`]: https://docs.rs/uefi/latest/uefi/proto/trait.Protocol.html
/// [send-and-sync]: https://doc.rust-lang.org/nomicon/send-and-sync.html
#[proc_macro_attribute]
pub fn unsafe_protocol(args: TokenStream, input: TokenStream) -> TokenStream {
let expr = parse_macro_input!(args as Expr);
let args = parse_macro_input!(args as ProtocolArgs);
let item_struct = parse_macro_input!(input as ItemStruct);
let ident = &item_struct.ident;
let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();

let guid_val = match expr {
Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) => {
quote!(::uefi::guid!(#lit))
let proto_guid = guid_from_expr(args.protocol_guid);
let service_binding_guid = match args.service_binding_guid {
None => quote!(None),
Some(expr) => {
let guid = guid_from_expr(expr);
quote!(Some(#guid))
}
Expr::Path(ExprPath { path, .. }) => quote!(#path),
_ => {
return err!(
expr,
"macro input must be either a string literal or path to a constant"
)
.into()
};
let wrapper_type = match args.wrapper_type {
None => quote!(::uefi::proto::NoWrapper),
Some(Type::Path(TypePath { path, .. })) => {
let wrapper_ident = &item_struct.ident;
let wrapper_generics = &item_struct.generics;
quote!(::uefi::proto::StructWrapper<#path, #wrapper_ident #wrapper_generics>)
}
Some(typ) => return err!(typ, "wrapper type must be a path").into(),
};

let item_struct = parse_macro_input!(input as ItemStruct);

let ident = &item_struct.ident;
let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();

quote! {
// Disable this lint for now. It doesn't account for the fact that
// currently it doesn't work to `derive(Debug)` on structs that have
Expand All @@ -91,14 +105,93 @@ pub fn unsafe_protocol(args: TokenStream, input: TokenStream) -> TokenStream {
#item_struct

unsafe impl #impl_generics ::uefi::Identify for #ident #ty_generics #where_clause {
const GUID: ::uefi::Guid = #guid_val;
const GUID: ::uefi::Guid = #proto_guid;
}

impl #impl_generics ::uefi::proto::Protocol for #ident #ty_generics #where_clause {}
impl #impl_generics ::uefi::proto::Protocol for #ident #ty_generics #where_clause {
const SERVICE_BINDING: Option<::uefi::Guid> = #service_binding_guid;
type Raw = #wrapper_type;
}
}
.into()
}

struct ProtocolArgs {
protocol_guid: Expr,
service_binding_guid: Option<Expr>,
wrapper_type: Option<Type>,
}

impl Parse for ProtocolArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
// Always parse a GUID
let protocol_guid = input.parse::<Expr>()?;
let mut args = Self {
protocol_guid,
service_binding_guid: None,
wrapper_type: None,
};

// Next is always a comma
if !input.is_empty() {
let _ = input.parse::<token::Comma>()?;
}

// Next can be a GUID or a comma
let lookahead = input.lookahead1();

// ... so parse a GUID if not a comma
if !input.is_empty() && !lookahead.peek(token::Comma) {
let service_binding_guid = input.parse::<Expr>()?;
args.service_binding_guid = Some(service_binding_guid);
}

// ... and then parse a comma unless at the end
if !input.is_empty() {
let _ = input.parse::<token::Comma>()?;
}

// Next can be a type or a (trailing) comma
let lookahead = input.lookahead1();

// ... so parse a Type if not a comma
if !input.is_empty() && !lookahead.peek(token::Comma) {
let wrapper_type = input.parse::<Type>()?;
args.wrapper_type = Some(wrapper_type);
}

// ... and then parse a (trailing) comma unless at the end
if !input.is_empty() {
let _ = input.parse::<token::Comma>()?;
}

// Error if this is not the end
if !input.is_empty() {
return Err(input.error("up to 3 comma-separated args are supported"));
}

Ok(args)
}
}

fn guid_from_expr(expr: Expr) -> TokenStream2 {
match expr {
Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) => {
quote!(::uefi::guid!(#lit))
}
Expr::Path(ExprPath { path, .. }) => quote!(#path),
_ => {
return err!(
expr,
"macro input must be either a string literal or path to a constant"
)
.into()
}
}
}

/// Get the name of a function's argument at `arg_index`.
fn get_function_arg_name(f: &ItemFn, arg_index: usize, errors: &mut TokenStream2) -> Option<Ident> {
if let Some(FnArg::Typed(arg)) = f.sig.inputs.iter().nth(arg_index) {
Expand Down
165 changes: 165 additions & 0 deletions uefi-raw/src/protocol/dhcp4.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use crate::{guid, Char8, Guid, Status};
use core::ffi::c_void;

newtype_enum! {
pub enum Event: i32 => {
NULL = 0x00,
DHCP4_SEND_DISCOVER = 0x01,
DHCP4_RCVD_OFFER = 0x02,
DHCP4_SELECT_OFFER = 0x03,
DHCP4_SEND_REQUEST = 0x04,
DHCP4_RCVD_ACK = 0x05,
DHCP4_RCVD_NAK = 0x06,
DHCP4_SEND_DECLINE = 0x07,
DHCP4_BOUND_COMPLETED = 0x08,
DHCP4_ENTER_RENEWING = 0x09,
DHCP4_ENTER_REBINDING = 0x0a,
DHCP4_ADDRESS_LOST = 0x0b,
DHCP4_FAIL = 0x0c,
}
}

newtype_enum! {
pub enum State: i32 => {
DHCP4_STOPPED = 0x0,
DHCP4_INIT = 0x1,
DHCP4_SELECTING = 0x2,
DHCP4_REQUESTING = 0x3,
DHCP4_BOUND = 0x4,
DHCP4_RENEWING = 0x5,
DHCP4_REBINDING = 0x6,
DHCP4_INIT_REBOOT = 0x7,
DHCP4_REBOOTING = 0x8,
}
}

#[repr(C, packed(1))]
pub struct Packet {
pub size: u32,
pub length: u32,
pub op_code: u8,
pub hw_type: u8,
pub hw_addr_len: u8,
pub hops: u8,
pub xid: u32,
pub seconds: u16,
pub reserved: u16,
pub client_addr: [u8; 4],
pub your_addr: [u8; 4],
pub server_addr: [u8; 4],
pub gateway_addr: [u8; 4],
pub client_hw_addr: [u8; 16],
pub server_name: [Char8; 64],
pub boot_file_name: [Char8; 128],
pub magik: u32,
pub option: *const u8,
}

/// PacketOption is a dynamically sized struct. The data field can be sized
/// between 0 and 255 bytes. Length must always equal N.
///
/// Arrays of PacketOptions must be packed with zero padding between bytes,
/// except data must always have at least one byte, even when length is 0.
#[derive(Debug)]
#[repr(C, packed(1))]
pub struct PacketOption<const N: usize> {
pub op_code: u8,
pub length: u8,
pub data: [u8; N],
}

#[repr(C)]
pub struct ConfigData {
pub discover_try_count: u32,
pub discover_timeout: *mut u32,
pub request_try_count: u32,
pub request_timeout: *mut u32,
pub client_address: [u8; 4],
pub callback: Option<
extern "efiapi" fn(
this: *mut Dhcp4Protocol,
context: *const c_void,
current_state: State,
dhcp4_event: Event,
packet: *const Packet,
new_packet: *mut *const Packet,
) -> Status,
>,
pub callback_context: *const c_void,

// The option list is an array of dynamically sized PacketOption structs
// with no padding between items.
pub option_count: u32,
pub option_list: *mut *const PacketOption<1>,
}

#[repr(C)]
pub struct ModeData {
pub state: State,
pub config_data: ConfigData,
pub client_address: [u8; 4],
pub client_mac_address: [u8; 32],
pub server_address: [u8; 4],
pub router_address: [u8; 4],
pub subnet_mask: [u8; 4],
pub lease_time: u32,
pub reply_packet: *mut Packet,
}

#[repr(C)]
pub struct ListenPoint {
pub listen_address: [u8; 4],
pub subnet_mask: [u8; 4],
pub listen_port: u16,
}

#[repr(C)]
pub struct TransmitReceiveToken {
pub status: Status,
pub completion_event: Event,
pub remote_address: [u8; 4],
pub remote_port: u16,
pub gateway_address: [u8; 4],
pub listen_point_count: u32,
pub listen_points: *mut ListenPoint,
pub timeout_value: u32,
pub packet: *mut Packet,
pub response_count: u32,
pub response_list: *mut Packet,
}

#[repr(C)]
pub struct Dhcp4Protocol {
pub get_mode_data: unsafe extern "efiapi" fn(this: &Self, mode_data: *mut ModeData) -> Status,
pub configure: unsafe extern "efiapi" fn(this: &Self, cfg_data: *const ConfigData) -> Status,
pub start: extern "efiapi" fn(this: &Self, completion_event: Event) -> Status,
pub renew_rebind: unsafe extern "efiapi" fn(
this: &Self,
rebind_request: bool,
completion_event: Event,
) -> Status,
pub release: extern "efiapi" fn(this: &Self) -> Status,
pub stop: extern "efiapi" fn(this: &Self) -> Status,
pub build: unsafe extern "efiapi" fn(
this: &Self,
seed_packet: *mut Packet,
delete_count: u32,
delete_list: *mut u8,
append_count: u32,
append_list: *mut PacketOption<1>,
new_packet: *mut *mut Packet,
) -> Status,
pub transmit_receive:
unsafe extern "efiapi" fn(this: &Self, token: *mut TransmitReceiveToken) -> Status,
pub parse: unsafe extern "efiapi" fn(
this: &Self,
packet: *mut Packet,
option_count: *mut u32,
packet_option_list: *mut PacketOption<1>,
) -> Status,
}

impl Dhcp4Protocol {
pub const GUID: Guid = guid!("8a219718-4ef5-4761-91c8-c0f04bda9e56");
pub const SERVICE_GUID: Guid = guid!("9d9a39d8-bd42-4a73-a4d5-8ee94be11380");
}
Loading