<commit_before>use core::convert::Infallible;

/// Enum mapping type
///
/// This trait is internally used by `#[derive(Enum)]`. `Enum<T>` is
/// implemented by any enum type where V is a generic type representing a
/// value. The purpose of this generic type is to allow providing a value
/// type for arrays, as Rust currently does not support higher kinded types.
///
/// This trait is also implemented by `bool` and `u8`. While `u8` is
/// strictly speaking not an actual enum, there are good reasons to consider
/// it like one, as array of `u8` keys is a relatively common pattern.
pub trait Enum: Sized {
    /// Length of the enum.
    const LENGTH: usize;

    /// Takes an usize, and returns an element matching `into_usize` function.
    fn from_usize(value: usize) -> Self;
    /// Returns an unique identifier for a value within range of `0..Array::LENGTH`.
    fn into_usize(self) -> usize;
}

/// Trait associating
pub trait EnumArray<V>: Enum {
    /// Representation of an enum map for type `V`.
    type Array: Array<V>;
}

/// Array for enum-map storage.
///
/// This trait is inteded for primitive array types (with fixed length).
pub trait Array<V> {
    /// Coerces a reference to the array into a reference to a slice.
    fn slice(&self) -> &[V];

    /// Coerces a mutable reference to the array into a mutable reference to a slice.
    fn slice_mut(&mut self) -> &mut [V];
}

impl<V, const N: usize> Array<V> for [V; N] {
    fn slice(&self) -> &[V] {
        self
    }
    fn slice_mut(&mut self) -> &mut [V] {
        self
    }
}

impl Enum for bool {
    const LENGTH: usize = 2;

    #[inline]
    fn from_usize(value: usize) -> Self {
        match value {
            0 => false,
            1 => true,
            _ => unreachable!(),
        }
    }
    #[inline]
    fn into_usize(self) -> usize {
        usize::from(self)
    }
}

impl<T> EnumArray<T> for bool {
    type Array = [T; Self::LENGTH];
}

impl Enum for u8 {
    const LENGTH: usize = 256;

    #[inline]
    fn from_usize(value: usize) -> Self {
        value.try_into().unwrap()
    }
    #[inline]
    fn into_usize(self) -> usize {
        self as usize
    }
}

impl<T> EnumArray<T> for u8 {
    type Array = [T; Self::LENGTH];
}

impl Enum for Infallible {
    const LENGTH: usize = 0;

    #[inline]
    fn from_usize(_: usize) -> Self {
        unreachable!();
    }
    #[inline]
    fn into_usize(self) -> usize {
        match self {}
    }
}

impl<T> EnumArray<T> for Infallible {
    type Array = [T; Self::LENGTH];
}
<commit_msg>Actually use usize::from in into_usize for u8<commit_after>use core::convert::Infallible;

/// Enum mapping type
///
/// This trait is internally used by `#[derive(Enum)]`. `Enum<T>` is
/// implemented by any enum type where V is a generic type representing a
/// value. The purpose of this generic type is to allow providing a value
/// type for arrays, as Rust currently does not support higher kinded types.
///
/// This trait is also implemented by `bool` and `u8`. While `u8` is
/// strictly speaking not an actual enum, there are good reasons to consider
/// it like one, as array of `u8` keys is a relatively common pattern.
pub trait Enum: Sized {
    /// Length of the enum.
    const LENGTH: usize;

    /// Takes an usize, and returns an element matching `into_usize` function.
    fn from_usize(value: usize) -> Self;
    /// Returns an unique identifier for a value within range of `0..Array::LENGTH`.
    fn into_usize(self) -> usize;
}

/// Trait associating
pub trait EnumArray<V>: Enum {
    /// Representation of an enum map for type `V`.
    type Array: Array<V>;
}

/// Array for enum-map storage.
///
/// This trait is inteded for primitive array types (with fixed length).
pub trait Array<V> {
    /// Coerces a reference to the array into a reference to a slice.
    fn slice(&self) -> &[V];

    /// Coerces a mutable reference to the array into a mutable reference to a slice.
    fn slice_mut(&mut self) -> &mut [V];
}

impl<V, const N: usize> Array<V> for [V; N] {
    fn slice(&self) -> &[V] {
        self
    }
    fn slice_mut(&mut self) -> &mut [V] {
        self
    }
}

impl Enum for bool {
    const LENGTH: usize = 2;

    #[inline]
    fn from_usize(value: usize) -> Self {
        match value {
            0 => false,
            1 => true,
            _ => unreachable!(),
        }
    }
    #[inline]
    fn into_usize(self) -> usize {
        usize::from(self)
    }
}

impl<T> EnumArray<T> for bool {
    type Array = [T; Self::LENGTH];
}

impl Enum for u8 {
    const LENGTH: usize = 256;

    #[inline]
    fn from_usize(value: usize) -> Self {
        value.try_into().unwrap()
    }
    #[inline]
    fn into_usize(self) -> usize {
        usize::from(self)
    }
}

impl<T> EnumArray<T> for u8 {
    type Array = [T; Self::LENGTH];
}

impl Enum for Infallible {
    const LENGTH: usize = 0;

    #[inline]
    fn from_usize(_: usize) -> Self {
        unreachable!();
    }
    #[inline]
    fn into_usize(self) -> usize {
        match self {}
    }
}

impl<T> EnumArray<T> for Infallible {
    type Array = [T; Self::LENGTH];
}
<|endoftext|>


<commit_before>
  fn take_n<'r>(v: &'r [u8], n: uint) -> (&'r [u8], &'r [u8]) {
    (vec::slice(v, 0, n), vec::slice(v, n, v.len()))
  }

  fn take_atmost_n<'r>(v: &'r [u8], n: uint) -> (&'r [u8], &'r [u8]) {
    take_n(v, uint::min(n, v.len()))
  }

  fn take_head<'r>(v: &'r [u8]) -> (u8, &'r [u8]) {
    assert!(!v.is_empty());
    (*v.head(), vec::slice(v, 1, v.len()))
  }

  // in_integer
  enum RedisType {
    TyNone,
    TyList,
    TyData,
    TyInt
  }

  enum Inside {
    in_nothing,
    in_number,
    in_number_digits_only,
    in_number_done,
    in_number_need_newline,
    in_data,
    //in_data_need_newline
  }

  struct RedisState {
    in: Inside,
    typ: RedisType,
    data_size: uint,
    data_have: uint,
    number: uint,
    negative_number: bool,
  }

  enum RedisResult {
    Finished,
    NeedMore,
    Error
  }

  enum Result {
    Invalid,
    Nil,
    Int(int),
    Data(~[u8]),
    List(~[Result]),
    Err(~str),
    Status(~str)
  }

  // Return
  trait Visitor {
    fn on_list(&mut self, len: uint) -> bool;

    fn on_data_beg(&mut self, len: uint);
    fn on_data(&mut self, data: &[u8]);
    fn on_data_end(&mut self) -> bool;

    fn on_integer(&mut self, num: uint, sign: bool) -> bool;

    fn on_nil(&mut self) -> bool;
  }

