<commit_before>use super::Scratchpad;
use crate::StrError;
use russell_lab::{inverse, mat_mat_mul};

/// Indicates that the determinant of the Jacobian is not available (e.g., Shells)
///
/// **Note:** This also indicates that the inverse Jacobian matrix has not been computed
pub const DET_JAC_NOT_AVAILABLE: f64 = -1.0;

impl Scratchpad {
    /// Calculates the Jacobian of the mapping from general to reference space
    ///
    /// The components of the Jacobian matrix are
    ///
    /// ```text
    ///        ∂xᵢ
    /// Jᵢⱼ := ——— = Σ X[m][i] * L[m][j]
    ///        ∂ξⱼ   m
    /// ```
    ///
    /// Thus, in matrix notation
    ///
    /// ```text
    /// jacobian := J = Xᵀ · L
    /// jacobian := Jcable = Xᵀ · L
    /// jacobian := Jshell = Xᵀ · L
    /// ```
    ///
    /// where:
    ///
    /// * `Jcable`` -- Jacobian for line in multi-dimensions (geom_ndim < space_ndim)
    /// * `Jshell`` -- Jacobian for 3D surfaces (geo_ndim = 2 and space_ndim = 3)
    ///
    /// For the `SOLID` case (`geo_ndim = space_ndim`), this function also computes the inverse Jacobian:
    ///
    /// ```text
    /// inv_jacobian := J⁻¹
    /// ```
    ///
    /// # Output
    ///
    /// * `deriv` -- derivatives of the interpolation functions (nnode); `L` matrix
    /// * `jacobian` -- Jacobian matrix (space_ndim,geo_ndim)
    /// * `inv_jacobian` -- If `geo_ndim = space_ndim` (`SOLID` case): inverse Jacobian matrix (space_ndim,space_ndim)
    /// * Returns one of the following:
    ///     * `CABLE`: (geo_ndim = 1 and space_ndim = 2 or 3), returns the norm of the Jacobian vector
    ///     * `SHELL`: (geo_ndim = 2 and space_ndim = 3), returns [DET_JAC_NOT_AVAILABLE] indicating that the
    ///        determinant of the Jacobian is not available and the inverse Jacobian has not been computed
    ///     * `SOLID`: (geo_ndim = space_ndim), returns the determinant of the Jacobian
    ///
    /// # Input
    ///
    /// * `ksi` -- reference coordinates ξ with len ≥ geo_ndim
    ///
    /// # Examples
    ///
    /// ```
    /// use gemlab::shapes::{GeoKind, Scratchpad};
    /// use gemlab::StrError;
    /// use russell_chk::assert_approx_eq;
    ///
    /// fn main() -> Result<(), StrError> {
    ///     //  3-------------2         ξ₀   ξ₁
    ///     //  |      ξ₁     |  node    r    s
    ///     //  |      |      |     0 -1.0 -1.0
    ///     //  |      +--ξ₀  |     1  1.0 -1.0
    ///     //  |             |     2  1.0  1.0
    ///     //  |             |     3 -1.0  1.0
    ///     //  0-------------1
    ///
    ///     let a = 3.0;
    ///     let space_ndim = 2;
    ///     let mut pad = Scratchpad::new(space_ndim, GeoKind::Qua4)?;
    ///     pad.set_xx(0, 0, 0.0);
    ///     pad.set_xx(0, 1, 0.0);
    ///     pad.set_xx(1, 0, 2.0 * a);
    ///     pad.set_xx(1, 1, 0.0);
    ///     pad.set_xx(2, 0, 2.0 * a);
    ///     pad.set_xx(2, 1, a);
    ///     pad.set_xx(3, 0, 0.0);
    ///     pad.set_xx(3, 1, a);
    ///
    ///     let det_jac = pad.calc_jacobian(&[0.0, 0.0])?;
    ///     assert_approx_eq!(det_jac, a * a / 2.0, 1e-15);
    ///
    ///     // the solution is
    ///     //  ┌         ┐
    ///     //  │  a   0  │
    ///     //  │  0  a/2 │
    ///     //  └         ┘
    ///     assert_eq!(
    ///         format!("{}", pad.jacobian),
    ///         "┌         ┐\n\
    ///          │   3   0 │\n\
    ///          │   0 1.5 │\n\
    ///          └         ┘"
    ///     );
    ///     Ok(())
    /// }
    /// ```
    pub fn calc_jacobian(&mut self, ksi: &[f64]) -> Result<f64, StrError> {
        // check
        if !self.ok_xxt {
            return Err("all components of the coordinates matrix must be set first");
        }

        // matrix L: dNᵐ/dξ
        (self.fn_deriv)(&mut self.deriv, ksi);

        // matrix J: dx/dξ
        mat_mat_mul(&mut self.jacobian, 1.0, &self.xxt, &self.deriv)?;

        // inverse Jacobian and determinant/norm (or not possible)
        let (space_ndim, geo_ndim) = self.jacobian.dims();
        if geo_ndim == space_ndim {
            // SOLID case: inverse J (returns determinant)
            inverse(&mut self.inv_jacobian, &self.jacobian)
        } else {
            // CABLE case: norm of Jacobian vector
            if geo_ndim == 1 {
                let mut norm_jac = 0.0;
                for i in 0..space_ndim {
                    norm_jac += self.jacobian[i][0] * self.jacobian[i][0];
                }
                Ok(f64::sqrt(norm_jac))
            } else {
                // SHELL case
                Ok(DET_JAC_NOT_AVAILABLE)
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
    use super::DET_JAC_NOT_AVAILABLE;
    use crate::shapes::scratchpad_testing::aux;
    use crate::shapes::{GeoKind, Scratchpad};
    use russell_chk::assert_deriv_approx_eq;
    use russell_lab::{Matrix, Vector};

    #[test]
    fn calc_jacobian_handles_errors() {
        let mut pad = Scratchpad::new(2, GeoKind::Tri3).unwrap();
        assert_eq!(
            pad.calc_jacobian(&[0.0, 0.0]).err(),
            Some("all components of the coordinates matrix must be set first")
        );

        // bugged Jacobian matrix
        // (this would only happen if the user messes up the properties of Scratchpad directly)
        pad.set_xx(2, 1, 0.0); // setting the last component
                               // (cannot really check that all components have been set)
        pad.jacobian = Matrix::new(0, 0);
        assert_eq!(pad.calc_jacobian(&[0.0, 0.0]).err(), Some("matrices are incompatible"));
    }

    // Holds arguments for numerical differentiation of x with respect to ξ => Jacobian
    struct ArgsNumJac {
        pad: Scratchpad,  // scratchpad to send to calc_coords
        at_ksi: Vec<f64>, // at reference coord value
        ksi: Vec<f64>,    // temporary reference coord
        x: Vector,        // (space_ndim) coordinates at ξ
        i: usize,         // dimension index from 0 to space_ndim
        j: usize,         // dimension index from 0 to geo_ndim
    }

    // Computes xᵢ(ξ) with variable v := ξⱼ
    fn x_given_ksi(v: f64, args: &mut ArgsNumJac) -> f64 {
        args.ksi.copy_from_slice(&args.at_ksi);
        args.ksi[args.j] = v;
        args.pad.calc_coords(&mut args.x, &args.ksi).unwrap();
        args.x[args.i]
    }

    #[test]
    fn calc_jacobian_works() {
        // kind and tolerances
        let problem = vec![
            // Lin
            (GeoKind::Lin2, 1e-12),
            (GeoKind::Lin3, 1e-11),
            (GeoKind::Lin4, 1e-11),
            (GeoKind::Lin5, 1e-11),
            // Tri
            (GeoKind::Tri3, 1e-11),
            (GeoKind::Tri6, 1e-11),
            (GeoKind::Tri10, 1e-10),
            (GeoKind::Tri15, 1e-10),
            // Qua
            (GeoKind::Qua4, 1e-11),
            (GeoKind::Qua8, 1e-11),
            (GeoKind::Qua9, 1e-12),
            (GeoKind::Qua12, 1e-10),
            (GeoKind::Qua16, 1e-10),
            (GeoKind::Qua17, 1e-10),
            // Tet
            (GeoKind::Tet4, 1e-12),
            (GeoKind::Tet10, 1e-11),
            (GeoKind::Tet20, 1e-10),
            // Hex
            (GeoKind::Hex8, 1e-11),
            (GeoKind::Hex20, 1e-11),
            (GeoKind::Hex32, 1e-9),
        ];
        assert_eq!(problem.len(), GeoKind::VALUES.len());

        // loop over shapes
        for (kind, tol) in problem {
            // scratchpad with coordinates
            let geo_ndim = kind.ndim();
            let space_ndim = usize::max(2, geo_ndim);
            let mut pad = aux::gen_scratchpad_with_coords(space_ndim, kind);

            // set ξ within reference space
            let at_ksi = vec![0.25; geo_ndim];

            // compute Jacobian, its inverse, and determinant
            let det_jac = pad.calc_jacobian(&at_ksi).unwrap();
            assert!(det_jac > 0.0);

            // set arguments for numerical integration
            let args = &mut ArgsNumJac {
                pad: pad.clone(),
                at_ksi,
                ksi: vec![0.0; geo_ndim],
                x: Vector::new(space_ndim),
                i: 0,
                j: 0,
            };

            // check J(ξ) = dx(ξ)/dξ
            for i in 0..space_ndim {
                args.i = i;
                for j in 0..geo_ndim {
                    args.j = j;
                    // Jᵢⱼ := dxᵢ/dξⱼ
                    assert_deriv_approx_eq!(pad.jacobian[i][j], args.at_ksi[j], x_given_ksi, args, tol);
                }
            }
        }
    }

    #[test]
    fn calc_jacobian_special_cases_work() {
        // CABLE: line in 2d
        let space_ndim = 2;
        let mut pad = Scratchpad::new(space_ndim, GeoKind::Lin2).unwrap();
        let l = 3.5;
        pad.set_xx(0, 0, 0.0); // node 0
        pad.set_xx(0, 1, 0.0);
        pad.set_xx(1, 0, l); // node 1
        pad.set_xx(1, 1, 0.0);
        let norm_jac_vec = pad.calc_jacobian(&[0.0]).unwrap();
        assert_eq!(norm_jac_vec, l / 2.0); // 2.0 = length of shape in the reference space

        // SHELL: triangle on a plane diagonal to the y-z plane
        let space_ndim = 3;
        let mut pad = Scratchpad::new(space_ndim, GeoKind::Tri3).unwrap();
        pad.set_xx(0, 0, 0.0); // node 0
        pad.set_xx(0, 1, 0.0);
        pad.set_xx(0, 2, 0.0);
        pad.set_xx(1, 0, 1.0); // node 1
        pad.set_xx(1, 1, 0.0);
        pad.set_xx(1, 2, 0.0);
        pad.set_xx(2, 0, 0.5); // node 2
        pad.set_xx(2, 1, 1.0);
        pad.set_xx(2, 2, 1.0);
        let norm_jac_vec = pad.calc_jacobian(&[0.0, 0.0]).unwrap();
        assert_eq!(norm_jac_vec, DET_JAC_NOT_AVAILABLE);
        // pad.draw_shape_simple("/tmp/gemlab/test_jacobian_tri3_in_3d.svg")
        //     .unwrap();
    }
}
<commit_msg>Increase tol in jacobian test for notebook<commit_after>use super::Scratchpad;
use crate::StrError;
use russell_lab::{inverse, mat_mat_mul};

/// Indicates that the determinant of the Jacobian is not available (e.g., Shells)
///
/// **Note:** This also indicates that the inverse Jacobian matrix has not been computed
pub const DET_JAC_NOT_AVAILABLE: f64 = -1.0;

impl Scratchpad {
    /// Calculates the Jacobian of the mapping from general to reference space
    ///
    /// The components of the Jacobian matrix are
    ///
    /// ```text
    ///        ∂xᵢ
    /// Jᵢⱼ := ——— = Σ X[m][i] * L[m][j]
    ///        ∂ξⱼ   m
    /// ```
    ///
    /// Thus, in matrix notation
    ///
    /// ```text
    /// jacobian := J = Xᵀ · L
    /// jacobian := Jcable = Xᵀ · L
    /// jacobian := Jshell = Xᵀ · L
    /// ```
    ///
    /// where:
    ///
    /// * `Jcable`` -- Jacobian for line in multi-dimensions (geom_ndim < space_ndim)
    /// * `Jshell`` -- Jacobian for 3D surfaces (geo_ndim = 2 and space_ndim = 3)
    ///
    /// For the `SOLID` case (`geo_ndim = space_ndim`), this function also computes the inverse Jacobian:
    ///
    /// ```text
    /// inv_jacobian := J⁻¹
    /// ```
    ///
    /// # Output
    ///
    /// * `deriv` -- derivatives of the interpolation functions (nnode); `L` matrix
    /// * `jacobian` -- Jacobian matrix (space_ndim,geo_ndim)
    /// * `inv_jacobian` -- If `geo_ndim = space_ndim` (`SOLID` case): inverse Jacobian matrix (space_ndim,space_ndim)
    /// * Returns one of the following:
    ///     * `CABLE`: (geo_ndim = 1 and space_ndim = 2 or 3), returns the norm of the Jacobian vector
    ///     * `SHELL`: (geo_ndim = 2 and space_ndim = 3), returns [DET_JAC_NOT_AVAILABLE] indicating that the
    ///        determinant of the Jacobian is not available and the inverse Jacobian has not been computed
    ///     * `SOLID`: (geo_ndim = space_ndim), returns the determinant of the Jacobian
    ///
    /// # Input
    ///
    /// * `ksi` -- reference coordinates ξ with len ≥ geo_ndim
    ///
    /// # Examples
    ///
    /// ```
    /// use gemlab::shapes::{GeoKind, Scratchpad};
    /// use gemlab::StrError;
    /// use russell_chk::assert_approx_eq;
    ///
    /// fn main() -> Result<(), StrError> {
    ///     //  3-------------2         ξ₀   ξ₁
    ///     //  |      ξ₁     |  node    r    s
    ///     //  |      |      |     0 -1.0 -1.0
    ///     //  |      +--ξ₀  |     1  1.0 -1.0
    ///     //  |             |     2  1.0  1.0
    ///     //  |             |     3 -1.0  1.0
    ///     //  0-------------1
    ///
    ///     let a = 3.0;
    ///     let space_ndim = 2;
    ///     let mut pad = Scratchpad::new(space_ndim, GeoKind::Qua4)?;
    ///     pad.set_xx(0, 0, 0.0);
    ///     pad.set_xx(0, 1, 0.0);
    ///     pad.set_xx(1, 0, 2.0 * a);
    ///     pad.set_xx(1, 1, 0.0);
    ///     pad.set_xx(2, 0, 2.0 * a);
    ///     pad.set_xx(2, 1, a);
    ///     pad.set_xx(3, 0, 0.0);
    ///     pad.set_xx(3, 1, a);
    ///
    ///     let det_jac = pad.calc_jacobian(&[0.0, 0.0])?;
    ///     assert_approx_eq!(det_jac, a * a / 2.0, 1e-15);
    ///
    ///     // the solution is
    ///     //  ┌         ┐
    ///     //  │  a   0  │
    ///     //  │  0  a/2 │
    ///     //  └         ┘
    ///     assert_eq!(
    ///         format!("{}", pad.jacobian),
    ///         "┌         ┐\n\
    ///          │   3   0 │\n\
    ///          │   0 1.5 │\n\
    ///          └         ┘"
    ///     );
    ///     Ok(())
    /// }
    /// ```
    pub fn calc_jacobian(&mut self, ksi: &[f64]) -> Result<f64, StrError> {
        // check
        if !self.ok_xxt {
            return Err("all components of the coordinates matrix must be set first");
        }

        // matrix L: dNᵐ/dξ
        (self.fn_deriv)(&mut self.deriv, ksi);

        // matrix J: dx/dξ
        mat_mat_mul(&mut self.jacobian, 1.0, &self.xxt, &self.deriv)?;

        // inverse Jacobian and determinant/norm (or not possible)
        let (space_ndim, geo_ndim) = self.jacobian.dims();
        if geo_ndim == space_ndim {
            // SOLID case: inverse J (returns determinant)
            inverse(&mut self.inv_jacobian, &self.jacobian)
        } else {
            // CABLE case: norm of Jacobian vector
            if geo_ndim == 1 {
                let mut norm_jac = 0.0;
                for i in 0..space_ndim {
                    norm_jac += self.jacobian[i][0] * self.jacobian[i][0];
                }
                Ok(f64::sqrt(norm_jac))
            } else {
                // SHELL case
                Ok(DET_JAC_NOT_AVAILABLE)
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
    use super::DET_JAC_NOT_AVAILABLE;
    use crate::shapes::scratchpad_testing::aux;
    use crate::shapes::{GeoKind, Scratchpad};
    use russell_chk::assert_deriv_approx_eq;
    use russell_lab::{Matrix, Vector};

    #[test]
    fn calc_jacobian_handles_errors() {
        let mut pad = Scratchpad::new(2, GeoKind::Tri3).unwrap();
        assert_eq!(
            pad.calc_jacobian(&[0.0, 0.0]).err(),
            Some("all components of the coordinates matrix must be set first")
        );

        // bugged Jacobian matrix
        // (this would only happen if the user messes up the properties of Scratchpad directly)
        pad.set_xx(2, 1, 0.0); // setting the last component
                               // (cannot really check that all components have been set)
        pad.jacobian = Matrix::new(0, 0);
        assert_eq!(pad.calc_jacobian(&[0.0, 0.0]).err(), Some("matrices are incompatible"));
    }

    // Holds arguments for numerical differentiation of x with respect to ξ => Jacobian
    struct ArgsNumJac {
        pad: Scratchpad,  // scratchpad to send to calc_coords
        at_ksi: Vec<f64>, // at reference coord value
        ksi: Vec<f64>,    // temporary reference coord
        x: Vector,        // (space_ndim) coordinates at ξ
        i: usize,         // dimension index from 0 to space_ndim
        j: usize,         // dimension index from 0 to geo_ndim
    }

    // Computes xᵢ(ξ) with variable v := ξⱼ
    fn x_given_ksi(v: f64, args: &mut ArgsNumJac) -> f64 {
        args.ksi.copy_from_slice(&args.at_ksi);
        args.ksi[args.j] = v;
        args.pad.calc_coords(&mut args.x, &args.ksi).unwrap();
        args.x[args.i]
    }

    #[test]
    fn calc_jacobian_works() {
        // kind and tolerances
        let problem = vec![
            // Lin
            (GeoKind::Lin2, 1e-12),
            (GeoKind::Lin3, 1e-11),
            (GeoKind::Lin4, 1e-11),
            (GeoKind::Lin5, 1e-11),
            // Tri
            (GeoKind::Tri3, 1e-11),
            (GeoKind::Tri6, 1e-11),
            (GeoKind::Tri10, 1e-10),
            (GeoKind::Tri15, 1e-10),
            // Qua
            (GeoKind::Qua4, 1e-11),
            (GeoKind::Qua8, 1e-11),
            (GeoKind::Qua9, 1e-11),
            (GeoKind::Qua12, 1e-10),
            (GeoKind::Qua16, 1e-10),
            (GeoKind::Qua17, 1e-10),
            // Tet
            (GeoKind::Tet4, 1e-12),
            (GeoKind::Tet10, 1e-11),
            (GeoKind::Tet20, 1e-10),
            // Hex
            (GeoKind::Hex8, 1e-11),
            (GeoKind::Hex20, 1e-11),
            (GeoKind::Hex32, 1e-9),
        ];
        assert_eq!(problem.len(), GeoKind::VALUES.len());

        // loop over shapes
        for (kind, tol) in problem {
            // scratchpad with coordinates
            let geo_ndim = kind.ndim();
            let space_ndim = usize::max(2, geo_ndim);
            let mut pad = aux::gen_scratchpad_with_coords(space_ndim, kind);

            // set ξ within reference space
            let at_ksi = vec![0.25; geo_ndim];

            // compute Jacobian, its inverse, and determinant
            let det_jac = pad.calc_jacobian(&at_ksi).unwrap();
            assert!(det_jac > 0.0);

            // set arguments for numerical integration
            let args = &mut ArgsNumJac {
                pad: pad.clone(),
                at_ksi,
                ksi: vec![0.0; geo_ndim],
                x: Vector::new(space_ndim),
                i: 0,
                j: 0,
            };

            // check J(ξ) = dx(ξ)/dξ
            for i in 0..space_ndim {
                args.i = i;
                for j in 0..geo_ndim {
                    args.j = j;
                    // Jᵢⱼ := dxᵢ/dξⱼ
                    assert_deriv_approx_eq!(pad.jacobian[i][j], args.at_ksi[j], x_given_ksi, args, tol);
                }
            }
        }
    }

    #[test]
    fn calc_jacobian_special_cases_work() {
        // CABLE: line in 2d
        let space_ndim = 2;
        let mut pad = Scratchpad::new(space_ndim, GeoKind::Lin2).unwrap();
        let l = 3.5;
        pad.set_xx(0, 0, 0.0); // node 0
        pad.set_xx(0, 1, 0.0);
        pad.set_xx(1, 0, l); // node 1
        pad.set_xx(1, 1, 0.0);
        let norm_jac_vec = pad.calc_jacobian(&[0.0]).unwrap();
        assert_eq!(norm_jac_vec, l / 2.0); // 2.0 = length of shape in the reference space

        // SHELL: triangle on a plane diagonal to the y-z plane
        let space_ndim = 3;
        let mut pad = Scratchpad::new(space_ndim, GeoKind::Tri3).unwrap();
        pad.set_xx(0, 0, 0.0); // node 0
        pad.set_xx(0, 1, 0.0);
        pad.set_xx(0, 2, 0.0);
        pad.set_xx(1, 0, 1.0); // node 1
        pad.set_xx(1, 1, 0.0);
        pad.set_xx(1, 2, 0.0);
        pad.set_xx(2, 0, 0.5); // node 2
        pad.set_xx(2, 1, 1.0);
        pad.set_xx(2, 2, 1.0);
        let norm_jac_vec = pad.calc_jacobian(&[0.0, 0.0]).unwrap();
        assert_eq!(norm_jac_vec, DET_JAC_NOT_AVAILABLE);
        // pad.draw_shape_simple("/tmp/gemlab/test_jacobian_tri3_in_3d.svg")
        //     .unwrap();
    }
}
<|endoftext|>


<commit_before>use std::io::{TempDir, MemWriter};
use std::str::from_utf8;
use schema;
use buffer;

#[deriving(Show, Eq, Hash)]
struct Register {
	record: schema::Record,
	datatype: schema::SqlType,
}

impl Register {
	fn new(rec: schema::Record, typ: schema::SqlType) -> Register {
		Register {
			record: rec,
			datatype: typ,
		}
	}

	fn set_int(&mut self, val: int) {
		// TODO
	}

	fn get_int(&self) -> int {
		self.record.to_int()
	}

	fn set_str(&mut self) {
		// TODO
	}

	fn get_str<'a>(&'a self) -> &'a str {
		let d = self.record.get_data();
		from_utf8(d).unwrap()
	}
}

impl Ord for Register {
	fn lt(&self, other: &Register) -> bool {
		// not the most robust implementation
		self.get_int() < other.get_int()
	}
}

trait Operatorish<T>: Iterator<T> {
	//fn open();
	//fn close(&self);
}

struct TableScan<'a, 'b> {
	relation: schema::Relation,
	segment: &'a mut schema::SPSegment<'b>,
	current: uint,
}

impl<'a, 'b> TableScan<'a, 'b> {
	fn new(rel: schema::Relation, seg: &'a mut schema::SPSegment<'b>) -> TableScan<'a, 'b> {
		TableScan {
			relation: rel,
			segment: seg,
			current: 0,
		}
	}
}

impl<'a, 'b> Operatorish<Vec<Register>> for TableScan<'a, 'b> {
}

impl<'a, 'b> Iterator<Vec<Register>> for TableScan<'a, 'b> {
	fn next(&mut self) -> Option<Vec<Register>> {
		if (self.current as u64) < self.relation.inserted {
			let tup = self.relation.get(self.segment, self.current);
			let mut res = tup.move_iter().map(|(v, t)| Register::new(v, t)).
				collect::<Vec<Register>>();
			debug!("TS: {}", res);
			self.current += 1;
			Some(res)
		} else {
			None
		}
	}
}

struct Print<'a, T, V> {
	input: T,
	output: &'a mut V,
}

impl<'a, T: Operatorish<Vec<Register>>, V: Writer> Print<'a, T, V> {
	fn new(input: T, output: &'a mut V) -> Print<'a, T, V> {
		Print {
			input: input,
			output: output
		}
	}
}

impl<'a, T: Operatorish<Vec<Register>>, V: Writer> Iterator<Vec<Register>> for Print<'a, T, V> {
	fn next(&mut self) -> Option<Vec<Register>> {
		let cur = self.input.next();
		match cur {
			Some(reg) => {
				for item in reg.iter() {
					match item.datatype {
						schema::Varchar(_) => self.output.write(item.get_str().as_bytes()),
						schema::Integer => self.output.write(
							format!("{}", item.get_int()).as_bytes())
					};
					self.output.write(", ".as_bytes());
				}
				self.output.write("\n".as_bytes());
				Some(reg)
			},
			None => None,
		}
	}
}

struct Project<T> {
	input: T,
	registerids: Vec<uint>,
}

impl<T: Operatorish<Vec<Register>>> Project<T> {
	fn new(input: T, regs: Vec<uint>) -> Project<T> {
		Project {
			input: input,
			registerids: regs,
		}
	}
}

impl<T: Operatorish<Vec<Register>>> Iterator<Vec<Register>> for Project<T> {
	fn next(&mut self) -> Option<Vec<Register>> {
		let cur = self.input.next();
		match cur {
			None => None,
			Some(mut reg) => {
				let mut res = Vec::with_capacity(self.registerids.len());
				let mut regids = self.registerids.clone();
				regids.reverse();
				for index in regids.move_iter() {
					let value = reg.remove(index);
					let v = match value {
						None => fail!("Projection doesn't have vield {}", index),
						Some(v) => v,
					};
					res.push(v);
				}
				Some(res)
			}
		}
	}
}

impl<T: Operatorish<Vec<Register>>> Operatorish<Vec<Register>> for Project<T> {
}

#[deriving(Show)]
enum Selectable {
	Varchar(~str),
	Integer(int),
}

struct Select<T> {
	input: T,
	index: uint,
	value: Selectable,
}

impl<T: Operatorish<Vec<Register>>> Select<T> {
	fn new(input: T, index: uint, value: Selectable) -> Select<T> {
		Select {
			input: input,
			index: index,
			value: value,
		}
	}
}

impl<T: Operatorish<Vec<Register>>> Iterator<Vec<Register>> for Select<T> {
	fn next(&mut self) -> Option<Vec<Register>> {
		// initial value
		let mut cur = self.input.next();
		while cur.is_some() {
			// we made sure cur is Some(…), so we can unwrap safely
			let reg = cur.unwrap();

			// check what we got to select for and retrieve values
			// accordingly
			match self.value {
				// comparing to a string
				Varchar(ref v) => {
					println!("Comparing {} with {}",
						reg.get(self.index).get_str(),
						v);
					// getting the register value as string
					if reg.get(self.index).get_str() == *v {
						return Some(reg)
					} else {
						// not equal, skip to next entry
						cur = self.input.next();
						continue
					}
				},
				// comparing to a number
				Integer(v) => {
					// compare against reg as integer
					if reg.get(self.index).get_int() == v {
						return Some(reg)
					} else {
						// not equal, skip to next entry
						cur = self.input.next();
						continue
					}
				}
			}
		}
		// no more entries, signal iterator exhaustion
		None
	}
}

impl<T: Operatorish<Vec<Register>>> Operatorish<Vec<Register>> for Select<T> {
}

struct HashJoin<T> {
	left: T,
	right: T,
	on: (uint, uint),
}

impl<T: Operatorish<Vec<Register>>> HashJoin<T> {
	fn new(left: T, right: T, on: (uint, uint)) -> HashJoin<T> {
		// TODO create hashmap of 'left'
		HashJoin {
			left: left,
			right: right,
			on: on,
		}
	}
}

impl<T: Operatorish<Vec<Register>>> Iterator<Vec<Register>> for HashJoin<T> {
	fn next(&mut self) -> Option<Vec<Register>> {
		None
	}
}

impl<T: Operatorish<Vec<Register>>> Operatorish<Vec<Register>> for HashJoin<T> {
}

#[test]
fn simple_tablescan() {
	let dir = match TempDir::new("tablescan") {
		Some(temp_dir) => temp_dir,
		None => fail!("creation of temporary directory"),
	};

	//let p = dir.path();
	let p = Path::new(".");

	let mut manager = buffer::BufferManager::new(1024, p.clone());
	let mut seg = schema::SPSegment {id: 1, manager: &mut manager};

	let name = schema::Column::new(~"name", schema::Varchar(128), vec!(schema::NotNull));
	let age = schema::Column::new(~"age", schema::Integer, vec!(schema::NotNull));
	let mut relation = schema::Relation::new(~"Person");
	relation.add_column(name);
	relation.add_column(age);
	relation.insert(&mut seg, vec!(schema::Record::from_str(~"Alice"), schema::Record::from_int(20)));
	relation.insert(&mut seg, vec!(schema::Record::from_str(~"Bob"), schema::Record::from_int(40)));

	{
		let mut ts = TableScan::new(relation.clone(), &mut seg);
		for tuple in ts {
			println!("Got entry {}", tuple);
		}
	}

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(relation.clone(), &mut seg);
			let mut pr = Print::new(ts, &mut mw);
			// force write by iterating, strange API
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(relation.clone(), &mut seg);
			let mut pr = Project::new(ts, vec!(0));
			let mut pr = Print::new(pr, &mut mw);
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(relation.clone(), &mut seg);
			let mut se = Select::new(ts, 1, Integer(20));
			let mut pr = Print::new(se, &mut mw);
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(relation.clone(), &mut seg);
			let mut se = Select::new(ts, 0, Varchar(~"Bob"));
			let mut pr = Print::new(se, &mut mw);
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	assert!(false);
}

#[test]
fn simple_hashjoin() {
	let dir = match TempDir::new("hashjoin") {
		Some(temp_dir) => temp_dir,
		None => fail!("creation of temporary directory"),
	};

	//let p = dir.path();
	let p = Path::new(".");

	let mut manager = buffer::BufferManager::new(1024, p.clone());
	let mut seg = schema::SPSegment {id: 1, manager: &mut manager};

	let id = schema::Column::new(~"id", schema::Integer, vec!(schema::NotNull));
	let name = schema::Column::new(~"name", schema::Varchar(128), vec!(schema::NotNull));
	let mut people = schema::Relation::new(~"Person");
	people.add_column(name);
	people.add_column(age);
	people.insert(&mut seg, vec!(schema::Record::from_str(~"Alice"), schema::Record::from_int(20)));
	people.insert(&mut seg, vec!(schema::Record::from_str(~"Bob"), schema::Record::from_int(40)));

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(people.clone(), &mut seg);
			let mut se = Select::new(ts, 0, Varchar(~"Bob"));
			let mut pr = Print::new(se, &mut mw);
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	assert!(false);
}
<commit_msg>Clean up testing code<commit_after>use std::io::{TempDir, MemWriter};
use std::str::from_utf8;
use schema;
use buffer;

#[deriving(Show, Eq, Hash)]
struct Register {
	record: schema::Record,
	datatype: schema::SqlType,
}

impl Register {
	fn new(rec: schema::Record, typ: schema::SqlType) -> Register {
		Register {
			record: rec,
			datatype: typ,
		}
	}

	fn set_int(&mut self, val: int) {
		// TODO
	}

	fn get_int(&self) -> int {
		self.record.to_int()
	}

	fn set_str(&mut self) {
		// TODO
	}

	fn get_str<'a>(&'a self) -> &'a str {
		let d = self.record.get_data();
		from_utf8(d).unwrap()
	}
}

impl Ord for Register {
	fn lt(&self, other: &Register) -> bool {
		// not the most robust implementation
		self.get_int() < other.get_int()
	}
}

trait Operatorish<T>: Iterator<T> {
	//fn open();
	//fn close(&self);
}

struct TableScan<'a, 'b> {
	relation: schema::Relation,
	segment: &'a mut schema::SPSegment<'b>,
	current: uint,
}

impl<'a, 'b> TableScan<'a, 'b> {
	fn new(rel: schema::Relation, seg: &'a mut schema::SPSegment<'b>) -> TableScan<'a, 'b> {
		TableScan {
			relation: rel,
			segment: seg,
			current: 0,
		}
	}
}

impl<'a, 'b> Operatorish<Vec<Register>> for TableScan<'a, 'b> {
}

impl<'a, 'b> Iterator<Vec<Register>> for TableScan<'a, 'b> {
	fn next(&mut self) -> Option<Vec<Register>> {
		if (self.current as u64) < self.relation.inserted {
			let tup = self.relation.get(self.segment, self.current);
			let mut res = tup.move_iter().map(|(v, t)| Register::new(v, t)).
				collect::<Vec<Register>>();
			debug!("TS: {}", res);
			self.current += 1;
			Some(res)
		} else {
			None
		}
	}
}

struct Print<'a, T, V> {
	input: T,
	output: &'a mut V,
}

impl<'a, T: Operatorish<Vec<Register>>, V: Writer> Print<'a, T, V> {
	fn new(input: T, output: &'a mut V) -> Print<'a, T, V> {
		Print {
			input: input,
			output: output
		}
	}
}

impl<'a, T: Operatorish<Vec<Register>>, V: Writer> Iterator<Vec<Register>> for Print<'a, T, V> {
	fn next(&mut self) -> Option<Vec<Register>> {
		let cur = self.input.next();
		match cur {
			Some(reg) => {
				for item in reg.iter() {
					match item.datatype {
						schema::Varchar(_) => self.output.write(item.get_str().as_bytes()),
						schema::Integer => self.output.write(
							format!("{}", item.get_int()).as_bytes())
					};
					self.output.write(", ".as_bytes());
				}
				self.output.write("\n".as_bytes());
				Some(reg)
			},
			None => None,
		}
	}
}

struct Project<T> {
	input: T,
	registerids: Vec<uint>,
}

impl<T: Operatorish<Vec<Register>>> Project<T> {
	fn new(input: T, regs: Vec<uint>) -> Project<T> {
		Project {
			input: input,
			registerids: regs,
		}
	}
}

impl<T: Operatorish<Vec<Register>>> Iterator<Vec<Register>> for Project<T> {
	fn next(&mut self) -> Option<Vec<Register>> {
		let cur = self.input.next();
		match cur {
			None => None,
			Some(mut reg) => {
				let mut res = Vec::with_capacity(self.registerids.len());
				let mut regids = self.registerids.clone();
				regids.reverse();
				for index in regids.move_iter() {
					let value = reg.remove(index);
					let v = match value {
						None => fail!("Projection doesn't have vield {}", index),
						Some(v) => v,
					};
					res.push(v);
				}
				Some(res)
			}
		}
	}
}

impl<T: Operatorish<Vec<Register>>> Operatorish<Vec<Register>> for Project<T> {
}

#[deriving(Show)]
enum Selectable {
	Varchar(~str),
	Integer(int),
}

struct Select<T> {
	input: T,
	index: uint,
	value: Selectable,
}

impl<T: Operatorish<Vec<Register>>> Select<T> {
	fn new(input: T, index: uint, value: Selectable) -> Select<T> {
		Select {
			input: input,
			index: index,
			value: value,
		}
	}
}

impl<T: Operatorish<Vec<Register>>> Iterator<Vec<Register>> for Select<T> {
	fn next(&mut self) -> Option<Vec<Register>> {
		// initial value
		let mut cur = self.input.next();
		while cur.is_some() {
			// we made sure cur is Some(…), so we can unwrap safely
			let reg = cur.unwrap();

			// check what we got to select for and retrieve values
			// accordingly
			match self.value {
				// comparing to a string
				Varchar(ref v) => {
					println!("Comparing {} with {}",
						reg.get(self.index).get_str(),
						v);
					// getting the register value as string
					if reg.get(self.index).get_str() == *v {
						return Some(reg)
					} else {
						// not equal, skip to next entry
						cur = self.input.next();
						continue
					}
				},
				// comparing to a number
				Integer(v) => {
					// compare against reg as integer
					if reg.get(self.index).get_int() == v {
						return Some(reg)
					} else {
						// not equal, skip to next entry
						cur = self.input.next();
						continue
					}
				}
			}
		}
		// no more entries, signal iterator exhaustion
		None
	}
}

impl<T: Operatorish<Vec<Register>>> Operatorish<Vec<Register>> for Select<T> {
}

struct HashJoin<T> {
	left: T,
	right: T,
	on: (uint, uint),
}

impl<T: Operatorish<Vec<Register>>> HashJoin<T> {
	fn new(left: T, right: T, on: (uint, uint)) -> HashJoin<T> {
		// TODO create hashmap of 'left'
		HashJoin {
			left: left,
			right: right,
			on: on,
		}
	}
}

impl<T: Operatorish<Vec<Register>>> Iterator<Vec<Register>> for HashJoin<T> {
	fn next(&mut self) -> Option<Vec<Register>> {
		None
	}
}

impl<T: Operatorish<Vec<Register>>> Operatorish<Vec<Register>> for HashJoin<T> {
}

#[test]
fn simple_tablescan() {
	let dir = match TempDir::new("tablescan") {
		Some(temp_dir) => temp_dir,
		None => fail!("creation of temporary directory"),
	};

	//let p = dir.path();
	let p = Path::new(".");

	let mut manager = buffer::BufferManager::new(1024, p.clone());
	let mut seg = schema::SPSegment {id: 1, manager: &mut manager};

	let name = schema::Column::new(~"name", schema::Varchar(128), vec!(schema::NotNull));
	let age = schema::Column::new(~"age", schema::Integer, vec!(schema::NotNull));
	let mut relation = schema::Relation::new(~"Person");
	relation.add_column(name);
	relation.add_column(age);
	relation.insert(&mut seg, vec!(schema::Record::from_str(~"Alice"), schema::Record::from_int(20)));
	relation.insert(&mut seg, vec!(schema::Record::from_str(~"Bob"), schema::Record::from_int(40)));

	{
		let mut ts = TableScan::new(relation.clone(), &mut seg);
		for tuple in ts {
			println!("Got entry {}", tuple);
		}
	}

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(relation.clone(), &mut seg);
			let mut pr = Print::new(ts, &mut mw);
			// force write by iterating, strange API
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(relation.clone(), &mut seg);
			let mut pr = Project::new(ts, vec!(0));
			let mut pr = Print::new(pr, &mut mw);
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(relation.clone(), &mut seg);
			let mut se = Select::new(ts, 1, Integer(20));
			let mut pr = Print::new(se, &mut mw);
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	{
		let mut mw = MemWriter::new();
		{
			let mut ts = TableScan::new(relation.clone(), &mut seg);
			let mut se = Select::new(ts, 0, Varchar(~"Bob"));
			let mut pr = Print::new(se, &mut mw);
			for _ in pr {}
		}
		println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());
	}

