Coverage Report

Created: 2025-12-27 06:52

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