ipfix 0.1.1

IPFIX (RFC7011) parser library with JSON export, based on nom
Documentation
#[macro_use]
extern crate nom;

pub mod conversions;
pub mod printer;

pub use printer::*;

use conversions::*;
use std::collections::HashMap;

// Using binary trees allows the JSON output to stay in the same order for each record, but its performance is slower than a HashMap.
// Should make functions generic and allow this to be changed by a bool flag.
use std::collections::BTreeMap;

pub struct IpfixConsumer {
    templates: HashMap<u16, Template>,
    options_templates: HashMap<u16, OptionsTemplate>,
}

#[allow(dead_code)]
struct IpfixHeader {
    version: u16,
    length: u16,
    export_time: u32,
    sequence_number: u32,
    observation_domain_id: u32,
}

#[allow(dead_code)]
struct IpfixMessage<'a> {
    header: IpfixHeader,
    sets: Vec<Set<'a>>,
}

pub enum Set<'a> {
    TemplateSet(TemplateSet),
    OptionsTemplateSet(OptionsTemplateSet),
    DataSet(DataSet<'a>),
}

struct SetHeader {
    set_id: u16, // 2: Template Set, 3: Options Template Set, >255: Data Set
    length: u16,
}

pub struct TemplateSet {
    #[allow(dead_code)]
    header: SetHeader,
    records: Vec<Template>,
}

struct TemplateHeader {
    template_id: u16,
    field_count: u16,
}

struct Template {
    header: TemplateHeader,
    field_specifiers: Vec<FieldSpecifier>,
}

pub struct OptionsTemplateSet {
    #[allow(dead_code)]
    header: SetHeader,
    records: Vec<OptionsTemplate>,
}

struct OptionsTemplateHeader {
    id: u16,
    field_count: u16,
    #[allow(dead_code)]
    scope_field_count: u16,
}

struct OptionsTemplate {
    header: OptionsTemplateHeader,
    field_specifiers: Vec<FieldSpecifier>,
}

struct FieldSpecifier {
    ident: u16, // 15b in msg
    field_length: u16,
    #[allow(dead_code)]
    enterprise_number: Option<u32>,
}

pub struct DataSet<'a> {
    #[allow(dead_code)]
    header: SetHeader,
    records: Vec<DataRecord<'a>>,
}

#[derive(PartialEq)]
pub struct DataRecord<'a> {
    values: BTreeMap<u16, &'a [u8]>,
}

impl IpfixConsumer {
    pub fn new() -> IpfixConsumer {
        IpfixConsumer {
            templates: HashMap::new(),
            options_templates: HashMap::new(),
        }
    }

    #[inline]
    fn add_template(&mut self, template: Template) {
        let id = template.header.template_id;
        self.templates.insert(id, template);
    }

    #[inline]
    fn add_options_template(&mut self, template: OptionsTemplate) {
        let id = template.header.id;
        self.options_templates.insert(id, template);
    }

    #[inline]
    pub fn parse_message<'a>(&'a mut self, data: &'a [u8]) -> Result<Vec<DataSet>, &'static str> {
        // this should be 1:1 with UDP datagrams
        // we aren't currently using any of the data from the ipfix message header but we still
        // need to chop it off
        if let nom::IResult::Done(bytes, _) = parse_ipfix_header(data) {
            let mut remaining_bytes = bytes;
            let mut datasets = Vec::<DataSet>::new();
            loop {
                if let nom::IResult::Done(bytes, set_header) = parse_set_header(remaining_bytes) {
                    let set_length = (set_header.length - 4) as usize;
                    let set_bytes = &bytes.clone()[0..set_length];
                    if bytes.len() - set_length > 0 {
                        remaining_bytes = &bytes[set_length..bytes.len()];
                    } else {
                        remaining_bytes = &[];
                    }
                    let set = match set_header.set_id.clone() {
                        2 => {
                            // template set
                            Some(parse_template_set(set_bytes, set_header))
                        }
                        3 => {
                            // options template set
                            Some(parse_options_template_set(set_bytes, set_header))
                        }
                        _ => {
                            // data set
                            if self.templates.contains_key(&set_header.set_id) {
                                let template = {
                                    self.templates.get(&set_header.set_id).unwrap()
                                };
                                Some(parse_data_set(set_bytes, set_header, template))
                            } else if self.options_templates.contains_key(&set_header.set_id) {
                                let options_template = {
                                    self.options_templates.get(&set_header.set_id).unwrap()
                                };
                                Some(parse_options_set(set_bytes, set_header, options_template))
                            } else {
                                None
                            }
                        }
                    };
                    if let Some(nom::IResult::Done(_, object)) = set {
                        match object {
                            Set::TemplateSet(set) => {
                                for template in set.records {
                                    self.add_template(template);
                                }
                            }
                            Set::OptionsTemplateSet(set) => {
                                for template in set.records {
                                    self.add_options_template(template);
                                }
                            }
                            Set::DataSet(dataset) => {
                                datasets.push(dataset);
                            }
                        }
                    }
                }
                if remaining_bytes.len() == 0 as usize {
                    break;
                }
            }
            return Ok(datasets);
        }
        Err("Parsing failed")
    }
}

