<commit_before>// See LICENSE file for copyright and license details.

// Marauder is grid-less turn-based strategy game.

extern mod glfw;
extern mod gl;
extern mod cgmath;

use gltypes = gl::types;
use cgmath::matrix::{
  Matrix,
  Mat4,
  Mat3,
  ToMat4
};
use cgmath::vector::{
  Vec3,
  Vec2
};
use cgmath::projection;
use cgmath::angle;

#[link(name = "glfw")]
#[link(name = "m")]
extern {}

pub mod misc {
  use cgmath::matrix::Mat4;
  use std;

  pub fn print_mat4(name: &str, mat: Mat4<f32>) {
    println!("{}:", name);
    for i in range(0u, 4) {
      for j in range(0u, 4) {
        print!("{} ", *mat.cr(i, j));
      }
      println("");
    }
    println("");
  }

  /// usage: let s = mvp_matrix; println(type_of(&s));
  pub fn type_of<T>(_: &T) -> &'static str {
    unsafe {
      (*std::unstable::intrinsics::get_tydesc::<T>()).name
    }
  }
}

static mut MOUSE_POS: Vec2<f32> = Vec2{x: 0.0f32, y: 0.0};
static mut CAMERA_POS: Vec3<f32> = Vec3{x: 0.0f32, y: 0.0, z: 0.0};

static WIN_SIZE: Vec2<uint> = Vec2{x: 640, y: 480};
static VERTICES_COUNT: i32 = 3 * 2;

static VERTEX_DATA: [gltypes::GLfloat, ..VERTICES_COUNT * 3] = [
   0.0,  1.0, 0.0,
   2.0, -1.0, 0.0,
  -2.0, -1.0, 0.0,

  0.0,  1.0,  0.0,
  0.0, -1.0,  2.0,
  0.0, -1.0, -2.0
];

static VERTEX_SHADER_SRC: &'static str = "
  #version 130
  in vec3 position;
  uniform mat4 model_view_proj_matrix;
  void main() {
    vec4 v = vec4(position, 1);
    gl_Position = model_view_proj_matrix * v;
  }
";

static FRAGMENT_SHADER_SRC: &'static str = "
  #version 130
  out vec4 out_color;
  void main() {
    out_color = vec4(1.0, 1.0, 1.0, 1.0);
  }
";

fn compile_shader(src: &str, shader_type: gltypes::GLenum) -> gltypes::GLuint {
  let shader = gl::CreateShader(shader_type);
  unsafe {
    gl::ShaderSource(shader, 1, &src.to_c_str().unwrap(), std::ptr::null());
    gl::CompileShader(shader);

    let mut status = gl::FALSE as gltypes::GLint;
    gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

    if status != (gl::TRUE as gltypes::GLint) {
      let mut len = 0;
      gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
      // subtract 1 to skip the trailing null character
      let mut buf = std::vec::from_elem(len as uint - 1, 0u8);
      gl::GetShaderInfoLog(shader, len, std::ptr::mut_null(),
        std::vec::raw::to_mut_ptr(buf) as *mut gltypes::GLchar
      );
      fail!("compile_shader(): " + std::str::raw::from_utf8(buf));
    }
  }
  shader
}

fn link_program(
    vertex_shader: gltypes::GLuint,
    fragment_shader: gltypes::GLuint
) -> gltypes::GLuint {
  let program = gl::CreateProgram();
  gl::AttachShader(program, vertex_shader);
  gl::AttachShader(program, fragment_shader);
  gl::LinkProgram(program);
  unsafe {
    let mut status = gl::FALSE as gltypes::GLint;
    gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);

    if status != (gl::TRUE as gltypes::GLint) {
      let mut len: gltypes::GLint = 0;
      gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
      // subtract 1 to skip the trailing null character
      let mut buf = std::vec::from_elem(len as uint - 1, 0u8);
      gl::GetProgramInfoLog(program, len, std::ptr::mut_null(),
        std::vec::raw::to_mut_ptr(buf) as *mut gltypes::GLchar
      );
      fail!("link_program(): " + std::str::raw::from_utf8(buf));
    }
  }
  program
}

fn tr(m: Mat4<f32>, v: Vec3<f32>) -> Mat4<f32> {
  let mut t = Mat4::<f32>::identity();
  *t.mut_cr(3, 0) = v.x;
  *t.mut_cr(3, 1) = v.y;
  *t.mut_cr(3, 2) = v.z;
  m.mul_m(&t)
}

fn rot_x(m: Mat4<f32>, angle: f32) -> Mat4<f32> {
  let r = Mat3::from_angle_x(angle::rad(angle)).to_mat4();
  m.mul_m(&r)
}

fn rot_y(m: Mat4<f32>, angle: f32) -> Mat4<f32> {
  let r = Mat3::from_angle_y(angle::rad(angle)).to_mat4();
  m.mul_m(&r)
}

struct Win {
  vertex_shader: gltypes::GLuint,
  fragment_shader: gltypes::GLuint,
  program: gltypes::GLuint,
  vertex_array_obj: gltypes::GLuint,
  vertex_buffer_obj: gltypes::GLuint,
  matrix_id: gltypes::GLint,
  projection_matrix: Mat4<f32>,
  window: glfw::Window
}

fn get_projection_matrix() -> Mat4<f32> {
  let fov = angle::deg(45.0f32);
  let ratio = 4.0 / 3.0;
  let display_range_min = 0.1;
  let display_range_max = 100.0;
  projection::perspective(
    fov, ratio, display_range_min, display_range_max
  )
}

impl Win {
  fn new() -> Win {
    let mut win = Win{
      vertex_shader: 0,
      fragment_shader: 0,
      program: 0,
      vertex_array_obj: 0,
      vertex_buffer_obj: 0,
      matrix_id: 0,
      projection_matrix: get_projection_matrix(),
      window: glfw::Window::create(
        WIN_SIZE.x as u32,
        WIN_SIZE.y as u32,
        "OpenGL",
        glfw::Windowed
      ).unwrap()
    };
    win.init_opengl();
    win
  }

  fn init_opengl(&mut self) {
    // TODO: Move to opengl_init method
    // glfw::window_hint::context_version(3, 2);

    self.window.make_context_current();
    self.window.set_cursor_pos_callback(~CursorPosContext);
    self.window.set_key_callback(~KeyContext);

    // Load the OpenGL function pointers
    gl::load_with(glfw::get_proc_address);

    self.vertex_shader = compile_shader(
      VERTEX_SHADER_SRC, gl::VERTEX_SHADER);
    self.fragment_shader = compile_shader(
      FRAGMENT_SHADER_SRC, gl::FRAGMENT_SHADER);
    self.program = link_program(self.vertex_shader, self.fragment_shader);

    unsafe {
      // Create Vertex Array Object
      gl::GenVertexArrays(1, &mut self.vertex_array_obj);
      gl::BindVertexArray(self.vertex_array_obj);

      // Create a Vertex Buffer Object
      gl::GenBuffers(1, &mut self.vertex_buffer_obj);
      gl::BindBuffer(gl::ARRAY_BUFFER, self.vertex_buffer_obj);

      // Copy vertex data to VBO
      let float_size = std::mem::size_of::<gltypes::GLfloat>();
      let vertices_ptr = (VERTEX_DATA.len() * float_size) as gltypes::GLsizeiptr;
      gl::BufferData(
        gl::ARRAY_BUFFER,
        vertices_ptr,
        std::cast::transmute(&VERTEX_DATA[0]),
        gl::STATIC_DRAW
      );

      gl::UseProgram(self.program);
      gl::BindFragDataLocation(
        self.program, 0, "out_color".to_c_str().unwrap());

      // Specify the layout of the vertex data
      let pos_attr = gl::GetAttribLocation(
        self.program, "position".to_c_str().unwrap()) as gltypes::GLuint;
      gl::EnableVertexAttribArray(pos_attr);

      let size = 3;
      let normalized = gl::FALSE as gltypes::GLboolean;
      let stride = 0;
      gl::VertexAttribPointer(
        pos_attr,
        size,
        gl::FLOAT,
        normalized,
        stride,
        std::ptr::null()
      );

      self.matrix_id = gl::GetUniformLocation(
        self.program, "model_view_proj_matrix".to_c_str().unwrap()
      );
    }
  }

  fn cleanup(&self) {
    gl::DeleteProgram(self.program);
    gl::DeleteShader(self.fragment_shader);
    gl::DeleteShader(self.vertex_shader);
    unsafe {
      gl::DeleteBuffers(1, &self.vertex_buffer_obj);
      gl::DeleteVertexArrays(1, &self.vertex_array_obj);
    }
  }

  fn update_matrices(&self) {
    let mut mvp_matrix = self.projection_matrix;
    unsafe {
      mvp_matrix = tr(mvp_matrix, Vec3{x: 0.0f32, y: 0.0, z: -10.0f32});
      mvp_matrix = rot_x(mvp_matrix, MOUSE_POS.y / 100.0);
      mvp_matrix = rot_y(mvp_matrix, MOUSE_POS.x / 100.0);
      mvp_matrix = tr(mvp_matrix, CAMERA_POS);

      // Send our transformation to the currently bound shader,
      // in the "model_view_proj_matrix" uniform for each model
      // you render, since the model_view_proj_matrix will be
      // different (at least the M part).
      gl::UniformMatrix4fv(self.matrix_id, 1, gl::FALSE, mvp_matrix.cr(0, 0));
    }
  }

  fn draw(&self) {
    self.update_matrices();
    gl::ClearColor(0.3, 0.3, 0.3, 1.0);
    gl::Clear(gl::COLOR_BUFFER_BIT);
    gl::DrawArrays(gl::TRIANGLES, 0, VERTICES_COUNT);
    self.window.swap_buffers();
  }

  fn is_running(&self) -> bool {
    return !self.window.should_close()
  }

  fn process_events(&self) {
    glfw::poll_events();
  }
}

struct CursorPosContext;
impl glfw::CursorPosCallback for CursorPosContext {
  fn call(&self, w: &glfw::Window, xpos: f64, ypos: f64) {
    if w.get_mouse_button(glfw::MouseButtonRight) == glfw::Press {
      unsafe {
        MOUSE_POS.x = xpos as f32;
        MOUSE_POS.y = ypos as f32;
      }
    }
  }
}