// keep recursion in visitor?
  struct MyVisitor {
    stack: ~[~[Result]], // (len, idx)
    result: Result
  }

  // returns true if we are finished parsing a redis request
  fn element_done(visitor: &mut MyVisitor, value: Result) -> bool {
    if visitor.stack.is_empty() {
      visitor.result = value;
      true
    }
    else {

      vec::push(&mut (visitor.stack[0/*visitor.stack.len()-1*/]), value);
      false
/*      let (len, idx) = visitor.stack.last();
      assert!(idx+1 <= len);
      visitor.stack[visitor.stack.len()-1] = (len, idx+1);
      if idx + 1 == len {
        error!("Finsihed list");
        let _ = vec::pop(&mut visitor.stack);
        element_done(visitor)
      }
      else {
        false
      }
*/
    }
  }

  impl Visitor for MyVisitor {
    fn on_list(&mut self, len: uint) -> bool {
      error!("on_list(len: %u)", len);
      if len == 0 {
        element_done(self, List(~[]))
      }
      else {
        //vec::push(&mut self.stack, (len, 0));
        false
      }
    }

    fn on_data_beg(&mut self, len: uint) {
      error!("on_data_beg(len: %u)", len)
    }

    fn on_data(&mut self, data: &[u8]) {
      error!("on_data(%?)", data)
    }

    fn on_data_end(&mut self) -> bool {
      error!("on_data_end()");
      element_done(self, Data(~[1,2,3]))
    }

    fn on_integer(&mut self, num: uint, sign: bool) -> bool {
      error!("on_integer(%?, %?)", num, sign);
      element_done(self, Int(num as int)) // XXX
    }

    fn on_nil(&mut self) -> bool {
      error!("on_nil");
      element_done(self, Nil)
    }
  }

  fn parse_redis<'r, V: Visitor>(st: &mut RedisState, buf: &'r [u8], visitor: &mut V) -> (RedisResult, &'r [u8]) {
    let mut buf = buf;

    loop {
      match st.in {
        in_nothing => {
          if (buf.is_empty()) {break}

          let (c, b) = take_head(buf);
          buf = b;

          match c as char {
            '\r' | '\n' | ' ' =>  {
                error!("ignoring carriage return/new/whitespace line")
              }
            '*' => {
                st.typ = TyList;
                st.in = in_number;
                st.number = 0;
                st.negative_number = false;
              }
            '$' => {
                st.typ = TyData;
                st.in = in_number;
                st.number = 0;
                st.negative_number = false;
             }
            ':' => {
                st.typ = TyInt;
                st.in = in_number;
                st.number = 0;
                st.negative_number = false;
             }

             _ => {
                return (Error, buf) // XXX: return a slice including the character that failed
             }
          }

        }

        in_data => {
          if (buf.is_empty()) {break}
          let (data, b) = take_atmost_n(buf, st.data_size - st.data_have);
          buf = b;
          visitor.on_data(data);
          st.data_have += data.len();
          assert!(st.data_have <= st.data_size);
          if st.data_have == st.data_size {
            //XXX: st.in = in_data_need_newline;
            st.in = in_nothing;
            if visitor.on_data_end() {
              return (Finished, buf)
            }
          }
        }

        // we could treat the newline after data differently.
/*
        in_data_need_newline => {
          if (buf.is_empty()) {break}
          let (c, b) = take_head(buf);
          buf = b;
          if c == ('\n' as u8) {
            st.in = in_nothing;
          }
          else if c == ('\r' as u8) || c == (' ' as u8) {
            error("Consume whitespace");
          }
          else {
            return (Error, buf)
          }
        }
*/

        in_number_need_newline => {
          if (buf.is_empty()) {break}
          let (c, b) = take_head(buf);
          buf = b;
          if c == ('\n' as u8) {
            st.in = in_number_done;
          }
          else {
            return (Error, buf)
          }
        }

        // XXX: make a function instead of a STATE
        in_number_done => {
          match st.typ {
            TyData => {
              if st.negative_number {
                if st.number == 1 {
                  st.in = in_nothing;
                  if visitor.on_nil() {
                    return (Finished, buf)
                  }
                }
                else {
                  return (Error, buf)
                }
              }
              else {
                st.data_size = st.number;
                st.data_have = 0;
                st.in = in_data;
                visitor.on_data_beg(st.data_size);
              }
            }
            TyList => {
              // XXX:
              // push current recursion level and index on stack
              st.in = in_nothing;
              if st.negative_number {
                if (st.number == 1) {
                  if visitor.on_nil() {
                    return (Finished, buf)
                  }
                }
                else {
                  return (Error, buf)
                }
              }
              else {
                if visitor.on_list(st.number) {
                  return (Finished, buf)
                }
              }
            }

            TyInt => {
              st.in = in_nothing;
              if visitor.on_integer(st.number, st.negative_number) {
                return (Finished, buf)
              }
            }
            _ => {
              fail!(~"THIS SHOULD NEVER HAPPEN")
            }
          }
        }

        in_number | in_number_digits_only => {
          if (buf.is_empty()) {break}
          let (c, b) = take_head(buf);
          buf = b;

          if c >= ('0' as u8) && c <= ('9' as u8) {
            st.number *= 10;
            st.number += (c - ('0' as u8)) as uint;
            st.in = in_number_digits_only;
          }
          else if c as char == '-' {
            match st.in {
              in_number_digits_only => {
                return (Error, buf)
              }
              _ => {
                st.negative_number = true;
                st.in = in_number_digits_only;
              }
            }
          }
          else if c as char == '\r' || c as char == ' ' {
            st.in = in_number_need_newline;
          }
          else if c as char == '\n' {
            st.in = in_number_done;
          }
          else {
            return (Error, buf)
          }

        }
      }
    }
    (NeedMore, buf)
  }

fn main() {
  let mut st = RedisState {
    in: in_nothing,
    typ: TyNone,
    data_size: 0,
    data_have: 0,
    number: 0,
    negative_number: false,
  };

  error!("%?", st);

  let mut visitor = MyVisitor {stack: ~[], result: Invalid};

  let s = ~"*4\r\n$3\r\nabc\r\n:123\n:1\n$-1\n";
  let slice = str::as_bytes_slice(s);
  let x = parse_redis(&mut st, slice, &mut visitor);
  error!("%?", st);
  error!("%?", x);
  error!("%?", visitor.result);
}
<commit_msg>unindent<commit_after>
fn take_n<'r>(v: &'r [u8], n: uint) -> (&'r [u8], &'r [u8]) {
  (vec::slice(v, 0, n), vec::slice(v, n, v.len()))
}

fn take_atmost_n<'r>(v: &'r [u8], n: uint) -> (&'r [u8], &'r [u8]) {
  take_n(v, uint::min(n, v.len()))
}

fn take_head<'r>(v: &'r [u8]) -> (u8, &'r [u8]) {
  assert!(!v.is_empty());
  (*v.head(), vec::slice(v, 1, v.len()))
}

// in_integer
enum RedisType {
  TyNone,
  TyList,
  TyData,
  TyInt
}

enum Inside {
  in_nothing,
  in_number,
  in_number_digits_only,
  in_number_done,
  in_number_need_newline,
  in_data,
  //in_data_need_newline
}

struct RedisState {
  in: Inside,
  typ: RedisType,
  data_size: uint,
  data_have: uint,
  number: uint,
  negative_number: bool,
}

enum RedisResult {
  Finished,
  NeedMore,
  Error
}

enum Result {
  Invalid,
  Nil,
  Int(int),
  Data(~[u8]),
  List(~[Result]),
  Err(~str),
  Status(~str)
}

// Return
trait Visitor {
  fn on_list(&mut self, len: uint) -> bool;

  fn on_data_beg(&mut self, len: uint);
  fn on_data(&mut self, data: &[u8]);
  fn on_data_end(&mut self) -> bool;

  fn on_integer(&mut self, num: uint, sign: bool) -> bool;

  fn on_nil(&mut self) -> bool;
}

// keep recursion in visitor?
struct MyVisitor {
  stack: ~[~[Result]], // (len, idx)
  result: Result
}

// returns true if we are finished parsing a redis request
fn element_done(visitor: &mut MyVisitor, value: Result) -> bool {
  if visitor.stack.is_empty() {
    visitor.result = value;
    true
  }
  else {

    vec::push(&mut (visitor.stack[0/*visitor.stack.len()-1*/]), value);
    false
/*      let (len, idx) = visitor.stack.last();
    assert!(idx+1 <= len);
    visitor.stack[visitor.stack.len()-1] = (len, idx+1);
    if idx + 1 == len {
      error!("Finsihed list");
      let _ = vec::pop(&mut visitor.stack);
      element_done(visitor)
    }
    else {
      false
    }
*/
  }
}

impl Visitor for MyVisitor {
  fn on_list(&mut self, len: uint) -> bool {
    error!("on_list(len: %u)", len);
    if len == 0 {
      element_done(self, List(~[]))
    }
    else {
      //vec::push(&mut self.stack, (len, 0));
      false
    }
  }

  fn on_data_beg(&mut self, len: uint) {
    error!("on_data_beg(len: %u)", len)
  }

  fn on_data(&mut self, data: &[u8]) {
    error!("on_data(%?)", data)
  }

  fn on_data_end(&mut self) -> bool {
    error!("on_data_end()");
    element_done(self, Data(~[1,2,3]))
  }

  fn on_integer(&mut self, num: uint, sign: bool) -> bool {
    error!("on_integer(%?, %?)", num, sign);
    element_done(self, Int(num as int)) // XXX
  }

  fn on_nil(&mut self) -> bool {
    error!("on_nil");
    element_done(self, Nil)
  }
}

