// Rshare - a file sharing program written in Rust.
// Copyright (C) 2017 Cooper Paul EdenDay
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Electronic mail: cedenday@protonmail.com
// Paper mail: 100 Front Street, #31, Bath, Maine, 04530, USA

use std;
use ring;
use bincode;
use semver;
use constants;
use VERSION;

const SIZE_LIMIT: bincode::Bounded = bincode::Bounded(constants::MAX_MESSAGE_SIZE);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Wrapper {
    pub version: String,
    pub data: Vec<u8>,
    pub hash: Vec<u8>,
}

pub fn encode(data: &[u8]) -> Vec<u8> {
    let hash = ring::digest::digest(&ring::digest::SHA256, data);
    bincode::serialize(&Wrapper {
                           version: String::from(VERSION),
                           data: data.to_owned(),
                           hash: hash.as_ref().to_owned(),
                       },
                       SIZE_LIMIT)
            .expect("Couldn't encode data.")
}

pub fn decode<T>(mut reader: T) -> Result<Vec<u8>, String>
    where T: std::io::Read
{
    let wrapper: Result<Wrapper, Box<bincode::ErrorKind>> = bincode::deserialize_from(&mut reader,
                                                                                      SIZE_LIMIT);
    if let Ok(wrapper) = wrapper {
        let client_version = semver::Version::parse(&wrapper.version);
        if let Ok(client_version) = client_version {
            let server_version = semver::VersionReq::parse(&format!("~{}", VERSION))
                .expect("server version error");
            if server_version.matches(&client_version) {
                let digest = ring::digest::digest(&ring::digest::SHA256, &wrapper.data);
                if ring::constant_time::verify_slices_are_equal(digest.as_ref(),
                                                                wrapper.hash.as_slice())
                           .is_ok() {
                    Ok(wrapper.data)
                } else {
                    Err(String::from("Hashes don't match."))
                }
            } else {
                Err(String::from("Client's version is incompatible."))
            }
        } else {
            Err(String::from("Couldn't parse client's version."))
        }
    } else {
        Err(String::from("Couldn't parse Bincode."))
    }
}

#[test]
fn coding() {
    let my_encoded_message = vec!(0; constants::PART_SIZE as usize);
    let encoded = encode(&my_encoded_message);
    let peer_encoded_message = decode(encoded.as_slice()).unwrap();
    assert_eq!(my_encoded_message, peer_encoded_message);
}