struct ErrorContext;
impl glfw::ErrorCallback for ErrorContext {
  fn call(&self, _: glfw::Error, description: ~str) {
    println!("GLFW Error: {:s}", description);
  }
}

struct KeyContext;
impl glfw::KeyCallback for KeyContext {
  fn call(
    &self,
    window: &glfw::Window,
    key:    glfw::Key,
    _:      std::libc::c_int,
    action: glfw::Action,
    _:      glfw::Modifiers
  ) {
    let distance = 1.0;
    if action != glfw::Press {
      return;
    }
    match key {
      glfw::KeyEscape | glfw::KeyQ
                     => window.set_should_close(true),
      glfw::KeySpace => println!("space"),
      glfw::KeyUp    => unsafe { CAMERA_POS.y -= distance },
      glfw::KeyDown  => unsafe { CAMERA_POS.y += distance },
      glfw::KeyRight => unsafe { CAMERA_POS.x -= distance },
      glfw::KeyLeft  => unsafe { CAMERA_POS.x += distance },
      _ => {}
    }
  }
}

fn main() {
  glfw::set_error_callback(~ErrorContext);
  do glfw::start {
    let win =  Win::new();
    while win.is_running() {
      win.process_events();
      win.draw();
    }
    win.cleanup();
  }
}

#[start]
fn start(argc: int, argv: **u8) -> int {
  std::rt::start_on_main_thread(argc, argv, main)
}

// vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab:
<commit_msg>WIN_SIZE: Vec2<uint> -> Vec2<u32><commit_after>// See LICENSE file for copyright and license details.

// Marauder is grid-less turn-based strategy game.

extern mod glfw;
extern mod gl;
extern mod cgmath;

use gltypes = gl::types;
use cgmath::matrix::{
  Matrix,
  Mat4,
  Mat3,
  ToMat4
};
use cgmath::vector::{
  Vec3,
  Vec2
};
use cgmath::projection;
use cgmath::angle;

#[link(name = "glfw")]
#[link(name = "m")]
extern {}

pub mod misc {
  use cgmath::matrix::Mat4;
  use std;

  pub fn print_mat4(name: &str, mat: Mat4<f32>) {
    println!("{}:", name);
    for i in range(0u, 4) {
      for j in range(0u, 4) {
        print!("{} ", *mat.cr(i, j));
      }
      println("");
    }
    println("");
  }

  /// usage: let s = mvp_matrix; println(type_of(&s));
  pub fn type_of<T>(_: &T) -> &'static str {
    unsafe {
      (*std::unstable::intrinsics::get_tydesc::<T>()).name
    }
  }
}

static mut MOUSE_POS: Vec2<f32> = Vec2{x: 0.0f32, y: 0.0};
static mut CAMERA_POS: Vec3<f32> = Vec3{x: 0.0f32, y: 0.0, z: 0.0};

static WIN_SIZE: Vec2<u32> = Vec2{x: 640, y: 480};
static VERTICES_COUNT: i32 = 3 * 2;

static VERTEX_DATA: [gltypes::GLfloat, ..VERTICES_COUNT * 3] = [
   0.0,  1.0, 0.0,
   2.0, -1.0, 0.0,
  -2.0, -1.0, 0.0,

  0.0,  1.0,  0.0,
  0.0, -1.0,  2.0,
  0.0, -1.0, -2.0
];

static VERTEX_SHADER_SRC: &'static str = "
  #version 130
  in vec3 position;
  uniform mat4 model_view_proj_matrix;
  void main() {
    vec4 v = vec4(position, 1);
    gl_Position = model_view_proj_matrix * v;
  }
";

static FRAGMENT_SHADER_SRC: &'static str = "
  #version 130
  out vec4 out_color;
  void main() {
    out_color = vec4(1.0, 1.0, 1.0, 1.0);
  }
";

fn compile_shader(src: &str, shader_type: gltypes::GLenum) -> gltypes::GLuint {
  let shader = gl::CreateShader(shader_type);
  unsafe {
    gl::ShaderSource(shader, 1, &src.to_c_str().unwrap(), std::ptr::null());
    gl::CompileShader(shader);

    let mut status = gl::FALSE as gltypes::GLint;
    gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

    if status != (gl::TRUE as gltypes::GLint) {
      let mut len = 0;
      gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
      // subtract 1 to skip the trailing null character
      let mut buf = std::vec::from_elem(len as uint - 1, 0u8);
      gl::GetShaderInfoLog(shader, len, std::ptr::mut_null(),
        std::vec::raw::to_mut_ptr(buf) as *mut gltypes::GLchar
      );
      fail!("compile_shader(): " + std::str::raw::from_utf8(buf));
    }
  }
  shader
}

fn link_program(
    vertex_shader: gltypes::GLuint,
    fragment_shader: gltypes::GLuint
) -> gltypes::GLuint {
  let program = gl::CreateProgram();
  gl::AttachShader(program, vertex_shader);
  gl::AttachShader(program, fragment_shader);
  gl::LinkProgram(program);
  unsafe {
    let mut status = gl::FALSE as gltypes::GLint;
    gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);

    if status != (gl::TRUE as gltypes::GLint) {
      let mut len: gltypes::GLint = 0;
      gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
      // subtract 1 to skip the trailing null character
      let mut buf = std::vec::from_elem(len as uint - 1, 0u8);
      gl::GetProgramInfoLog(program, len, std::ptr::mut_null(),
        std::vec::raw::to_mut_ptr(buf) as *mut gltypes::GLchar
      );
      fail!("link_program(): " + std::str::raw::from_utf8(buf));
    }
  }
  program
}

fn tr(m: Mat4<f32>, v: Vec3<f32>) -> Mat4<f32> {
  let mut t = Mat4::<f32>::identity();
  *t.mut_cr(3, 0) = v.x;
  *t.mut_cr(3, 1) = v.y;
  *t.mut_cr(3, 2) = v.z;
  m.mul_m(&t)
}

fn rot_x(m: Mat4<f32>, angle: f32) -> Mat4<f32> {
  let r = Mat3::from_angle_x(angle::rad(angle)).to_mat4();
  m.mul_m(&r)
}

fn rot_y(m: Mat4<f32>, angle: f32) -> Mat4<f32> {
  let r = Mat3::from_angle_y(angle::rad(angle)).to_mat4();
  m.mul_m(&r)
}

struct Win {
  vertex_shader: gltypes::GLuint,
  fragment_shader: gltypes::GLuint,
  program: gltypes::GLuint,
  vertex_array_obj: gltypes::GLuint,
  vertex_buffer_obj: gltypes::GLuint,
  matrix_id: gltypes::GLint,
  projection_matrix: Mat4<f32>,
  window: glfw::Window
}

fn get_projection_matrix() -> Mat4<f32> {
  let fov = angle::deg(45.0f32);
  let ratio = 4.0 / 3.0;
  let display_range_min = 0.1;
  let display_range_max = 100.0;
  projection::perspective(
    fov, ratio, display_range_min, display_range_max
  )
}

impl Win {
  fn new() -> Win {
    let mut win = Win{
      vertex_shader: 0,
      fragment_shader: 0,
      program: 0,
      vertex_array_obj: 0,
      vertex_buffer_obj: 0,
      matrix_id: 0,
      projection_matrix: get_projection_matrix(),
      window: glfw::Window::create(
        WIN_SIZE.x,
        WIN_SIZE.y,
        "OpenGL",
        glfw::Windowed
      ).unwrap()
    };
    win.init_opengl();
    win
  }

  fn init_opengl(&mut self) {
    // TODO: Move to opengl_init method
    // glfw::window_hint::context_version(3, 2);

    self.window.make_context_current();
    self.window.set_cursor_pos_callback(~CursorPosContext);
    self.window.set_key_callback(~KeyContext);

    // Load the OpenGL function pointers
    gl::load_with(glfw::get_proc_address);

    self.vertex_shader = compile_shader(
      VERTEX_SHADER_SRC, gl::VERTEX_SHADER);
    self.fragment_shader = compile_shader(
      FRAGMENT_SHADER_SRC, gl::FRAGMENT_SHADER);
    self.program = link_program(self.vertex_shader, self.fragment_shader);

    unsafe {
      // Create Vertex Array Object
      gl::GenVertexArrays(1, &mut self.vertex_array_obj);
      gl::BindVertexArray(self.vertex_array_obj);

      // Create a Vertex Buffer Object
      gl::GenBuffers(1, &mut self.vertex_buffer_obj);
      gl::BindBuffer(gl::ARRAY_BUFFER, self.vertex_buffer_obj);

      // Copy vertex data to VBO
      let float_size = std::mem::size_of::<gltypes::GLfloat>();
      let vertices_ptr = (VERTEX_DATA.len() * float_size) as gltypes::GLsizeiptr;
      gl::BufferData(
        gl::ARRAY_BUFFER,
        vertices_ptr,
        std::cast::transmute(&VERTEX_DATA[0]),
        gl::STATIC_DRAW
      );

      gl::UseProgram(self.program);
      gl::BindFragDataLocation(
        self.program, 0, "out_color".to_c_str().unwrap());

      // Specify the layout of the vertex data
      let pos_attr = gl::GetAttribLocation(
        self.program, "position".to_c_str().unwrap()) as gltypes::GLuint;
      gl::EnableVertexAttribArray(pos_attr);

      let size = 3;
      let normalized = gl::FALSE as gltypes::GLboolean;
      let stride = 0;
      gl::VertexAttribPointer(
        pos_attr,
        size,
        gl::FLOAT,
        normalized,
        stride,
        std::ptr::null()
      );

      self.matrix_id = gl::GetUniformLocation(
        self.program, "model_view_proj_matrix".to_c_str().unwrap()
      );
    }
  }

  fn cleanup(&self) {
    gl::DeleteProgram(self.program);
    gl::DeleteShader(self.fragment_shader);
    gl::DeleteShader(self.vertex_shader);
    unsafe {
      gl::DeleteBuffers(1, &self.vertex_buffer_obj);
      gl::DeleteVertexArrays(1, &self.vertex_array_obj);
    }
  }

