Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-dji-uav.c
Line
Count
Source
1
/* packet-dji-uav.c
2
 * Routines for the disassembly of the command protocol for the
3
 * DJI Phantom 2 Vision+ UAV
4
 * http://www.dji.com/product/phantom-2-vision-plus
5
 * and possibly others.
6
 *
7
 * Copyright 2014,2015 Joerg Mayer (see AUTHORS file)
8
 *
9
 * Wireshark - Network traffic analyzer
10
 * By Gerald Combs <gerald@wireshark.org>
11
 * Copyright 1998 Gerald Combs
12
 *
13
 * SPDX-License-Identifier: GPL-2.0-or-later
14
 */
15
16
/*
17
 */
18
#include "config.h"
19
20
#include <epan/packet.h>
21
/* TCP desegmentation */
22
#include "packet-tcp.h"
23
/* Request Response tracking */
24
#include <epan/conversation.h>
25
#include <epan/prefs.h>
26
#include <epan/proto_data.h>
27
28
void proto_register_djiuav(void);
29
void proto_reg_handoff_djiuav(void);
30
31
static dissector_handle_t djiuav_handle;
32
33
/* Enable desegmentation of djiuav over TCP */
34
static bool djiuav_desegment = true;
35
36
/* Command/Response tracking */
37
typedef struct _djiuav_conv_info_t {
38
  wmem_map_t *pdus;
39
} djiuav_conv_info_t;
40
41
typedef struct _djiuav_transaction_t {
42
  uint16_t  seqno;
43
  uint8_t command;
44
  uint32_t  request_frame;
45
  uint32_t  reply_frame;
46
  nstime_t request_time;
47
} djiuav_transaction_t;
48
49
/* Finally: Protocol specific stuff */
50
51
/* protocol handles */
52
static int proto_djiuav;
53
54
/* ett handles */
55
static int ett_djiuav;
56
57
/* hf elements */
58
static int hf_djiuav_magic;
59
static int hf_djiuav_length;
60
static int hf_djiuav_flags;
61
static int hf_djiuav_seqno;
62
static int hf_djiuav_cmd;
63
static int hf_djiuav_checksum;
64
#if 0
65
static int hf_djiuav_cmd04_unknown;
66
static int hf_djiuav_resp04_unknown;
67
#endif
68
static int hf_djiuav_cmd20_unknown;
69
#if 0
70
static int hf_djiuav_resp20_unknown;
71
#endif
72
static int hf_djiuav_cmdunk;
73
static int hf_djiuav_respunk;
74
static int hf_djiuav_extradata;
75
/* hf request/response tracking */
76
static int hf_djiuav_response_in;
77
static int hf_djiuav_response_to;
78
static int hf_djiuav_response_time;
79
80
16
#define PORT_DJIUAV 2001 /* Not IANA registered */
81
82
static const value_string djiuav_pdu_type[] = {
83
  { 0x20, "Set Time" },
84
85
  { 0,  NULL }
86
};
87
88
static void
89
request_response_handling(tvbuff_t *tvb, packet_info *pinfo, proto_tree *djiuav_tree,
90
  uint32_t offset)