fn parse_redis<'r, V: Visitor>(st: &mut RedisState, buf: &'r [u8], visitor: &mut V) -> (RedisResult, &'r [u8]) {
  let mut buf = buf;

  loop {
    match st.in {
      in_nothing => {
        if (buf.is_empty()) {break}

        let (c, b) = take_head(buf);
        buf = b;

        match c as char {
          '\r' | '\n' | ' ' =>  {
              error!("ignoring carriage return/new/whitespace line")
            }
          '*' => {
              st.typ = TyList;
              st.in = in_number;
              st.number = 0;
              st.negative_number = false;
            }
          '$' => {
              st.typ = TyData;
              st.in = in_number;
              st.number = 0;
              st.negative_number = false;
           }
          ':' => {
              st.typ = TyInt;
              st.in = in_number;
              st.number = 0;
              st.negative_number = false;
           }

           _ => {
              return (Error, buf) // XXX: return a slice including the character that failed
           }
        }

      }

      in_data => {
        if (buf.is_empty()) {break}
        let (data, b) = take_atmost_n(buf, st.data_size - st.data_have);
        buf = b;
        visitor.on_data(data);
        st.data_have += data.len();
        assert!(st.data_have <= st.data_size);
        if st.data_have == st.data_size {
          //XXX: st.in = in_data_need_newline;
          st.in = in_nothing;
          if visitor.on_data_end() {
            return (Finished, buf)
          }
        }
      }

      // we could treat the newline after data differently.
/*
      in_data_need_newline => {
        if (buf.is_empty()) {break}
        let (c, b) = take_head(buf);
        buf = b;
        if c == ('\n' as u8) {
          st.in = in_nothing;
        }
        else if c == ('\r' as u8) || c == (' ' as u8) {
          error("Consume whitespace");
        }
        else {
          return (Error, buf)
        }
      }
*/

      in_number_need_newline => {
        if (buf.is_empty()) {break}
        let (c, b) = take_head(buf);
        buf = b;
        if c == ('\n' as u8) {
          st.in = in_number_done;
        }
        else {
          return (Error, buf)
        }
      }

      // XXX: make a function instead of a STATE
      in_number_done => {
        match st.typ {
          TyData => {
            if st.negative_number {
              if st.number == 1 {
                st.in = in_nothing;
                if visitor.on_nil() {
                  return (Finished, buf)
                }
              }
              else {
                return (Error, buf)
              }
            }
            else {
              st.data_size = st.number;
              st.data_have = 0;
              st.in = in_data;
              visitor.on_data_beg(st.data_size);
            }
          }
          TyList => {
            // XXX:
            // push current recursion level and index on stack
            st.in = in_nothing;
            if st.negative_number {
              if (st.number == 1) {
                if visitor.on_nil() {
                  return (Finished, buf)
                }
              }
              else {
                return (Error, buf)
              }
            }
            else {
              if visitor.on_list(st.number) {
                return (Finished, buf)
              }
            }
          }

          TyInt => {
            st.in = in_nothing;
            if visitor.on_integer(st.number, st.negative_number) {
              return (Finished, buf)
            }
          }
          _ => {
            fail!(~"THIS SHOULD NEVER HAPPEN")
          }
        }
      }

      in_number | in_number_digits_only => {
        if (buf.is_empty()) {break}
        let (c, b) = take_head(buf);
        buf = b;

        if c >= ('0' as u8) && c <= ('9' as u8) {
          st.number *= 10;
          st.number += (c - ('0' as u8)) as uint;
          st.in = in_number_digits_only;
        }
        else if c as char == '-' {
          match st.in {
            in_number_digits_only => {
              return (Error, buf)
            }
            _ => {
              st.negative_number = true;
              st.in = in_number_digits_only;
            }
          }
        }
        else if c as char == '\r' || c as char == ' ' {
          st.in = in_number_need_newline;
        }
        else if c as char == '\n' {
          st.in = in_number_done;
        }
        else {
          return (Error, buf)
        }

      }
    }
  }
  (NeedMore, buf)
}

fn main() {
  let mut st = RedisState {
    in: in_nothing,
    typ: TyNone,
    data_size: 0,
    data_have: 0,
    number: 0,
    negative_number: false,
  };

  error!("%?", st);

  let mut visitor = MyVisitor {stack: ~[], result: Invalid};

  let s = ~"*4\r\n$3\r\nabc\r\n:123\n:1\n$-1\n";
  let slice = str::as_bytes_slice(s);
  let x = parse_redis(&mut st, slice, &mut visitor);
  error!("%?", st);
  error!("%?", x);
  error!("%?", visitor.result);
}
<|endoftext|>


<commit_before>#[feature(macro_rules)];

use std::result::{Result,Ok,Err};

#[deriving(Eq,Show)]
enum SmtpCommand<'a> {
  MAIL(&'a str),
  RCPT(&'a str),
  DATA,
}

#[deriving(Eq,Show)]
enum ParseError {
  SyntaxError(&'static str),
  InvalidLineEnding,
  UnknownCommand,
}

fn ascii_eq_ignore_case(a: &[u8], b: &[u8]) -> bool {
  if a.len() == b.len() {
    a.to_ascii().eq_ignore_case(b.to_ascii())
  }
  else {
    false
  }
}

fn parse_command<'a>(line: &'a[u8]) -> Result<SmtpCommand<'a>, ParseError>  {
    if line.len() < 2 {
      return Err(InvalidLineEnding);
    }

    let crlf = line.slice(line.len() - 2, line.len());
    if crlf != bytes!("\r\n") {
      return Err(InvalidLineEnding);
    }

    let line = line.initn(2); // drop off line ending

    let cmd = line.slice(0, 4);
    if ascii_eq_ignore_case(cmd, bytes!("MAIL")) {
        if ascii_eq_ignore_case(line.slice(4, 11), bytes!(" FROM:<")) {
            let addr = line.slice_from(11).init();
            let rem = line.slice_from(line.len() - 1);
            if rem == bytes!(">") {
                // XXX: Verify mail addr
                Ok(MAIL(std::str::from_utf8(addr).unwrap()))
            }
            else {
                Err(SyntaxError("Invalid MAIL command: Missing >"))
            }
        }
        else {
            Err(SyntaxError("Invalid MAIL command"))
        }
    }
    else if ascii_eq_ignore_case(cmd, bytes!("DATA")) {
      if line.slice_from(4).len() == 0 {
          Ok(DATA)
      }
      else {
          Err(SyntaxError("Invalid DATA command"))
      }
    }
    else {
      Err(UnknownCommand)
    }
}

macro_rules! assert_match(
    ($given:expr , $pattern:pat) => ({
        let given_val = $given;
        match given_val {
          $pattern => {}
          _ => {
             fail!("assertion failed: `value does not match pattern`");
          }
        }
    })
)

macro_rules! test_parse_command (
  ($str:expr, $pat:pat) => (
    match bytes!($str) {
      cmd => assert_match!(parse_command(cmd), $pat)
    }
  )
)

#[test]
fn test_commands() {
  //test_parse_command!("", Err(InvalidLineEnding));
  test_parse_command!("\r", Err(InvalidLineEnding));
  test_parse_command!("\n", Err(InvalidLineEnding));
  test_parse_command!("\n\r", Err(InvalidLineEnding));
  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>", Err(InvalidLineEnding));
  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>\r", Err(InvalidLineEnding));
  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>\n", Err(InvalidLineEnding));
  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>\n\r", Err(InvalidLineEnding));

  test_parse_command!("MAIL FROM:<mneumann@ntecs.de\r\n", Err(SyntaxError("Invalid MAIL command: Missing >")));

  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>\r\n", Ok(MAIL("mneumann@ntecs.de")));

  test_parse_command!("DATA\r\n", Ok(DATA));
  test_parse_command!("data\r\n", Ok(DATA));
  test_parse_command!("data test\r\n", Err(SyntaxError("Invalid DATA command")));
}

fn main() {
  let buf = bytes!("MAIL FROM:<mneumann@ntecs.de>\r\n");
  let cmd = parse_command(buf);
  println!("{}", cmd);
}
<commit_msg>Use SliceReader to simplify code<commit_after>#[feature(macro_rules)];

use std::result::{Result,Ok,Err};

#[deriving(Eq,Show)]
enum SmtpCommand<'a> {
  MAIL(&'a str),
  RCPT(&'a str),
  DATA,
}

#[deriving(Eq,Show)]
enum ParseError {
  SyntaxError(&'static str),
  InvalidLineEnding,
  UnknownCommand,
}

fn ascii_eq_ignore_case(a: &[u8], b: &[u8]) -> bool {
  if a.len() == b.len() {
    a.to_ascii().eq_ignore_case(b.to_ascii())
  }
  else {
    false
  }
}

struct SliceReader<'a> {
  data: &'a[u8]
}

impl<'a> SliceReader<'a> {

  fn new(data: &'a[u8]) -> SliceReader<'a> {
      SliceReader { data: data }
  }

  fn len(&self) -> uint { self.data.len() }
  fn is_empty(&self) -> bool { self.len() == 0 }
  fn data(&self) -> &'a[u8] { self.data }

  /// Remove `n` (but no more than len()) items from the back and return them.
  fn pop_back(&mut self, n: uint) -> &'a[u8] {
    if n > self.len() { debug!("pop_back(): n > len"); }
    let n = std::cmp::min(n, self.len());
    let (front, back) = self.split_at(self.len() - n);
    self.data = front;
    return back;
  }

  /// Remove `n` (but no more than len()) items from the front and return them.
  fn pop_front(&mut self, n: uint) -> &'a[u8] {
    if n > self.len() { debug!("pop_front(): n > len"); }
    let n = std::cmp::min(n, self.len());

    let (front, back) = self.split_at(n);
    self.data = back;
    return front;
  }

  fn pop_while(&mut self, cond: |u8| -> bool) -> &'a[u8] {
    let mut cnt = 0;
    for &b in self.data.iter() {
      if cond(b) {
        cnt += 1;
      } else {
        break;
      }
    }
    self.pop_front(cnt)
  }

  fn split_at(&self, pos: uint) -> (&'a[u8], &'a[u8]) {
    assert!(pos <= self.data.len());
    (self.data.slice(0, pos), self.data.slice(pos, self.data.len()))
  }
}

