Compare commits

..

175 Commits

Author SHA1 Message Date
Srivats P
318fe124bf Suggest disabling intelligent import if pcap diff 2024-02-02 17:02:04 +05:30
Srivats P
b3ac5f8d5d Add support for RARP 2024-01-19 15:53:00 +05:30
Srivats P
f185ceb206 Stop thread if still running in destructor
Threads addressed in this commit:
 - Transmitter (txThread, txStats)
 - Capturer
 - EmulXcvr
2023-11-24 13:12:35 +05:30
Srivats P
cf84060277 Start/stop the txStats thread along with txThread
This will avoid having an always running thread unnecessarily
2023-11-23 17:02:58 +05:30
Srivats P
469e0b054e Simplify .pro file by removing some duplication 2023-11-16 11:53:52 +05:30
Srivats P
23ee0e6f00 Extract libostfilegui out of libostfile
libostfile now has no GUI dependencies
2023-11-16 11:53:52 +05:30
Srivats P
f3dccb9484 Move spinBoxDelegate back to ostprotogui
This was mistakenly moved to ostfile when it was extracted from
ostprotogui
2023-11-16 11:53:52 +05:30
Srivats P
8f89c577ef Extract file format UI code into separate files 2023-11-16 11:53:52 +05:30
Srivats P
71435869cf Rename pcapfileformatoptions to pcapoptionsdialog 2023-11-16 11:53:52 +05:30
Srivats P
f779b0138b Extract pcapfileformatoptions into separate files 2023-11-16 11:53:52 +05:30
Srivats P
8c58d8610f Prep to move PcapOptions UI into separate files
This commit only creates a copy of pcapfileformat.* files for better
history tracking. The actual code move will happen in next commit.
2023-11-16 11:53:52 +05:30
Srivats P
c285e91d4a Extract libostfile from libprotogui
All .pro file changes only

libostfile still has GUI dependency due to PCAP import options UI.
Need to see if we can remove that - maybe extract into a separate
libostfilegui.
2023-11-16 11:53:52 +05:30
Srivats P
f5350663cd Prepare to extract libostfile from libostprotogui
This commit only copies ostprotogui as ostfile to track changes. Actual
changes will be made in subsequent commits
2023-11-16 11:53:52 +05:30
Srivats P
483f7fb4c5 Fix empty port stats when viewing rsvd ports only
This was a regression introduced in 1.3.0 as part of the port stats UX
improvement. The 'user' row was changed from the first (0) to the last,
but the proxy model continued to use a hard-coded value of 0 to check
for user to determine whether to show a port column or not.
2023-11-08 12:55:39 +05:30
Srivats P
d067019959 Bump to next dev version 1.4.0-dev 2023-11-08 10:44:51 +05:30
Srivats P
eb9d5eaf1a Bump version to 1.3.0 2023-10-19 10:50:25 +05:30
Srivats P
e677dc7d16 Fix div by 0 crash in stream timing stats (jitter) 2023-10-14 12:05:21 +05:30
Srivats P
ebe6aef62d Fix icon-only delegate
The delegate was still rendering display text. But we didn't realize
because the only place where this delegate was used was returning a
QVariant() for DisplayText role earlier. Once we started returning a
valid QString (for copy-paste purposes), this bug showed up.

This didn't work because we further delegate the actual painting to
the base QStyledItemDelegate which re-initializes option.features
from the item index values, so removing HasDisplay does not work.

The fix is to override the displayText() to return a null string.
2023-10-13 12:16:03 +05:30
Srivats P
1e8486991d Include state in port stats text copy to clipboard
The view uses an icon-only delegate for state to suppress the text
display
2023-10-11 13:07:02 +05:30
Srivats P
fff61d7773 Add assert version checks for upcoming 1.3.0 2023-10-11 12:57:25 +05:30
Srivats P
eb3331f74d Update copyright years in About 2023-10-11 12:57:05 +05:30
Srivats P
f3f553d149 Override RxStats filter if specified in drone.ini
This was added to work around any libpcap wrong BPF generation like #354 or
to workaround issues like #355 (stream stats with ICMPv6)
2023-10-03 11:41:57 +05:30
Srivats P
49cb7c40e0 Check and warn about stream stats for ICMP streams
Stream stats are not supportedd for ICMP packets
2023-10-03 11:39:46 +05:30
Srivats P
5581118e2f Work around faulty linux rx stats capture filter
libpcap has a bug on Linux which leads to incorrect BPF generation for our
capture filter. This commit changes the filter expression on Linux such that
correct BPF is generated.

Fixes #354
2023-10-03 10:39:03 +05:30
Srivats P
3bcd31a1ea Use IPv4AddrValidator for IPv4 protocol config UI 2023-07-17 16:10:07 +05:30
Srivats P
268fad0690 Include headers also when copying stream stats
This is for plain text copy format.

This also replaces any newlines in the header text with a space to maintain
formatting.
2023-06-26 18:08:49 +05:30
Srivats P
28b308ce6c Try and fit all stream stats column within window
* Resize columns to content (instead of using default width)
 * Use Kpps/Mpps for Pkt Rate with 3 decimal
 * Use 3 decimal places for bit-rates (is more logical because units change
   every 1000 anyway)
 * Use 2 decimal places for time interval

This just improves the chances of all columns fitting - but is not guaranteed
2023-06-26 17:03:47 +05:30
Srivats P
bfda96a888 Add jitter to Stream Stats in the GUI
These are the GUI side changes to the jitter server side changes committed
earlier.
2023-06-26 14:38:28 +05:30
Srivats P
a2734647b6 Fix stream timing clear for all GUIDs for a port
AbstractPort uses a value of UINT_MAX to signify 'all guids', but
StreamTiming uses 'invalid guid' value which are NOT the same value. But
UINT_MAX is guaranteed to be greater than invalid GUID - so instead of
equality we check for greater than comparison.
2023-06-26 13:03:25 +05:30
Srivats P
5eb2ad1979 Calculate per stream jitter using latency values
We calculate "average jitter".

The changes include only the server side, not the client/GUI side which will
be done in a future commit.
2023-06-26 13:01:11 +05:30
Srivats P
1fa84ec644 Use pkt-set delay instead of list's in intlvd mode
Since we now create an explicit packet set even for interleaved mode, we can
calculate and use the packet set delay instead of using the list loop delay.

With this change both sequential and interleaved mode do not use list loop
delay at all, but we still retain the code for that - in case we need to use
it later for some reason.
2023-06-22 11:45:22 +05:30
Srivats P
c044880f1a Fix inaccurate low tx rates (~1000pps) in seq mode
Creation of explicit packet sets had both a packet set delay and a list loop
delay set leading to lower than configured rate at tx. Earlier implicit
packet set always had set delay as 0 and only list loop delay was used.

Fix is to always set list loop delay to 0 in sequential mode as packet set
will have the correct delay set due to explicit packet sets.
2023-06-22 10:47:13 +05:30
Srivats P
2941c7ec22 Fix crash for top speed tx with ttag in seq mode
Crash was due to loopDelay being 0 in case of topSpeed. For ttags, we
recalculatue loopDelay based on max line rate and avg pkt size.

The problem doesn't exist in interleaved mode because minGap and duration
are non-zero in the top speed tx case.
2023-06-20 11:59:56 +05:30
Srivats P
ee45aaf0eb Change StreamTiming::TxRxKey format
The format now matches the ttag list format input in recordTxTime() making
that API a little more optimized.
2023-06-17 12:14:18 +05:30
Srivats P
5ec7010c51 Fix contention for port streamStats across threads
Tx/Rx stream stats related threads no longer have a direct reference to the
port's stream stats - instead they have their own copy that they keep and
return (in a reset-on-read fashion) when asked for. Each copy also has it's
own lock to prevent contention for read/update/clear.

PcapPort now fetches Tx(Transmitter) and Rx(Poller) stats on demand and
updates the port's stream stats - under protection of a lock.
2023-06-17 11:31:48 +05:30
Srivats P
bef0f1d162 Make stream timing recordTx/Rx APIs inline 2023-06-15 12:56:53 +05:30
Srivats P
598e6bf243 Merge changes made in base code for turbo/latency 2023-06-15 12:32:20 +05:30
Srivats P
63ed64a9d2 Fix extra whitespace in latency feature added code 2023-05-26 11:31:52 +05:30
Srivats P
8ef9da062d Comment out L4Cksum code from pcap tx thread
We are not rewriting L4Cksum for ttag packets at the moment. See
comment in packetsequence.h

Commenting out this code doesn't seem to observably improve tx
performance though.

The latency code seems to reduce stream stats max tx performance
by around 3 to 5%. Recovering this may be done separately as part
of overall performance optimzation of Tx code.
2023-05-26 11:31:52 +05:30
Srivats P
7fee90313e Don't use avgDelay in interleaved mode
The interleaved mode's single packet set MUST always have 0 delay
for accurate rate.

Before latency code, the interleaved packet set was added implicitly
and had 0 delay. Latency code added explicit packet set and used
avgDelay for the set. The avgDelay was needed for the original algo
used to determine when to send ttag packets. That algo is no longer
used (ttag markers are now explicitly configured by AbstractPort).

Turbo still needs avgDelay for other use, but changes will be made in
Turbo code for that.
2023-05-26 11:31:52 +05:30
Srivats P
bafdd948f8 Make win32 specific changes for per-stream latency
There are 2 changes -
1. Encode txPort in ttag packets and use it at TxTtagStats and
RxPcapStats to identify Tx and Rx packets respectively
2. Don't use pcap_sendqueue_transmit() if stream timing is in use -
since we can't modify TTAG packets inside that API
2023-05-26 11:31:52 +05:30
Srivats P
e138fa0d3d Fix windows build issues 2023-05-26 11:31:52 +05:30
Srivats P
8583299a1c Compile out timing debug prints
They can be compiled in if required
2023-05-26 11:31:52 +05:30
Srivats P
4ba98cc520 Fix latency timers not getting started/stopped
In a previous commit, we start/stop these timers based on number of
ports tracking stream stats triggered by RPCs. However, timers cannot
be triggered across threads (RPC thread and main thread in this case).

This fix uses a queued connection to post it to the other queue.
2023-05-26 11:31:52 +05:30
Srivats P
649fa03575 Fix tx ttag pcap filter capture filter syntax
The capture filter was not getting compiled earlier
2023-05-26 11:31:52 +05:30
Srivats P
650c098370 Use TtagTimeInterval to determine ttag markers
This is for interlaved mode; sequential mode was already using it
2023-05-26 11:31:52 +05:30
Srivats P
d375736a39 Don't use udiffTimeStamp() with struct timeval
It will fail to build for non-Linux platforms where TimeStamp is NOT timeval
2023-05-26 11:31:52 +05:30
Srivats P
976fc72de8 Retain seq as param for sendQueueTransmit for now
If and when we remove PacketSequence::ttagL4ChecksumOffset we will take a call
if we should revert back to passing seq->sendQueue instead of seq at that time
2023-05-26 11:31:52 +05:30
Srivats P
4426a51d0f Run stream latency timers only when required
Required => At least one port is tracking stream stats

Also changed some optimization FIXMEs as TODOs
2023-05-26 11:31:52 +05:30
Srivats P
3c6632b6a2 Remove comment about trying read-write lock
The stream timingHash is read by getStreamStats() while it is read/write
for processRecords(), the latter is a more frequent operation so there's
no real benefit of using a read-write lock instead of simple mutex.
2023-05-26 11:31:52 +05:30
Srivats P
d3be505a0c Make app QObject parent of StreamTiming singleton 2023-05-26 11:31:52 +05:30
Srivats P
70f1b6c6e6 Add thread name for PcapRxStats
PcapTxTtagStats thread name has also been shortened since names beyond 16
chars are truncated.
2023-05-26 11:31:52 +05:30
Srivats P
d65fea00d5 Change stream timing GC timer to CLOCK_REALTIME 2023-05-26 11:31:52 +05:30
Srivats P
0afc150264 Don't fix incorrect checksum for Ttag packets
See comments in code
2023-05-26 11:31:52 +05:30
Srivats P
6ba942d00f Remove PcapTxTtagStats::handle_ member
PcaptxTtagStats inherits from PcapSession which already includes a protected
handle_ member.

This removal was likely left off when PcapTxTtagStats started inheriting from
PcapSession.
2023-05-26 11:31:52 +05:30
Srivats P
4ee91c1bc2 Rework sequential mode build for new ttag algo
The previous commit changed the algo to determine which packets were Ttag'd,
but changes were done only for interleaved mode.

This commit adds the changes required for sequential mode.
2023-05-26 11:31:52 +05:30
Srivats P
aebb609e37 Change algo to infer next Ttag pkt (interleaved mode)
The algo works for the following cases of interleaved streams -
 * pktListDuration < ttagTimeInterval
 * pktListDuration > ttagTimeInterval
 * some streams have Ttag, some don't
    - first stream has Ttag
    - first stream does NOT have Ttag
 * no streams have Ttag

Changes for sequential mode are pending
2023-05-26 11:31:52 +05:30
Srivats P
7160f724cb Fix infinite loop in building interleaved streams
Incorrect timestamp comparison was leading to infinite loop
2023-05-26 11:31:52 +05:30
Srivats P
a48a11fe02 Add explicit packet set for interleaved streams
Interleaved mode used an implicitly added packet set in both base and Turbo
code. This has been chaned to use an explicit mode to keep things consistent.

Turbo code still has the implicit packet set related code - that needs to be
removed, once the explicit packet set code is validated and tested.
2023-05-26 11:31:52 +05:30
Srivats P
91a6efdeb7 Use qFrom/ToBigEndian instead of ntohs/htons
For consistency with rest of the code
2023-05-26 11:31:52 +05:30
Srivats P
bfdcee2baa Recompute L4 checksum on-the-fly for TTag packets 2023-05-26 11:31:52 +05:30
Srivats P
c2967b663d Calculate L4 checksum offset for TTag packets 2023-05-26 11:31:52 +05:30
Srivats P
70b5e60440 Rename delay as latency in Protobuf/RPC
The GUI uses the term 'latency', so it's better if the API alsos use the same
term instead of 'delay'
2023-05-26 11:31:52 +05:30
Srivats P
5540253e61 Fix StreamTiming TxRxKey
makeKey was incorrect by mistake
2023-05-26 11:31:52 +05:30
Srivats P
8ecbe78ddd GUI changes to display avg latency
At this time we only show per-guid latency aggregated across all ports
2023-05-26 11:31:52 +05:30
Srivats P
3e3b5144aa Process pending before fetching streamTiming delay 2023-05-26 11:31:52 +05:30
Srivats P
596df69519 Fix MacOS build break by removing unused member
PcapTxTtagStats::lastPcapStats_ was unused because debugStats() was moved to
PcapSession, but removing this member var was left out
2023-05-26 11:31:52 +05:30
Srivats P
680f6eb89f Fix streamTiming garbage collection infinite loop 2023-05-26 11:31:52 +05:30
Srivats P
1b18149aaa Delete PcapTxTtagSession::debugStats
debugStats() was moved to base class PcapSession earlier, but removing it
from here was missed out
2023-05-26 11:31:52 +05:30
Srivats P
bf749847e0 Rename PcapRxStats::id_ as PcapRxStats::portId_
Better code clarity
2023-05-26 11:31:52 +05:30
Srivats P
ab713ce043 Integrate StreamTiming with the code
Bugs found during integration were fixed and minor code improvements were made
such as using consts, const params, renaming members etc.
2023-05-26 11:31:52 +05:30
Srivats P
fc2d8408fa Change StreamTiming::timing_ from QList to QHash
Using QList meant we need to know the port count in the constructor - which is
difficult to know because StreamTiming is designed as a singleton
2023-05-26 11:31:52 +05:30
Srivats P
39c8d6f5f3 Add initial cut of StreamTiming class
This singleton class will keep track of Ttag timing across all ports and GUIDs.

A bunch of FIXMEs/TODOs are pending for this class implementation; also this
class has not been hooked up to the rest of the code yet.
2023-05-26 11:31:52 +05:30
Srivats P
219ad576ad Fix another MacOS build break
Break was due to following warnings (being promoted to errors) -
 * id_ private member was unused
 * tv_usec is int on MacOS (but long on Linux)
2023-05-26 11:31:52 +05:30
Srivats P
3060701386 Move debugStats() from PcapRx to base PcapSession
With this change, other classes that use PcapSession as base can also
use debugStats(), if required
2023-05-26 11:31:52 +05:30
Srivats P
47325c38b0 Ignore failures when stopping stream stats tracking
Stop everything irrespective of any failures
2023-05-26 11:31:52 +05:30
Srivats P
21ce331c43 Create a Tx Ttag tracker thread
For now we are just debug printing timestamp with T-TagId and GUID. We
need to store this tuple and compare when we Rx the same - this will be
in a upcoming commit.
2023-05-26 11:31:52 +05:30
Srivats P
fd2fae711b Fix MacOS build break
For some reason udiffTimeStamp is not defined for MacOS. To be investigated
later.
2023-05-26 11:31:52 +05:30
Srivats P
2d998b3708 Add an incrementing tag id to T-Tag packets 2023-05-26 11:31:52 +05:30
Srivats P
f65aed7bb0 Add infra to update L4 checksum for T-Tag packets
Pending
 * Calculate L4 checksum offset instead of hardcoded value
 * Recalculate packet L4 checksum instead of using 0 value
2023-05-26 11:31:52 +05:30
Srivats P
fdf8c77350 Change T-Tag on the fly 2023-05-26 11:31:52 +05:30
Srivats P
429eff123d Don't create implicit packet sets for Tx
As part of Turbo changes, we made changes to create explicit packet
sets, but for the base code we continued creating implicit packet
sets for some cases. With this change we don't create any implicit
packet set.

This change needs to be tested thoroughly for multiple cases.
2023-05-26 11:31:52 +05:30
Srivats P
3c98900092 Reduce vertical whitespace in sendQueueTransmit() 2023-05-26 11:31:52 +05:30
Srivats P
159cd7c0da Add T-Tag placeholder in sign protocol 2023-05-26 11:31:52 +05:30
Srivats P
46b148b62b Reformat TxThread/run {} to use less vertical space 2023-05-26 11:31:52 +05:30
Srivats P
f29d31d38a Update comments about implict packetset 2023-05-26 11:31:52 +05:30
Srivats P
1056b8d170 Fix werror warning about 0 being signed by default 2023-05-26 11:31:52 +05:30
Srivats P
4a4de23d8a Replace Donate button with Github Sponsors button 2023-05-09 18:27:15 +05:30
Srivats P
425e4ef261 Rename port stats names to begin with send/receive
This will hopefully make it quicker and easier for the user to find
the stats item of interest.
2023-03-04 15:31:24 +05:30
Srivats P
d950432bc9 Move port stats hidden rows to the end
This avoids having to recalculate row indices
2023-03-04 10:49:35 +05:30
Srivats P
9849973562 Visually group related port stats
A horizontal line is drawn between groups
2023-03-04 09:57:56 +05:30
Srivats P
4f6749f16d Set alternate colors for port stats row headers
All 'sent' rows including the row header have the alt color, and
the 'recv' rows including the row header has the base color. This
change should make it easier to quickly distinguish sent/recv rows
2023-03-03 17:37:43 +05:30
Srivats P
8dbd01622c Make sent/recv order consistent for port stats
Also remove sent/recv byte rates as we have bit rates and that
should be sufficient
2023-03-03 14:14:08 +05:30
Srivats P
2f3add63d8 ostinato -[hv] should output to stdout 2023-02-24 15:15:59 +05:30
Srivats P
7b7ede351b Fix build break with stream stats fix
The fix was validated with Windows, but not on Linux. This commit fixes
the build break for non-windows platforms.
2023-02-09 15:06:34 +05:30
Srivats P
c70811eaa4 Fix spurious stream stats drops
The problem happens for bidirectional flows. The sequence of events is
as follows when you start Tx on Ports p1, p2 with the current code -

1. Clear stream stats on p1
2. Start tx on p1
3. Clear stream stats on p2
4. Start tx on p2

By the time #3 is executed, it may have already rx packets from p1 which
are being incorrectly cleared, this will cause these number of packets
to show up as dropped instead - incorrectly.

The fix is to change the order like this -

1. Clear stream stats on p1
2. Clear stream stats on p2
3. Start tx on p1
4. Start tx on p2

Unidirectional flows will not see this problem - as long as startTx is
done only on the Tx port and not the Rx port.

This bug is a regression caused due to the code changes introduced for the
stream stats rates feature implemented in 1.2.0
2023-02-08 16:34:03 +05:30
Srivats P
e2369c02bc Fix value for str/bytes field when save as Python
Protobuf string type should be treated as a Python unicode string usable
in both Python 2.x and Python 3.x. Since we are now using unicode strings,
force encoding as utf-8.

Protobuf bytes type should be treated as a Python byte string. Use hex
values in byte literal even for printable characters, for a better UX.

escapeString() is no longer being used, but has been retained in the
code.
2022-12-28 19:30:50 +05:30
Srivats P
c07d9e8691 Fix drone crash at startup on Linux
The crash happens if there is a default route without a gateway in
the main routing table
2022-12-23 13:14:19 +05:30
Srivats P
d44fdf4ae7 Add a prefix to combo protocol fields
For same protocol numbers (e.g. IP 4o4), use Outer/Inner as prefix. For
"similar" protocols like VlanStack or IP 4o6, use the protocol name as
the prefix.
2022-12-20 13:38:16 +05:30
Srivats P
f07cba39d5 Reset port widget current on portgroup disconnect
Failure to do so was causing a crash because port widget was trying to
disconnect signal from a non-existent port (corresponding to the current
index that was not reset) after the portgroup reconnected or another
portgroup came up and a port was selected in the port list.

This bug was a regression caused by the refactoring changes when
portwidget (and streamswidget) was extracted from portswindow.
2022-12-08 17:00:15 +05:30
Srivats P
1e50f9b095 Use errorOccurred() signal in disconnect as well
8d3f0c807f had changed the signal name in connect, but missed out in
using the new name in disconnect()
2022-11-14 17:27:47 +05:30
Srivats P
3cea0244d4 Rename port configuration slot
Adding a parameter with a default value to the existing on_XXX slot
in a previous commit makes auto connect signal-slots by name to flag
signal not found.

So rename the slot name and add an explicit connection.
2022-10-07 12:58:45 +05:30
Srivats P
8d3f0c807f Fix error msg not shown when missing packet.dll
Qt5.6 renamed the QProcess::error() signal to QProcess::errorOccurred().
Use the new signal name.
2022-09-30 20:33:25 +05:30
Srivats P
449a489986 Change version from 1.3.0-alpha to 1.3.0-dev
Dev is a better word choice for current stage
2022-09-27 11:40:22 +05:30
Srivats P
b645e02963 Double click port to open port config dialog
Or select and press Enter using the keyboard

Fixes #344
2022-09-27 11:38:03 +05:30
Srivats P
32ddf223b6 Show port name/description in port stats window
By default the port name (ifXXX for Windows) is shown. If a port has a
user description, that is shown instead of port name.
2022-09-12 15:46:06 +05:30
Srivats P
42091e5221 Allow user to add a port description
If a user description is available, that is shown in the ports window
other wise the system determined description is shown.

Updates #223
2022-09-09 12:25:17 +05:30
Srivats P
82db82d85b Save PortConfigDialog UI using Qt5 designer
No functional changes. Just whitespace and formatting changes.
2022-09-01 14:48:42 +05:30
Srivats P
e9fa8a0c5b Rename qhexedit2 VERSION file to VERSION.txt
On MacOS, clang incorrectly includes the qHexEdit2 VERSION file
instead of it's own - so rename our file
2022-08-31 18:38:21 +05:30
Srivats P
027b0562de Bump version to 1.3.0-alpha
This bump is to avoid compatiblity issues between development on master
and the actual 1.2.x code.
2022-08-31 18:19:42 +05:30
Srivats P
c82cccc5eb Bump version to 1.2.0 2022-08-15 13:06:22 +05:30
Srivats P
94a6423a96 Add a 1.2.0 version updater test 2022-08-15 13:05:41 +05:30
Srivats P
cda08d9afb Bump version to 1.2.0-rc.1 2022-07-29 20:59:16 +05:30
Srivats P
97068b97e3 Use /usr/share/ostinato-controller/themes on Linux
This is because the Ostinato packaging reccomendation is to use
ostinato-agent and ostinato-controller package names. The ostinato
package is a meta package containing the aforementioned sub-packages
2022-07-29 20:54:14 +05:30
Srivats P
06ad12777f Warn if sign proto may be followed by padding
For now, we check and tell the user. The actual fix should be for
sign proto to add padding before it's content, if required