91
0
{
92
0
  conversation_t    *conversation;
93
0
  djiuav_conv_info_t  *djiuav_info;
94
0
  djiuav_transaction_t  *djiuav_trans;
95
96
0
  uint16_t      seq_no;
97
0
  bool    is_cmd;
98
0
  uint8_t     packet_type;
99
100
0
  is_cmd = (pinfo->match_uint == pinfo->destport);
101
0
  seq_no = tvb_get_letohs(tvb, offset + 4);
102
0
  packet_type = tvb_get_uint8(tvb, offset + 6);
103
104
0
  conversation = find_or_create_conversation(pinfo);
105
0
  djiuav_info = (djiuav_conv_info_t *)conversation_get_proto_data(conversation, proto_djiuav);
106
0
  if (!djiuav_info) {
107
0
    djiuav_info = wmem_new(wmem_file_scope(), djiuav_conv_info_t);
108
0
    djiuav_info->pdus=wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
109
110
0
    conversation_add_proto_data(conversation, proto_djiuav, djiuav_info);
111
0
  }
112
0
  if (!pinfo->fd->visited) {
113
0
    if (is_cmd) {
114
0
      djiuav_trans=wmem_new(wmem_file_scope(), djiuav_transaction_t);
115
0
      djiuav_trans->request_frame=pinfo->num;
116
0
      djiuav_trans->reply_frame=0;
117
0
      djiuav_trans->request_time=pinfo->abs_ts;
118
0
      djiuav_trans->seqno=seq_no;
119
0
      djiuav_trans->command=packet_type;
120
0
      wmem_map_insert(djiuav_info->pdus, GUINT_TO_POINTER((unsigned)seq_no), (void *)djiuav_trans);
121
0
    } else {
122
0
      djiuav_trans=(djiuav_transaction_t *)wmem_map_lookup(djiuav_info->pdus, GUINT_TO_POINTER((unsigned)seq_no));
123
0
      if (djiuav_trans) {
124
        /* Special case: djiuav seems to send 0x24 replies with seqno 0 and without a request */
125
0
        if (djiuav_trans->reply_frame == 0)
126
0
          djiuav_trans->reply_frame=pinfo->num;
127
0
      }
128
0
    }
129
    /* Remember the transaction for this frame, so that later passes
130
     * do not find a later transaction that reused the seqno. */
131
0
    if (djiuav_trans)
132
0
      p_add_proto_data(wmem_file_scope(), pinfo, proto_djiuav, seq_no, djiuav_trans);
133
0
  } else {
134
0
    djiuav_trans=(djiuav_transaction_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_djiuav, seq_no);
135
0
  }
136
137
  /* djiuav_trans may be 0 in case it's a reply without a matching request */
138
139
0
  if (djiuav_tree && djiuav_trans) {
140
0
    if (is_cmd) {
141
0
      if (djiuav_trans->reply_frame) {
142
0
        proto_item *it;
143
144
0
        it = proto_tree_add_uint(djiuav_tree, hf_djiuav_response_in,
145
0
            tvb, 0, 0, djiuav_trans->reply_frame);
146
0
        proto_item_set_generated(it);
147
0
      }
148
0
    } else {
149
0
      if (djiuav_trans->request_frame) {
150
0
        proto_item *it;
151
0
        nstime_t ns;
152
153
0
        it = proto_tree_add_uint(djiuav_tree, hf_djiuav_response_to,
154
0
            tvb, 0, 0, djiuav_trans->request_frame);
155
0
        proto_item_set_generated(it);
156
157
0
        nstime_delta(&ns, &pinfo->abs_ts, &djiuav_trans->request_time);
158
0
        it = proto_tree_add_time(djiuav_tree, hf_djiuav_response_time, tvb, 0, 0, &ns);
159
0
        proto_item_set_generated(it);
160
0
      }
161
0
    }
162
0
  }
163
0
}
164
165
static int
166
dissect_djiuav_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
167
0
{
168
0
  proto_item    *ti;
169
0
  proto_tree    *djiuav_tree = NULL;
170
0
  uint32_t      offset = 0;
171
0
  uint32_t      pdu_length;
172
0
  uint8_t     packet_type;
173
0
  bool    is_cmd;
174
175
0
  is_cmd = (pinfo->match_uint == pinfo->destport);
176
0
  packet_type = tvb_get_uint8(tvb, 6);
177
178
0
  col_set_str(pinfo->cinfo, COL_PROTOCOL, "DJIUAV");
179
0
  col_add_str(pinfo->cinfo, COL_INFO, is_cmd?"C: ":"R: ");
180
0
  col_append_str(pinfo->cinfo, COL_INFO, val_to_str(pinfo->pool, packet_type,
181
0
      djiuav_pdu_type, "Type 0x%02x"));
182
183
0
  ti = proto_tree_add_item(tree, proto_djiuav, tvb, offset, -1, ENC_NA);
184
0
  djiuav_tree = proto_item_add_subtree(ti, ett_djiuav);
185
186
0
  request_response_handling(tvb, pinfo, djiuav_tree, offset);
187
188
0
  if (tree) {
189
0
    proto_tree_add_item(djiuav_tree, hf_djiuav_magic, tvb, offset, 2,
190
0
      ENC_BIG_ENDIAN);
191
0
    offset += 2;
192
193
0
    pdu_length = tvb_get_uint8(tvb, offset);
194
0
    proto_tree_add_item(djiuav_tree, hf_djiuav_length, tvb, offset, 1,
195
0
      ENC_NA);
196
0
    offset += 1;
197
198
0
    proto_tree_add_item(djiuav_tree, hf_djiuav_flags, tvb, offset, 1,
199
0
      ENC_NA);
200
0
    offset += 1;
201
202
0
    proto_tree_add_item(djiuav_tree, hf_djiuav_seqno, tvb, offset, 2,
203
0
      ENC_LITTLE_ENDIAN);
204
0
    offset += 2;
205
206
0
    proto_tree_add_item(djiuav_tree, hf_djiuav_cmd, tvb, offset, 1,
207
0
      ENC_NA);
208
0
    offset += 1;
209
210
0
    if (is_cmd) { /* Command */
211
0
      switch (packet_type) {
212
0
      case 0x20: /* Set time */
213
/* FIXME: Properly decode this: year(lo) year(hi) month date hour minute second */
214
0
        proto_tree_add_item(djiuav_tree, hf_djiuav_cmd20_unknown, tvb, offset, 7,
215
0
          ENC_NA);
216
0
        offset += 7;
217
0
        break;
218
0
      default:
219
0
        proto_tree_add_item(djiuav_tree, hf_djiuav_cmdunk, tvb, offset, pdu_length - 8,
220
0
          ENC_NA);
221
0
        offset += (pdu_length - 8);
222
0
        break;
223
0
      }
224
0
    } else { /* Response */
225
0
      switch (packet_type) {
226
0
      default:
227
0
        proto_tree_add_item(djiuav_tree, hf_djiuav_respunk, tvb,
228
0
          offset, pdu_length - 8, ENC_NA);
229
0
        offset += (pdu_length - 8);
230
0
        break;
231
0
      }
232
0
    }
233
0
    if (offset < pdu_length - 1) { /* We guessed wrong about the cmd len */
234
0
      proto_tree_add_item(djiuav_tree, hf_djiuav_extradata, tvb, offset,
235
0
        pdu_length - 1 - offset, ENC_NA);
236
0
      offset += pdu_length - 1 - offset;
237
0
    }
238
/* FIXME: calculate XOR and validate transmitted value */
239
0
    proto_tree_add_checksum(djiuav_tree, tvb, offset, hf_djiuav_checksum, -1, NULL, pinfo, 0, ENC_BIG_ENDIAN, PROTO_CHECKSUM_NO_FLAGS);
240
0
    offset += 1;
241
242
0
  }
243
0
  return offset;
244
0
}
245
246
static bool
247
test_djiuav(tvbuff_t *tvb)
248
0
{
249
  /* Minimum of 8 bytes, beginning with magic bytes 0x55BB */
250
0
  if ( tvb_captured_length(tvb) < 8 /* Size of a command with empty data is at least 8 */
251
0
    || tvb_get_ntohs(tvb, 0) != 0x55BB
252
0
  ) {
253
0
    return false;
254
0
  }
255
0
  return true;
256
0
}
257
258
/* Get the length of the full pdu */
259
static unsigned
260
get_djiuav_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
261
0
{
262
0
  return tvb_get_uint8(tvb, offset + 2);
263
0
}
264
265
static int
266
dissect_djiuav_static(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
267
0
{
268
0
  if ( !test_djiuav(tvb) ) {
269
0
    return 0;
270
0
  }
271
0
  tcp_dissect_pdus(tvb, pinfo, tree, djiuav_desegment, 8,
272
0
    get_djiuav_pdu_len, dissect_djiuav_pdu, data);
273
274
0
  return tvb_captured_length(tvb);
275
0
}
276
277
void
278
proto_register_djiuav(void)
279
16
{
280
16
  static hf_register_info hf[] = {
281
282
  /* DJIUAV header */
283
16
    { &hf_djiuav_magic,
284
16
    { "Protocol Magic", "djiuav.magic", FT_UINT16, BASE_HEX, NULL,
285
16
      0x0, NULL, HFILL }},
286
287
16
    { &hf_djiuav_length,
288
16
    { "PDU Length", "djiuav.length", FT_UINT8, BASE_HEX, NULL,
289
16
      0x0, NULL, HFILL }},
290
291
16
    { &hf_djiuav_flags,
292
16
    { "Flags",  "djiuav.flags", FT_UINT8, BASE_HEX, NULL,
293
16
      0x0, NULL, HFILL }},
294
295
16
    { &hf_djiuav_seqno,
296
16
    { "Sequence No",  "djiuav.seqno", FT_UINT16, BASE_DEC, NULL,
297
16
      0x0, NULL, HFILL }},
298
299
16
    { &hf_djiuav_cmd,
300
16
    { "PDU Type", "djiuav.pdutype", FT_UINT8, BASE_HEX, VALS(djiuav_pdu_type),
301
16
      0x0, NULL, HFILL }},
302
303
16
    { &hf_djiuav_checksum,
304
16
    { "Checksum", "djiuav.checksum", FT_UINT8, BASE_HEX, NULL,
305
16
      0x0, NULL, HFILL }},
306
307
  /* 0x04 */
308
#if 0
309
    { &hf_djiuav_cmd04_unknown,
310
    { "C04 Unknown", "djiuav.cmd04.unknown", FT_UINT8, BASE_HEX, NULL,
311
        0x0, NULL, HFILL }},
312
313
    { &hf_djiuav_resp04_unknown,
314
    { "R04 Unknown", "djiuav.resp04.unknown", FT_UINT8, BASE_HEX, NULL,
315
        0x0, NULL, HFILL }},
316
#endif
317
  /* Set time */
318
16
    { &hf_djiuav_cmd20_unknown,
319
16
    { "Time in BCD", "djiuav.cmd04.bcdtime", FT_BYTES, BASE_NONE, NULL,
320
16
      0x0, NULL, HFILL }},
321
#if 0
322
    { &hf_djiuav_resp20_unknown,
323
    { "R20 Unknown", "djiuav.resp04.unknown", FT_UINT8, BASE_HEX, NULL,
324
      0x0, NULL, HFILL }},
325
#endif
326
  /* CMD Unknown */
327
16
    { &hf_djiuav_cmdunk,
328
16
    { "C Unknown", "djiuav.cmd.unknown", FT_BYTES, BASE_NONE, NULL,
329
16
      0x0, NULL, HFILL }},
330
331
  /* RESP Unknown */
332
16
    { &hf_djiuav_respunk,
333
16
    { "R Unknown", "djiuav.resp.unknown", FT_BYTES, BASE_NONE, NULL,
334
16
      0x0, NULL, HFILL }},
335
336
  /* Extra Data (unexpected) */
337
16
    { &hf_djiuav_extradata,
338
16
    { "Unexpected", "djiuav.unexpected", FT_BYTES, BASE_NONE, NULL,
339
16
      0x0, NULL, HFILL }},
340
341
  /* Request - Response tracking */
342
16
    { &hf_djiuav_response_in,
343
16
    { "Response In", "djiuav.response_in", FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE),
344
16
      0x0, "Matching response in frame", HFILL }},
345
346
16
    { &hf_djiuav_response_to,
347
16
    { "Request In", "djiuav.response_to",
348
16
      FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST),
349
16
      0x0, "Matching command in frame", HFILL }},
350
351
16
    { &hf_djiuav_response_time,
352
16
    { "Response Time", "djiuav.response_time",
353
16
      FT_RELATIVE_TIME, BASE_NONE, NULL,
354
16
      0x0, "Time between Command and matching Response", HFILL }},
355
16
  };