fn parse_command<'a>(line: &'a[u8]) -> Result<SmtpCommand<'a>, ParseError>  {
    let mut line = SliceReader::new(line);

    if line.len() < 2 {
      return Err(InvalidLineEnding);
    }

    let crlf = line.pop_back(2);
    if crlf != bytes!("\r\n") {
      return Err(InvalidLineEnding);
    }

    let cmd = line.pop_front(4);
    if ascii_eq_ignore_case(cmd, bytes!("MAIL")) {
        if line.pop_while(|b| b == (' ' as u8) ).len() == 0 {
            return Err(SyntaxError("Invalid MAIL command: Missing SP"));
        }

        if ascii_eq_ignore_case(line.pop_front(5), bytes!("FROM:")) {
            let addr = line.pop_while(|b| b != (' ' as u8));
            if line.is_empty() {
                // XXX: Verify mail addr
                Ok(MAIL(std::str::from_utf8(addr).unwrap()))
            }
            else {
                Err(SyntaxError("Invalid MAIL command"))
            }
        }
        else {
            Err(SyntaxError("Invalid MAIL command"))
        }
    }
    else if ascii_eq_ignore_case(cmd, bytes!("DATA")) {
      if line.is_empty() {
          Ok(DATA)
      }
      else {
          Err(SyntaxError("Invalid DATA command"))
      }
    }
    else {
      Err(UnknownCommand)
    }
}

macro_rules! assert_match(
    ($given:expr , $pattern:pat) => ({
        let given_val = $given;
        match given_val {
          $pattern => {}
          _ => {
             fail!("assertion failed: `value does not match pattern`");
          }
        }
    })
)

macro_rules! test_parse_command (
  ($str:expr, $pat:pat) => (
    match bytes!($str) {
      cmd => assert_match!(parse_command(cmd), $pat)
    }
  )
)

#[test]
fn test_commands() {
  //test_parse_command!("", Err(InvalidLineEnding));
  test_parse_command!("\r", Err(InvalidLineEnding));
  test_parse_command!("\n", Err(InvalidLineEnding));
  test_parse_command!("\n\r", Err(InvalidLineEnding));
  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>", Err(InvalidLineEnding));
  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>\r", Err(InvalidLineEnding));
  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>\n", Err(InvalidLineEnding));
  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>\n\r", Err(InvalidLineEnding));

  test_parse_command!("MAIL FROM:<mneumann@ntecs.de blah\r\n", Err(SyntaxError("Invalid MAIL command")));

  test_parse_command!("MAIL FROM:<mneumann@ntecs.de>\r\n", Ok(MAIL("<mneumann@ntecs.de>")));

  test_parse_command!("DATA\r\n", Ok(DATA));
  test_parse_command!("data\r\n", Ok(DATA));
  test_parse_command!("data test\r\n", Err(SyntaxError("Invalid DATA command")));
}

fn main() {
  let buf = bytes!("MAIL FROM:<mneumann@ntecs.de>\r\n");
  let cmd = parse_command(buf);
  println!("{}", cmd);
}
<|endoftext|>


<commit_before>// Copyright (C) 2019, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#[macro_use]
extern crate log;

use std::net::ToSocketAddrs;

use ring::rand::*;

const MAX_DATAGRAM_SIZE: usize = 1350;

const USAGE: &str = "Usage:
  http3-client [options] URL
  http3-client -h | --help

Options:
  --method METHOD          Use the given HTTP request method [default: GET].
  --body FILE              Send the given file as request body.
  --max-data BYTES         Connection-wide flow control limit [default: 10000000].
  --max-stream-data BYTES  Per-stream flow control limit [default: 1000000].
  --wire-version VERSION   The version number to send to the server [default: babababa].
  --no-verify              Don't verify server's certificate.
  --no-grease              Don't send GREASE.
  -H --header HEADER ...   Add a request header.
  -n --requests REQUESTS   Send the given number of identical requests [default: 1].
  -h --help                Show this screen.
";

fn main() {
    let mut buf = [0; 65535];
    let mut out = [0; MAX_DATAGRAM_SIZE];

    env_logger::builder()
        .default_format_timestamp_nanos(true)
        .init();

    let args = docopt::Docopt::new(USAGE)
        .and_then(|dopt| dopt.parse())
        .unwrap_or_else(|e| e.exit());

    let max_data = args.get_str("--max-data");
    let max_data = u64::from_str_radix(max_data, 10).unwrap();

    let max_stream_data = args.get_str("--max-stream-data");
    let max_stream_data = u64::from_str_radix(max_stream_data, 10).unwrap();

    let version = args.get_str("--wire-version");
    let version = u32::from_str_radix(version, 16).unwrap();

    let url = url::Url::parse(args.get_str("URL")).unwrap();

    // Request headers (can be multiple).
    let req_headers = args.get_vec("--header");

    let reqs_count = args.get_str("--requests");
    let reqs_count = u64::from_str_radix(reqs_count, 10).unwrap();

    let mut reqs_complete = 0;

    // Setup the event loop.
    let poll = mio::Poll::new().unwrap();
    let mut events = mio::Events::with_capacity(1024);

    // Resolve server address.
    let peer_addr = url.to_socket_addrs().unwrap().next().unwrap();

    // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the
    // server address. This is needed on macOS and BSD variants that don't
    // support binding to IN6ADDR_ANY for both v4 and v6.
    let bind_addr = match peer_addr {
        std::net::SocketAddr::V4(_) => "0.0.0.0:0",
        std::net::SocketAddr::V6(_) => "[::]:0",
    };

    // Create the UDP socket backing the QUIC connection, and register it with
    // the event loop.
    let socket = std::net::UdpSocket::bind(bind_addr).unwrap();
    socket.connect(peer_addr).unwrap();

    let socket = mio::net::UdpSocket::from_socket(socket).unwrap();
    poll.register(
        &socket,
        mio::Token(0),
        mio::Ready::readable(),
        mio::PollOpt::edge(),
    )
    .unwrap();

    // Create the configuration for the QUIC connection.
    let mut config = quiche::Config::new(version).unwrap();

    config.verify_peer(true);

    config
        .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)
        .unwrap();

    config.set_idle_timeout(5000);
    config.set_max_packet_size(MAX_DATAGRAM_SIZE as u64);
    config.set_initial_max_data(max_data);
    config.set_initial_max_stream_data_bidi_local(max_stream_data);
    config.set_initial_max_stream_data_bidi_remote(max_stream_data);
    config.set_initial_max_stream_data_uni(max_stream_data);
    config.set_initial_max_streams_bidi(100);
    config.set_initial_max_streams_uni(100);
    config.set_disable_active_migration(true);

    let mut http3_conn = None;

    if args.get_bool("--no-verify") {
        config.verify_peer(false);
    }

    if args.get_bool("--no-grease") {
        config.grease(false);
    }

    if std::env::var_os("SSLKEYLOGFILE").is_some() {
        config.log_keys();
    }

    // Generate a random source connection ID for the connection.
    let mut scid = [0; quiche::MAX_CONN_ID_LEN];
    SystemRandom::new().fill(&mut scid[..]).unwrap();

    // Create a QUIC connection and initiate handshake.
    let mut conn = quiche::connect(url.domain(), &scid, &mut config).unwrap();

    info!(
        "connecting to {:} from {:} with scid {}",
        peer_addr,
        socket.local_addr().unwrap(),
        hex_dump(&scid)
    );

    let write = conn.send(&mut out).expect("initial send failed");

    while let Err(e) = socket.send(&out[..write]) {
        if e.kind() == std::io::ErrorKind::WouldBlock {
            debug!("send() would block");
            continue;
        }

        panic!("send() failed: {:?}", e);
    }

    debug!("written {}", write);

    let req_start = std::time::Instant::now();

    loop {
        poll.poll(&mut events, conn.timeout()).unwrap();

        // Read incoming UDP packets from the socket and feed them to quiche,
        // until there are no more packets to read.
        'read: loop {
            // If the event loop reported no events, it means that the timeout
            // has expired, so handle it without attempting to read packets. We
            // will then proceed with the send loop.
            if events.is_empty() {
                debug!("timed out");

                conn.on_timeout();

                break 'read;
            }

            let len = match socket.recv(&mut buf) {
                Ok(v) => v,

                Err(e) => {
                    // There are no more UDP packets to read, so end the read
                    // loop.
                    if e.kind() == std::io::ErrorKind::WouldBlock {
                        debug!("recv() would block");
                        break 'read;
                    }

                    panic!("recv() failed: {:?}", e);
                },
            };

            debug!("got {} bytes", len);

            // Process potentially coalesced packets.
            let read = match conn.recv(&mut buf[..len]) {
                Ok(v) => v,

                Err(quiche::Error::Done) => {
                    debug!("done reading");
                    break;
                },

                Err(e) => {
                    error!("recv failed: {:?}", e);
                    break 'read;
                },
            };

            debug!("processed {} bytes", read);
        }

        if conn.is_closed() {
            info!("connection closed, {:?}", conn.stats());

            if reqs_complete != reqs_count {
                error!("connection timed out after {:?} and only completed {}/{} requests",
                       req_start.elapsed(), reqs_complete, reqs_count);
            }

            break;
        }

        // Create a new HTTP/3 connection and end an HTTP request as soon as
        // the QUIC connection is established.
        if conn.is_established() && http3_conn.is_none() {
            let h3_config = quiche::h3::Config::new().unwrap();

            let mut h3_conn =
                quiche::h3::Connection::with_transport(&mut conn, &h3_config)
                    .unwrap();

            let mut path = String::from(url.path());

            if let Some(query) = url.query() {
                path.push('?');
                path.push_str(query);
            }

            let mut req = vec![
                quiche::h3::Header::new(":method", args.get_str("--method")),
                quiche::h3::Header::new(":scheme", url.scheme()),
                quiche::h3::Header::new(":authority", url.host_str().unwrap()),
                quiche::h3::Header::new(":path", &path),
                quiche::h3::Header::new("user-agent", "quiche"),
            ];

            // Add custom headers to the request.
            for header in &req_headers {
                let header_split: Vec<&str> = header.splitn(2, ": ").collect();
                if header_split.len() != 2 {
                    panic!("malformed header provided - \"{}\"", header);
                }

                req.push(quiche::h3::Header::new(
                    header_split[0],
                    header_split[1],
                ));
            }

            let body = if args.get_bool("--body") {
                std::fs::read(args.get_str("--body")).ok()
            } else {
                None
            };

            if body.is_some() {
                req.push(quiche::h3::Header::new(
                    "content-length",
                    &body.as_ref().unwrap().len().to_string(),
                ));
            }

            for _ in 0..reqs_count {
                info!("sending HTTP request {:?}", req);

                let s =
                    match h3_conn.send_request(&mut conn, &req, body.is_none()) {
                        Ok(v) => v,

                        Err(e) => {
                            error!("failed to send request {:?}", e);
                            break;
                        },
                    };

                if let Some(body) = &body {
                    if let Err(e) = h3_conn.send_body(&mut conn, s, body, true) {
                        error!("failed to send request body {:?}", e);
                        break;
                    }
                }
            }

            http3_conn = Some(h3_conn);
        }

        if let Some(http3_conn) = &mut http3_conn {
            // Process HTTP/3 events.
            loop {
                match http3_conn.poll(&mut conn) {
                    Ok((stream_id, quiche::h3::Event::Headers(headers))) => {
                        info!(
                            "got response headers {:?} on stream id {}",
                            headers, stream_id
                        );
                    },

                    Ok((stream_id, quiche::h3::Event::Data)) => {
                        if let Ok(read) =
                            http3_conn.recv_body(&mut conn, stream_id, &mut buf)
                        {
                            debug!(
                                "got {} bytes of response data on stream {}",
                                read, stream_id
                            );

                            print!("{}", unsafe {
                                std::str::from_utf8_unchecked(&buf[..read])
                            });
                        }
                    },

                    Ok((_stream_id, quiche::h3::Event::Finished)) => {
                        reqs_complete += 1;

                        debug!(
                            "{}/{} responses received",
                            reqs_complete, reqs_count
                        );

                        if reqs_complete == reqs_count {
                            info!(
                                "{}/{} response(s) received in {:?}, closing...",
                                reqs_complete,
                                reqs_count,
                                req_start.elapsed()
                            );

                            match conn.close(true, 0x00, b"kthxbye") {
                                // Already closed.
                                Ok(_) | Err(quiche::Error::Done) => (),

                                Err(e) => panic!("error closing conn: {:?}", e),
                            }

                            break;
                        }
                    },

                    Err(quiche::h3::Error::Done) => {
                        break;
                    },

                    Err(e) => {
                        error!("HTTP/3 processing failed: {:?}", e);

                        break;
                    },
                }
            }
        }

        // Generate outgoing QUIC packets and send them on the UDP socket, until
        // quiche reports that there are no more packets to be sent.
        loop {
            let write = match conn.send(&mut out) {
                Ok(v) => v,

                Err(quiche::Error::Done) => {
                    debug!("done writing");
                    break;
                },

                Err(e) => {
                    error!("send failed: {:?}", e);

                    conn.close(false, 0x1, b"fail").ok();
                    break;
                },
            };

            if let Err(e) = socket.send(&out[..write]) {
                if e.kind() == std::io::ErrorKind::WouldBlock {
                    debug!("send() would block");
                    break;
                }

                panic!("send() failed: {:?}", e);
            }

            debug!("written {}", write);
        }

        if conn.is_closed() {
            info!("connection closed, {:?}", conn.stats());

            if reqs_complete != reqs_count {
                error!("connection timed out after {:?} and only completed {}/{} requests",
                       req_start.elapsed(), reqs_complete, reqs_count);
            }

            break;
        }
    }
}

