1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Implementation of a [Dash7](https://dash7-alliance.org/) ALP protocol parser from its
//! public specification.
//!
//! The protocol
//! ==============================================================================
//! The protocol specifies ALP Commands that can be sent to another system to communicate.
//! Each command is an aggregation of ALP Actions.
//!
//! The protocol is based on the fact that each communicating party hold a Dash7 filesystem.
//! Each request toward an other device is then composed as an array of simple filesystem operation
//! (ALP actions).
//!
//! About this library
//! ==============================================================================
//! The goal of this library is to implement a specification with an emphasis on correctness, then
//! on usability. Performance and memory usage are currently considered a secondary objective.

#[cfg(test)]
mod test_tools;
#[cfg(test)]
use hex_literal::hex;

#[cfg(test)]
use test_tools::test_item;

/// ALP basic Actions used to build Commands
pub mod action;
/// A Codec module specifying how to encode/decode each encodable items
pub mod codec;
/// Dash7 specific items (most of the ALP protocol could be in theory be used over any
/// communication link)
pub mod dash7;
/// Filesystem related items
pub mod data;
/// Module managing the creation of protected items
pub mod new;
/// Operands used to build the ALP Actions
pub mod operand;
/// ALP variable int codec implementation
pub mod varint;
pub use action::Action;
pub use codec::Codec;

// TODO Verify each item's name against the SPEC

// TODO Look into const function to replace some macros?
// TODO Use uninitialized memory where possible
// TODO Int enums: fn from(): find a way to avoid double value definition
// TODO Int enums: optim: find a way to cast from int to enum instead of calling a matching
// function (much more resource intensive). Only do that for enums that match all possible
// values that result from the parsing.
// TODO Optimize min size calculation (fold it into the upper OP when possible)
// TODO usize is target dependent. In other words, on a 16 bit processor, we will run into
// troubles if we were to convert u32 to usize (even if a 64Ko payload seems a bit big).
// Maybe we should just embrace this limitation? (Not to be lazy or anything...)
// The bad thing is that u32 to u16 will compile and panic at runtime if the value is too big.
// TODO Slice copies still check length consistency dynamically. Is there a way to get rid of that
// at runtime while still testing it at compile/test time?
//      - For simple index access, get_unchecked_mut can do the trick. But It makes the code hard to
//      read...
// TODO is {out = &out[offset..]; out[..size]} more efficient than {out[offset..offset+size]} ?
// TODO Add function to encode without having to define a temporary structure

// ===============================================================================
// Definitions
// ===============================================================================
#[derive(Clone, Debug, PartialEq)]
pub enum Enum {
    OpCode,
    NlsMethod,
    RetryMode,
    RespMode,
    InterfaceId,
    PermissionId,
    PermissionLevel,
    QueryComparisonType,
    QueryRangeComparisonType,
    QueryCode,
    StatusType,
    ActionCondition,
}

// ===============================================================================
// Command
// ===============================================================================
/// ALP request that can be sent to an ALP compatible device.
#[derive(Clone, Debug, PartialEq)]
pub struct Command {
    pub actions: Vec<Action>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CommandParseFail {
    pub actions: Vec<Action>,
    pub error: codec::ParseFail,
}

impl Default for Command {
    fn default() -> Self {
        Self { actions: vec![] }
    }
}
impl Command {
    fn partial_decode(out: &[u8]) -> Result<codec::ParseValue<Command>, CommandParseFail> {
        let mut actions = vec![];
        let mut offset = 0;
        loop {
            if offset == out.len() {
                break;
            }
            match Action::decode(&out[offset..]) {
                Ok(codec::ParseValue { value, size }) => {
                    actions.push(value);
                    offset += size;
                }
                Err(error) => {
                    return Err(CommandParseFail {
                        actions,
                        error: error.inc_offset(offset),
                    })
                }
            }
        }
        Ok(codec::ParseValue {
            value: Self { actions },
            size: offset,
        })
    }
}
impl Codec for Command {
    fn encoded_size(&self) -> usize {
        self.actions.iter().map(|act| act.encoded_size()).sum()
    }
    unsafe fn encode(&self, out: &mut [u8]) -> usize {
        let mut offset = 0;
        for action in self.actions.iter() {
            offset += action.encode(&mut out[offset..]);
        }
        offset
    }
    fn decode(out: &[u8]) -> codec::ParseResult<Self> {
        Self::partial_decode(out).map_err(|v| v.error)
    }
}
#[test]
fn test_command() {
    test_item(
        Command {
            actions: vec![
                Action::RequestTag(action::RequestTag { id: 66, eop: true }),
                Action::ReadFileData(
                    action::new::ReadFileData {
                        resp: true,
                        group: false,
                        file_id: 0,
                        offset: 0,
                        size: 8,
                    }
                    .build()
                    .unwrap(),
                ),
                Action::Nop(action::Nop {
                    resp: true,
                    group: true,
                }),
            ],
        },
        &hex!("B4 42   41 00 00 08   C0"),
    )
}