356
16
  static int *ett[] = {
357
16
    &ett_djiuav,
358
16
  };
359
16
  module_t *djiuav_module;
360
361
16
  proto_djiuav = proto_register_protocol("DJI UAV Drone Control Protocol", "DJIUAV", "djiuav");
362
16
  proto_register_field_array(proto_djiuav, hf, array_length(hf));
363
16
  proto_register_subtree_array(ett, array_length(ett));
364
365
  /* Preferences */
366
16
  djiuav_module = prefs_register_protocol(proto_djiuav, NULL);
367
368
16
  prefs_register_bool_preference(djiuav_module, "desegment",
369
16
    "Reassemble DJIUAV messages",
370
16
    "Whether DJIUAV should reassemble messages spanning multiple"
371
16
      " TCP segments (required to get useful results)",
372
16
    &djiuav_desegment);
373
374
16
  djiuav_handle = register_dissector("djiuav", dissect_djiuav_static, proto_djiuav);
375
16
}
376
377
void
378
proto_reg_handoff_djiuav(void)
379
16
{
380
16
  dissector_add_uint_with_preference("tcp.port", PORT_DJIUAV, djiuav_handle);
381
16
}
382
383
/*
384
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
385
 *
386
 * Local variables:
387
 * c-basic-offset: 8
388
 * tab-width: 8
389
 * indent-tabs-mode: t
390
 * End:
391
 *
392
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
393
 * :indentSize=8:tabSize=8:noTabs=false:
394
 */