fn hex_dump(buf: &[u8]) -> String {
    let vec: Vec<String> = buf.iter().map(|b| format!("{:02x}", b)).collect();

    vec.join("")
}
<commit_msg>examples/http3-client: try resending requests that failed due to stream limits<commit_after>// Copyright (C) 2019, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#[macro_use]
extern crate log;

use std::net::ToSocketAddrs;

use ring::rand::*;

const MAX_DATAGRAM_SIZE: usize = 1350;

const USAGE: &str = "Usage:
  http3-client [options] URL
  http3-client -h | --help

Options:
  --method METHOD          Use the given HTTP request method [default: GET].
  --body FILE              Send the given file as request body.
  --max-data BYTES         Connection-wide flow control limit [default: 10000000].
  --max-stream-data BYTES  Per-stream flow control limit [default: 1000000].
  --wire-version VERSION   The version number to send to the server [default: babababa].
  --no-verify              Don't verify server's certificate.
  --no-grease              Don't send GREASE.
  -H --header HEADER ...   Add a request header.
  -n --requests REQUESTS   Send the given number of identical requests [default: 1].
  -h --help                Show this screen.
";

fn main() {
    let mut buf = [0; 65535];
    let mut out = [0; MAX_DATAGRAM_SIZE];

    env_logger::builder()
        .default_format_timestamp_nanos(true)
        .init();

    let args = docopt::Docopt::new(USAGE)
        .and_then(|dopt| dopt.parse())
        .unwrap_or_else(|e| e.exit());

    let max_data = args.get_str("--max-data");
    let max_data = u64::from_str_radix(max_data, 10).unwrap();

    let max_stream_data = args.get_str("--max-stream-data");
    let max_stream_data = u64::from_str_radix(max_stream_data, 10).unwrap();

    let version = args.get_str("--wire-version");
    let version = u32::from_str_radix(version, 16).unwrap();

    let url = url::Url::parse(args.get_str("URL")).unwrap();

    // Request headers (can be multiple).
    let req_headers = args.get_vec("--header");

    let reqs_count = args.get_str("--requests");
    let reqs_count = u64::from_str_radix(reqs_count, 10).unwrap();

    let mut reqs_complete = 0;

    // Setup the event loop.
    let poll = mio::Poll::new().unwrap();
    let mut events = mio::Events::with_capacity(1024);

    // Resolve server address.
    let peer_addr = url.to_socket_addrs().unwrap().next().unwrap();

    // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the
    // server address. This is needed on macOS and BSD variants that don't
    // support binding to IN6ADDR_ANY for both v4 and v6.
    let bind_addr = match peer_addr {
        std::net::SocketAddr::V4(_) => "0.0.0.0:0",
        std::net::SocketAddr::V6(_) => "[::]:0",
    };

    // Create the UDP socket backing the QUIC connection, and register it with
    // the event loop.
    let socket = std::net::UdpSocket::bind(bind_addr).unwrap();
    socket.connect(peer_addr).unwrap();

    let socket = mio::net::UdpSocket::from_socket(socket).unwrap();
    poll.register(
        &socket,
        mio::Token(0),
        mio::Ready::readable(),
        mio::PollOpt::edge(),
    )
    .unwrap();

    // Create the configuration for the QUIC connection.
    let mut config = quiche::Config::new(version).unwrap();

    config.verify_peer(true);

    config
        .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)
        .unwrap();

    config.set_idle_timeout(5000);
    config.set_max_packet_size(MAX_DATAGRAM_SIZE as u64);
    config.set_initial_max_data(max_data);
    config.set_initial_max_stream_data_bidi_local(max_stream_data);
    config.set_initial_max_stream_data_bidi_remote(max_stream_data);
    config.set_initial_max_stream_data_uni(max_stream_data);
    config.set_initial_max_streams_bidi(100);
    config.set_initial_max_streams_uni(100);
    config.set_disable_active_migration(true);

    let mut http3_conn = None;

    if args.get_bool("--no-verify") {
        config.verify_peer(false);
    }

    if args.get_bool("--no-grease") {
        config.grease(false);
    }

    if std::env::var_os("SSLKEYLOGFILE").is_some() {
        config.log_keys();
    }

    // Generate a random source connection ID for the connection.
    let mut scid = [0; quiche::MAX_CONN_ID_LEN];
    SystemRandom::new().fill(&mut scid[..]).unwrap();

    // Create a QUIC connection and initiate handshake.
    let mut conn = quiche::connect(url.domain(), &scid, &mut config).unwrap();

    info!(
        "connecting to {:} from {:} with scid {}",
        peer_addr,
        socket.local_addr().unwrap(),
        hex_dump(&scid)
    );

    let write = conn.send(&mut out).expect("initial send failed");

    while let Err(e) = socket.send(&out[..write]) {
        if e.kind() == std::io::ErrorKind::WouldBlock {
            debug!("send() would block");
            continue;
        }

        panic!("send() failed: {:?}", e);
    }

    debug!("written {}", write);

    let h3_config = quiche::h3::Config::new().unwrap();

    // Prepare request.
    let mut path = String::from(url.path());

    if let Some(query) = url.query() {
        path.push('?');
        path.push_str(query);
    }

    let mut req = vec![
        quiche::h3::Header::new(":method", args.get_str("--method")),
        quiche::h3::Header::new(":scheme", url.scheme()),
        quiche::h3::Header::new(":authority", url.host_str().unwrap()),
        quiche::h3::Header::new(":path", &path),
        quiche::h3::Header::new("user-agent", "quiche"),
    ];

    // Add custom headers to the request.
    for header in &req_headers {
        let header_split: Vec<&str> = header.splitn(2, ": ").collect();
        if header_split.len() != 2 {
            panic!("malformed header provided - \"{}\"", header);
        }

        req.push(quiche::h3::Header::new(header_split[0], header_split[1]));
    }

    let body = if args.get_bool("--body") {
        std::fs::read(args.get_str("--body")).ok()
    } else {
        None
    };

    if body.is_some() {
        req.push(quiche::h3::Header::new(
            "content-length",
            &body.as_ref().unwrap().len().to_string(),
        ));
    }

    let mut reqs_sent = 0;

    let req_start = std::time::Instant::now();

    loop {
        poll.poll(&mut events, conn.timeout()).unwrap();

        // Read incoming UDP packets from the socket and feed them to quiche,
        // until there are no more packets to read.
        'read: loop {
            // If the event loop reported no events, it means that the timeout
            // has expired, so handle it without attempting to read packets. We
            // will then proceed with the send loop.
            if events.is_empty() {
                debug!("timed out");

                conn.on_timeout();

                break 'read;
            }

            let len = match socket.recv(&mut buf) {
                Ok(v) => v,

                Err(e) => {
                    // There are no more UDP packets to read, so end the read
                    // loop.
                    if e.kind() == std::io::ErrorKind::WouldBlock {
                        debug!("recv() would block");
                        break 'read;
                    }

                    panic!("recv() failed: {:?}", e);
                },
            };

            debug!("got {} bytes", len);

            // Process potentially coalesced packets.
            let read = match conn.recv(&mut buf[..len]) {
                Ok(v) => v,

                Err(quiche::Error::Done) => {
                    debug!("done reading");
                    break;
                },

                Err(e) => {
                    error!("recv failed: {:?}", e);
                    break 'read;
                },
            };

            debug!("processed {} bytes", read);
        }

        if conn.is_closed() {
            info!("connection closed, {:?}", conn.stats());

            if reqs_complete != reqs_count {
                error!("connection timed out after {:?} and only completed {}/{} requests",
                       req_start.elapsed(), reqs_complete, reqs_count);
            }

            break;
        }

        // Create a new HTTP/3 connection once the QUIC connection is established.
        if conn.is_established() && http3_conn.is_none() {
            http3_conn = Some(
                quiche::h3::Connection::with_transport(&mut conn, &h3_config)
                    .unwrap(),
            );
        }

        // Send HTTP requests once the QUIC connection is established, and until
        // all requests have been sent.
        if let Some(h3_conn) = &mut http3_conn {
            let mut reqs_done = 0;

            for _ in reqs_sent..reqs_count {
                info!("sending HTTP request {:?}", req);

                let s =
                    match h3_conn.send_request(&mut conn, &req, body.is_none()) {
                        Ok(v) => v,

                        Err(quiche::h3::Error::TransportError(
                            quiche::Error::StreamLimit,
                        )) => {
                            debug!("not enough stream credits, retry later...");
                            break;
                        },

                        Err(e) => {
                            error!("failed to send request {:?}", e);
                            break;
                        },
                    };

                if let Some(body) = &body {
                    if let Err(e) = h3_conn.send_body(&mut conn, s, body, true) {
                        error!("failed to send request body {:?}", e);
                        break;
                    }
                }

                reqs_done += 1;
            }

            reqs_sent += reqs_done;
        }

        if let Some(http3_conn) = &mut http3_conn {
            // Process HTTP/3 events.
            loop {
                match http3_conn.poll(&mut conn) {
                    Ok((stream_id, quiche::h3::Event::Headers(headers))) => {
                        info!(
                            "got response headers {:?} on stream id {}",
                            headers, stream_id
                        );
                    },

                    Ok((stream_id, quiche::h3::Event::Data)) => {
                        if let Ok(read) =
                            http3_conn.recv_body(&mut conn, stream_id, &mut buf)
                        {
                            debug!(
                                "got {} bytes of response data on stream {}",
                                read, stream_id
                            );

                            print!("{}", unsafe {
                                std::str::from_utf8_unchecked(&buf[..read])
                            });
                        }
                    },

                    Ok((_stream_id, quiche::h3::Event::Finished)) => {
                        reqs_complete += 1;

                        debug!(
                            "{}/{} responses received",
                            reqs_complete, reqs_count
                        );

                        if reqs_complete == reqs_count {
                            info!(
                                "{}/{} response(s) received in {:?}, closing...",
                                reqs_complete,
                                reqs_count,
                                req_start.elapsed()
                            );

                            match conn.close(true, 0x00, b"kthxbye") {
                                // Already closed.
                                Ok(_) | Err(quiche::Error::Done) => (),

                                Err(e) => panic!("error closing conn: {:?}", e),
                            }

                            break;
                        }
                    },

                    Err(quiche::h3::Error::Done) => {
                        break;
                    },

                    Err(e) => {
                        error!("HTTP/3 processing failed: {:?}", e);

                        break;
                    },
                }
            }
        }

        // Generate outgoing QUIC packets and send them on the UDP socket, until
        // quiche reports that there are no more packets to be sent.
        loop {
            let write = match conn.send(&mut out) {
                Ok(v) => v,

                Err(quiche::Error::Done) => {
                    debug!("done writing");
                    break;
                },

                Err(e) => {
                    error!("send failed: {:?}", e);

                    conn.close(false, 0x1, b"fail").ok();
                    break;
                },
            };

            if let Err(e) = socket.send(&out[..write]) {
                if e.kind() == std::io::ErrorKind::WouldBlock {
                    debug!("send() would block");
                    break;
                }

                panic!("send() failed: {:?}", e);
            }

            debug!("written {}", write);
        }

        if conn.is_closed() {
            info!("connection closed, {:?}", conn.stats());

            if reqs_complete != reqs_count {
                error!("connection timed out after {:?} and only completed {}/{} requests",
                       req_start.elapsed(), reqs_complete, reqs_count);
            }

            break;
        }
    }
}