	assert!(false);
}

#[test]
fn simple_hashjoin() {
	let dir = match TempDir::new("hashjoin") {
		Some(temp_dir) => temp_dir,
		None => fail!("creation of temporary directory"),
	};

	//let p = dir.path();
	let p = Path::new(".");

	let mut manager = buffer::BufferManager::new(1024, p.clone());
	let mut seg = schema::SPSegment {id: 1, manager: &mut manager};

	let id = schema::Column::new(~"id", schema::Integer, vec!(schema::NotNull));
	let name = schema::Column::new(~"name", schema::Varchar(128), vec!(schema::NotNull));
	let mut people = schema::Relation::new(~"Person");
	people.add_column(id);
	people.add_column(name);
	people.insert(&mut seg, vec!(schema::Record::from_int(0), schema::Record::from_str(~"Alice")));
	people.insert(&mut seg, vec!(schema::Record::from_int(1), schema::Record::from_str(~"Bob")));

	let mut mw = MemWriter::new();
	let mut ts = TableScan::new(people, &mut seg);
	let mut se = Select::new(ts, 0, Integer(0));
	{
		let mut pr = Print::new(se, &mut mw);
		for _ in pr {}
	}
	println!("Saved: {}", from_utf8(mw.unwrap()).unwrap());

	assert!(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 dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
use dom::bindings::js::{JS, Root};
use dom::bindings::proxyhandler::{fill_property_descriptor, get_property_descriptor};
use dom::bindings::reflector::{Reflectable, Reflector};
use dom::bindings::utils::WindowProxyHandler;
use dom::bindings::utils::get_array_index_from_id;
use dom::document::Document;
use dom::element::Element;
use dom::window::Window;
use js::JSCLASS_IS_GLOBAL;
use js::glue::{CreateWrapperProxyHandler, ProxyTraps, NewWindowProxy};
use js::glue::{GetProxyPrivate, SetProxyExtra};
use js::jsapi::{Handle, JS_ForwardSetPropertyTo, ObjectOpResult, RootedObject, RootedValue};
use js::jsapi::{HandleId, HandleObject, MutableHandle, MutableHandleValue};
use js::jsapi::{JSAutoCompartment, JSAutoRequest, JS_GetClass};
use js::jsapi::{JSContext, JSErrNum, JSObject, JSPropertyDescriptor};
use js::jsapi::{JS_AlreadyHasOwnPropertyById, JS_ForwardGetPropertyTo};
use js::jsapi::{JS_DefinePropertyById6, JS_GetPropertyDescriptorById};
use js::jsval::{ObjectValue, UndefinedValue, PrivateValue};
use std::ptr;

#[dom_struct]
pub struct BrowsingContext {
    reflector: Reflector,
    history: Vec<SessionHistoryEntry>,
    active_index: usize,
    frame_element: Option<JS<Element>>,
}

impl BrowsingContext {
    pub fn new_inherited(document: &Document, frame_element: Option<&Element>) -> BrowsingContext {
        BrowsingContext {
            reflector: Reflector::new(),
            history: vec![SessionHistoryEntry::new(document)],
            active_index: 0,
            frame_element: frame_element.map(JS::from_ref),
        }
    }

    #[allow(unsafe_code)]
    pub fn new(document: &Document, frame_element: Option<&Element>) -> Root<BrowsingContext> {
        unsafe {
            let window = document.window();

            let WindowProxyHandler(handler) = window.windowproxy_handler();
            assert!(!handler.is_null());

            let cx = window.get_cx();
            let _ar = JSAutoRequest::new(cx);
            let parent = window.reflector().get_jsobject();
            assert!(!parent.get().is_null());
            assert!(((*JS_GetClass(parent.get())).flags & JSCLASS_IS_GLOBAL) != 0);
            let _ac = JSAutoCompartment::new(cx, parent.get());
            let window_proxy = RootedObject::new(cx,
                NewWindowProxy(cx, parent, handler));
            assert!(!window_proxy.ptr.is_null());

            let object = box BrowsingContext::new_inherited(document, frame_element);

            let raw = Box::into_raw(object);
            SetProxyExtra(window_proxy.ptr, 0, PrivateValue(raw as *const _));

            (*raw).init_reflector(window_proxy.ptr);

            Root::from_ref(&*raw)
        }
    }

    pub fn active_document(&self) -> &Document {
        &*self.history[self.active_index].document
    }

    pub fn active_window(&self) -> &Window {
        self.active_document().window()
    }

    pub fn frame_element(&self) -> Option<&Element> {
        self.frame_element.as_ref().map(|element| &**element)
    }

    pub fn window_proxy(&self) -> *mut JSObject {
        let window_proxy = self.reflector.get_jsobject();
        assert!(!window_proxy.get().is_null());
        window_proxy.get()
    }
}

// This isn't a DOM struct, just a convenience struct
// without a reflector, so we don't mark this as #[dom_struct]
#[must_root]
#[privatize]
#[derive(JSTraceable, HeapSizeOf)]
pub struct SessionHistoryEntry {
    document: JS<Document>,
    children: Vec<JS<BrowsingContext>>,
}

impl SessionHistoryEntry {
    fn new(document: &Document) -> SessionHistoryEntry {
        SessionHistoryEntry {
            document: JS::from_ref(document),
            children: vec![],
        }
    }
}

#[allow(unsafe_code)]
unsafe fn GetSubframeWindow(cx: *mut JSContext,
                            proxy: HandleObject,
                            id: HandleId)
                            -> Option<Root<Window>> {
    let index = get_array_index_from_id(cx, id);
    if let Some(index) = index {
        let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
        let win = root_from_handleobject::<Window>(target.handle()).unwrap();
        let mut found = false;
        return win.IndexedGetter(index, &mut found);
    }

    None
}

#[allow(unsafe_code)]
unsafe extern "C" fn getOwnPropertyDescriptor(cx: *mut JSContext,
                                              proxy: HandleObject,
                                              id: HandleId,
                                              desc: MutableHandle<JSPropertyDescriptor>)
                                              -> bool {
    let window = GetSubframeWindow(cx, proxy, id);
    if let Some(window) = window {
        let mut val = RootedValue::new(cx, UndefinedValue());
        window.to_jsval(cx, val.handle_mut());
        (*desc.ptr).value = val.ptr;
        fill_property_descriptor(&mut *desc.ptr, *proxy.ptr, true);
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    // XXX This should be JS_GetOwnPropertyDescriptorById
    if !JS_GetPropertyDescriptorById(cx, target.handle(), id, desc) {
        return false;
    }

    if (*desc.ptr).obj != target.ptr {
        // Not an own property
        (*desc.ptr).obj = ptr::null_mut();
    } else {
        (*desc.ptr).obj = *proxy.ptr;
    }

    true
}

#[allow(unsafe_code)]
unsafe extern "C" fn defineProperty(cx: *mut JSContext,
                                    proxy: HandleObject,
                                    id: HandleId,
                                    desc: Handle<JSPropertyDescriptor>,
                                    res: *mut ObjectOpResult)
                                    -> bool {
    if get_array_index_from_id(cx, id).is_some() {
        // Spec says to Reject whether this is a supported index or not,
        // since we have no indexed setter or indexed creator.  That means
        // throwing in strict mode (FIXME: Bug 828137), doing nothing in
        // non-strict mode.
        (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    JS_DefinePropertyById6(cx, target.handle(), id, desc, res)
}

#[allow(unsafe_code)]
unsafe extern "C" fn hasOwn(cx: *mut JSContext,
                            proxy: HandleObject,
                            id: HandleId,
                            bp: *mut bool)
                            -> bool {
    let window = GetSubframeWindow(cx, proxy, id);
    if window.is_some() {
        *bp = true;
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    let mut found = false;
    if !JS_AlreadyHasOwnPropertyById(cx, target.handle(), id, &mut found) {
        return false;
    }

    *bp = found;
    true
}

#[allow(unsafe_code)]
unsafe extern "C" fn get(cx: *mut JSContext,
                         proxy: HandleObject,
                         receiver: HandleObject,
                         id: HandleId,
                         vp: MutableHandleValue)
                         -> bool {
    let window = GetSubframeWindow(cx, proxy, id);
    if let Some(window) = window {
        window.to_jsval(cx, vp);
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    JS_ForwardGetPropertyTo(cx, target.handle(), id, receiver, vp)
}

#[allow(unsafe_code)]
unsafe extern "C" fn set(cx: *mut JSContext,
                         proxy: HandleObject,
                         receiver: HandleObject,
                         id: HandleId,
                         vp: MutableHandleValue,
                         res: *mut ObjectOpResult)
                         -> bool {
    if get_array_index_from_id(cx, id).is_some() {
        // Reject (which means throw if and only if strict) the set.
        (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t;
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    let receiver = RootedValue::new(cx, ObjectValue(&**receiver.ptr));
    JS_ForwardSetPropertyTo(cx,
                            target.handle(),
                            id,
                            vp.to_handle(),
                            receiver.handle(),
                            res)
}

static PROXY_HANDLER: ProxyTraps = ProxyTraps {
    enter: None,
    getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor),
    defineProperty: Some(defineProperty),
    ownPropertyKeys: None,
    delete_: None,
    enumerate: None,
    preventExtensions: None,
    isExtensible: None,
    has: None,
    get: Some(get),
    set: Some(set),
    call: None,
    construct: None,
    getPropertyDescriptor: Some(get_property_descriptor),
    hasOwn: Some(hasOwn),
    getOwnEnumerablePropertyKeys: None,
    nativeCall: None,
    hasInstance: None,
    objectClassIs: None,
    className: None,
    fun_toString: None,
    boxedValue_unbox: None,
    defaultValue: None,
    trace: None,
    finalize: None,
    objectMoved: None,
    isCallable: None,
    isConstructor: None,
};

#[allow(unsafe_code)]
pub fn new_window_proxy_handler() -> WindowProxyHandler {
    unsafe {
        WindowProxyHandler(CreateWrapperProxyHandler(&PROXY_HANDLER))
    }
}
<commit_msg>Auto merge of #9602 - servo:bc-prop, r=nox<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 dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
use dom::bindings::js::{JS, Root};
use dom::bindings::proxyhandler::{fill_property_descriptor, get_property_descriptor};
use dom::bindings::reflector::{Reflectable, Reflector};
use dom::bindings::utils::WindowProxyHandler;
use dom::bindings::utils::get_array_index_from_id;
use dom::document::Document;
use dom::element::Element;
use dom::window::Window;
use js::JSCLASS_IS_GLOBAL;
use js::glue::{CreateWrapperProxyHandler, ProxyTraps, NewWindowProxy};
use js::glue::{GetProxyPrivate, SetProxyExtra};
use js::jsapi::{Handle, JS_ForwardSetPropertyTo, ObjectOpResult, RootedObject, RootedValue};
use js::jsapi::{HandleId, HandleObject, MutableHandle, MutableHandleValue};
use js::jsapi::{JSAutoCompartment, JSAutoRequest, JS_GetClass};
use js::jsapi::{JSContext, JSErrNum, JSObject, JSPropertyDescriptor};
use js::jsapi::{JS_AlreadyHasOwnPropertyById, JS_ForwardGetPropertyTo};
use js::jsapi::{JS_DefinePropertyById6, JS_GetOwnPropertyDescriptorById};
use js::jsval::{ObjectValue, UndefinedValue, PrivateValue};

#[dom_struct]
pub struct BrowsingContext {
    reflector: Reflector,
    history: Vec<SessionHistoryEntry>,
    active_index: usize,
    frame_element: Option<JS<Element>>,
}

impl BrowsingContext {
    pub fn new_inherited(document: &Document, frame_element: Option<&Element>) -> BrowsingContext {
        BrowsingContext {
            reflector: Reflector::new(),
            history: vec![SessionHistoryEntry::new(document)],
            active_index: 0,
            frame_element: frame_element.map(JS::from_ref),
        }
    }

    #[allow(unsafe_code)]
    pub fn new(document: &Document, frame_element: Option<&Element>) -> Root<BrowsingContext> {
        unsafe {
            let window = document.window();

            let WindowProxyHandler(handler) = window.windowproxy_handler();
            assert!(!handler.is_null());

            let cx = window.get_cx();
            let _ar = JSAutoRequest::new(cx);
            let parent = window.reflector().get_jsobject();
            assert!(!parent.get().is_null());
            assert!(((*JS_GetClass(parent.get())).flags & JSCLASS_IS_GLOBAL) != 0);
            let _ac = JSAutoCompartment::new(cx, parent.get());
            let window_proxy = RootedObject::new(cx,
                NewWindowProxy(cx, parent, handler));
            assert!(!window_proxy.ptr.is_null());

            let object = box BrowsingContext::new_inherited(document, frame_element);

            let raw = Box::into_raw(object);
            SetProxyExtra(window_proxy.ptr, 0, PrivateValue(raw as *const _));

            (*raw).init_reflector(window_proxy.ptr);

            Root::from_ref(&*raw)
        }
    }

    pub fn active_document(&self) -> &Document {
        &*self.history[self.active_index].document
    }

    pub fn active_window(&self) -> &Window {
        self.active_document().window()
    }

    pub fn frame_element(&self) -> Option<&Element> {
        self.frame_element.as_ref().map(|element| &**element)
    }

    pub fn window_proxy(&self) -> *mut JSObject {
        let window_proxy = self.reflector.get_jsobject();
        assert!(!window_proxy.get().is_null());
        window_proxy.get()
    }
}

// This isn't a DOM struct, just a convenience struct
// without a reflector, so we don't mark this as #[dom_struct]
#[must_root]
#[privatize]
#[derive(JSTraceable, HeapSizeOf)]
pub struct SessionHistoryEntry {
    document: JS<Document>,
    children: Vec<JS<BrowsingContext>>,
}

impl SessionHistoryEntry {
    fn new(document: &Document) -> SessionHistoryEntry {
        SessionHistoryEntry {
            document: JS::from_ref(document),
            children: vec![],
        }
    }
}

#[allow(unsafe_code)]
unsafe fn GetSubframeWindow(cx: *mut JSContext,
                            proxy: HandleObject,
                            id: HandleId)
                            -> Option<Root<Window>> {
    let index = get_array_index_from_id(cx, id);
    if let Some(index) = index {
        let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
        let win = root_from_handleobject::<Window>(target.handle()).unwrap();
        let mut found = false;
        return win.IndexedGetter(index, &mut found);
    }

    None
}

#[allow(unsafe_code)]
unsafe extern "C" fn getOwnPropertyDescriptor(cx: *mut JSContext,
                                              proxy: HandleObject,
                                              id: HandleId,
                                              desc: MutableHandle<JSPropertyDescriptor>)
                                              -> bool {
    let window = GetSubframeWindow(cx, proxy, id);
    if let Some(window) = window {
        let mut val = RootedValue::new(cx, UndefinedValue());
        window.to_jsval(cx, val.handle_mut());
        (*desc.ptr).value = val.ptr;
        fill_property_descriptor(&mut *desc.ptr, *proxy.ptr, true);
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    if !JS_GetOwnPropertyDescriptorById(cx, target.handle(), id, desc) {
        return false;
    }

    assert!(desc.get().obj.is_null() || desc.get().obj == target.ptr);
    if desc.get().obj == target.ptr {
        desc.get().obj = *proxy.ptr;
    }

    true
}

#[allow(unsafe_code)]
unsafe extern "C" fn defineProperty(cx: *mut JSContext,
                                    proxy: HandleObject,
                                    id: HandleId,
                                    desc: Handle<JSPropertyDescriptor>,
                                    res: *mut ObjectOpResult)
                                    -> bool {
    if get_array_index_from_id(cx, id).is_some() {
        // Spec says to Reject whether this is a supported index or not,
        // since we have no indexed setter or indexed creator.  That means
        // throwing in strict mode (FIXME: Bug 828137), doing nothing in
        // non-strict mode.
        (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    JS_DefinePropertyById6(cx, target.handle(), id, desc, res)
}

#[allow(unsafe_code)]
unsafe extern "C" fn hasOwn(cx: *mut JSContext,
                            proxy: HandleObject,
                            id: HandleId,
                            bp: *mut bool)
                            -> bool {
    let window = GetSubframeWindow(cx, proxy, id);
    if window.is_some() {
        *bp = true;
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    let mut found = false;
    if !JS_AlreadyHasOwnPropertyById(cx, target.handle(), id, &mut found) {
        return false;
    }

    *bp = found;
    true
}

#[allow(unsafe_code)]
unsafe extern "C" fn get(cx: *mut JSContext,
                         proxy: HandleObject,
                         receiver: HandleObject,
                         id: HandleId,
                         vp: MutableHandleValue)
                         -> bool {
    let window = GetSubframeWindow(cx, proxy, id);
    if let Some(window) = window {
        window.to_jsval(cx, vp);
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    JS_ForwardGetPropertyTo(cx, target.handle(), id, receiver, vp)
}

#[allow(unsafe_code)]
unsafe extern "C" fn set(cx: *mut JSContext,
                         proxy: HandleObject,
                         receiver: HandleObject,
                         id: HandleId,
                         vp: MutableHandleValue,
                         res: *mut ObjectOpResult)
                         -> bool {
    if get_array_index_from_id(cx, id).is_some() {
        // Reject (which means throw if and only if strict) the set.
        (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t;
        return true;
    }

    let target = RootedObject::new(cx, GetProxyPrivate(*proxy.ptr).to_object());
    let receiver = RootedValue::new(cx, ObjectValue(&**receiver.ptr));
    JS_ForwardSetPropertyTo(cx,
                            target.handle(),
                            id,
                            vp.to_handle(),
                            receiver.handle(),
                            res)
}

static PROXY_HANDLER: ProxyTraps = ProxyTraps {
    enter: None,
    getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor),
    defineProperty: Some(defineProperty),
    ownPropertyKeys: None,
    delete_: None,
    enumerate: None,
    preventExtensions: None,
    isExtensible: None,
    has: None,
    get: Some(get),
    set: Some(set),
    call: None,
    construct: None,
    getPropertyDescriptor: Some(get_property_descriptor),
    hasOwn: Some(hasOwn),
    getOwnEnumerablePropertyKeys: None,
    nativeCall: None,
    hasInstance: None,
    objectClassIs: None,
    className: None,
    fun_toString: None,
    boxedValue_unbox: None,
    defaultValue: None,
    trace: None,
    finalize: None,
    objectMoved: None,
    isCallable: None,
    isConstructor: None,
};

#[allow(unsafe_code)]
pub fn new_window_proxy_handler() -> WindowProxyHandler {
    unsafe {
        WindowProxyHandler(CreateWrapperProxyHandler(&PROXY_HANDLER))
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0
#![allow(unused_macros)] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0

extern crate proc_macro;

// spell-checker:ignore () SIGPIPE maint uucore uumain uutils

//## rust proc-macro background info
//* ref: <https://dev.to/naufraghi/procedural-macro-in-rust-101-k3f> @@ <http://archive.is/Vbr5e>
//* ref: [path construction from LitStr](https://oschwald.github.io/maxminddb-rust/syn/struct.LitStr.html) @@ <http://archive.is/8YDua>

//## proc_dbg macro
//* used to help debug the compile-time proc_macro code

#[cfg(feature = "debug")]
macro_rules! proc_dbg {
    ($x:expr) => {
        dbg!($x)
    };
}
#[cfg(not(feature = "debug"))]
macro_rules! proc_dbg {
    ($x:expr) => {
        $x
    };
}

//## main!()

// main!( EXPR )
// generates a `main()` function for utilities within the uutils group
// EXPR == syn::Expr::Lit::String | syn::Expr::Path::Ident ~ EXPR contains the lexical path to the utility `uumain()` function
//* NOTE: EXPR is ultimately expected to be a multi-segment lexical path (eg, `crate::func`); so, if a single segment path is provided, a trailing "::uumain" is automatically added
//* for more generic use (and future use of "eager" macros), EXPR may be in either STRING or IDENT form

struct Tokens {
    expr: syn::Expr,
}

impl syn::parse::Parse for Tokens {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        Ok(Tokens {
            expr: input.parse()?,
        })
    }
}

#[proc_macro]
#[cfg(not(test))] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0
pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let Tokens { expr } = syn::parse_macro_input!(stream as Tokens);
    proc_dbg!(&expr);

    const ARG_PANIC_TEXT: &str =
        "expected ident lexical path (or a literal string version) to 'uumain()' as argument";

    // match EXPR as a string literal or an ident path, o/w panic!()
    let mut expr = match expr {
        syn::Expr::Lit(expr_lit) => match expr_lit.lit {
            syn::Lit::Str(ref lit_str) => lit_str.parse::<syn::ExprPath>().unwrap(),
            _ => panic!(ARG_PANIC_TEXT),
        },
        syn::Expr::Path(expr_path) => expr_path,
        _ => panic!(ARG_PANIC_TEXT),
    };
    proc_dbg!(&expr);

    // for a single segment ExprPath argument, add trailing '::uumain' segment
    if expr.path.segments.len() < 2 {
        expr = syn::parse_quote!( #expr::uumain );
    };
    proc_dbg!(&expr);

    let f = quote::quote! { #expr(uucore::args().collect()) };
    proc_dbg!(&f);

    // generate a uutils utility `main()` function, tailored for the calling utility
    let result = quote::quote! {
        fn main() {
            use std::io::Write;
            uucore::panic::mute_sigpipe_panic(); // suppress extraneous error output for SIGPIPE failures/panics
            let code = #f; // execute utility code
            std::io::stdout().flush().expect("could not flush stdout"); // (defensively) flush stdout for utility prior to exit; see <https://github.com/rust-lang/rust/issues/23818>
            std::process::exit(code);
        }
    };
    proc_macro::TokenStream::from(result)
}
<commit_msg>fix `cargo clippy` warning (no_effect)<commit_after>#![allow(dead_code)] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0
#![allow(unused_macros)] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0

extern crate proc_macro;

// spell-checker:ignore () SIGPIPE maint uucore uumain uutils

//## rust proc-macro background info
//* ref: <https://dev.to/naufraghi/procedural-macro-in-rust-101-k3f> @@ <http://archive.is/Vbr5e>
//* ref: [path construction from LitStr](https://oschwald.github.io/maxminddb-rust/syn/struct.LitStr.html) @@ <http://archive.is/8YDua>

//## proc_dbg macro
//* used to help debug the compile-time proc_macro code

#[cfg(feature = "debug")]
macro_rules! proc_dbg {
    ($x:expr) => {
        dbg!($x)
    };
}
#[cfg(not(feature = "debug"))]
macro_rules! proc_dbg {
    ($x:expr) => {};
}

//## main!()

// main!( EXPR )
// generates a `main()` function for utilities within the uutils group
// EXPR == syn::Expr::Lit::String | syn::Expr::Path::Ident ~ EXPR contains the lexical path to the utility `uumain()` function
//* NOTE: EXPR is ultimately expected to be a multi-segment lexical path (eg, `crate::func`); so, if a single segment path is provided, a trailing "::uumain" is automatically added
//* for more generic use (and future use of "eager" macros), EXPR may be in either STRING or IDENT form

struct Tokens {
    expr: syn::Expr,
}

impl syn::parse::Parse for Tokens {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        Ok(Tokens {
            expr: input.parse()?,
        })
    }
}

#[proc_macro]
#[cfg(not(test))] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0
pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let Tokens { expr } = syn::parse_macro_input!(stream as Tokens);
    proc_dbg!(&expr);

    const ARG_PANIC_TEXT: &str =
        "expected ident lexical path (or a literal string version) to 'uumain()' as argument";

    // match EXPR as a string literal or an ident path, o/w panic!()
    let mut expr = match expr {
        syn::Expr::Lit(expr_lit) => match expr_lit.lit {
            syn::Lit::Str(ref lit_str) => lit_str.parse::<syn::ExprPath>().unwrap(),
            _ => panic!(ARG_PANIC_TEXT),
        },
        syn::Expr::Path(expr_path) => expr_path,
        _ => panic!(ARG_PANIC_TEXT),
    };
    proc_dbg!(&expr);

    // for a single segment ExprPath argument, add trailing '::uumain' segment
    if expr.path.segments.len() < 2 {
        expr = syn::parse_quote!( #expr::uumain );
    };
    proc_dbg!(&expr);

    let f = quote::quote! { #expr(uucore::args().collect()) };
    proc_dbg!(&f);

    // generate a uutils utility `main()` function, tailored for the calling utility
    let result = quote::quote! {
        fn main() {
            use std::io::Write;
            uucore::panic::mute_sigpipe_panic(); // suppress extraneous error output for SIGPIPE failures/panics
            let code = #f; // execute utility code
            std::io::stdout().flush().expect("could not flush stdout"); // (defensively) flush stdout for utility prior to exit; see <https://github.com/rust-lang/rust/issues/23818>
            std::process::exit(code);
        }
    };
    proc_macro::TokenStream::from(result)
}
<|endoftext|>


<commit_before>//! Types for the *m.room.encrypted* event.

use std::collections::BTreeMap;

use js_int::UInt;
use ruma_events_macros::MessageEventContent;
use ruma_identifiers::DeviceIdBox;
use serde::{Deserialize, Serialize};

use crate::{room::message::Relation, MessageEvent};

/// An event that has been encrypted.
pub type EncryptedEvent = MessageEvent<EncryptedEventContent>;

/// The encryption scheme for `EncryptedEventContent`
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "algorithm")]
pub enum EncryptedEventScheme {
    /// An event encrypted with *m.olm.v1.curve25519-aes-sha2*.
    #[serde(rename = "m.olm.v1.curve25519-aes-sha2")]
    OlmV1Curve25519AesSha2(OlmV1Curve25519AesSha2Content),

    /// An event encrypted with *m.megolm.v1.aes-sha2*.
    #[serde(rename = "m.megolm.v1.aes-sha2")]
    MegolmV1AesSha2(MegolmV1AesSha2Content),
}

/// The content payload for `EncryptedEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, MessageEventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.room.encrypted")]
pub struct EncryptedEventContent {
    /// Encrypted event content
    #[serde(flatten)]
    pub scheme: EncryptedEventScheme,

    /// Information about related messages for
    /// [rich replies](https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies).
    #[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
    pub relates_to: Option<Relation>,
}

impl EncryptedEventContent {
    /// Creates a new `EncryptedEventContent` with the given scheme and relation.
    pub fn new(scheme: EncryptedEventScheme, relates_to: Option<Relation>) -> Self {
        Self { scheme, relates_to }
    }
}

/// The to-device version of the payload for the `EncryptedEvent`.
pub type EncryptedToDeviceEventContent = EncryptedEventContent;

/// The payload for `EncryptedEvent` using the *m.olm.v1.curve25519-aes-sha2* algorithm.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct OlmV1Curve25519AesSha2Content {
    /// A map from the recipient Curve25519 identity key to ciphertext information.
    pub ciphertext: BTreeMap<String, CiphertextInfo>,

    /// The Curve25519 key of the sender.
    pub sender_key: String,
}

impl OlmV1Curve25519AesSha2Content {
    /// Creates a new `OlmV1Curve25519AesSha2Content` with the given ciphertext and sender key.
    pub fn new(ciphertext: BTreeMap<String, CiphertextInfo>, sender_key: String) -> Self {
        Self { ciphertext, sender_key }
    }
}

/// Ciphertext information holding the ciphertext and message type.
///
/// Used for messages encrypted with the *m.olm.v1.curve25519-aes-sha2* algorithm.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct CiphertextInfo {
    /// The encrypted payload.
    pub body: String,

    /// The Olm message type.
    #[serde(rename = "type")]
    pub message_type: UInt,
}

impl CiphertextInfo {
    /// Creates a new `CiphertextInfo` with the given body and type.
    pub fn new(body: String, message_type: UInt) -> Self {
        Self { body, message_type }
    }
}

/// The payload for `EncryptedEvent` using the *m.megolm.v1.aes-sha2* algorithm.
///
/// To create an instance of this type, first create a `MegolmV1AesSha2ContentInit` and convert it
/// via `MegolmV1AesSha2Content::from` / `.into()`.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct MegolmV1AesSha2Content {
    /// The encrypted content of the event.
    pub ciphertext: String,

    /// The Curve25519 key of the sender.
    pub sender_key: String,

    /// The ID of the sending device.
    pub device_id: DeviceIdBox,

    /// The ID of the session used to encrypt the message.
    pub session_id: String,
}

/// Mandatory initial set of fields of `MegolmV1AesSha2Content`.
///
/// This struct will not be updated even if additional fields are added to `MegolmV1AesSha2Content`
/// in a new (non-breaking) release of the Matrix specification.
#[derive(Clone, Debug)]
pub struct MegolmV1AesSha2ContentInit {
    /// The encrypted content of the event.
    pub ciphertext: String,

    /// The Curve25519 key of the sender.
    pub sender_key: String,

    /// The ID of the sending device.
    pub device_id: DeviceIdBox,

    /// The ID of the session used to encrypt the message.
    pub session_id: String,
}

impl From<MegolmV1AesSha2ContentInit> for MegolmV1AesSha2Content {
    /// Creates a new `MegolmV1AesSha2Content` from the given init struct.
    fn from(init: MegolmV1AesSha2ContentInit) -> Self {
        let MegolmV1AesSha2ContentInit { ciphertext, sender_key, device_id, session_id } = init;
        Self { ciphertext, sender_key, device_id, session_id }
    }
}

#[cfg(test)]
mod tests {
    use matches::assert_matches;
    use ruma_serde::Raw;
    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

    use super::{EncryptedEventContent, EncryptedEventScheme, MegolmV1AesSha2Content};
    use crate::room::message::{InReplyTo, Relation};
    use ruma_identifiers::event_id;

    #[test]
    fn serialization() {
        let key_verification_start_content = EncryptedEventContent {
            scheme: EncryptedEventScheme::MegolmV1AesSha2(MegolmV1AesSha2Content {
                ciphertext: "ciphertext".into(),
                sender_key: "sender_key".into(),
                device_id: "device_id".into(),
                session_id: "session_id".into(),
            }),
            relates_to: Some(Relation::Reply {
                in_reply_to: InReplyTo { event_id: event_id!("$h29iv0s8:example.com") },
            }),
        };

        let json_data = json!({
            "algorithm": "m.megolm.v1.aes-sha2",
            "ciphertext": "ciphertext",
            "sender_key": "sender_key",
            "device_id": "device_id",
            "session_id": "session_id",
            "m.relates_to": {
                "m.in_reply_to": {
                    "event_id": "$h29iv0s8:example.com"
                }
            },
        });

        assert_eq!(to_json_value(&key_verification_start_content).unwrap(), json_data);
    }

    #[test]
    fn deserialization() {
        let json_data = json!({
            "algorithm": "m.megolm.v1.aes-sha2",
            "ciphertext": "ciphertext",
            "sender_key": "sender_key",
            "device_id": "device_id",
            "session_id": "session_id",
            "m.relates_to": {
                "m.in_reply_to": {
                    "event_id": "$h29iv0s8:example.com"
                }
            },
        });

        let content: EncryptedEventContent =
            from_json_value::<Raw<EncryptedEventContent>>(json_data)
                .unwrap()
                .deserialize()
                .unwrap();

        assert_matches!(
            content.scheme,
            EncryptedEventScheme::MegolmV1AesSha2(MegolmV1AesSha2Content {
                ciphertext,
                sender_key,
                device_id,
                session_id,
            }) if ciphertext == "ciphertext"
                && sender_key == "sender_key"
                && device_id == "device_id"
                && session_id == "session_id"
        );

        assert_matches!(
            content.relates_to,
            Some(Relation::Reply { in_reply_to })
                if in_reply_to.event_id == event_id!("$h29iv0s8:example.com")
        );
    }

    #[test]
    fn deserialization_olm() {
        let json_data = json!({
            "sender_key": "test_key",
            "ciphertext": {
                "test_curve_key": {
                    "body": "encrypted_body",
                    "type": 1
                }
            },
            "algorithm": "m.olm.v1.curve25519-aes-sha2"
        });
        let content = from_json_value::<Raw<EncryptedEventContent>>(json_data)
            .unwrap()
            .deserialize()
            .unwrap();

        match content.scheme {
            EncryptedEventScheme::OlmV1Curve25519AesSha2(c) => {
                assert_eq!(c.sender_key, "test_key");
                assert_eq!(c.ciphertext.len(), 1);
                assert_eq!(c.ciphertext["test_curve_key"].body, "encrypted_body");
                assert_eq!(c.ciphertext["test_curve_key"].message_type, 1_u16.into());
            }
            _ => panic!("Wrong content type, expected a OlmV1 content"),
        }
    }

    #[test]
    fn deserialization_failure() {
        assert!(from_json_value::<Raw<EncryptedEventContent>>(
            json!({ "algorithm": "m.megolm.v1.aes-sha2" })
        )
        .unwrap()
        .deserialize()
        .is_err());
    }
}
<commit_msg>events: Construct EncryptedEventContent from EncryptedEventScheme<commit_after>//! Types for the *m.room.encrypted* event.

use std::collections::BTreeMap;

use js_int::UInt;
use ruma_events_macros::MessageEventContent;
use ruma_identifiers::DeviceIdBox;
use serde::{Deserialize, Serialize};

use crate::{room::message::Relation, MessageEvent};

/// An event that has been encrypted.
pub type EncryptedEvent = MessageEvent<EncryptedEventContent>;

/// The encryption scheme for `EncryptedEventContent`
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "algorithm")]
pub enum EncryptedEventScheme {
    /// An event encrypted with *m.olm.v1.curve25519-aes-sha2*.
    #[serde(rename = "m.olm.v1.curve25519-aes-sha2")]
    OlmV1Curve25519AesSha2(OlmV1Curve25519AesSha2Content),

    /// An event encrypted with *m.megolm.v1.aes-sha2*.
    #[serde(rename = "m.megolm.v1.aes-sha2")]
    MegolmV1AesSha2(MegolmV1AesSha2Content),
}

/// The content payload for `EncryptedEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, MessageEventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.room.encrypted")]
pub struct EncryptedEventContent {
    /// Encrypted event content
    #[serde(flatten)]
    pub scheme: EncryptedEventScheme,

    /// Information about related messages for
    /// [rich replies](https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies).
    #[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
    pub relates_to: Option<Relation>,
}

impl EncryptedEventContent {
    /// Creates a new `EncryptedEventContent` with the given scheme and relation.
    pub fn new(scheme: EncryptedEventScheme, relates_to: Option<Relation>) -> Self {
        Self { scheme, relates_to }
    }
}

impl From<EncryptedEventScheme> for EncryptedEventContent {
    fn from(scheme: EncryptedEventScheme) -> Self {
        Self { scheme, relates_to: None }
    }
}

/// The to-device version of the payload for the `EncryptedEvent`.
pub type EncryptedToDeviceEventContent = EncryptedEventContent;

/// The payload for `EncryptedEvent` using the *m.olm.v1.curve25519-aes-sha2* algorithm.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct OlmV1Curve25519AesSha2Content {
    /// A map from the recipient Curve25519 identity key to ciphertext information.
    pub ciphertext: BTreeMap<String, CiphertextInfo>,

    /// The Curve25519 key of the sender.
    pub sender_key: String,
}

impl OlmV1Curve25519AesSha2Content {
    /// Creates a new `OlmV1Curve25519AesSha2Content` with the given ciphertext and sender key.
    pub fn new(ciphertext: BTreeMap<String, CiphertextInfo>, sender_key: String) -> Self {
        Self { ciphertext, sender_key }
    }
}

/// Ciphertext information holding the ciphertext and message type.
///
/// Used for messages encrypted with the *m.olm.v1.curve25519-aes-sha2* algorithm.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct CiphertextInfo {
    /// The encrypted payload.
    pub body: String,

    /// The Olm message type.
    #[serde(rename = "type")]
    pub message_type: UInt,
}

impl CiphertextInfo {
    /// Creates a new `CiphertextInfo` with the given body and type.
    pub fn new(body: String, message_type: UInt) -> Self {
        Self { body, message_type }
    }
}

/// The payload for `EncryptedEvent` using the *m.megolm.v1.aes-sha2* algorithm.
///
/// To create an instance of this type, first create a `MegolmV1AesSha2ContentInit` and convert it
/// via `MegolmV1AesSha2Content::from` / `.into()`.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct MegolmV1AesSha2Content {
    /// The encrypted content of the event.
    pub ciphertext: String,

    /// The Curve25519 key of the sender.
    pub sender_key: String,

    /// The ID of the sending device.
    pub device_id: DeviceIdBox,

    /// The ID of the session used to encrypt the message.
    pub session_id: String,
}

/// Mandatory initial set of fields of `MegolmV1AesSha2Content`.
///
/// This struct will not be updated even if additional fields are added to `MegolmV1AesSha2Content`
/// in a new (non-breaking) release of the Matrix specification.
#[derive(Clone, Debug)]
pub struct MegolmV1AesSha2ContentInit {
    /// The encrypted content of the event.
    pub ciphertext: String,

    /// The Curve25519 key of the sender.
    pub sender_key: String,

    /// The ID of the sending device.
    pub device_id: DeviceIdBox,

    /// The ID of the session used to encrypt the message.
    pub session_id: String,
}

impl From<MegolmV1AesSha2ContentInit> for MegolmV1AesSha2Content {
    /// Creates a new `MegolmV1AesSha2Content` from the given init struct.
    fn from(init: MegolmV1AesSha2ContentInit) -> Self {
        let MegolmV1AesSha2ContentInit { ciphertext, sender_key, device_id, session_id } = init;
        Self { ciphertext, sender_key, device_id, session_id }
    }
}

#[cfg(test)]
mod tests {
    use matches::assert_matches;
    use ruma_serde::Raw;
    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

    use super::{EncryptedEventContent, EncryptedEventScheme, MegolmV1AesSha2Content};
    use crate::room::message::{InReplyTo, Relation};
    use ruma_identifiers::event_id;

    #[test]
    fn serialization() {
        let key_verification_start_content = EncryptedEventContent {
            scheme: EncryptedEventScheme::MegolmV1AesSha2(MegolmV1AesSha2Content {
                ciphertext: "ciphertext".into(),
                sender_key: "sender_key".into(),
                device_id: "device_id".into(),
                session_id: "session_id".into(),
            }),
            relates_to: Some(Relation::Reply {
                in_reply_to: InReplyTo { event_id: event_id!("$h29iv0s8:example.com") },
            }),
        };

        let json_data = json!({
            "algorithm": "m.megolm.v1.aes-sha2",
            "ciphertext": "ciphertext",
            "sender_key": "sender_key",
            "device_id": "device_id",
            "session_id": "session_id",
            "m.relates_to": {
                "m.in_reply_to": {
                    "event_id": "$h29iv0s8:example.com"
                }
            },
        });

        assert_eq!(to_json_value(&key_verification_start_content).unwrap(), json_data);
    }

    #[test]
    fn deserialization() {
        let json_data = json!({
            "algorithm": "m.megolm.v1.aes-sha2",
            "ciphertext": "ciphertext",
            "sender_key": "sender_key",
            "device_id": "device_id",
            "session_id": "session_id",
            "m.relates_to": {
                "m.in_reply_to": {
                    "event_id": "$h29iv0s8:example.com"
                }
            },
        });

        let content: EncryptedEventContent =
            from_json_value::<Raw<EncryptedEventContent>>(json_data)
                .unwrap()
                .deserialize()
                .unwrap();

        assert_matches!(
            content.scheme,
            EncryptedEventScheme::MegolmV1AesSha2(MegolmV1AesSha2Content {
                ciphertext,
                sender_key,
                device_id,
                session_id,
            }) if ciphertext == "ciphertext"
                && sender_key == "sender_key"
                && device_id == "device_id"
                && session_id == "session_id"
        );

        assert_matches!(
            content.relates_to,
            Some(Relation::Reply { in_reply_to })
                if in_reply_to.event_id == event_id!("$h29iv0s8:example.com")
        );
    }

    #[test]
    fn deserialization_olm() {
        let json_data = json!({
            "sender_key": "test_key",
            "ciphertext": {
                "test_curve_key": {
                    "body": "encrypted_body",
                    "type": 1
                }
            },
            "algorithm": "m.olm.v1.curve25519-aes-sha2"
        });
        let content = from_json_value::<Raw<EncryptedEventContent>>(json_data)
            .unwrap()
            .deserialize()
            .unwrap();

        match content.scheme {
            EncryptedEventScheme::OlmV1Curve25519AesSha2(c) => {
                assert_eq!(c.sender_key, "test_key");
                assert_eq!(c.ciphertext.len(), 1);
                assert_eq!(c.ciphertext["test_curve_key"].body, "encrypted_body");
                assert_eq!(c.ciphertext["test_curve_key"].message_type, 1_u16.into());
            }
            _ => panic!("Wrong content type, expected a OlmV1 content"),
        }
    }

    #[test]
    fn deserialization_failure() {
        assert!(from_json_value::<Raw<EncryptedEventContent>>(
            json!({ "algorithm": "m.megolm.v1.aes-sha2" })
        )
        .unwrap()
        .deserialize()
        .is_err());
    }
}
<|endoftext|>