/* Copyright (C) 2017 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

use crate::common::nom7::take_until_and_consume;
use crate::smb::error::SmbError;
use crate::smb::smb::*;
use crate::smb::smb_records::*;
use nom7::bytes::streaming::{tag, take};
use nom7::combinator::{complete, cond, peek, rest, verify};
use nom7::error::{make_error, ErrorKind};
use nom7::Err;
use nom7::multi::many1;
use nom7::number::streaming::{le_u8, le_u16, le_u32, le_u64};
use nom7::IResult;

pub const SMB1_HEADER_SIZE: usize = 32;

// SMB_FLAGS_REPLY in Microsoft docs.
const SMB1_FLAGS_RESPONSE: u8 = 0x80;

fn smb_get_unicode_string_with_offset(i: &[u8], offset: usize) -> IResult<&[u8], Vec<u8>, SmbError>
{
    let (i, _) = cond(offset % 2 == 1, take(1_usize))(i)?;
    smb_get_unicode_string(i)
}

/// take a string, unicode or ascii based on record
pub fn smb1_get_string<'a>(i: &'a[u8], r: &SmbRecord, offset: usize) -> IResult<&'a[u8], Vec<u8>, SmbError> {
    if r.has_unicode_support() {
        smb_get_unicode_string_with_offset(i, offset)
    } else {
        smb_get_ascii_string(i)
    }
}


#[derive(Debug,PartialEq, Eq)]
pub struct SmbParamBlockAndXHeader {
    pub wct: u8,
    pub andx_command: u8,
    pub andx_offset: u16,
}

pub fn smb1_parse_andx_header(i: &[u8]) -> IResult<&[u8], SmbParamBlockAndXHeader> {
    let (i, wct) = le_u8(i)?;
    let (i, andx_command) = le_u8(i)?;
    let (i, _) = take(1_usize)(i)?; // reserved
    let (i, andx_offset) = le_u16(i)?;
    let hdr = SmbParamBlockAndXHeader {
        wct,
        andx_command,
        andx_offset,
    };
    Ok((i, hdr))
}

#[derive(Debug,PartialEq, Eq)]
pub struct Smb1WriteRequestRecord<'a> {
    pub offset: u64,
    pub len: u32,
    pub fid: &'a[u8],
    pub data: &'a[u8],
}

pub fn parse_smb1_write_request_record(i: &[u8]) -> IResult<&[u8], Smb1WriteRequestRecord<'_>> {
    let (i, _wct) = le_u8(i)?;
    let (i, fid) = take(2_usize)(i)?;
    let (i, _count) = le_u16(i)?;
    let (i, offset) = le_u32(i)?;
    let (i, _remaining) = le_u16(i)?;
    let (i, _bcc) = le_u16(i)?;
    let (i, _buffer_format) = le_u8(i)?;
    let (i, data_len) = le_u16(i)?;
    let (i, file_data) = take(data_len)(i)?;
    let record = Smb1WriteRequestRecord {
        offset: offset as u64,
        len: data_len as u32,
        fid,
        data:file_data,
    };
    Ok((i, record))
}

pub fn parse_smb1_write_andx_request_record(i : &[u8], andx_offset: usize) -> IResult<&[u8], Smb1WriteRequestRecord<'_>> {
    let origin_i = i;
    let ax = andx_offset as u16;
    let (i, wct) = le_u8(i)?;
    let (i, _andx_command) = le_u8(i)?;
    let (i, _) = take(1_usize)(i)?; // reserved
    let (i, _andx_offset) = le_u16(i)?;
    let (i, fid) = take(2_usize)(i)?;
    let (i, offset) = le_u32(i)?;
    let (i, _) = take(4_usize)(i)?; // reserved
    let (i, _write_mode) = le_u16(i)?;
    let (i, _remaining) = le_u16(i)?;
    let (i, data_len_high) = le_u16(i)?;
    let (i, data_len_low) = le_u16(i)?;
    let data_len = ((data_len_high as u32) << 16)|(data_len_low as u32);
    let (i, data_offset) = le_u16(i)?;
    if data_offset < 0x3c || data_offset < ax{
        return Err(Err::Error(make_error(i, ErrorKind::LengthValue)));
    }
    let (i, high_offset) = cond(wct == 14, le_u32)(i)?;
    let (_i, _bcc) = le_u16(i)?;
    let (i, _padding_data) = take(data_offset-ax)(origin_i)?;
    let (i, file_data) = take(std::cmp::min(data_len, i.len() as u32))(i)?;

    let record = Smb1WriteRequestRecord {
        offset: ((high_offset.unwrap_or(0) as u64) << 32) | offset as u64,
        len: data_len,
        fid,
        data: file_data,
    };
    Ok((i, record))
}

pub fn parse_smb1_write_and_close_request_record(i: &[u8]) -> IResult<&[u8], Smb1WriteRequestRecord<'_>> {
    let (i, _wct) = le_u8(i)?;
    let (i, fid) = take(2_usize)(i)?;
    let (i, count) = le_u16(i)?;
    let (i, offset) = le_u32(i)?;
    let (i, _last_write) = take(4_usize)(i)?;
    let (i, bcc) = le_u16(i)?;
    let (i, _padding) = cond(bcc > count, |b| take(bcc - count)(b))(i)?;
    let (i, file_data) = take(count)(i)?;
    let record = Smb1WriteRequestRecord {
        offset: offset as u64,
        len: count as u32,
        fid,
        data: file_data,
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct Smb1NegotiateProtocolResponseRecord<'a> {
    pub dialect_idx: u16,
    pub server_guid: &'a[u8],
}

pub fn parse_smb1_negotiate_protocol_response_record_error(i: &[u8])
    -> IResult<&[u8], Smb1NegotiateProtocolResponseRecord<'_>> {
     let (i, _wct) = le_u8(i)?;
     let (i, _bcc) = le_u16(i)?;
     let record = Smb1NegotiateProtocolResponseRecord {
         dialect_idx: 0,
         server_guid: &[],
     };
     Ok((i, record))
}

pub fn parse_smb1_negotiate_protocol_response_record_ok(i: &[u8])
    -> IResult<&[u8], Smb1NegotiateProtocolResponseRecord<'_>> {
    let (i, _wct) = le_u8(i)?;
    let (i, dialect_idx) = le_u16(i)?;
    let (i, _sec_mode) = le_u8(i)?;
    let (i, _) = take(16_usize)(i)?;
    let (i, _caps) = le_u32(i)?;
    let (i, _sys_time) = le_u64(i)?;
    let (i, _server_tz) = le_u16(i)?;
    let (i, _challenge_len) = le_u8(i)?;
    let (i, bcc) = le_u16(i)?;
    let (i, server_guid) = cond(bcc >= 16, take(16_usize))(i)?;
    let record = Smb1NegotiateProtocolResponseRecord {
        dialect_idx,
        server_guid: server_guid.unwrap_or(&[]),
    };
    Ok((i, record))
}

pub fn parse_smb1_negotiate_protocol_response_record(i: &[u8])
    -> IResult<&[u8], Smb1NegotiateProtocolResponseRecord<'_>> {
    let (i, wct) = peek(le_u8)(i)?;
    match wct {
        0 => parse_smb1_negotiate_protocol_response_record_error(i),
        _ => parse_smb1_negotiate_protocol_response_record_ok(i),
    }
}

#[derive(Debug,PartialEq, Eq)]
pub struct Smb1NegotiateProtocolRecord<'a> {
    pub dialects: Vec<&'a [u8]>,
}

pub fn parse_smb1_negotiate_protocol_record(i: &[u8])
    -> IResult<&[u8], Smb1NegotiateProtocolRecord<'_>> {
    let (i, _wtc) = le_u8(i)?;
    let (i, _bcc) = le_u16(i)?;
    // dialects is a list of [1 byte buffer format][string][0 terminator]
    let (i, dialects) = many1(complete(take_until_and_consume(b"\0")))(i)?;
    let record = Smb1NegotiateProtocolRecord { dialects };
    Ok((i, record))
}


#[derive(Debug,PartialEq, Eq)]
pub struct Smb1ResponseRecordTreeConnectAndX<'a> {
    pub service: &'a[u8],
    pub nativefs: &'a[u8],
}

pub fn parse_smb_connect_tree_andx_response_record(i: &[u8])
    -> IResult<&[u8], Smb1ResponseRecordTreeConnectAndX<'_>> {
    let (i, wct) = le_u8(i)?;
    let (i, _andx_command) = le_u8(i)?;
    let (i, _) = take(1_usize)(i)?; // reserved
    let (i, _andx_offset) = le_u16(i)?;
    let (i, _) = cond(wct >= 3, take(2_usize))(i)?; // optional support
    let (i, _) = cond(wct == 7, take(8_usize))(i)?; // access masks
    let (i, _bcc) = le_u16(i)?;
    let (i, service) = take_until_and_consume(b"\x00")(i)?;
    let (i, nativefs) = take_until_and_consume(b"\x00")(i)?;
    let record = Smb1ResponseRecordTreeConnectAndX {
        service,
        nativefs
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRecordTreeConnectAndX<'a> {
    pub path: Vec<u8>,
    pub service: &'a[u8],
}

pub fn parse_smb_connect_tree_andx_record<'a>(i: &'a[u8], r: &SmbRecord)
   -> IResult<&'a[u8], SmbRecordTreeConnectAndX<'a>, SmbError> {
   let (i, _skip1) = take(7_usize)(i)?;
   let (i, pwlen) = le_u16(i)?;
   let (i, _bcc) = le_u16(i)?;
   let (i, _pw) = take(pwlen)(i)?;
   let (i, path) = smb1_get_string(i, r, 11 + pwlen as usize)?;
   let (i, service) = take_until_and_consume(b"\x00")(i)?;
   let record = SmbRecordTreeConnectAndX {
       path,
       service
   };
   Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRecordTransRequest<'a> {
    pub params: SmbRecordTransRequestParams,
    pub pipe: Option<SmbPipeProtocolRecord<'a>>,
    pub txname: Vec<u8>,
    pub data: SmbRecordTransRequestData<'a>,
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbPipeProtocolRecord<'a> {
    pub function: u16,
    pub fid: &'a[u8],
}

pub fn parse_smb_trans_request_record_pipe(i: &[u8])
    -> IResult<&[u8], SmbPipeProtocolRecord<'_>, SmbError> {
    let (i, fun) = le_u16(i)?;
    let (i, fid) = take(2_usize)(i)?;
    let record = SmbPipeProtocolRecord {
        function: fun,
        fid
    };
    Ok((i, record))
}


#[derive(Debug,PartialEq, Eq)]
pub struct SmbRecordTransRequestParams<> {
    pub max_data_cnt: u16,
    param_cnt: u16,
    param_offset: u16,
    data_cnt: u16,
    data_offset: u16,
    bcc: u16,
}

pub fn parse_smb_trans_request_record_params(i: &[u8])
    -> IResult<&[u8], (SmbRecordTransRequestParams, Option<SmbPipeProtocolRecord<'_>>), SmbError>
{
   let (i, wct) = le_u8(i)?;
   let (i, _total_param_cnt) = le_u16(i)?;
   let (i, _total_data_count) = le_u16(i)?;
   let (i, _max_param_cnt) = le_u16(i)?;
   let (i, max_data_cnt) = le_u16(i)?;
   let (i, _max_setup_cnt) = le_u8(i)?;
   let (i, _) = take(1_usize)(i)?; // reserved
   let (i, _) = take(2_usize)(i)?; // flags
   let (i, _timeout) = le_u32(i)?;
   let (i, _) = take(2_usize)(i)?; // reserved
   let (i, param_cnt) = le_u16(i)?;
   let (i, param_offset) = le_u16(i)?;
   let (i, data_cnt) = le_u16(i)?;
   let (i, data_offset) = le_u16(i)?;
   let (i, setup_cnt) = le_u8(i)?;
   let (i, _) = take(1_usize)(i)?; // reserved
   let (i, pipe) = cond(wct == 16 && setup_cnt == 2 && data_cnt > 0, parse_smb_trans_request_record_pipe)(i)?;
   let (i, bcc) = le_u16(i)?;
   let params = SmbRecordTransRequestParams {
            max_data_cnt,
            param_cnt,
            param_offset,
            data_cnt,
            data_offset,
            bcc
        };
   Ok((i, (params, pipe)))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRecordTransRequestData<'a> {
    pub data: &'a[u8],
}

pub fn parse_smb_trans_request_record_data(i: &[u8],
        pad1: usize, param_cnt: u16, pad2: usize, data_len: u16)
    -> IResult<&[u8], SmbRecordTransRequestData<'_>, SmbError>
{
    let (i, _) = take(pad1)(i)?;
    let (i, _) = take(param_cnt)(i)?;
    let (i, _) = take(pad2)(i)?;
    let (i, data) = take(data_len)(i)?;
    let req = SmbRecordTransRequestData { data };
    Ok((i, req))
}

pub fn parse_smb_trans_request_record<'a>(i: &'a[u8], r: &SmbRecord)
    -> IResult<&'a[u8], SmbRecordTransRequest<'a>, SmbError>
{
    let (rem, (params, pipe)) = parse_smb_trans_request_record_params(i)?;
    let mut offset = 32 + (i.len() - rem.len()); // init with SMB header
    SCLogDebug!("params {:?}: offset {}", params, offset);

    let (rem2, n) = smb1_get_string(rem, r, offset)?;
    offset += rem.len() - rem2.len();
    SCLogDebug!("n {:?}: offset {}", n, offset);

    // spec says pad to 4 bytes, but traffic shows this doesn't
    // always happen.
    let pad1 = if offset == params.param_offset as usize ||
                  offset == params.data_offset as usize {
        0
    } else {
        offset % 4
    };
    SCLogDebug!("pad1 {}", pad1);
    offset += pad1;
    offset += params.param_cnt as usize;

    let recdata = if params.data_cnt > 0 {
        // ignore padding rule if we're already at the correct
        // offset.
        let pad2 = if offset == params.data_offset as usize {
            0
        } else {
            offset % 4
        };
        SCLogDebug!("pad2 {}", pad2);

        let d = match parse_smb_trans_request_record_data(rem2,
                pad1, params.param_cnt, pad2, params.data_cnt) {
            Ok((_, rd)) => rd,
            Err(e) => { return Err(e); }
        };
        SCLogDebug!("d {:?}", d);
        d
    } else {
        SmbRecordTransRequestData { data: &[], } // no data
    };

    let res = SmbRecordTransRequest {
        params, pipe, txname: n, data: recdata,
    };
    Ok((rem, res))
}


#[derive(Debug,PartialEq, Eq)]
pub struct SmbRecordTransResponse<'a> {
    pub data_cnt: u16,
    pub bcc: u16,
    pub data: &'a[u8],
}

pub fn parse_smb_trans_response_error_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse<'_>> {
   let (i, _wct) = le_u8(i)?;
   let (i, bcc) = le_u16(i)?;
   let resp = SmbRecordTransResponse {
       data_cnt: 0,
       bcc,
       data: &[],
   };
   Ok((i, resp))
}

pub fn parse_smb_trans_response_regular_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse<'_>> {
   let (i, wct) = le_u8(i)?;
   let (i, _total_param_cnt) = le_u16(i)?;
   let (i, _total_data_count) = le_u16(i)?;
   let (i, _) = take(2_usize)(i)?; // reserved
   let (i, _param_cnt) = le_u16(i)?;
   let (i, _param_offset) = le_u16(i)?;
   let (i, _param_displacement) = le_u16(i)?;
   let (i, data_cnt) = le_u16(i)?;
   let (i, data_offset) = le_u16(i)?;
   let (i, _data_displacement) = le_u16(i)?;
   let (i, _setup_cnt) = le_u8(i)?;
   let (i, _) = take(1_usize)(i)?; // reserved
   let (i, bcc) = le_u16(i)?;
   let (i, _) = take(1_usize)(i)?; // padding
   let (i, _padding_evasion) = cond(
       data_offset > 36+2*(wct as u16),
       |b| take(data_offset - (36+2*(wct as u16)))(b)
    )(i)?;
   let (i, data) = take(data_cnt)(i)?;
   let resp = SmbRecordTransResponse {
       data_cnt,
       bcc,
       data
   };
   Ok((i, resp))
}

pub fn parse_smb_trans_response_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse<'_>> {
    let (i, wct) = peek(le_u8)(i)?;
    match wct {
        0 => parse_smb_trans_response_error_record(i),
        _ => parse_smb_trans_response_regular_record(i),
    }
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRecordSetupAndX<'a> {
    pub sec_blob: &'a[u8],
}

pub fn parse_smb_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbRecordSetupAndX<'_>> {
    let (i, _skip1) = take(15_usize)(i)?;
    let (i, sec_blob_len) = le_u16(i)?;
    let (i, _skip2) = take(8_usize)(i)?;
    let (i, _bcc) = le_u16(i)?;
    let (i, sec_blob) = take(sec_blob_len)(i)?;
    let record = SmbRecordSetupAndX { sec_blob };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbResponseRecordSetupAndX<'a> {
    pub sec_blob: &'a[u8],
}

fn response_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX<'_>> {
   let (i, _skip1) = take(7_usize)(i)?;
   let (i, sec_blob_len) = le_u16(i)?;
   let (i, _bcc) = le_u16(i)?;
   let (i, sec_blob) = take(sec_blob_len)(i)?;
   let record = SmbResponseRecordSetupAndX { sec_blob };
   Ok((i, record))
}

fn response_setup_andx_wct3_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX<'_>> {
   let (i, _skip1) = take(7_usize)(i)?;
   let (i, _bcc) = le_u16(i)?;
   let record = SmbResponseRecordSetupAndX {
        sec_blob: &[],
   };
   Ok((i, record))
}

fn response_setup_andx_error_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX<'_>> {
   let (i, _wct) = le_u8(i)?;
   let (i, _bcc) = le_u16(i)?;
   let record = SmbResponseRecordSetupAndX {
        sec_blob: &[],
   };
   Ok((i, record))
}

pub fn parse_smb_response_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX<'_>> {
    let (i, wct) = peek(le_u8)(i)?;
    match wct {
        0 => response_setup_andx_error_record(i),
        3 => response_setup_andx_wct3_record(i),
        _ => response_setup_andx_record(i),
    }
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRequestReadAndXRecord<'a> {
    pub fid: &'a[u8],
    pub size: u64,
    pub offset: u64,
}

pub fn parse_smb_read_andx_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestReadAndXRecord<'_>> {
    let (i, wct) = le_u8(i)?;
    let (i, _andx_command) = le_u8(i)?;
    let (i, _) = take(1_usize)(i)?; // reserved
    let (i, _andx_offset) = le_u16(i)?;
    let (i, fid) = take(2_usize)(i)?;
    let (i, offset) = le_u32(i)?;
    let (i, max_count_low) = le_u16(i)?;
    let (i, _) = take(2_usize)(i)?;
    let (i, max_count_high) = le_u32(i)?;
    let (i, _) = take(2_usize)(i)?;
    let (i, high_offset) = cond(wct == 12,le_u32)(i)?; // only from wct ==12?
    let record = SmbRequestReadAndXRecord {
        fid,
        size: (((max_count_high as u64) << 16)|max_count_low as u64),
        offset: high_offset.map(|ho| ((ho as u64) << 32) | offset as u64).unwrap_or(0),
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbResponseReadAndXRecord<'a> {
    pub len: u32,
    pub data: &'a[u8],
}

pub fn parse_smb_read_andx_response_record(i: &[u8]) -> IResult<&[u8], SmbResponseReadAndXRecord<'_>> {
    let (i, wct) = le_u8(i)?;
    let (i, _andx_command) = le_u8(i)?;
    let (i, _) = take(1_usize)(i)?; // reserved
    let (i, _andx_offset) = le_u16(i)?;
    let (i, _) = take(6_usize)(i)?;
    let (i, data_len_low) = le_u16(i)?;
    let (i, data_offset) = le_u16(i)?;
    let (i, data_len_high) = le_u32(i)?;
    let (i, _) = take(6_usize)(i)?; // reserved
    let (i, bcc) = le_u16(i)?;
    let (i, _padding) = cond(
        bcc > data_len_low,
        |b| take(bcc - data_len_low)(b)
    )(i)?; // TODO figure out how this works with data_len_high
    let (i, _padding_evasion) = cond(
        data_offset > 36+2*(wct as u16),
        |b| take(data_offset - (36+2*(wct as u16)))(b)
    )(i)?;
    let (i, file_data) = rest(i)?;

    let record = SmbResponseReadAndXRecord {
        len: ((data_len_high << 16)|data_len_low as u32),
        data: file_data,
   };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRequestRenameRecord {
    pub oldname: Vec<u8>,
    pub newname: Vec<u8>,
}

pub fn parse_smb_rename_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestRenameRecord, SmbError> {
    let (i, _wct) = le_u8(i)?;
    let (i, _search_attr) = le_u16(i)?;
    let (i, _bcc) = le_u16(i)?;
    let (i, _oldtype) = le_u8(i)?;
    let (i, oldname) = smb_get_unicode_string(i)?;
    let (i, _newtype) = le_u8(i)?;
    let (i, newname) = smb_get_unicode_string_with_offset(i, 1)?; // HACK if we assume oldname is a series of utf16 chars offset would be 1
    let record = SmbRequestRenameRecord {
        oldname,
        newname
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRequestCreateAndXRecord<> {
    pub disposition: u32,
    pub create_options: u32,
    pub file_name: Vec<u8>,
}

pub fn parse_smb_create_andx_request_record<'a>(i: &'a[u8], r: &SmbRecord)
    -> IResult<&'a[u8], SmbRequestCreateAndXRecord<>, SmbError>
{
    let (i, _skip1) = take(6_usize)(i)?;
    let (i, file_name_len) = le_u16(i)?;
    let (i, _skip3) = take(28_usize)(i)?;
    let (i, disposition) = le_u32(i)?;
    let (i, create_options) = le_u32(i)?;
    let (i, _skip2) = take(5_usize)(i)?;
    let (i, bcc) = le_u16(i)?;
    let (i, file_name) = cond(
        bcc >= file_name_len,
        |b| smb1_get_string(b, r, (bcc - file_name_len) as usize)
    )(i)?;
    let (i, _skip3) = rest(i)?;
    let record = SmbRequestCreateAndXRecord {
        disposition,
        create_options,
        file_name: file_name.unwrap_or_default(),
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct Trans2RecordParamSetFileInfoDisposition<> {
    pub delete: bool,
}

pub fn parse_trans2_request_data_set_file_info_disposition(i: &[u8])
    -> IResult<&[u8], Trans2RecordParamSetFileInfoDisposition> {
    let (i, delete) = le_u8(i)?;
    let record = Trans2RecordParamSetFileInfoDisposition {
        delete: delete & 1 == 1,
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct Trans2RecordParamSetFileInfo<'a> {
    pub fid: &'a[u8],
    pub loi: u16,
}

pub fn parse_trans2_request_params_set_file_info(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetFileInfo<'_>> {
    let (i, fid) = take(2_usize)(i)?;
    let (i, loi) = le_u16(i)?;
    let record = Trans2RecordParamSetFileInfo { fid, loi };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct Trans2RecordParamSetFileInfoRename<'a> {
    pub replace: bool,
    pub newname: &'a[u8],
}

pub fn parse_trans2_request_data_set_file_info_rename(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetFileInfoRename<'_>> {
    let (i, replace) = le_u8(i)?;
    let (i, _reserved) = take(3_usize)(i)?;
    let (i, _root_dir) = take(4_usize)(i)?;
    let (i, newname_len) = le_u32(i)?;
    let (i, newname) = take(newname_len)(i)?;
    let record = Trans2RecordParamSetFileInfoRename {
        replace: replace==1,
        newname,
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct Trans2RecordParamSetPathInfo<> {
    pub loi: u16,
    pub oldname: Vec<u8>,
}

pub fn parse_trans2_request_params_set_path_info(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetPathInfo, SmbError> {
    let (i, loi) = le_u16(i)?;
    let (i, _reserved) = take(4_usize)(i)?;
    let (i, oldname) = smb_get_unicode_string(i)?;
    let record = Trans2RecordParamSetPathInfo { loi, oldname };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct Trans2RecordParamSetPathInfoRename<'a> {
    pub replace: bool,
    pub newname: &'a[u8],
}

pub fn parse_trans2_request_data_set_path_info_rename(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetPathInfoRename<'_>> {
    let (i, replace) = le_u8(i)?;
    let (i, _reserved) = take(3_usize)(i)?;
    let (i, _root_dir) = take(4_usize)(i)?;
    let (i, newname_len) = le_u32(i)?;
    let (i, newname) = take(newname_len)(i)?;
    let record = Trans2RecordParamSetPathInfoRename {
        replace: replace==1,
        newname
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRequestTrans2Record<'a> {
    pub subcmd: u16,
    pub setup_blob: &'a[u8],
    pub data_blob: &'a[u8],
}

pub fn parse_smb_trans2_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestTrans2Record<'_>> {
    let (i, _wct) = le_u8(i)?;
    let (i, _total_param_cnt) = le_u16(i)?;
    let (i, _total_data_cnt) = le_u16(i)?;
    let (i, _max_param_cnt) = le_u16(i)?;
    let (i, _max_data_cnt) = le_u16(i)?;
    let (i, _max_setup_cnt) = le_u8(i)?;
    let (i, _reserved1) = take(1_usize)(i)?;
    let (i, _flags) = le_u16(i)?;
    let (i, _timeout) = le_u32(i)?;
    let (i, _reserved2) = take(2_usize)(i)?;
    let (i, param_cnt) = le_u16(i)?;
    let (i, param_offset) = verify(le_u16, |&v| v <= (u16::MAX - param_cnt))(i)?;
    let (i, data_cnt) = le_u16(i)?;
    let (i, data_offset) = le_u16(i)?;
    let (i, _setup_cnt) = le_u8(i)?;
    let (i, _reserved3) = take(1_usize)(i)?;
    let (i, subcmd) = le_u16(i)?;
    let (i, _bcc) = le_u16(i)?;
    //TODO test and use param_offset
    let (i, _padding) = take(3_usize)(i)?;
    let (i, setup_blob) = take(param_cnt)(i)?;
    let (i, _padding2) = cond(
        data_offset > param_offset + param_cnt,
        |b| take(data_offset - param_offset - param_cnt)(b)
    )(i)?;
    let (i, data_blob) = take(data_cnt)(i)?;

    let record = SmbRequestTrans2Record {
        subcmd,
        setup_blob,
        data_blob
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbResponseCreateAndXRecord<'a> {
    pub fid: &'a[u8],
    pub create_ts: SMBFiletime,
    pub last_access_ts: SMBFiletime,
    pub last_write_ts: SMBFiletime,
    pub last_change_ts: SMBFiletime,
    pub file_size: u64,
}

pub fn parse_smb_create_andx_response_record(i: &[u8]) -> IResult<&[u8], SmbResponseCreateAndXRecord<'_>> {
    let (i, wct) = le_u8(i)?;
    let (i, _andx_command) = le_u8(i)?;
    let (i, _) = take(1_usize)(i)?; // reserved
    let (i, _andx_offset) = le_u16(i)?;
    let (i, _oplock_level) = le_u8(i)?;
    let (i, fid) = take(2_usize)(i)?;
    let (i, _create_action) = le_u32(i)?;
    let (i, create_ts) = le_u64(i)?;
    let (i, last_access_ts) = le_u64(i)?;
    let (i, last_write_ts) = le_u64(i)?;
    let (i, last_change_ts) = le_u64(i)?;
    let (i, _) = take(4_usize)(i)?;
    let (i, file_size) = le_u64(i)?;
    let (i, _eof) = le_u64(i)?;
    let (i, _file_type) = le_u16(i)?;
    let (i, _ipc_state) = le_u16(i)?;
    let (i, _is_dir) = le_u8(i)?;
    let (i, _) = cond(wct == 42, take(32_usize))(i)?;
    let (i, _bcc) = le_u16(i)?;
    let record = SmbResponseCreateAndXRecord {
        fid,
        create_ts: SMBFiletime::new(create_ts),
        last_access_ts: SMBFiletime::new(last_access_ts),
        last_write_ts: SMBFiletime::new(last_write_ts),
        last_change_ts: SMBFiletime::new(last_change_ts),
        file_size,
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRequestCloseRecord<'a> {
    pub fid: &'a[u8],
}

pub fn parse_smb1_close_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestCloseRecord<'_>> {
    let (i, _) = take(1_usize)(i)?;
    let (i, fid) = take(2_usize)(i)?;
    let record = SmbRequestCloseRecord {
        fid,
    };
    Ok((i, record))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbVersion<> {
    pub version: u8,
}

pub fn parse_smb_version(i: &[u8]) -> IResult<&[u8], SmbVersion> {
    let (i, version) = le_u8(i)?;
    let (i, _) = tag(b"SMB")(i)?;
    let version = SmbVersion { version };
    Ok((i, version))
}

#[derive(Debug,PartialEq, Eq)]
pub struct SmbRecord<'a> {
    pub command: u8,
    pub is_dos_error: bool,
    pub nt_status: u32,
    pub flags: u8,
    pub flags2: u16,

    pub tree_id: u16,
    pub user_id: u16,
    pub multiplex_id: u16,

    pub process_id: u32,
    pub ssn_id: u32,

    pub data: &'a[u8],
}

impl SmbRecord<'_> {
    pub fn has_unicode_support(&self) -> bool {
        self.flags2 & 0x8000_u16 != 0
    }

    /// Return true if record is a request.
    pub fn is_request(&self) -> bool {
        self.flags & SMB1_FLAGS_RESPONSE == 0
    }

    /// Return true if record is a reply.
    pub fn is_response(&self) -> bool {
        self.flags & SMB1_FLAGS_RESPONSE != 0
    }
}

pub fn parse_smb_record(i: &[u8]) -> IResult<&[u8], SmbRecord<'_>> {
    let (i, _) = tag(b"\xffSMB")(i)?;
    let (i, command) = le_u8(i)?;
    let (i, nt_status) = le_u32(i)?;
    let (i, flags) = le_u8(i)?;
    let (i, flags2) = le_u16(i)?;
    let (i, process_id_high) = le_u16(i)?;
    let (i, _signature) = take(8_usize)(i)?;
    let (i, _reserved) = take(2_usize)(i)?;
    let (i, tree_id) = le_u16(i)?;
    let (i, process_id) = le_u16(i)?;
    let (i, user_id) = le_u16(i)?;
    let (i, multiplex_id) = le_u16(i)?;
    let (i, data) = rest(i)?;

    let record = SmbRecord {
        command,
        nt_status,
        flags,
        flags2,
        is_dos_error: (flags2 & 0x4000_u16 == 0),// && nt_status != 0),
        tree_id,
        user_id,
        multiplex_id,

        process_id: ((process_id_high as u32) << 16) | process_id as u32,
        //ssn_id: (((process_id as u32)<< 16)|(user_id as u32)),
        ssn_id: user_id as u32,
        data,
    };
    Ok((i, record))
}

#[test]
fn test_parse_smb1_write_andx_request_record_origin() {
    let data = hex::decode("0eff000000014000000000ff00000008001400000014003f000000000014004142434445464748494a4b4c4d4e4f5051520a0a").unwrap();
    let result = parse_smb1_write_andx_request_record(&data, SMB1_HEADER_SIZE);
	assert!(result.is_ok());
    let record = result.unwrap().1;
    assert_eq!(record.offset, 0);
    assert_eq!(record.len, 20);
    assert_eq!(record.fid, &[0x01, 0x40]);
    assert_eq!(record.data.len(), 20);
    assert_eq!(record.data, b"ABCDEFGHIJKLMNOPQR\n\n");
}