fn hex_dump(buf: &[u8]) -> String {
    let vec: Vec<String> = buf.iter().map(|b| format!("{:02x}", b)).collect();

    vec.join("")
}
<|endoftext|>


<commit_before>extern crate intercom;
extern crate intercom_common;
mod os;
mod host;

use std::io::Write;

#[derive(Debug)]
pub enum BuildError {
    ParseError( String ),
    IoError( String, std::io::Error ),
    CommandError( String, String, String ),
}

impl BuildError {
    fn print_as_warning( &self, w : &mut Write ) -> Result<(), std::io::Error> {

        let msg : std::borrow::Cow<str> = match *self {
            BuildError::ParseError( ref m, .. ) => m.into(),
            BuildError::IoError( ref m, ref e ) => format!( "{}: {}", m, e ).into(),
            BuildError::CommandError( ref m, .. ) => m.into(),
        };

        writeln!( w, "cargo:warning={}", msg )?;

        match *self {
            BuildError::CommandError( _, ref stdout, ref stderr ) => {

                if stdout.len() > 0 {
                    writeln!( w, "cargo:warning=" )?;
                    writeln!( w, "cargo:warning=Program stdout:" )?;
                    writeln!( w, "cargo:warning=---------------" )?;
                    for line in stdout.split( '\n' ) {
                        writeln!( w, "cargo:warning={}", line )?;
                    }
                }

                if stderr.len() > 0 {
                    writeln!( w, "cargo:warning=" )?;
                    writeln!( w, "cargo:warning=Program stderr:" )?;
                    writeln!( w, "cargo:warning=---------------" )?;
                    for line in stderr.split( '\n' ) {
                        writeln!( w, "cargo:warning={}", line )?;
                    }
                }

                if stdout.len() == 0 && stderr.len() == 0 {
                    writeln!( w, "cargo:warning=(Command produced no output)" )?;
                }
            },
            _ => {}  // No extra lines.
        }

        Ok(())
    }
}

pub fn build( all_type_systems : bool ) {
    match os::build( all_type_systems ) {
        Ok(..) => {}
        Err( e ) => {
            e.print_as_warning( &mut std::io::stdout() )
                    .expect( "Cannot write to stdout" );
            println!( "cargo:warning=Some Intercom functionality might not be available" );
        }
    }
}
<commit_msg>Fix clippy issues<commit_after>extern crate intercom;
extern crate intercom_common;
mod os;
mod host;

use std::io::Write;

