1// This file performs the file reading step
2// Actual preprocessing is done in dbus_timeline_vis.js
3
4function MyFloatMillisToBigIntUsec(x) {
5  x = ('' + x).split('.');
6  ret = BigInt(x[0]) * BigInt(1000);
7  return ret;
8}
9
10// When the Open File dialog is completed, the name of the opened file will be passed
11// to this routine. Then the program will do the following:
12// 1. Launch "linecount.py" to get the total packet count in the PCAP file for
13//    progress
14// 2. Launch "dbus-pcap" to get the timestamps of each DBus message
15// 3. Launch "dbus-pcap" to get the JSON representation of each DBus message
16//
17function OpenDBusPcapFile(file_name) {
18  // First try to parse using dbus-pcap
19
20  ShowBlocker('Determining the number of packets in the pcap file ...');
21  const num_lines_py =
22      spawn('python3', ['linecount.py', file_name]);
23  let stdout_num_lines = '';
24  num_lines_py.stdout.on('data', (data) => {
25    stdout_num_lines += data;
26  });
27
28  num_lines_py.on('close', (code) => {
29    let num_packets = parseInt(stdout_num_lines.trim());
30    ShowBlocker('Running dbus-pcap (Pass 1/2, packet timestamps) ...');
31    const dbus_pcap1 =
32        //spawn('python3', ['dbus-pcap', file_name, '--json', '--progress']);
33        spawn('python3', ['dbus-pcap', file_name]);
34    let stdout1 = '';
35    let timestamps = [];
36    let count1 = 0; // In the first phase, consecutive newlines indicate a new entry
37    //const r = new RegExp('([0-9]+/[0-9]+) [0-9]+\.[0-9]+:.*');
38    const r = new RegExp('([0-9]+\.[0-9]+):.*');
39
40    let is_last_newline = false;  // Whether the last scanned character is a newline
41    let last_update_millis = 0;
42    dbus_pcap1.stdout.on('data', (data) => {
43      const s = data.toString();
44      stdout1 += s;
45      for (let i=0; i<s.length; i++) {
46        const ch = s[i];
47        let is_new_line = false;
48        if (ch == '\n' || ch == '\r') {
49          is_new_line = true;
50        }
51        if (!is_last_newline && is_new_line) {
52          count1 ++;
53        }
54        is_last_newline = is_new_line;
55      }
56      const millis = Date.now();
57      if (millis - last_update_millis > 100) { // Refresh at most 10 times per second
58        let pct = parseInt(count1 * 100 / num_packets);
59        ShowBlocker('Running dbus-pcap (Pass 1/2, packet timestamps): ' + count1 + '/' + num_packets + ' (' + pct + '%)');
60        last_update_millis = millis;
61      }
62    });
63
64    dbus_pcap1.stderr.on('data', (data) => {
65      console.error(data.toString());
66    });
67
68    dbus_pcap1.on('close', (code) => {
69      ShowBlocker('Running dbus-pcap (Pass 2/2, packet contents) ...');
70      let stdout2 = '';
71      let count2 = 0;
72      is_last_newline = false;
73      const dbus_pcap2 =
74          spawn('python3', ['dbus-pcap', file_name, '--json']);
75      dbus_pcap2.stdout.on('data', (data) => {
76        const s = data.toString();
77        stdout2 += s;
78        for (let i=0; i<s.length; i++) {
79          const ch = s[i];
80          let is_new_line = false;
81          if (ch == '\n' || ch == '\r') {
82            is_new_line = true;
83          }
84          if (!is_last_newline && is_new_line) {
85            count2 ++;
86          }
87          is_last_newline = is_new_line;
88        }
89        const millis = Date.now();
90        if (millis - last_update_millis > 100) { // Refresh at most 10 times per second
91          let pct = parseInt(count2 * 100 / num_packets);
92          ShowBlocker('Running dbus-pcap (Pass 2/2, packet contents): ' + count2 + '/' + num_packets + ' (' + pct + '%)');
93          last_update_millis = millis;
94        }
95      });
96
97      dbus_pcap2.stderr.on('data', (data) => {
98        console.error(data.toString());
99      });
100
101      dbus_pcap2.on('close', (code) => {
102        { ShowBlocker('Processing dbus-pcap output ... '); }
103
104        let packets = [];
105        // Parse timestamps
106        let lines = stdout1.split('\n');
107        for (let i=0; i<lines.length; i++) {
108          let l = lines[i].trim();
109          if (l.length > 0) {
110            // Timestamp
111            l = l.substr(0, l.indexOf(':'));
112            const ts_usec = parseFloat(l) * 1000.0;
113            if (!isNaN(ts_usec)) {
114              timestamps.push(ts_usec);
115            } else {
116              console.log('not a number: ' + l);
117            }
118          }
119        }
120
121        // JSON
122        lines = stdout2.split('\n');
123        for (let i=0; i<lines.length; i++) {
124          let l = lines[i].trim();
125          let parsed = undefined;
126          if (l.length > 0) {
127            try {
128              parsed = JSON.parse(l);
129            } catch (e) {
130              console.log(e);
131            }
132
133            if (parsed == undefined) {
134              try {
135                l = l.replace("NaN", "null");
136                parsed = JSON.parse(l);
137              } catch (e) {
138                console.log(e);
139              }
140            }
141
142            if (parsed != undefined) {
143              packets.push(parsed);
144            }
145          }
146        }
147
148        Timestamps_DBus = timestamps;
149
150        Data_DBus = packets.slice();
151        OnGroupByConditionChanged_DBus();
152        const v = dbus_timeline_view;
153
154        // Will return 2 packages
155        // 1) sensor PropertyChagned signal emissions
156        // 2) everything else
157        let preproc = Preprocess_DBusPcap(packets, timestamps);
158
159        let grouped = Group_DBus(preproc, v.GroupBy);
160        GenerateTimeLine_DBus(grouped);
161
162        dbus_timeline_view.IsCanvasDirty = true;
163        if (dbus_timeline_view.IsEmpty() == false ||
164            ipmi_timeline_view.IsEmpty() == false) {
165          dbus_timeline_view.CurrentFileName = file_name;
166          ipmi_timeline_view.CurrentFileName = file_name;
167          HideWelcomeScreen();
168          ShowDBusTimeline();
169          ShowIPMITimeline();
170          ShowNavigation();
171          UpdateFileNamesString();
172        }
173        HideBlocker();
174
175        g_btn_zoom_reset.click(); // Zoom to capture time range
176      });
177    });
178  })
179}
180
181// Input: data and timestamps obtained from
182// Output: Two arrays
183//   The first is sensor PropertyChanged emissions only
184//   The second is all other DBus message types
185//
186// This function also determines the starting timestamp of the capture
187//
188function Preprocess_DBusPcap(data, timestamps) {
189  // Also clear IPMI entries
190  g_ipmi_parsed_entries = [];
191
192  let ret = [];
193
194  let in_flight = {};
195  let in_flight_ipmi = {};
196
197  for (let i = 0; i < data.length; i++) {
198    const packet = data[i];
199
200    // Fields we are interested in
201    const fixed_header = packet[0];  // is an [Array(5), Array(6)]
202
203    if (fixed_header == undefined) {  // for hacked dbus-pcap
204      console.log(packet);
205      continue;
206    }
207
208    const payload = packet[1];
209    const ty = fixed_header[0][1];
210    let timestamp = timestamps[i];
211    let timestamp_end = undefined;
212    const IDX_TIMESTAMP_END = 8;
213    const IDX_MC_OUTCOME = 9;  // Outcome of method call
214
215    let serial, path, member, iface, destination, sender, signature = '';
216    // Same as the format of the Dummy data set
217
218    switch (ty) {
219      case 4: {  // signal
220        serial = fixed_header[0][5];
221        path = fixed_header[1][0][1];
222        iface = fixed_header[1][1][1];
223        member = fixed_header[1][2][1];
224        // signature = fixed_header[1][3][1];
225        // fixed_header[1] can have variable length.
226        // For example: signal from org.freedesktop.PolicyKit1.Authority can
227        // have only 4 elements, while most others are 5
228        const idx = fixed_header[1].length - 1;
229        sender = fixed_header[1][idx][1];
230
231        // Ugly fix for:
232        if (sender == "s" || sender == "sss") {
233          sender = packet[1][0];
234          if (fixed_header[1].length == 6) {
235            // Example: fixed_header is
236            // 0: (2) [7, "org.freedesktop.DBus"]
237            // 1: (2) [6, ":1.1440274"]
238            // 2: (2) [1, "/org/freedesktop/DBus"]
239            // 3: (2) [2, "org.freedesktop.DBus"]
240            // 4: (2) [3, "NameLost"]
241            // 5: (2) [8, "s"]
242            path = fixed_header[1][2][1];
243            iface = fixed_header[1][3][1];
244            member = fixed_header[1][4][1];
245          } else if (fixed_header[1].length == 5) {
246            // Example:  fixed_header is
247            // 0: (2) [7, "org.freedesktop.DBus"]
248            // 1: (2) [1, "/org/freedesktop/DBus"]
249            // 2: (2) [2, "org.freedesktop.DBus"]
250            // 3: (2) [3, "NameOwnerChanged"]
251            // 4: (2) [8, "sss"]
252            path = fixed_header[1][1][1];
253            iface = fixed_header[1][2][1];
254            member = fixed_header[1][3][1];
255          }
256        }
257
258
259        destination = '<none>';
260        timestamp_end = timestamp;
261        let entry = [
262          'sig', timestamp, serial, sender, destination, path, iface, member,
263          timestamp_end, payload
264        ];
265
266        // Legacy IPMI interface uses signal for IPMI request
267        if (iface == 'org.openbmc.HostIpmi' && member == 'ReceivedMessage') {
268          console.log('Legacy IPMI interface, request');
269        }
270
271        ret.push(entry);
272        break;
273      }
274      case 1: {  // method call
275        serial = fixed_header[0][5];
276        path = fixed_header[1][0][1];
277        member = fixed_header[1][1][1];
278        iface = fixed_header[1][2][1];
279        destination = fixed_header[1][3][1];
280        if (fixed_header[1].length > 5) {
281          sender = fixed_header[1][5][1];
282          signature = fixed_header[1][4][1];
283        } else {
284          sender = fixed_header[1][4][1];
285        }
286        let entry = [
287          'mc', timestamp, serial, sender, destination, path, iface, member,
288          timestamp_end, payload, packet, ''
289        ];
290
291        // Legacy IPMI interface uses method call for IPMI response
292        if (iface == 'org.openbmc.HostIpmi' && member == 'sendMessge') {
293          console.log('Legacy IPMI interface, response')
294        } else if (
295            iface == 'xyz.openbmc_project.Ipmi.Server' && member == 'execute') {
296          let ipmi_entry = {
297            netfn: payload[0],
298            lun: payload[1],
299            cmd: payload[2],
300            request: payload[3],
301            start_usec: MyFloatMillisToBigIntUsec(timestamp),
302            end_usec: 0,
303            response: []
304          };
305          in_flight_ipmi[serial] = (ipmi_entry);
306        }
307
308
309        ret.push(entry);
310        in_flight[serial] = (entry);
311        break;
312      }
313      case 2: {  // method reply
314        let reply_serial = fixed_header[1][0][1];
315        if (reply_serial in in_flight) {
316          let x = in_flight[reply_serial];
317          delete in_flight[reply_serial];
318          x[IDX_TIMESTAMP_END] = timestamp;
319          x[IDX_MC_OUTCOME] = 'ok';
320        }
321
322        if (reply_serial in in_flight_ipmi) {
323          let x = in_flight_ipmi[reply_serial];
324          delete in_flight_ipmi[reply_serial];
325          if (payload[0] != undefined && payload[0][4] != undefined) {
326            x.response = payload[0][4];
327          }
328          x.end_usec = MyFloatMillisToBigIntUsec(timestamp);
329          g_ipmi_parsed_entries.push(x);
330        }
331        break;
332      }
333      case 3: {  // error reply
334        let reply_serial = fixed_header[1][0][1];
335        if (reply_serial in in_flight) {
336          let x = in_flight[reply_serial];
337          delete in_flight[reply_serial];
338          x[IDX_TIMESTAMP_END] = timestamp;
339          x[IDX_MC_OUTCOME] = 'error';
340        }
341      }
342    }
343  }
344
345  if (g_ipmi_parsed_entries.length > 0) UpdateLayout();
346  return ret;
347}
348