  fn update_matrices(&self) {
    let mut mvp_matrix = self.projection_matrix;
    unsafe {
      mvp_matrix = tr(mvp_matrix, Vec3{x: 0.0f32, y: 0.0, z: -10.0f32});
      mvp_matrix = rot_x(mvp_matrix, MOUSE_POS.y / 100.0);
      mvp_matrix = rot_y(mvp_matrix, MOUSE_POS.x / 100.0);
      mvp_matrix = tr(mvp_matrix, CAMERA_POS);

      // Send our transformation to the currently bound shader,
      // in the "model_view_proj_matrix" uniform for each model
      // you render, since the model_view_proj_matrix will be
      // different (at least the M part).
      gl::UniformMatrix4fv(self.matrix_id, 1, gl::FALSE, mvp_matrix.cr(0, 0));
    }
  }

  fn draw(&self) {
    self.update_matrices();
    gl::ClearColor(0.3, 0.3, 0.3, 1.0);
    gl::Clear(gl::COLOR_BUFFER_BIT);
    gl::DrawArrays(gl::TRIANGLES, 0, VERTICES_COUNT);
    self.window.swap_buffers();
  }

  fn is_running(&self) -> bool {
    return !self.window.should_close()
  }

  fn process_events(&self) {
    glfw::poll_events();
  }
}

struct CursorPosContext;
impl glfw::CursorPosCallback for CursorPosContext {
  fn call(&self, w: &glfw::Window, xpos: f64, ypos: f64) {
    if w.get_mouse_button(glfw::MouseButtonRight) == glfw::Press {
      unsafe {
        MOUSE_POS.x = xpos as f32;
        MOUSE_POS.y = ypos as f32;
      }
    }
  }
}

struct ErrorContext;
impl glfw::ErrorCallback for ErrorContext {
  fn call(&self, _: glfw::Error, description: ~str) {
    println!("GLFW Error: {:s}", description);
  }
}

struct KeyContext;
impl glfw::KeyCallback for KeyContext {
  fn call(
    &self,
    window: &glfw::Window,
    key:    glfw::Key,
    _:      std::libc::c_int,
    action: glfw::Action,
    _:      glfw::Modifiers
  ) {
    let distance = 1.0;
    if action != glfw::Press {
      return;
    }
    match key {
      glfw::KeyEscape | glfw::KeyQ
                     => window.set_should_close(true),
      glfw::KeySpace => println!("space"),
      glfw::KeyUp    => unsafe { CAMERA_POS.y -= distance },
      glfw::KeyDown  => unsafe { CAMERA_POS.y += distance },
      glfw::KeyRight => unsafe { CAMERA_POS.x -= distance },
      glfw::KeyLeft  => unsafe { CAMERA_POS.x += distance },
      _ => {}
    }
  }
}

fn main() {
  glfw::set_error_callback(~ErrorContext);
  do glfw::start {
    let win =  Win::new();
    while win.is_running() {
      win.process_events();
      win.draw();
    }
    win.cleanup();
  }
}

#[start]
fn start(argc: int, argv: **u8) -> int {
  std::rt::start_on_main_thread(argc, argv, main)
}

// vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab:
<|endoftext|>


<commit_before>use actix::Message;
use bytes::{BufMut, BytesMut};
use std::io::{Error, ErrorKind};
use tokio::codec::{Decoder, Encoder};

// 5 MiB limit
const MAX_MSG_LEN: u32 = 5_242_880;
// 512 bits should be way more than enough
const MAX_ID_LEN: u8 = 64;

#[derive(Clone, Debug, Message)]
pub struct Payload {
    pub id: BytesMut,
    pub msg: BytesMut,
}

#[derive(Default, Debug)]
pub struct Codec {
    id_len: u8,
    msg_len: u32,
}

impl Codec {
    pub fn new() -> Codec {
        Codec::default()
    }
}

impl Encoder for Codec {
    type Item = Payload;
    type Error = Error;

    fn encode(&mut self, item: Self::Item, buf: &mut BytesMut) -> Result<(), Error> {
        assert!(item.id.len() < usize::from(MAX_ID_LEN));
        assert!((item.msg.len() as u32) < MAX_MSG_LEN);
        buf.reserve(item.id.len() + item.msg.len() + 5);
        buf.put_u8(item.id.len() as u8);
        buf.put_u32_be(item.msg.len() as u32);
        buf.put(item.id);
        buf.put(item.msg);
        Ok(())
    }
}

impl Decoder for Codec {
    type Item = Payload;
    type Error = Error;

    fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Error> {
        if self.id_len == 0 && self.msg_len == 0 && buf.len() >= 5 {
            let buf = buf.split_to(5);
            self.id_len = buf[0];
            self.msg_len = u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]);
            if self.id_len > MAX_ID_LEN {
                return Err(Error::new(
                    ErrorKind::Other,
                    format!("id must be <={} bytes", MAX_ID_LEN),
                ));
            } else if self.msg_len > MAX_MSG_LEN {
                return Err(Error::new(
                    ErrorKind::Other,
                    format!("msg must be <={} bytes", MAX_MSG_LEN),
                ));
            }
        }
        if self.id_len != 0 && buf.len() >= usize::from(self.id_len) + (self.msg_len as usize) {
            let id_len = self.id_len;
            self.id_len = 0;

            let msg_len = self.msg_len;
            self.msg_len = 0;

            let id = buf.split_to(id_len as usize);
            let msg = buf.split_to(msg_len as usize);
            Ok(Some(Payload { id, msg }))
        } else {
            Ok(None)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::error::Error;

    macro_rules! assert_decode {
        ($codec:expr, $bytes:expr, $id:expr, $msg:expr) => {{
            let res = $codec.decode(&mut $bytes).unwrap();
            assert!(res.is_some(), "decoding failure, expected Ok");
            let res = res.expect("decoding failure, expected payload");
            assert_eq!(res.id, $id, "id mismatch");
            assert_eq!(res.msg, $msg, "msg mismatch");
        }};
    }

    #[test]
    fn test_max_id_len() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![MAX_ID_LEN + 1, 0, 0, 0, 0]);
        let err = codec.decode(&mut bytes).unwrap_err();
        assert_eq!(
            err.description(),
            format!("id must be <={} bytes", MAX_ID_LEN)
        );
    }

    #[test]
    fn test_max_msg_len() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![1, 255, 255, 255, 255]);
        let err = codec.decode(&mut bytes).unwrap_err();
        assert_eq!(
            err.description(),
            format!("msg must be <={} bytes", MAX_MSG_LEN)
        );
    }

    #[test]
    fn test_encode_full_frame() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::new();
        codec
            .encode(
                Payload {
                    id: vec![0, 1, 2, 3, 4, 5].into(),
                    msg: vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245].into(),
                },
                &mut bytes,
            )
            .unwrap();
        assert_eq!(
            bytes,
            vec![
                6, // ID len
                0, 0, 0, 11, // Msg len
                0, 1, 2, 3, 4, 5, // ID
                255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245 // Msg
            ]
        );
    }

    #[test]
    fn test_decode_full_frame() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![
            6, // ID len
            0, 0, 0, 11, // Msg len
            0, 1, 2, 3, 4, 5, // ID
            255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, // Msg
        ]);

        assert_decode!(
            codec,
            bytes,
            vec![0, 1, 2, 3, 4, 5],
            vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245]
        );
    }

    #[test]
    fn test_decode_multipart_frame() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![
            6, // ID len
            0, 0, 0, 11, // Msg len
            0, 1, 2, 3, 4, 5, // ID
            255, 254, 253, 252, 251, 250, 249, 248, // Msg
        ]);
        let res = codec.decode(&mut bytes).unwrap();
        assert!(res.is_none());

        bytes.reserve(3);
        bytes.put_slice(&vec![247, 246, 245]);
        assert_decode!(
            codec,
            bytes,
            vec![0, 1, 2, 3, 4, 5],
            vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245]
        );
    }

    #[test]
    fn test_decode_multiple_multipart_frames() {
        let mut codec = Codec::new();
        #[rustfmt::skip]
        let mut bytes = BytesMut::from(vec![
            6, // ID len
            0, 0, 0, 11, // Msg len
            0, 1, 2, 3, 4, 5, // ID
            255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, // Msg
            3, // ID len
        ]);
        assert_decode!(
            codec,
            bytes,
            vec![0, 1, 2, 3, 4, 5],
            vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245]
        );

        // Ensure the codec data is correct
        assert_eq!(bytes.len(), 1);
        assert_eq!(codec.id_len, 0);
        assert_eq!(codec.msg_len, 0);

        bytes.reserve(4);
        bytes.put_slice(&vec![0, 0, 0, 5]); // Write msg len
        let res = codec.decode(&mut bytes).unwrap();
        assert!(res.is_none());

        assert_eq!(bytes.len(), 0);
        assert_eq!(codec.id_len, 3);
        assert_eq!(codec.msg_len, 5);

        bytes.reserve(8);
        bytes.put_slice(&vec![
            1, 1, 1, // ID
            2, 2, 2, 2, 2, // Msg
        ]);
        assert_decode!(codec, bytes, vec![1, 1, 1], vec![2, 2, 2, 2, 2]);
    }

    #[test]
    fn test_decode_multiple_full_frames() {
        let mut codec = Codec::new();
        #[rustfmt::skip]
        let mut bytes = BytesMut::from(vec![
            // First payload
            6, // ID len
            0, 0, 0, 11, // Msg len
            0, 1, 2, 3, 4, 5, // ID
            255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, // Msg
            // Second payload
            3,
            0, 0, 0, 4,
            1, 1, 1,
            2, 2, 2, 2,
            // Third payload
            4,
            0, 0, 0, 5,
            3, 3, 3, 3,
            4, 4, 4, 4, 4
        ]);

        assert_decode!(
            codec,
            bytes,
            vec![0, 1, 2, 3, 4, 5],
            vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245]
        );

        assert_decode!(codec, bytes, vec![1, 1, 1], vec![2, 2, 2, 2]);

        assert_decode!(codec, bytes, vec![3, 3, 3, 3], vec![4, 4, 4, 4, 4]);

        assert_eq!(bytes.len(), 0);
        assert_eq!(codec.id_len, 0);
        assert_eq!(codec.msg_len, 0);
    }
}
<commit_msg>Test empty message frame decoding<commit_after>use actix::Message;
use bytes::{BufMut, BytesMut};
use std::io::{Error, ErrorKind};
use tokio::codec::{Decoder, Encoder};

// 5 MiB limit
const MAX_MSG_LEN: u32 = 5_242_880;
// 512 bits should be way more than enough
const MAX_ID_LEN: u8 = 64;

