extern crate proc_macro;
use proc_macro::TokenStream;
use syn::{parse::{self, Parse}, Ident, LitStr, Token};
use quote::quote;
fn u32_into_vlq(v: u32, result: &mut Vec<u8>) {
if v > 0x0FFFFFFF {
result.push(((v >> 28) | 0x80) as u8); }
if v > 0x1FFFFF {
result.push((((v >> 21) & 0xFF) | 0x80) as u8);
}
if v > 0x3FFF {
result.push((((v >> 14) & 0xFF) | 0x80) as u8);
}
if v > 0x7F {
result.push((((v >> 7) & 0xFF) | 0x80) as u8);
}
result.push((v & 0x7F) as u8); }
fn oid_to_bytes_vec(oid_str: LitStr) -> Vec<u8> {
let oid_string = oid_str.value();
let arcs: Vec<&str> = oid_string.split(".").collect();
if arcs.len() < 2 {
panic!("OID should have 2 or more arcs.")
}
let first = arcs[0].parse::<u32>().expect("First arc value must be either 0, 1, or 2");
if first > 2 {
panic!("First arc of OID must be 0, 1, or 2.")
}
let computed = first.checked_mul(40).expect("Invalid first arc. Arc value must be either 0, 1, or 2.")
.checked_add(
arcs[1].parse::<u32>().expect("Second arg must be valid 32 bits unsigned integer.")
).expect("Arc1 * 40 + Arc2 caused overflow. Check that both arcs are in valid range of value.");
let mut encoded = Vec::new();
u32_into_vlq(computed, &mut encoded);
arcs.split_at(2).1.into_iter().for_each(|arc| {
let val = arc.parse::<u32>().expect("Each arc should be valid 32 bits unsigned integer.");
u32_into_vlq(val, &mut encoded);
});
encoded
}
struct ConstOid {
ident: Ident,
comma: Token![,],
oid: LitStr
}
impl Parse for ConstOid {
fn parse(input: parse::ParseStream) -> syn::Result<Self> {
Ok(
ConstOid {
ident: input.parse()?,
comma: input.parse()?,
oid: input.parse()?
}
)
}
}
struct Oid {
oid: LitStr
}
impl Parse for Oid {
fn parse(input: parse::ParseStream) -> syn::Result<Self> {
Ok(
Oid {
oid: input.parse()?
}
)
}
}
#[proc_macro]
pub fn const_oid(item: TokenStream) -> TokenStream {
let ConstOid {
ident,
comma: _comma,
oid
} = syn::parse(item).expect("The argument to proc_macro `const_oid` must be a name of constant variable, followed by comma, followed by OID literal string.");
let encoded = oid_to_bytes_vec(oid);
let n = encoded.len();
quote! { const #ident: [u8; #n] = [#(#encoded,)*]; }.into()
}
#[proc_macro]
pub fn oid(item: TokenStream) -> TokenStream {
let Oid {
oid
} = syn::parse(item).expect("The argument to proc_macro `oid` must be string literal like \"2.5.4.3\"");
let encoded = oid_to_bytes_vec(oid);
quote! {[#(#encoded,)*]}.into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn single_arc() {
oid(quote! {"1"}.into());
}
#[test]
#[should_panic]
fn two_arcs_digit() {
oid(quote! {1.2}.into());
}
#[test]
#[should_panic]
fn two_arcs_invalid_first_arc() {
oid(quote! {"3.2"}.into());
}
#[test]
#[should_panic]
fn const_invalid_name_type() {
const_oid(quote! {"COMMON_NAME", "2.5.4.3"}.into());
}
#[test]
#[should_panic]
fn const_lack_oid() {
const_oid(quote! {COMMON_NAME}.into());
}
#[test]
#[should_panic]
fn const_invalid_oid_type() {
const_oid(quote! {COMMON_NAME, 2.5.4.3}.into());
}
#[test]
#[should_panic]
fn const_invalid_type() {
const_oid(quote! {"COMMON_NAME", 2.5.4.3}.into());
}
}