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
170
171
172
173
174
use std::io::Write;

use byteorder::WriteBytesExt;
use integer_encoding::VarIntWriter;

use super::{Topic, Message, SDO, Field, Data, DataType, fields::{PAGE_SIZE, TIMEOUT}};

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum Error {
    #[error("io error")]
    Io(#[from] std::io::Error)
}

pub fn encode_field(header: &Field, data: &Option<Data>) -> Vec<u8> {
    let mut buf = vec![];
    let b = 0x00;
    let has_null = false;
    buf.write_u8(((header.data_type as u8) << 3) + ((header.wire_type as u8) << 1) + if has_null { 1 } else { 0 });

    buf.write_varint((header.field_id.unwrap_or(0) << 1) + (if header.data_type == DataType::DateTime { 1 } else { 0 }));

    match &data {
        Some(Data::StringW(array)) => {
            for str in array.iter().flatten() {
                buf.write_varint(str.len() + 1);
                buf.write_u8(0);
                buf.write(str.as_bytes());
            }
        },
        Some(Data::AsciiString(array)) => {
            for str in array.iter().flatten() {
                buf.write_varint(str.0.len() + 1);
                buf.write(str.0.as_bytes());
            }
        },
        Some(Data::Long(array) | Data::Short(array)) => {
            for value in array.iter().flatten() {
                buf.write_varint(**value);
            }
        },
        Some(Data::SDO(array)) => {
            for sdo in array.iter().flatten() {
                buf.write_all(&sdo.encode());
            }
        },
        Some(Data::Bool(array)) => {
            // buf.write_u8(0x28);
            let mut bytes = vec![];
            let mut n = 0;
            let mut s = 0u8;
            for (r, value) in array.into_iter().enumerate() {
                if let Some(value) = value {
                    if n > 0 && n % 8 == 0 {
                        bytes.push(s);
                        s = 0;
                        n = 0;
                    }
                    s += (if true == *value { 1 } else { 0 }) << (7 - (n % 8));
                    n += 1;
                }
            }
            if n > 0 {
                bytes.push(s)
            }
            // buf.write_varint(bytes.len());
            for b in bytes {
                buf.write_u8(b);
            }
        }
        type_ => warn!(?type_, "unknown type")
    }
    buf
}

impl SDO {
    pub fn encode(&self) -> Vec<u8> {
        let mut buf = vec![];
        buf.write_u8(0x17);
        buf.write_varint((self.topic as i32 + 1) as u32);
        for (header, data) in &self.fields {
            buf.write(&encode_field(header, data));
        }
        buf.write_u8(0);
        buf
    }
}

impl Message {
    pub fn encode(&self) -> Vec<u8> {
        let mut buf = vec![];
        let mut header_sdo = SDO::new(Topic::UndefinedTopic);
        header_sdo.push_string_w(74, vec![self.id.clone()]);
        buf.write(&header_sdo.encode());

        let mut payload = self.sdo.clone();
        if self.timeout.is_some() {
            payload.push_string_w(TIMEOUT, self.timeout.clone())
        }
        payload.push_string_w(PAGE_SIZE, Some(self.page_size.unwrap_or(1000).to_string()));
        buf.write(&self.sdo.encode());

        buf
    }
}

#[cfg(test)]
mod test {
    use std::{io::{Cursor, Write}, fs::File};

    use crate::sdo::decode::read_msg;

    #[test]
    pub fn test_decode_encode() -> miette::Result<()> {
        /*let buf: Vec<u8> = include_bytes!("../../../../data/mitm_TrLoginInfo.bin").to_vec();

        let msg = read_msg(&mut Cursor::new(buf.clone()))?;
        let newbuf = msg.encode();

        let mut f = File::create("./mitm_TrLoginInfo.reencode.bin").unwrap();
        f.write_all(&newbuf).unwrap();
        
        assert_eq!(buf, newbuf);*/
        Ok(())
    }

    /* #[test]
    pub fn test_session_start() -> miette::Result<()> {
        static WS_DUMP: &[u8] = include_bytes!("../../../../data/tx_session_start.bin");
        println!("expected: {:x?}", WS_DUMP);

        let mut message = Message::new(Topic::TdQuote);
        message.fields.push(Field { 
            field_id: 77, // UserName
            data: crate::sdo::encode::Data::StringW(vec![Some(Box::new("IN8001646".to_string()))])
        });
        message.fields.push(Field { 
            field_id: 486, // CompanyName
            data: crate::sdo::encode::Data::StringW(vec![Some(Box::new("invast".to_string()))])
        });
        message.fields.push(Field { 
            field_id: 489, // ClientType
            data: crate::sdo::encode::Data::Long(vec![Some(Box::new(ClientType::TraderPlus as u32))])
        });
        message.fields.push(Field { 
            field_id: 499,
            data: crate::sdo::encode::Data::Long(vec![Some(Box::new(1))])
        });
        message.fields.push(Field { 
            field_id: 6140, // acsLoginToken
            data: crate::sdo::encode::Data::StringW(vec![Some(Box::new("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".to_string()))])
        });
        message.fields.push(Field { 
            field_id: 3961, // entryId
            data: crate::sdo::encode::Data::StringW(vec![Some(Box::new("invast".to_string()))])
        });
        message.fields.push(Field { 
            field_id: 9161, // entryIdToKick
            data: crate::sdo::encode::Data::StringW(vec![Some(Box::new("invast".to_string()))])
        });
        message.fields.push(Field { 
            field_id: 1076,
            data: crate::sdo::encode::Data::StringW(vec![Some(Box::new("1".to_string()))])
        });
        message.fields.push(Field { 
            field_id: 490, // productFullVersion
            data: crate::sdo::encode::Data::StringW(vec![Some(Box::new("22.10.10/2022.14".to_string()))])
        });
        let bytes = message.encode();
        // println!("got:      {:x?}", bytes);

        assert_eq!(WS_DUMP, bytes);
        Ok(())
    } */
}