#[derive(Debug)]
pub enum BuildError {
    ParseError( String ),
    IoError( String, std::io::Error ),
    CommandError( String, String, String ),
}

impl BuildError {
    fn print_as_warning( &self, w : &mut Write ) -> Result<(), std::io::Error> {

        let msg : std::borrow::Cow<str> = match *self {
            BuildError::ParseError( ref m, .. ) => m.into(),
            BuildError::IoError( ref m, ref e ) => format!( "{}: {}", m, e ).into(),
            BuildError::CommandError( ref m, .. ) => m.into(),
        };

        writeln!( w, "cargo:warning={}", msg )?;

        // Handle all the errors that provide extra info.
        // (Only one currently, but we'll still want to structure this as a match)
        #[allow(clippy::single_match)]
        match *self {
            BuildError::CommandError( _, ref stdout, ref stderr ) => {
                if ! stdout.is_empty() {
                    writeln!( w, "cargo:warning=" )?;
                    writeln!( w, "cargo:warning=Program stdout:" )?;
                    writeln!( w, "cargo:warning=---------------" )?;
                    for line in stdout.split( '\n' ) {
                        writeln!( w, "cargo:warning={}", line )?;
                    }
                }

                if ! stderr.is_empty() {
                    writeln!( w, "cargo:warning=" )?;
                    writeln!( w, "cargo:warning=Program stderr:" )?;
                    writeln!( w, "cargo:warning=---------------" )?;
                    for line in stderr.split( '\n' ) {
                        writeln!( w, "cargo:warning={}", line )?;
                    }
                }

                // If the program didn't provide stdout or stderr, we'll want to
                // inform the user of that so they aren't left confused on why
                // our error messages are crap.
                if stdout.is_empty() && stderr.is_empty() {
                    writeln!( w, "cargo:warning=(Command produced no output)" )?;
                }
            },
            _ => {}  // No extra lines.
        }

        Ok(())
    }
}

pub fn build( all_type_systems : bool ) {
    match os::build( all_type_systems ) {
        Ok(..) => {}
        Err( e ) => {
            e.print_as_warning( &mut std::io::stdout() )
                    .expect( "Cannot write to stdout" );
            println!( "cargo:warning=Some Intercom functionality might not be available" );
        }
    }
}
<|endoftext|>


<commit_before>use super::*;

use std::{thread, time};
use std::collections::HashMap;
use std::env;
use types::ConfigSection;

#[test]
fn setup_cwd_empty() {
    env::set_var("CARGO_MAKE_WORKING_DIRECTORY", "EMPTY");

    setup_cwd(None);

    assert!(env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap() != "EMPTY");
}

#[test]
fn setup_env_empty() {
    let config = Config { config: ConfigSection::new(), env: HashMap::new(), tasks: HashMap::new() };

    setup_env(&config, "setup_env_empty1");

    let mut value = env::var("CARGO_MAKE_TASK");
    assert_eq!(value.unwrap(), "setup_env_empty1");

    setup_env(&config, "setup_env_empty2");

    let delay = time::Duration::from_millis(10);
    thread::sleep(delay);

    value = env::var("CARGO_MAKE_TASK");
    assert_eq!(value.unwrap(), "setup_env_empty2");
}

#[test]
fn setup_env_values() {
    let mut config = Config { config: ConfigSection::new(), env: HashMap::new(), tasks: HashMap::new() };
    config.env.insert("MY_ENV_KEY".to_string(), "MY_ENV_VALUE".to_string());
    config.env.insert("MY_ENV_KEY2".to_string(), "MY_ENV_VALUE2".to_string());

    assert_eq!(env::var("MY_ENV_KEY").unwrap_or("NONE".to_string()), "NONE".to_string());
    assert_eq!(env::var("MY_ENV_KEY2").unwrap_or("NONE".to_string()), "NONE".to_string());

    setup_env(&config, "set_env_values");

    assert_eq!(env::var("MY_ENV_KEY").unwrap(), "MY_ENV_VALUE");
    assert_eq!(env::var("MY_ENV_KEY2").unwrap(), "MY_ENV_VALUE2");
}

#[test]
fn setup_env_for_crate_load_toml_found() {
    env::set_var("CARGO_MAKE_CRATE_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_FS_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_VERSION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DESCRIPTION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_LICENSE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DOCUMENTATION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_HOMEPAGE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_REPOSITORY", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_IS_WORKSPACE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS", "EMPTY");

    setup_env_for_crate();

    assert_eq!(env::var("CARGO_MAKE_CRATE_NAME").unwrap(), "cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_FS_NAME").unwrap(), "cargo_make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_VERSION").unwrap(), env!("CARGO_PKG_VERSION"));
    assert_eq!(env::var("CARGO_MAKE_CRATE_DESCRIPTION").unwrap(), env!("CARGO_PKG_DESCRIPTION"));
    assert_eq!(env::var("CARGO_MAKE_CRATE_LICENSE").unwrap(), "Apache-2.0");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DOCUMENTATION").unwrap(), "https://sagiegurari.github.io/cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_HOMEPAGE").unwrap(), "https://sagiegurari.github.io/cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_REPOSITORY").unwrap(), "https://github.com/sagiegurari/cargo-make.git");
    assert_eq!(env::var("CARGO_MAKE_CRATE_IS_WORKSPACE").unwrap(), "FALSE");
    assert_eq!(env::var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap(), "");
}

#[test]
fn setup_env_for_crate_load_toml_not_found_and_cwd() {
    env::set_var("CARGO_MAKE_CRATE_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_FS_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_VERSION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DESCRIPTION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_LICENSE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DOCUMENTATION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_HOMEPAGE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_REPOSITORY", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_IS_WORKSPACE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS", "EMPTY");

    env::set_var("CARGO_MAKE_WORKING_DIRECTORY", "EMPTY");
    assert!(env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap() == "EMPTY");

    setup_cwd(Some("examples"));
    setup_env_for_crate();
    setup_cwd(Some(".."));

    assert!(env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap() != "EMPTY");

    assert_eq!(env::var("CARGO_MAKE_CRATE_NAME").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_FS_NAME").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_VERSION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DESCRIPTION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_LICENSE").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DOCUMENTATION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_HOMEPAGE").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_REPOSITORY").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_IS_WORKSPACE").unwrap(), "FALSE");
    assert_eq!(env::var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap(), "");

    setup_env_for_crate();

    assert_eq!(env::var("CARGO_MAKE_CRATE_NAME").unwrap(), "cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_FS_NAME").unwrap(), "cargo_make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_VERSION").unwrap(), env!("CARGO_PKG_VERSION"));
    assert_eq!(env::var("CARGO_MAKE_CRATE_DESCRIPTION").unwrap(), env!("CARGO_PKG_DESCRIPTION"));
    assert_eq!(env::var("CARGO_MAKE_CRATE_LICENSE").unwrap(), "Apache-2.0");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DOCUMENTATION").unwrap(), "https://sagiegurari.github.io/cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_HOMEPAGE").unwrap(), "https://sagiegurari.github.io/cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_REPOSITORY").unwrap(), "https://github.com/sagiegurari/cargo-make.git");
    assert_eq!(env::var("CARGO_MAKE_CRATE_IS_WORKSPACE").unwrap(), "FALSE");
    assert_eq!(env::var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap(), "");
}

#[test]
fn setup_env_for_crate_workspace() {
    env::set_var("CARGO_MAKE_CRATE_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_FS_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_VERSION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DESCRIPTION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_LICENSE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DOCUMENTATION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_HOMEPAGE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_REPOSITORY", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_IS_WORKSPACE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS", "EMPTY");

    setup_cwd(Some("examples/workspace"));
    setup_env_for_crate();
    setup_cwd(Some("../.."));

    assert_eq!(env::var("CARGO_MAKE_CRATE_NAME").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_FS_NAME").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_VERSION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DESCRIPTION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_LICENSE").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DOCUMENTATION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_HOMEPAGE").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_REPOSITORY").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_IS_WORKSPACE").unwrap(), "TRUE");
    assert_eq!(env::var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap(), "member1,member2");
}

#[test]
fn setup_env_for_git_repo_with_values() {
    env::set_var("CARGO_MAKE_GIT_BRANCH", "EMPTY");
    env::set_var("CARGO_MAKE_GIT_USER_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_GIT_USER_EMAIL", "EMPTY");

    let git_info = setup_env_for_git_repo();

    if git_info.branch.is_some() {
        assert_eq!(env::var("CARGO_MAKE_GIT_BRANCH").unwrap(), git_info.branch.unwrap());
    }
    if git_info.user_name.is_some() {
        assert_eq!(env::var("CARGO_MAKE_GIT_USER_NAME").unwrap(), git_info.user_name.unwrap());
    }
    if git_info.user_email.is_some() {
        assert_eq!(env::var("CARGO_MAKE_GIT_USER_EMAIL").unwrap(), git_info.user_email.unwrap());
    }
}