#[derive(Clone, Debug, Message)]
pub struct Payload {
    pub id: BytesMut,
    pub msg: BytesMut,
}

#[derive(Default, Debug)]
pub struct Codec {
    id_len: u8,
    msg_len: u32,
}

impl Codec {
    pub fn new() -> Codec {
        Codec::default()
    }
}

impl Encoder for Codec {
    type Item = Payload;
    type Error = Error;

    fn encode(&mut self, item: Self::Item, buf: &mut BytesMut) -> Result<(), Error> {
        assert!(item.id.len() < usize::from(MAX_ID_LEN));
        assert!((item.msg.len() as u32) < MAX_MSG_LEN);
        buf.reserve(item.id.len() + item.msg.len() + 5);
        buf.put_u8(item.id.len() as u8);
        buf.put_u32_be(item.msg.len() as u32);
        buf.put(item.id);
        buf.put(item.msg);
        Ok(())
    }
}

impl Decoder for Codec {
    type Item = Payload;
    type Error = Error;

    fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Error> {
        if self.id_len == 0 && self.msg_len == 0 && buf.len() >= 5 {
            let buf = buf.split_to(5);
            self.id_len = buf[0];
            self.msg_len = u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]);
            if self.id_len > MAX_ID_LEN {
                return Err(Error::new(
                    ErrorKind::Other,
                    format!("id must be <={} bytes", MAX_ID_LEN),
                ));
            } else if self.msg_len > MAX_MSG_LEN {
                return Err(Error::new(
                    ErrorKind::Other,
                    format!("msg must be <={} bytes", MAX_MSG_LEN),
                ));
            }
        }
        if self.id_len != 0 && buf.len() >= usize::from(self.id_len) + (self.msg_len as usize) {
            let id_len = self.id_len;
            self.id_len = 0;

            let msg_len = self.msg_len;
            self.msg_len = 0;

            let id = buf.split_to(id_len as usize);
            let msg = buf.split_to(msg_len as usize);
            Ok(Some(Payload { id, msg }))
        } else {
            Ok(None)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::error::Error;

    macro_rules! assert_decode {
        ($codec:expr, $bytes:expr, $id:expr, $msg:expr) => {{
            let res = $codec.decode(&mut $bytes).unwrap();
            assert!(res.is_some(), "decoding failure, expected Ok");
            let res = res.expect("decoding failure, expected payload");
            assert_eq!(res.id, $id, "id mismatch");
            assert_eq!(res.msg, $msg, "msg mismatch");
        }};
    }

    #[test]
    fn test_max_id_len() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![MAX_ID_LEN + 1, 0, 0, 0, 0]);
        let err = codec.decode(&mut bytes).unwrap_err();
        assert_eq!(
            err.description(),
            format!("id must be <={} bytes", MAX_ID_LEN)
        );
    }

    #[test]
    fn test_max_msg_len() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![1, 255, 255, 255, 255]);
        let err = codec.decode(&mut bytes).unwrap_err();
        assert_eq!(
            err.description(),
            format!("msg must be <={} bytes", MAX_MSG_LEN)
        );
    }

    #[test]
    fn test_encode_full_frame() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::new();
        codec
            .encode(
                Payload {
                    id: vec![0, 1, 2, 3, 4, 5].into(),
                    msg: vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245].into(),
                },
                &mut bytes,
            )
            .unwrap();
        assert_eq!(
            bytes,
            vec![
                6, // ID len
                0, 0, 0, 11, // Msg len
                0, 1, 2, 3, 4, 5, // ID
                255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245 // Msg
            ]
        );
    }

    #[test]
    fn test_decode_empty_msg_frame() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![
            4, // ID len
            0, 0, 0, 0, // Msg len
            0, 1, 2, 3, // ID
        ]);

        assert_decode!(codec, bytes, vec![0, 1, 2, 3], vec![]);
    }

    #[test]
    fn test_decode_full_frame() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![
            6, // ID len
            0, 0, 0, 11, // Msg len
            0, 1, 2, 3, 4, 5, // ID
            255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, // Msg
        ]);

        assert_decode!(
            codec,
            bytes,
            vec![0, 1, 2, 3, 4, 5],
            vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245]
        );
    }

    #[test]
    fn test_decode_multipart_frame() {
        let mut codec = Codec::new();
        let mut bytes = BytesMut::from(vec![
            6, // ID len
            0, 0, 0, 11, // Msg len
            0, 1, 2, 3, 4, 5, // ID
            255, 254, 253, 252, 251, 250, 249, 248, // Msg
        ]);
        let res = codec.decode(&mut bytes).unwrap();
        assert!(res.is_none());

        bytes.reserve(3);
        bytes.put_slice(&vec![247, 246, 245]);
        assert_decode!(
            codec,
            bytes,
            vec![0, 1, 2, 3, 4, 5],
            vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245]
        );
    }

    #[test]
    fn test_decode_multiple_multipart_frames() {
        let mut codec = Codec::new();
        #[rustfmt::skip]
        let mut bytes = BytesMut::from(vec![
            6, // ID len
            0, 0, 0, 11, // Msg len
            0, 1, 2, 3, 4, 5, // ID
            255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, // Msg
            3, // ID len
        ]);
        assert_decode!(
            codec,
            bytes,
            vec![0, 1, 2, 3, 4, 5],
            vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245]
        );

        // Ensure the codec data is correct
        assert_eq!(bytes.len(), 1);
        assert_eq!(codec.id_len, 0);
        assert_eq!(codec.msg_len, 0);

        bytes.reserve(4);
        bytes.put_slice(&vec![0, 0, 0, 5]); // Write msg len
        let res = codec.decode(&mut bytes).unwrap();
        assert!(res.is_none());

        assert_eq!(bytes.len(), 0);
        assert_eq!(codec.id_len, 3);
        assert_eq!(codec.msg_len, 5);

        bytes.reserve(8);
        bytes.put_slice(&vec![
            1, 1, 1, // ID
            2, 2, 2, 2, 2, // Msg
        ]);
        assert_decode!(codec, bytes, vec![1, 1, 1], vec![2, 2, 2, 2, 2]);
    }

    #[test]
    fn test_decode_multiple_full_frames() {
        let mut codec = Codec::new();
        #[rustfmt::skip]
        let mut bytes = BytesMut::from(vec![
            // First payload
            6, // ID len
            0, 0, 0, 11, // Msg len
            0, 1, 2, 3, 4, 5, // ID
            255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, // Msg
            // Second payload
            3,
            0, 0, 0, 4,
            1, 1, 1,
            2, 2, 2, 2,
            // Third payload
            4,
            0, 0, 0, 5,
            3, 3, 3, 3,
            4, 4, 4, 4, 4
        ]);

        assert_decode!(
            codec,
            bytes,
            vec![0, 1, 2, 3, 4, 5],
            vec![255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245]
        );

        assert_decode!(codec, bytes, vec![1, 1, 1], vec![2, 2, 2, 2]);

        assert_decode!(codec, bytes, vec![3, 3, 3, 3], vec![4, 4, 4, 4, 4]);

        assert_eq!(bytes.len(), 0);
        assert_eq!(codec.id_len, 0);
        assert_eq!(codec.msg_len, 0);
    }
}
<|endoftext|>


<commit_before>use std::fmt::Write as _;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR;
use std::process::Command;
use std::process::Stdio;
use std::str;

use protobuf::descriptor::field_descriptor_proto;
use protobuf::descriptor::DescriptorProto;
use protobuf::descriptor::EnumDescriptorProto;
use protobuf::descriptor::FieldDescriptorProto;
use protobuf::descriptor::FileDescriptorProto;
use protobuf::descriptor::FileDescriptorSet;
use protobuf::descriptor::MethodDescriptorProto;
use protobuf::descriptor::OneofDescriptorProto;
use protobuf::descriptor::ServiceDescriptorProto;
use protobuf::text_format::lexer::float::parse_protobuf_float;
use protobuf_codegen::Codegen;
use protobuf_parse::Parser;
use protobuf_test_common::build::copy_tests_v2_v3;
use protobuf_test_common::build::glob_simple;
use regex::Regex;

fn to_paths(iter: impl IntoIterator<Item = impl Into<String>>) -> Vec<PathBuf> {
    iter.into_iter()
        .map(|item| item.into().replace(MAIN_SEPARATOR, "/").into())
        .collect()
}

#[derive(Default, Debug)]
struct TestStats {
    passed: u32,
    passed_marked_skip: u32,
    skipped: u32,
    failed: u32,
}

fn normalize_generated_file(contents: &str) -> String {
    let parsed_by = Regex::new("^// \\.proto file is parsed by.*").unwrap();

    let mut r = String::new();
    let mut inside_descriptor_data = false;
    for line in contents.lines() {
        let line = if inside_descriptor_data {
            if line == "\";" {
                inside_descriptor_data = false;
                line.to_owned()
            } else {
                continue;
            }
        } else if line.starts_with("static file_descriptor_proto_data") {
            inside_descriptor_data = true;
            line.to_owned()
        } else {
            parsed_by.replace(line, "").into_owned()
        };

        writeln!(&mut r, "{}", line).unwrap();
    }

    // sanity check
    assert!(!inside_descriptor_data);

    r
}

fn normalize_generated_file_in_place(path: &Path) {
    let contents = fs::read_to_string(path).unwrap();
    let contents = normalize_generated_file(&contents);
    fs::write(path, &contents).unwrap();
}

fn print_diff(dir: &Path, a: &Path, b: &Path) {
    if cfg!(windows) {
        // likely we don't have `diff` command on Windows
        return;
    }

    // sanity check
    let mut a_full = dir.to_path_buf();
    a_full.push(a);
    assert!(a_full.exists());
    let mut b_full = dir.to_path_buf();
    b_full.push(b);
    assert!(b_full.exists());

    // Not using difference crate because it's slow for large files
    let output = Command::new("diff")
        .current_dir(dir)
        .arg("-u")
        .arg(a)
        .arg(b)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .unwrap();

    print!("{}", str::from_utf8(&output.stdout).unwrap());
    print!("{}", str::from_utf8(&output.stderr).unwrap());
}

fn normalize_descriptor_set(fds: &mut FileDescriptorSet) {
    for desc in &mut fds.file {
        normalize_file_descriptor(desc)
    }
}