impl<'a> DataRecord<'a> {
    pub fn to_json(&self) {
        for field in &self.values {
            println!("{:?}", field);
        }
    }
}

#[inline]
pub fn is_template_set(data: &[u8]) -> Result<bool, &'static str> {
    // big-endian 0x0f-0x10
    if data.len() >= 18 {
        let id = conversions::be_buf_to_u16(&data[16..18]);
        Ok(id == 2 || id == 3)
    } else {
        Err("Data too short".into())
    }
}

#[inline]
pub fn get_message_length(data: &[u8]) -> Result<u16, &'static str> {
    // big-endian, 0x02-0x03
    if data.len() >= 3 {
        Ok(conversions::be_buf_to_u16(&data[2..4]))
    } else {
        Err("Data too short".into())
    }
}

#[inline]
fn parse_ipfix_header(data: &[u8]) -> nom::IResult<&[u8], IpfixHeader> {
    //  0                   1                   2                   3
    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |       Version Number          |            Length             |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                           Export Time                         |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                       Sequence Number                         |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                    Observation Domain ID                      |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    named!(ipfix_version <&[u8], u16>, map!(take!(2), be_buf_to_u16));
    named!(message_length <&[u8], u16>, map!(take!(2), be_buf_to_u16));
    named!(export_time <&[u8], u32>, map!(take!(4), be_buf_to_u32));
    named!(sequence_number <&[u8], u32>, map!(take!(4), be_buf_to_u32));
    named!(observation_domain_id <&[u8], u32>, map!(take!(4), be_buf_to_u32));

    named!(ipfix_header <&[u8], IpfixHeader>, chain!(
        version: ipfix_version ~
        length: message_length ~
        time: export_time ~
        sequence: sequence_number ~
        domain_id: observation_domain_id,
        || { IpfixHeader {
            version: version,
            length: length,
            export_time: time,
            sequence_number: sequence,
            observation_domain_id: domain_id
        }}
    ));

    ipfix_header(&data)
}

#[inline]
fn parse_field_specifier(data: &[u8]) -> nom::IResult<&[u8], FieldSpecifier> {
    // 0                   1                   2                   3
    // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |E|  Information Element ident. |        Field Length           |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                      Enterprise Number                        |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    // if greater than 2^15, bit is 1, and subtract 2^15 for ident
    named!(element_ident_with_enterprise_bit <&[u8], u16>, map!(take!(2), be_buf_to_u16));
    named!(field_length <&[u8], u16>, map!(take!(2), be_buf_to_u16));
    named!(enterprise_number <&[u8], u32>, map!(take!(4), be_buf_to_u32));

    named!(enterprise_field_specifier<&[u8], (u16, u32)>, tuple!(
        field_length,
        enterprise_number
    ));

    if let nom::IResult::Done(bytes, id) = element_ident_with_enterprise_bit(data) {
        // pull off the first bit
        if id > 32767 {
            if let nom::IResult::Done(remaining_data, (field_length, enterprise_number)) =
                enterprise_field_specifier(bytes) {
                return nom::IResult::Done(remaining_data,
                                          FieldSpecifier {
                                              enterprise_number: Some(enterprise_number),
                                              ident: id - 32768,
                                              field_length: field_length,
                                          });
            }
        } else {
            if let nom::IResult::Done(remaining_data, field_length) = field_length(bytes) {
                return nom::IResult::Done(remaining_data,
                                          FieldSpecifier {
                                              enterprise_number: None,
                                              ident: id,
                                              field_length: field_length,
                                          });
            }
        }
    }
    nom::IResult::Incomplete(nom::Needed::Unknown)
}