Updates #313
2022-06-30 18:24:48 +05:30
Srivats P
2970a292c6 Fix build break and warnings 2022-06-28 18:32:39 +05:30
Srivats P
701a058c7d Merge commit 'refs/subrepo/ostinato/fetch' into subrepo/ostinato
Update from master repo into turbo
2022-06-27 14:35:22 +05:30
Srivats P
2103f2c5a6 Minor editorial change for cleaning up message 2022-06-27 13:43:48 +05:30
Srivats P
4be8a2969d Create and print activation hash on request
To request, pass option -a on the command-line
2022-06-27 13:43:48 +05:30
Srivats P
46a54fd56b Define PCAP promisc flag only if not defined 2022-06-27 13:43:48 +05:30
Srivats P
d32f89d30b Enable libbpf logging when drone logs are enabled 2022-06-27 13:43:48 +05:30
Srivats P
1b647ade1b Print "Starting..." after processing command line args 2022-06-27 13:43:47 +05:30
Srivats P
789338c8e1 Add startup/shutdown console prints 2022-06-27 13:43:47 +05:30
Srivats P
898b56fc76 Add turbo license validation code
Signed up for and using keygen.sh licensing SAAS
2022-06-27 13:43:47 +05:30
Srivats P
9d42ed12cc Use a invalid GUID instead of 0 as default
If a packet does not have a sign/guid, use an invalid guid instead of 0
as the default, so that these packets get tracked against the invalid
guid which can be excluded while updating the port's stream stats.
2022-06-27 13:43:47 +05:30
Srivats P
c949dc6682 Integrate XdpPort more cleanly into port manager
* No ugly #ifdef TURBO
* Turbo ports need to be specified explicitly in drone.ini
* If a port is not a turbo port, fall back to LinuxPort
* Turbo ports set their description as 'Turbo' which shows up in GUI
* Some XdpPort creation/destruction error checks
2022-06-27 13:43:47 +05:30
Srivats P
eb2ca12f32 Reorg (part 4) - add left out file
ostinato/server/timespecops.h got left out from part 1 commit of
the reorg - because it was a new file and was ignored by a wrong
.gitignore rule
2022-06-27 13:43:46 +05:30
Srivats P
e79b61b189 Use -Werror for all code except protobuf generated
protobuf files will use -Wno-error
2022-06-27 13:43:46 +05:30
Srivats P
741202ca76 Remove compiler warning
Compiler treats literal '0' as int i.e. signed
2022-06-27 13:43:46 +05:30
Srivats P
843733567a Reoganize code using subrepo for Ostinato (part 2)
This commit includes all the new and modified files
2022-06-27 13:43:46 +05:30
Srivats P
6f4ef70dbc Add FUNDING.yml for GitHub sponsors 2022-06-24 15:46:51 +05:30
Srivats P
b296c5fddd Check duration > 0 before calculating stream rates
Avoid divide by zero error and/or inf/nan values
2022-06-22 12:56:07 +05:30
Srivats P
d9cd90a13d Clear stream stats before starting Tx on port
This is required for correct stream rate calculations
2022-06-21 18:53:35 +05:30
Srivats P
fb879d2c72 Return txDuration as 0 for ports with no streams 2022-06-21 18:51:42 +05:30
Srivats P
5e7bf77b0c
Merge pull request #350 from pstavirs/streamrates
Add average rate per GUID in stream statistics
2022-06-20 17:27:24 +05:30
Srivats P
a1d985e44b Show stream stats in GUI sorted by GUID 2022-06-16 18:14:23 +05:30
Srivats P
b8d5d9421f Show Duration 'n rates in stream stats by default
Port Pkt stats are no longer shown by default. To see them and byte
counters, use context menu option - "Show details" (renamed from the
existing "Show Byte counters")
2022-06-16 14:46:18 +05:30
Srivats P
2868806f3f GUI side changes for displaying stream rates
At the moment, the Tx duration for a GUID is taken to be the largest of
all tx duration for that GUID across all ports. In the future, we may
consider changing it to average instead of largest.
2022-06-16 12:30:13 +05:30
Srivats P
ccf5b5ca47 Add a few asserts to debug GUI client crash
The crash seems to happen in the following scenario -
 * Ostinato and drone are both running (with some devices/streams)
 * Kill and restart drone
 * Wait for GUI to reconnect to drone
 * Click on one of the ports in the port list
2022-06-09 20:32:22 +05:30
Srivats P
c03038167c Initiate ARP/NDP resolve during session open
The current implementation won't work for all cases. See the notes added
in the code.

Updates #311
2022-06-09 20:30:42 +05:30
Srivats P
de9018166d Remove extra braces 2022-06-09 15:11:55 +05:30
Srivats P
c0d860b92d Support 0 rate for interleaved streams
Fixes #297
2022-06-09 13:35:05 +05:30
Srivats P
81c975d6d1 Wait 500ms for ARP/NDP to finish in resolve RPC
Fixes #318
2022-06-06 16:30:33 +05:30
Srivats P
1854f1ab9e
Merge pull request #336 from pstavirs/gre
WIP: Add GRE protocol
2022-05-27 13:43:46 +05:30
Srivats P
5a91dc4561 Remove TODOs from GRE implementation files 2022-05-26 17:30:29 +05:30
Srivats P
3d6f39e84d Implement override checksum for GRE 2022-05-25 15:16:33 +05:30
Srivats P
60f61ed947 Add and use UIntEdit for 32-bit GRE Key/Seq fields 2022-05-23 17:04:27 +05:30
Srivats P
26d0c8ab9c Skip not-included fields in variable fields list
Optional protocol fields not included in the packet have bitSize = 0
2022-05-23 13:31:01 +05:30
Srivats P
25a91e52f6 Change GRE optional fields implementation
* frameFieldCount() always includes all frame fields whether or not
included in packet
* if an optional field is not included in packet
    - FieldName is still returned
    - FieldTextValue is returned as "<not-included>"
    - FieldBitWidth is 0
    - all remaining attributes are QVariant()
2022-05-23 13:18:18 +05:30
Srivats P
c8cc7a021f Merge branch 'master' into gre 2022-05-21 12:16:14 +05:30
Srivats P
564687ffe3
Merge pull request #348 from Benni0109/master
Update portstatswindow.ui
2022-04-22 21:05:38 +05:30
Benjamin
9d4aaa4f7b
Update portstatswindow.ui 2022-04-16 19:14:44 +02:00
Srivats P
329469dd6e Calculate port tx duration and send to controller
This commit only includes server side changes plus the .proto changes.

Client side UI changes are still pending.
2022-02-21 22:20:59 +05:30
Srivats P
7374c74b95 Increase default width of port list pane
The list and detail panes were currently set to 1:2, so that the list
pane is 1/3rd the width. But for some reason it is not working like I
expect it to work. Setting it 1:1 looks better.
2022-02-12 19:26:18 +05:30
Srivats P
6f433bb7a1 Move View menu immediately after Edit menu
This seems to be the convention and also recommended by MacOS HIG
2022-02-11 18:34:06 +05:30
Srivats P
2ee19da15c Add Stream and Device menus to main window menu bar
Stream and Device related actions from the File menu has been moved
under these new menus
2022-02-11 18:09:30 +05:30
Srivats P
22b8c405f7 Use palette colors for stream stats
Using harcoded colors leads to unexpected results in dark mode.
2022-02-11 11:37:03 +05:30
Srivats P
2ce7f0c7a0 Disconnect portgroup before removing from list
Disconnecting _during_ remove sometimes causes crash because disconnect
triggers the logs window alert which **can** lead to repainting of
other windows which access port groups which have not been synced after
the removal of one or more portgroups.

Also emit endRemoveRows after each portgroup so that begin/end signals
match.
2022-02-10 20:34:10 +05:30
Srivats P
041f500e65 Avoid crash at GUI start up
The crash can happen before mainWindow is allocated. Don't remember how
that happens, but I have seen it happen!
2022-02-09 21:30:51 +05:30
Srivats P
7dd070fd98 Fix packet dump view bg color when using dark theme
Qt already paints the background before calling the paint event, so we
don't need to repaint it.
2022-02-07 21:44:40 +05:30
Srivats P
ac52844b3f Don't stretch stream config simple protocols widget
Only avoid the vertical stretch by adding a spacer, let horizontal still
stretch
2022-02-05 21:22:32 +05:30
Srivats P
2bf2973a23
Merge pull request #347 from pstavirs/themes
Add Ostinato Themes
2022-02-05 20:51:32 +05:30
Srivats P
341d0c3be8 Install themes as part of 'make install'
Tested only on Windows; pending on MacOS/Linux
2022-01-30 13:13:20 +05:30
Srivats P
6534312968 Look for themes in different locations on Mac/Unix
Not tested yet - since I'm coding this currently on Windows
2022-01-30 12:15:17 +05:30
Srivats P
a089cc1751 Update material themes and add qds themes 2022-01-29 12:24:08 +05:30
Srivats P
b0a81fb231 Change recalc cksum defaults implementation
These are the existing and desired defaults -
 * GUI: recalcCksum = true
 * PCAP import test: recalcCksum = false

With the existing implementation, doing PCAP import in 'raw' mode twice
would lead to recalcCksum being checked in the dialog the second time.
This was because we were always forcing it to be true for the GUI case
without checking for anything.
2022-01-23 21:46:42 +05:30
Srivats P
735e960dcb Use nsec pcap as intermediate when opening non-pcap
When opening a non-PCAP file, we convert it to intermediate PCAP format.
Use nanosecond pcap format instead of standard microsec pcap format for
higher timestamp resolution.

Updates #238
2022-01-19 18:54:46 +05:30
Srivats P
226705f015 Use integer storage and arithmetic for nanosec pcap
quint64 has larger range than double so has better accuracy. However,
for calculating packet rate, use floating-point arithmetic since the
packet rate is a double

Updates #238
2022-01-19 18:46:09 +05:30
Srivats P
45d5e15f23 Save PCAP always in nanosecond PCAP format
Updates #238
2022-01-17 18:49:24 +05:30
Srivats P
d09d83000e Add comment specifying PDML reader needs PCAP file
Updates #238
2022-01-15 18:36:15 +05:30
Srivats P
0f322fb2d8 Read nanosec PCAP files natively in non-pdml mode
In PDML mode, nanosecond support is already present - in fact, only
nanosecond is supported not microseconds.

Updates #238
2022-01-15 16:22:28 +05:30
Srivats P
0587e22de9 Add theme-manager and qt-material themes 2021-12-24 21:36:14 +05:30
Srivats P
5045b7f273 Update preferences.ui as per recent QtDesigner
No changes to the form - .ui file changes only because QtDesigner
generates the .ui file differently
2021-12-19 14:16:23 +05:30
Srivats P
a89400a4e5
Merge pull request #346 from pstavirs/find-replace
Find replace
2021-12-18 12:03:02 +05:30
Srivats P
73dd198069 Add GRE protocol 2021-01-09 13:15:26 +05:30
140 changed files with 11652 additions and 649 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: pstavirs

View File