#[test]
fn setup_env_for_rust_simple_check() {
    env::set_var("CARGO_MAKE_RUST_VERSION", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_CHANNEL", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_ARCH", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_ENV", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_OS", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_POINTER_WIDTH", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_VENDOR", "EMPTY");

    assert!(env::var("CARGO_MAKE_RUST_VERSION").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_CHANNEL").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_ARCH").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_ENV").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_OS").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_POINTER_WIDTH").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_VENDOR").unwrap() == "EMPTY");

    setup_env_for_rust();

    assert!(env::var("CARGO_MAKE_RUST_VERSION").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_CHANNEL").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_ARCH").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_ENV").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_OS").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_POINTER_WIDTH").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_VENDOR").unwrap() != "EMPTY");
}
<commit_msg>more tests<commit_after>use super::*;

use std::{thread, time};
use std::collections::HashMap;
use std::env;
use types::ConfigSection;

#[test]
fn setup_cwd_empty() {
    env::set_var("CARGO_MAKE_WORKING_DIRECTORY", "EMPTY");

    setup_cwd(None);

    assert!(env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap() != "EMPTY");
}

#[test]
fn setup_env_empty() {
    let config = Config { config: ConfigSection::new(), env: HashMap::new(), tasks: HashMap::new() };

    setup_env(&config, "setup_env_empty1");

    let mut value = env::var("CARGO_MAKE_TASK");
    assert_eq!(value.unwrap(), "setup_env_empty1");

    setup_env(&config, "setup_env_empty2");

    let delay = time::Duration::from_millis(10);
    thread::sleep(delay);

    value = env::var("CARGO_MAKE_TASK");
    assert_eq!(value.unwrap(), "setup_env_empty2");
}

#[test]
fn setup_env_values() {
    let mut config = Config { config: ConfigSection::new(), env: HashMap::new(), tasks: HashMap::new() };
    config.env.insert("MY_ENV_KEY".to_string(), "MY_ENV_VALUE".to_string());
    config.env.insert("MY_ENV_KEY2".to_string(), "MY_ENV_VALUE2".to_string());

    assert_eq!(env::var("MY_ENV_KEY").unwrap_or("NONE".to_string()), "NONE".to_string());
    assert_eq!(env::var("MY_ENV_KEY2").unwrap_or("NONE".to_string()), "NONE".to_string());

    setup_env(&config, "set_env_values");

    assert_eq!(env::var("MY_ENV_KEY").unwrap(), "MY_ENV_VALUE");
    assert_eq!(env::var("MY_ENV_KEY2").unwrap(), "MY_ENV_VALUE2");
}

#[test]
fn setup_env_for_crate_load_toml_found() {
    env::set_var("CARGO_MAKE_CRATE_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_FS_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_VERSION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DESCRIPTION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_LICENSE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DOCUMENTATION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_HOMEPAGE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_REPOSITORY", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_IS_WORKSPACE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS", "EMPTY");

    setup_env_for_crate();

    assert_eq!(env::var("CARGO_MAKE_CRATE_NAME").unwrap(), "cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_FS_NAME").unwrap(), "cargo_make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_VERSION").unwrap(), env!("CARGO_PKG_VERSION"));
    assert_eq!(env::var("CARGO_MAKE_CRATE_DESCRIPTION").unwrap(), env!("CARGO_PKG_DESCRIPTION"));
    assert_eq!(env::var("CARGO_MAKE_CRATE_LICENSE").unwrap(), "Apache-2.0");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DOCUMENTATION").unwrap(), "https://sagiegurari.github.io/cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_HOMEPAGE").unwrap(), "https://sagiegurari.github.io/cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_REPOSITORY").unwrap(), "https://github.com/sagiegurari/cargo-make.git");
    assert_eq!(env::var("CARGO_MAKE_CRATE_IS_WORKSPACE").unwrap(), "FALSE");
    assert_eq!(env::var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap(), "");
}

#[test]
fn setup_env_for_crate_load_toml_not_found_and_cwd() {
    env::set_var("CARGO_MAKE_CRATE_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_FS_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_VERSION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DESCRIPTION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_LICENSE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DOCUMENTATION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_HOMEPAGE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_REPOSITORY", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_IS_WORKSPACE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS", "EMPTY");

    env::set_var("CARGO_MAKE_WORKING_DIRECTORY", "EMPTY");
    assert!(env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap() == "EMPTY");

    setup_cwd(Some("examples"));
    setup_env_for_crate();
    setup_cwd(Some(".."));

    assert!(env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap() != "EMPTY");

    assert_eq!(env::var("CARGO_MAKE_CRATE_NAME").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_FS_NAME").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_VERSION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DESCRIPTION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_LICENSE").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DOCUMENTATION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_HOMEPAGE").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_REPOSITORY").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_IS_WORKSPACE").unwrap(), "FALSE");
    assert_eq!(env::var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap(), "");

    setup_env_for_crate();

    assert_eq!(env::var("CARGO_MAKE_CRATE_NAME").unwrap(), "cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_FS_NAME").unwrap(), "cargo_make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_VERSION").unwrap(), env!("CARGO_PKG_VERSION"));
    assert_eq!(env::var("CARGO_MAKE_CRATE_DESCRIPTION").unwrap(), env!("CARGO_PKG_DESCRIPTION"));
    assert_eq!(env::var("CARGO_MAKE_CRATE_LICENSE").unwrap(), "Apache-2.0");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DOCUMENTATION").unwrap(), "https://sagiegurari.github.io/cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_HOMEPAGE").unwrap(), "https://sagiegurari.github.io/cargo-make");
    assert_eq!(env::var("CARGO_MAKE_CRATE_REPOSITORY").unwrap(), "https://github.com/sagiegurari/cargo-make.git");
    assert_eq!(env::var("CARGO_MAKE_CRATE_IS_WORKSPACE").unwrap(), "FALSE");
    assert_eq!(env::var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap(), "");
}

#[test]
fn setup_env_for_crate_workspace() {
    env::set_var("CARGO_MAKE_CRATE_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_FS_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_VERSION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DESCRIPTION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_LICENSE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_DOCUMENTATION", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_HOMEPAGE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_REPOSITORY", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_IS_WORKSPACE", "EMPTY");
    env::set_var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS", "EMPTY");

    setup_cwd(Some("examples/workspace"));
    setup_env_for_crate();
    setup_cwd(Some("../.."));

    assert_eq!(env::var("CARGO_MAKE_CRATE_NAME").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_FS_NAME").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_VERSION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DESCRIPTION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_LICENSE").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_DOCUMENTATION").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_HOMEPAGE").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_REPOSITORY").unwrap(), "EMPTY");
    assert_eq!(env::var("CARGO_MAKE_CRATE_IS_WORKSPACE").unwrap(), "TRUE");
    assert_eq!(env::var("CARGO_MAKE_CRATE_WORKSPACE_MEMBERS").unwrap(), "member1,member2");
}

#[test]
fn setup_env_for_git_repo_with_values() {
    env::set_var("CARGO_MAKE_GIT_BRANCH", "EMPTY");
    env::set_var("CARGO_MAKE_GIT_USER_NAME", "EMPTY");
    env::set_var("CARGO_MAKE_GIT_USER_EMAIL", "EMPTY");

    let git_info = setup_env_for_git_repo();

    if git_info.branch.is_some() {
        assert_eq!(env::var("CARGO_MAKE_GIT_BRANCH").unwrap(), git_info.branch.unwrap());
    }
    if git_info.user_name.is_some() {
        assert_eq!(env::var("CARGO_MAKE_GIT_USER_NAME").unwrap(), git_info.user_name.unwrap());
    }
    if git_info.user_email.is_some() {
        assert_eq!(env::var("CARGO_MAKE_GIT_USER_EMAIL").unwrap(), git_info.user_email.unwrap());
    }
}

#[test]
fn setup_env_for_rust_simple_check() {
    env::set_var("CARGO_MAKE_RUST_VERSION", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_CHANNEL", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_ARCH", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_ENV", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_OS", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_POINTER_WIDTH", "EMPTY");
    env::set_var("CARGO_MAKE_RUST_TARGET_VENDOR", "EMPTY");

    assert!(env::var("CARGO_MAKE_RUST_VERSION").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_CHANNEL").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_ARCH").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_ENV").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_OS").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_POINTER_WIDTH").unwrap() == "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_VENDOR").unwrap() == "EMPTY");

    setup_env_for_rust();

    assert!(env::var("CARGO_MAKE_RUST_VERSION").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_CHANNEL").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_ARCH").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_ENV").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_OS").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_POINTER_WIDTH").unwrap() != "EMPTY");
    assert!(env::var("CARGO_MAKE_RUST_TARGET_VENDOR").unwrap() != "EMPTY");
}

#[test]
fn is_env_defined_found() {
    env::set_var("ENV_VAR_FOUND_VALUE", "EMPTY");

    let found = is_env_defined("ENV_VAR_FOUND_VALUE");

    assert!(found);
}

#[test]
fn is_env_defined_empty() {
    env::set_var("ENV_VAR_FOUND_EMPTY", "");

    let found = is_env_defined("ENV_VAR_FOUND_EMPTY");

    assert!(found);
}

#[test]
fn is_env_defined_not_found() {
    let found = is_env_defined("ENV_VAR_NOT_FOUND");

    assert!(!found);
}
<|endoftext|>