#[inline]
fn parse_set_header(data: &[u8]) -> nom::IResult<&[u8], SetHeader> {
    //  0                   1                   2                   3
    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |          Set ID               |          Length               |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    named!(set_id <&[u8], u16>, map!(take!(2), be_buf_to_u16));
    named!(set_length <&[u8], u16>, map!(take!(2), be_buf_to_u16));

    named!(set_header <&[u8], SetHeader>, chain!(
        id: set_id ~
        length: set_length,
        || { SetHeader {
            set_id: id,
            length: length
        }}
    ));

    set_header(data)
}

#[inline]
fn parse_template_header(data: &[u8]) -> nom::IResult<&[u8], TemplateHeader> {
    // 0                   1                   2                   3
    // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |      Template ID (> 255)      |         Field Count           |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    named!(template_id <&[u8], u16>, map!(take!(2), be_buf_to_u16));
    named!(field_count <&[u8], u16>, map!(take!(2), be_buf_to_u16));

    named!(template_header <&[u8], TemplateHeader>, chain!(
        id: template_id ~
        field_count: field_count,
        || { TemplateHeader {
            template_id: id,
            field_count: field_count
        }}
    ));

    template_header(data)
}

#[inline]
fn parse_template_set(mut data: &[u8], set_header: SetHeader) -> nom::IResult<&[u8], Set> {
    //  0                   1                   2                   3
    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |1| Information Element id. 1.1 |        Field Length 1.1       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                    Enterprise Number  1.1                     |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |0| Information Element id. 1.2 |        Field Length 1.2       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |             ...               |              ...              |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |1| Information Element id. 1.N |        Field Length 1.N       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                    Enterprise Number  1.N                     |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |      Template ID = 257        |         Field Count = M       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |0| Information Element id. 2.1 |        Field Length 2.1       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |1| Information Element id. 2.2 |        Field Length 2.2       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                    Enterprise Number  2.2                     |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |             ...               |              ...              |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |1| Information Element id. 2.M |        Field Length 2.M       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                    Enterprise Number  2.M                     |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |                          Padding (opt)                        |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    let mut templates = Vec::<Template>::new();

    while data.len() > 0 as usize {
        let mut fields = Vec::<FieldSpecifier>::new();
        if let nom::IResult::Done(bytes, header) = parse_template_header(&data) {
            data = bytes;
            for _ in 0..header.field_count {
                if let nom::IResult::Done(bytes, field) = parse_field_specifier(data) {
                    fields.push(field);
                    data = bytes;
                }
            }
            templates.push(Template {
                header: header,
                field_specifiers: fields,
            });
        }
    }

    let result = Set::TemplateSet(TemplateSet {
        header: set_header,
        records: templates,
    });

    nom::IResult::Done(data, result)
}

#[inline]
fn parse_options_template_header(data: &[u8]) -> nom::IResult<&[u8], OptionsTemplateHeader> {
    //  0                   1                   2                   3
    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |         Template ID (> 255)   |         Field Count           |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |      Scope Field Count        |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    named!(template_id <&[u8], u16>, map!(take!(2), be_buf_to_u16));
    named!(field_count <&[u8], u16>, map!(take!(2), be_buf_to_u16));
    named!(scope_field_count <&[u8], u16>, map!(take!(2), be_buf_to_u16));

    named!(options_template_header <&[u8], OptionsTemplateHeader>, chain!(
        id: template_id ~
        field_count: field_count ~
        scope_field_count: scope_field_count,
        || { OptionsTemplateHeader {
            id: id,
            field_count: field_count,
            scope_field_count: scope_field_count
        }}
    ));

    options_template_header(data)
}

