// Copyright 2015-2016 Brian Smith.
// Portions Copyright (c) 2014, 2015, Google Inc.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

// TODO: enforce maximum input length.

use super::{
    block::{Block, BLOCK_LEN},
    Tag,
};
use crate::{bssl, c, error};

/// A Poly1305 key.
pub struct Key([Block; KEY_BLOCKS]);

impl From<[Block; KEY_BLOCKS]> for Key {
    fn from(value: [Block; KEY_BLOCKS]) -> Self {
        Self(value)
    }
}

pub const KEY_BLOCKS: usize = 2;

pub struct Context {
    opaque: Opaque,
    nonce: Nonce,
    func: Funcs,
}

/// The memory manipulated by the assembly.
#[repr(C, align(8))]
struct Opaque([u8; OPAQUE_LEN]);
const OPAQUE_LEN: usize = 192;

impl Context {
    #[inline]
    pub fn from_key(Key(key_and_nonce): Key) -> Self {
        extern "C" {
            fn GFp_poly1305_blocks(
                state: &mut Opaque,
                input: *const u8,
                len: c::size_t,
                should_pad: Pad,
            );
            fn GFp_poly1305_emit(state: &mut Opaque, tag: &mut Tag, nonce: &Nonce);
        }

        let key = DerivedKey(key_and_nonce[0].clone());
        let nonce = Nonce(key_and_nonce[1].clone());

        let mut ctx = Self {
            opaque: Opaque([0u8; OPAQUE_LEN]),
            nonce,
            func: Funcs {
                blocks_fn: GFp_poly1305_blocks,
                emit_fn: GFp_poly1305_emit,
            },
        };

        // On some platforms `init()` doesn't initialize `funcs`. The
        // return value of `init()` indicates whether it did or not. Since
        // we already gave `func` a default value above, we can ignore the
        // return value assuming `init()` doesn't change `func` if it chose
        // not to initialize it. Note that this is different than what
        // BoringSSL does.
        let _ = init(&mut ctx.opaque, key, &mut ctx.func);

        ctx
    }

    pub fn update_block(&mut self, block: Block, pad: Pad) {
        self.func.blocks(&mut self.opaque, block.as_ref(), pad);
    }

    pub fn update_blocks(&mut self, input: &[u8]) {
        debug_assert_eq!(input.len() % BLOCK_LEN, 0);
        self.func.blocks(&mut self.opaque, input, Pad::Pad);
    }

    pub(super) fn finish(mut self) -> Tag {
        self.func.emit(&mut self.opaque, &self.nonce)
    }
}

#[cfg(test)]
pub fn check_state_layout() {
    let required_state_size = if cfg!(target_arch = "x86") {
        // See comment above `_poly1305_init_sse2` in poly1305-x86.pl.
        Some(4 * (5 + 1 + 4 + 2 + 4 * 9))
    } else if cfg!(target_arch = "x86_64") {
        // See comment above `__poly1305_block` in poly1305-x86_64.pl.
        Some(4 * (5 + 1 + 2 * 2 + 2 + 4 * 9))
    } else {
        // TODO(davidben): Figure out the layout of the struct. For now,
        // `OPAQUE_LEN` is taken from OpenSSL.
        None
    };

    if let Some(required_state_size) = required_state_size {
        assert!(core::mem::size_of::<Opaque>() >= required_state_size);
    }
}

#[repr(C)]
struct DerivedKey(Block);

/// This is *not* an "AEAD nonce"; it's a Poly1305-specific nonce.
#[repr(C)]
struct Nonce(Block);

#[repr(C)]
struct Funcs {
    blocks_fn:
        unsafe extern "C" fn(&mut Opaque, input: *const u8, input_len: c::size_t, should_pad: Pad),
    emit_fn: unsafe extern "C" fn(&mut Opaque, &mut Tag, nonce: &Nonce),
}

#[inline]
fn init(state: &mut Opaque, key: DerivedKey, func: &mut Funcs) -> Result<(), error::Unspecified> {
    extern "C" {
        fn GFp_poly1305_init_asm(
            state: &mut Opaque,
            key: &DerivedKey,
            out_func: &mut Funcs,
        ) -> bssl::Result;
    }
    Result::from(unsafe { GFp_poly1305_init_asm(state, &key, func) })
}

#[repr(u32)]
pub enum Pad {
    AlreadyPadded = 0,
    Pad = 1,
}

impl Funcs {
    #[inline]
    fn blocks(&self, state: &mut Opaque, data: &[u8], should_pad: Pad) {
        unsafe {
            (self.blocks_fn)(state, data.as_ptr(), data.len(), should_pad);
        }
    }

    #[inline]
    fn emit(&self, state: &mut Opaque, nonce: &Nonce) -> Tag {
        let mut tag = Tag(Block::zero());
        unsafe {
            (self.emit_fn)(state, &mut tag, nonce);
        }
        tag
    }
}

/// Implements the original, non-IETF padding semantics.
///
/// This is used by chacha20_poly1305_openssh and the standalone
/// poly1305 test vectors.
pub(super) fn sign(key: Key, input: &[u8]) -> Tag {
    let mut ctx = Context::from_key(key);
    let remainder_len = input.len() % BLOCK_LEN;
    let full_blocks_len = input.len() - remainder_len;
    let (full_blocks, remainder) = input.split_at(full_blocks_len);
    ctx.update_blocks(full_blocks);
    if remainder_len > 0 {
        let mut bytes = [0; BLOCK_LEN];
        bytes[..remainder_len].copy_from_slice(remainder);
        bytes[remainder_len] = 1;
        ctx.update_block(Block::from(&bytes), Pad::AlreadyPadded);
    }
    ctx.finish()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{polyfill::convert::Into_, test};
    use core::convert::TryInto;

    #[test]
    pub fn test_state_layout() {
        check_state_layout();
    }

    // Adapted from BoringSSL's crypto/poly1305/poly1305_test.cc.
    #[test]
    pub fn test_poly1305() {
        test::run(test_file!("poly1305_test.txt"), |section, test_case| {
            assert_eq!(section, "");
            let key = test_case.consume_bytes("Key");
            let key: &[u8; BLOCK_LEN * 2] = key.as_slice().try_into().unwrap();
            let key: [Block; 2] = key.into_();
            let input = test_case.consume_bytes("Input");
            let expected_mac = test_case.consume_bytes("MAC");
            let key = Key::from(key);
            let Tag(actual_mac) = sign(key, &input);
            assert_eq!(expected_mac, actual_mac.as_ref());

            Ok(())
        })
    }
}