fn normalize_file_descriptor(desc: &mut FileDescriptorProto) {
    if !desc.has_syntax() {
        desc.set_syntax("proto2".into())
    }
    for desc in &mut *desc.message_type {
        normalize_descriptor(desc)
    }
    for desc in &mut desc.enum_type {
        normalize_enum_descriptor(desc)
    }

    for desc in &mut desc.extension {
        desc.options.mut_or_default();
    }
    desc.options.mut_or_default();

    for service in &mut desc.service {
        normalize_service(service);
    }

    // for unittest_custom_options.proto where a custom option
    // is an extension. Probably nobody outside of Google uses it.
    desc.options
        .mut_or_default()
        .unknown_fields
        .remove(15478479);
}

fn normalize_enum_descriptor(desc: &mut EnumDescriptorProto) {
    desc.options.mut_or_default();

    for value in &mut desc.value {
        value.options.mut_or_default();
    }
}

fn normalize_oneof_descriptor(desc: &mut OneofDescriptorProto) {
    desc.options.mut_or_default();
}

fn normalize_descriptor(desc: &mut DescriptorProto) {
    for desc in &mut desc.nested_type {
        normalize_descriptor(desc);
    }
    for desc in &mut desc.enum_type {
        normalize_enum_descriptor(desc);
    }
    for desc in &mut desc.oneof_decl {
        normalize_oneof_descriptor(desc);
    }

    desc.options.mut_or_default();

    // group are not supported
    desc.options.mut_or_default().unknown_fields.remove(7636463);

    for field in &mut desc.field {
        normalize_field(field);
    }

    for ext in &mut desc.extension {
        ext.options.mut_or_default();
    }

    for ext in &mut desc.extension_range {
        // If ext range end exceeds max field number,
        // the actual upper limit does not matter.
        // protoc is not consistent in behavior thus flush
        // the value to some arbitrary compatible value.
        if ext.has_end() && ext.get_end() >= 0x20000000 {
            ext.set_end(0x20000000);
        }
    }
}

fn normalize_method(method: &mut MethodDescriptorProto) {
    method.options.mut_or_default();
}

fn normalize_service(service: &mut ServiceDescriptorProto) {
    for m in &mut service.method {
        normalize_method(m);
    }

    service.options.mut_or_default();
}

fn normalize_field(field: &mut FieldDescriptorProto) {
    field.options.mut_or_default();

    if field.has_default_value() {
        if field.get_field_type() == field_descriptor_proto::Type::TYPE_FLOAT {
            field.set_default_value(format!(
                "{}",
                parse_protobuf_float(field.get_default_value()).unwrap()
            ));
        }
    }
}

fn pretty_message<M: protobuf::Message>(message: &M) -> String {
    format!("{:#}", message)
}

fn descriptor_for_file<'a>(fds: &'a FileDescriptorSet, file_name: &str) -> &'a FileDescriptorProto {
    for file in &fds.file {
        if Path::new(file.get_name()).file_name().unwrap()
            == Path::new(file_name).file_name().unwrap()
        {
            return file;
        }
    }
    panic!(
        "file not found: {}; all files: {}",
        file_name,
        fds.file
            .iter()
            .map(|f| f.get_name())
            .collect::<Vec<_>>()
            .join(", ")
    );
}

fn test_diff_in<F>(root: &str, sources_dir: &str, include: &str, should_skip: F)
where
    F: Fn(&str) -> bool,
{
    let mut stats = TestStats {
        passed: 0,
        passed_marked_skip: 0,
        skipped: 0,
        failed: 0,
    };

    let mut include_root = Path::new(root).to_path_buf();
    include_root.push(include);

    let include_full = format!("{}/{}", root, include);
    let s_full = format!("{}/{}", root, sources_dir);

    let inputs_glob = format!("{}/*.proto*", s_full);
    let inputs = to_paths(glob_simple(&inputs_glob));
    assert!(!inputs.is_empty(), "glob is empty: {}", inputs_glob);
    let includes = to_paths(vec![include_full.as_str(), "../proto"]);

    let temp_dir = tempfile::Builder::new()
        .prefix("protobuf-codegen-identical-test")
        .tempdir()
        .unwrap();

    let protoc_dir = format!("{}/protoc", temp_dir.path().to_str().unwrap());
    let pure_dir = format!("{}/pure", temp_dir.path().to_str().unwrap());

    fs::create_dir(&protoc_dir).unwrap();
    fs::create_dir(&pure_dir).unwrap();

    Codegen::new()
        .protoc()
        .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap())
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&protoc_dir)
        .run()
        .unwrap();

    Codegen::new()
        .pure()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&pure_dir)
        .run()
        .unwrap();

    let mut protoc_descriptors = Parser::new()
        .protoc()
        .includes(&includes)
        .inputs(&inputs)
        .file_descriptor_set()
        .unwrap();
    let mut pure_descriptors = Parser::new()
        .pure()
        .includes(&includes)
        .inputs(&inputs)
        .file_descriptor_set()
        .unwrap();
    normalize_descriptor_set(&mut protoc_descriptors);
    normalize_descriptor_set(&mut pure_descriptors);

    for input in &inputs {
        let label = input.strip_prefix(root).unwrap().to_str().unwrap();
        let proto_file_name = input.strip_prefix(&include_root).unwrap().to_str().unwrap();
        let proto_name = input.file_name().unwrap().to_str().unwrap();
        let rs_name = protobuf_codegen::proto_name_to_rs(proto_name);
        let protoc_rs = format!("{}/{}", protoc_dir, rs_name);
        let pure_rs = format!("{}/{}", pure_dir, rs_name);

        normalize_generated_file_in_place(Path::new(&protoc_rs));
        normalize_generated_file_in_place(Path::new(&pure_rs));

        let protoc_rs_contents =
            fs::read_to_string(&protoc_rs).expect(&format!("while reading {}", protoc_rs));
        let pure_rs_contents =
            fs::read_to_string(&pure_rs).expect(&format!("while reading {}", pure_rs));

        let protoc_descriptor_for_file = descriptor_for_file(&protoc_descriptors, &proto_file_name);
        let pure_descriptor_for_file = descriptor_for_file(&pure_descriptors, &proto_file_name);

        let skip = should_skip(input.to_str().unwrap());
        if protoc_rs_contents == pure_rs_contents
            && protoc_descriptor_for_file == pure_descriptor_for_file
        {
            if !skip {
                stats.passed += 1;
                println!("{}: PASSED", label);
            } else {
                stats.passed_marked_skip += 1;
                println!("{}: PASSED BUT MARKED SKIP", label);
            }
        } else {
            if skip {
                stats.skipped += 1;
                println!("{} SKIPPED", label);
            } else {
                stats.failed += 1;
                println!("{} FAILED", label);
            }

            print_diff(
                temp_dir.path(),
                Path::new(&protoc_rs).strip_prefix(temp_dir.path()).unwrap(),
                Path::new(&pure_rs).strip_prefix(temp_dir.path()).unwrap(),
            );

            fs::write(
                format!("{}/{}.descriptors", protoc_dir, proto_name),
                pretty_message(protoc_descriptor_for_file),
            )
            .unwrap();
            fs::write(
                format!("{}/{}.descriptors", pure_dir, proto_name),
                pretty_message(pure_descriptor_for_file),
            )
            .unwrap();
            print_diff(
                temp_dir.path(),
                Path::new(&format!("protoc/{}.descriptors", proto_name)),
                Path::new(&format!("pure/{}.descriptors", proto_name)),
            );
        }
    }

    println!("{:?}", stats);
    assert!(stats.failed == 0, "at least one test failed");
}

fn should_skip_with_marker(path: &str) -> bool {
    fs::read_to_string(path)
        .unwrap()
        .contains("@skip-codegen-identical-test")
}

#[test]
fn common_v2() {
    test_diff_in(
        "../protobuf-test",
        "src/common/v2",
        "src/common/v2",
        should_skip_with_marker,
    );
}

#[test]
fn common_v3() {
    let common_v3_root = tempfile::Builder::new()
        .prefix("common-v3")
        .tempdir()
        .unwrap();
    fs::create_dir_all(format!(
        "{}/src/common/v3",
        common_v3_root.path().to_str().unwrap()
    ))
    .unwrap();

    copy_tests_v2_v3(
        "../protobuf-test/src/common/v2",
        &format!("{}/src/common/v3", common_v3_root.path().to_str().unwrap()),
    );

    test_diff_in(
        common_v3_root.path().to_str().unwrap(),
        "src/common/v3",
        "src/common/v3",
        should_skip_with_marker,
    );
}

#[test]
fn v2() {
    test_diff_in(
        "../protobuf-test",
        "src/v2",
        "src/v2",
        should_skip_with_marker,
    );
}

#[test]
fn v3() {
    test_diff_in(
        "../protobuf-test",
        "src/v3",
        "src/v3",
        should_skip_with_marker,
    );
}

#[test]
fn interop() {
    test_diff_in(
        "../protobuf-test",
        "../interop/cxx",
        "../interop/cxx",
        should_skip_with_marker,
    );
}

#[test]
fn google() {
    test_diff_in("../protobuf-test", "src/google/protobuf", "src", |_| false);
}
<commit_msg>On wrong output, print longer diff<commit_after>use std::fmt::Write as _;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR;
use std::process::Command;
use std::process::Stdio;
use std::str;

use protobuf::descriptor::field_descriptor_proto;
use protobuf::descriptor::DescriptorProto;
use protobuf::descriptor::EnumDescriptorProto;
use protobuf::descriptor::FieldDescriptorProto;
use protobuf::descriptor::FileDescriptorProto;
use protobuf::descriptor::FileDescriptorSet;
use protobuf::descriptor::MethodDescriptorProto;
use protobuf::descriptor::OneofDescriptorProto;
use protobuf::descriptor::ServiceDescriptorProto;
use protobuf::text_format::lexer::float::parse_protobuf_float;
use protobuf_codegen::Codegen;
use protobuf_parse::Parser;
use protobuf_test_common::build::copy_tests_v2_v3;
use protobuf_test_common::build::glob_simple;
use regex::Regex;

fn to_paths(iter: impl IntoIterator<Item = impl Into<String>>) -> Vec<PathBuf> {
    iter.into_iter()
        .map(|item| item.into().replace(MAIN_SEPARATOR, "/").into())
        .collect()
}

#[derive(Default, Debug)]
struct TestStats {
    passed: u32,
    passed_marked_skip: u32,
    skipped: u32,
    failed: u32,
}

