def_oid 1.0.0

Define OID into ASN1 bytes format at compile time by using OID arcs literal string
Documentation
//! # def_oid
//! This crate provides two procedural macros.
//! Both macros is used to declare fixed size array of `u8`.
//! The array will contains OID encoded according to ASN1 encoding scheme.
//! See [X.690](https://www.itu.int/rec/T-REC-X.690) for more detail.
//! 
//! Two usable macros are:
//! - [const_oid!] - This macro can be used to declare constant variable. Since Rust required
//! that all constant must have explicit type declared and OID size required some calculation,
//! this macro do all that. For example, OID of `2.5.4.3` is `[u8; 3]` while `1.1.3.4.5555`
//! is `[u8; 5]`. See [const_oid!] for how to use it.
//! - [oid!] - This macro can be used to declare any variable. It will return fixed size array
//! of `u8`.
extern crate proc_macro;

use proc_macro::TokenStream;
use syn::{parse::{self, Parse}, Ident, LitStr, Token};
use quote::quote;

/// Convert parameter `v` into variable-length-quantity bytes and append it to `result` vec.
/// The output byte order is BigEndian.
fn u32_into_vlq(v: u32, result: &mut Vec<u8>) {
  if v > 0x0FFFFFFF {
    result.push(((v >> 28) | 0x80) as u8); // 4 MSB need to bit mask as there's no higher bit value.
  }
  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); // Last byte always have 8th bit = 0
}

/// Convert OID &str token into bytes vec of ASN1 encoded OID.
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
}
/// An argument type accepted by proc_macro [const_oid].
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()? 
      }
    )
  }
}
/// An argument type accept by proc_macro [oid]
struct Oid {
  oid: LitStr
}
impl Parse for Oid {
  fn parse(input: parse::ParseStream) -> syn::Result<Self> {
      Ok(
        Oid {
          oid: input.parse()?
        }
      )
  }
}
/// This macro is used to declare an [OID (Object-Identifier)](https://en.m.wikipedia.org/wiki/Object_identifier) constant variable.
/// It will automatically convert OID string into bytes array that conform to ASN1 OID encoding
/// according to [X.690](https://www.itu.int/rec/T-REC-X.690) standard.
/// 
/// ## Example
/// Declare a fixed size array of bytes constant.
/// ```rust
/// use def_oid::const_oid;
/// const_oid!(COMMON_NAME, "2.5.4.3"); // It will declare `const COMMON_NAME: [u8; 3] = ...` for you.
/// assert_eq!(COMMON_NAME, [85, 4, 3]); // Check that the COMMON_NAME exist and valid.
/// ```
#[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()
}

/// This macro is used to define an [OID (Object-Identifier)](https://en.m.wikipedia.org/wiki/Object_identifier).
/// It will automatically convert OID string into bytes array that conform to ASN1 OID encoding
/// according to [X.690](https://www.itu.int/rec/T-REC-X.690) standard.
/// 
/// ## Example
/// ### In constant usage as `const &[u8]`.
/// It is possible to declare const variable by using this macro since it return fixed size array.
/// The error prone part is to calculate the size of array. To workaround this, convert it to slice.
/// ```rust
/// use def_oid::oid;
/// const COMMON_NAME: &'static [u8] = &oid!("2.5.4.3");
/// ```
/// **Note**: It required to use borrowed slice because type annotation is required for every constant.
/// However, the array size is computed value. The borrowed value will turn fixed size array into
/// borrowed slice so the type can be declared.
/// 
/// If this indirection is not preferred, consider using [const_oid!] macro.
/// It will take care of entire constant declaration to avoid complexity of type declaration.
/// ### In general usage
/// Since non-const variable doesn't required explict type declaration, it is straightforward to use.
/// ```rust
/// use def_oid::oid;
/// let common_name_oid = oid!("2.5.4.3");
/// assert_eq!(common_name_oid, [85u8, 4, 3]);
/// ```
#[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());
  }
}