#[inline]
fn parse_options_template_set(mut data: &[u8], set_header: SetHeader) -> nom::IResult<&[u8], Set> {
    //  0                   1                   2                   3
    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |         <HEADER USE>          |0|  Scope 1 Infor. Element id. |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |     Scope 1 Field Length      |0|  Scope 2 Infor. Element id. |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |     Scope 2 Field Length      |             ...               |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |            ...                |1|  Scope N Infor. Element id. |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |     Scope N Field Length      |   Scope N Enterprise Number  ...
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // ..  Scope N Enterprise Number   |1| Option 1 Infor. Element id. |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |    Option 1 Field Length      |  Option 1 Enterprise Number  ...
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // .. Option 1 Enterprise Number   |              ...              |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |             ...               |0| Option M Infor. Element id. |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |     Option M Field Length     |      Padding (optional)       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    //    if let nom::IResult::Done(bytes, header) = parse_options_template_header(&data) {

    //TODO: merge this into parse_template_set

    let mut templates = Vec::<OptionsTemplate>::new();

    while data.len() > 0 as usize {
        let mut fields = Vec::<FieldSpecifier>::new();
        if let nom::IResult::Done(bytes, header) = parse_options_template_header(&data) {
            data = bytes;
            for _ in 0..header.field_count {
                if let nom::IResult::Done(bytes, field) = parse_field_specifier(data) {
                    fields.push(field);
                    data = bytes;
                }
            }
            templates.push(OptionsTemplate {
                header: header,
                field_specifiers: fields,
            });
        }
    }

    let result = Set::OptionsTemplateSet(OptionsTemplateSet {
        header: set_header,
        records: templates,
    });

    nom::IResult::Done(data, result)
}

#[inline]
fn parse_data_set<'a>(data: &'a [u8],
                      set_header: SetHeader,
                      template: &Template)
                      -> nom::IResult<&'a [u8], Set<'a>> {
    //  0                   1                   2                   3
    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 1 - Field Value 1    |   Record 1 - Field Value 2    |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 1 - Field Value 3    |             ...               |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 2 - Field Value 1    |   Record 2 - Field Value 2    |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 2 - Field Value 3    |             ...               |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 3 - Field Value 1    |   Record 3 - Field Value 2    |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 3 - Field Value 3    |             ...               |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |              ...              |      Padding (optional)       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    let mut records = Vec::<DataRecord>::new();
    let mut offset = 0;

    while offset < data.len() {
        let mut values = BTreeMap::<u16, &[u8]>::new();
        for field in &template.field_specifiers {
            let bytes = &data[offset..offset + field.field_length as usize];
            values.insert(field.ident, bytes);
            offset += field.field_length as usize;
        }
        records.push(DataRecord { values: values });
    }

    nom::IResult::Done(data,
                       Set::DataSet(DataSet {
                           header: set_header,
                           records: records,
                       }))
}

#[inline]
fn parse_options_set<'a>(data: &'a [u8],
                         set_header: SetHeader,
                         template: &OptionsTemplate)
                         -> nom::IResult<&'a [u8], Set<'a>> {
    //  0                   1                   2                   3
    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 1 - Field Value 1    |   Record 1 - Field Value 2    |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 1 - Field Value 3    |             ...               |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 2 - Field Value 1    |   Record 2 - Field Value 2    |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 2 - Field Value 3    |             ...               |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 3 - Field Value 1    |   Record 3 - Field Value 2    |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |   Record 3 - Field Value 3    |             ...               |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // |              ...              |      Padding (optional)       |
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    let mut records = Vec::<DataRecord>::new();
    let mut offset = 0;

    while offset < data.len() {
        let mut values = BTreeMap::<u16, &[u8]>::new();
        for field in &template.field_specifiers {
            let bytes = &data[offset..offset + field.field_length as usize];
            values.insert(field.ident, bytes);
            offset += field.field_length as usize;
        }
        records.push(DataRecord { values: values });
    }

    nom::IResult::Done(data,
                       Set::DataSet(DataSet {
                           header: set_header,
                           records: records,
                       }))
}