fn normalize_generated_file(contents: &str) -> String {
    let parsed_by = Regex::new("^// \\.proto file is parsed by.*").unwrap();

    let mut r = String::new();
    let mut inside_descriptor_data = false;
    for line in contents.lines() {
        let line = if inside_descriptor_data {
            if line == "\";" {
                inside_descriptor_data = false;
                line.to_owned()
            } else {
                continue;
            }
        } else if line.starts_with("static file_descriptor_proto_data") {
            inside_descriptor_data = true;
            line.to_owned()
        } else {
            parsed_by.replace(line, "").into_owned()
        };

        writeln!(&mut r, "{}", line).unwrap();
    }

    // sanity check
    assert!(!inside_descriptor_data);

    r
}

fn normalize_generated_file_in_place(path: &Path) {
    let contents = fs::read_to_string(path).unwrap();
    let contents = normalize_generated_file(&contents);
    fs::write(path, &contents).unwrap();
}

fn print_diff(dir: &Path, a: &Path, b: &Path) {
    if cfg!(windows) {
        // likely we don't have `diff` command on Windows
        return;
    }

    // sanity check
    let mut a_full = dir.to_path_buf();
    a_full.push(a);
    assert!(a_full.exists());
    let mut b_full = dir.to_path_buf();
    b_full.push(b);
    assert!(b_full.exists());

    // Not using difference crate because it's slow for large files
    let output = Command::new("diff")
        .current_dir(dir)
        .arg("-U10")
        .arg(a)
        .arg(b)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .unwrap();

    print!("{}", str::from_utf8(&output.stdout).unwrap());
    print!("{}", str::from_utf8(&output.stderr).unwrap());
}

fn normalize_descriptor_set(fds: &mut FileDescriptorSet) {
    for desc in &mut fds.file {
        normalize_file_descriptor(desc)
    }
}

fn normalize_file_descriptor(desc: &mut FileDescriptorProto) {
    if !desc.has_syntax() {
        desc.set_syntax("proto2".into())
    }
    for desc in &mut *desc.message_type {
        normalize_descriptor(desc)
    }
    for desc in &mut desc.enum_type {
        normalize_enum_descriptor(desc)
    }

    for desc in &mut desc.extension {
        desc.options.mut_or_default();
    }
    desc.options.mut_or_default();

    for service in &mut desc.service {
        normalize_service(service);
    }

    // for unittest_custom_options.proto where a custom option
    // is an extension. Probably nobody outside of Google uses it.
    desc.options
        .mut_or_default()
        .unknown_fields
        .remove(15478479);
}

fn normalize_enum_descriptor(desc: &mut EnumDescriptorProto) {
    desc.options.mut_or_default();

    for value in &mut desc.value {
        value.options.mut_or_default();
    }
}

fn normalize_oneof_descriptor(desc: &mut OneofDescriptorProto) {
    desc.options.mut_or_default();
}

fn normalize_descriptor(desc: &mut DescriptorProto) {
    for desc in &mut desc.nested_type {
        normalize_descriptor(desc);
    }
    for desc in &mut desc.enum_type {
        normalize_enum_descriptor(desc);
    }
    for desc in &mut desc.oneof_decl {
        normalize_oneof_descriptor(desc);
    }

    desc.options.mut_or_default();

    // group are not supported
    desc.options.mut_or_default().unknown_fields.remove(7636463);

    for field in &mut desc.field {
        normalize_field(field);
    }

    for ext in &mut desc.extension {
        ext.options.mut_or_default();
    }

    for ext in &mut desc.extension_range {
        // If ext range end exceeds max field number,
        // the actual upper limit does not matter.
        // protoc is not consistent in behavior thus flush
        // the value to some arbitrary compatible value.
        if ext.has_end() && ext.get_end() >= 0x20000000 {
            ext.set_end(0x20000000);
        }
    }
}

fn normalize_method(method: &mut MethodDescriptorProto) {
    method.options.mut_or_default();
}

fn normalize_service(service: &mut ServiceDescriptorProto) {
    for m in &mut service.method {
        normalize_method(m);
    }

    service.options.mut_or_default();
}

fn normalize_field(field: &mut FieldDescriptorProto) {
    field.options.mut_or_default();

    if field.has_default_value() {
        if field.get_field_type() == field_descriptor_proto::Type::TYPE_FLOAT {
            field.set_default_value(format!(
                "{}",
                parse_protobuf_float(field.get_default_value()).unwrap()
            ));
        }
    }
}

fn pretty_message<M: protobuf::Message>(message: &M) -> String {
    format!("{:#}", message)
}

fn descriptor_for_file<'a>(fds: &'a FileDescriptorSet, file_name: &str) -> &'a FileDescriptorProto {
    for file in &fds.file {
        if Path::new(file.get_name()).file_name().unwrap()
            == Path::new(file_name).file_name().unwrap()
        {
            return file;
        }
    }
    panic!(
        "file not found: {}; all files: {}",
        file_name,
        fds.file
            .iter()
            .map(|f| f.get_name())
            .collect::<Vec<_>>()
            .join(", ")
    );
}

fn test_diff_in<F>(root: &str, sources_dir: &str, include: &str, should_skip: F)
where
    F: Fn(&str) -> bool,
{
    let mut stats = TestStats {
        passed: 0,
        passed_marked_skip: 0,
        skipped: 0,
        failed: 0,
    };

    let mut include_root = Path::new(root).to_path_buf();
    include_root.push(include);

    let include_full = format!("{}/{}", root, include);
    let s_full = format!("{}/{}", root, sources_dir);

    let inputs_glob = format!("{}/*.proto*", s_full);
    let inputs = to_paths(glob_simple(&inputs_glob));
    assert!(!inputs.is_empty(), "glob is empty: {}", inputs_glob);
    let includes = to_paths(vec![include_full.as_str(), "../proto"]);

    let temp_dir = tempfile::Builder::new()
        .prefix("protobuf-codegen-identical-test")
        .tempdir()
        .unwrap();

    let protoc_dir = format!("{}/protoc", temp_dir.path().to_str().unwrap());
    let pure_dir = format!("{}/pure", temp_dir.path().to_str().unwrap());

    fs::create_dir(&protoc_dir).unwrap();
    fs::create_dir(&pure_dir).unwrap();

    Codegen::new()
        .protoc()
        .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap())
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&protoc_dir)
        .run()
        .unwrap();

    Codegen::new()
        .pure()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&pure_dir)
        .run()
        .unwrap();

    let mut protoc_descriptors = Parser::new()
        .protoc()
        .includes(&includes)
        .inputs(&inputs)
        .file_descriptor_set()
        .unwrap();
    let mut pure_descriptors = Parser::new()
        .pure()
        .includes(&includes)
        .inputs(&inputs)
        .file_descriptor_set()
        .unwrap();
    normalize_descriptor_set(&mut protoc_descriptors);
    normalize_descriptor_set(&mut pure_descriptors);

    for input in &inputs {
        let label = input.strip_prefix(root).unwrap().to_str().unwrap();
        let proto_file_name = input.strip_prefix(&include_root).unwrap().to_str().unwrap();
        let proto_name = input.file_name().unwrap().to_str().unwrap();
        let rs_name = protobuf_codegen::proto_name_to_rs(proto_name);
        let protoc_rs = format!("{}/{}", protoc_dir, rs_name);
        let pure_rs = format!("{}/{}", pure_dir, rs_name);

        normalize_generated_file_in_place(Path::new(&protoc_rs));
        normalize_generated_file_in_place(Path::new(&pure_rs));

        let protoc_rs_contents =
            fs::read_to_string(&protoc_rs).expect(&format!("while reading {}", protoc_rs));
        let pure_rs_contents =
            fs::read_to_string(&pure_rs).expect(&format!("while reading {}", pure_rs));

        let protoc_descriptor_for_file = descriptor_for_file(&protoc_descriptors, &proto_file_name);
        let pure_descriptor_for_file = descriptor_for_file(&pure_descriptors, &proto_file_name);

        let skip = should_skip(input.to_str().unwrap());
        if protoc_rs_contents == pure_rs_contents
            && protoc_descriptor_for_file == pure_descriptor_for_file
        {
            if !skip {
                stats.passed += 1;
                println!("{}: PASSED", label);
            } else {
                stats.passed_marked_skip += 1;
                println!("{}: PASSED BUT MARKED SKIP", label);
            }
        } else {
            if skip {
                stats.skipped += 1;
                println!("{} SKIPPED", label);
            } else {
                stats.failed += 1;
                println!("{} FAILED", label);
            }

            print_diff(
                temp_dir.path(),
                Path::new(&protoc_rs).strip_prefix(temp_dir.path()).unwrap(),
                Path::new(&pure_rs).strip_prefix(temp_dir.path()).unwrap(),
            );

            fs::write(
                format!("{}/{}.descriptors", protoc_dir, proto_name),
                pretty_message(protoc_descriptor_for_file),
            )
            .unwrap();
            fs::write(
                format!("{}/{}.descriptors", pure_dir, proto_name),
                pretty_message(pure_descriptor_for_file),
            )
            .unwrap();
            print_diff(
                temp_dir.path(),
                Path::new(&format!("protoc/{}.descriptors", proto_name)),
                Path::new(&format!("pure/{}.descriptors", proto_name)),
            );
        }
    }

    println!("{:?}", stats);
    assert!(stats.failed == 0, "at least one test failed");
}

fn should_skip_with_marker(path: &str) -> bool {
    fs::read_to_string(path)
        .unwrap()
        .contains("@skip-codegen-identical-test")
}

#[test]
fn common_v2() {
    test_diff_in(
        "../protobuf-test",
        "src/common/v2",
        "src/common/v2",
        should_skip_with_marker,
    );
}

#[test]
fn common_v3() {
    let common_v3_root = tempfile::Builder::new()
        .prefix("common-v3")
        .tempdir()
        .unwrap();
    fs::create_dir_all(format!(
        "{}/src/common/v3",
        common_v3_root.path().to_str().unwrap()
    ))
    .unwrap();

    copy_tests_v2_v3(
        "../protobuf-test/src/common/v2",
        &format!("{}/src/common/v3", common_v3_root.path().to_str().unwrap()),
    );

    test_diff_in(
        common_v3_root.path().to_str().unwrap(),
        "src/common/v3",
        "src/common/v3",
        should_skip_with_marker,
    );
}

