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
use deku::prelude::*;

/// File access type event that will trigger an ALP action.
#[derive(DekuRead, DekuWrite, Default, Debug, Clone, PartialEq)]
#[deku(bits = 3, endian = "big", type = "u8")]
pub enum ActionCondition {
    #[default]
    #[deku(id = "0")]
    List,
    #[deku(id = "1")]
    Read,
    #[deku(id = "2")]
    Write,
    #[deku(id = "3")]
    WriteFlush,
}

#[derive(DekuRead, DekuWrite, Default, Debug, Clone, PartialEq)]
#[deku(bits = 2, type = "u8")]
pub enum StorageClass {
    /// The content is not kept in memory. It cannot be read back.
    #[default]
    #[deku(id = "0")]
    Transient,
    /// The content is kept in a volatile memory of the device. It is accessible for
    /// read, and is lost on power off.
    #[deku(id = "1")]
    Volatile,
    /// The content is kept in a volatile memory of the device. It is accessible for
    /// read, and can be backed-up upon request in a permanent storage
    /// location. It is restored from the permanent location on device power on.
    #[deku(id = "2")]
    Restorable,
    /// The content is kept in a permanent memory of the device. It is accessible
    /// for read and write.
    #[deku(id = "3")]
    Permanent,
}

#[derive(DekuRead, DekuWrite, Default, Debug, Clone, PartialEq)]
pub struct UserPermissions {
    #[deku(bits = 1)]
    pub read: bool,
    #[deku(bits = 1)]
    pub write: bool,
    #[deku(bits = 1)]
    pub executable: bool,
}

#[derive(DekuRead, DekuWrite, Default, Debug, Clone, PartialEq)]
pub struct FilePermissions {
    #[deku(bits = 1)]
    pub encrypted: bool,
    #[deku(bits = 1)]
    pub executable: bool,
    pub user: UserPermissions,
    pub guest: UserPermissions,
}

#[derive(DekuRead, DekuWrite, Default, Debug, Clone, PartialEq)]
pub struct FileProperties {
    /// Enables the D7AActP (ALP action to trigger upon some type of access to this file)
    #[deku(bits = 1)]
    pub enabled: bool,

    /// Type of access needed to trigger the D7AActP
    pub condition: ActionCondition,

    /// Type of storage of this file
    #[deku(pad_bits_before = "2")]
    pub storage_class: StorageClass,
}

#[derive(DekuRead, DekuWrite, Default, Debug, Clone, PartialEq)]
pub struct FileHeader {
    pub permissions: FilePermissions,
    pub properties: FileProperties,
    pub alp_command_file_id: u8,
    pub interface_file_id: u8,
    #[deku(endian = "big")]
    pub file_size: u32,
    #[deku(endian = "big")]
    pub allocated_size: u32,
}

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

    #[test]
    fn test_file_permissions() {
        let permissions = FilePermissions {
            encrypted: true,
            executable: false,
            user: UserPermissions {
                read: true,
                write: true,
                executable: true,
            },
            guest: UserPermissions {
                read: false,
                write: false,
                executable: false,
            },
        };

        let expected = hex!("B8");

        assert_eq!(permissions.to_bytes().unwrap(), expected);
        assert_eq!(
            FilePermissions::from_bytes((&expected, 0)).unwrap().1,
            permissions
        );
    }

    #[test]
    fn test_file_header() {
        let header = FileHeader {
            permissions: FilePermissions {
                encrypted: true,
                executable: false,
                user: UserPermissions {
                    read: true,
                    write: true,
                    executable: true,
                },
                guest: UserPermissions {
                    read: false,
                    write: false,
                    executable: false,
                },
            },
            properties: FileProperties {
                enabled: false,
                condition: ActionCondition::Read,
                storage_class: StorageClass::Permanent,
            },
            alp_command_file_id: 1,
            interface_file_id: 2,
            file_size: 0xDEAD_BEEF,
            allocated_size: 0xBAAD_FACE,
        };

        let expected = hex!("B8 13 01 02 DEADBEEF BAADFACE");

        assert_eq!(header.to_bytes().unwrap(), expected);
        assert_eq!(FileHeader::from_bytes((&expected, 0)).unwrap().1, header);
    }
}