3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
11 #include <epan/conversation.h>
12 #include <epan/conversation_table.h>
13 #include <epan/maxmind_db.h>
14 #include <epan/addr_resolv.h>
16 #include <wsutil/utf8_entities.h>
17 #include <wsutil/nstime.h>
18 #include <wsutil/str_util.h>
20 #include <ui/qt/utils/qt_ui_utils.h>
21 #include <ui/qt/utils/variant_pointer.h>
22 #include <ui/qt/main_application.h>
23 #include <ui/qt/models/atap_data_model.h>
24 #include <ui/qt/models/timeline_delegate.h>
31 static QString
formatString(qlonglong value
)
33 return QLocale().formattedDataSize(value
, 0, QLocale::DataSizeSIFormat
);
36 ATapDataModel::ATapDataModel(dataModelType type
, int protoId
, QString filter
, QObject
*parent
):
37 QAbstractListModel(parent
)
39 hash_
.conv_array
= nullptr;
40 hash_
.hashtable
= nullptr;
41 hash_
.user_data
= this;
44 _resolveNames
= false;
45 _absoluteTime
= false;
57 _tapFlags
= TL_IGNORE_DISPLAY_FILTER
+TL_IP_AGGREGATION_ORI
;
59 QString
_tap(proto_get_protocol_filter_name(protoId
));
62 ATapDataModel::~ATapDataModel()
64 /* Only remove the tap if we come from a enabled model */
66 remove_tap_listener(hash());
68 if (_type
== ATapDataModel::DATAMODEL_ENDPOINT
)
69 reset_endpoint_table_data(&hash_
);
70 else if (_type
== ATapDataModel::DATAMODEL_CONVERSATION
)
71 reset_conversation_table_data(&hash_
);
74 int ATapDataModel::protoId() const
79 QString
ATapDataModel::tap() const
81 return proto_get_protocol_filter_name(_protoId
);
85 bool ATapDataModel::hasGeoIPData()
87 bool coordsFound
= false;
89 int count
= rowCount();
90 while (!coordsFound
&& row
< count
)
92 QModelIndex idx
= index(row
, 0);
93 if (!data(idx
, ATapDataModel::ROW_IS_FILTERED
).toBool()) {
94 if (_type
== ATapDataModel::DATAMODEL_ENDPOINT
)
95 coordsFound
= qobject_cast
<EndpointDataModel
*>(this)->data(idx
, ATapDataModel::GEODATA_AVAILABLE
).toBool();
96 else if (_type
== ATapDataModel::DATAMODEL_CONVERSATION
)
97 coordsFound
= qobject_cast
<ConversationDataModel
*>(this)->data(idx
, ATapDataModel::GEODATA_AVAILABLE
).toBool();
106 bool ATapDataModel::enableTap()
108 /* We can't reenable a tap, so just return */
114 /* The errorString is ignored. If this is not working, there is nothing really the user may do about
115 * it, so the error is only interesting to the developer.*/
116 GString
* errorString
= register_tap_listener(tap().toUtf8().constData(), hash(), _filter
.toUtf8().constData(),
117 _tapFlags
, &ATapDataModel::tapReset
, conversationPacketHandler(), &ATapDataModel::tapDraw
, nullptr);
118 if (errorString
&& errorString
->len
> 0) {
119 g_string_free(errorString
, TRUE
);
121 emit
tapListenerChanged(false);
126 g_string_free(errorString
, TRUE
);
128 emit
tapListenerChanged(true);
133 void ATapDataModel::disableTap()
135 /* Only remove the tap if we come from a enabled model */
137 remove_tap_listener(hash());
139 emit
tapListenerChanged(false);
142 void ATapDataModel::updateFlags(unsigned flag
)
146 _tapFlags
|= TL_IP_AGGREGATION_ORI
;
149 _tapFlags
&= ~(TL_IP_AGGREGATION_NULL
|
150 TL_IP_AGGREGATION_ORI
|
151 TL_IP_AGGREGATION_RESERVED
);
153 set_tap_flags(&hash_
, _tapFlags
);
156 int ATapDataModel::rowCount(const QModelIndex
&parent
) const
158 return (storage_
&& !parent
.isValid()) ? (int) storage_
->len
: 0;
161 void ATapDataModel::tapReset(void *tapdata
) {
165 conv_hash_t
*hash
= (conv_hash_t
*)tapdata
;
166 ATapDataModel
* dataModel
= qobject_cast
<ATapDataModel
*>((ATapDataModel
*)hash
->user_data
);
168 dataModel
->resetData();
171 void ATapDataModel::tapDraw(void *tapdata
)
176 conv_hash_t
*hash
= (conv_hash_t
*)tapdata
;
177 ATapDataModel
* dataModel
= qobject_cast
<ATapDataModel
*>((ATapDataModel
*)hash
->user_data
);
179 dataModel
->updateData(hash
->conv_array
);
182 conv_hash_t
* ATapDataModel::hash()
187 register_ct_t
* ATapDataModel::registerTable() const
190 return get_conversation_by_proto_id(_protoId
);
195 tap_packet_cb
ATapDataModel::conversationPacketHandler()
197 register_ct_t
* table
= registerTable();
199 if (_type
== ATapDataModel::DATAMODEL_ENDPOINT
)
200 return get_endpoint_packet_func(table
);
201 else if (_type
== ATapDataModel::DATAMODEL_CONVERSATION
)
202 return get_conversation_packet_func(table
);
208 void ATapDataModel::resetData()
215 if (_type
== ATapDataModel::DATAMODEL_ENDPOINT
)
216 reset_endpoint_table_data(&hash_
);
217 else if (_type
== ATapDataModel::DATAMODEL_CONVERSATION
)
218 reset_conversation_table_data(&hash_
);
220 _minRelStartTime
= 0;
226 void ATapDataModel::updateData(GArray
* newData
)
235 if (_type
== ATapDataModel::DATAMODEL_CONVERSATION
)
236 ((ConversationDataModel
*)(this))->doDataUpdate();
239 bool ATapDataModel::resolveNames() const
241 return _resolveNames
;
244 void ATapDataModel::setResolveNames(bool resolve
)
246 if (_resolveNames
== resolve
)
250 _resolveNames
= resolve
;
254 bool ATapDataModel::allowsNameResolution() const
259 QStringList mac_protos
= QStringList() << "bluetooth" << "eth" << "fddi"
260 << "sll" << "tr" << "wlan";
261 QStringList net_protos
= QStringList() << "dccp" << "ip" << "ipv6"
262 << "jxta" << "mptcp" << "ncp"
263 << "rsvp" << "sctp" << "sll"
265 QStringList transport_protos
= QStringList() << "dccp" << "mptcp" << "sctp"
268 QString table_proto
= proto_get_protocol_filter_name(_protoId
);
270 if (mac_protos
.contains(table_proto
) && gbl_resolv_flags
.mac_name
)
272 if (net_protos
.contains(table_proto
) && gbl_resolv_flags
.network_name
)
274 if (transport_protos
.contains(table_proto
) && gbl_resolv_flags
.transport_name
)
280 void ATapDataModel::useAbsoluteTime(bool absolute
)
282 if (absolute
== _absoluteTime
)
286 _absoluteTime
= absolute
;
290 void ATapDataModel::useNanosecondTimestamps(bool nanoseconds
)
292 if (_nanoseconds
== nanoseconds
)
296 _nanoseconds
= nanoseconds
;
300 void ATapDataModel::setFilter(QString filter
)
306 GString
* errorString
= set_tap_dfilter(&hash_
, !_filter
.isEmpty() ? _filter
.toUtf8().constData() : nullptr);
307 if (errorString
&& errorString
->len
> 0) {
308 /* If this fails, chances are that the main system failed as well. Silently exiting as the
309 * user cannot react to it */
314 g_string_free(errorString
, TRUE
);
317 QString
ATapDataModel::filter() const
322 ATapDataModel::dataModelType
ATapDataModel::modelType() const
327 bool ATapDataModel::portsAreHidden() const
329 return (get_conversation_hide_ports(registerTable()));
332 bool ATapDataModel::showTotalColumn() const
334 /* Implemented to ensure future changes may be done more easily */
335 return _filter
.length() > 0;
338 EndpointDataModel::EndpointDataModel(int protoId
, QString filter
, QObject
*parent
) :
339 ATapDataModel(ATapDataModel::DATAMODEL_ENDPOINT
, protoId
, filter
, parent
)
342 int EndpointDataModel::columnCount(const QModelIndex
&) const
344 #ifdef HAVE_MAXMINDDB
345 int proto_ipv4
= proto_get_id_by_filter_name("ip");
346 int proto_ipv6
= proto_get_id_by_filter_name("ipv6");
347 if (protoId() == proto_ipv4
|| protoId() == proto_ipv6
) {
348 return ENDP_NUM_GEO_COLUMNS
;
351 return ENDP_NUM_COLUMNS
;
354 QVariant
EndpointDataModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
356 if (orientation
== Qt::Vertical
)
359 if (role
== Qt::DisplayRole
) {
361 case ENDP_COLUMN_ADDR
:
362 return tr("Address"); break;
363 case ENDP_COLUMN_PORT
:
364 return tr("Port"); break;
365 case ENDP_COLUMN_PACKETS
:
366 return tr("Packets"); break;
367 case ENDP_COLUMN_BYTES
:
368 return tr("Bytes"); break;
369 case ENDP_COLUMN_PACKETS_TOTAL
:
370 return tr("Total Packets"); break;
371 case ENDP_COLUMN_BYTES_TOTAL
:
372 return tr("Percent Filtered"); break;
373 case ENDP_COLUMN_PKT_AB
:
374 return tr("Tx Packets"); break;
375 case ENDP_COLUMN_BYTES_AB
:
376 return tr("Tx Bytes"); break;
377 case ENDP_COLUMN_PKT_BA
:
378 return tr("Rx Packets"); break;
379 case ENDP_COLUMN_BYTES_BA
:
380 return tr("Rx Bytes"); break;
381 case ENDP_COLUMN_GEO_COUNTRY
:
382 return tr("Country"); break;
383 case ENDP_COLUMN_GEO_CITY
:
384 return tr("City"); break;
385 case ENDP_COLUMN_GEO_LATITUDE
:
386 return tr("Latitude"); break;
387 case ENDP_COLUMN_GEO_LONGITUDE
:
388 return tr("Longitude"); break;
389 case ENDP_COLUMN_GEO_AS_NUM
:
390 return tr("AS Number"); break;
391 case ENDP_COLUMN_GEO_AS_ORG
:
392 return tr("AS Organization"); break;
394 } else if (role
== Qt::TextAlignmentRole
) {
396 case ENDP_COLUMN_ADDR
:
397 case ENDP_COLUMN_GEO_COUNTRY
:
398 case ENDP_COLUMN_GEO_CITY
:
399 case ENDP_COLUMN_GEO_AS_ORG
:
400 return Qt::AlignLeft
;
404 return Qt::AlignRight
;
410 QVariant
EndpointDataModel::data(const QModelIndex
&idx
, int role
) const
415 // Column text cooked representation.
416 endpoint_item_t
*item
= &g_array_index(storage_
, endpoint_item_t
, idx
.row());
417 const mmdb_lookup_t
*mmdb_lookup
= nullptr;
418 #ifdef HAVE_MAXMINDDB
419 char addr
[WS_INET6_ADDRSTRLEN
];
420 if (item
->myaddress
.type
== AT_IPv4
) {
421 const ws_in4_addr
* ip4
= (const ws_in4_addr
*) item
->myaddress
.data
;
422 mmdb_lookup
= maxmind_db_lookup_ipv4(ip4
);
423 ws_inet_ntop4(ip4
, addr
, sizeof(addr
));
424 } else if (item
->myaddress
.type
== AT_IPv6
) {
425 const ws_in6_addr
* ip6
= (const ws_in6_addr
*) item
->myaddress
.data
;
426 mmdb_lookup
= maxmind_db_lookup_ipv6(ip6
);
427 ws_inet_ntop6(ip6
, addr
, sizeof(addr
));
431 QString
ipAddress(addr
);
434 if (role
== Qt::DisplayRole
|| role
== ATapDataModel::UNFORMATTED_DISPLAYDATA
) {
435 switch (idx
.column()) {
436 case ENDP_COLUMN_ADDR
: {
437 char* addr_str
= get_conversation_address(NULL
, &item
->myaddress
, _resolveNames
);
438 QString
q_addr_str(addr_str
);
439 wmem_free(NULL
, addr_str
);
442 case ENDP_COLUMN_PORT
:
444 char* port_str
= get_endpoint_port(NULL
, item
, _resolveNames
);
445 QString
q_port_str(port_str
);
446 wmem_free(NULL
, port_str
);
449 return quint32(item
->port
);
451 case ENDP_COLUMN_PACKETS
:
453 qlonglong packets
= (qlonglong
)(item
->tx_frames
+ item
->rx_frames
);
454 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg(packets
) : (QVariant
)packets
;
456 case ENDP_COLUMN_BYTES
:
457 return role
== Qt::DisplayRole
? formatString((qlonglong
)(item
->tx_bytes
+ item
->rx_bytes
)) :
458 QVariant((qlonglong
)(item
->tx_bytes
+ item
->rx_bytes
));
459 case ENDP_COLUMN_PACKETS_TOTAL
:
461 qlonglong packets
= 0;
462 if (showTotalColumn())
463 packets
= item
->tx_frames_total
+ item
->rx_frames_total
;
464 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg(packets
) : (QVariant
)packets
;
466 case ENDP_COLUMN_BYTES_TOTAL
:
469 if (showTotalColumn()) {
470 qlonglong totalPackets
= (qlonglong
)(item
->tx_frames_total
+ item
->rx_frames_total
);
471 qlonglong packets
= (qlonglong
)(item
->tx_frames
+ item
->rx_frames
);
472 percent
= totalPackets
== 0 ? 0 : (double) packets
* 100 / (double) totalPackets
;
474 QString rounded
= QString::number(percent
, 'f', 2);
475 /* Qt guarantees that this roundtrip conversion compares equally,
476 * so filtering with equality will work as expected.
477 * Perhaps the UNFORMATTED_DISPLAYDATA role shoud be split
478 * into one used for raw data export, and one used for comparisons.
480 return role
== Qt::DisplayRole
? rounded
+ "%" : QVariant(rounded
.toDouble());
482 case ENDP_COLUMN_PKT_AB
:
483 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg((qlonglong
)item
->tx_frames
) : QVariant((qlonglong
) item
->tx_frames
);
484 case ENDP_COLUMN_BYTES_AB
:
485 return role
== Qt::DisplayRole
? formatString((qlonglong
)item
->tx_bytes
) : QVariant((qlonglong
)item
->tx_bytes
);
486 case ENDP_COLUMN_PKT_BA
:
487 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg((qlonglong
)item
->rx_frames
) : QVariant((qlonglong
) item
->rx_frames
);
488 case ENDP_COLUMN_BYTES_BA
:
489 return role
== Qt::DisplayRole
? formatString((qlonglong
)item
->rx_bytes
) : QVariant((qlonglong
)item
->rx_bytes
);
490 case ENDP_COLUMN_GEO_COUNTRY
:
491 if (mmdb_lookup
&& mmdb_lookup
->found
&& mmdb_lookup
->country
) {
492 return QVariant(mmdb_lookup
->country
);
495 case ENDP_COLUMN_GEO_CITY
:
496 if (mmdb_lookup
&& mmdb_lookup
->found
&& mmdb_lookup
->city
) {
497 return QVariant(mmdb_lookup
->city
);
500 case ENDP_COLUMN_GEO_LATITUDE
:
501 if (mmdb_lookup
&& mmdb_lookup
->found
&& mmdb_lookup
->latitude
>= -90.0 && mmdb_lookup
->latitude
<= 90.0) {
502 return role
== Qt::DisplayRole
? QStringLiteral("%L1%2").arg(mmdb_lookup
->latitude
).arg(UTF8_DEGREE_SIGN
) : QVariant(mmdb_lookup
->latitude
);
505 case ENDP_COLUMN_GEO_LONGITUDE
:
506 if (mmdb_lookup
&& mmdb_lookup
->found
&& mmdb_lookup
->longitude
>= -180.0 && mmdb_lookup
->longitude
<= 180.0) {
507 return role
== Qt::DisplayRole
? QStringLiteral("%L1%2").arg(mmdb_lookup
->longitude
).arg(UTF8_DEGREE_SIGN
) : QVariant(mmdb_lookup
->longitude
);
510 case ENDP_COLUMN_GEO_AS_NUM
:
511 if (mmdb_lookup
&& mmdb_lookup
->found
&& mmdb_lookup
->as_number
) {
512 return QVariant(mmdb_lookup
->as_number
);
515 case ENDP_COLUMN_GEO_AS_ORG
:
516 if (mmdb_lookup
&& mmdb_lookup
->found
&& mmdb_lookup
->as_org
) {
517 return QVariant(mmdb_lookup
->as_org
);
523 } else if (role
== Qt::TextAlignmentRole
) {
524 switch (idx
.column()) {
525 case ENDP_COLUMN_ADDR
:
526 case ENDP_COLUMN_GEO_COUNTRY
:
527 case ENDP_COLUMN_GEO_CITY
:
528 case ENDP_COLUMN_GEO_AS_ORG
:
529 return Qt::AlignLeft
;
533 return Qt::AlignRight
;
534 } else if (role
== ATapDataModel::DISPLAY_FILTER
) {
535 return QString(get_endpoint_filter(item
));
536 } else if (role
== ATapDataModel::ROW_IS_FILTERED
) {
537 return (bool)item
->filtered
&& showTotalColumn();
539 #ifdef HAVE_MAXMINDDB
540 else if (role
== ATapDataModel::GEODATA_AVAILABLE
) {
541 return (bool)(mmdb_lookup
&& maxmind_db_has_coords(mmdb_lookup
));
542 } else if (role
== ATapDataModel::GEODATA_LOOKUPTABLE
) {
543 return VariantPointer
<const mmdb_lookup_t
>::asQVariant(mmdb_lookup
);
544 } else if (role
== ATapDataModel::GEODATA_ADDRESS
) {
548 else if (role
== ATapDataModel::PROTO_ID
) {
550 } else if (role
== ATapDataModel::DATA_ADDRESS_TYPE
) {
551 if (idx
.column() == EndpointDataModel::ENDP_COLUMN_ADDR
)
552 return (int)item
->myaddress
.type
;
553 return (int) AT_NONE
;
554 } else if (role
== ATapDataModel::DATA_IPV4_INTEGER
|| role
== ATapDataModel::DATA_IPV6_LIST
) {
555 if (idx
.column() == EndpointDataModel::ENDP_COLUMN_ADDR
) {
556 if (role
== ATapDataModel::DATA_IPV4_INTEGER
&& item
->myaddress
.type
== AT_IPv4
) {
557 const ws_in4_addr
* ip4
= (const ws_in4_addr
*) item
->myaddress
.data
;
558 return (quint32
) GUINT32_FROM_BE(*ip4
);
560 else if (role
== ATapDataModel::DATA_IPV6_LIST
&& item
->myaddress
.type
== AT_IPv6
) {
561 const ws_in6_addr
* ip6
= (const ws_in6_addr
*) item
->myaddress
.data
;
562 QList
<quint8
> result
;
564 std::copy(ip6
->bytes
+ 0, ip6
->bytes
+ 16, std::back_inserter(result
));
565 return QVariant::fromValue(result
);
573 ConversationDataModel::ConversationDataModel(int protoId
, QString filter
, QObject
*parent
) :
574 ATapDataModel(ATapDataModel::DATAMODEL_CONVERSATION
, protoId
, filter
, parent
)
577 void ConversationDataModel::doDataUpdate()
579 _minRelStartTime
= 0;
582 for (int row
= 0; row
< rowCount(); row
++) {
583 conv_item_t
*conv_item
= &g_array_index(storage_
, conv_item_t
, row
);
586 _minRelStartTime
= nstime_to_sec(&(conv_item
->start_time
));
587 _maxRelStopTime
= nstime_to_sec(&(conv_item
->stop_time
));
589 double item_rel_start
= nstime_to_sec(&(conv_item
->start_time
));
590 if (item_rel_start
< _minRelStartTime
) {
591 _minRelStartTime
= item_rel_start
;
594 double item_rel_stop
= nstime_to_sec(&(conv_item
->stop_time
));
595 if (item_rel_stop
> _maxRelStopTime
) {
596 _maxRelStopTime
= item_rel_stop
;
602 int ConversationDataModel::columnCount(const QModelIndex
&) const
605 return CONV_TCP_EXT_NUM_COLUMNS
;
607 return CONV_NUM_COLUMNS
;
610 QVariant
ConversationDataModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
612 if (orientation
== Qt::Vertical
)
615 if (role
== Qt::DisplayRole
) {
617 case CONV_COLUMN_SRC_ADDR
:
618 return tr("Address A"); break;
619 case CONV_COLUMN_SRC_PORT
:
620 return tr("Port A"); break;
621 case CONV_COLUMN_DST_ADDR
:
622 return tr("Address B"); break;
623 case CONV_COLUMN_DST_PORT
:
624 return tr("Port B"); break;
625 case CONV_COLUMN_PACKETS
:
626 return tr("Packets"); break;
627 case CONV_COLUMN_BYTES
:
628 return tr("Bytes"); break;
629 case CONV_COLUMN_CONV_ID
:
630 return tr("Stream ID"); break;
631 case CONV_COLUMN_PACKETS_TOTAL
:
632 return tr("Total Packets"); break;
633 case CONV_COLUMN_BYTES_TOTAL
:
634 return tr("Percent Filtered"); break;
635 case CONV_COLUMN_PKT_AB
:
636 return tr("Packets A " UTF8_RIGHTWARDS_ARROW
" B"); break;
637 case CONV_COLUMN_BYTES_AB
:
638 return tr("Bytes A " UTF8_RIGHTWARDS_ARROW
" B"); break;
639 case CONV_COLUMN_PKT_BA
:
640 return tr("Packets B " UTF8_RIGHTWARDS_ARROW
" A"); break;
641 case CONV_COLUMN_BYTES_BA
:
642 return tr("Bytes B " UTF8_RIGHTWARDS_ARROW
" A"); break;
643 case CONV_COLUMN_START
:
644 return _absoluteTime
? tr("Abs Start") : tr("Rel Start"); break;
645 case CONV_COLUMN_DURATION
:
646 return tr("Duration"); break;
647 case CONV_COLUMN_BPS_AB
:
648 return tr("Bits/s A " UTF8_RIGHTWARDS_ARROW
" B"); break;
649 case CONV_COLUMN_BPS_BA
:
650 return tr("Bits/s B " UTF8_RIGHTWARDS_ARROW
" A"); break;
652 /* Extended conversations columns, e.g. TCP */
655 case CONV_TCP_EXT_COLUMN_A
:
656 return tr("Flows"); break;
658 ws_assert_not_reached(); break;
661 } else if (role
== Qt::TextAlignmentRole
) {
662 if (section
== CONV_COLUMN_SRC_ADDR
|| section
== CONV_COLUMN_DST_ADDR
)
663 return Qt::AlignLeft
;
664 return Qt::AlignRight
;
670 static const double min_bw_calc_duration_
= 5 / 1000.0; // seconds
672 QVariant
ConversationDataModel::data(const QModelIndex
&idx
, int role
) const
677 // Column text cooked representation.
678 conv_item_t
*conv_item
= (conv_item_t
*)&g_array_index(storage_
, conv_item_t
,idx
.row());
680 double duration
= nstime_to_sec(&conv_item
->stop_time
) - nstime_to_sec(&conv_item
->start_time
);
681 double bps_ab
= 0, bps_ba
= 0;
682 bool bpsCalculated
= false;
683 if (duration
> min_bw_calc_duration_
) {
684 bps_ab
= conv_item
->tx_bytes
* 8 / duration
;
685 bps_ba
= conv_item
->rx_bytes
* 8 / duration
;
686 bpsCalculated
= true;
689 if (role
== Qt::DisplayRole
|| role
== ATapDataModel::UNFORMATTED_DISPLAYDATA
) {
690 switch(idx
.column()) {
691 case CONV_COLUMN_SRC_ADDR
:
693 char* addr_str
= get_conversation_address(NULL
, &conv_item
->src_address
, _resolveNames
);
694 QString
q_addr_str(addr_str
);
695 wmem_free(NULL
, addr_str
);
698 case CONV_COLUMN_SRC_PORT
:
700 char* port_str
= get_conversation_port(NULL
, conv_item
->src_port
, conv_item
->ctype
, _resolveNames
);
701 QString
q_port_str(port_str
);
702 wmem_free(NULL
, port_str
);
705 return quint32(conv_item
->src_port
);
707 case CONV_COLUMN_DST_ADDR
:
709 char* addr_str
= get_conversation_address(NULL
, &conv_item
->dst_address
, _resolveNames
);
710 QString
q_addr_str(addr_str
);
711 wmem_free(NULL
, addr_str
);
714 case CONV_COLUMN_DST_PORT
:
716 char* port_str
= get_conversation_port(NULL
, conv_item
->dst_port
, conv_item
->ctype
, _resolveNames
);
717 QString
q_port_str(port_str
);
718 wmem_free(NULL
, port_str
);
721 return quint32(conv_item
->dst_port
);
723 case CONV_COLUMN_PACKETS
:
725 qlonglong packets
= conv_item
->tx_frames
+ conv_item
->rx_frames
;
726 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg(packets
) : (QVariant
)packets
;
728 case CONV_COLUMN_BYTES
:
729 return role
== Qt::DisplayRole
? formatString((qlonglong
)conv_item
->tx_bytes
+ conv_item
->rx_bytes
) :
730 QVariant((qlonglong
)conv_item
->tx_bytes
+ conv_item
->rx_bytes
);
731 case CONV_COLUMN_CONV_ID
:
732 if(conv_item
->conv_id
!=CONV_ID_UNSET
) {
733 return (int) conv_item
->conv_id
;
736 case CONV_COLUMN_PACKETS_TOTAL
:
738 qlonglong packets
= 0;
739 if (showTotalColumn())
740 packets
= conv_item
->tx_frames_total
+ conv_item
->rx_frames_total
;
742 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg(packets
) : (QVariant
)packets
;
744 case CONV_COLUMN_BYTES_TOTAL
:
747 if (showTotalColumn()) {
748 qlonglong totalPackets
= (qlonglong
)(conv_item
->tx_frames_total
+ conv_item
->rx_frames_total
);
749 qlonglong packets
= (qlonglong
)(conv_item
->tx_frames
+ conv_item
->rx_frames
);
750 percent
= totalPackets
== 0 ? 0 : (double) packets
* 100 / (double) totalPackets
;
752 QString rounded
= QString::number(percent
, 'f', 2);
753 /* Qt guarantees that this roundtrip conversion compares equally,
754 * so filtering with equality will work as expected.
755 * XXX: Perhaps the UNFORMATTED_DISPLAYDATA role shoud be split
756 * into one used for raw data export and comparisons with each
757 * other, and another for comparing with filters?
759 return role
== Qt::DisplayRole
? rounded
+ "%" : QVariant(rounded
.toDouble());
761 case CONV_COLUMN_PKT_AB
:
763 qlonglong packets
= conv_item
->tx_frames
;
764 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg(packets
) : (QVariant
)packets
;
766 case CONV_COLUMN_BYTES_AB
:
767 return role
== Qt::DisplayRole
? formatString((qlonglong
)conv_item
->tx_bytes
) : QVariant((qlonglong
)conv_item
->tx_bytes
);
768 case CONV_COLUMN_PKT_BA
:
770 qlonglong packets
= conv_item
->rx_frames
;
771 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg(packets
) : (QVariant
)packets
;
773 case CONV_COLUMN_BYTES_BA
:
774 return role
== Qt::DisplayRole
? formatString((qlonglong
)conv_item
->rx_bytes
) : QVariant((qlonglong
)conv_item
->rx_bytes
);
775 case CONV_COLUMN_START
:
777 int width
= _nanoseconds
? 9 : 6;
780 nstime_t
*abs_time
= &conv_item
->start_abs_time
;
781 /* XXX: QDateTime only supports millisecond resolution,
782 * and we have microseconds or nanoseconds.
783 * Should we use something else, particularly for exporting
784 * raw data? GDateTime handles microseconds.
786 QDateTime abs_dt
= QDateTime::fromMSecsSinceEpoch(nstime_to_msec(abs_time
));
787 if (role
== Qt::DisplayRole
) {
788 if (_maxRelStopTime
>= 24*60*60) {
789 return abs_dt
.toString(Qt::ISODateWithMs
);
791 return abs_dt
.time().toString(Qt::ISODateWithMs
);
794 return QVariant(abs_dt
);
797 return role
== Qt::DisplayRole
?
798 QString::number(nstime_to_sec(&conv_item
->start_time
), 'f', width
) :
799 (QVariant
)((double) nstime_to_sec(&conv_item
->start_time
));
802 case CONV_COLUMN_DURATION
:
804 int width
= _nanoseconds
? 6 : 4;
805 return role
== Qt::DisplayRole
? QString::number(duration
, 'f', width
) : (QVariant
)duration
;
807 case CONV_COLUMN_BPS_AB
:
808 return bpsCalculated
? (role
== Qt::DisplayRole
? gchar_free_to_qstring(format_size((int64_t)bps_ab
, FORMAT_SIZE_UNIT_BITS_S
, FORMAT_SIZE_PREFIX_SI
)) : QVariant((qlonglong
)bps_ab
)): QVariant();
809 case CONV_COLUMN_BPS_BA
:
810 return bpsCalculated
? (role
== Qt::DisplayRole
? gchar_free_to_qstring(format_size((int64_t)bps_ba
, FORMAT_SIZE_UNIT_BITS_S
, FORMAT_SIZE_PREFIX_SI
)) : QVariant((qlonglong
)bps_ba
)): QVariant();
812 /* Extended conversations columns, e.g. TCP */
814 switch(idx
.column()) {
815 case CONV_TCP_EXT_COLUMN_A
:
817 qlonglong flows
= (qlonglong
)conv_item
->ext_tcp
.flows
;
818 return role
== Qt::DisplayRole
? QStringLiteral("%L1").arg(flows
) : (QVariant
)flows
; break;
821 ws_assert_not_reached(); break;
824 } else if (role
== Qt::ToolTipRole
) {
825 if (idx
.column() == CONV_COLUMN_START
|| idx
.column() == CONV_COLUMN_DURATION
)
826 return QObject::tr("Bars show the relative timeline for each conversation.");
827 } else if (role
== Qt::TextAlignmentRole
) {
828 if (idx
.column() == CONV_COLUMN_SRC_ADDR
|| idx
.column() == CONV_COLUMN_DST_ADDR
)
829 return Qt::AlignLeft
;
830 return Qt::AlignRight
;
831 } else if (role
== ATapDataModel::TIMELINE_DATA
) {
832 struct timeline_span span_data
;
833 span_data
.minRelTime
= _minRelStartTime
;
834 span_data
.maxRelTime
= _maxRelStopTime
;
835 span_data
.startTime
= nstime_to_sec(&conv_item
->start_time
);
836 span_data
.stopTime
= nstime_to_sec(&conv_item
->stop_time
);
837 span_data
.colStart
= CONV_COLUMN_START
;
838 span_data
.colDuration
= CONV_COLUMN_DURATION
;
840 if ((_maxRelStopTime
- _minRelStartTime
) > 0) {
841 return QVariant::fromValue(span_data
);
843 } else if (role
== ATapDataModel::ENDPOINT_DATATYPE
) {
844 return (int)(conv_item
->ctype
);
845 } else if (role
== ATapDataModel::PROTO_ID
) {
847 } else if (role
== ATapDataModel::CONVERSATION_ID
) {
848 return (int)(conv_item
->conv_id
);
849 } else if (role
== ATapDataModel::ROW_IS_FILTERED
) {
850 return (bool)conv_item
->filtered
&& showTotalColumn();
851 } else if (role
== ATapDataModel::DATA_ADDRESS_TYPE
) {
852 if (idx
.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR
|| idx
.column() == ConversationDataModel::CONV_COLUMN_DST_ADDR
) {
853 address tst_address
= idx
.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR
? conv_item
->src_address
: conv_item
->dst_address
;
854 return (int)tst_address
.type
;
856 return (int) AT_NONE
;
857 } else if (role
== ATapDataModel::DATA_IPV4_INTEGER
|| role
== ATapDataModel::DATA_IPV6_LIST
) {
858 if (idx
.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR
|| idx
.column() == ConversationDataModel::CONV_COLUMN_DST_ADDR
) {
859 address tst_address
= idx
.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR
? conv_item
->src_address
: conv_item
->dst_address
;
860 if (role
== ATapDataModel::DATA_IPV4_INTEGER
&& tst_address
.type
== AT_IPv4
) {
861 const ws_in4_addr
* ip4
= (const ws_in4_addr
*) tst_address
.data
;
862 return (quint32
) GUINT32_FROM_BE(*ip4
);
864 else if (role
== ATapDataModel::DATA_IPV6_LIST
&& tst_address
.type
== AT_IPv6
) {
865 const ws_in6_addr
* ip6
= (const ws_in6_addr
*) tst_address
.data
;
866 QList
<quint8
> result
;
868 std::copy(ip6
->bytes
+ 0, ip6
->bytes
+ 16, std::back_inserter(result
));
869 return QVariant::fromValue(result
);
877 conv_item_t
* ConversationDataModel::itemForRow(int row
)
879 if (row
< 0 || row
>= rowCount())
881 return (conv_item_t
*)&g_array_index(storage_
, conv_item_t
, row
);
884 bool ConversationDataModel::showConversationId(int row
) const
886 if (!storage_
|| row
< 0 || row
>= (int) storage_
->len
)
889 conv_item_t
*conv_item
= (conv_item_t
*)&g_array_index(storage_
, conv_item_t
, row
);
890 if (conv_item
&& (conv_item
->ctype
== CONVERSATION_TCP
||
891 conv_item
->ctype
== CONVERSATION_UDP
||
892 conv_item
->ctype
== CONVERSATION_IP
||
893 conv_item
->ctype
== CONVERSATION_IPV6
||
894 conv_item
->ctype
== CONVERSATION_ETH
))