#[test]
fn v2() {
    test_diff_in(
        "../protobuf-test",
        "src/v2",
        "src/v2",
        should_skip_with_marker,
    );
}

#[test]
fn v3() {
    test_diff_in(
        "../protobuf-test",
        "src/v3",
        "src/v3",
        should_skip_with_marker,
    );
}

#[test]
fn interop() {
    test_diff_in(
        "../protobuf-test",
        "../interop/cxx",
        "../interop/cxx",
        should_skip_with_marker,
    );
}

#[test]
fn google() {
    test_diff_in("../protobuf-test", "src/google/protobuf", "src", |_| false);
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::collections::HashSet;
use std::collections::hash_map::RandomState;
use std::iter::FromIterator;
use std::path::Path;
use std::path::PathBuf;
use std::vec::Vec;

use devicemapper::consts::SECTOR_SIZE;
use devicemapper::DM;
use devicemapper::{DataBlocks, Sectors};
use devicemapper::LinearDev;
use devicemapper::{ThinPoolDev, ThinPoolStatus, ThinPoolWorkingStatus};
use time::now;
use uuid::Uuid;
use serde_json;

use engine::EngineError;
use engine::EngineResult;
use engine::ErrorEnum;
use engine::Filesystem;
use engine::Pool;
use engine::RenameAction;
use engine::engine::Redundancy;
use engine::strat_engine::device::wipe_sectors;
use consts::IEC::Mi;

use super::super::engine::{FilesystemUuid, HasName, HasUuid};
use super::super::structures::Table;

use super::serde_structs::{Isomorphism, PoolSave};
use super::blockdevmgr::BlockDevMgr;
use super::dmdevice::{FlexRole, ThinPoolRole, format_flex_name, format_thinpool_name};
use super::filesystem::{StratFilesystem, FilesystemStatus};
use super::metadata::MIN_MDA_SECTORS;

const DATA_BLOCK_SIZE: Sectors = Sectors(2048);
const META_LOWATER: u64 = 512;
const DATA_LOWATER: DataBlocks = DataBlocks(512);

const INITIAL_META_SIZE: Sectors = Sectors(16 * Mi / SECTOR_SIZE as u64);
const INITIAL_DATA_SIZE: Sectors = Sectors(512 * Mi / SECTOR_SIZE as u64);

#[derive(Debug)]
pub struct StratPool {
    name: String,
    pool_uuid: Uuid,
    pub block_devs: BlockDevMgr,
    pub filesystems: Table<StratFilesystem>,
    redundancy: Redundancy,
    thin_pool: ThinPoolDev,
}

impl StratPool {
    /// Initialize a Stratis Pool.
    /// 1. Initialize the block devices specified by paths.
    /// 2. Set up thinpool device to back filesystems.
    pub fn initialize(name: &str,
                      dm: &DM,
                      paths: &[&Path],
                      redundancy: Redundancy,
                      force: bool)
                      -> EngineResult<StratPool> {
        let pool_uuid = Uuid::new_v4();

        let mut block_mgr =
            try!(BlockDevMgr::initialize(&pool_uuid, paths, MIN_MDA_SECTORS, force));

        if block_mgr.avail_space() < StratPool::min_initial_size() {
            let avail_size = block_mgr.avail_space().bytes();
            try!(block_mgr.destroy_all());
            return Err(EngineError::Engine(ErrorEnum::Invalid,
                                           format!("Space on pool must be at least {} bytes, \
                                                   available space is only {} bytes",
                                                   StratPool::min_initial_size().bytes(),
                                                   avail_size)));

        }

        let meta_regions = block_mgr
            .alloc_space(INITIAL_META_SIZE)
            .expect("blockmgr must not fail, already checked for space");
        let device_name = format_flex_name(&pool_uuid, FlexRole::ThinMeta);
        let meta_dev = try!(LinearDev::new(&device_name, dm, meta_regions));

        // When constructing a thin-pool, Stratis reserves the first N
        // sectors on a block device by creating a linear device with a
        // starting offset. DM writes the super block in the first block.
        // DM requires this first block to be zeros when the meta data for
        // the thin-pool is initially created. If we don't zero the
        // superblock DM issue error messages because it triggers code paths
        // that are trying to re-adopt the device with the attributes that
        // have been passed.
        try!(wipe_sectors(&try!(meta_dev.devnode()), Sectors(0), INITIAL_META_SIZE));

        let data_regions = block_mgr
            .alloc_space(INITIAL_DATA_SIZE)
            .expect("blockmgr must not fail, already checked for space");
        let device_name = format_flex_name(&pool_uuid, FlexRole::ThinData);
        let data_dev = try!(LinearDev::new(&device_name, dm, data_regions));
        let length = try!(data_dev.size()).sectors();

        let device_name = format_thinpool_name(&pool_uuid, ThinPoolRole::Pool);
        // TODO Fix hard coded data blocksize and low water mark.
        let thinpool_dev = try!(ThinPoolDev::new(&device_name,
                                                 dm,
                                                 length,
                                                 DATA_BLOCK_SIZE,
                                                 DataBlocks(256000),
                                                 meta_dev,
                                                 data_dev));

        let mut pool = StratPool {
            name: name.to_owned(),
            pool_uuid: pool_uuid,
            block_devs: block_mgr,
            filesystems: Table::new(),
            redundancy: redundancy,
            thin_pool: thinpool_dev,
        };

        try!(pool.write_metadata());

        Ok(pool)
    }

    /// Minimum initial size for a pool.
    pub fn min_initial_size() -> Sectors {
        INITIAL_META_SIZE + INITIAL_DATA_SIZE
    }

    // TODO: Check current time against global last updated, and use
    // alternate time value if earlier, as described in SWDD
    pub fn write_metadata(&mut self) -> EngineResult<()> {
        let data = try!(serde_json::to_string(&self.to_save()));
        self.block_devs
            .save_state(&now().to_timespec(), data.as_bytes())
    }

    pub fn check(&mut self) -> () {
        let dm = DM::new().expect("Could not get DM handle");

        let result = match self.thin_pool.status(&dm) {
            Ok(r) => r,
            Err(_) => {
                error!("Could not get thinpool status");
                // TODO: Take pool offline?
                return;
            }
        };

        match result {
            ThinPoolStatus::Good(wstatus, usage) => {
                match wstatus {
                    ThinPoolWorkingStatus::Good => {}
                    ThinPoolWorkingStatus::ReadOnly => {
                        // TODO: why is pool r/o and how do we get it
                        // rw again?
                    }
                    ThinPoolWorkingStatus::OutOfSpace => {
                        // TODO: Add more space if possible, or
                        // prevent further usage
                        // Should never happen -- we should be extending first!
                    }
                    ThinPoolWorkingStatus::NeedsCheck => {
                        // TODO: Take pool offline?
                        // TODO: run thin_check
                    }
                }

                if usage.used_meta > usage.total_meta - META_LOWATER {
                    // TODO: Extend meta device
                }

                if usage.used_data > usage.total_data - DATA_LOWATER {
                    // TODO: Extend data device
                }
            }
            ThinPoolStatus::Fail => {
                // TODO: Take pool offline?
                // TODO: Run thin_check
            }
        }

        for fs in &mut self.filesystems {
            match fs.check(&dm) {
                Ok(f_status) => {
                    if let FilesystemStatus::Failed = f_status {
                        // TODO: recover fs? (how?)
                    }
                }
                Err(_e) => error!("fs.check() failed"),
            }
        }
    }

    /// Teardown a pool.
    /// Take down the device mapper devices belonging to the pool.
    /// This method and destroy() must keep their DM teardown operations
    /// in sync.
    pub fn teardown(self) -> EngineResult<()> {
        // TODO: any necessary clean up of filesystems
        if !self.filesystems.is_empty() {
            return Err(EngineError::Engine(ErrorEnum::Busy,
                                           format!("May be unsynced files on device.")));
        }
        let dm = try!(DM::new());
        try!(self.thin_pool.teardown(&dm));
        Ok(())
    }
}

impl Pool for StratPool {
    fn create_filesystems<'a, 'b>(&'a mut self,
                                  specs: &[&'b str])
                                  -> EngineResult<Vec<(&'b str, FilesystemUuid)>> {
        let names: HashSet<_, RandomState> = HashSet::from_iter(specs);
        for name in names.iter() {
            if self.filesystems.contains_name(name) {
                return Err(EngineError::Engine(ErrorEnum::AlreadyExists, name.to_string()));
            }
        }

        let pool_uuid = &self.pool_uuid;
        let mut result = Vec::new();
        for name in names.iter() {
            let uuid = Uuid::new_v4();
            let dm = try!(DM::new());
            let new_filesystem =
                try!(StratFilesystem::initialize(pool_uuid, uuid, name, &dm, &mut self.thin_pool));
            self.filesystems.insert(new_filesystem);
            result.push((**name, uuid));
        }

        Ok(result)
    }

    fn add_blockdevs(&mut self, paths: &[&Path], force: bool) -> EngineResult<Vec<PathBuf>> {
        let bdev_paths = try!(self.block_devs.add(&self.pool_uuid, paths, force));
        try!(self.write_metadata());
        Ok(bdev_paths)
    }

    fn destroy(self) -> EngineResult<()> {
        // Ensure that DM teardown operations in this method are in sync
        // with operations in teardown().
        let dm = try!(DM::new());
        try!(self.thin_pool.teardown(&dm));
        try!(self.block_devs.destroy_all());

        Ok(())
    }

    fn destroy_filesystems<'a, 'b>(&'a mut self,
                                   fs_uuids: &[&'b FilesystemUuid])
                                   -> EngineResult<Vec<&'b FilesystemUuid>> {
        destroy_filesystems!{self; fs_uuids}
    }

    fn rename_filesystem(&mut self,
                         uuid: &FilesystemUuid,
                         new_name: &str)
                         -> EngineResult<RenameAction> {
        rename_filesystem!{self; uuid; new_name}
    }

    fn rename(&mut self, name: &str) {
        self.name = name.to_owned();
    }

    fn get_filesystem(&mut self, uuid: &FilesystemUuid) -> Option<&mut Filesystem> {
        get_filesystem!(self; uuid)
    }
}

impl HasUuid for StratPool {
    fn uuid(&self) -> &FilesystemUuid {
        &self.pool_uuid
    }
}

impl HasName for StratPool {
    fn name(&self) -> &str {
        &self.name
    }
}

impl Isomorphism<PoolSave> for StratPool {
    fn to_save(&self) -> PoolSave {
        PoolSave { name: self.name.clone() }
    }
}
<commit_msg>Add a TODO about rolling back on filesystem initialization failure<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::collections::HashSet;
use std::collections::hash_map::RandomState;
use std::iter::FromIterator;
use std::path::Path;
use std::path::PathBuf;
use std::vec::Vec;

use devicemapper::consts::SECTOR_SIZE;
use devicemapper::DM;
use devicemapper::{DataBlocks, Sectors};
use devicemapper::LinearDev;
use devicemapper::{ThinPoolDev, ThinPoolStatus, ThinPoolWorkingStatus};
use time::now;
use uuid::Uuid;
use serde_json;

use engine::EngineError;
use engine::EngineResult;
use engine::ErrorEnum;
use engine::Filesystem;
use engine::Pool;
use engine::RenameAction;
use engine::engine::Redundancy;
use engine::strat_engine::device::wipe_sectors;
use consts::IEC::Mi;

use super::super::engine::{FilesystemUuid, HasName, HasUuid};
use super::super::structures::Table;

use super::serde_structs::{Isomorphism, PoolSave};
use super::blockdevmgr::BlockDevMgr;
use super::dmdevice::{FlexRole, ThinPoolRole, format_flex_name, format_thinpool_name};
use super::filesystem::{StratFilesystem, FilesystemStatus};
use super::metadata::MIN_MDA_SECTORS;

const DATA_BLOCK_SIZE: Sectors = Sectors(2048);
const META_LOWATER: u64 = 512;
const DATA_LOWATER: DataBlocks = DataBlocks(512);

const INITIAL_META_SIZE: Sectors = Sectors(16 * Mi / SECTOR_SIZE as u64);
const INITIAL_DATA_SIZE: Sectors = Sectors(512 * Mi / SECTOR_SIZE as u64);

#[derive(Debug)]
pub struct StratPool {
    name: String,
    pool_uuid: Uuid,
    pub block_devs: BlockDevMgr,
    pub filesystems: Table<StratFilesystem>,
    redundancy: Redundancy,
    thin_pool: ThinPoolDev,
}

impl StratPool {
    /// Initialize a Stratis Pool.
    /// 1. Initialize the block devices specified by paths.
    /// 2. Set up thinpool device to back filesystems.
    pub fn initialize(name: &str,
                      dm: &DM,
                      paths: &[&Path],
                      redundancy: Redundancy,
                      force: bool)
                      -> EngineResult<StratPool> {
        let pool_uuid = Uuid::new_v4();

        let mut block_mgr =
            try!(BlockDevMgr::initialize(&pool_uuid, paths, MIN_MDA_SECTORS, force));

        if block_mgr.avail_space() < StratPool::min_initial_size() {
            let avail_size = block_mgr.avail_space().bytes();
            try!(block_mgr.destroy_all());
            return Err(EngineError::Engine(ErrorEnum::Invalid,
                                           format!("Space on pool must be at least {} bytes, \
                                                   available space is only {} bytes",
                                                   StratPool::min_initial_size().bytes(),
                                                   avail_size)));

        }

        let meta_regions = block_mgr
            .alloc_space(INITIAL_META_SIZE)
            .expect("blockmgr must not fail, already checked for space");
        let device_name = format_flex_name(&pool_uuid, FlexRole::ThinMeta);
        let meta_dev = try!(LinearDev::new(&device_name, dm, meta_regions));

        // When constructing a thin-pool, Stratis reserves the first N
        // sectors on a block device by creating a linear device with a
        // starting offset. DM writes the super block in the first block.
        // DM requires this first block to be zeros when the meta data for
        // the thin-pool is initially created. If we don't zero the
        // superblock DM issue error messages because it triggers code paths
        // that are trying to re-adopt the device with the attributes that
        // have been passed.
        try!(wipe_sectors(&try!(meta_dev.devnode()), Sectors(0), INITIAL_META_SIZE));

        let data_regions = block_mgr
            .alloc_space(INITIAL_DATA_SIZE)
            .expect("blockmgr must not fail, already checked for space");
        let device_name = format_flex_name(&pool_uuid, FlexRole::ThinData);
        let data_dev = try!(LinearDev::new(&device_name, dm, data_regions));
        let length = try!(data_dev.size()).sectors();

        let device_name = format_thinpool_name(&pool_uuid, ThinPoolRole::Pool);
        // TODO Fix hard coded data blocksize and low water mark.
        let thinpool_dev = try!(ThinPoolDev::new(&device_name,
                                                 dm,
                                                 length,
                                                 DATA_BLOCK_SIZE,
                                                 DataBlocks(256000),
                                                 meta_dev,
                                                 data_dev));

        let mut pool = StratPool {
            name: name.to_owned(),
            pool_uuid: pool_uuid,
            block_devs: block_mgr,
            filesystems: Table::new(),
            redundancy: redundancy,
            thin_pool: thinpool_dev,
        };

        try!(pool.write_metadata());

        Ok(pool)
    }

    /// Minimum initial size for a pool.
    pub fn min_initial_size() -> Sectors {
        INITIAL_META_SIZE + INITIAL_DATA_SIZE
    }

    // TODO: Check current time against global last updated, and use
    // alternate time value if earlier, as described in SWDD
    pub fn write_metadata(&mut self) -> EngineResult<()> {
        let data = try!(serde_json::to_string(&self.to_save()));
        self.block_devs
            .save_state(&now().to_timespec(), data.as_bytes())
    }

    pub fn check(&mut self) -> () {
        let dm = DM::new().expect("Could not get DM handle");

        let result = match self.thin_pool.status(&dm) {
            Ok(r) => r,
            Err(_) => {
                error!("Could not get thinpool status");
                // TODO: Take pool offline?
                return;
            }
        };

        match result {
            ThinPoolStatus::Good(wstatus, usage) => {
                match wstatus {
                    ThinPoolWorkingStatus::Good => {}
                    ThinPoolWorkingStatus::ReadOnly => {
                        // TODO: why is pool r/o and how do we get it
                        // rw again?
                    }
                    ThinPoolWorkingStatus::OutOfSpace => {
                        // TODO: Add more space if possible, or
                        // prevent further usage
                        // Should never happen -- we should be extending first!
                    }
                    ThinPoolWorkingStatus::NeedsCheck => {
                        // TODO: Take pool offline?
                        // TODO: run thin_check
                    }
                }

                if usage.used_meta > usage.total_meta - META_LOWATER {
                    // TODO: Extend meta device
                }

                if usage.used_data > usage.total_data - DATA_LOWATER {
                    // TODO: Extend data device
                }
            }
            ThinPoolStatus::Fail => {
                // TODO: Take pool offline?
                // TODO: Run thin_check
            }
        }

        for fs in &mut self.filesystems {
            match fs.check(&dm) {
                Ok(f_status) => {
                    if let FilesystemStatus::Failed = f_status {
                        // TODO: recover fs? (how?)
                    }
                }
                Err(_e) => error!("fs.check() failed"),
            }
        }
    }

    /// Teardown a pool.
    /// Take down the device mapper devices belonging to the pool.
    /// This method and destroy() must keep their DM teardown operations
    /// in sync.
    pub fn teardown(self) -> EngineResult<()> {
        // TODO: any necessary clean up of filesystems
        if !self.filesystems.is_empty() {
            return Err(EngineError::Engine(ErrorEnum::Busy,
                                           format!("May be unsynced files on device.")));
        }
        let dm = try!(DM::new());
        try!(self.thin_pool.teardown(&dm));
        Ok(())
    }
}

impl Pool for StratPool {
    fn create_filesystems<'a, 'b>(&'a mut self,
                                  specs: &[&'b str])
                                  -> EngineResult<Vec<(&'b str, FilesystemUuid)>> {
        let names: HashSet<_, RandomState> = HashSet::from_iter(specs);
        for name in names.iter() {
            if self.filesystems.contains_name(name) {
                return Err(EngineError::Engine(ErrorEnum::AlreadyExists, name.to_string()));
            }
        }

        // TODO: Roll back on filesystem initialization failure.
        let pool_uuid = &self.pool_uuid;
        let mut result = Vec::new();
        for name in names.iter() {
            let uuid = Uuid::new_v4();
            let dm = try!(DM::new());
            let new_filesystem =
                try!(StratFilesystem::initialize(pool_uuid, uuid, name, &dm, &mut self.thin_pool));
            self.filesystems.insert(new_filesystem);
            result.push((**name, uuid));
        }

        Ok(result)
    }

    fn add_blockdevs(&mut self, paths: &[&Path], force: bool) -> EngineResult<Vec<PathBuf>> {
        let bdev_paths = try!(self.block_devs.add(&self.pool_uuid, paths, force));
        try!(self.write_metadata());
        Ok(bdev_paths)
    }

    fn destroy(self) -> EngineResult<()> {
        // Ensure that DM teardown operations in this method are in sync
        // with operations in teardown().
        let dm = try!(DM::new());
        try!(self.thin_pool.teardown(&dm));
        try!(self.block_devs.destroy_all());

        Ok(())
    }

    fn destroy_filesystems<'a, 'b>(&'a mut self,
                                   fs_uuids: &[&'b FilesystemUuid])
                                   -> EngineResult<Vec<&'b FilesystemUuid>> {
        destroy_filesystems!{self; fs_uuids}
    }

    fn rename_filesystem(&mut self,
                         uuid: &FilesystemUuid,
                         new_name: &str)
                         -> EngineResult<RenameAction> {
        rename_filesystem!{self; uuid; new_name}
    }

    fn rename(&mut self, name: &str) {
        self.name = name.to_owned();
    }

    fn get_filesystem(&mut self, uuid: &FilesystemUuid) -> Option<&mut Filesystem> {
        get_filesystem!(self; uuid)
    }
}

impl HasUuid for StratPool {
    fn uuid(&self) -> &FilesystemUuid {
        &self.pool_uuid
    }
}

impl HasName for StratPool {
    fn name(&self) -> &str {
        &self.name
    }
}

impl Isomorphism<PoolSave> for StratPool {
    fn to_save(&self) -> PoolSave {
        PoolSave { name: self.name.clone() }
    }
}
<|endoftext|>