// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Portions derived from Chrony copyright Richard P. Curnow 1997-2003
// and Miroslav Lichvar 2009, 2012-2020
// SPDX-License-Identifier: GPL-2.0-only

//! Data structures representing requests

use bytes::{Buf, BufMut};
use chrony_candm_derive::{ChronyMessage, ChronySerialize};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{convert::TryInto, time::SystemTime};

use crate::common::*;
use crate::reply;

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct Online {
    pub mask: ChronyAddr,
    pub address: ChronyAddr,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct Offline {
    pub mask: ChronyAddr,
    pub address: ChronyAddr,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct Burst {
    pub mask: ChronyAddr,
    pub address: ChronyAddr,
    pub n_good_samples: i32,
    pub n_total_samples: i32,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyMinPoll {
    pub address: ChronyAddr,
    pub new_minpoll: i32,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyMaxPoll {
    pub address: ChronyAddr,
    pub new_maxpoll: i32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyMaxDelay {
    pub address: ChronyAddr,
    pub new_max_delay: ChronyFloat,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyMaxDelayRatio {
    pub address: ChronyAddr,
    pub new_max_delay_ratio: ChronyFloat,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyMaxDelayDevRatio {
    pub address: ChronyAddr,
    pub new_max_delay_dev_ratio: ChronyFloat,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyMinStratum {
    pub address: ChronyAddr,
    pub new_min_stratum: i32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyPollTarget {
    pub address: ChronyAddr,
    pub new_poll_target: i32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyMaxUpdateSkew {
    pub new_max_update_skew: ChronyFloat,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ModifyMakeStep {
    pub limit: i32,
    pub threshold: ChronyFloat,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct Logon {
    pub ts: SystemTime,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct SetTime {
    pub ts: SystemTime,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct Local {
    pub on_off: i32,
    pub stratum: i32,
    pub distance: ChronyFloat,
    pub orphan: i32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct Manual {
    pub option: i32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct SourceData {
    pub index: i32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct AllowDeny {
    pub ip: ChronyAddr,
    pub subnet_bits: i32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct AcCheck {
    pub ip: ChronyAddr,
}

#[repr(u32)]
#[derive(
    Debug,
    Copy,
    Clone,
    PartialEq,
    Eq,
    Ord,
    PartialOrd,
    Hash,
    IntoPrimitive,
    TryFromPrimitive,
    ChronySerialize,
)]
pub enum AddSrcType {
    Server = 1,
    Peer = 2,
    Pool = 3,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct NtpSource {
    pub src_type: AddSrcType,
    pub name: [u8; 256],
    pub port: u32,
    pub minpoll: i32,
    pub maxpoll: i32,
    pub presend_minpoll: i32,
    pub min_stratum: i32,
    pub poll_target: u32,
    pub version: u32,
    pub max_sources: u32,
    pub min_samples: u32,
    pub max_samples: u32,
    pub authkey: u32,
    pub nts_port: u32,
    pub max_delay: ChronyFloat,
    pub max_delay_ratio: ChronyFloat,
    pub max_delay_dev_ratio: ChronyFloat,
    pub min_delay: ChronyFloat,
    pub asymmetry: ChronyFloat,
    pub offset: ChronyFloat,
    pub flags: SourceFlags,
    pub filter_length: i32,
    #[pad = 8]
    pub cert_set: i32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct DelSource {
    pub ip_addr: ChronyAddr,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct DFreq {
    pub dfreq: ChronyFloat,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct DOffset {
    pub doffset: ChronyFloat,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct SourceStats {
    pub index: u32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ClientAccessesByIndex {
    pub first_index: u32,
    pub n_clients: u32,
    pub min_hits: u32,
    pub reset: u32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ManualDelete {
    pub index: u32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct ReselectDistance {
    pub distance: ChronyFloat,
}

#[repr(i32)]
#[derive(
    Debug,
    Copy,
    Clone,
    PartialEq,
    Eq,
    Ord,
    PartialOrd,
    Hash,
    IntoPrimitive,
    TryFromPrimitive,
    ChronySerialize,
)]
pub enum SmoothTimeOption {
    Reset,
    Activate,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct SmoothTime {
    pub option: SmoothTimeOption,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct NtpData {
    pub ip_addr: ChronyAddr,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct NtpSourceName {
    pub ip_addr: ChronyAddr,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct AuthData {
    pub ip_addr: ChronyAddr,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronySerialize)]
pub struct SelectData {
    pub index: u32,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, ChronyMessage)]
pub enum RequestBody {
    Null,
    Online(Online),
    Offline(Offline),
    Burst(Burst),
    ModifyMinPoll(ModifyMinPoll),
    ModifyMaxPoll(ModifyMaxPoll),
    #[pad = 4]
    Dump,
    ModifyMaxDelay(ModifyMaxDelay),
    ModifyMaxDelayRatio(ModifyMaxDelayRatio),
    ModifyMaxUpdateSkew(ModifyMaxUpdateSkew),
    Logon(Logon),
    SetTime(SetTime),
    #[cmd = 13]
    Manual(Manual),
    NSources,
    SourceData(SourceData),
    Rekey,
    Allow(AllowDeny),
    AllowAll(AllowDeny),
    Deny(AllowDeny),
    DenyAll(AllowDeny),
    CmdAllow(AllowDeny),
    CmdAllowAll(AllowDeny),
    CmdDeny(AllowDeny),
    CmdDenyAll(AllowDeny),
    AcCheck(AcCheck),
    CmdAcCheck(AcCheck),
    #[cmd = 29]
    DelSource(DelSource),
    WriteRtc,
    DFreq(DFreq),
    #[cmd = 33]
    Tracking,
    SourceStats(SourceStats),
    RtcReport,
    TrimRtc,
    CycleLogs,
    #[cmd = 41]
    ManualList,
    ManualDelete(ManualDelete),
    MakeStep,
    Activity,
    ModifyMinStratum(ModifyMinStratum),
    ModifyPollTarget(ModifyPollTarget),
    ModifyMaxDelayDevRatio(ModifyMaxDelayDevRatio),
    Reselect,
    ReselectDistance(ReselectDistance),
    ModifyMakeStep(ModifyMakeStep),
    Smoothing,
    SmoothTime(SmoothTime),
    Refresh,
    ServerStats,
    #[cmd = 56]
    Local2(Local),
    NtpData(NtpData),
    #[cmd = 62]
    Shutdown,
    OnOffline,
    AddSource(NtpSource),
    NtpSourceName(NtpSourceName),
    ResetSources,
    AuthData(AuthData),
    ClientAccessesByIndex3(ClientAccessesByIndex),
    SelectData(SelectData),
    ReloadSources,
    DOffset2(DOffset),
}

impl RequestBody {
    pub(crate) fn reply_body_length(&self) -> usize {
        match self {
            Self::Null => 0,
            Self::Online(_) => 0,
            Self::Offline(_) => 0,
            Self::Burst(_) => 0,
            Self::ModifyMinPoll(_) => 0,
            Self::ModifyMaxPoll(_) => 0,
            Self::Dump => 0,
            Self::ModifyMaxDelay(_) => 0,
            Self::ModifyMaxDelayRatio(_) => 0,
            Self::ModifyMaxUpdateSkew(_) => 0,
            Self::Logon(_) => 0,
            Self::SetTime(_) => reply::ManualTimestamp::length(),
            Self::Manual(_) => 0,
            Self::NSources => reply::NSources::length(),
            Self::SourceData(_) => reply::SourceData::length(),
            Self::Rekey => 0,
            Self::Allow(_) => 0,
            Self::AllowAll(_) => 0,
            Self::Deny(_) => 0,
            Self::DenyAll(_) => 0,
            Self::CmdAllow(_) => 0,
            Self::CmdAllowAll(_) => 0,
            Self::CmdDeny(_) => 0,
            Self::CmdDenyAll(_) => 0,
            Self::AcCheck(_) => 0,
            Self::CmdAcCheck(_) => 0,
            Self::DelSource(_) => 0,
            Self::WriteRtc => 0,
            Self::DFreq(_) => 0,
            Self::Tracking => reply::Tracking::length(),
            Self::SourceStats(_) => reply::SourceStats::length(),
            Self::RtcReport => reply::Rtc::length(),
            Self::TrimRtc => 0,
            Self::CycleLogs => 0,
            Self::ManualList => reply::ManualList::length(),
            Self::ManualDelete(_) => 0,
            Self::MakeStep => 0,
            Self::Activity => reply::Activity::length(),
            Self::ModifyMinStratum(_) => 0,
            Self::ModifyPollTarget(_) => 0,
            Self::ModifyMaxDelayDevRatio(_) => 0,
            Self::Reselect => 0,
            Self::ReselectDistance(_) => 0,
            Self::ModifyMakeStep(_) => 0,
            Self::Smoothing => reply::Smoothing::length(),
            Self::SmoothTime(_) => 0,
            Self::Refresh => 0,
            Self::ServerStats => reply::ServerStats::length(),
            Self::Local2(_) => 0,
            Self::NtpData(_) => reply::NtpData::length(),
            Self::Shutdown => 0,
            Self::OnOffline => 0,
            Self::AddSource(_) => 0,
            Self::NtpSourceName(_) => reply::NtpSourceName::length(),
            Self::ResetSources => 0,
            Self::AuthData(_) => reply::AuthData::length(),
            Self::ClientAccessesByIndex3(_) => reply::ClientAccessesByIndex::length(),
            Self::SelectData(_) => reply::SelectData::length(),
            Self::ReloadSources => 0,
            Self::DOffset2(_) => 0,
        }
    }
}

/// A request to Chrony
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct Request {
    /// Sequence number
    pub sequence: u32,
    /// Attempt number
    pub attempt: u16,
    /// Body of the request
    pub body: RequestBody,
}

impl Request {
    pub fn length(&self) -> usize {
        std::cmp::max(
            self.body.body_length() + REQUEST_HEADER_LENGTH,
            self.body.reply_body_length() + REPLY_HEADER_LENGTH,
        )
    }

    pub fn serialize<B: BufMut>(&self, buf: &mut B) {
        let cmd = self.body.cmd();
        let body_length = self.body.body_length();
        let reply_body_length = self.body.reply_body_length();
        let padding_length = std::cmp::max(
            body_length + REQUEST_HEADER_LENGTH,
            reply_body_length + REPLY_HEADER_LENGTH,
        ) - REQUEST_HEADER_LENGTH
            - body_length;

        buf.put_u8(6); //Version
        buf.put_u8(1); //Packet type = request
        buf.put_u8(0); //Reserved
        buf.put_u8(0); //Reserved
        buf.put_u16(cmd);
        buf.put_u16(self.attempt);
        buf.put_u32(self.sequence);
        buf.put_u32(0); //Padding
        buf.put_u32(0); //Padding
        self.body.serialize_body(buf);
        buf.put_slice(vec![0u8; padding_length].as_slice());
    }

    pub fn deserialize<B: Buf>(buf: &mut B) -> Result<Self, DeserializationError> {
        if buf.remaining() < REQUEST_HEADER_LENGTH {
            return Err(DeserializationError::new("message too short"));
        }
        if buf.get_u8() != 6 {
            //Version
            return Err(DeserializationError::new("unsupported version"));
        }

        if buf.get_u8() != 1 {
            //Packet type
            return Err(DeserializationError::new("wrong packet type"));
        }

        buf.get_u8(); //Reserved
        buf.get_u8(); //Reserved
        let cmd = buf.get_u16();
        let attempt = buf.get_u16();
        let sequence = buf.get_u32();
        buf.get_u32(); //Padding
        buf.get_u32(); //Padding
        let body = RequestBody::deserialize_body(cmd, buf)?;

        let body_length = body.body_length();
        let reply_body_length = body.reply_body_length();
        let padding_length = std::cmp::max(body_length, reply_body_length) - body_length;
        if buf.remaining() < padding_length {
            return Err(DeserializationError::new("insufficient padding"));
        }
        buf.advance(padding_length);
        Ok(Self {
            sequence,
            attempt,
            body,
        })
    }

    pub fn cmd(&self) -> u16 {
        self.body.cmd()
    }
}

pub fn increment_attempt(buf: &mut [u8]) {
    let old_attempt = u16::from_be_bytes(buf[6..8].try_into().unwrap());
    let new_attempt = old_attempt + 1;
    buf[6..8].copy_from_slice(&new_attempt.to_be_bytes());
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only

//! All the derive macros defined in this crate are for traits internal to
//! the `chrony-candm` crate. There is no need for any other crate to depend
//! directly on this one.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, DeriveInput};

#[doc(hidden)]
#[proc_macro_derive(ChronySerialize, attributes(pad))]
pub fn derive_chrony_serialize(item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as DeriveInput);
    let name = input.ident;

    match input.data {
        syn::Data::Struct(ds) => derive_chrony_serialize_struct(name, ds),
        syn::Data::Enum(ds) => derive_chrony_serialize_enum(name, input.attrs.as_slice(), ds),
        _ => panic!("Cannot derive ChronySerialize for a non-struct"),
    }
}

fn derive_chrony_serialize_struct(name: proc_macro2::Ident, ds: syn::DataStruct) -> TokenStream {
    let mut length = quote!(0usize);
    let mut serialize = quote!();
    let mut deserialize = quote!();

    for field in ds.fields.iter() {
        let ty = &field.ty;
        let ident = field
            .ident
            .as_ref()
            .expect("Deriving ChronySerialize for tuple structs is not supported.");
        let pad = parse_pad_attr(field.attrs.as_ref());
        length
            .extend(quote! { + <#ty as ::chrony_candm::common::ChronySerialize>::length() + #pad });
        serialize.extend(quote! {
            ::chrony_candm::common::ChronySerialize::serialize(&self.#ident, buf);
            if #pad != 0 {
                buf.put_bytes(0, #pad)
            }
        });
        deserialize.extend(quote! {
            #ident: {
                let field = <#ty as ::chrony_candm::common::ChronySerialize>::deserialize_unchecked(buf)?;
                if #pad != 0 {
                    buf.advance(#pad)
                }
                field
            },
        })
    }

    let expanded = quote! {
        impl ::chrony_candm::common::ChronySerialize for #name {
            fn length() -> usize {
                #length
            }

            fn serialize<B: ::bytes::BufMut>(&self, buf: &mut B) {
                #serialize
            }

            fn deserialize_unchecked<B: ::bytes::Buf>(buf: &mut B) -> ::std::result::Result<Self, ::chrony_candm::common::DeserializationError> {
                ::std::result::Result::Ok(#name {
                    #deserialize
                })
            }

        }
    };

    TokenStream::from(expanded)
}

fn derive_chrony_serialize_enum(
    name: proc_macro2::Ident,
    attrs: &[syn::Attribute],
    _ds: syn::DataEnum,
) -> TokenStream {
    let repr = attrs
        .iter()
        .find_map(|attr| {
            if let syn::Meta::List(meta_list) = attr.parse_meta().ok()? {
                if meta_list.path.get_ident()? == "repr" {
                    let repr = meta_list.nested.iter().next()?;
                    let repr: syn::Ident = parse_quote! { #repr };
                    Some(repr)
                } else {
                    None
                }
            } else {
                None
            }
        })
        .expect("Must specify a #[repr] attribute to derive ChronySerialize for an enum.");

    let expanded = quote! {
        impl ::chrony_candm::common::ChronySerialize for #name {
            fn length() -> usize {
                ::std::mem::size_of::<#repr>()
            }

            fn serialize<B: ::bytes::BufMut>(&self, buf: &mut B) {
                buf.put_slice((&<#repr>::from(*self).to_be_bytes()) as &[u8]);
            }

            fn deserialize_unchecked<B: ::bytes::Buf>(buf: &mut B) -> ::std::result::Result<Self, ::chrony_candm::common::DeserializationError> {
                let mut dst = [0u8; ::std::mem::size_of::<#repr>()];
                buf.copy_to_slice(&mut dst);
                <Self as ::std::convert::TryFrom<#repr>>::try_from(<#repr>::from_be_bytes(dst)).map_err(|_| ::chrony_candm::common::DeserializationError::new("value outside of enum range"))
            }
        }
    };

    TokenStream::from(expanded)
}

#[doc(hidden)]
#[proc_macro_derive(ChronyMessage, attributes(pad, cmd))]
pub fn derive_chrony_message(item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as DeriveInput);
    let name = input.ident;

    let ds = match input.data {
        syn::Data::Enum(ds) => ds,
        _ => panic!("Cannot derive ChronyMessage for a non-enum"),
    };

    let mut length = quote!();
    let mut cmd = quote!();
    let mut serialize = quote!();
    let mut deserialize = quote!();

    let mut index = 0u16;
    for variant in ds.variants.iter() {
        let ident = &variant.ident;
        let mut iter = variant.fields.iter();
        let arg = iter.next();
        if arg.is_some() {
            if iter.next().is_some() {
                panic!("ChronyMessage variants must have at most a single field.")
            }
        }

        let pad = parse_pad_attr(variant.attrs.as_ref());
        if let Some(cmd) = parse_cmd_attr(variant.attrs.as_ref()) {
            if index > cmd {
                panic!("Command numbers must be strictly increasing.")
            }
            index = cmd;
        }

        match arg {
            Some(field) => {
                let ty = &field.ty;
                length.extend(quote! { Self::#ident(_) => <#ty as ::chrony_candm::common::ChronySerialize>::length() + #pad, });
                cmd.extend(quote! { Self::#ident(_) => #index, });
                serialize.extend(quote! {
                    Self::#ident(x) => {
                        if #pad != 0 {
                            buf.put_bytes(0, #pad)
                        }
                        ::chrony_candm::common::ChronySerialize::serialize(x, buf);
                    },
                });
                deserialize.extend(quote! {
                    #index => {
                        if #pad != 0 {
                            buf.advance(#pad)
                        }
                        let body = <#ty as ::chrony_candm::common::ChronySerialize>::deserialize(buf)?;
                        Ok(Self::#ident(body))
                    },
                });
            }
            None => {
                length.extend(quote! { Self::#ident => #pad, });
                cmd.extend(quote! { Self::#ident => #index, });
                serialize.extend(quote! {
                    Self::#ident => {
                        if #pad != 0 {
                            buf.put_bytes(0, #pad)
                        }
                    },
                });
                deserialize.extend(quote! {
                    #index => {
                        if #pad != 0 {
                            buf.advance(#pad)
                        }
                        Ok(Self::#ident)
                    },
                });
            }
        };

        index += 1;
    }

    let expanded = quote! {
        impl ::chrony_candm::common::ChronyMessage for #name {
            fn body_length(&self) -> usize {
                match self {
                    #length
                }
            }

            fn cmd(&self) -> u16 {
                match self {
                    #cmd
                }
            }

            fn serialize_body<B: ::bytes::BufMut>(&self, buf: &mut B) {
                match self {
                    #serialize
                }
            }

            fn deserialize_body<B: ::bytes::Buf>(cmd: u16, buf: &mut B) -> ::std::result::Result<Self, ::chrony_candm::common::DeserializationError> {
                match cmd {
                    #deserialize
                    _ => ::std::result::Result::Err(::chrony_candm::common::DeserializationError::new("unsupported command number"))
                }
            }

        }
    };

    TokenStream::from(expanded)
}

fn parse_pad_attr(attrs: &[syn::Attribute]) -> usize {
    for attr in attrs.iter() {
        if let Ok(syn::Meta::NameValue(meta_namevalue)) = attr.parse_meta() {
            if meta_namevalue.path.is_ident("pad") {
                if let syn::Lit::Int(i) = meta_namevalue.lit {
                    match i.base10_parse() {
                        Ok(size) => return size,
                        Err(e) => panic!("{}", e),
                    }
                } else {
                    panic!("Argument to pad attribute must be an integer literal")
                }
            }
        }
    }

    0
}

fn parse_cmd_attr(attrs: &[syn::Attribute]) -> Option<u16> {
    for attr in attrs.iter() {
        if let Ok(syn::Meta::NameValue(meta_namevalue)) = attr.parse_meta() {
            if meta_namevalue.path.is_ident("cmd") {
                if let syn::Lit::Int(i) = meta_namevalue.lit {
                    match i.base10_parse() {
                        Ok(cmd) => return Some(cmd),
                        Err(e) => panic!("{}", e),
                    }
                } else {
                    panic!("Argument to cmd attribute must be an integer literal")
                }
            }
        }
    }

    None
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use chrono::Utc;
use clock_bound_c::ClockBoundClient;
use std::env;
use std::thread::sleep;
use std::time::Duration;

const ONE_SECOND_IN_NANOSECONDS: u64 = 1000000000;

fn main() {
    let args: Vec<String> = env::args().collect();
    let clock_bound_d_socket = &args[1];

    let client =
        match ClockBoundClient::new_with_path(std::path::PathBuf::from(clock_bound_d_socket)) {
            Ok(client) => client,
            Err(e) => {
                println!("Could not create client: {}", e);
                return;
            }
        };

    // Get the current time in nanoseconds since the Unix Epoch
    let timestamp = Utc::now().timestamp_nanos() as u64;

    // Test with a timestamp 1 second in the future
    let timestamp = timestamp + ONE_SECOND_IN_NANOSECONDS;

    // Checks if a point in time is after the current time's error bounds
    let response = match client.after(timestamp) {
        Ok(response) => response,
        Err(e) => {
            println!("Couldn't complete after request: {}", e);
            return;
        }
    };

    // A clock synchronized via NTP should generally not be a second off. One second past the
    // current time should generally be after the latest error bound and should return true.
    if response.after == false {
        println!(
            "{} nanoseconds since the Unix Epoch is not after the current time's error bounds.",
            timestamp
        )
    } else if response.after == true {
        println!(
            "{} nanoseconds since the Unix Epoch is after the current time's error bounds.",
            timestamp
        )
    }

    println!("Waiting 2 seconds...");

    // Checking again after the timestamp has passed should no longer be after the latest error
    // bound and return false.
    sleep(Duration::from_secs(2));

    // Checks if a point in time is after the current time's error bounds
    let response = match client.after(timestamp) {
        Ok(response) => response,
        Err(e) => {
            println!("Couldn't complete after request: {}", e);
            return;
        }
    };

    if response.after == false {
        println!(
            "{} nanoseconds since the Unix Epoch is not after the current time's error bounds.",
            timestamp
        )
    } else if response.after == true {
        println!(
            "{} nanoseconds since the Unix Epoch is after the current time's error bounds.",
            timestamp
        )
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use chrono::Utc;
use clock_bound_c::ClockBoundClient;
use std::env;
use std::thread::sleep;
use std::time::Duration;

fn main() {
    let args: Vec<String> = env::args().collect();
    let clock_bound_d_socket = &args[1];

    let client =
        match ClockBoundClient::new_with_path(std::path::PathBuf::from(clock_bound_d_socket)) {
            Ok(client) => client,
            Err(e) => {
                println!("Could not create client: {}", e);
                return;
            }
        };

    // Get the current time in nanoseconds since the Unix Epoch
    let timestamp = Utc::now().timestamp_nanos() as u64;

    // Checks if a point in time is before the current time's error bounds
    let response = match client.before(timestamp) {
        Ok(response) => response,
        Err(e) => {
            println!("Couldn't complete before request: {}", e);
            return;
        }
    };

    // With a before request done using the current time for comparison, it is likely that the
    // request is processed faster than the local Clock Error Bound. This means that generally the
    // current time will not be before the earliest bound and therefore return false.
    if response.before == false {
        println!(
            "{} nanoseconds since the Unix Epoch is not before the current time's error bounds.",
            timestamp
        )
    } else if response.before == true {
        println!(
            "{} nanoseconds since the Unix Epoch is before the current time's error bounds.",
            timestamp
        )
    }

    println!("Waiting 1 second...");

    // Checking again after a brief period of time (a pessimistic 1 second for this example's sake)
    // the timestamp should be before the earliest error bound and return true.
    sleep(Duration::from_secs(1));

    // Checks if a point in time is before the current time's error bounds
    let response = match client.before(timestamp) {
        Ok(response) => response,
        Err(e) => {
            println!("Couldn't complete before request: {}", e);
            return;
        }
    };

    if response.before == false {
        println!(
            "{} nanoseconds since the Unix Epoch is not before the current time's error bounds.",
            timestamp
        )
    } else if response.before == true {
        println!(
            "{} nanoseconds since the Unix Epoch is before the current time's error bounds.",
            timestamp
        )
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use chrono::prelude::DateTime;
use chrono::Utc;
use clock_bound_c::ClockBoundClient;
use std::env;
use std::time::{Duration, UNIX_EPOCH};

fn main() {
    let args: Vec<String> = env::args().collect();
    let clock_bound_d_socket = &args[1];

    let client =
        match ClockBoundClient::new_with_path(std::path::PathBuf::from(clock_bound_d_socket)) {
            Ok(client) => client,
            Err(e) => {
                println!("Could not create client: {}", e);
                return;
            }
        };

    let response = match client.now() {
        Ok(response) => response,
        Err(e) => {
            println!("Could not complete now request: {}", e);
            return;
        }
    };

    let earliest_d = UNIX_EPOCH + Duration::from_nanos(response.bound.earliest);
    let latest_d = UNIX_EPOCH + Duration::from_nanos(response.bound.latest);
    let timestamp_d = UNIX_EPOCH + Duration::from_nanos(response.timestamp);
    let datetime_earliest = DateTime::<Utc>::from(earliest_d);
    let datetime_latest = DateTime::<Utc>::from(latest_d);
    let datetime_timestamp = DateTime::<Utc>::from(timestamp_d);
    let datetime_str_earliest = datetime_earliest.format("%Y-%m-%d %H:%M:%S.%f").to_string();
    let datetime_str_latest = datetime_latest.format("%Y-%m-%d %H:%M:%S.%f").to_string();
    let datetime_str_timestamp = datetime_timestamp
        .format("%Y-%m-%d %H:%M:%S.%f")
        .to_string();

    println!(
        "The UTC timestamp {} has the following error bounds.",
        datetime_str_timestamp
    );
    println!(
        "In nanoseconds since the Unix epoch: ({:?},{:?})",
        response.bound.earliest, response.bound.latest
    );
    println!(
        "In UTC in date/time format: ({}, {})",
        datetime_str_earliest, datetime_str_latest
    );
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use chrono::prelude::DateTime;
use chrono::Utc;
use clock_bound_c::ClockBoundClient;
use std::env;

fn foo() -> fn() -> i32 {
    move || return 1
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let clock_bound_d_socket = &args[1];

    let client =
        match ClockBoundClient::new_with_path(std::path::PathBuf::from(clock_bound_d_socket)) {
            Ok(client) => client,
            Err(e) => {
                println!("Could not create client: {}", e);
                return;
            }
        };

    let (response, _result) = match client.timing(foo()) {
        Ok((response, result)) => (response, result),
        Err((e, _res)) => {
            println!("Could not complete timing request: {}", e);
            return;
        }
    };

    let datetime_earliest: DateTime<Utc> = response.earliest_start.into();
    let datetime_latest: DateTime<Utc> = response.latest_finish.into();
    let datetime_str_earliest = datetime_earliest.format("%Y-%m-%d %H:%M:%S.%f").to_string();
    let datetime_str_latest = datetime_latest.format("%Y-%m-%d %H:%M:%S.%f").to_string();

    println!(
        "Earliest start time for the timing request: {:?}", datetime_str_earliest
    );
    println!(
        "Latest finish time for the timing request: {:?}", datetime_str_latest
    );
    println!(
        "Minimum execution duration of timing request: {:?}", response.min_execution_time
    );
    println!(
        "Maximum execution duration of timing request: {:?}", response.max_execution_time
    )
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;

/// ClockBoundCError enumerates all possible errors.
#[derive(Error, Debug)]
pub enum ClockBoundCError {
    /// Represents an error when trying to connect to ClockBoundD's socket.
    #[error("Could not connect to ClockBoundD's socket. {0}")]
    ConnectError(#[source] std::io::Error),
    /// Represents an error when trying to bind to a socket.
    #[error("Could not bind to socket. {0}")]
    BindError(#[source] std::io::Error),
    /// Represents an error when trying to set permissions on a socket file.
    #[error("Could not set permissions on socket. {0}")]
    SetPermissionsError(#[source] std::io::Error),
    /// Represents an error when trying to send a message to ClockBoundD.
    #[error("Could not send message to ClockBoundD. {0}")]
    SendMessageError(#[source] std::io::Error),
    /// Represents an error when trying receive a message from ClockBoundD.
    #[error("Could not receive message from ClockBoundD. {0}")]
    ReceiveMessageError(#[source] std::io::Error),
    /// Represents an error when trying to write a request.
    #[error("Could not write a request. {0}")]
    WriteRequestError(#[source] std::io::Error),
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//! A client library to communicate with ClockBoundD.
//! # Usage
//! ClockBoundC requires ClockBoundD to be running to work. See [ClockBoundD documentation](../clock-bound-d/README.md) for installation instructions.
//!
//! For Rust programs built with Cargo, add "clock-bound-c" as a dependency in your Cargo.toml.
//!
//! For example:
//! ```text
//! [dependencies]
//! clock-bound-c = "0.1.0"
//! ```
//!
//! ## Examples
//!
//! Runnable examples exist at [examples](examples) and can be run with Cargo.
//!
//! "/run/clockboundd/clockboundd.sock" is the expected default clockboundd.sock location, but the examples can be run with a
//! different socket location if desired:
//!
//! ```text
//! cargo run --example now /run/clockboundd/clockboundd.sock
//! cargo run --example before /run/clockboundd/clockboundd.sock
//! cargo run --example after /run/clockboundd/clockboundd.sock
//! cargo run --example timing /run/clockboundd/clockboundd.sock
//! ```
//!
//! # Updating README
//!
//! This README is generated via [cargo-readme](https://crates.io/crates/cargo-readme). Updating can be done by running:
//! ```text
//! cargo readme > README.md
//! ```
mod error;

use crate::error::ClockBoundCError;
use byteorder::{ByteOrder, NetworkEndian};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::UnixDatagram;
use std::path::PathBuf;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// The default Unix Datagram Socket file that is generated by ClockBoundD.
pub const CLOCKBOUNDD_SOCKET_ADDRESS_PATH: &str = "/run/clockboundd/clockboundd.sock";
/// The prefix of a ClockBoundC socket name. It is appended with a randomized string
/// Ex: clockboundc-G0uv7ULMLNyeLIGKSejG.sock
pub const CLOCKBOUNDC_SOCKET_NAME_PREFIX: &str = "clockboundc";

/// Setting clock frequency to 1ppm to match chrony
pub const FREQUENCY_ERROR: u64 = 1; //1ppm

/// A structure for containing the error bounds returned from ClockBoundD. The values represent
/// the time since the Unix Epoch in nanoseconds.
pub struct Bound {
    /// System time minus the error calculated from chrony in nanoseconds since the Unix Epoch
    pub earliest: u64,
    /// System time plus the error calculated from chrony in nanoseconds since the Unix Epoch
    pub latest: u64,
}

/// A structure for holding the header of the received response.
pub struct ResponseHeader {
    /// The version of the response received from ClockBoundD.
    pub response_version: u8,
    /// The type of the response received from ClockBoundD.
    pub response_type: u8,
    /// A flag representing if Chrony is reporting as unsynchronized.
    pub unsynchronized_flag: bool,
}

/// A structure for holding the response of a now request.
pub struct ResponseNow {
    pub header: ResponseHeader,
    pub bound: Bound,
    /// The timestamp, represented as nanoseconds since the Unix Epoch, bounded against.
    pub timestamp: u64,
}

/// A structure for holding the response of a before request.
pub struct ResponseBefore {
    pub header: ResponseHeader,
    /// A boolean indicating if the requested time is before the current error bounds or not.
    pub before: bool,
}

/// A structure for holding the response of an after request.
pub struct ResponseAfter {
    pub header: ResponseHeader,
    /// A boolean indicating if the requested time is after the current error bounds or not.
    pub after: bool,
}

/// A structure for holding the response of a timing request.
#[derive(Debug)]
pub struct TimingResult {
    /// Callback began executing no earlier than this time
    pub earliest_start : SystemTime,
    /// Callback finished executing no later than this time
    pub latest_finish : SystemTime,
    /// No less than this amount of time elapsed from when timing() was called
    /// to when it returned.
    pub min_execution_time : Duration,
    /// No more than this amount of time elapsed when the callback was invoked
    /// to when it returned.
    pub max_execution_time : Duration
}

/// A structure for holding a client to communicate with ClockBoundD.
pub struct ClockBoundClient {
    /// A ClockBoundClient must have a socket to communicate with ClockBoundD.
    socket: UnixDatagram,
}

impl ClockBoundClient {
    /// Create a new ClockBoundClient using the default clockboundd.sock path at
    /// "/run/clockboundd/clockboundd.sock".
    ///
    /// # Examples
    ///
    /// ```
    /// use clock_bound_c::ClockBoundClient;
    /// let client = match ClockBoundClient::new(){
    ///     Ok(client) => client,
    ///     Err(e) => {
    ///         println!("Couldn't create client: {}", e);
    ///         return
    ///     }
    /// };
    pub fn new() -> Result<ClockBoundClient, ClockBoundCError> {
        ClockBoundClient::new_with_path(std::path::PathBuf::from(CLOCKBOUNDD_SOCKET_ADDRESS_PATH))
    }
    /// Create a new ClockBoundClient using a defined clockboundd.sock path.
    ///
    /// The expected default socket path is at "/run/clockboundd/clockboundd.sock", but if a
    /// different desired location has been set up this allows its usage.
    ///
    /// If using the default location at "/run/clockboundd/clockboundd.sock", use new() instead.
    ///
    /// # Arguments
    ///
    /// * `clock_bound_d_socket` - The path at which the clockboundd.sock lives.
    ///
    /// # Examples
    ///
    /// ```
    /// use clock_bound_c::ClockBoundClient;
    /// let client = match ClockBoundClient::new_with_path(std::path::PathBuf::from("/run/clockboundd/clockboundd.sock")){
    ///     Ok(client) => client,
    ///     Err(e) => {
    ///         println!("Couldn't create client: {}", e);
    ///         return
    ///     }
    /// };
    /// ```
    pub fn new_with_path(
        clock_bound_d_socket: PathBuf,
    ) -> Result<ClockBoundClient, ClockBoundCError> {
        let client_path = get_socket_path();

        // Binding will fail if the socket file already exists. However, since the socket file is
        // uniquely created based on the current time this should not fail.
        let sock = match UnixDatagram::bind(client_path.as_path()) {
            Ok(sock) => sock,
            Err(e) => return Err(ClockBoundCError::BindError(e)),
        };

        let mode = 0o666;
        let permissions = fs::Permissions::from_mode(mode);
        match fs::set_permissions(client_path, permissions) {
            Err(e) => return Err(ClockBoundCError::SetPermissionsError(e)),
            _ => {}
        }

        match sock.connect(clock_bound_d_socket.as_path()) {
            Err(e) => return Err(ClockBoundCError::ConnectError(e)),
            _ => {}
        }

        Ok(ClockBoundClient { socket: sock })
    }

    /// Returns the bounds of the current system time +/- the error calculated from chrony.
    ///
    /// # Examples
    ///
    /// ```
    /// use clock_bound_c::ClockBoundClient;
    /// let client = match ClockBoundClient::new(){
    ///     Ok(client) => client,
    ///     Err(e) => {
    ///         println!("Couldn't create client: {}", e);
    ///         return
    ///     }
    /// };
    /// let response = match client.now(){
    ///     Ok(response) => response,
    ///     Err(e) => {
    ///         println!("Couldn't complete now request: {}", e);
    ///         return
    ///     }
    /// };
    /// ```
    pub fn now(&self) -> Result<ResponseNow, ClockBoundCError> {
        // Header
        // 1st - Version
        // 2nd - Command Type
        // 3rd, 4th - Reserved
        let mut request: [u8; 4] = [1, 1, 0, 0];

        match self.socket.send(&mut request) {
            Err(e) => return Err(ClockBoundCError::SendMessageError(e)),
            _ => {}
        }
        let mut response: [u8; 20] = [0; 20];
        match self.socket.recv(&mut response) {
            Err(e) => return Err(ClockBoundCError::ReceiveMessageError(e)),
            _ => {}
        }
        let response_version = response[0];
        let response_type = response[1];
        let unsynchronized_flag = response[2] != 0;
        let earliest = NetworkEndian::read_u64(&response[4..12]);
        let latest = NetworkEndian::read_u64(&response[12..20]);
        Ok(ResponseNow {
            header: ResponseHeader {
                response_version,
                response_type,
                unsynchronized_flag,
            },
            bound: Bound { earliest, latest },
            // Since the bounds are the system time +/- the Clock Error Bound, the system time
            // timestamp can be calculated with the below formula.
            timestamp: (latest - ((latest - earliest) / 2)),
        })
    }

    /// Returns true if the provided timestamp is before the earliest error bound.
    /// Otherwise, returns false.
    ///
    /// # Arguments
    ///
    /// * `before_time` - A timestamp, represented as nanoseconds since the Unix Epoch, that is
    /// tested against the earliest error bound.
    ///
    /// # Examples
    ///
    /// ```
    /// use clock_bound_c::ClockBoundClient;
    /// let client = match ClockBoundClient::new(){
    ///     Ok(client) => client,
    ///     Err(e) => {
    ///         println!("Couldn't create client: {}", e);
    ///         return
    ///     }
    /// };
    /// // Using 0 which equates to the Unix Epoch
    /// let response = match client.before(0){
    ///     Ok(response) => response,
    ///     Err(e) => {
    ///         println!("Couldn't complete before request: {}", e);
    ///         return
    ///     }
    /// };
    /// ```
    pub fn before(&self, before_time: u64) -> Result<ResponseBefore, ClockBoundCError> {
        // Header
        // 1st - Version
        // 2nd - Command Type
        // 3rd, 4th - Reserved
        let mut request: [u8; 12] = [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

        // Body
        NetworkEndian::write_u64(&mut request[4..12], before_time);

        match self.socket.send(&mut request) {
            Err(e) => return Err(ClockBoundCError::SendMessageError(e)),
            _ => {}
        }
        let mut response: [u8; 5] = [0; 5];
        match self.socket.recv(&mut response) {
            Err(e) => return Err(ClockBoundCError::ReceiveMessageError(e)),
            _ => {}
        }
        let response_version = response[0];
        let response_type = response[1];
        let unsynchronized_flag = response[2] != 0;
        let before = response[4] != 0;
        Ok(ResponseBefore {
            header: ResponseHeader {
                response_version,
                response_type,
                unsynchronized_flag,
            },
            before,
        })
    }

    /// Returns true if the provided timestamp is after the latest error bound.
    /// Otherwise, returns false.
    ///
    /// # Arguments
    ///
    /// * `after_time` - A timestamp, represented as nanoseconds since the Unix Epoch, that is
    /// tested against the latest error bound.
    ///
    /// # Examples
    ///
    /// ```
    /// use clock_bound_c::ClockBoundClient;
    /// let client = match ClockBoundClient::new(){
    ///     Ok(client) => client,
    ///     Err(e) => {
    ///         println!("Couldn't create client: {}", e);
    ///         return
    ///     }
    /// };
    /// // Using 0 which equates to the Unix Epoch
    /// let response = match client.after(0){
    ///     Ok(response) => response,
    ///     Err(e) => {
    ///         println!("Couldn't complete after request: {}", e);
    ///         return
    ///     }
    /// };
    /// ```
    pub fn after(&self, after_time: u64) -> Result<ResponseAfter, ClockBoundCError> {
        // Header
        // 1st - Version
        // 2nd - Command Type
        // 3rd, 4th - Reserved
        let mut request: [u8; 12] = [1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

        // Body
        NetworkEndian::write_u64(&mut request[4..12], after_time);

        match self.socket.send(&mut request) {
            Err(e) => return Err(ClockBoundCError::SendMessageError(e)),
            _ => {}
        }
        let mut response: [u8; 5] = [0; 5];
        match self.socket.recv(&mut response) {
            Err(e) => return Err(ClockBoundCError::ReceiveMessageError(e)),
            _ => {}
        }
        let response_version = response[0];
        let response_type = response[1];
        let unsynchronized_flag = response[2] != 0;
        let after = response[4] != 0;
        Ok(ResponseAfter {
            header: ResponseHeader {
                response_version,
                response_type,
                unsynchronized_flag,
            },
            after,
        })
    }

    ///Execute `f` and return bounds on execution time
    pub fn timing<A, F>(&self, f: F) -> Result<(TimingResult, A), (ClockBoundCError, Result<A,F>)>
        where F: FnOnce() -> A {

        // Get the first timestamps

        // Header
        // 1st - Version
        // 2nd - Command Type
        // 3rd, 4th - Reserved
        let mut request: [u8; 4] = [1, 1, 0, 0];

        match self.socket.send(&mut request) {
            Err(e) => return Err((ClockBoundCError::SendMessageError(e), Err(f))),
            _ => {}
        }
        let mut response: [u8; 20] = [0; 20];
        match self.socket.recv(&mut response) {
            Err(e) => return Err((ClockBoundCError::ReceiveMessageError(e), Err(f))),
            _ => {}
        }
        let earliest_start = NetworkEndian::read_u64(&response[4..12]);
        let latest_start = NetworkEndian::read_u64(&response[12..20]);

        // Execute the provided function, f
        let callback = f();

        // Get the second timestamps
        let mut request: [u8; 4] = [1, 1, 0, 0];
        match self.socket.send(&mut request) {
            Err(e) => return Err((ClockBoundCError::SendMessageError(e), Ok(callback))),
            _ => {}
        }
        let mut response: [u8; 20] = [0; 20];
        match self.socket.recv(&mut response) {
            Err(e) => return Err((ClockBoundCError::ReceiveMessageError(e), Ok(callback))),
            _ => {}
        }
        let earliest_finish = NetworkEndian::read_u64(&response[4..12]);
        let latest_finish = NetworkEndian::read_u64(&response[12..20]);

        // Calculate midpoints of start and finish
        let start_midpoint = (earliest_start + latest_start)/2;
        let end_midpoint = (earliest_finish + latest_finish)/2;

        // Convert to SystemTime
        let earliest_start = UNIX_EPOCH + Duration::from_nanos(earliest_start);
        let latest_finish = UNIX_EPOCH + Duration::from_nanos(latest_finish);

        // Calculates duration between the two midpoints
        let execution_time = end_midpoint - start_midpoint;
        let error_rate = (execution_time * FREQUENCY_ERROR) / 1_000_000 +
            //Ugly way of saying .div_ceil() until it stabilizes
            if (execution_time * FREQUENCY_ERROR) % 1_000_000 == 0 { 0 } else { 1 };

        let min_execution_time = Duration::from_nanos(execution_time - error_rate);
        let max_execution_time = Duration::from_nanos(execution_time + error_rate);

        Ok((TimingResult{
            earliest_start,
            latest_finish,
            min_execution_time,
            max_execution_time
        }, callback))
    }
}

impl Drop for ClockBoundClient {
    /// Remove the client socket file when a ClockBoundClient is dropped.
    fn drop(&mut self) {
        if let Ok(addr) = self.socket.local_addr() {
            if let Some(path) = addr.as_pathname() {
                let _ = self.socket.shutdown(std::net::Shutdown::Both);
                let _ = std::fs::remove_file(path);
            }
        }
    }
}

/// Create a unique client socket file in the system's temp directory
///
/// The socket name will have clockboundc as a prefix, followed by a random string of 20
/// alphanumeric characters.
/// Ex: clockboundc-G0uv7ULMLNyeLIGKSejG.sock
fn get_socket_path() -> PathBuf {
    let dir = std::env::temp_dir();
    let mut rng = thread_rng();
    let random_str: String = (&mut rng)
        .sample_iter(Alphanumeric)
        .take(20)
        .map(char::from)
        .collect();

    let client_path_buf =
        dir.join(CLOCKBOUNDC_SOCKET_NAME_PREFIX.to_owned() + "-" + &*random_str + ".sock");
    return client_path_buf;
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only
use chrony_candm::reply::Tracking;

/// A struct containing the Clock Error Bound. The Clock Error Bound is the bound of error that is
/// accumulated for a NTP packet.
///
/// Clock Error Bound is calculated with the formula:
///
/// |System time offset| + Root dispersion + (Root delay / 2)
///
/// Where:
///
/// System time offset - Difference between chrony's estimate of the "true time" from it's root
/// reference and the system's clock.
///
/// Root dispersion - Sum of dispersion across each strata.
///
/// Root delay - Sum of network latency accumulated across each strata.
#[derive(Clone, Debug)]
pub struct ClockErrorBound {
    pub ceb: f64,
}

impl ClockErrorBound {
    /// Calculate the Clock Error Bound using the Tracking information from Chrony.
    pub fn from(packet: Tracking) -> ClockErrorBound {
        ClockErrorBound {
            ceb: get_clock_error_bound(
                f64::from(packet.current_correction),
                f64::from(packet.root_dispersion),
                f64::from(packet.root_delay),
            ),
        }
    }
}

/// Get the Clock Error Bound.
///
/// Clock Error Bound is calculated with the formula:
/// |System time offset| + Root dispersion + (Root delay / 2)
///
/// # Arguments
/// * `system_time_offset` - Difference between chrony's estimate of the "true time" from it's root
/// reference and the system's clock.
/// * `root_dispersion` - Sum of dispersion across each strata.
/// * `root_delay` - Sum of network latency accumulated across each strata.
pub fn get_clock_error_bound(
    system_time_offset: f64,
    root_dispersion: f64,
    root_delay: f64,
) -> f64 {
    round_f64_nanos(system_time_offset.abs() + root_dispersion + (root_delay / 2_f64))
}

/// Round a f64 to nanosecond precision.
///
/// A ChronyFloat as defined by Chrony can introduce some loss when converting from a f64.
/// However, the loss is in a value of precision that is not needed by ClockBound. Since ClockBound
/// provides bounds in the nanosecond accuracy this extra loss in precision can be ignored by
/// rounding to the nearest nanosecond.
///
/// # Arguments
/// * `value` - A f64 value to round to the nearest nanosecond.
pub fn round_f64_nanos(value: f64) -> f64 {
    (value * 1000000000.0).round() / 1000000000.0
}

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

    #[test]
    fn round_f64_nanos_successful() {
        let value = round_f64_nanos(0.0000000055_f64);
        assert_eq!(value, 0.000000006);
    }

    #[test]
    fn get_clock_error_bound_successful() {
        let ceb = get_clock_error_bound(0.0002_f64, 0.0001_f64, 0.0004_f64);
        assert_eq!(ceb, 0.0005_f64);
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only
use chrony_candm::blocking_query;
use chrony_candm::reply::{ReplyBody, Tracking};
use chrony_candm::request::RequestBody;
use chrony_candm::ClientOptions;
use log::{error, warn};
use std::net::{Ipv6Addr, SocketAddr, SocketAddrV6};
use std::str::FromStr;
use std::time::SystemTime;
use tokio::sync::watch::Sender;

/// The interval in seconds that we attempt to get an initial poll from chrony.
pub const CHRONY_TRACKING_INITIALIZE_INTERVAL: u64 = 10;

/// The interval in seconds that the chrony poller thread runs
pub const CHRONY_POLL_INTERVAL: u64 = 1;

/// A leap status value of 3 means unsynchronized.
pub const LEAP_STATUS_UNSYNCHRONIZED: u16 = 3;

/// Poll chronyd for tracking information.
pub fn poll() -> Option<Tracking> {
    let request_body = RequestBody::Tracking;

    // Chrony by default can be communicated with via localhost on port 323
    let server_addr = SocketAddr::V6(SocketAddrV6::new(
        Ipv6Addr::from_str("::1").unwrap(),
        323,
        0,
        0,
    ));
    let result = blocking_query(request_body, ClientOptions::default(), &server_addr);

    match result {
        Err(e) => {
            error!("No reply from chronyd. Is it running? Error: {:?}", e);
            None
        }
        Ok(reply) => {
            if let ReplyBody::Tracking(body) = reply.body {
                Some(body)
            } else {
                error!(
                    "Reply from chronyd was invalid. Expected tracking data. Reply: {:?}",
                    reply
                );
                None
            }
        }
    }
}

/// Initialize tracking information from a poll to Chrony.
///
/// We need to initialize tracking information from a poll to chrony at least once before we can
/// start sending responses to clients. This ensures that chrony is running at startup and that
/// we can get initial tracking information to work with.
pub fn initialize_tracking() -> Tracking {
    loop {
        // Do an initial poll to initialize the tracking data before starting the Chrony poller
        // thread
        let result = poll();

        match result {
            Some(tracking) => {
                // When chronyd starts it takes a few seconds to initially synchronize. This check
                // makes sure that chronyd is initially synchronized before tracking data is
                // considered initialized. Otherwise there is a brief period when chronyd starts
                // that all tracking information will be default values.
                if tracking.leap_status != LEAP_STATUS_UNSYNCHRONIZED {
                    return tracking;
                }
                warn!(
                    "chronyd is reporting as unsynchronized. Starting clockboundd requires chronyd \
                 to be synchronized. Retrying..."
                );
            }
            None => error!("Unable to initialize tracking information from Chrony."),
        };

        // Sleep and retry. We don't want to constantly spam the logs with errors while we wait for
        // Chrony to startup.
        std::thread::sleep(std::time::Duration::from_secs(
            CHRONY_TRACKING_INITIALIZE_INTERVAL,
        ));
    }
}

/// Start the Chrony poller thread.
/// This thread obtains the tracking information from Chrony every second by default.
///
/// # Arguments
///
/// * `tx_tracking` - A tokio::sync::watch::channel sender handle that is used for sending Chrony
/// tracking information to the main thread.
/// * `tx_error_flag` - A tokio::sync::watch::channel sender handle that is used for sending an
/// error flag, indicating that the last Chrony poll failed, to the main thread.
pub fn start_chrony_poller(tx_tracking: Sender<Tracking>, tx_error_flag: Sender<bool>) {
    std::thread::spawn(move || loop {
        let result = poll();

        // If an error happens when polling Chrony, or attempting to send the tracking data to the
        // main thread, update the error_flag to true.
        let error_flag = match result {
            Some(tracking) => {
                // If chronyd is restarted it will report default values until it first syncs to a
                // source. If chronyd is reporting ref time as the Unix Epoch, then do not send
                // tracking information to clockboundd's main thread. This lets clockboundd continue
                // to use the last set of valid tracking data from chronyd instead of having the
                // clock error bound jump up until chronyd syncs to a source.
                if tracking.ref_time != SystemTime::UNIX_EPOCH {
                    // Send tracking data to the main thread
                    match tx_tracking.send(tracking.clone()) {
                        Ok(..) => false,
                        Err(e) => {
                            error!("Unable to send tracking data to main thread: {:?}", e);
                            true
                        }
                    }
                } else {
                    warn!(
                        "chronyd has not synced to a source since starting. Calculating error \
                    locally until chronyd synchronizes."
                    );
                    true
                }
            }
            None => true,
        };

        // Send error flag to the main thread
        match tx_error_flag.send(error_flag.clone()) {
            Ok(..) => (),
            Err(e) => error!("Unable to send Chrony error flag to main thread: {:?}", e),
        }

        std::thread::sleep(std::time::Duration::from_secs(CHRONY_POLL_INTERVAL));
    });
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only
//! A daemon to provide clients with an error bounded timestamp interval.
//!
//! # Prerequisites
//!
//! [chronyd](https://chrony.tuxfamily.org/) must be running in order to run ClockBoundD. If running
//! on Amazon Linux 2, chronyd is already set as the default NTP daemon for you.
//!
//! If running on Amazon EC2, see the [EC2 User Guide](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-time.html) for more information on installing Chrony and syncing
//! to the Amazon Time Sync Service.
//!
//! # Installation
//! ## Cargo
//! ClockBoundD can be installed using Cargo. Instructions on how to install cargo can be found at
//! [doc.rust-lang.org](https://doc.rust-lang.org/cargo/getting-started/installation.html).
//!
//! If it's your first time installing Cargo on an AL2 EC2 instance you may need to also install gcc:
//! ```text
//! sudo yum install gcc
//! ```
//!
//! Run cargo install:
//! ```text
//! cargo install clock-bound-d
//! ```
//!
//! If cargo was installed with the rustup link above the default install location will be at
//! ```text
//! $HOME/.cargo/bin/clockboundd
//! ```
//!
//! ## Systemd configuration
//!
//! If built from source using cargo, it is recommended to set up systemd to manage ClockBoundD.
//!
//! Configuration Example:
//!
//! * Move binary to the location you want to run it from
//! ```text
//! sudo mv $HOME/.cargo/bin/clockboundd /usr/local/bin/clockboundd
//! ```
//!
//! * Create system user that systemd can use
//! ```text
//! sudo useradd -r clockbound
//! ```
//!
//! * Create unit file /usr/lib/systemd/system/clockboundd.service with the following contents
//! ```text
//! [Unit]
//! Description=ClockBoundD
//!
//! [Service]
//! Type=simple
//! Restart=always
//! RestartSec=10
//! ExecStart=/usr/local/bin/clockboundd
//! RuntimeDirectory=clockboundd
//! WorkingDirectory=/run/clockboundd
//! User=clockbound
//!
//! [Install]
//! WantedBy=multi-user.target
//! ```
//!
//! * Reload systemd
//! ```text
//! sudo systemctl daemon-reload
//! ```
//!
//! * Enable ClockBoundD to start at boot
//! ```text
//! sudo systemctl enable clockboundd
//! ```
//!
//! * Start ClockBoundD now
//! ```text
//! sudo systemctl start clockboundd
//! ```
//!
//! You can then check the status of the service with:
//! ```text
//! systemctl status clockboundd
//! ```
//!
//! # Usage
//!
//! To communicate with ClockBoundD a client is required. A rust client library exists at [ClockBoundC](../clock-bound-c/README.md)
//! that an application can use to communicate with ClockBoundD.
//!
//! ## Custom Client
//!
//! If you want to create a custom client see [Custom Client](../README.md#custom-client) for more information.
//!
//! # Logging
//!
//! By default, ClockBoundD logs to syslog at /var/log/daemon.log.
//!
//! syslog logs can be viewed with journalctl:
//! ```text
//! journalctl -u clockboundd
//! ```
//! # Updating README
//!
//! This README is generated via [cargo-readme](https://crates.io/crates/cargo-readme). Updating can be done by running:
//! ```text
//! cargo readme > README.md
//! ```
mod ceb;
mod chrony_poller;
mod response;
mod server;
mod socket;
mod tracking;

use crate::chrony_poller::start_chrony_poller;
use crate::server::ClockBoundServer;
use chrony_candm::reply::Tracking;
use log::{error, info};
use tokio::sync::watch;
use tokio::sync::watch::Receiver;

/// Start ClockBoundD.
///
/// # Arguments
///
/// * `max_clock_error` - The assumed maximum frequency error that a system clock can gain between updates in ppm.
pub fn run(max_clock_error: f64) {
    info!("Initialized ClockBoundD");

    // Do an initial poll to initialize the tracking data before starting the Chrony poller
    // thread
    let tracking = chrony_poller::initialize_tracking();
    // Set up a channel for sending tracking data between threads
    let (tx_tracking, rx_tracking) = watch::channel(tracking);
    // An error flag used to inform the main thread if there was an error with the most recent
    // poll to Chrony. This flag will make it's way to clients via the response header.
    let error_flag = false;
    // Set up a channel for sending error flag data between threads
    let (tx_error_flag, rx_error_flag) = watch::channel(error_flag);
    // Initialize the server with initial tracking data
    let server = ClockBoundServer::new(tracking);

    // Chrony poller thread
    start_chrony_poller(tx_tracking, tx_error_flag);
    info!("Initialized Chrony Poller thread");

    // Start main thread
    start_main_thread(server, rx_tracking, rx_error_flag, max_clock_error);
}

/// Start the main thread of ClockBoundD.
/// This thread processes any client requests that are received.
///
/// # Arguments
///
/// * `server` - A ClockBoundServer bound to our ClockBoundD Unix Socket.
/// * `rx_tracking` - A tokio::sync::watch::channel receiver handle that is used for receiving Chrony
/// tracking information from the Chrony Poller thread.
/// * `rx_error_flag` - A tokio::sync::watch::channel receiver handle that is used for receiving an
/// error flag, indicating that the last Chrony poll failed, from the Chrony Poller thread.
/// * `max_clock_error` - The assumed maximum frequency error that a system clock can gain between updates in ppm.
pub fn start_main_thread(
    mut server: ClockBoundServer,
    rx_tracking: Receiver<Tracking>,
    rx_error_flag: Receiver<bool>,
    max_clock_error: f64,
) {
    // Main thread
    loop {
        match server.handle_client(rx_tracking.clone(), rx_error_flag.clone(), max_clock_error) {
            Err(e) => error!("Failed to communicate with client. Error: {:?}", e),
            _ => {}
        };
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only
use clap::{value_t, App, Arg};
use clock_bound_d::run;
use syslog::Error;

// Constants that reference package information from Cargo.toml
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const NAME: &'static str = env!("CARGO_PKG_NAME");
const AUTHORS: &'static str = env!("CARGO_PKG_AUTHORS");
const DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION");

pub const DEFAULT_MAX_CLOCK_ERROR: f64 = 1.0; // 1ppm, same value as what chronyd is hard-coded to

// ClockBoundD application entry point.
fn main() -> Result<(), Error> {
    // Create CLI options using Cargo.toml package information for reference
    let matches = App::new(NAME)
        .version(VERSION)
        .author(AUTHORS)
        .about(DESCRIPTION)
        .arg(Arg::with_name("level")
            .short("L")
            .long("level")
            .takes_value(true)
            .possible_values(&["0", "1", "2", "3", "4"])
            .help("Set the minimum log level of messages written to syslog. The available levels are: 0 (Trace), 1 (Debug), 2 (Info), 3 (Warning), 4 (Error). The default value is 2 (Info)."))
        .arg(Arg::with_name("max_clock_error")
            .short("e")
            .long("max_clock_error")
            .takes_value(true)
            .help("Set the max clock error in ppm. This is the assumed maximum frequency error that a system clock can gain between updates. This should be set to the same value as maxclockerror in chrony's configuration. Default value is 1 ppm."))
        .get_matches();

    // Validate max_clock_error is a float. Otherwise, use the default value.
    // Clap will log an error and exit if an invalid argument is provided.
    let max_clock_error = if matches.is_present("max_clock_error") {
        value_t!(matches.value_of("max_clock_error"), f64).unwrap_or_else(|e| e.exit())
    } else {
        DEFAULT_MAX_CLOCK_ERROR
    };

    // Default minimum log level is Info
    let mut log_level = log::LevelFilter::Info;
    if matches.is_present("level") {
        log_level = match matches.value_of("level").unwrap() {
            "0" => log::LevelFilter::Trace,
            "1" => log::LevelFilter::Debug,
            "2" => log::LevelFilter::Info,
            "3" => log::LevelFilter::Warn,
            "4" => log::LevelFilter::Error,
            _ => log::LevelFilter::Info,
        };
    }

    // Setup syslog format to match RFC 3164
    // Logs are output to the syslog /var/log/daemon.log
    let formatter = syslog::Formatter3164 {
        facility: syslog::Facility::LOG_DAEMON,
        hostname: None,
        process: "clockboundd".into(),
        pid: std::process::id() as i32,
    };

    let logger = syslog::unix(formatter).expect("could not connect to syslog");
    log::set_boxed_logger(Box::new(syslog::BasicLogger::new(logger)))
        .map(|()| log::set_max_level(log_level))
        .unwrap();

    run(max_clock_error);
    Ok(())
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only
use crate::ceb::ClockErrorBound;
use crate::chrony_poller::LEAP_STATUS_UNSYNCHRONIZED;
#[cfg(not(test))]
use crate::tracking::update_root_dispersion;
use byteorder::{ByteOrder, NetworkEndian, WriteBytesExt};
#[cfg(not(test))]
use chrono::Utc;
use chrony_candm::reply::Tracking;
use log::error;

/// The current ClockBound protocol version
pub const RESPONSE_VERSION: u8 = 1;

/// An Error Response to include in the header
pub const ERROR_RESPONSE: u8 = 0;

/// Validate a request.
///
/// Checks if the protocol versions between the client and daemon match.
/// Checks if the request type is valid.
/// Checks if the request is the correct size.
///
/// # Arguments
///
/// * `request_version` - The version of the ClockBound protocol the request is using.
/// * `request_type` - The request type: Error (0), Now (1), Before (2), After (3).
/// * `request_size` - The amount of bytes read from a request received from a client.
pub fn validate_request(request_version: u8, request_type: u8, request_size: usize) -> bool {
    // Validate request version
    if request_version != RESPONSE_VERSION {
        error!(
            "The request version {} does not match the response version {}",
            request_version, RESPONSE_VERSION
        );
        return false;
    }

    // Validate request type and size
    return match request_type {
        1 => {
            // Now request. A now request should be the size of just a header which is 4 bytes.
            if request_size == 4 {
                return true;
            }
            error!(
                "Received Now request with invalid size. Expected: 4 bytes, Received: {} bytes",
                request_size
            );
            false
        }
        2 => {
            // Before request. A before request should have 12 bytes.
            // 4 bytes in the header and 8 in the body.
            if request_size == 12 {
                return true;
            }
            error!(
                "Received Before request with invalid size. Expected: 12 bytes, Received: {} bytes",
                request_size
            );
            false
        }
        3 => {
            // After request. A after request should have 12 bytes.
            // 4 bytes in the header and 8 in the body.
            if request_size == 12 {
                return true;
            }
            error!(
                "Received After request with invalid size. Expected: 12 bytes, Received: {} bytes",
                request_size
            );
            false
        }
        _ => {
            // Any other request is invalid.
            error!(
                "Received invalid request type. Valid types: 1 (Now), 2 (Before), 3 (After), Received: {}",
                request_type
            );
            false
        }
    };
}

/// Build a response to send to a client.
///
/// # Arguments
///
/// * `request` - The request received from a client.
/// * `request_size` - The amount of bytes read from a request received from a client.
/// * `tracking` - The tracking information received from Chrony.
/// * `error_flag` - An error flag indicating if there has been an error when getting the tracking
/// information from Chrony.
/// * `max_clock_error` - The assumed maximum frequency error that a system clock can gain between updates in ppm.
pub fn build_response(
    request: [u8; 12],
    request_size: usize,
    tracking: Tracking,
    error_flag: bool,
    max_clock_error: f64,
) -> Vec<u8> {
    // The protocol version of the request
    let request_version = request[0];
    // The request type
    let request_type = request[1];

    // The 3rd and 4th bytes are reserved

    // Chrony tracking information provides the leap status which can be one of four values:
    // Normal (0), Insert second (1), Delete second (2), or Not synchronised (3)
    // If leap status reports as "Not synchronised" then that means Chrony is not synchronised to a
    // source. A client will want to know if Chrony is synchronised or not so set the sync flag
    // false (1) if unsynchronized; otherwise, true (0).
    let sync_flag: u8 = match tracking.leap_status {
        LEAP_STATUS_UNSYNCHRONIZED => 1, // False
        _ => 0,                          // True
    };

    // Don't update the root dispersion when building a response from a test. Updating the root
    // dispersion incorporates checking the current system time which is difficult to test against.
    // Updating root dispersion is tested in its own standalone test.
    #[cfg(not(test))]
    // If there is an issue updating the root dispersion then send back the header only with the
    // response type as error.
    // Even if chronyd is not running the root dispersion will grow based on the last tracking
    // data received from chronyd. Clients can handle this case by seeing that the error flag is
    // set to true due to not being able to get tracking data from chronyd.
    let tracking = match update_root_dispersion(tracking, max_clock_error) {
        Ok(t) => t,
        Err(e) => {
            error!("Root dispersion could not be updated. {:?}", e);
            // If updating the root dispersion fails, then send back only a header with the
            // response type as Error (0).
            return build_response_header(ERROR_RESPONSE, sync_flag);
        }
    };

    let is_valid_request = validate_request(request_version, request_type, request_size);

    // If the error flag is true or if the version of the request does not match the response
    // version; set the response type to Error (0)
    let response_header = if error_flag || !is_valid_request {
        build_response_header(ERROR_RESPONSE, sync_flag)
    } else {
        build_response_header(request_type, sync_flag)
    };

    let ceb_data = ClockErrorBound::from(tracking);

    // Build response based on Request Type
    // Invalid type = Error Response
    // 1 = Now
    // 2 = Before
    // 3 = After
    return match request_type {
        1 => build_response_now(response_header, &ceb_data),
        2 | 3 => {
            // If our request is a before (2) or after (3) request then a body is expected
            let request_body = NetworkEndian::read_u64(&request[4..12]);
            build_response_before_after(response_header, &ceb_data, request_body)
        }
        _ => {
            // If invalid request type then send back the header. The header will return a request
            // type of 0 to indicate an error.
            response_header
        }
    };
}

/// Builds the header of a response.
///
/// # Arguments:
///
/// * `request_type` - The request type: Error (0), Now (1), Before (2), After (3).
/// * `sync_flag` - A flag indicating if Chrony is synchronized to a source. This flag is set based
/// on the leap status value from Chrony's tracking data. If the value is reported as unsynchronized
/// then this flag gets set to false. Otherwise, true.
fn build_response_header(mut request_type: u8, sync_flag: u8) -> Vec<u8> {
    let mut response: Vec<u8> = Vec::with_capacity(4);
    // Send back the response version of ClockBoundD
    response.push(RESPONSE_VERSION);

    // If the request type is not a valid type then set it to Error (0)
    let request_types: [u8; 4] = [0, 1, 2, 3];
    if !request_types.contains(&request_type) {
        request_type = ERROR_RESPONSE;
    }

    // Send back the request type. Will be set to Error (0) if an error occurred.
    response.push(request_type);
    // Set the sync flag based on the Chrony tracking information
    response.push(sync_flag);
    // 4th byte is currently reserved
    let reserved: u8 = 0;
    response.push(reserved);
    response
}

/// Builds the response of a now request to send to a client.
///
/// # Arguments:
///
/// * `header` - The header of the response.
/// * `ceb_data` - The Clock Error Bound calculated from the Chrony tracking data.
fn build_response_now(header: Vec<u8>, ceb_data: &ClockErrorBound) -> Vec<u8> {
    let mut response: Vec<u8> = header;
    let (earliest, latest): (u64, u64) = clockbound_now(ceb_data.ceb);
    response.write_u64::<NetworkEndian>(earliest).unwrap();
    response.write_u64::<NetworkEndian>(latest).unwrap();
    response
}

/// Builds the response of a before or after request to send to a client.
///
/// # Arguments:
///
/// * `header` - The header of the response.
/// * `ceb_data` - The ClockErrorBound struct containing the CEB calculated from the Chrony tracking data.
/// * `time_epoch` - The timestamp in nanoseconds since the Unix Epoch to compare against.
fn build_response_before_after(
    header: Vec<u8>,
    ceb_data: &ClockErrorBound,
    time_epoch: u64,
) -> Vec<u8> {
    let mut response = header;
    let (earliest, latest) = clockbound_now(ceb_data.ceb);
    // response[1] holds the response type
    // 2 = Before
    // 3 = After
    if response[1] == 2 {
        response
            .write_u8(clockbound_before(earliest, time_epoch))
            .unwrap();
    } else if response[1] == 3 {
        response
            .write_u8(clockbound_after(latest, time_epoch))
            .unwrap();
    }
    response
}

/// Takes a Clock Error Bound and generates earliest and latest bounds based on the current system
/// time.
///
/// Calculation of bounds is computed with [System time - CEB, System time + CEB]
///
/// # Arguments:
///
/// * `ceb` - The Clock Error Bound calculated from the Chrony tracking data.
fn clockbound_now(ceb: f64) -> (u64, u64) {
    // For testing purposes we will use a mock function to retrieve our timestamp instead of getting
    // the current time.
    #[cfg(test)]
    let time_nanos: u64 = tests::mock_get_epoch_us();
    #[cfg(not(test))]
    let time_nanos: u64 = get_epoch_us();
    // Convert seconds to nanoseconds and round to the nearest nanosecond.
    let ceb_nanos: u64 = (ceb * 1000000000.0) as u64;
    return (time_nanos - ceb_nanos, time_nanos + ceb_nanos);
}

/// Takes the earliest bound calculated from a now request and compares it against a provided
/// timestamp represented as nanoseconds since the Unix epoch. If the timestamp provided is earlier
/// than the earliest bound then it returns true, otherwise false.
///
/// Earliest bound is computed as [System time - CEB]
///
/// # Arguments:
///
/// * `earliest` - The earliest bound calculated from a now request. Computed by [System time - CEB]
/// * `time_epoch` - The timestamp in nanoseconds since the Unix Epoch to compare against.
fn clockbound_before(earliest: u64, time_epoch: u64) -> u8 {
    return if time_epoch < earliest { 1 } else { 0 };
}

/// Takes the latest bound calculated from a now request and compares it against a provided
/// timestamp represented as nanoseconds since the Unix epoch. If the timestamp provided is later
/// than the latest bound then it returns true, otherwise false.
///
/// Latest bound is computed as [System time + CEB]
///
/// # Arguments:
///
/// * `latest` - The latest bound calculated from a now request. Computed by [System time + CEB]
/// * `time_epoch` - The timestamp in nanoseconds since the Unix Epoch to compare against.
fn clockbound_after(latest: u64, time_epoch: u64) -> u8 {
    return if time_epoch > latest { 1 } else { 0 };
}

/// Get the current system time in nanoseconds since the Unix epoch.
#[cfg(not(test))]
fn get_epoch_us() -> u64 {
    let now = Utc::now();
    now.timestamp_nanos() as u64
}

#[cfg(test)]
mod tests {
    use crate::ceb::ClockErrorBound;
    use crate::chrony_poller::LEAP_STATUS_UNSYNCHRONIZED;
    use crate::response::{
        build_response, build_response_header, clockbound_after, clockbound_before, clockbound_now,
        validate_request, RESPONSE_VERSION,
    };
    use crate::tracking::mock_tracking;
    use byteorder::NetworkEndian;
    use byteorder::{ReadBytesExt, WriteBytesExt};
    use chrono::{TimeZone, Utc};
    use std::convert::TryFrom;
    use std::io::Cursor;

    pub fn mock_get_epoch_us() -> u64 {
        // This equals 1000000000000000000 nanoseconds since the unix epoch
        let now = Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 0);
        now.timestamp_nanos() as u64
    }

    #[test]
    fn test_build_response_now_successful() {
        let tracking = mock_tracking();

        let mut request: [u8; 12] = [0; 12];

        // Now request
        let request_type: u8 = 1;

        // Create a now request to test
        // Header
        // Version
        request[0] = RESPONSE_VERSION;
        // Command Type
        request[1] = request_type;

        let response = build_response(request, 4, tracking, false, 1.0);

        let mut rdr = Cursor::new(response);
        assert_eq!(RESPONSE_VERSION, rdr.read_u8().unwrap());
        assert_eq!(request_type, rdr.read_u8().unwrap());
        // Sync flag
        assert_eq!(0, rdr.read_u8().unwrap());
        // Reserved
        assert_eq!(0, rdr.read_u8().unwrap());
        // Get the CEB from mock tracking data
        let ceb = ClockErrorBound::from(tracking).ceb;
        let bounds = clockbound_now(ceb);
        // Earliest bound
        assert_eq!(bounds.0, rdr.read_u64::<NetworkEndian>().unwrap());
        // Latest bound
        assert_eq!(bounds.1, rdr.read_u64::<NetworkEndian>().unwrap());
    }

    #[test]
    fn test_build_response_before_true_successful() {
        let tracking = mock_tracking();

        let mut request: Vec<u8> = Vec::new();

        // Before request
        let request_type: u8 = 2;

        // Create a before request to test
        // Header
        // Version
        request.push(RESPONSE_VERSION);
        // Command Type
        request.push(request_type);
        // Reserved
        request.push(0);
        request.push(0);

        // Get the CEB from mock tracking data
        let ceb = ClockErrorBound::from(tracking).ceb;
        let bounds = clockbound_now(ceb);
        // 0 is the Unix Epoch
        let before_time = 0;

        request.write_u64::<NetworkEndian>(before_time).unwrap();

        let response = build_response(
            <[u8; 12]>::try_from(request).unwrap(),
            12,
            tracking,
            false,
            1.0,
        );

        let mut rdr = Cursor::new(response);
        assert_eq!(RESPONSE_VERSION, rdr.read_u8().unwrap());
        assert_eq!(request_type, rdr.read_u8().unwrap());
        // Sync flag
        assert_eq!(0, rdr.read_u8().unwrap());
        // Reserved
        assert_eq!(0, rdr.read_u8().unwrap());
        // Is 0 nanoseconds since epoch less than the mock current time of 1000000000000000000 nanoseconds since epoch?
        let before_flag = clockbound_before(bounds.0, before_time);
        // Before Flag. 0 < 1000000000000000000 should return 1
        assert_eq!(before_flag, rdr.read_u8().unwrap());
    }

    #[test]
    fn test_build_response_before_false_successful() {
        let tracking = mock_tracking();

        let mut request: Vec<u8> = Vec::new();

        // Before request
        let request_type: u8 = 2;

        // Create a before request to test
        // Header
        // Version
        request.push(RESPONSE_VERSION);
        // Command Type
        request.push(request_type);
        // Reserved
        request.push(0);
        request.push(0);

        // Get the CEB from mock tracking data
        let ceb = ClockErrorBound::from(tracking).ceb;
        let bounds = clockbound_now(ceb);
        // 1000000000000000001 is one nanosecond more than our mock data of 1000000000000000000
        let before_time = 1000000000000000001;

        request.write_u64::<NetworkEndian>(before_time).unwrap();

        let response = build_response(
            <[u8; 12]>::try_from(request).unwrap(),
            12,
            tracking,
            false,
            1.0,
        );

        let mut rdr = Cursor::new(response);
        assert_eq!(RESPONSE_VERSION, rdr.read_u8().unwrap());
        assert_eq!(request_type, rdr.read_u8().unwrap());
        // Sync flag
        assert_eq!(0, rdr.read_u8().unwrap());
        // Reserved
        assert_eq!(0, rdr.read_u8().unwrap());
        // Is 1000000000000000001 nanoseconds since epoch less than the mock current time of 1000000000000000000 nanoseconds since epoch?
        let before_flag = clockbound_before(bounds.0, before_time);
        // Before Flag. 1000000000000000001 < 1000000000000000000 should return 0
        assert_eq!(before_flag, rdr.read_u8().unwrap());
    }

    #[test]
    fn test_build_response_after_true_successful() {
        let tracking = mock_tracking();

        let mut request: Vec<u8> = Vec::new();

        // After request
        let request_type: u8 = 3;

        // Create a after request to test
        // Header
        // Version
        request.push(RESPONSE_VERSION);
        // Command Type
        request.push(request_type);
        // Reserved
        request.push(0);
        request.push(0);

        // Get the CEB from mock tracking data
        let ceb = ClockErrorBound::from(tracking).ceb;
        let bounds = clockbound_now(ceb);
        // 1000000000000000001 is one nanosecond more than our mock data of 1000000000000000000
        let after_time = 1000000000000000001;

        request.write_u64::<NetworkEndian>(after_time).unwrap();

        let response = build_response(
            <[u8; 12]>::try_from(request).unwrap(),
            12,
            tracking,
            false,
            1.0,
        );

        let mut rdr = Cursor::new(response);
        assert_eq!(RESPONSE_VERSION, rdr.read_u8().unwrap());
        assert_eq!(request_type, rdr.read_u8().unwrap());
        // Sync flag
        assert_eq!(0, rdr.read_u8().unwrap());
        // Reserved
        assert_eq!(0, rdr.read_u8().unwrap());
        // Is 1000000000000000001 nanoseconds since epoch greater than the mock current time of 1000000000000000000 nanoseconds since epoch?
        let after_flag = clockbound_after(bounds.1, after_time);
        // After Flag. 1000000000000000001 > 1000000000000000000 should return 1
        assert_eq!(after_flag, rdr.read_u8().unwrap());
    }

    #[test]
    fn test_build_response_after_false_successful() {
        let tracking = mock_tracking();

        let mut request: Vec<u8> = Vec::new();

        // After request
        let request_type: u8 = 3;

        // Create a after request to test
        // Header
        // Version
        request.push(RESPONSE_VERSION);
        // Command Type
        request.push(request_type);
        // Reserved
        request.push(0);
        request.push(0);

        // Get the CEB from mock tracking data
        let ceb = ClockErrorBound::from(tracking).ceb;
        let bounds = clockbound_now(ceb);
        // 0 is the Unix Epoch
        let after_time = 0;

        request.write_u64::<NetworkEndian>(after_time).unwrap();

        let response = build_response(
            <[u8; 12]>::try_from(request).unwrap(),
            12,
            tracking,
            false,
            1.0,
        );

        let mut rdr = Cursor::new(response);
        assert_eq!(RESPONSE_VERSION, rdr.read_u8().unwrap());
        assert_eq!(request_type, rdr.read_u8().unwrap());
        // Sync flag
        assert_eq!(0, rdr.read_u8().unwrap());
        // Reserved
        assert_eq!(0, rdr.read_u8().unwrap());
        // Is 0 nanoseconds since epoch greater than the mock current time of 1000000000000000000 nanoseconds since epoch?
        let after_flag = clockbound_after(bounds.1, after_time);
        // After Flag. 0 > 1000000000000000000 should return 0
        assert_eq!(after_flag, rdr.read_u8().unwrap());
    }

    #[test]
    fn test_build_response_header_error_successful() {
        // Any request other than 1, 2 or 3 should reply with an Error (0) response
        let request_type: u8 = 0;
        let header = build_response_header(request_type, 0);
        let mut rdr = Cursor::new(header);
        assert_eq!(RESPONSE_VERSION, rdr.read_u8().unwrap());
        assert_eq!(request_type, rdr.read_u8().unwrap());
        //sync flag
        assert_eq!(0, rdr.read_u8().unwrap());
        //reserved
        assert_eq!(0, rdr.read_u8().unwrap());

        // Test a non 0 value as well.
        let request_type: u8 = 8;
        let header = build_response_header(request_type, 0);
        let mut rdr = Cursor::new(header);
        assert_eq!(RESPONSE_VERSION, rdr.read_u8().unwrap());
        // Should respond with an Error (0) response
        assert_eq!(0, rdr.read_u8().unwrap());
        //sync flag
        assert_eq!(0, rdr.read_u8().unwrap());
        //reserved
        assert_eq!(0, rdr.read_u8().unwrap());
    }

    #[test]
    fn test_validate_request_valid() {
        // Valid Now request
        assert_eq!(validate_request(RESPONSE_VERSION, 1, 4), true);

        // Valid Before request
        assert_eq!(validate_request(RESPONSE_VERSION, 2, 12), true);

        // Valid After request
        assert_eq!(validate_request(RESPONSE_VERSION, 3, 12), true);
    }

    #[test]
    fn test_validate_request_invalid() {
        // Invalid request version
        assert_eq!(validate_request(0, 1, 4), false);

        // Invalid request type
        assert_eq!(validate_request(RESPONSE_VERSION, 0, 4), false);

        // Invalid Now request size
        assert_eq!(validate_request(RESPONSE_VERSION, 1, 12), false);

        // Invalid Before request size
        assert_eq!(validate_request(RESPONSE_VERSION, 2, 4), false);

        // Invalid After request size
        assert_eq!(validate_request(RESPONSE_VERSION, 3, 4), false);
    }

    #[test]
    fn test_build_response_sync_flag_false() {
        let mut tracking = mock_tracking();

        tracking.leap_status = LEAP_STATUS_UNSYNCHRONIZED;

        let mut request: [u8; 12] = [0; 12];

        // Now request
        let request_type: u8 = 1;

        // Create a now request to test
        // Header
        // Version
        request[0] = RESPONSE_VERSION;
        // Command Type
        request[1] = request_type;

        let response = build_response(request, 4, tracking, false, 1.0);

        let mut rdr = Cursor::new(response);
        assert_eq!(RESPONSE_VERSION, rdr.read_u8().unwrap());
        assert_eq!(request_type, rdr.read_u8().unwrap());
        // Sync flag should be false
        assert_eq!(1, rdr.read_u8().unwrap());
        // Reserved
        assert_eq!(0, rdr.read_u8().unwrap());
        // Should still build the body of the response with the sync flag as false
        // Get the CEB from mock tracking data
        let ceb = ClockErrorBound::from(tracking).ceb;
        let bounds = clockbound_now(ceb);
        // Earliest bound
        assert_eq!(bounds.0, rdr.read_u64::<NetworkEndian>().unwrap());
        // Latest bound
        assert_eq!(bounds.1, rdr.read_u64::<NetworkEndian>().unwrap());
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only
use crate::response::build_response;
use crate::socket;
use chrony_candm::reply::Tracking;
use log::warn;
use std::io;
use tokio::sync::watch::Receiver;
use uds::UnixDatagramExt;

/// The Unix Datagram Socket file for ClockBoundD
pub const CLOCKBOUND_SERVER_SOCKET: &str = "clockboundd.sock";

/// ClockBoundServer holds the Tracking data from Chrony and binds to the ClockBoundD unix socket
/// as a server.
pub struct ClockBoundServer {
    socket: std::os::unix::net::UnixDatagram,
    tracking: Tracking,
}

impl ClockBoundServer {
    /// Initialize ClockBound to contain default Tracking information and bind to the ClockBoundD
    /// unix socket.
    ///
    /// # Arguments
    ///
    /// * `tracking` - The tracking information received from Chrony.
    pub fn new(tracking: Tracking) -> ClockBoundServer {
        let socket = socket::create_unix_socket(std::path::Path::new(CLOCKBOUND_SERVER_SOCKET));

        return ClockBoundServer { socket, tracking };
    }

    /// Update Tracking data.
    ///
    /// # Arguments
    ///
    /// * `tracking` - The tracking information received from Chrony.
    pub fn update_tracking(&mut self, tracking: Tracking) {
        self.tracking = tracking;
    }

    /// Handle a request from a client.
    ///
    /// # Arguments
    ///
    /// * `rx_tracking` - A tokio::sync::watch::channel receiver handle that is used for receiving Chrony
    /// tracking information from the Chrony Poller thread.
    /// * `rx_error_flag` - A tokio::sync::watch::channel receiver handle that is used for receiving an
    /// error flag, indicating that the last Chrony poll failed, from the Chrony Poller thread.
    /// * `max_clock_error` - The assumed maximum frequency error that a system clock can gain between updates in ppm.
    pub fn handle_client(
        &mut self,
        rx_tracking: Receiver<Tracking>,
        rx_error_flag: Receiver<bool>,
        max_clock_error: f64,
    ) -> Result<(), io::Error> {
        let mut request: [u8; 12] = [0; 12];

        let (request_size, client) = self.socket.recv_from_unix_addr(&mut request)?;

        // Get tracking data from chrony poller thread
        let tracking = *rx_tracking.borrow();

        // Update the tracking information with the latest information from the chrony poller thread
        self.update_tracking(tracking.clone());

        // Get error flag from chrony poller thread
        let error_flag = *rx_error_flag.borrow();

        let mut response: Vec<u8> = build_response(
            request,
            request_size,
            self.tracking,
            error_flag,
            max_clock_error,
        );

        if let Err(e) = self.socket.send_to_unix_addr(&mut response, &client) {
            warn!("Failed to send response to client. Error: {:?}", e);
        }

        Ok(())
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only
use log::{error, info};
use std::fs;
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
use std::os::unix::net::UnixDatagram;

///
/// Create a local Unix Datagram Socket.
///
/// Remove any existing socket file at the exact same path, throwing an error if the removal fails.
/// Creates a socket and binds to it at the specified path.
///
/// # Arguments:
///
/// * `path`: The path and filename of the UNIX socket to be created. The directory up to
///          the filename must exist or it will fail.
pub fn create_unix_socket(path: &std::path::Path) -> UnixDatagram {
    // Remove existing socket file if found
    remove_socket_file(path);
    // Create socket and bind to path
    let sock = match UnixDatagram::bind(path) {
        Ok(s) => {
            info!("Created unix socket at path {}", path.display());
            info!("Connected to local unix socket at path {}", path.display());
            s
        }
        Err(e) => {
            panic!("Failed to bind to unix socket {:?}. Error: {:?}", path, e)
        }
    };

    let mode = 0o777;
    let permissions = fs::Permissions::from_mode(mode);

    // Set permissions to rwx rwx rwx so that an unprivileged process can
    // write requests to the socket.
    if let Err(err_permissions) = fs::set_permissions(path, permissions) {
        error!("Failed to set permissions: {}", err_permissions);
    };

    return sock;
}

/// Remove the socket file at the path if possible.
/// Checks if the path exists, then checks if the file is a socket, and removes it.
/// If the remove succeeds or the path doesn't exist at all, panic since we can not start the daemon
/// without a socket.
///
/// # Arguments:
/// * `path`: The path of the socket to be removed.
///
pub fn remove_socket_file(path: &std::path::Path) {
    if path.exists() {
        let metadata = fs::metadata(path);
        if let Err(e) = metadata {
            panic!(
                "Failed to check file metadata for removal of file: {:?}. Error: {:?}",
                path, e
            );
        }
        // Fail preemptively so that we don't remove non-socket files
        if !metadata.unwrap().file_type().is_socket() {
            panic!(
                "Failed to remove socket file. An existing file that is not a socket exists: {:?}",
                path
            );
        }

        if let Err(e) = fs::remove_file(&path) {
            panic!("Failed to remove socket file: {:?}. Error: {:?}", path, e);
        }
        info!(
            "Removed preexisting local unix socket at path {}",
            path.display()
        );
        return;
    }

    info!(
        "Local unix socket at path {} does not exist, skipping remove",
        path.display()
    );
}