@ -13,11 +13,11 @@ I have been developing and maintaining Ostinato [single-handedly](https://github
I sell binary licenses on [ostinato.org](https://ostinato.org/downloads) to try and cover the costs of development. Please consider buying those - they are priced low enough that you can afford it or you could just as easily expense them to your organisation. I sell binary licenses on [ostinato.org](https://ostinato.org/downloads) to try and cover the costs of development. Please consider buying those - they are priced low enough that you can afford it or you could just as easily expense them to your organisation.
If you build Ostinato from source and find it useful, please donate to keep the lights on and sustain the project. If you build Ostinato from source and find it useful, please sponsor to keep the lights on and sustain the project.
[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86&style=for-the-badge)](https://github.com/sponsors/pstavirs)
Read the Ostinato [origin story](https://ostinato.org/about). Read the Ostinato [origin story](https://ostinato.org/about).
[![Donate](https://ostinato.org/images/donate.png)](https://gum.co/ostdonate)
Srivats P<br/> Srivats P<br/>
Author, Ostinato Author, Ostinato

View File

@ -94,7 +94,7 @@
<item> <item>
<widget class="QLabel" name="CopyrightLabel" > <widget class="QLabel" name="CopyrightLabel" >
<property name="text" > <property name="text" >
<string>Copyright (c) 2007-2020 Srivats P.</string> <string>Copyright (c) 2007-2023 Srivats P.</string>
</property> </property>
<property name="alignment" > <property name="alignment" >
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>

View File

@ -209,7 +209,7 @@ void DeviceGroupDialog::updateTotalDeviceCount()
void DeviceGroupDialog::updateIp4Gateway() void DeviceGroupDialog::updateIp4Gateway()
{ {
quint32 net = ip4Address->value() & (~0 << (32 - ip4PrefixLength->value())); quint32 net = ip4Address->value() & (~0UL << (32 - ip4PrefixLength->value()));
ip4Gateway->setValue(net | 0x01); ip4Gateway->setValue(net | 0x01);
} }

View File

@ -262,8 +262,10 @@ void DumpView::paintEvent(QPaintEvent* /*event*/)
// FIXME(LOW): unable to set the self widget's font in constructor // FIXME(LOW): unable to set the self widget's font in constructor
painter.setFont(QFont("Courier")); painter.setFont(QFont("Courier"));
// set a white background // Qt automatically clears the background before we are called
painter.fillRect(rect(), QBrush(QColor(Qt::white))); // QWidget::paintEvent doc:
// When the paint event occurs, the update region has normally
// been erased, so you are painting on the widget's background.
if (model()) if (model())
{ {

View File

@ -31,9 +31,13 @@ class IconOnlyDelegate : public QStyledItemDelegate
{ {
QStyleOptionViewItem opt = option; QStyleOptionViewItem opt = option;
opt.decorationPosition = QStyleOptionViewItem::Top; opt.decorationPosition = QStyleOptionViewItem::Top;
opt.features &= ~QStyleOptionViewItem::HasDisplay;
QStyledItemDelegate::paint(painter, opt, index); QStyledItemDelegate::paint(painter, opt, index);
} }
QString displayText(const QVariant&, const QLocale&) const
{
return QString();
}
}; };
#endif #endif

View File

@ -182,6 +182,8 @@ void LogsWindow::alert(State state)
// start - center of main window // start - center of main window
QRect start; QRect start;
QWidget *view = mainWindow; QWidget *view = mainWindow;
if (!view)
return;
alert_->setParent(view); alert_->setParent(view);
alert_->raise(); alert_->raise();
start.setSize(QSize(256, 256).scaled(view->size()/2, Qt::KeepAspectRatio)); start.setSize(QSize(256, 256).scaled(view->size()/2, Qt::KeepAspectRatio));

View File

@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "params.h" #include "params.h"
#include "preferences.h" #include "preferences.h"
#include "settings.h" #include "settings.h"
#include "thememanager.h"
#include <QApplication> #include <QApplication>
#include <QDateTime> #include <QDateTime>
@ -84,6 +85,8 @@ int main(int argc, char* argv[])
appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(), appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(),
appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString());
ThemeManager::instance()->setTheme(appSettings->value(kThemeKey).toString());
qsrand(QDateTime::currentDateTime().toTime_t()); qsrand(QDateTime::currentDateTime().toTime_t());
mainWindow = new MainWindow; mainWindow = new MainWindow;

View File

@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#endif #endif
#include "clipboardhelper.h" #include "clipboardhelper.h"
#include "fileformatoptions.h"
#include "jumpurl.h" #include "jumpurl.h"
#include "logsmodel.h" #include "logsmodel.h"
#include "logswindow.h" #include "logswindow.h"
@ -85,8 +86,14 @@ MainWindow::MainWindow(QWidget *parent)
localServer_ = new QProcess(this); localServer_ = new QProcess(this);
connect(localServer_, SIGNAL(finished(int, QProcess::ExitStatus)), connect(localServer_, SIGNAL(finished(int, QProcess::ExitStatus)),
SLOT(onLocalServerFinished(int, QProcess::ExitStatus))); SLOT(onLocalServerFinished(int, QProcess::ExitStatus)));
#if QT_VERSION >= 0x050600
connect(localServer_, SIGNAL(errorOccurred(QProcess::ProcessError)),
SLOT(onLocalServerError(QProcess::ProcessError)));
#else
connect(localServer_, SIGNAL(error(QProcess::ProcessError)), connect(localServer_, SIGNAL(error(QProcess::ProcessError)),
SLOT(onLocalServerError(QProcess::ProcessError))); SLOT(onLocalServerError(QProcess::ProcessError)));
#endif
localServer_->setProcessChannelMode(QProcess::ForwardedChannels); localServer_->setProcessChannelMode(QProcess::ForwardedChannels);
localServer_->start(serverApp, QStringList()); localServer_->start(serverApp, QStringList());
QTimer::singleShot(5000, this, SLOT(stopLocalServerMonitor())); QTimer::singleShot(5000, this, SLOT(stopLocalServerMonitor()));
@ -119,8 +126,11 @@ MainWindow::MainWindow(QWidget *parent)
setupUi(this); setupUi(this);
menuFile->insertActions(menuFile->actions().at(3), portsWindow->actions()); menuFile->insertActions(menuFile->actions().at(3),
portsWindow->portActions());
menuEdit->addActions(clipboardHelper->actions()); menuEdit->addActions(clipboardHelper->actions());
menuStreams->addActions(portsWindow->streamActions());
menuDevices->addActions(portsWindow->deviceActions());
statsDock->setWidget(statsWindow); statsDock->setWidget(statsWindow);
addDockWidget(Qt::BottomDockWidgetArea, statsDock); addDockWidget(Qt::BottomDockWidgetArea, statsDock);
@ -165,6 +175,7 @@ MainWindow::MainWindow(QWidget *parent)
this, SLOT(onNewVersion(QString))); this, SLOT(onNewVersion(QString)));
updater->checkForNewVersion(); updater->checkForNewVersion();
// TODO: If session file specified (and valid?), don't add local drone PG
// Add the "Local" Port Group // Add the "Local" Port Group
if (appParams.optLocalDrone()) { if (appParams.optLocalDrone()) {
PortGroup *pg = new PortGroup; PortGroup *pg = new PortGroup;
@ -409,8 +420,13 @@ void MainWindow::on_actionHelpAbout_triggered()
void MainWindow::stopLocalServerMonitor() void MainWindow::stopLocalServerMonitor()
{ {
// We are only interested in startup errors // We are only interested in startup errors
#if QT_VERSION >= 0x050600
disconnect(localServer_, SIGNAL(errorOccurred(QProcess::ProcessError)),
this, SLOT(onLocalServerError(QProcess::ProcessError)));
#else
disconnect(localServer_, SIGNAL(error(QProcess::ProcessError)), disconnect(localServer_, SIGNAL(error(QProcess::ProcessError)),
this, SLOT(onLocalServerError(QProcess::ProcessError))); this, SLOT(onLocalServerError(QProcess::ProcessError)));
#endif
disconnect(localServer_, SIGNAL(finished(int, QProcess::ExitStatus)), disconnect(localServer_, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(onLocalServerFinished(int, QProcess::ExitStatus))); this, SLOT(onLocalServerFinished(int, QProcess::ExitStatus)));
} }
@ -451,8 +467,8 @@ void MainWindow::reportLocalServerError()
if (localServer_->exitCode() == STATUS_DLL_NOT_FOUND) if (localServer_->exitCode() == STATUS_DLL_NOT_FOUND)
errorStr.append(tr("<p>This is most likely because Packet.dll " errorStr.append(tr("<p>This is most likely because Packet.dll "
"was not found - make sure you have " "was not found - make sure you have "
"<a href='%1'>WinPcap" "<a href='%1'>npcap installed and accessible</a>."
"</a> installed.</p>") "</p>")
.arg(jumpUrl("winpcap"))); .arg(jumpUrl("winpcap")));
#endif #endif
msgBox.setText(errorStr); msgBox.setText(errorStr);
@ -532,7 +548,7 @@ bool MainWindow::openSession(QString fileName, QString &error)
goto _fail; goto _fail;
} }
if ((optDialog = fmt->openOptionsDialog())) if ((optDialog = FileFormatOptions::openOptionsDialog(fmt)))
{ {
int ret; int ret;
optDialog->setParent(this, Qt::Dialog); optDialog->setParent(this, Qt::Dialog);

View File

@ -25,21 +25,10 @@
<addaction name="actionOpenSession" /> <addaction name="actionOpenSession" />
<addaction name="actionSaveSession" /> <addaction name="actionSaveSession" />
<addaction name="separator" /> <addaction name="separator" />
<addaction name="separator" />
<addaction name="actionPreferences" /> <addaction name="actionPreferences" />
<addaction name="actionFileExit" /> <addaction name="actionFileExit" />
</widget> </widget>
<widget class="QMenu" name="menuHelp" >
<property name="title" >
<string>&amp;Help</string>
</property>
<addaction name="actionHelpOnline" />
<addaction name="separator" />
<addaction name="actionDonate" />
<addaction name="actionCheckForUpdates" />
<addaction name="separator" />
<addaction name="actionHelpAbout" />
<addaction name="actionAboutQt" />
</widget>
<widget class="QMenu" name="menuEdit" > <widget class="QMenu" name="menuEdit" >
<property name="title" > <property name="title" >
<string>&amp;Edit</string> <string>&amp;Edit</string>
@ -52,9 +41,34 @@
<addaction name="actionViewShowMyReservedPortsOnly" /> <addaction name="actionViewShowMyReservedPortsOnly" />
<addaction name="actionViewRestoreDefaults" /> <addaction name="actionViewRestoreDefaults" />
</widget> </widget>
<widget class="QMenu" name="menuStreams" >
<property name="title" >
<string>&amp;Streams</string>
</property>
<addaction name="actionTest" />
</widget>
<widget class="QMenu" name="menuDevices" >
<property name="title" >
<string>&amp;Devices</string>
</property>
</widget>
<widget class="QMenu" name="menuHelp" >
<property name="title" >
<string>&amp;Help</string>
</property>
<addaction name="actionHelpOnline" />
<addaction name="separator" />
<addaction name="actionDonate" />
<addaction name="actionCheckForUpdates" />
<addaction name="separator" />
<addaction name="actionHelpAbout" />
<addaction name="actionAboutQt" />
</widget>
<addaction name="menuFile" /> <addaction name="menuFile" />
<addaction name="menuEdit" /> <addaction name="menuEdit" />
<addaction name="menuView" /> <addaction name="menuView" />
<addaction name="menuStreams" />
<addaction name="menuDevices" />
<addaction name="menuHelp" /> <addaction name="menuHelp" />
</widget> </widget>
<widget class="QStatusBar" name="statusbar" /> <widget class="QStatusBar" name="statusbar" />

View File

@ -5,31 +5,26 @@ win32:RC_FILE = ostinato.rc
macx:ICON = icons/logo.icns macx:ICON = icons/logo.icns
QT += widgets network script xml svg QT += widgets network script xml svg
INCLUDEPATH += "../rpc/" "../common/" INCLUDEPATH += "../rpc/" "../common/"
OBJDIR = .
win32 { win32 {
QMAKE_LFLAGS += -static QMAKE_LFLAGS += -static
CONFIG(debug, debug|release) { CONFIG(debug, debug|release) {
LIBS += -L"../common/debug" -lostprotogui -lostproto OBJDIR = debug
LIBS += -L"../rpc/debug" -lpbrpc
POST_TARGETDEPS += \
"../common/debug/libostprotogui.a" \
"../common/debug/libostproto.a" \
"../rpc/debug/libpbrpc.a"
} else { } else {
LIBS += -L"../common/release" -lostprotogui -lostproto OBJDIR = release
LIBS += -L"../rpc/release" -lpbrpc
POST_TARGETDEPS += \
"../common/release/libostprotogui.a" \
"../common/release/libostproto.a" \
"../rpc/release/libpbrpc.a"
} }
} else {
LIBS += -L"../common" -lostprotogui -lostproto
LIBS += -L"../rpc" -lpbrpc
POST_TARGETDEPS += \
"../common/libostprotogui.a" \
"../common/libostproto.a" \
"../rpc/libpbrpc.a"
} }
LIBS += -L"../common/$$OBJDIR" -lostfile -lostfilegui
LIBS += -L"../common/$$OBJDIR" -lostprotogui -lostproto
LIBS += -L"../rpc/$$OBJDIR" -lpbrpc
POST_TARGETDEPS += \
"../common/$$OBJDIR/libostfilegui.a" \
"../common/$$OBJDIR/libostfile.a" \
"../common/$$OBJDIR/libostprotogui.a" \
"../common/$$OBJDIR/libostproto.a" \
"../rpc/$$OBJDIR/libpbrpc.a"
LIBS += -lprotobuf LIBS += -lprotobuf
LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2 LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2
RESOURCES += ostinato.qrc RESOURCES += ostinato.qrc
@ -128,12 +123,23 @@ SOURCES += \
streamstatsmodel.cpp \ streamstatsmodel.cpp \
streamstatswindow.cpp \ streamstatswindow.cpp \
streamswidget.cpp \ streamswidget.cpp \
thememanager.cpp \
variablefieldswidget.cpp variablefieldswidget.cpp
THEMES += \
themes/material-dark.qss \
themes/material-dark.rcc \
themes/material-light.qss \
themes/material-light.rcc \
themes/qds-dark.qss \
themes/qds-dark.rcc \
themes/qds-light.qss \
themes/qds-light.rcc \
QMAKE_DISTCLEAN += object_script.* QMAKE_DISTCLEAN += object_script.*
include(../install.pri) include(../install.pri)
include(../shared.pri)
include(../version.pri) include(../version.pri)
include(../options.pri) include(../options.pri)

View File

@ -45,11 +45,11 @@ int Params::parseCommandLine(int argc, char* argv[])
logsDisabled_ = false; logsDisabled_ = false;
break; break;
case 'v': case 'v':
qDebug("Ostinato %s rev %s\n", version, revision); printf("Ostinato %s rev %s\n", version, revision);
exit(0); exit(0);
case 'h': case 'h':
default: default:
qDebug("usage: %s [-cdhv]\n", argv[0]); printf("usage: %s [-cdhv]\n", argv[0]);
exit(1); exit(1);
} }
n++; n++;

View File

@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "port.h" #include "port.h"
#include "emulation.h" #include "emulation.h"
#include "fileformatoptions.h"
#include "streamfileformat.h" #include "streamfileformat.h"
#include <QApplication> #include <QApplication>
@ -604,7 +605,7 @@ bool Port::openStreams(QString fileName, bool append, QString &error)
goto _fail; goto _fail;
} }
if ((optDialog = fmt->openOptionsDialog())) if ((optDialog = FileFormatOptions::openOptionsDialog(fmt)))
{ {
int ret; int ret;
optDialog->setParent(mainWindow, Qt::Dialog); optDialog->setParent(mainWindow, Qt::Dialog);

View File

@ -88,8 +88,15 @@ public:
{ return d.port_id().id(); } { return d.port_id().id(); }
const QString name() const const QString name() const
{ return QString().fromStdString(d.name()); } { return QString().fromStdString(d.name()); }
const QString description() const const QString systemDescription() const
{ return QString().fromStdString(d.description()); } { return QString().fromStdString(d.description()); }
const QString userDescription() const
{ return QString().fromStdString(d.user_description()); }
const QString description() const
{
return userDescription().isEmpty() ?
systemDescription() : userDescription();
}
const QString notes() const const QString notes() const
{ return QString().fromStdString(d.notes()); } { return QString().fromStdString(d.notes()); }
const QString userName() const const QString userName() const

View File

@ -32,6 +32,8 @@ PortConfigDialog::PortConfigDialog(
setupUi(this); setupUi(this);
description->setPlaceholderText(portConfig_.description().c_str());
description->setText(portConfig_.user_description().c_str());
switch(portConfig_.transmit_mode()) switch(portConfig_.transmit_mode())
{ {
case OstProto::kSequentialTransmit: case OstProto::kSequentialTransmit:
@ -80,6 +82,8 @@ void PortConfigDialog::accept()
{ {
OstProto::Port pc; OstProto::Port pc;
pc.set_user_description(description->text().toStdString());
if (sequentialStreamsButton->isChecked()) if (sequentialStreamsButton->isChecked())
pc.set_transmit_mode(OstProto::kSequentialTransmit); pc.set_transmit_mode(OstProto::kSequentialTransmit);
else if (interleavedStreamsButton->isChecked()) else if (interleavedStreamsButton->isChecked())
@ -109,6 +113,11 @@ void PortConfigDialog::accept()
pc.set_is_tracking_stream_stats(streamStatsButton->isChecked()); pc.set_is_tracking_stream_stats(streamStatsButton->isChecked());
// Update fields that have changed, clear the rest // Update fields that have changed, clear the rest
if (pc.user_description() != portConfig_.user_description())
portConfig_.set_user_description(pc.user_description());
else
portConfig_.clear_user_description();
if (pc.transmit_mode() != portConfig_.transmit_mode()) if (pc.transmit_mode() != portConfig_.transmit_mode())
portConfig_.set_transmit_mode(pc.transmit_mode()); portConfig_.set_transmit_mode(pc.transmit_mode());
else else

View File

@ -1,37 +1,51 @@
<ui version="4.0" > <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PortConfigDialog</class> <class>PortConfigDialog</class>
<widget class="QDialog" name="PortConfigDialog" > <widget class="QDialog" name="PortConfigDialog">
<property name="geometry" > <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>244</width> <width>248</width>
<height>257</height> <height>292</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle">
<string>Port Config</string> <string>Port Config</string>
</property> </property>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout">
<item> <item>
<widget class="QGroupBox" name="transmitModeBox" > <widget class="QLabel" name="label">
<property name="title" > <property name="text">
<string>Description</string>
</property>
<property name="buddy">
<cstring>description</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="description"/>
</item>
<item>
<widget class="QGroupBox" name="transmitModeBox">
<property name="title">
<string>Transmit Mode</string> <string>Transmit Mode</string>
</property> </property>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout">
<item> <item>
<widget class="QRadioButton" name="sequentialStreamsButton" > <widget class="QRadioButton" name="sequentialStreamsButton">
<property name="text" > <property name="text">
<string>Sequential Streams</string> <string>Sequential Streams</string>
</property> </property>
<property name="checked" > <property name="checked">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="interleavedStreamsButton" > <widget class="QRadioButton" name="interleavedStreamsButton">
<property name="text" > <property name="text">
<string>Interleaved Streams</string> <string>Interleaved Streams</string>
</property> </property>
</widget> </widget>
@ -40,21 +54,21 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox" > <widget class="QGroupBox" name="groupBox">
<property name="title" > <property name="title">
<string>Reservation</string> <string>Reservation</string>
</property> </property>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout">
<item> <item>
<widget class="QLabel" name="reservedBy" > <widget class="QLabel" name="reservedBy">
<property name="text" > <property name="text">
<string>Reserved by: </string> <string>Reserved by: </string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="reserveButton" > <widget class="QCheckBox" name="reserveButton">
<property name="text" > <property name="text">
<string>Reserve</string> <string>Reserve</string>
</property> </property>
</widget> </widget>
@ -63,25 +77,25 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="exclusiveControlButton" > <widget class="QCheckBox" name="exclusiveControlButton">
<property name="text" > <property name="text">
<string>Exclusive Control</string> <string>Exclusive Control</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="streamStatsButton" > <widget class="QCheckBox" name="streamStatsButton">
<property name="text" > <property name="text">
<string>Stream Statistics</string> <string>Stream Statistics</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation" > <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
<property name="sizeHint" > <property name="sizeHint" stdset="0">
<size> <size>
<width>226</width> <width>226</width>
<height>71</height> <height>71</height>
@ -90,17 +104,25 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation" > <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons" > <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<tabstops>
<tabstop>description</tabstop>
<tabstop>sequentialStreamsButton</tabstop>
<tabstop>interleavedStreamsButton</tabstop>
<tabstop>reserveButton</tabstop>
<tabstop>exclusiveControlButton</tabstop>
<tabstop>streamStatsButton</tabstop>
</tabstops>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>
@ -109,11 +131,11 @@
<receiver>PortConfigDialog</receiver> <receiver>PortConfigDialog</receiver>
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>234</x> <x>234</x>
<y>205</y> <y>205</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>157</x> <x>157</x>
<y>214</y> <y>214</y>
</hint> </hint>
@ -125,11 +147,11 @@
<receiver>PortConfigDialog</receiver> <receiver>PortConfigDialog</receiver>
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>234</x> <x>234</x>
<y>205</y> <y>205</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>243</x> <x>243</x>
<y>214</y> <y>214</y>
</hint> </hint>

View File

@ -1111,8 +1111,11 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
// * modify (new) deviceGroups // * modify (new) deviceGroups
// * add (new) stream ids // * add (new) stream ids
// * modify (new) streams // * modify (new) streams
// * resolve neighbors
// * build packets
// XXX: This assumes getDeviceGroupIdList() was invoked before // XXX: This assumes getDeviceGroupIdList() was invoked before
// getStreamIdList() - if the order changes this code will break! // getStreamIdList() - if the order changes this code will break!
// XXX: See resolve/build notes below
// XXX: same name as input param, but shouldn't cause any problem // XXX: same name as input param, but shouldn't cause any problem
PbRpcController *controller; PbRpcController *controller;
@ -1153,6 +1156,7 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
} }
// add/modify deviceGroups // add/modify deviceGroups
bool resolve = false;
if (newPortContent->device_groups_size()) if (newPortContent->device_groups_size())
{ {
OstProto::DeviceGroupIdList *deviceGroupIdList OstProto::DeviceGroupIdList *deviceGroupIdList
@ -1184,6 +1188,7 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
deviceGroupConfigList, ack, deviceGroupConfigList, ack,
NewCallback(this, &PortGroup::processModifyDeviceGroupAck, NewCallback(this, &PortGroup::processModifyDeviceGroupAck,
portIndex, controller)); portIndex, controller));
resolve = true;
} }
// add/modify streams // add/modify streams
@ -1214,6 +1219,26 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
serviceStub->modifyStream(controller, streamConfigList, ack, serviceStub->modifyStream(controller, streamConfigList, ack,
NewCallback(this, &PortGroup::processModifyStreamAck, NewCallback(this, &PortGroup::processModifyStreamAck,
portIndex, controller)); portIndex, controller));
resolve = true;
}
// XXX: Ideally resolve and build should be called after **all**
// ports and portgroups are configured. As of now, any resolve
// replied to by ports/portgroups configured later in the open
// session sequence will fail.
// However, to do that, we may need to rethink the open session
// implementation - so going with this for now
if (resolve)
{
OstProto::PortIdList *portIdList = new OstProto::PortIdList;
portIdList->add_port_id()->set_id(portId);
OstProto::Ack *ack = new OstProto::Ack;
controller = new PbRpcController(portIdList, ack);
serviceStub->resolveDeviceNeighbors(controller, portIdList, ack,
NewCallback(this,
&PortGroup::processResolveDeviceNeighborsAck,
controller));
resolve = false;
} }
// build packets using the new config // build packets using the new config

View File

@ -68,6 +68,7 @@ bool PortGroupList::isPort(const QModelIndex& index)
PortGroup& PortGroupList::portGroup(const QModelIndex& index) PortGroup& PortGroupList::portGroup(const QModelIndex& index)
{ {
Q_ASSERT(index.isValid());
Q_ASSERT(mPortGroupListModel.isPortGroup(index)); Q_ASSERT(mPortGroupListModel.isPortGroup(index));
return *(mPortGroups[index.row()]); return *(mPortGroups[index.row()]);
@ -75,6 +76,8 @@ PortGroup& PortGroupList::portGroup(const QModelIndex& index)
Port& PortGroupList::port(const QModelIndex& index) Port& PortGroupList::port(const QModelIndex& index)
{ {
Q_ASSERT(index.isValid());
Q_ASSERT(index.parent().isValid());
Q_ASSERT(mPortGroupListModel.isPort(index)); Q_ASSERT(mPortGroupListModel.isPort(index));
return (*mPortGroups.at(index.parent().row())->mPorts[index.row()]); return (*mPortGroups.at(index.parent().row())->mPorts[index.row()]);
} }
@ -97,6 +100,9 @@ void PortGroupList::addPortGroup(PortGroup &portGroup)
connect(&portGroup, SIGNAL(portListChanged(quint32)), connect(&portGroup, SIGNAL(portListChanged(quint32)),
&mPortStatsModel, SLOT(when_portListChanged())); &mPortStatsModel, SLOT(when_portListChanged()));
connect(&portGroup, SIGNAL(portGroupDataChanged(int, int)),
&mPortStatsModel, SLOT(when_portGroupDataChanged(int, int)));
connect(&portGroup, SIGNAL(statsChanged(quint32)), connect(&portGroup, SIGNAL(statsChanged(quint32)),
&mPortStatsModel, SLOT(when_portGroup_stats_update(quint32))); &mPortStatsModel, SLOT(when_portGroup_stats_update(quint32)));
@ -110,8 +116,10 @@ void PortGroupList::addPortGroup(PortGroup &portGroup)
void PortGroupList::removePortGroup(PortGroup &portGroup) void PortGroupList::removePortGroup(PortGroup &portGroup)
{ {
mPortGroupListModel.portGroupAboutToBeRemoved(&portGroup); // Disconnect before removing from list
portGroup.disconnectFromHost();
mPortGroupListModel.portGroupAboutToBeRemoved(&portGroup);
PortGroup* pg = mPortGroups.takeAt(mPortGroups.indexOf(&portGroup)); PortGroup* pg = mPortGroups.takeAt(mPortGroups.indexOf(&portGroup));
qDebug("after takeAt()"); qDebug("after takeAt()");
mPortGroupListModel.portGroupRemoved(); mPortGroupListModel.portGroupRemoved();
@ -128,11 +136,12 @@ void PortGroupList::removeAllPortGroups()
do { do {
PortGroup *pg = mPortGroups.at(0); PortGroup *pg = mPortGroups.at(0);
pg->disconnectFromHost();
mPortGroupListModel.portGroupAboutToBeRemoved(pg); mPortGroupListModel.portGroupAboutToBeRemoved(pg);
mPortGroups.removeFirst(); mPortGroups.removeFirst();
delete pg; delete pg;
mPortGroupListModel.portGroupRemoved();
} while (!mPortGroups.isEmpty()); } while (!mPortGroups.isEmpty());
mPortGroupListModel.portGroupRemoved();
mPortGroupListModel.when_portListChanged(); mPortGroupListModel.when_portListChanged();
mPortStatsModel.when_portListChanged(); mPortStatsModel.when_portListChanged();

View File

@ -49,7 +49,8 @@ QList<uint> PortStatsFilterDialog::getItemList(bool* ok,
{ {
QStandardItem *item; QStandardItem *item;
item = new QStandardItem(model->headerData(i, orientation).toString()); item = new QStandardItem(model->headerData(i, orientation)
.toString().replace('\n', ' '));
item->setData(i, kLogicalIndex); item->setData(i, kLogicalIndex);
item->setData(initial.indexOf(i), kVisualIndex); item->setData(initial.indexOf(i), kVisualIndex);
item->setFlags(Qt::ItemIsSelectable item->setFlags(Qt::ItemIsSelectable

View File

@ -20,7 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portstatsmodel.h" #include "portstatsmodel.h"
#include "portgrouplist.h" #include "portgrouplist.h"
#include <QApplication>
#include <QPainter> #include <QPainter>
#include <QPalette>
#include <QPixmapCache> #include <QPixmapCache>
#include <QTimer> #include <QTimer>
@ -135,7 +137,10 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const
// States // States
case e_COMBO_STATE: case e_COMBO_STATE:
return QVariant(); return QString("Link %1%2%3")
.arg(LinkStateName.at(stats.state().link_state()))
.arg(stats.state().is_transmit_on() ? ";Tx On" : "")
.arg(stats.state().is_capture_on() ? ";Cap On" : "");
// Statistics // Statistics
case e_STAT_FRAMES_RCVD: case e_STAT_FRAMES_RCVD:
@ -156,11 +161,13 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const
case e_STAT_BYTES_SENT: case e_STAT_BYTES_SENT:
return QString("%L1").arg(quint64(stats.tx_bytes())); return QString("%L1").arg(quint64(stats.tx_bytes()));
#if 0
case e_STAT_BYTE_SEND_RATE: case e_STAT_BYTE_SEND_RATE:
return QString("%L1").arg(quint64(stats.tx_bps())); return QString("%L1").arg(quint64(stats.tx_bps()));
case e_STAT_BYTE_RECV_RATE: case e_STAT_BYTE_RECV_RATE:
return QString("%L1").arg(quint64(stats.rx_bps())); return QString("%L1").arg(quint64(stats.rx_bps()));
#endif
case e_STAT_BIT_SEND_RATE: case e_STAT_BIT_SEND_RATE:
return QString("%L1").arg(quint64( return QString("%L1").arg(quint64(
@ -270,6 +277,14 @@ QVariant PortStatsModel::headerData(int section, Qt::Orientation orientation, in
return QVariant(); return QVariant();
} }
if ((role == Qt::BackgroundRole) && (orientation == Qt::Vertical)
&& qApp->styleSheet().isEmpty())
{
QPalette palette = QApplication::palette();
return section & 0x1 ?
palette.alternateBase() : palette.base();
}
if (role != Qt::DisplayRole) if (role != Qt::DisplayRole)
return QVariant(); return QVariant();
@ -280,16 +295,23 @@ QVariant PortStatsModel::headerData(int section, Qt::Orientation orientation, in
if (numPorts.isEmpty() || section >= numPorts.last()) if (numPorts.isEmpty() || section >= numPorts.last())
return QVariant(); return QVariant();
getDomainIndexes(index(0, section), portGroupIdx, portIdx); getDomainIndexes(index(0, section), portGroupIdx, portIdx);
PortGroup *portGroup = pgl->mPortGroups.at(portGroupIdx);
Port *port = portGroup->mPorts.at(portIdx);
portName = QString("Port %1-%2") portName = QString("Port %1-%2")
.arg(pgl->mPortGroups.at(portGroupIdx)->id()) .arg(portGroup->id())
.arg(pgl->mPortGroups.at(portGroupIdx)->mPorts.at(portIdx)->id()); .arg(port->id());
if (portGroupIdx < (uint) pgl->mPortGroups.size() if (portGroupIdx < (uint) pgl->mPortGroups.size()
&& portIdx < (uint) pgl->mPortGroups.at(portGroupIdx)->mPorts.size()) && portIdx < (uint) portGroup->mPorts.size())
{ {
if (!pgl->mPortGroups.at(portGroupIdx)->mPorts[portIdx]->notes() if (!port->notes().isEmpty())
.isEmpty())
portName += " *"; portName += " *";
portName += "\n";
portName += port->userDescription().isEmpty() ?
port->userAlias() : port->userDescription();
} }
return portName; return portName;
} }
@ -363,6 +385,16 @@ void PortStatsModel::when_portListChanged()
endResetModel(); endResetModel();
} }
void PortStatsModel::when_portGroupDataChanged(int /*portGroupId*/, int /*portId*/)
{
if (!columnCount())
return;
// Port (user) description may have changed - update column headers
// TODO: update only the changed ports, not all
emit headerDataChanged(Qt::Horizontal, 0, columnCount()-1);
}
// FIXME: unused? if used, the index calculation row/column needs to be swapped // FIXME: unused? if used, the index calculation row/column needs to be swapped
#if 0 #if 0
void PortStatsModel::on_portStatsUpdate(int port, void* /*stats*/) void PortStatsModel::on_portStatsUpdate(int port, void* /*stats*/)

View File

@ -26,15 +26,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
class QTimer; class QTimer;
typedef enum { typedef enum {
// Info
e_INFO_START = 0,
e_INFO_USER = e_INFO_START,
e_INFO_END = e_INFO_USER,
// State // State
e_STATE_START, e_STATE_START = 0,
e_COMBO_STATE = e_STATE_START, e_COMBO_STATE = e_STATE_START,
@ -43,14 +36,16 @@ typedef enum {
// Statistics // Statistics
e_STATISTICS_START, e_STATISTICS_START,
e_STAT_FRAMES_RCVD = e_STATISTICS_START, e_STAT_FRAMES_SENT = e_STATISTICS_START,
e_STAT_FRAMES_SENT, e_STAT_FRAMES_RCVD,
e_STAT_BYTES_SENT,
e_STAT_BYTES_RCVD,
e_STAT_FRAME_SEND_RATE, e_STAT_FRAME_SEND_RATE,
e_STAT_FRAME_RECV_RATE, e_STAT_FRAME_RECV_RATE,
e_STAT_BYTES_RCVD, #if 0
e_STAT_BYTES_SENT,
e_STAT_BYTE_SEND_RATE, e_STAT_BYTE_SEND_RATE,
e_STAT_BYTE_RECV_RATE, e_STAT_BYTE_RECV_RATE,
#endif
e_STAT_BIT_SEND_RATE, e_STAT_BIT_SEND_RATE,
e_STAT_BIT_RECV_RATE, e_STAT_BIT_RECV_RATE,
#if 0 #if 0
@ -69,24 +64,34 @@ typedef enum {
e_STATISTICS_END = e_STAT_RX_FRAME_ERRORS, e_STATISTICS_END = e_STAT_RX_FRAME_ERRORS,
// Info
e_INFO_START,
// XXX: keep hidden rows at end to avoid having to recalculate rows
e_INFO_USER = e_INFO_START,
e_INFO_END = e_INFO_USER,
e_STAT_MAX e_STAT_MAX
} PortStat; } PortStat;
static QStringList PortStatName = (QStringList() static const QStringList PortStatName = (QStringList()
<< "User"
<< "Status" << "Status"
<< "Frames Received" << "Sent Frames"
<< "Frames Sent" << "Received Frames"
<< "Frame Send Rate (fps)" << "Sent Bytes"
<< "Frame Receive Rate (fps)" << "Received Bytes"
<< "Bytes Received"
<< "Bytes Sent" << "Send Frame Rate (fps)"
<< "Byte Send Rate (Bps)" << "Receive Frame Rate (fps)"
<< "Byte Receive Rate (Bps)" #if 0
<< "Bit Send Rate (bps)" << "Send Byte Rate (Bps)"
<< "Bit Receive Rate (bps)" << "Receive Byte Rate (Bps)"
#endif
<< "Send Bit Rate (bps)"
<< "Receive Bit Rate (bps)"
#if 0 #if 0
<< "Frames Received (NIC)" << "Frames Received (NIC)"
<< "Frames Sent (NIC)" << "Frames Sent (NIC)"
@ -98,6 +103,8 @@ static QStringList PortStatName = (QStringList()
<< "Receive Errors" << "Receive Errors"
<< "Receive Fifo Errors" << "Receive Fifo Errors"
<< "Receive Frame Errors" << "Receive Frame Errors"
<< "User"
); );
static QStringList LinkStateName = (QStringList() static QStringList LinkStateName = (QStringList()
@ -136,6 +143,7 @@ class PortStatsModel : public QAbstractTableModel
public slots: public slots:
void when_portListChanged(); void when_portListChanged();
//void on_portStatsUpdate(int port, void*stats); //void on_portStatsUpdate(int port, void*stats);
void when_portGroupDataChanged(int portGroupId, int portId);
void when_portGroup_stats_update(quint32 portGroupId); void when_portGroup_stats_update(quint32 portGroupId);
private slots: private slots:

View File

@ -26,8 +26,8 @@ class PortStatsProxyModel : public QSortFilterProxyModel
{ {
Q_OBJECT Q_OBJECT
public: public:
PortStatsProxyModel(QObject *parent = 0) PortStatsProxyModel(int userRow, QObject *parent = 0)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent), userRow_(userRow)
{ {
setFilterRegExp(QRegExp(".*")); setFilterRegExp(QRegExp(".*"));
} }
@ -36,7 +36,7 @@ protected:
bool filterAcceptsColumn(int sourceColumn, bool filterAcceptsColumn(int sourceColumn,
const QModelIndex &sourceParent) const const QModelIndex &sourceParent) const
{ {
QModelIndex index = sourceModel()->index(0, sourceColumn, sourceParent); QModelIndex index = sourceModel()->index(userRow_, sourceColumn,sourceParent);
QString user = sourceModel()->data(index).toString(); QString user = sourceModel()->data(index).toString();
return filterRegExp().exactMatch(user) ? true : false; return filterRegExp().exactMatch(user) ? true : false;
@ -44,9 +44,10 @@ protected:
bool filterAcceptsRow(int sourceRow, bool filterAcceptsRow(int sourceRow,
const QModelIndex &/*sourceParent*/) const const QModelIndex &/*sourceParent*/) const
{ {
// Hide row 0 - username (needed only by this filter class) return sourceRow == userRow_ ? false : true;
return (sourceRow > 0) ? true : false;
} }
private:
int userRow_;
}; };
#endif #endif

View File

@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portstatsfilterdialog.h" #include "portstatsfilterdialog.h"
#include "portstatsmodel.h" #include "portstatsmodel.h"
#include "portstatsproxymodel.h" #include "portstatsproxymodel.h"
#include "rowborderdelegate.h"
#include "streamstatsmodel.h" #include "streamstatsmodel.h"
#include "streamstatswindow.h" #include "streamstatswindow.h"
#include "settings.h" #include "settings.h"
@ -42,7 +43,8 @@ PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
this->pgl = pgl; this->pgl = pgl;
model = pgl->getPortStatsModel(); model = pgl->getPortStatsModel();
proxyStatsModel = new PortStatsProxyModel(this); // Hide 'user' row
proxyStatsModel = new PortStatsProxyModel(e_INFO_USER, this);
if (proxyStatsModel) { if (proxyStatsModel) {
proxyStatsModel->setSourceModel(model); proxyStatsModel->setSourceModel(model);
tvPortStats->setModel(proxyStatsModel); tvPortStats->setModel(proxyStatsModel);
@ -55,6 +57,17 @@ PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
tvPortStats->verticalHeader()->setDefaultSectionSize( tvPortStats->verticalHeader()->setDefaultSectionSize(
tvPortStats->verticalHeader()->minimumSectionSize()); tvPortStats->verticalHeader()->minimumSectionSize());
// XXX: Set Delegates for port stats view
// RowBorderDelegate: Group related stats using a horizontal line
// IconOnlyDelegate : For status, show only icons not icons+text
tvPortStats->setItemDelegate(
new RowBorderDelegate(
QSet<int>({
e_STAT_FRAMES_SENT,
e_STAT_FRAME_SEND_RATE,
e_STAT_RX_DROPS}),
this));
statusDelegate = new IconOnlyDelegate(this); statusDelegate = new IconOnlyDelegate(this);
#if 0 #if 0
// XXX: Ideally we should use this, but it doesn't work because in // XXX: Ideally we should use this, but it doesn't work because in
@ -68,9 +81,7 @@ PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
statusDelegate); statusDelegate);
#else #else
// ... so we use this hard-coded hack // ... so we use this hard-coded hack
tvPortStats->setItemDelegateForRow( tvPortStats->setItemDelegateForRow(e_COMBO_STATE, statusDelegate);
proxyStatsModel ? e_COMBO_STATE-1 : e_COMBO_STATE,
statusDelegate);
#endif #endif
connect(tvPortStats->selectionModel(), connect(tvPortStats->selectionModel(),

View File

@ -165,7 +165,7 @@
<string>Stop Capture</string> <string>Stop Capture</string>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>End capture on selecteed port(s)</string> <string>End capture on selected port(s)</string>
</property> </property>
<property name="text"> <property name="text">
<string>Stop</string> <string>Stop</string>

View File

@ -97,14 +97,19 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
connect(plm->getPortModel(), SIGNAL(modelReset()), connect(plm->getPortModel(), SIGNAL(modelReset()),
SLOT(when_portModel_reset())); SLOT(when_portModel_reset()));
connect( tvPortList->selectionModel(), connect(actionPort_Configuration, SIGNAL(triggered()),
SLOT(when_actionPort_Configuration_triggered()));
connect(tvPortList, SIGNAL(activated(const QModelIndex&)),
SLOT(when_actionPort_Configuration_triggered(const QModelIndex&)));
connect(tvPortList->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(when_portView_currentChanged(const QModelIndex&, this, SLOT(when_portView_currentChanged(const QModelIndex&,
const QModelIndex&))); const QModelIndex&)));
connect(this, connect(this,
SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)), SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)),
portWidget, SLOT(setCurrentPortIndex(const QModelIndex&))); portWidget,
SLOT(setCurrentPortIndex(const QModelIndex&, const QModelIndex&)));
connect(this, connect(this,
SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)), SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)),
streamsWidget, SLOT(setCurrentPortIndex(const QModelIndex&))); streamsWidget, SLOT(setCurrentPortIndex(const QModelIndex&)));
@ -233,6 +238,21 @@ bool PortsWindow::saveSession(
return true; return true;
} }
QList<QAction*> PortsWindow::portActions()
{
return tvPortList->actions();
}
QList<QAction*> PortsWindow::streamActions()
{
return streamsWidget->actions();
}
QList<QAction*> PortsWindow::deviceActions()
{
return devicesWidget->actions();
}
void PortsWindow::clearCurrentSelection() void PortsWindow::clearCurrentSelection()
{ {
tvPortList->selectionModel()->clearCurrentIndex(); tvPortList->selectionModel()->clearCurrentIndex();
@ -582,9 +602,11 @@ void PortsWindow::on_actionExclusive_Control_triggered(bool checked)
} }
} }
void PortsWindow::on_actionPort_Configuration_triggered() void PortsWindow::when_actionPort_Configuration_triggered(
const QModelIndex &portIndex)
{ {
QModelIndex current = tvPortList->selectionModel()->currentIndex(); QModelIndex current = portIndex.isValid() ?
portIndex : tvPortList->selectionModel()->currentIndex();
if (proxyPortModel) if (proxyPortModel)
current = proxyPortModel->mapToSource(current); current = proxyPortModel->mapToSource(current);
@ -599,6 +621,8 @@ void PortsWindow::on_actionPort_Configuration_triggered()
// TODO: extend Port::protoDataCopyInto() to accept an optional param // TODO: extend Port::protoDataCopyInto() to accept an optional param
// which says copy only modifiable fields // which says copy only modifiable fields
//plm->port(current).protoDataCopyInto(&config); //plm->port(current).protoDataCopyInto(&config);
config.set_description(port.systemDescription().toStdString());
config.set_user_description(port.userDescription().toStdString());
config.set_transmit_mode(port.transmitMode()); config.set_transmit_mode(port.transmitMode());
config.set_is_tracking_stream_stats(port.trackStreamStats()); config.set_is_tracking_stream_stats(port.trackStreamStats());
config.set_is_exclusive_control(port.hasExclusiveControl()); config.set_is_exclusive_control(port.hasExclusiveControl());

View File

@ -50,6 +50,10 @@ public:
QString &error, QString &error,
QProgressDialog *progress = NULL); QProgressDialog *progress = NULL);
QList<QAction*> portActions();
QList<QAction*> streamActions();
QList<QAction*> deviceActions();
signals: signals:
void currentPortChanged(const QModelIndex &current, void currentPortChanged(const QModelIndex &current,
const QModelIndex &previous); const QModelIndex &previous);
@ -76,7 +80,8 @@ private slots:
void on_actionDisconnect_Port_Group_triggered(); void on_actionDisconnect_Port_Group_triggered();
void on_actionExclusive_Control_triggered(bool checked); void on_actionExclusive_Control_triggered(bool checked);
void on_actionPort_Configuration_triggered(); void when_actionPort_Configuration_triggered(
const QModelIndex &portIndex = QModelIndex());
private: private:
PortGroupList *plm; PortGroupList *plm;

View File

@ -39,7 +39,7 @@
<widget class="QStackedWidget" name="swDetail"> <widget class="QStackedWidget" name="swDetail">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch> <horstretch>1</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>

View File

@ -48,30 +48,34 @@ PortWidget::~PortWidget()
{ {
} }
void PortWidget::setCurrentPortIndex(const QModelIndex &portIndex) void PortWidget::setCurrentPortIndex(const QModelIndex &currentIndex,
const QModelIndex &previousIndex)
{ {
if (!plm) if (!plm)
return; return;
// XXX: We assume portIndex corresponds to sourceModel, not proxyModel qDebug("In %s", __PRETTY_FUNCTION__);
if (!plm->isPort(portIndex))
return;
qDebug("In %s", __FUNCTION__); // XXX: We assume indices corresponds to sourceModel, not proxyModel
// - caller/sender should ensure this
// Disconnect previous port // Disconnect previous port
if (plm->isPort(currentPortIndex_)) if (plm->isPort(previousIndex))
disconnect(&(plm->port(currentPortIndex_)), disconnect(&(plm->port(previousIndex)),
SIGNAL(portRateChanged(int, int)), SIGNAL(portRateChanged(int, int)),
this, SLOT(updatePortRates())); this, SLOT(updatePortRates()));
currentPortIndex_ = portIndex; if (!plm->isPort(currentIndex)) {
currentPortIndex_ = QModelIndex(); // set to invalid
return;
}
currentPortIndex_ = currentIndex;
// Connect current port // Connect current port
if (plm->isPort(currentPortIndex_)) connect(&(plm->port(currentPortIndex_)),
connect(&(plm->port(currentPortIndex_)), SIGNAL(portRateChanged(int, int)),
SIGNAL(portRateChanged(int, int)), this, SLOT(updatePortRates()));
this, SLOT(updatePortRates()));
double speed = plm->port(currentPortIndex_).speed(); double speed = plm->port(currentPortIndex_).speed();
portSpeed->setText(QString("Max %L1 Mbps").arg(speed)); portSpeed->setText(QString("Max %L1 Mbps").arg(speed));

View File

@ -37,7 +37,8 @@ public:
void setPortGroupList(PortGroupList *portGroups); void setPortGroupList(PortGroupList *portGroups);
public slots: public slots:
void setCurrentPortIndex(const QModelIndex &portIndex); void setCurrentPortIndex(const QModelIndex &currentIndex,
const QModelIndex &previousIndex);
private slots: private slots:

View File

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "../common/ostprotolib.h" #include "../common/ostprotolib.h"
#include "settings.h" #include "settings.h"
#include "thememanager.h"
#include <QFileDialog> #include <QFileDialog>
#include <QtGlobal> #include <QtGlobal>
@ -40,6 +41,7 @@ Preferences::Preferences()
setupUi(this); setupUi(this);
// Program paths
wiresharkPathEdit->setText(appSettings->value(kWiresharkPathKey, wiresharkPathEdit->setText(appSettings->value(kWiresharkPathKey,
kWiresharkPathDefaultValue).toString()); kWiresharkPathDefaultValue).toString());
tsharkPathEdit->setText(appSettings->value(kTsharkPathKey, tsharkPathEdit->setText(appSettings->value(kTsharkPathKey,
@ -51,6 +53,10 @@ Preferences::Preferences()
awkPathEdit->setText(appSettings->value(kAwkPathKey, awkPathEdit->setText(appSettings->value(kAwkPathKey,
kAwkPathDefaultValue).toString()); kAwkPathDefaultValue).toString());
// Theme
theme->addItems(ThemeManager::instance()->themeList());
theme->setCurrentText(appSettings->value(kThemeKey).toString());
// TODO(only if required): kUserKey // TODO(only if required): kUserKey
} }
@ -78,6 +84,7 @@ void Preferences::initDefaults()
void Preferences::accept() void Preferences::accept()
{ {
// Program paths
appSettings->setValue(kWiresharkPathKey, wiresharkPathEdit->text()); appSettings->setValue(kWiresharkPathKey, wiresharkPathEdit->text());
appSettings->setValue(kTsharkPathKey, tsharkPathEdit->text()); appSettings->setValue(kTsharkPathKey, tsharkPathEdit->text());
appSettings->setValue(kGzipPathKey, gzipPathEdit->text()); appSettings->setValue(kGzipPathKey, gzipPathEdit->text());
@ -90,6 +97,9 @@ void Preferences::accept()
appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(), appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(),
appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString());
// Theme
ThemeManager::instance()->setTheme(theme->currentText());
QDialog::accept(); QDialog::accept();
} }

View File

@ -1,7 +1,8 @@
<ui version="4.0" > <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Preferences</class> <class>Preferences</class>
<widget class="QDialog" name="Preferences" > <widget class="QDialog" name="Preferences">
<property name="geometry" > <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
@ -9,148 +10,149 @@
<height>220</height> <height>220</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle">
<string>Preferences</string> <string>Preferences</string>
</property> </property>
<property name="windowIcon" > <property name="windowIcon">
<iconset resource="ostinato.qrc" >:/icons/preferences.png</iconset> <iconset resource="ostinato.qrc">
<normaloff>:/icons/preferences.png</normaloff>:/icons/preferences.png</iconset>
</property> </property>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout">
<item> <item>
<widget class="QFrame" name="frame" > <widget class="QFrame" name="frame">
<property name="frameShape" > <property name="frameShape">
<enum>QFrame::Box</enum> <enum>QFrame::Box</enum>
</property> </property>
<property name="frameShadow" > <property name="frameShadow">
<enum>QFrame::Sunken</enum> <enum>QFrame::Sunken</enum>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout">
<item row="0" column="0" > <item row="0" column="0">
<widget class="QLabel" name="label" > <widget class="QLabel" name="label">
<property name="text" > <property name="text">
<string>'wireshark' Path</string> <string>'wireshark' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>wiresharkPathEdit</cstring> <cstring>wiresharkPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" > <item row="0" column="1">
<widget class="QLineEdit" name="wiresharkPathEdit" > <widget class="QLineEdit" name="wiresharkPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2" > <item row="0" column="2">
<widget class="QToolButton" name="wiresharkPathButton" > <widget class="QToolButton" name="wiresharkPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" > <item row="1" column="0">
<widget class="QLabel" name="label_2" > <widget class="QLabel" name="label_2">
<property name="text" > <property name="text">
<string>'tshark' Path</string> <string>'tshark' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>tsharkPathEdit</cstring> <cstring>tsharkPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" > <item row="1" column="1">
<widget class="QLineEdit" name="tsharkPathEdit" > <widget class="QLineEdit" name="tsharkPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2" > <item row="1" column="2">
<widget class="QToolButton" name="tsharkPathButton" > <widget class="QToolButton" name="tsharkPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" > <item row="2" column="0">
<widget class="QLabel" name="label_5" > <widget class="QLabel" name="label_5">
<property name="text" > <property name="text">
<string>'gzip' Path</string> <string>'gzip' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>diffPathEdit</cstring> <cstring>diffPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" > <item row="2" column="1">
<widget class="QLineEdit" name="gzipPathEdit" > <widget class="QLineEdit" name="gzipPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="2" > <item row="2" column="2">
<widget class="QToolButton" name="gzipPathButton" > <widget class="QToolButton" name="gzipPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" > <item row="3" column="0">
<widget class="QLabel" name="label_3" > <widget class="QLabel" name="label_3">
<property name="text" > <property name="text">
<string>'diff' Path</string> <string>'diff' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>diffPathEdit</cstring> <cstring>diffPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" > <item row="3" column="1">
<widget class="QLineEdit" name="diffPathEdit" > <widget class="QLineEdit" name="diffPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="2" > <item row="3" column="2">
<widget class="QToolButton" name="diffPathButton" > <widget class="QToolButton" name="diffPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" > <item row="4" column="0">
<widget class="QLabel" name="label_4" > <widget class="QLabel" name="label_4">
<property name="text" > <property name="text">
<string>'awk' Path</string> <string>'awk' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>awkPathEdit</cstring> <cstring>awkPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1" > <item row="4" column="1">
<widget class="QLineEdit" name="awkPathEdit" > <widget class="QLineEdit" name="awkPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="2" > <item row="4" column="2">
<widget class="QToolButton" name="awkPathButton" > <widget class="QToolButton" name="awkPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1" > <item row="5" column="1">
<spacer> <spacer>
<property name="orientation" > <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
<property name="sizeHint" > <property name="sizeHint" stdset="0">
<size> <size>
<width>21</width> <width>21</width>
<height>61</height> <height>61</height>
@ -162,12 +164,26 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox" > <layout class="QHBoxLayout" name="horizontalLayout">
<property name="orientation" > <item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Theme (Experimental)</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="theme"/>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons" > <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -187,7 +203,7 @@
<tabstop>buttonBox</tabstop> <tabstop>buttonBox</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="ostinato.qrc" /> <include location="ostinato.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>
@ -196,11 +212,11 @@
<receiver>Preferences</receiver> <receiver>Preferences</receiver>
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>248</x> <x>248</x>
<y>254</y> <y>254</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>157</x> <x>157</x>
<y>274</y> <y>274</y>
</hint> </hint>
@ -212,11 +228,11 @@
<receiver>Preferences</receiver> <receiver>Preferences</receiver>
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>316</x> <x>316</x>
<y>260</y> <y>260</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>286</x> <x>286</x>
<y>274</y> <y>274</y>
</hint> </hint>

View File

@ -0,0 +1,50 @@
/*
Copyright (C) 2023 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _ROW_BORDER_DELEGATE
#define _ROW_BORDER_DELEGATE
#include <QStyledItemDelegate>
#include <QSet>
class RowBorderDelegate : public QStyledItemDelegate
{
public:
RowBorderDelegate(QSet<int> rows, QObject *parent = nullptr)
: QStyledItemDelegate(parent), rows_(rows)
{
}
private:
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
if (rows_.contains(index.row())) {
const QRect rect(option.rect);
painter->drawLine(rect.topLeft(), rect.topRight());
}
}
QSet<int> rows_;
};
#endif

View File

@ -74,6 +74,8 @@ const QString kAwkPathDefaultValue("/usr/bin/awk");
const QString kAwkPathDefaultValue("/usr/bin/awk"); const QString kAwkPathDefaultValue("/usr/bin/awk");
#endif #endif
const QString kThemeKey("Theme");
const QString kUserKey("User"); const QString kUserKey("User");
extern QString kUserDefaultValue; extern QString kUserDefaultValue;

View File

@ -675,6 +675,19 @@ QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-colo
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page_2"> <widget class="QWidget" name="page_2">

View File

@ -361,15 +361,13 @@ void StreamModel::setCurrentPortIndex(const QModelIndex &current)
else else
{ {
qDebug("change to valid port"); qDebug("change to valid port");
// Disconnect any existing connection to avoid duplication
// Qt 4.6 has Qt::UniqueConnection, but we want to remain compatible
// with earlier Qt versions
if (mCurrentPort) if (mCurrentPort)
{ {
disconnect(mCurrentPort, SIGNAL(streamListChanged(int, int)), disconnect(mCurrentPort, SIGNAL(streamListChanged(int, int)),
this, SLOT(when_mCurrentPort_streamListChanged(int, int))); this, SLOT(when_mCurrentPort_streamListChanged(int, int)));
} }
quint16 pg = current.internalId() >> 16; quint16 pg = current.internalId() >> 16;
// TODO: make mCurrentPort a smart weak pointer
mCurrentPort = pgl->mPortGroups[pgl->indexOfPortGroup(pg)]->mPorts[current.row()]; mCurrentPort = pgl->mPortGroups[pgl->indexOfPortGroup(pg)]->mPorts[current.row()];
connect(mCurrentPort, SIGNAL(streamListChanged(int, int)), connect(mCurrentPort, SIGNAL(streamListChanged(int, int)),
this, SLOT(when_mCurrentPort_streamListChanged(int, int))); this, SLOT(when_mCurrentPort_streamListChanged(int, int)));

View File

@ -20,8 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "streamstatsmodel.h" #include "streamstatsmodel.h"
#include "protocol.pb.h" #include "protocol.pb.h"
#include "xqlocale.h"
#include <QApplication>
#include <QBrush> #include <QBrush>
#include <QFont>
#include <QPalette>
// XXX: Keep the enum in sync with it's string // XXX: Keep the enum in sync with it's string
enum { enum {
@ -42,12 +46,26 @@ enum {
kAggrTxPkts, kAggrTxPkts,
kAggrRxPkts, kAggrRxPkts,
kAggrPktLoss, kAggrPktLoss,
kTxDuration,
kAvgTxFrameRate,
kAvgRxFrameRate,
kAvgTxBitRate,
kAvgRxBitRate,
kAvgLatency,
kAvgJitter,
kMaxAggrStreamStats kMaxAggrStreamStats
}; };
static QStringList aggrStatTitles = QStringList() static QStringList aggrStatTitles = QStringList()
<< "Total\nTx Pkts" << "Total\nTx Pkts"
<< "Total\nRx Pkts" << "Total\nRx Pkts"
<< "Total\nPkt Loss"; << "Total\nPkt Loss"
<< "Duration\n(secs)"
<< "Avg\nTx PktRate"
<< "Avg\nRx PktRate"
<< "Avg\nTx BitRate"
<< "Avg\nRx BitRate"
<< "Avg\nLatency"
<< "Avg\nJitter";
static const uint kAggrGuid = 0xffffffff; static const uint kAggrGuid = 0xffffffff;
@ -103,20 +121,35 @@ QVariant StreamStatsModel::data(const QModelIndex &index, int role) const
return Qt::AlignRight; return Qt::AlignRight;
int portColumn = index.column() - kMaxAggrStreamStats; int portColumn = index.column() - kMaxAggrStreamStats;
if (role == Qt::BackgroundRole) {
if (portColumn < 0) // Stylesheets typically don't use or set palette colors, so if
return QBrush(QColor("lavender")); // Aggregate Column // using one, don't use palette colors
if (index.row() == (guidList_.size() - 1)) if ((role == Qt::BackgroundRole) && qApp->styleSheet().isEmpty()) {
return QBrush(QColor("burlywood")); // Aggregate Row QPalette palette = QApplication::palette();
else if ((portColumn/kMaxStreamStats) & 1) if (index.row() == (guidList_.size() - 1)) // Aggregate Row
return QBrush(QColor("beige")); // Color alternate Ports return palette.dark();
if (portColumn < 0) // Aggregate Column
return palette.alternateBase();
if ((portColumn/kMaxStreamStats) & 1) // Color alternate Ports
return palette.alternateBase();
} }
Guid guid = guidList_.at(index.row()); Guid guid = guidList_.at(index.row());
if (role == Qt::ForegroundRole) { if ((role == Qt::ForegroundRole && qApp->styleSheet().isEmpty())) {
QPalette palette = QApplication::palette();
if ((index.column() == kAggrPktLoss) if ((index.column() == kAggrPktLoss)
&& aggrGuidStats_.value(guid).pktLoss) && aggrGuidStats_.value(guid).pktLoss)
return QBrush(QColor("firebrick")); return palette.link();
if (index.row() == (guidList_.size() - 1)) // Aggregate Row
return palette.brightText();
}
if (role == Qt::FontRole ) {
if (index.row() == (guidList_.size() - 1)) { // Aggregate Row
QFont font;
font.setBold(true);
return font;
}
} }
if (role != Qt::DisplayRole) if (role != Qt::DisplayRole)
@ -131,6 +164,42 @@ QVariant StreamStatsModel::data(const QModelIndex &index, int role) const
return QString("%L1").arg(aggrGuidStats_.value(guid).txPkts); return QString("%L1").arg(aggrGuidStats_.value(guid).txPkts);
case kAggrPktLoss: case kAggrPktLoss:
return QString("%L1").arg(aggrGuidStats_.value(guid).pktLoss); return QString("%L1").arg(aggrGuidStats_.value(guid).pktLoss);
case kTxDuration:
return QString("%L1").arg(aggrGuidStats_.value(guid).txDuration);
case kAvgTxFrameRate:
return aggrGuidStats_.value(guid).txDuration <= 0 ? QString("-") :
XLocale().toPktRateString(
aggrGuidStats_.value(guid).txPkts
/ aggrGuidStats_.value(guid).txDuration);
case kAvgRxFrameRate:
return aggrGuidStats_.value(guid).txDuration <= 0 ? QString("-") :
XLocale().toPktRateString(
aggrGuidStats_.value(guid).rxPkts
/ aggrGuidStats_.value(guid).txDuration);
case kAvgTxBitRate:
return aggrGuidStats_.value(guid).txDuration <= 0 ? QString("-") :
XLocale().toBitRateString(
(aggrGuidStats_.value(guid).txBytes
+ 24 * aggrGuidStats_.value(guid).txPkts) * 8
/ aggrGuidStats_.value(guid).txDuration);
case kAvgRxBitRate:
return aggrGuidStats_.value(guid).txDuration <= 0 ? QString("-") :
XLocale().toBitRateString(
(aggrGuidStats_.value(guid).rxBytes
+ 24 * aggrGuidStats_.value(guid).rxPkts) * 8
/ aggrGuidStats_.value(guid).txDuration);
case kAvgLatency:
return aggrGuidStats_.value(guid).latencyCount <= 0
|| aggrGuidStats_.value(guid).latencySum <= 0 ? QString("-") :
XLocale().toTimeIntervalString(
aggrGuidStats_.value(guid).latencySum
/ aggrGuidStats_.value(guid).latencyCount);
case kAvgJitter:
return aggrGuidStats_.value(guid).latencyCount <= 0
|| aggrGuidStats_.value(guid).latencySum <= 0 ? QString("-") :
XLocale().toTimeIntervalString(
aggrGuidStats_.value(guid).jitterSum
/ aggrGuidStats_.value(guid).latencyCount);
default: default:
break; break;
}; };
@ -205,6 +274,8 @@ void StreamStatsModel::appendStreamStatsList(
ss.txPkts = s.tx_pkts(); ss.txPkts = s.tx_pkts();
ss.rxBytes = s.rx_bytes(); ss.rxBytes = s.rx_bytes();
ss.txBytes = s.tx_bytes(); ss.txBytes = s.tx_bytes();
ss.rxLatency = s.latency();
ss.rxJitter = s.jitter();
aggrPort.rxPkts += ss.rxPkts; aggrPort.rxPkts += ss.rxPkts;
aggrPort.txPkts += ss.txPkts; aggrPort.txPkts += ss.txPkts;
@ -214,10 +285,28 @@ void StreamStatsModel::appendStreamStatsList(
aggrGuid.rxPkts += ss.rxPkts; aggrGuid.rxPkts += ss.rxPkts;
aggrGuid.txPkts += ss.txPkts; aggrGuid.txPkts += ss.txPkts;
aggrGuid.pktLoss += ss.txPkts - ss.rxPkts; aggrGuid.pktLoss += ss.txPkts - ss.rxPkts;
aggrGuid.rxBytes += ss.rxBytes;
aggrGuid.txBytes += ss.txBytes;
if (s.tx_duration() > aggrGuid.txDuration)
aggrGuid.txDuration = s.tx_duration(); // XXX: use largest or avg?
if (ss.rxLatency) {
aggrGuid.latencySum += ss.rxLatency;
aggrGuid.jitterSum += ss.rxJitter;
aggrGuid.latencyCount++;
}
aggrAggr.rxPkts += ss.rxPkts; aggrAggr.rxPkts += ss.rxPkts;
aggrAggr.txPkts += ss.txPkts; aggrAggr.txPkts += ss.txPkts;
aggrAggr.pktLoss += ss.txPkts - ss.rxPkts; aggrAggr.pktLoss += ss.txPkts - ss.rxPkts;
aggrAggr.rxBytes += ss.rxBytes;
aggrAggr.txBytes += ss.txBytes;
if (aggrGuid.txDuration > aggrAggr.txDuration)
aggrAggr.txDuration = aggrGuid.txDuration;
if (ss.rxLatency) {
aggrAggr.latencySum += ss.rxLatency;
aggrAggr.jitterSum += ss.rxJitter;
aggrAggr.latencyCount++;
}
if (!portList_.contains(pgp)) if (!portList_.contains(pgp))
portList_.append(pgp); portList_.append(pgp);
@ -228,6 +317,8 @@ void StreamStatsModel::appendStreamStatsList(
if (guidList_.size() && !guidList_.contains(kAggrGuid)) if (guidList_.size() && !guidList_.contains(kAggrGuid))
guidList_.append(kAggrGuid); guidList_.append(kAggrGuid);
std::sort(guidList_.begin(), guidList_.end());
#if QT_VERSION >= 0x040600 #if QT_VERSION >= 0x040600
endResetModel(); endResetModel();
#else #else

View File

@ -57,11 +57,19 @@ private:
quint64 txPkts; quint64 txPkts;
quint64 rxBytes; quint64 rxBytes;
quint64 txBytes; quint64 txBytes;
quint64 rxLatency;
quint64 rxJitter;
}; };
struct AggrGuidStats { struct AggrGuidStats {
quint64 rxPkts; quint64 rxPkts;
quint64 txPkts; quint64 txPkts;
quint64 rxBytes;
quint64 txBytes;
qint64 pktLoss; qint64 pktLoss;
double txDuration;
quint64 latencySum;
quint64 jitterSum;
uint latencyCount;
}; };
QList<Guid> guidList_; QList<Guid> guidList_;
QList<PortGroupPort> portList_; QList<PortGroupPort> portList_;

View File

@ -31,7 +31,7 @@ StreamStatsWindow::StreamStatsWindow(QAbstractItemModel *model, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
setupUi(this); setupUi(this);
streamStats->addAction(actionShowByteCounters); streamStats->addAction(actionShowDetails);
if (id) if (id)
setWindowTitle(windowTitle() + QString("(%1)").arg(id)); setWindowTitle(windowTitle() + QString("(%1)").arg(id));
@ -39,13 +39,17 @@ StreamStatsWindow::StreamStatsWindow(QAbstractItemModel *model, QWidget *parent)
count++; count++;
filterModel_ = new StreamStatsFilterModel(this); filterModel_ = new StreamStatsFilterModel(this);
filterModel_->setFilterRegExp(QRegExp(".*Pkt.*")); filterModel_->setFilterRegExp(QRegExp(kDefaultFilter_));
filterModel_->setSourceModel(model); filterModel_->setSourceModel(model);
streamStats->setModel(filterModel_); streamStats->setModel(filterModel_);
streamStats->verticalHeader()->setHighlightSections(false); streamStats->verticalHeader()->setHighlightSections(false);
streamStats->verticalHeader()->setDefaultSectionSize( streamStats->verticalHeader()->setDefaultSectionSize(
streamStats->verticalHeader()->minimumSectionSize()); streamStats->verticalHeader()->minimumSectionSize());
// Fit all columns in window whenever data changes
connect(model, &QAbstractItemModel::modelReset,
[=]() { streamStats->resizeColumnsToContents(); });
} }
StreamStatsWindow::~StreamStatsWindow() StreamStatsWindow::~StreamStatsWindow()
@ -56,10 +60,12 @@ StreamStatsWindow::~StreamStatsWindow()
id = 0; id = 0;
} }
void StreamStatsWindow::on_actionShowByteCounters_triggered(bool checked) void StreamStatsWindow::on_actionShowDetails_triggered(bool checked)
{ {
if (checked) if (checked)
filterModel_->setFilterRegExp(QRegExp(".*")); filterModel_->setFilterRegExp(QRegExp(".*"));
else else
filterModel_->setFilterRegExp(QRegExp(".*Pkt.*")); filterModel_->setFilterRegExp(QRegExp(kDefaultFilter_));
streamStats->resizeColumnsToContents();
} }

View File

@ -33,9 +33,10 @@ public:
~StreamStatsWindow(); ~StreamStatsWindow();
private slots: private slots:
void on_actionShowByteCounters_triggered(bool checked); void on_actionShowDetails_triggered(bool checked);
private: private:
QString kDefaultFilter_{"^(?!Port).*"};
QSortFilterProxyModel *filterModel_; QSortFilterProxyModel *filterModel_;
}; };

View File

@ -19,6 +19,9 @@
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum> <enum>Qt::ActionsContextMenu</enum>
</property> </property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="whatsThis"> <property name="whatsThis">
<string>Oops! We don't seem to have any stream statistics for the requested port(s) <string>Oops! We don't seem to have any stream statistics for the requested port(s)
@ -27,12 +30,12 @@ Wait a little bit to see if they appear, otherwise verify your stream stats conf
</widget> </widget>
</item> </item>
</layout> </layout>
<action name="actionShowByteCounters"> <action name="actionShowDetails">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Show Byte Counters</string> <string>Show Details</string>
</property> </property>
</action> </action>
</widget> </widget>

View File

@ -126,6 +126,8 @@ void StreamsWidget::on_tvStreamList_activated(const QModelIndex & index)
qDebug("stream list activated\n"); qDebug("stream list activated\n");
Q_ASSERT(plm->isPort(currentPortIndex_));
Port &curPort = plm->port(currentPortIndex_); Port &curPort = plm->port(currentPortIndex_);
QList<Stream*> streams; QList<Stream*> streams;
@ -143,11 +145,10 @@ void StreamsWidget::setCurrentPortIndex(const QModelIndex &portIndex)
if (!plm) if (!plm)
return; return;
// XXX: We assume portIndex corresponds to sourceModel, not proxyModel // XXX: We assume portIndex corresponds to sourceModel, not proxyModel;
if (!plm->isPort(portIndex)) // caller should ensure this
return;
qDebug("In %s", __FUNCTION__); qDebug("In %s", __PRETTY_FUNCTION__);
currentPortIndex_ = portIndex; currentPortIndex_ = portIndex;
plm->getStreamModel()->setCurrentPortIndex(portIndex); plm->getStreamModel()->setCurrentPortIndex(portIndex);
@ -223,6 +224,8 @@ void StreamsWidget::on_actionNew_Stream_triggered()
count = selectionModel->selection().at(0).height(); count = selectionModel->selection().at(0).height();
} }
Q_ASSERT(plm->isPort(currentPortIndex_));
Port &curPort = plm->port(currentPortIndex_); Port &curPort = plm->port(currentPortIndex_);
QList<Stream*> streams; QList<Stream*> streams;
@ -243,6 +246,8 @@ void StreamsWidget::on_actionEdit_Stream_triggered()
if (!streamModel->hasSelection()) if (!streamModel->hasSelection())
return; return;
Q_ASSERT(plm->isPort(currentPortIndex_));
Port &curPort = plm->port(currentPortIndex_); Port &curPort = plm->port(currentPortIndex_);
QList<Stream*> streams; QList<Stream*> streams;
@ -262,6 +267,8 @@ void StreamsWidget::on_actionDuplicate_Stream_triggered()
qDebug("Duplicate Stream Action"); qDebug("Duplicate Stream Action");
Q_ASSERT(plm->isPort(currentPortIndex_));
if (model->hasSelection()) if (model->hasSelection())
{ {
bool isOk; bool isOk;
@ -304,6 +311,8 @@ void StreamsWidget::on_actionFind_Replace_triggered()
{ {
qDebug("Find & Replace Action"); qDebug("Find & Replace Action");
Q_ASSERT(plm->isPort(currentPortIndex_));
QItemSelectionModel* selectionModel = tvStreamList->selectionModel(); QItemSelectionModel* selectionModel = tvStreamList->selectionModel();
FindReplaceDialog::Action action; FindReplaceDialog::Action action;

130
client/thememanager.cpp Normal file
View File

@ -0,0 +1,130 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "thememanager.h"
#include "settings.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QRegularExpression>
#include <QResource>
ThemeManager *ThemeManager::instance_{nullptr};
ThemeManager::ThemeManager()
{
themeDir_ = QCoreApplication::applicationDirPath() + "/themes/";
#if defined(Q_OS_MAC)
/*
* Executable and Theme directory location inside app bundle -
* Ostinato.app/Contents/MacOS/
* Ostinato.app/Contents/SharedSupport/themes/
*/
themeDir_.replace("/MacOS/", "/SharedSupport/");
#elif defined(Q_OS_UNIX)
/*
* Possible (but not comprehensive) locations for Ostinato executable
* and theme directory locations
*
* non-install-dir/
* non-install-dir/themes/
*
* /usr/[local]/bin/
* /usr/[local]/share/ostinato-controller/themes/
*
* /opt/ostinato/bin/
* /opt/ostinato/share/themes/
*/
if (themeDir_.contains(QRegularExpression("^/usr/.*/bin/")))
themeDir_.replace("/bin/", "/share/ostinato-controller/");
else if (themeDir_.contains(QRegularExpression("^/opt/.*/bin/")))
themeDir_.replace("/bin/", "/share/");
#endif
qDebug("Themes directory: %s", qPrintable(themeDir_));
}
QStringList ThemeManager::themeList()
{
QDir themeDir(themeDir_);
themeDir.setFilter(QDir::Files);
themeDir.setNameFilters(QStringList() << "*.qss");
themeDir.setSorting(QDir::Name);
QStringList themes = themeDir.entryList();
for (QString& theme : themes)
theme.remove(".qss");
themes.prepend("default");
return themes;
}
void ThemeManager::setTheme(QString theme)
{
// Remove current theme, if we have one
QString oldTheme = appSettings->value(kThemeKey).toString();
if (!oldTheme.isEmpty()) {
// Remove stylesheet first so that there are
// no references to resources when unregistering 'em
qApp->setStyleSheet("");
QString rccFile = themeDir_ + oldTheme + ".rcc";
if (QResource::unregisterResource(rccFile)) {
qDebug("Unable to unregister theme rccFile %s",
qPrintable(rccFile));
}
appSettings->setValue(kThemeKey, QVariant());
}
if (theme.isEmpty() || (theme == "default"))
return;
// Apply new theme
QFile qssFile(themeDir_ + theme + ".qss");
if (!qssFile.open(QFile::ReadOnly)) {
qDebug("Unable to open theme qssFile %s",
qPrintable(qssFile.fileName()));
return;
}
// Register theme resource before applying theme style sheet
QString rccFile = themeDir_ + theme + ".rcc";
if (!QResource::registerResource(rccFile))
qDebug("Unable to register theme rccFile %s", qPrintable(rccFile));
#if 0 // FIXME: debug only
QDirIterator it(":", QDirIterator::Subdirectories);
while (it.hasNext()) {
qDebug() << it.next();
}
#endif
QString styleSheet { qssFile.readAll() };
qApp->setStyleSheet(styleSheet);
appSettings->setValue(kThemeKey, theme);
}
ThemeManager* ThemeManager::instance()
{
if (!instance_)
instance_ = new ThemeManager();
return instance_;
}

42
client/thememanager.h Normal file
View File

@ -0,0 +1,42 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _THEME_MANAGER_H
#define _THEME_MANAGER_H
#include <QStringList>
class ThemeManager
{
public:
ThemeManager();
QStringList themeList();
void setTheme(QString theme);
static ThemeManager* instance();
private:
QString themeDir_;
static ThemeManager *instance_;
};
#endif

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

2213
client/themes/qds-dark.qss Normal file

File diff suppressed because it is too large Load Diff

BIN
client/themes/qds-dark.rcc Normal file

Binary file not shown.

2213
client/themes/qds-light.qss Normal file

File diff suppressed because it is too large Load Diff

BIN
client/themes/qds-light.rcc Normal file

Binary file not shown.

View File

@ -376,6 +376,9 @@ void VariableFieldsWidget::loadProtocolFields(
int byteOfs = bitOfs >> 3; int byteOfs = bitOfs >> 3;
uint bitSize = protocol->fieldData(i, AbstractProtocol::FieldBitSize) uint bitSize = protocol->fieldData(i, AbstractProtocol::FieldBitSize)
.toInt(); .toInt();
if (bitSize == 0)
continue;
vm["offset"] = byteOfs; vm["offset"] = byteOfs;
if (bitSize <= 8) { if (bitSize <= 8) {
vm["type"] = int(OstProto::VariableField::kCounter8); vm["type"] = int(OstProto::VariableField::kCounter8);

View File

@ -85,20 +85,49 @@ public:
return toDouble(text, ok) * multiplier; return toDouble(text, ok) * multiplier;
} }
QString toPktRateString(double pps) const
{
QString text;
if (pps >= 1e6)
return QObject::tr("%L1 Mpps").arg(pps/1e6, 0, 'f', 3);
if (pps >= 1e3)
return QObject::tr("%L1 Kpps").arg(pps/1e3, 0, 'f', 3);
return QObject::tr("%L1").arg(pps, 0, 'f', 3);
}
QString toBitRateString(double bps) const QString toBitRateString(double bps) const
{ {
QString text; QString text;
if (bps >= 1e9) if (bps >= 1e9)
return QObject::tr("%L1 Gbps").arg(bps/1e9, 0, 'f', 4); return QObject::tr("%L1 Gbps").arg(bps/1e9, 0, 'f', 3);
if (bps >= 1e6) if (bps >= 1e6)
return QObject::tr("%L1 Mbps").arg(bps/1e6, 0, 'f', 4); return QObject::tr("%L1 Mbps").arg(bps/1e6, 0, 'f', 3);
if (bps >= 1e3) if (bps >= 1e3)
return QObject::tr("%L1 Kbps").arg(bps/1e3, 0, 'f', 4); return QObject::tr("%L1 Kbps").arg(bps/1e3, 0, 'f', 3);
return QObject::tr("%L1 bps").arg(bps, 0, 'f', 4); return QObject::tr("%L1 bps").arg(bps, 0, 'f', 3);
}
QString toTimeIntervalString(qint64 nanosecs) const
{
QString text;
if (nanosecs >= 1e9)
return QObject::tr("%L1 s").arg(nanosecs/1e9, 0, 'f', 2);
if (nanosecs >= 1e6)
return QObject::tr("%L1 ms").arg(nanosecs/1e6, 0, 'f', 2);
if (nanosecs >= 1e3)
return QObject::tr("%L1 us").arg(nanosecs/1e3, 0, 'f', 2);
return QObject::tr("%L1 ns").arg(nanosecs);
} }
}; };

View File

@ -169,7 +169,8 @@ private:
for (int i = start; i < end; i++) for (int i = start; i < end; i++)
if (indexes.contains(model()->index(indexes.first().row(), i))) if (indexes.contains(model()->index(indexes.first().row(), i)))
text.append(model()->headerData(i, Qt::Horizontal) text.append(model()->headerData(i, Qt::Horizontal)
.toString()+"\t");; .toString().replace('\n', ' ')
+"\t");;
text.append("\n"); text.append("\n");
} }

View File

@ -62,12 +62,14 @@ void ArpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol)
QString ArpProtocol::name() const QString ArpProtocol::name() const
{ {
return QString("Address Resolution Protocol"); return isRarp() ?
QString("Reverse Address Resolution Protocol") :
QString("Address Resolution Protocol");
} }
QString ArpProtocol::shortName() const QString ArpProtocol::shortName() const
{ {
return QString("ARP"); return isRarp() ? QString("RARP") : QString("ARP");
} }
/*! /*!
@ -96,7 +98,7 @@ quint32 ArpProtocol::protocolId(ProtocolIdType type) const
{ {
switch(type) switch(type)
{ {
case ProtocolIdEth: return 0x0806; case ProtocolIdEth: return isRarp() ? 0x8035 : 0x0806;
default:break; default:break;
} }
@ -808,3 +810,11 @@ int ArpProtocol::protocolFrameVariableCount() const
return count; return count;
} }
bool ArpProtocol::isRarp() const
{
if ((data.op_code() == 3)
|| (data.op_code() ==4))
return true;
return false;
}

View File

@ -96,6 +96,8 @@ public:
virtual int protocolFrameVariableCount() const; virtual int protocolFrameVariableCount() const;
private: private:
bool isRarp() const;
OstProto::Arp data; OstProto::Arp data;
}; };

View File

@ -30,6 +30,8 @@ ArpConfigForm::ArpConfigForm(QWidget *parent)
opCodeCombo->setValidator(new QIntValidator(0, 0xFFFF, this)); opCodeCombo->setValidator(new QIntValidator(0, 0xFFFF, this));
opCodeCombo->addItem(1, "ARP Request"); opCodeCombo->addItem(1, "ARP Request");
opCodeCombo->addItem(2, "ARP Reply"); opCodeCombo->addItem(2, "ARP Reply");
opCodeCombo->addItem(3, "Reverse ARP Request");
opCodeCombo->addItem(4, "Reverse ARP Reply");
connect(senderHwAddrMode, SIGNAL(currentIndexChanged(int)), connect(senderHwAddrMode, SIGNAL(currentIndexChanged(int)),
SLOT(on_senderHwAddrMode_currentIndexChanged(int))); SLOT(on_senderHwAddrMode_currentIndexChanged(int)));

View File

@ -25,10 +25,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
template <int protoNumber, class ProtoA, class ProtoB> template <int protoNumber, class ProtoA, class ProtoB>
class ComboProtocol : public AbstractProtocol class ComboProtocol : public AbstractProtocol
{ {
protected:
ProtoA *protoA;
ProtoB *protoB;
public: public:
ComboProtocol(StreamBase *stream, AbstractProtocol *parent = 0) ComboProtocol(StreamBase *stream, AbstractProtocol *parent = 0)
: AbstractProtocol(stream, parent) : AbstractProtocol(stream, parent)
@ -40,6 +36,12 @@ public:
qDebug("%s: protoNumber = %d, %p <--> %p", __FUNCTION__, qDebug("%s: protoNumber = %d, %p <--> %p", __FUNCTION__,
protoNumber, protoA, protoB); protoNumber, protoA, protoB);
if (protoA->protocolNumber() == protoB->protocolNumber())
fieldPrefix = OuterInnerPrefix;
else if (similarProto())
fieldPrefix = ProtoNamePrefix;
} }
virtual ~ComboProtocol() virtual ~ComboProtocol()
@ -125,11 +127,29 @@ public:
int streamIndex = 0) const int streamIndex = 0) const
{ {
int cnt = protoA->fieldCount(); int cnt = protoA->fieldCount();
QVariant value = index < cnt ?
protoA->fieldData(index, attrib, streamIndex) :
protoB->fieldData(index - cnt, attrib, streamIndex);
if (index < cnt) if (attrib == FieldName) {
return protoA->fieldData(index, attrib, streamIndex); switch (fieldPrefix) {
else case OuterInnerPrefix:
return protoB->fieldData(index - cnt, attrib, streamIndex); value = QString("%1 %2")
.arg(index < cnt ? QString("Outer") : QString("Inner"))
.arg(value.toString());
break;
case ProtoNamePrefix:
value = QString("%1 %2")
.arg(index < cnt ? protoA->shortName() : protoB->shortName())
.arg(value.toString());
break;
case NoPrefix:
// Fall-through
break;
}
}
return value;
} }
virtual bool setFieldData(int index, const QVariant &value, virtual bool setFieldData(int index, const QVariant &value,
FieldAttrib attrib = FieldValue) FieldAttrib attrib = FieldValue)
@ -186,6 +206,32 @@ public:
CksumType cksumType = CksumIp) const; CksumType cksumType = CksumIp) const;
#endif #endif
template <int protocolNumber, class FormA, class FormB, class ProtocolA, class ProtocolB> friend class ComboProtocolConfigForm; template <int protocolNumber, class FormA, class FormB, class ProtocolA, class ProtocolB> friend class ComboProtocolConfigForm;
protected:
ProtoA *protoA;
ProtoB *protoB;
private:
bool similarProto()
{
// TODO: Use Levenshtein distance or something similar with > 70% match
// For now we use an ugly hack!
if (protoA->shortName().contains("IPv")
&& protoB->shortName().contains("IPv"))
return true;
if (protoA->shortName().contains("Vlan")
&& protoB->shortName().contains("Vlan"))
return true;
return false;
}
enum FieldNamePrefix {
NoPrefix,
ProtoNamePrefix,
OuterInnerPrefix
};
FieldNamePrefix fieldPrefix{NoPrefix};
}; };
#endif #endif

29
common/debugdefs.h Normal file
View File

@ -0,0 +1,29 @@
/*
Copyright (C) 2023 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _DEBUG_DEFS_H
#define _DEBUG_DEFS_H
#if 0
#define timingDebug(fmt, ...) qDebug("TIMING:" fmt, __VA_ARGS__)
#else
#define timingDebug(...)
#endif
#endif

View File

@ -0,0 +1,47 @@
/*
Copyright (C) 2022 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "fileformatoptions.h"
#include "pcapfileformat.h"
#include "pcapoptionsdialog.h"
#include "streamfileformat.h"
QDialog* FileFormatOptions::openOptionsDialog(StreamFileFormat *fileFormat)
{
if (dynamic_cast<PcapFileFormat*>(fileFormat))
return new PcapImportOptionsDialog(fileFormat->options());
return NULL;
}
QDialog* FileFormatOptions::saveOptionsDialog(StreamFileFormat* /*fileFormat*/)
{
return NULL;
}
QDialog* FileFormatOptions::openOptionsDialog(SessionFileFormat* /*fileFormat*/)
{
return NULL;
}
QDialog* FileFormatOptions::saveOptionsDialog(SessionFileFormat* /*fileFormat*/)
{
return NULL;
}

View File

@ -0,0 +1,41 @@
/*
Copyright (C) 2022 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _FILE_FORMAT_OPTIONS_H
#define _FILE_FORMAT_OPTIONS_H
#include <QObject>
class SessionFileFormat;
class StreamFileFormat;
class QDialog;
class FileFormatOptions : QObject
{
Q_OBJECT
public:
static QDialog* openOptionsDialog(StreamFileFormat *fileFormat);
static QDialog* saveOptionsDialog(StreamFileFormat *fileFormat);
static QDialog* openOptionsDialog(SessionFileFormat *fileFormat);
static QDialog* saveOptionsDialog(SessionFileFormat *fileFormat);
};
#endif

View File

@ -27,6 +27,7 @@ struct FrameValueAttrib
enum ErrorFlag { enum ErrorFlag {
UnresolvedSrcMacError = 0x1, UnresolvedSrcMacError = 0x1,
UnresolvedDstMacError = 0x2, UnresolvedDstMacError = 0x2,
OutOfMemoryError = 0x4,
}; };
Q_DECLARE_FLAGS(ErrorFlags, ErrorFlag); Q_DECLARE_FLAGS(ErrorFlags, ErrorFlag);
ErrorFlags errorFlags{0}; ErrorFlags errorFlags{0};

471
common/gre.cpp Normal file
View File

@ -0,0 +1,471 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "gre.h"
GreProtocol::GreProtocol(StreamBase *stream, AbstractProtocol *parent)
: AbstractProtocol(stream, parent)
{
}
GreProtocol::~GreProtocol()
{
}
AbstractProtocol* GreProtocol::createInstance(StreamBase *stream,
AbstractProtocol *parent)
{
return new GreProtocol(stream, parent);
}
quint32 GreProtocol::protocolNumber() const
{
return OstProto::Protocol::kGreFieldNumber;
}
void GreProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const
{
protocol.MutableExtension(OstProto::gre)->CopyFrom(data);
protocol.mutable_protocol_id()->set_id(protocolNumber());
}
void GreProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol)
{
if (protocol.protocol_id().id() == protocolNumber() &&
protocol.HasExtension(OstProto::gre))
data.MergeFrom(protocol.GetExtension(OstProto::gre));
}
QString GreProtocol::name() const
{
return QString("General Routing Encapsulation Protocol");
}
QString GreProtocol::shortName() const
{
return QString("GRE");
}
AbstractProtocol::ProtocolIdType GreProtocol::protocolIdType() const
{
return ProtocolIdEth;
}
quint32 GreProtocol::protocolId(ProtocolIdType type) const
{
switch(type)
{
case ProtocolIdIp: return 47;
default:break;
}
return AbstractProtocol::protocolId(type);
}
int GreProtocol::fieldCount() const
{
return gre_fieldCount;
}
AbstractProtocol::FieldFlags GreProtocol::fieldFlags(int index) const
{
AbstractProtocol::FieldFlags flags;
flags = AbstractProtocol::fieldFlags(index);
switch (index) {
case gre_checksum:
flags |= CksumField;
break;
case gre_isOverrideChecksum:
flags &= ~FrameField;
flags |= MetaField;
break;
}
return flags;
}
QVariant GreProtocol::fieldData(int index, FieldAttrib attrib,
int streamIndex) const
{
switch (index)
{
case gre_flags:
{
switch(attrib)
{
case FieldName:
return QString("Flags");
case FieldValue:
return data.flags();
case FieldTextValue:
{
QString fstr;
fstr.append("Cksum:");
fstr.append(data.flags() & GRE_FLAG_CKSUM ? "Y" : "N");
fstr.append(" Key:");
fstr.append(data.flags() & GRE_FLAG_KEY ? "Y" : "N");
fstr.append(" Seq:");
fstr.append(data.flags() & GRE_FLAG_SEQ ? "Y" : "N");
return fstr;
}
case FieldFrameValue:
return QByteArray(1, char(data.flags()));
case FieldBitSize:
return 4;
default:
break;
}
break;
}
case gre_rsvd0:
{
switch(attrib)
{
case FieldName:
return QString("Reserved0");
case FieldValue:
return data.rsvd0();
case FieldTextValue:
return QString("%1").arg(data.rsvd0());
case FieldFrameValue:
{
QByteArray fv;
fv.resize(2);
qToBigEndian(quint16(data.rsvd0()), (uchar*)fv.data());
return fv;
}
case FieldBitSize:
return 9;
default:
break;
}
break;
}
case gre_version:
{
switch(attrib)
{
case FieldName:
return QString("Version");
case FieldValue:
return data.version();
case FieldFrameValue:
return QByteArray(1, char(data.version()));
case FieldTextValue:
return QString("%1").arg(data.version());
case FieldBitSize:
return 3;
default:
break;
}
break;
}
case gre_protocol:
{
quint16 protocol = payloadProtocolId(ProtocolIdEth);
switch(attrib)
{
case FieldName:
return QString("Protocol");
case FieldValue:
return protocol;
case FieldFrameValue:
{
QByteArray fv;
fv.resize(2);
qToBigEndian(protocol, (uchar*) fv.data());
return fv;
}
case FieldTextValue:
return QString("0x%1").arg(
protocol, 4, BASE_HEX, QChar('0'));;
default:
break;
}
break;
}
case gre_checksum:
{
if (attrib == FieldName)
return QString("Checksum");
if ((data.flags() & GRE_FLAG_CKSUM) == 0)
{
if (attrib == FieldTextValue)
return QObject::tr("<not-included>");
else
return QVariant();
}
if (attrib == FieldBitSize)
return 16;
quint16 cksum;
if (data.is_override_checksum()) {
cksum = data.checksum();
} else {
quint32 sum = 0;
cksum = protocolFrameCksum(streamIndex, CksumIp);
sum += (quint16) ~cksum;
cksum = protocolFramePayloadCksum(streamIndex, CksumIp);
sum += (quint16) ~cksum;
while (sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
cksum = (~sum) & 0xFFFF;
}
switch(attrib)
{
case FieldValue:
return cksum;
case FieldFrameValue:
{
QByteArray fv;
fv.resize(2);
qToBigEndian(cksum, (uchar*) fv.data());
return fv;
}
case FieldTextValue:
return QString("0x%1").arg(
cksum, 4, BASE_HEX, QChar('0'));;
default:
break;
}
break;
}
case gre_rsvd1:
{
if (attrib == FieldName)
return QString("Reserved1");
if ((data.flags() & GRE_FLAG_CKSUM) == 0)
{
if (attrib == FieldTextValue)
return QObject::tr("<not-included>");
else
return QVariant();
}
switch(attrib)
{
case FieldValue:
return data.rsvd1();
case FieldTextValue:
return QString("%1").arg(data.rsvd1());
case FieldFrameValue:
{
QByteArray fv;
fv.resize(2);
qToBigEndian((quint16) data.rsvd1(), (uchar*) fv.data());
return fv;
}
default:
break;
}
break;
}
case gre_key:
{
if (attrib == FieldName)
return QString("Key");
if ((data.flags() & GRE_FLAG_KEY) == 0)
{
if (attrib == FieldTextValue)
return QObject::tr("<not-included>");
else
return QVariant();
}
switch(attrib)
{
case FieldValue:
return data.key();
case FieldTextValue:
return QString("0x%1").arg(data.key(), 8, BASE_HEX, QChar('0'));
case FieldFrameValue:
{
QByteArray fv;
fv.resize(4);
qToBigEndian((quint32) data.key(), (uchar*) fv.data());
return fv;
}
default:
break;
}
break;
}
case gre_sequence:
{
if (attrib == FieldName)
return QString("Sequence Number");
if ((data.flags() & GRE_FLAG_SEQ) == 0)
{
if (attrib == FieldTextValue)
return QObject::tr("<not-included>");
else
return QVariant();
}
switch(attrib)
{
case FieldValue:
return data.sequence_num();
case FieldTextValue:
return QString("%1").arg(data.sequence_num());
case FieldFrameValue:
{
QByteArray fv;
fv.resize(4);
qToBigEndian((quint32) data.sequence_num(), (uchar*) fv.data());
return fv;
}
default:
break;
}
break;
}
// Meta fields
case gre_isOverrideChecksum:
{
switch(attrib)
{
case FieldValue:
return data.is_override_checksum();
default:
break;
}
break;
}
default:
qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
index);
break;
}
return AbstractProtocol::fieldData(index, attrib, streamIndex);
}
bool GreProtocol::setFieldData(int index, const QVariant &value,
FieldAttrib attrib)
{
bool isOk = false;
if (attrib != FieldValue)
goto _exit;
switch (index)
{
case gre_flags:
{
uint flags = value.toUInt(&isOk);
if (isOk)
data.set_flags(flags);
break;
}
case gre_rsvd0:
{
uint rsvd0 = value.toUInt(&isOk);
if (isOk)
data.set_rsvd0(rsvd0);
break;
}
case gre_version:
{
uint ver = value.toUInt(&isOk);
if (isOk)
data.set_version(ver);
break;
}
case gre_protocol:
{
uint proto = value.toUInt(&isOk);
if (isOk)
data.set_protocol_type(proto);
break;
}
case gre_checksum:
{
uint csum = value.toUInt(&isOk);
if (isOk)
data.set_checksum(csum);
break;
}
case gre_isOverrideChecksum:
{
data.set_is_override_checksum(value.toBool());
break;
}
case gre_rsvd1:
{
uint rsvd1 = value.toUInt(&isOk);
if (isOk)
data.set_rsvd1(rsvd1);
break;
}
case gre_key:
{
uint key = value.toUInt(&isOk);
if (isOk)
data.set_key(key);
break;
}
case gre_sequence:
{
uint seq = value.toUInt(&isOk);
if (isOk)
data.set_sequence_num(seq);
break;
}
default:
qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__,
index);
break;
}
_exit:
return isOk;
}
int GreProtocol::protocolFrameSize(int /*streamIndex*/) const
{
int size = 4; // mandatory fields - flags, rsvd0, version, protocol
if (data.flags() & GRE_FLAG_CKSUM)
size += 4;
if (data.flags() & GRE_FLAG_KEY)
size += 4;
if (data.flags() & GRE_FLAG_SEQ)
size += 4;
return size;
}

97
common/gre.h Normal file
View File

@ -0,0 +1,97 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _GRE_H
#define _GRE_H
#include "abstractprotocol.h"
#include "gre.pb.h"
/*
GRE Protocol Frame Format (RFC2890)-
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|C| |K|S| Reserved0 | Ver | Protocol Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum (optional) | Reserved1 (Optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Key (optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number (Optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figures in brackets represent field width in bits
*/
#define GRE_FLAG_CKSUM 0x8
#define GRE_FLAG_KEY 0x2
#define GRE_FLAG_SEQ 0x1
class GreProtocol : public AbstractProtocol
{
public:
enum grefield
{
// Frame Fields
gre_flags = 0,
gre_rsvd0,
gre_version,
gre_protocol,
gre_checksum,
gre_rsvd1,
gre_key,
gre_sequence,
// Meta Fields
gre_isOverrideChecksum,
gre_fieldCount
};
GreProtocol(StreamBase *stream, AbstractProtocol *parent = 0);
virtual ~GreProtocol();
static AbstractProtocol* createInstance(StreamBase *stream,
AbstractProtocol *parent = 0);
virtual quint32 protocolNumber() const;
virtual void protoDataCopyInto(OstProto::Protocol &protocol) const;
virtual void protoDataCopyFrom(const OstProto::Protocol &protocol);
virtual ProtocolIdType protocolIdType() const;
virtual quint32 protocolId(ProtocolIdType type) const;
virtual QString name() const;
virtual QString shortName() const;
virtual int fieldCount() const;
virtual AbstractProtocol::FieldFlags fieldFlags(int index) const;
virtual QVariant fieldData(int index, FieldAttrib attrib,
int streamIndex = 0) const;
virtual bool setFieldData(int index, const QVariant &value,
FieldAttrib attrib = FieldValue);
virtual int protocolFrameSize(int streamIndex = 0) const;
private:
OstProto::Gre data;
};
#endif

39
common/gre.proto Normal file
View File

@ -0,0 +1,39 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
import "protocol.proto";
package OstProto;
// GRE Protocol
message Gre {
optional uint32 flags = 1 [default = 0xa];
optional uint32 rsvd0 = 2;
optional uint32 version = 3;
optional uint32 protocol_type = 4;
optional uint32 checksum = 5;
optional bool is_override_checksum = 6;
optional uint32 rsvd1 = 7;
optional uint32 key = 8 [default = 0x2020bad7];
optional uint32 sequence_num = 9;
}
extend Protocol {
optional Gre gre = 405;
}

193
common/gre.ui Normal file
View File

@ -0,0 +1,193 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gre</class>
<widget class="QWidget" name="Gre">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>264</width>
<height>140</height>
</rect>
</property>
<property name="windowTitle">
<string>Gre</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="version">
<property name="specialValueText">
<string>0 (RFC2784)</string>
</property>
<property name="maximum">
<number>7</number>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="hasChecksum">
<property name="text">
<string>Checksum</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="IntEdit" name="checksum">
<property name="enabled">
<bool>false</bool>
</property>
<property name="specialValueText">
<string>&lt;auto&gt;</string>
</property>
<property name="prefix">
<string>0x</string>
</property>
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>-1</number>
</property>
<property name="displayIntegerBase">
<number>16</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="hasKey">
<property name="text">
<string>Key</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="UIntEdit" name="key">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="hasSequence">
<property name="text">
<string>Sequence No</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="UIntEdit" name="sequence">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>IntEdit</class>
<extends>QSpinBox</extends>
<header>intedit.h</header>
</customwidget>
<customwidget>
<class>UIntEdit</class>
<extends>QLineEdit</extends>
<header>uintedit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>hasChecksum</tabstop>
<tabstop>checksum</tabstop>
<tabstop>hasKey</tabstop>
<tabstop>key</tabstop>
<tabstop>hasSequence</tabstop>
<tabstop>sequence</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>hasKey</sender>
<signal>toggled(bool)</signal>
<receiver>key</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>32</x>
<y>69</y>
</hint>
<hint type="destinationlabel">
<x>107</x>
<y>71</y>
</hint>
</hints>
</connection>
<connection>
<sender>hasSequence</sender>
<signal>toggled(bool)</signal>
<receiver>sequence</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>75</x>
<y>99</y>
</hint>
<hint type="destinationlabel">
<x>125</x>
<y>97</y>
</hint>
</hints>
</connection>
<connection>
<sender>hasChecksum</sender>
<signal>toggled(bool)</signal>
<receiver>checksum</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>87</x>
<y>43</y>
</hint>
<hint type="destinationlabel">
<x>109</x>
<y>45</y>
</hint>
</hints>
</connection>
</connections>
</ui>

118
common/greconfig.cpp Normal file
View File

@ -0,0 +1,118 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "greconfig.h"
#include "gre.h"
GreConfigForm::GreConfigForm(QWidget *parent)
: AbstractProtocolConfigForm(parent)
{
setupUi(this);
connect(hasChecksum, SIGNAL(clicked(bool)),
this, SLOT(setAutoChecksum(bool)));
}
GreConfigForm::~GreConfigForm()
{
}
GreConfigForm* GreConfigForm::createInstance()
{
return new GreConfigForm;
}
// Load widget contents from proto
void GreConfigForm::loadWidget(AbstractProtocol *proto)
{
uint flags = proto->fieldData(GreProtocol::gre_flags,
AbstractProtocol::FieldValue)
.toUInt();
version->setValue(
proto->fieldData(
GreProtocol::gre_version,
AbstractProtocol::FieldValue
).toUInt());
hasChecksum->setChecked(flags & GRE_FLAG_CKSUM);
checksum->setValue(
proto->fieldData(
GreProtocol::gre_isOverrideChecksum,
AbstractProtocol::FieldValue).toBool() ?
proto->fieldData(
GreProtocol::gre_checksum,
AbstractProtocol::FieldValue).toUInt() : -1);
hasKey->setChecked(flags & GRE_FLAG_KEY);
key->setValue(
proto->fieldData(
GreProtocol::gre_key,
AbstractProtocol::FieldValue
).toUInt());
hasSequence->setChecked(flags & GRE_FLAG_SEQ);
sequence->setValue(
proto->fieldData(
GreProtocol::gre_sequence,
AbstractProtocol::FieldValue
).toUInt());
}
// Store widget contents into proto
void GreConfigForm::storeWidget(AbstractProtocol *proto)
{
uint flags = 0;
if (hasChecksum->isChecked())
flags |= GRE_FLAG_CKSUM;
if (hasKey->isChecked())
flags |= GRE_FLAG_KEY;
if (hasSequence->isChecked())
flags |= GRE_FLAG_SEQ;
proto->setFieldData(
GreProtocol::gre_flags,
flags);
proto->setFieldData(
GreProtocol::gre_version,
version->value());
proto->setFieldData(
GreProtocol::gre_checksum,
checksum->value());
proto->setFieldData(
GreProtocol::gre_isOverrideChecksum,
checksum->value() > -1 ? true: false);
proto->setFieldData(
GreProtocol::gre_key,
key->value());
proto->setFieldData(
GreProtocol::gre_sequence,
sequence->value());
}
void GreConfigForm::setAutoChecksum(bool enabled)
{
if (enabled)
checksum->setValue(-1); // auto
}

44
common/greconfig.h Normal file
View File

@ -0,0 +1,44 @@
/*
Copyright (C) 2010, 2014 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _GRE_CONFIG_H
#define _GRE_CONFIG_H
#include "abstractprotocolconfig.h"
#include "ui_gre.h"
class GreConfigForm :
public AbstractProtocolConfigForm,
private Ui::Gre
{
Q_OBJECT
public:
GreConfigForm(QWidget *parent = 0);
virtual ~GreConfigForm();
static GreConfigForm* createInstance();
virtual void loadWidget(AbstractProtocol *proto);
virtual void storeWidget(AbstractProtocol *proto);
private slots:
void setAutoChecksum(bool enabled);
};
#endif

69
common/grepdml.cpp Normal file
View File

@ -0,0 +1,69 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "grepdml.h"
#include "gre.pb.h"
PdmlGreProtocol::PdmlGreProtocol()
{
ostProtoId_ = OstProto::Protocol::kGreFieldNumber;
fieldMap_.insert("gre.proto", OstProto::Gre::kProtocolTypeFieldNumber);
fieldMap_.insert("gre.checksum", OstProto::Gre::kChecksumFieldNumber);
fieldMap_.insert("gre.offset", OstProto::Gre::kRsvd1FieldNumber);
fieldMap_.insert("gre.key", OstProto::Gre::kKeyFieldNumber);
fieldMap_.insert("gre.sequence_number", OstProto::Gre::kSequenceNumFieldNumber);
}
PdmlGreProtocol::~PdmlGreProtocol()
{
}
PdmlProtocol* PdmlGreProtocol::createInstance()
{
return new PdmlGreProtocol();
}
void PdmlGreProtocol::postProtocolHandler(OstProto::Protocol* pbProto,
OstProto::Stream* /*stream*/)
{
OstProto::Gre *gre = pbProto->MutableExtension(OstProto::gre);
qDebug("GRE: post");
gre->set_is_override_checksum(overrideCksum_);
return;
}
void PdmlGreProtocol::unknownFieldHandler(QString name,
int /*pos*/, int /*size*/, const QXmlStreamAttributes& attributes,
OstProto::Protocol* proto, OstProto::Stream* /*stream*/)
{
if (name == "gre.flags_and_version") {
bool isOk;
OstProto::Gre *gre = proto->MutableExtension(OstProto::gre);
quint16 flagsAndVersion = attributes.value("value")
.toUInt(&isOk, kBaseHex);
gre->set_flags(flagsAndVersion >> 12);
gre->set_rsvd0((flagsAndVersion & 0x0FFF) >> 3);
gre->set_version(flagsAndVersion & 0x0007);
}
}

45
common/grepdml.h Normal file
View File

@ -0,0 +1,45 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _GRE_PDML_H
#define _GRE_PDML_H
#include "pdmlprotocol.h"
class PdmlGreProtocol : public PdmlProtocol
{
public:
virtual ~PdmlGreProtocol();
static PdmlProtocol* createInstance();
virtual void postProtocolHandler(OstProto::Protocol *pbProto,
OstProto::Stream *stream);
void fieldHandler(QString name, const QXmlStreamAttributes &attributes,
OstProto::Protocol *pbProto, OstProto::Stream *stream);
virtual void unknownFieldHandler(QString name, int pos, int size,
const QXmlStreamAttributes &attributes,
OstProto::Protocol *pbProto, OstProto::Stream *stream);
protected:
PdmlGreProtocol();
};
#endif

View File

@ -214,12 +214,6 @@ Length (x4)</string>
</item> </item>
<item row="1" column="1" > <item row="1" column="1" >
<widget class="QLineEdit" name="leIpSrcAddr" > <widget class="QLineEdit" name="leIpSrcAddr" >
<property name="inputMask" >
<string>009.009.009.009; </string>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="alignment" > <property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
@ -264,12 +258,6 @@ Length (x4)</string>
<property name="enabled" > <property name="enabled" >
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="inputMask" >
<string>009.009.009.009; </string>
</property>
<property name="text" >
<string>...</string>
</property>
</widget> </widget>
</item> </item>
<item row="2" column="0" > <item row="2" column="0" >
@ -281,12 +269,6 @@ Length (x4)</string>
</item> </item>
<item row="2" column="1" > <item row="2" column="1" >
<widget class="QLineEdit" name="leIpDstAddr" > <widget class="QLineEdit" name="leIpDstAddr" >
<property name="inputMask" >
<string>000.000.000.000; </string>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="alignment" > <property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
@ -331,12 +313,6 @@ Length (x4)</string>
<property name="enabled" > <property name="enabled" >
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="inputMask" >
<string>009.009.009.009; </string>
</property>
<property name="text" >
<string>...</string>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "ip4config.h" #include "ip4config.h"
#include "ip4.h" #include "ip4.h"
#include "ipv4addressvalidator.h"
#include <QHostAddress> #include <QHostAddress>
@ -30,6 +31,10 @@ Ip4ConfigForm::Ip4ConfigForm(QWidget *parent)
leIpVersion->setValidator(new QIntValidator(0, 15, this)); leIpVersion->setValidator(new QIntValidator(0, 15, this));
leIpOptions->setValidator(new QRegExpValidator(QRegExp("[0-9a-fA-F]*"), leIpOptions->setValidator(new QRegExpValidator(QRegExp("[0-9a-fA-F]*"),
this)); this));
leIpSrcAddr->setValidator(new IPv4AddressValidator(this));
leIpSrcAddrMask->setValidator(new IPv4AddressValidator(this));
leIpDstAddr->setValidator(new IPv4AddressValidator(this));
leIpDstAddrMask->setValidator(new IPv4AddressValidator(this));
connect(cmbIpSrcAddrMode, SIGNAL(currentIndexChanged(int)), connect(cmbIpSrcAddrMode, SIGNAL(currentIndexChanged(int)),
this, SLOT(on_cmbIpSrcAddrMode_currentIndexChanged(int))); this, SLOT(on_cmbIpSrcAddrMode_currentIndexChanged(int)));

View File

@ -21,7 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "crc32c.h" #include "crc32c.h"
#include <QApplication> #include <QCoreApplication>
#include <QFile> #include <QFile>
#include <QVariant> #include <QVariant>
@ -437,17 +437,19 @@ _exit:
void NativeFileFormat::initFileMetaData(OstProto::FileMetaData &metaData) void NativeFileFormat::initFileMetaData(OstProto::FileMetaData &metaData)
{ {
QCoreApplication *app = QCoreApplication::instance();
// Fill in the "native" file format version // Fill in the "native" file format version
metaData.set_format_version_major(kFileFormatVersionMajor); metaData.set_format_version_major(kFileFormatVersionMajor);
metaData.set_format_version_minor(kFileFormatVersionMinor); metaData.set_format_version_minor(kFileFormatVersionMinor);
metaData.set_format_version_revision(kFileFormatVersionRevision); metaData.set_format_version_revision(kFileFormatVersionRevision);
metaData.set_generator_name( metaData.set_generator_name(
qApp->applicationName().toUtf8().constData()); app->applicationName().toUtf8().constData());
metaData.set_generator_version( metaData.set_generator_version(
qApp->property("version").toString().toUtf8().constData()); app->property("version").toString().toUtf8().constData());
metaData.set_generator_revision( metaData.set_generator_revision(
qApp->property("revision").toString().toUtf8().constData()); app->property("revision").toString().toUtf8().constData());
} }
int NativeFileFormat::fileMetaSize(const quint8* file, int size) int NativeFileFormat::fileMetaSize(const quint8* file, int size)

60
common/ostfile.pro Normal file
View File

@ -0,0 +1,60 @@
TEMPLATE = lib
CONFIG += qt staticlib
QT += network xml script
LIBS += \
-lprotobuf
PROTOS = \
fileformat.proto
HEADERS = \
ostprotolib.h \
nativefileformat.h \
ossnfileformat.h \
ostmfileformat.h \
pcapfileformat.h \
pdmlfileformat.h \
pythonfileformat.h \
pdmlprotocol.h \
pdmlprotocols.h \
pdmlreader.h \
sessionfileformat.h \
streamfileformat.h
SOURCES += \
ostprotolib.cpp \
nativefileformat.cpp \
ossnfileformat.cpp \
ostmfileformat.cpp \
pcapfileformat.cpp \
pdmlfileformat.cpp \
pythonfileformat.cpp \
pdmlprotocol.cpp \
pdmlprotocols.cpp \
pdmlreader.cpp \
sessionfileformat.cpp \
streamfileformat.cpp \
SOURCES += \
vlanpdml.cpp \
svlanpdml.cpp \
stppdml.cpp \
eth2pdml.cpp \
llcpdml.cpp \
arppdml.cpp \
ip4pdml.cpp \
ip6pdml.cpp \
grepdml.cpp \
icmppdml.cpp \
icmp6pdml.cpp \
igmppdml.cpp \
mldpdml.cpp \
tcppdml.cpp \
udppdml.cpp \
textprotopdml.cpp \
samplepdml.cpp
QMAKE_DISTCLEAN += object_script.*
include(../protobuf.pri)
include(../options.pri)

15
common/ostfilegui.pro Normal file
View File

@ -0,0 +1,15 @@
TEMPLATE = lib
CONFIG += qt staticlib
QT += widgets
FORMS = \
pcapfileimport.ui
HEADERS = \
fileformatoptions.h \
pcapoptionsdialog.h
SOURCES = \
fileformatoptions.cpp \
pcapoptionsdialog.cpp

View File

@ -29,6 +29,7 @@ PROTOS += \
ip4over6.proto \ ip4over6.proto \
ip4over4.proto \ ip4over4.proto \
ip6over6.proto \ ip6over6.proto \
gre.proto \
icmp.proto \ icmp.proto \
gmp.proto \ gmp.proto \
igmp.proto \ igmp.proto \
@ -69,6 +70,7 @@ HEADERS += \
ip6over4.h \ ip6over4.h \
ip6over6.h \ ip6over6.h \
gmp.h \ gmp.h \
gre.h \
icmp.h \ icmp.h \
igmp.h \ igmp.h \
mld.h \ mld.h \
@ -103,6 +105,7 @@ SOURCES += \
ip4.cpp \ ip4.cpp \
ip6.cpp \ ip6.cpp \
gmp.cpp \ gmp.cpp \
gre.cpp \
icmp.cpp \ icmp.cpp \
igmp.cpp \ igmp.cpp \
mld.cpp \ mld.cpp \
@ -110,6 +113,7 @@ SOURCES += \
udp.cpp \ udp.cpp \
textproto.cpp \ textproto.cpp \
hexdump.cpp \ hexdump.cpp \
packet.cpp \
payload.cpp \ payload.cpp \
sample.cpp \ sample.cpp \
sign.cpp \ sign.cpp \

View File

@ -2,11 +2,6 @@ TEMPLATE = lib
CONFIG += qt staticlib CONFIG += qt staticlib
QT += widgets network xml script QT += widgets network xml script
INCLUDEPATH += "../extra/qhexedit2/src" INCLUDEPATH += "../extra/qhexedit2/src"
LIBS += \
-lprotobuf
FORMS = \
pcapfileimport.ui \
FORMS += \ FORMS += \
mac.ui \ mac.ui \
@ -20,6 +15,7 @@ FORMS += \
ip4.ui \ ip4.ui \
ip6.ui \ ip6.ui \
gmp.ui \ gmp.ui \
gre.ui \
icmp.ui \ icmp.ui \
tcp.ui \ tcp.ui \
udp.ui \ udp.ui \
@ -31,28 +27,10 @@ FORMS += \
sign.ui \ sign.ui \
userscript.ui userscript.ui
PROTOS = \
fileformat.proto
# TODO: Move fileformat related stuff into a different library - why?
HEADERS = \ HEADERS = \
ostprotolib.h \
ipv4addressdelegate.h \ ipv4addressdelegate.h \
ipv6addressdelegate.h \ ipv6addressdelegate.h \
nativefileformat.h \ spinboxdelegate.h \
ossnfileformat.h \
ostmfileformat.h \
pcapfileformat.h \
pdmlfileformat.h \
pythonfileformat.h \
pdmlprotocol.h \
pdmlprotocols.h \
pdmlreader.h \
sessionfileformat.h \
streamfileformat.h \
spinboxdelegate.h
HEADERS += \
tosdscp.h tosdscp.h
HEADERS += \ HEADERS += \
@ -75,6 +53,7 @@ HEADERS += \
ip6config.h \ ip6config.h \
ip4over4config.h \ ip4over4config.h \
gmpconfig.h \ gmpconfig.h \
greconfig.h \
icmpconfig.h \ icmpconfig.h \
igmpconfig.h \ igmpconfig.h \
mldconfig.h \ mldconfig.h \
@ -88,21 +67,7 @@ HEADERS += \
userscriptconfig.h userscriptconfig.h
SOURCES += \ SOURCES += \
ostprotolib.cpp \ spinboxdelegate.cpp \
nativefileformat.cpp \
ossnfileformat.cpp \
ostmfileformat.cpp \
pcapfileformat.cpp \
pdmlfileformat.cpp \
pythonfileformat.cpp \
pdmlprotocol.cpp \
pdmlprotocols.cpp \
pdmlreader.cpp \
sessionfileformat.cpp \
streamfileformat.cpp \
spinboxdelegate.cpp
SOURCES += \
tosdscp.cpp tosdscp.cpp
SOURCES += \ SOURCES += \
@ -118,6 +83,7 @@ SOURCES += \
ip4config.cpp \ ip4config.cpp \
ip6config.cpp \ ip6config.cpp \
gmpconfig.cpp \ gmpconfig.cpp \
greconfig.cpp \
icmpconfig.cpp \ icmpconfig.cpp \
igmpconfig.cpp \ igmpconfig.cpp \
mldconfig.cpp \ mldconfig.cpp \
@ -130,25 +96,6 @@ SOURCES += \
signconfig.cpp \ signconfig.cpp \
userscriptconfig.cpp userscriptconfig.cpp
SOURCES += \
vlanpdml.cpp \
svlanpdml.cpp \
stppdml.cpp \
eth2pdml.cpp \
llcpdml.cpp \
arppdml.cpp \
ip4pdml.cpp \
ip6pdml.cpp \
icmppdml.cpp \
icmp6pdml.cpp \
igmppdml.cpp \
mldpdml.cpp \
tcppdml.cpp \
udppdml.cpp \
textprotopdml.cpp \
samplepdml.cpp
QMAKE_DISTCLEAN += object_script.* QMAKE_DISTCLEAN += object_script.*
include(../protobuf.pri)
include(../options.pri) include(../options.pri)

117
common/packet.cpp Normal file
View File

@ -0,0 +1,117 @@
/*
Copyright (C) 2023 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "packet.h"
using namespace Packet;
quint16 Packet::l4ChecksumOffset(const uchar *pktData, int pktLen)
{
Parser parser(pktData, pktLen);
quint16 offset = kEthTypeOffset;
// Skip VLANs, if any
quint16 ethType = parser.field16(offset);
if (!parser.ok()) return 0;
// TODO: support 802.3 frames
if (ethType <= 1500)
return 0;
while (kVlanEthTypes.contains(ethType)) {
offset += kVlanTagSize;
ethType = parser.field16(offset);
if (!parser.ok()) return 0;
}
offset += kEthTypeSize;
// XXX: offset now points to Eth payload
// Skip MPLS tags, if any
if (ethType == kMplsEthType) {
while (1) {
quint32 mplsTag = parser.field32(offset);
if (!parser.ok()) return 0;
offset += kMplsTagSize;
if (mplsTag & 0x100) { // BOS bit
quint32 nextWord = parser.field32(offset);
if (!parser.ok()) return 0;
if (nextWord == 0) { // PW Control Word
offset += kMplsTagSize;
ethType = 0;
break;
}
quint8 firstPayloadNibble = nextWord >> 28;
if (firstPayloadNibble == 0x4)
ethType = kIp4EthType;
else if (firstPayloadNibble == 0x6)
ethType = kIp6EthType;
else
ethType = 0;
break;
}
}
}
quint8 ipProto = 0;
if (ethType == kIp4EthType) {
ipProto = parser.field8(offset + kIp4ProtocolOffset);
if (!parser.ok()) return 0;
quint8 ipHdrLen = parser.field8(offset) & 0x0F;
if (!parser.ok()) return 0;
offset += 4*ipHdrLen;
} else if (ethType == kIp6EthType) {
ipProto = parser.field8(offset + kIp6NextHeaderOffset);
if (!parser.ok()) return 0;
offset += kIp6HeaderSize;
// XXX: offset now points to IPv6 payload
// Skip IPv6 extension headers, if any
while (kIp6ExtensionHeaders.contains(ipProto)) {
ipProto = parser.field8(offset + kIp6ExtNextHeaderOffset);
if (!parser.ok()) return 0;
quint16 extHdrLen = parser.field8(offset + kIp6ExtLengthOffset);
offset += 8 + 8*extHdrLen;
}
} else {
// Non-IP
// TODO: support MPLS PW with Eth payload
return 0;
}
// XXX: offset now points to IP payload
if (ipProto == kIpProtoTcp) {
parser.field16(offset + kTcpChecksumOffset);
if (!parser.ok()) return 0;
return offset + kTcpChecksumOffset;
} else if (ipProto == kIpProtoUdp) {
parser.field16(offset + kUdpChecksumOffset);
if (!parser.ok()) return 0;
return offset + kUdpChecksumOffset;
}
// No L4
return 0;
}

113
common/packet.h Normal file
View File

@ -0,0 +1,113 @@
/*
Copyright (C) 2023 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _PACKET_H
#define _PACKET_H
#include <QSet>
#include <QtGlobal>
namespace Packet {
class Parser {
public:
Parser(const uchar *data, int length)
: pktData_(data), pktLen_(length) {}
quint8 field8(int offset) {
if (offset >= pktLen_) {
ok_ = false;
return 0;
}
ok_ = true;
return pktData_[offset];
}
quint16 field16(int offset) {
if (offset + 1 >= pktLen_) {
ok_ = false;
return 0;
}
ok_ = true;
return pktData_[offset] << 8
| pktData_[offset+1];
}
quint32 field32(int offset) {
if (offset + 3 >= pktLen_) {
ok_ = false;
return 0;
}
ok_ = true;
return pktData_[offset] << 24
| pktData_[offset+1] << 16
| pktData_[offset+2] << 8
| pktData_[offset+3];
}
bool ok() {
return ok_;
}
private:
const uchar *pktData_;
int pktLen_;
bool ok_{false};
};
quint16 l4ChecksumOffset(const uchar *pktData, int pktLen);
//
// Constants
//
// Ethernet
const quint16 kEthTypeOffset = 12;
const quint16 kEthTypeSize = 2;
const quint16 kIp4EthType = 0x0800;
const quint16 kIp6EthType = 0x86dd;
const quint16 kMplsEthType = 0x8847;
const QSet<quint16> kVlanEthTypes = {0x8100, 0x9100, 0x88a8};
const uint kEthOverhead = 20;
// VLAN
const quint16 kVlanTagSize = 4;
// MPLS
const quint16 kMplsTagSize = 4;
// IPv4
const quint16 kIp4ProtocolOffset = 9;
// IPv6
const quint16 kIp6HeaderSize = 40;
const quint16 kIp6NextHeaderOffset = 6;
// IPv6 Extension Header
const quint16 kIp6ExtNextHeaderOffset = 0;
const quint16 kIp6ExtLengthOffset = 1;
// IPv4/IPv6 Proto/NextHeader values
const quint8 kIpProtoTcp = 6;
const quint8 kIpProtoUdp = 17;
const QSet<quint8> kIp6ExtensionHeaders = {0, 60, 43, 44, 51, 50, 60, 135}; // FIXME: use names
// TCP
const quint16 kTcpChecksumOffset = 16;
// UDP
const quint16 kUdpChecksumOffset = 6;
};
#endif

View File

@ -33,6 +33,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
const quint32 kPcapFileMagic = 0xa1b2c3d4; const quint32 kPcapFileMagic = 0xa1b2c3d4;
const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1; const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1;
const quint32 kNanoSecondPcapFileMagic = 0xa1b23c4d;
const quint32 kNanoSecondPcapFileMagicSwapped = 0x4d3cb2a1;
const quint16 kPcapFileVersionMajor = 2; const quint16 kPcapFileVersionMajor = 2;
const quint16 kPcapFileVersionMinor = 4; const quint16 kPcapFileVersionMinor = 4;
const quint32 kMaxSnapLen = 65535; const quint32 kMaxSnapLen = 65535;
@ -40,43 +42,9 @@ const quint32 kDltEthernet = 1;
PcapFileFormat pcapFileFormat; PcapFileFormat pcapFileFormat;
PcapImportOptionsDialog::PcapImportOptionsDialog(QVariantMap *options)
: QDialog(NULL)
{
setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
options_ = options;
viaPdml->setChecked(options_->value("ViaPdml").toBool());
recalculateCksums->setChecked(
options_->value("RecalculateCksums").toBool());
doDiff->setChecked(options_->value("DoDiff").toBool());
// XXX: By default this is false - for pcap import tests to show
// minimal diffs. However, for the user, this should be enabled
// by default
recalculateCksums->setChecked(true);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
}
PcapImportOptionsDialog::~PcapImportOptionsDialog()
{
}
void PcapImportOptionsDialog::accept()
{
options_->insert("ViaPdml", viaPdml->isChecked());
options_->insert("RecalculateCksums", recalculateCksums->isChecked());
options_->insert("DoDiff", doDiff->isChecked());
QDialog::accept();
}
PcapFileFormat::PcapFileFormat() PcapFileFormat::PcapFileFormat()
{ {
importOptions_.insert("ViaPdml", true); importOptions_.insert("ViaPdml", true);
importOptions_.insert("RecalculateCksums", false);
importOptions_.insert("DoDiff", true); importOptions_.insert("DoDiff", true);
} }
@ -92,11 +60,12 @@ bool PcapFileFormat::open(const QString fileName,
QTemporaryFile file2; QTemporaryFile file2;
quint32 magic; quint32 magic;
uchar gzipMagic[2]; uchar gzipMagic[2];
bool nsecResolution = false;
int len; int len;
PcapFileHeader fileHdr; PcapFileHeader fileHdr;
PcapPacketHeader pktHdr; PcapPacketHeader pktHdr;
OstProto::Stream *prevStream = NULL; OstProto::Stream *prevStream = NULL;
uint lastUsec = 0; quint64 lastXsec = 0;
int pktCount; int pktCount;
qint64 byteCount = 0; qint64 byteCount = 0;
qint64 byteTotal; qint64 byteTotal;
@ -166,15 +135,22 @@ _retry:
{ {
// Do nothing // Do nothing
} }
else if (magic == kPcapFileMagicSwapped) else if (magic == kNanoSecondPcapFileMagic)
{
nsecResolution = true;
}
else if ((magic == kPcapFileMagicSwapped)
|| (magic == kNanoSecondPcapFileMagicSwapped))
{ {
// Toggle Byte order // Toggle Byte order
if (fd_.byteOrder() == QDataStream::BigEndian) if (fd_.byteOrder() == QDataStream::BigEndian)
fd_.setByteOrder(QDataStream::LittleEndian); fd_.setByteOrder(QDataStream::LittleEndian);
else else
fd_.setByteOrder(QDataStream::BigEndian); fd_.setByteOrder(QDataStream::BigEndian);
nsecResolution = (magic == kNanoSecondPcapFileMagicSwapped);
} }
else // Not a pcap file else // Not a pcap file (could be pcapng or something else)
{ {
if (tryConvert) if (tryConvert)
{ {
@ -226,6 +202,9 @@ _retry:
pktBuf.resize(fileHdr.snapLen); pktBuf.resize(fileHdr.snapLen);
// XXX: PDML also needs the PCAP file to cross check packet bytes
// with the PDML data, so we can't do PDML conversion any earlier
// than this
qDebug("pdml check"); qDebug("pdml check");
if (importOptions_.value("ViaPdml").toBool()) if (importOptions_.value("ViaPdml").toBool())
{ {
@ -265,6 +244,8 @@ _retry:
emit status("Reading PDML packets..."); emit status("Reading PDML packets...");
emit target(100); // in percentage emit target(100); // in percentage
// pdml reader needs pcap, so pass self
isOk = reader.read(&pdmlFile, this, &stop_); isOk = reader.read(&pdmlFile, this, &stop_);
if (stop_) if (stop_)
@ -450,7 +431,7 @@ _retry:
diffFile.close(); diffFile.close();
if (diffFile.size()) if (diffFile.size())
{ {
error.append(tr("<p>There is a diff between the original and imported streams. See details to review the diff.</p><p>Why a diff? See <a href='%1'>possible reasons</a>.</p>\n\n\n\n").arg("https://jump.ostinato.org/pcapdiff")); error.append(tr("<p>There is a diff between the original and imported streams. See details to review the diff.</p><p>&#x1F4A1; If you don't need to edit packets, you can retry the import and uncheck the Intelligent Import option.</p><p>Why a diff? See <a href='%1'>possible reasons</a>.</p>\n\n\n\n").arg("https://jump.ostinato.org/pcapdiff"));
diffFile.open(); diffFile.open();
diffFile.seek(0); diffFile.seek(0);
error.append(QString(diffFile.readAll())); error.append(QString(diffFile.readAll()));
@ -460,6 +441,7 @@ _retry:
} }
_non_pdml: _non_pdml:
qDebug("pcap resolution: %s", nsecResolution ? "nsec" : "usec");
emit status("Reading Packets..."); emit status("Reading Packets...");
emit target(100); // in percentage emit target(100); // in percentage
pktCount = 1; pktCount = 1;
@ -487,20 +469,22 @@ _non_pdml:
stream->mutable_control()->set_num_packets(1); stream->mutable_control()->set_num_packets(1);
// setup packet rate to the timing in pcap (as close as possible) // setup packet rate to the timing in pcap (as close as possible)
const double kUsecsInSec = 1e6; // use quint64 rather than double to store micro/nano second as
uint usec = (pktHdr.tsSec*kUsecsInSec + pktHdr.tsUsec); // it has a larger range (~580 years) and therefore better accuracy
uint delta = usec - lastUsec; const quint64 kXsecsInSec = nsecResolution ? 1e9 : 1e6;
quint64 xsec = (pktHdr.tsSec*kXsecsInSec + pktHdr.tsUsec);
quint64 delta = xsec - lastXsec;
qDebug("pktCount = %d, delta = %llu", pktCount, delta);
if ((pktCount != 1) && delta) if ((pktCount != 1) && delta)
stream->mutable_control()->set_packets_per_sec(kUsecsInSec/delta); stream->mutable_control()->set_packets_per_sec(double(kXsecsInSec)/delta);
if (prevStream) if (prevStream)
prevStream->mutable_control()->CopyFrom(stream->control()); prevStream->mutable_control()->CopyFrom(stream->control());
lastUsec = usec; lastXsec = xsec;
prevStream = stream; prevStream = stream;
pktCount++; pktCount++;
qDebug("pktCount = %d", pktCount);
byteCount += pktHdr.inclLen + sizeof(pktHdr); byteCount += pktHdr.inclLen + sizeof(pktHdr);
emit progress(int(byteCount*100/byteTotal)); // in percentage emit progress(int(byteCount*100/byteTotal)); // in percentage
if (stop_) if (stop_)
@ -576,7 +560,7 @@ bool PcapFileFormat::convertToStandardPcap(
tshark.start(OstProtoLib::tsharkPath(), tshark.start(OstProtoLib::tsharkPath(),
QStringList() QStringList()
<< QString("-r%1").arg(fileName) << QString("-r%1").arg(fileName)
<< "-Fpcap" << "-Fnsecpcap"
<< QString("-w%1").arg(outputFileName)); << QString("-w%1").arg(outputFileName));
if (!tshark.waitForStarted(-1)) if (!tshark.waitForStarted(-1))
{ {
@ -639,7 +623,7 @@ bool PcapFileFormat::save(const OstProto::StreamConfigList streams,
fd_.setDevice(&file); fd_.setDevice(&file);
fileHdr.magicNumber = kPcapFileMagic; fileHdr.magicNumber = kNanoSecondPcapFileMagic;
fileHdr.versionMajor = kPcapFileVersionMajor; fileHdr.versionMajor = kPcapFileVersionMajor;
fileHdr.versionMinor = kPcapFileVersionMinor; fileHdr.versionMinor = kPcapFileVersionMinor;
fileHdr.thisZone = 0; fileHdr.thisZone = 0;
@ -687,11 +671,16 @@ bool PcapFileFormat::save(const OstProto::StreamConfigList streams,
fd_.writeRawData(pktBuf.data(), pktHdr.inclLen); fd_.writeRawData(pktBuf.data(), pktHdr.inclLen);
if (s.packetRate()) if (s.packetRate())
pktHdr.tsUsec += quint32(1e6/s.packetRate()); {
if (pktHdr.tsUsec >= 1000000) quint64 delta = quint64(1e9/s.packetRate());
pktHdr.tsSec += delta/quint32(1e9);
pktHdr.tsUsec += delta % quint32(1e9);
}
if (pktHdr.tsUsec >= quint32(1e9))
{ {
pktHdr.tsSec++; pktHdr.tsSec++;
pktHdr.tsUsec -= 1000000; pktHdr.tsUsec -= quint32(1e9);
} }
emit progress(i); emit progress(i);
@ -710,9 +699,9 @@ _exit:
return isOk; return isOk;
} }
QDialog* PcapFileFormat::openOptionsDialog() QVariantMap* PcapFileFormat::options()
{ {
return new PcapImportOptionsDialog(&importOptions_); return &importOptions_;
} }
bool PcapFileFormat::isMyFileFormat(const QString /*fileName*/) bool PcapFileFormat::isMyFileFormat(const QString /*fileName*/)

View File

@ -20,24 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#define _PCAP_FILE_FORMAT_H #define _PCAP_FILE_FORMAT_H
#include "streamfileformat.h" #include "streamfileformat.h"
#include "ui_pcapfileimport.h"
#include <QDataStream> #include <QDataStream>
#include <QVariantMap> #include <QVariantMap>
class PcapImportOptionsDialog: public QDialog, public Ui::PcapFileImport
{
public:
PcapImportOptionsDialog(QVariantMap *options);
~PcapImportOptionsDialog();
private slots:
void accept();
private:
QVariantMap *options_;
};
class PdmlReader; class PdmlReader;
class PcapFileFormat : public StreamFileFormat class PcapFileFormat : public StreamFileFormat
{ {
@ -52,7 +38,7 @@ public:
bool save(const OstProto::StreamConfigList streams, bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error); const QString fileName, QString &error);
virtual QDialog* openOptionsDialog(); virtual QVariantMap* options();
bool isMyFileFormat(const QString fileName); bool isMyFileFormat(const QString fileName);
bool isMyFileType(const QString fileType); bool isMyFileType(const QString fileType);

View File

@ -0,0 +1,55 @@
/*
Copyright (C) 2011 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "pcapoptionsdialog.h"
PcapImportOptionsDialog::PcapImportOptionsDialog(QVariantMap *options)
: QDialog(NULL)
{
Q_ASSERT(options != NULL);
setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
options_ = options;
viaPdml->setChecked(options_->value("ViaPdml").toBool());
// XXX: By default this key is absent - so that pcap import tests
// evaluate to false and hence show minimal diffs.
// However, for the GUI user, this should be enabled by default.
recalculateCksums->setChecked(
options_->value("RecalculateCksums", QVariant(true))
.toBool());
doDiff->setChecked(options_->value("DoDiff").toBool());
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
}
PcapImportOptionsDialog::~PcapImportOptionsDialog()
{
}
void PcapImportOptionsDialog::accept()
{
options_->insert("ViaPdml", viaPdml->isChecked());
options_->insert("RecalculateCksums", recalculateCksums->isChecked());
options_->insert("DoDiff", doDiff->isChecked());
QDialog::accept();
}

View File

@ -0,0 +1,39 @@
/*
Copyright (C) 2011 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _PCAP_OPTIONS_DIALOG_H
#define _PCAP_OPTIONS_DIALOG_H
#include "ui_pcapfileimport.h"
#include <QVariantMap>
class PcapImportOptionsDialog: public QDialog, public Ui::PcapFileImport
{
public:
PcapImportOptionsDialog(QVariantMap *options);
~PcapImportOptionsDialog();
private slots:
void accept();
private:
QVariantMap *options_;
};
#endif

View File

@ -28,6 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "arppdml.h" #include "arppdml.h"
#include "eth2pdml.h" #include "eth2pdml.h"
#include "grepdml.h"
#include "llcpdml.h" #include "llcpdml.h"
#include "icmppdml.h" #include "icmppdml.h"
#include "icmp6pdml.h" #include "icmp6pdml.h"
@ -62,6 +63,7 @@ PdmlReader::PdmlReader(OstProto::StreamConfigList *streams,
factory_.insert("arp", PdmlArpProtocol::createInstance); factory_.insert("arp", PdmlArpProtocol::createInstance);
factory_.insert("eth", PdmlEthProtocol::createInstance); factory_.insert("eth", PdmlEthProtocol::createInstance);
factory_.insert("gre", PdmlGreProtocol::createInstance);
factory_.insert("http", PdmlTextProtocol::createInstance); factory_.insert("http", PdmlTextProtocol::createInstance);
factory_.insert("icmp", PdmlIcmpProtocol::createInstance); factory_.insert("icmp", PdmlIcmpProtocol::createInstance);
factory_.insert("icmpv6", PdmlIcmp6Protocol::createInstance); factory_.insert("icmpv6", PdmlIcmp6Protocol::createInstance);

View File

@ -157,6 +157,7 @@ message Protocol {
kIcmpFieldNumber = 402; kIcmpFieldNumber = 402;
kIgmpFieldNumber = 403; kIgmpFieldNumber = 403;
kMldFieldNumber = 404; kMldFieldNumber = 404;
kGreFieldNumber = 405;
kTextProtocolFieldNumber = 500; kTextProtocolFieldNumber = 500;
} }
@ -215,6 +216,7 @@ message Port {
optional double speed = 10; // in Mbps optional double speed = 10; // in Mbps
optional uint32 mtu = 11; optional uint32 mtu = 11;
optional string user_description = 12;
} }
message PortConfigList { message PortConfigList {
@ -289,6 +291,10 @@ message StreamStats {
required PortId port_id = 1; required PortId port_id = 1;
required StreamGuid stream_guid = 2; required StreamGuid stream_guid = 2;
optional double tx_duration = 3; // in seconds
optional uint64 latency = 4; // in nanoseconds
optional uint64 jitter = 5; // in nanoseconds
optional uint64 rx_pkts = 11; optional uint64 rx_pkts = 11;
optional uint64 rx_bytes = 12; optional uint64 rx_bytes = 12;
optional uint64 tx_pkts = 13; optional uint64 tx_pkts = 13;

View File

@ -46,6 +46,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "ip6over6.h" #include "ip6over6.h"
// L4 Protos // L4 Protos
#include "gre.h"
#include "icmp.h" #include "icmp.h"
#include "igmp.h" #include "igmp.h"
#include "mld.h" #include "mld.h"
@ -112,6 +113,8 @@ ProtocolManager::ProtocolManager()
(void*) Ip6over6Protocol::createInstance); (void*) Ip6over6Protocol::createInstance);
// Layer 4 Protocols // Layer 4 Protocols
registerProtocol(OstProto::Protocol::kGreFieldNumber,
(void*) GreProtocol::createInstance);
registerProtocol(OstProto::Protocol::kIcmpFieldNumber, registerProtocol(OstProto::Protocol::kIcmpFieldNumber,
(void*) IcmpProtocol::createInstance); (void*) IcmpProtocol::createInstance);
registerProtocol(OstProto::Protocol::kIgmpFieldNumber, registerProtocol(OstProto::Protocol::kIgmpFieldNumber,

View File

@ -40,6 +40,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "ip6over4config.h" #include "ip6over4config.h"
#include "ip6over6config.h" #include "ip6over6config.h"
// L4 Protocol Widgets // L4 Protocol Widgets
#include "greconfig.h"
#include "icmpconfig.h" #include "icmpconfig.h"
#include "igmpconfig.h" #include "igmpconfig.h"
#include "mldconfig.h" #include "mldconfig.h"
@ -124,6 +125,9 @@ ProtocolWidgetFactory::ProtocolWidgetFactory()
(void*) Ip6over6ConfigForm::createInstance); (void*) Ip6over6ConfigForm::createInstance);
// Layer 4 Protocols // Layer 4 Protocols
OstProtocolWidgetFactory->registerProtocolConfigWidget(
OstProto::Protocol::kGreFieldNumber,
(void*) GreConfigForm::createInstance);
OstProtocolWidgetFactory->registerProtocolConfigWidget( OstProtocolWidgetFactory->registerProtocolConfigWidget(
OstProto::Protocol::kIcmpFieldNumber, OstProto::Protocol::kIcmpFieldNumber,
(void*) IcmpConfigForm::createInstance); (void*) IcmpConfigForm::createInstance);

View File

@ -63,6 +63,8 @@ bool PythonFileFormat::save(const OstProto::StreamConfigList streams,
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
goto _open_fail; goto _open_fail;
out.setCodec("UTF-8");
// import standard modules // import standard modules
emit status("Writing imports ..."); emit status("Writing imports ...");
emit target(0); emit target(0);
@ -450,9 +452,16 @@ void PythonFileFormat::writeFieldAssignment(
std::string val = fieldDesc->is_repeated() ? std::string val = fieldDesc->is_repeated() ?
refl->GetRepeatedStringReference(msg, fieldDesc, index, &val) : refl->GetRepeatedStringReference(msg, fieldDesc, index, &val) :
refl->GetStringReference(msg, fieldDesc, &val); refl->GetStringReference(msg, fieldDesc, &val);
QString escVal = escapeString(QString::fromStdString(val)); if (val == fieldDesc->default_value_string())
if (val != fieldDesc->default_value_string()) break;
out << fieldName << " = '" << escVal << "'\n"; if (fieldDesc->type() == FieldDescriptor::TYPE_BYTES) {
QString strVal = byteString(QByteArray(val.c_str(),
val.size()));
out << fieldName << " = b'" << strVal << "'\n";
} else {
QString strVal = QString::fromStdString(val);
out << fieldName << " = u'" << strVal << "'\n";
}
break; break;
} }
case FieldDescriptor::CPPTYPE_ENUM: case FieldDescriptor::CPPTYPE_ENUM:
@ -533,16 +542,16 @@ QString PythonFileFormat::singularize(QString plural)
return singular; return singular;
} }
QString PythonFileFormat::escapeString(QString str) QString PythonFileFormat::escapeString(QByteArray str)
{ {
QString escStr = ""; QString escStr = "";
for (int i=0; i < str.length(); i++) { for (int i=0; i < str.length(); i++) {
uchar c = str[i].cell(); uchar c = uchar(str.at(i));
if ((c < 128) && isprint(c)) { if ((c < 128) && isprint(c)) {
if (c == '\'') if (c == '\'')
escStr.append("\\'"); escStr.append("\\'");
else else
escStr.append(str[i]); escStr.append(QChar(c));
} }
else else
escStr.append(QString("\\x%1").arg(int(c), 2, 16, QChar('0'))); escStr.append(QString("\\x%1").arg(int(c), 2, 16, QChar('0')));
@ -550,6 +559,16 @@ QString PythonFileFormat::escapeString(QString str)
return escStr; return escStr;
} }
QString PythonFileFormat::byteString(QByteArray str)
{
QString byteStr = "";
for (int i=0; i < str.length(); i++) {
uchar c = uchar(str.at(i));
byteStr.append(QString("\\x%1").arg(int(c), 2, 16, QChar('0')));
}
return byteStr;
}
bool PythonFileFormat::useDecimalBase(QString fieldName) bool PythonFileFormat::useDecimalBase(QString fieldName)
{ {
// Heuristic - use Hex base for all except for the following // Heuristic - use Hex base for all except for the following

View File

@ -49,7 +49,8 @@ private:
const google::protobuf::FieldDescriptor *fieldDesc, const google::protobuf::FieldDescriptor *fieldDesc,
int index = -1); int index = -1);
QString singularize(QString plural); QString singularize(QString plural);
QString escapeString(QString str); QString escapeString(QByteArray str);
QString byteString(QByteArray str);
bool useDecimalBase(QString fieldName); bool useDecimalBase(QString fieldName);
}; };

View File

@ -32,12 +32,7 @@ SessionFileFormat::~SessionFileFormat()
{ {
} }
QDialog* SessionFileFormat::openOptionsDialog() QVariantMap* SessionFileFormat::options()
{
return NULL;
}
QDialog* SessionFileFormat::saveOptionsDialog()
{ {
return NULL; return NULL;
} }

View File

@ -23,10 +23,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "fileformat.pb.h" #include "fileformat.pb.h"
#include "protocol.pb.h" #include "protocol.pb.h"
#include <QThread>
#include <QString> #include <QString>
#include <QThread>
class QDialog; #include <QVariantMap>
class SessionFileFormat : public QThread class SessionFileFormat : public QThread
{ {
@ -42,8 +41,7 @@ public:
virtual bool save(const OstProto::SessionContent &session, virtual bool save(const OstProto::SessionContent &session,
const QString fileName, QString &error) = 0; const QString fileName, QString &error) = 0;
virtual QDialog* openOptionsDialog(); virtual QVariantMap* options();
virtual QDialog* saveOptionsDialog();
void openAsync(const QString fileName, void openAsync(const QString fileName,
OstProto::SessionContent &session, QString &error); OstProto::SessionContent &session, QString &error);

View File

@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "sign.h" #include "sign.h"
#include "../common/streambase.h"
SignProtocol::SignProtocol(StreamBase *stream, AbstractProtocol *parent) SignProtocol::SignProtocol(StreamBase *stream, AbstractProtocol *parent)
: AbstractProtocol(stream, parent) : AbstractProtocol(stream, parent)
{ {
@ -76,7 +78,9 @@ AbstractProtocol::FieldFlags SignProtocol::fieldFlags(int index) const
switch (index) switch (index)
{ {
case sign_magic: case sign_magic:
case sign_tlv_tx_port:
case sign_tlv_guid: case sign_tlv_guid:
case sign_tlv_ttag:
case sign_tlv_end: case sign_tlv_end:
break; break;
@ -116,6 +120,52 @@ QVariant SignProtocol::fieldData(int index, FieldAttrib attrib,
} }
break; break;
} }
case sign_tlv_ttag:
{
switch(attrib)
{
case FieldName:
return QString("T-Tag");
case FieldValue:
return 0;
case FieldTextValue:
return QString("%1").arg(0);
case FieldFrameValue:
{
QByteArray fv;
fv.resize(2);
fv[0] = 0;
fv[1] = kTypeLenTtagPlaceholder;
return fv;
}
default:
break;
}
break;
}
case sign_tlv_tx_port:
{
switch(attrib)
{
case FieldName:
return QString("TxPort");
case FieldValue:
return mpStream->portId();
case FieldTextValue:
return QString("%1").arg(mpStream->portId());
case FieldFrameValue:
{
QByteArray fv;
fv.resize(2);
fv[0] = mpStream->portId() & 0xFF;
fv[1] = kTypeLenTxPort;
return fv;
}
default:
break;
}
break;
}
case sign_tlv_guid: case sign_tlv_guid:
{ {
quint32 guid = data.stream_guid() & 0xFFFFFF; quint32 guid = data.stream_guid() & 0xFFFFFF;
@ -217,3 +267,29 @@ bool SignProtocol::packetGuid(const uchar *pkt, int pktLen, uint *guid)
} }
return false; return false;
} }
bool SignProtocol::packetTtagId(const uchar *pkt, int pktLen, uint *ttagId, uint *guid)
{
bool ret = false;
const uchar *p = pkt + pktLen - sizeof(kSignMagic);
quint32 magic = qFromBigEndian<quint32>(p);
if (magic != kSignMagic)
return ret;
*guid = kInvalidGuid;
p--;
while (*p != kTypeLenEnd) {
if (*p == kTypeLenTtag) {
*ttagId = *(p - 1);
ret = true;
} else if (*p == kTypeLenGuid) {
*guid = qFromBigEndian<quint32>(p - 3) >> 8;
} else if (*p == kTypeLenTxPort) {
#ifdef Q_OS_WIN32
*ttagId |= uint(*(p - 1)) << 8;
#endif
}
p -= 1 + (*p >> 5); // move to next TLV
}
return ret;
}

View File

@ -23,6 +23,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "abstractprotocol.h" #include "abstractprotocol.h"
#include "sign.pb.h" #include "sign.pb.h"
#include <limits.h>
/* /*
Sign Protocol is expected at the end of the frame (just before the Eth FCS) Sign Protocol is expected at the end of the frame (just before the Eth FCS)
---+--------+-------+ ---+--------+-------+
@ -39,9 +41,19 @@ TLVs are encoded as
Len does NOT include the one byte of TypeLen Len does NOT include the one byte of TypeLen
Size of the value field varies between 0 to 7 bytes Size of the value field varies between 0 to 7 bytes
Defined TLVs Defined TLVs
Type = 0, Len = 0 (0x00): End of TLVs Type = 0, Len = 0 (0x00): End of TLVs
Type = 1, Len = 3 (0x61): Stream GUID Type = 1, Len = 3 (0x61): Stream GUID
Type = 2, Len = 1 (0x22): T-Tag Placeholder (0 value)
Type = 3, Len = 1 (0x23): T-Tag with actual value
Type = 4, Len = 1 (0x24): Tx Port Id
Order of TLVs from end of packet towards beginning [Offset, Size]
[ -4, 4 bytes] Magic
[ -6, 2 bytes] TTag (Placeholder or actual)
[-10, 4 bytes] Stream Guid
[-12, 2 bytes] Tx Port Id
[-13, 1 byte ] End
*/ */
class SignProtocol : public AbstractProtocol class SignProtocol : public AbstractProtocol
@ -51,7 +63,9 @@ public:
{ {
// Frame Fields // Frame Fields
sign_tlv_end = 0, sign_tlv_end = 0,
sign_tlv_tx_port,
sign_tlv_guid, sign_tlv_guid,
sign_tlv_ttag,
sign_magic, sign_magic,
// Meta Fields // Meta Fields
@ -83,10 +97,19 @@ public:
static quint32 magic(); static quint32 magic();
static bool packetGuid(const uchar *pkt, int pktLen, uint *guid); static bool packetGuid(const uchar *pkt, int pktLen, uint *guid);
static bool packetTtagId(const uchar *pkt, int pktLen, uint *ttagId, uint *guid);
// XXX: Any change in kTypeLenXXX or magic value should also be done in
// TxThread/Ttag code as well where hardcoded values are used
static const quint32 kMaxGuid = 0x00ffffff;
static const quint32 kInvalidGuid = UINT_MAX;
static const quint8 kTypeLenTtagPlaceholder = 0x22;
static const quint8 kTypeLenTtag = 0x23;
private: private:
static const quint32 kSignMagic = 0x1d10c0da; // coda! (unicode - 0x1d10c) static const quint32 kSignMagic = 0x1d10c0da; // coda! (unicode - 0x1d10c)
static const quint8 kTypeLenEnd = 0x00; static const quint8 kTypeLenEnd = 0x00;
static const quint8 kTypeLenGuid = 0x61; static const quint8 kTypeLenGuid = 0x61;
static const quint8 kTypeLenTxPort = 0x24;
OstProto::Sign data; OstProto::Sign data;
}; };

View File

@ -145,7 +145,7 @@ void StreamBase::setFrameProtocol(ProtocolList protocolList)
} }
#endif #endif
bool StreamBase::hasProtocol(quint32 protocolNumber) bool StreamBase::hasProtocol(quint32 protocolNumber) const
{ {
foreach(const AbstractProtocol *proto, *currentFrameProtocols) foreach(const AbstractProtocol *proto, *currentFrameProtocols)
if (proto->protocolNumber() == protocolNumber) if (proto->protocolNumber() == protocolNumber)
@ -681,14 +681,38 @@ quint64 StreamBase::neighborMacAddress(int frameIndex) const
bool StreamBase::preflightCheck(QStringList &result) const bool StreamBase::preflightCheck(QStringList &result) const
{ {
bool pass = true; bool pass = true;
bool chkShort = true;
bool chkTrunc = true; bool chkTrunc = true;
bool chkJumbo = true; bool chkJumbo = true;
bool chkSignIcmp = true;
int count = isFrameSizeVariable() ? frameSizeVariableCount() : 1; int count = isFrameSizeVariable() ? frameSizeVariableCount() : 1;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
int pktLen = frameLen(i); int pktLen = frameLen(i);
if (chkShort && hasProtocol(OstProto::Protocol::kSignFieldNumber)
&& (pktLen > (frameProtocolLength(i) + kFcsSize)))
{
result << QObject::tr("Stream statistics may not work since "
"frame content &lt; 64 bytes and hence will get padded - "
"make sure special signature is at the end of the "
"frame and frame content &ge; 64 bytes");
chkShort = false;
pass = false;
}
if (chkSignIcmp && hasProtocol(OstProto::Protocol::kSignFieldNumber)
&& hasProtocol(OstProto::Protocol::kIcmpFieldNumber))
{
result << QObject::tr("Stream statistics are not supported "
"for ICMP packets - please use a non-ICMP protocol or "
"remove special signature from ICMP streams");
chkSignIcmp = false;
pass = false;
}
if (chkTrunc && (pktLen < (frameProtocolLength(i) + kFcsSize))) if (chkTrunc && (pktLen < (frameProtocolLength(i) + kFcsSize)))
{ {
result << QObject::tr("One or more frames may be truncated - " result << QObject::tr("One or more frames may be truncated - "

View File

@ -42,7 +42,7 @@ public:
void protoDataCopyFrom(const OstProto::Stream &stream); void protoDataCopyFrom(const OstProto::Stream &stream);
void protoDataCopyInto(OstProto::Stream &stream) const; void protoDataCopyInto(OstProto::Stream &stream) const;
bool hasProtocol(quint32 protocolNumber); bool hasProtocol(quint32 protocolNumber) const;
ProtocolListIterator* createProtocolListIterator() const; ProtocolListIterator* createProtocolListIterator() const;
//! \todo (LOW) should we have a copy constructor?? //! \todo (LOW) should we have a copy constructor??
@ -75,9 +75,8 @@ public:
quint32 id() const; quint32 id() const;
bool setId(quint32 id); bool setId(quint32 id);
quint32 portId() { return portId_;}
#if 0 // FIXME(HI): needed? #if 0 // FIXME(HI): needed?
quint32 portId()
{ return mCore->port_id();}
bool setPortId(quint32 id) bool setPortId(quint32 id)
{ mCore->set_port_id(id); return true;} { mCore->set_port_id(id); return true;}
#endif #endif

View File

@ -35,12 +35,7 @@ StreamFileFormat::~StreamFileFormat()
{ {
} }
QDialog* StreamFileFormat::openOptionsDialog() QVariantMap* StreamFileFormat::options()
{
return NULL;
}
QDialog* StreamFileFormat::saveOptionsDialog()
{ {
return NULL; return NULL;
} }

View File

@ -41,8 +41,7 @@ public:
virtual bool save(const OstProto::StreamConfigList streams, virtual bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error) = 0; const QString fileName, QString &error) = 0;
virtual QDialog* openOptionsDialog(); virtual QVariantMap* options();
virtual QDialog* saveOptionsDialog();
void openAsync(const QString fileName, void openAsync(const QString fileName,
OstProto::StreamConfigList &streams, QString &error); OstProto::StreamConfigList &streams, QString &error);

57
common/uintedit.h Normal file
View File

@ -0,0 +1,57 @@
/*
Copyright (C) 2022 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _UINT_EDIT_H
#define _UINT_EDIT_H
#include "ulonglongvalidator.h"
#include <QLineEdit>
#include <limits.h>
class UIntEdit: public QLineEdit
{
public:
UIntEdit(QWidget *parent = 0);
quint32 value();
void setValue(quint32 val);
};
// -------------------- //
inline UIntEdit::UIntEdit(QWidget *parent)
: QLineEdit(parent)
{
setValidator(new ULongLongValidator(0, UINT_MAX));
}
inline quint32 UIntEdit::value()
{
return text().toUInt(Q_NULLPTR, 0);
}
inline void UIntEdit::setValue(quint32 val)
{
setText(QString::number(val));
}
#endif

View File

@ -29,6 +29,11 @@ public:
: QValidator(parent) : QValidator(parent)
{ {
} }
ULongLongValidator(qulonglong min, qulonglong max, QObject *parent = 0)
: QValidator(parent)
{
setRange(min, max);
}
~ULongLongValidator() {} ~ULongLongValidator() {}
void setRange(qulonglong min, qulonglong max) void setRange(qulonglong min, qulonglong max)

View File

@ -32,6 +32,9 @@ Updater::Updater()
#if 1 #if 1
// Tests! // Tests!
Q_ASSERT(isVersionNewer("1.3.0", "1.2.0") == true);
Q_ASSERT(isVersionNewer("1.3.0", "1.1") == true);
Q_ASSERT(isVersionNewer("1.2.0", "1.1") == true);
Q_ASSERT(isVersionNewer("1.1", "1") == true); Q_ASSERT(isVersionNewer("1.1", "1") == true);
Q_ASSERT(isVersionNewer("10.1", "2") == true); Q_ASSERT(isVersionNewer("10.1", "2") == true);
Q_ASSERT(isVersionNewer("0.10", "0.2") == true); Q_ASSERT(isVersionNewer("0.10", "0.2") == true);

Some files were not shown because too many files have changed in this diff Show More