Merge pull request #184 from pstavirs/emul

Device Emulation
This commit is contained in:
Srivats P 2016-05-25 18:57:08 +05:30
commit 24fd176802
102 changed files with 10236 additions and 427 deletions

4
.gitignore vendored
View File

@ -31,6 +31,6 @@ pkg_info.json
*.swp *.swp
.DS_Store .DS_Store
# ctags # ctags/cscope
tags tags
cscope.out

View File

@ -18,6 +18,7 @@
import os import os
from rpc import OstinatoRpcChannel, OstinatoRpcController, RpcError from rpc import OstinatoRpcChannel, OstinatoRpcController, RpcError
import protocols.protocol_pb2 as ost_pb import protocols.protocol_pb2 as ost_pb
import protocols.emulproto_pb2 as emul
from __init__ import __version__ from __init__ import __version__
class DroneProxy(object): class DroneProxy(object):

151
client/arpstatusmodel.cpp Normal file
View File

@ -0,0 +1,151 @@
/*
Copyright (C) 2016 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 "arpstatusmodel.h"
#include "port.h"
#include "emulproto.pb.h"
#include <QHostAddress>
enum {
kIp4Address,
kMacAddress,
kStatus,
kFieldCount
};
static QStringList columns_ = QStringList()
<< "IPv4 Address"
<< "Mac Address"
<< "Status";
ArpStatusModel::ArpStatusModel(QObject *parent)
: QAbstractTableModel(parent)
{
port_ = NULL;
deviceIndex_ = -1;
neighbors_ = NULL;
}
int ArpStatusModel::rowCount(const QModelIndex &parent) const
{
if (!port_ || deviceIndex_ < 0 || parent.isValid())
return 0;
return port_->numArp(deviceIndex_);
}
int ArpStatusModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return columns_.size();
}
QVariant ArpStatusModel::headerData(
int section,
Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
switch (orientation) {
case Qt::Horizontal:
return columns_[section];
case Qt::Vertical:
return QString("%1").arg(section + 1);
default:
Q_ASSERT(false); // Unreachable
}
return QVariant();
}
QVariant ArpStatusModel::data(const QModelIndex &index, int role) const
{
QString str;
if (!port_ || deviceIndex_ < 0 || !index.isValid())
return QVariant();
int arpIdx = index.row();
int field = index.column();
Q_ASSERT(arpIdx < port_->numArp(deviceIndex_));
Q_ASSERT(field < kFieldCount);
const OstEmul::ArpEntry &arp = neighbors_->arp(arpIdx);
switch (field) {
case kIp4Address:
switch (role) {
case Qt::DisplayRole:
return QHostAddress(arp.ip4()).toString();
default:
break;
}
return QVariant();
case kMacAddress:
switch (role) {
case Qt::DisplayRole:
return QString("%1").arg(arp.mac(), 6*2, 16, QChar('0'))
.replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:")
.toUpper();
default:
break;
}
return QVariant();
case kStatus:
switch (role) {
case Qt::DisplayRole:
return arp.mac() ?
QString("Resolved") : QString("Failed");
default:
break;
}
return QVariant();
default:
Q_ASSERT(false); // unreachable!
break;
}
qWarning("%s: Unsupported field #%d", __FUNCTION__, field);
return QVariant();
}
void ArpStatusModel::setDeviceIndex(Port *port, int deviceIndex)
{
port_ = port;
deviceIndex_ = deviceIndex;
if (port_)
neighbors_ = port_->deviceNeighbors(deviceIndex);
reset();
}
void ArpStatusModel::updateArpStatus()
{
reset();
}

55
client/arpstatusmodel.h Normal file
View File

@ -0,0 +1,55 @@
/*
Copyright (C) 2016 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 _ARP_STATUS_MODEL_H
#define _ARP_STATUS_MODEL_H
#include <QAbstractTableModel>
class Port;
namespace OstEmul {
class DeviceNeighborList;
}
class ArpStatusModel: public QAbstractTableModel
{
Q_OBJECT
public:
ArpStatusModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role) const;
void setDeviceIndex(Port *port, int deviceIndex);
public slots:
void updateArpStatus();
private:
Port *port_;
int deviceIndex_;
const OstEmul::DeviceNeighborList *neighbors_;
};
#endif

View File

@ -0,0 +1,355 @@
/*
Copyright (C) 2016 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 "devicegroupdialog.h"
#include "port.h"
#include "spinboxdelegate.h"
#include "emulproto.pb.h"
#include "uint128.h"
#include <QHeaderView>
#include <QHostAddress>
enum { kVlanId, kVlanCount, kVlanStep, kVlanCfi, kVlanPrio, kVlanTpid,
kVlanColumns };
static QStringList vlanTableColumnHeaders = QStringList()
<< "Vlan Id" << "Count" << "Step" << "CFI/DE" << "Prio" << "TPID";
enum { kIpNone, kIp4, kIp6, kIpDual };
static QStringList ipStackItems = QStringList()
<< "None" << "IPv4" << "IPv6" << "Dual";
inline UInt128 UINT128(OstEmul::Ip6Address x)
{
return UInt128(x.hi(), x.lo());
}
inline OstEmul::Ip6Address IP6ADDR(UInt128 x)
{
OstEmul::Ip6Address ip;
ip.set_hi(x.hi64());
ip.set_lo(x.lo64());
return ip;
}
DeviceGroupDialog::DeviceGroupDialog(
Port *port,
int deviceGroupIndex,
QWidget *parent,
Qt::WindowFlags flags)
: QDialog(parent, flags), port_(port), index_(deviceGroupIndex)
{
// Setup the Dialog
setupUi(this);
vlanTagCount->setRange(0, kMaxVlanTags);
// Populate the Vlan Table with placeholders - we do this so that
// user entered values are retained during the lifetime of the dialog
// even if user is playing around with number of vlan tags
vlans->setRowCount(kMaxVlanTags);
vlans->setColumnCount(kVlanColumns);
vlans->setHorizontalHeaderLabels(vlanTableColumnHeaders);
for (int i = 0; i < kMaxVlanTags; i++) {
// Use same default values as defined in .proto
vlans->setItem(i, kVlanId,
new QTableWidgetItem(QString::number(100*(i+1))));
vlans->setItem(i, kVlanCount,
new QTableWidgetItem(QString::number(1)));
vlans->setItem(i, kVlanStep,
new QTableWidgetItem(QString::number(1)));
vlans->setItem(i, kVlanCfi,
new QTableWidgetItem(QString::number(0)));
vlans->setItem(i, kVlanPrio,
new QTableWidgetItem(QString::number(0)));
vlans->setItem(i, kVlanTpid,
new QTableWidgetItem(QString("0x8100")));
}
// Set SpinBoxDelegate for all columns except TPID
SpinBoxDelegate *spd = new SpinBoxDelegate(this);
spd->setColumnRange(kVlanId, 0, 4095);
spd->setColumnRange(kVlanStep, 0, 4095);
spd->setColumnRange(kVlanCfi, 0, 1);
spd->setColumnRange(kVlanPrio, 0, 7);
for (int i = 0; i < kVlanColumns; i++) {
if (i != kVlanTpid)
vlans->setItemDelegateForColumn(i, spd);
}
vlans->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
vlans->resizeRowsToContents();
// Set vlan tag count *after* adding all items, so connected slots
// can access the items
vlanTagCount->setValue(kMaxVlanTags);
ipStack->insertItems(0, ipStackItems);
// TODO: DeviceGroup Traversal; hide buttons for now
// NOTE for implementation: Use a QHash<int, deviceGroup*>
// to store modified values while traversing; in accept()
// update port->deviceGroups[] from the QHash
prev->setHidden(true);
next->setHidden(true);
// TODO: Preview devices expanded from deviceGroup configuration
// for user convenience
// setup dialog to auto-resize as widgets are hidden or shown
layout()->setSizeConstraint(QLayout::SetFixedSize);
connect(devicePerVlanCount, SIGNAL(valueChanged(const QString&)),
this, SLOT(updateTotalDeviceCount()));
connect(ip4Address, SIGNAL(textEdited(const QString&)),
this, SLOT(updateIp4Gateway()));
connect(ip4PrefixLength, SIGNAL(valueChanged(const QString&)),
this, SLOT(updateIp4Gateway()));
connect(ip6Address, SIGNAL(textEdited(const QString&)),
this, SLOT(updateIp6Gateway()));
connect(ip6PrefixLength, SIGNAL(valueChanged(const QString&)),
this, SLOT(updateIp6Gateway()));
loadDeviceGroup();
}
void DeviceGroupDialog::accept()
{
storeDeviceGroup();
QDialog::accept();
}
//
// Private Slots
//
void DeviceGroupDialog::on_vlanTagCount_valueChanged(int value)
{
Q_ASSERT((value >= 0) && (value <= kMaxVlanTags));
for (int row = 0; row < kMaxVlanTags; row++)
vlans->setRowHidden(row, row >= value);
vlans->setVisible(value > 0);
updateTotalVlanCount();
}
void DeviceGroupDialog::on_vlans_cellChanged(int row, int col)
{
if (col != kVlanCount)
return;
if (vlans->isRowHidden(row))
return;
updateTotalVlanCount();
}
void DeviceGroupDialog::on_ipStack_currentIndexChanged(int index)
{
switch (index) {
case kIpNone:
ip4->hide();
ip6->hide();
break;
case kIp4:
ip4->show();
ip6->hide();
break;
case kIp6:
ip4->hide();
ip6->show();
break;
case kIpDual:
ip4->show();
ip6->show();
break;
default:
Q_ASSERT(false); // Unreachable!
break;
}
}
void DeviceGroupDialog::updateTotalVlanCount()
{
int count = vlanTagCount->value() ? 1 : 0;
for (int i = 0; i < vlanTagCount->value(); i++)
count *= vlans->item(i, kVlanCount)->text().toUInt();
vlanCount->setValue(count);
updateTotalDeviceCount();
}
void DeviceGroupDialog::updateTotalDeviceCount()
{
totalDeviceCount->setValue(qMax(vlanCount->value(), 1)
* devicePerVlanCount->value());
}
void DeviceGroupDialog::updateIp4Gateway()
{
quint32 net = ip4Address->value() & (~0 << (32 - ip4PrefixLength->value()));
ip4Gateway->setValue(net | 0x01);
}
void DeviceGroupDialog::updateIp6Gateway()
{
UInt128 net = ip6Address->value()
& (~UInt128(0, 0) << (128 - ip6PrefixLength->value()));
ip6Gateway->setValue(net | UInt128(0, 1));
}
void DeviceGroupDialog::loadDeviceGroup()
{
const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_);
int tagCount = 0;
// use 1-indexed id so that it matches the port id used in the
// RFC 4814 compliant mac addresses assigned by default to deviceGroups
// XXX: use deviceGroupId also as part of the id?
quint32 id = (port_->id()+1) & 0xff;
Q_ASSERT(devGrp);
name->setText(QString::fromStdString(devGrp->core().name()));
if (devGrp->has_encap() && devGrp->encap().HasExtension(OstEmul::vlan)) {
OstEmul::VlanEmulation vlan = devGrp->encap()
.GetExtension(OstEmul::vlan);
tagCount = vlan.stack_size();
for (int i = 0; i < tagCount; i++) {
OstEmul::VlanEmulation::Vlan v = vlan.stack(i);
vlans->item(i, kVlanPrio)->setText(
QString::number((v.vlan_tag() >> 13) & 0x7));
vlans->item(i, kVlanCfi)->setText(
QString::number((v.vlan_tag() >> 12) & 0x1));
vlans->item(i, kVlanId)->setText(
QString::number(v.vlan_tag() & 0x0fff));
vlans->item(i, kVlanCount)->setText(QString::number(v.count()));
vlans->item(i, kVlanStep)->setText(QString::number(v.step()));
vlans->item(i, kVlanTpid)->setText(QString("0x%1")
.arg(v.tpid(), 0, 16));
}
}
vlanTagCount->setValue(tagCount);
updateTotalVlanCount();
devicePerVlanCount->setValue(devGrp->device_count());
OstEmul::MacEmulation mac = devGrp->GetExtension(OstEmul::mac);
Q_ASSERT(mac.has_address());
macAddress->setValue(mac.address());
macStep->setValue(mac.step());
OstEmul::Ip4Emulation ip4 = devGrp->GetExtension(OstEmul::ip4);
// If address is not set, assign one from RFC 2544 space - 192.18.0.0/15
// Use port Id as the 3rd octet of the address
ip4Address->setValue(ip4.has_address() ?
ip4.address() : 0xc6120002 | (id << 8));
ip4PrefixLength->setValue(ip4.prefix_length());
ip4Step->setValue(ip4.has_step()? ip4.step() : 1);
ip4Gateway->setValue(ip4.has_default_gateway() ?
ip4.default_gateway() : 0xc6120001 | (id << 8));
OstEmul::Ip6Emulation ip6 = devGrp->GetExtension(OstEmul::ip6);
// If address is not set, assign one from RFC 5180 space 2001:0200::/64
// Use port Id as the 3rd hextet of the address
ip6Address->setValue(ip6.has_address() ?
UINT128(ip6.address()) :
UInt128((0x200102000000ULL | id) << 16, 2));
ip6PrefixLength->setValue(ip6.prefix_length());
ip6Step->setValue(ip6.has_step() ? UINT128(ip6.step()) : UInt128(0, 1));
ip6Gateway->setValue(ip6.has_default_gateway() ?
UINT128(ip6.default_gateway()) :
UInt128((0x200102000000ULL | id) << 16, 1));
int stk = kIpNone;
if (devGrp->HasExtension(OstEmul::ip4))
if (devGrp->HasExtension(OstEmul::ip6))
stk = kIpDual;
else
stk = kIp4;
else if (devGrp->HasExtension(OstEmul::ip6))
stk = kIp6;
ipStack->setCurrentIndex(stk);
}
void DeviceGroupDialog::storeDeviceGroup()
{
OstProto::DeviceGroup *devGrp = port_->mutableDeviceGroupByIndex(index_);
int tagCount = vlanTagCount->value();
Q_ASSERT(devGrp);
devGrp->mutable_core()->set_name(name->text().toStdString());
OstEmul::VlanEmulation *vlan = devGrp->mutable_encap()
->MutableExtension(OstEmul::vlan);
vlan->clear_stack();
for (int i = 0; i < tagCount; i++) {
OstEmul::VlanEmulation::Vlan *v = vlan->add_stack();
v->set_vlan_tag(
vlans->item(i, kVlanPrio)->text().toUInt() << 13
| vlans->item(i, kVlanCfi)->text().toUInt() << 12
| vlans->item(i, kVlanId)->text().toUInt());
v->set_count(vlans->item(i, kVlanCount)->text().toUInt());
v->set_step(vlans->item(i, kVlanStep)->text().toUInt());
v->set_tpid(vlans->item(i, kVlanTpid)->text().toUInt(NULL, 16));
}
if (!tagCount)
devGrp->clear_encap();
devGrp->set_device_count(devicePerVlanCount->value());
OstEmul::MacEmulation *mac = devGrp->MutableExtension(OstEmul::mac);
mac->set_address(macAddress->value());
mac->set_step(macStep->value());
if (ipStack->currentIndex() == kIp4
|| ipStack->currentIndex() == kIpDual) {
OstEmul::Ip4Emulation *ip4 = devGrp->MutableExtension(OstEmul::ip4);
ip4->set_address(ip4Address->value());
ip4->set_prefix_length(ip4PrefixLength->value());
ip4->set_default_gateway(ip4Gateway->value());
ip4->set_step(ip4Step->value());
if (ipStack->currentIndex() == kIp4)
devGrp->ClearExtension(OstEmul::ip6);
}
if (ipStack->currentIndex() == kIp6
|| ipStack->currentIndex() == kIpDual) {
OstEmul::Ip6Emulation *ip6 = devGrp->MutableExtension(OstEmul::ip6);
ip6->mutable_address()->CopyFrom(IP6ADDR(ip6Address->value()));
ip6->set_prefix_length(ip6PrefixLength->value());
ip6->mutable_step()->CopyFrom(IP6ADDR(ip6Step->value()));
ip6->mutable_default_gateway()->CopyFrom(IP6ADDR(ip6Gateway->value()));
if (ipStack->currentIndex() == kIp6)
devGrp->ClearExtension(OstEmul::ip4);
}
if (ipStack->currentIndex() == kIpNone) {
devGrp->ClearExtension(OstEmul::ip4);
devGrp->ClearExtension(OstEmul::ip6);
}
}

View File

@ -0,0 +1,56 @@
/*
Copyright (C) 2016 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 _DEVICE_GROUP_DIALOG_H
#define _DEVICE_GROUP_DIALOG_H
#include "ui_devicegroupdialog.h"
#include <QDialog>
class Port;
class DeviceGroupDialog: public QDialog, private Ui::DeviceGroupDialog
{
Q_OBJECT
public:
DeviceGroupDialog(Port *port, int deviceGroupIndex,
QWidget *parent = NULL, Qt::WindowFlags flags = 0);
virtual void accept();
private slots:
void on_vlanTagCount_valueChanged(int value);
void on_vlans_cellChanged(int row, int col);
void on_ipStack_currentIndexChanged(int index);
void updateTotalVlanCount();
void updateTotalDeviceCount();
void updateIp4Gateway();
void updateIp6Gateway();
void loadDeviceGroup();
void storeDeviceGroup();
private:
static const int kMaxVlanTags = 4;
Port *port_;
int index_;
};
#endif

385
client/devicegroupdialog.ui Normal file
View File

@ -0,0 +1,385 @@
<ui version="4.0" >
<class>DeviceGroupDialog</class>
<widget class="QDialog" name="DeviceGroupDialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>504</width>
<height>465</height>
</rect>
</property>
<property name="windowTitle" >
<string>Devices</string>
</property>
<layout class="QVBoxLayout" >
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="name" />
</item>
<item>
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Vlan Tags</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="vlanTagCount" />
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="vlans" />
</item>
<item>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_11" >
<property name="text" >
<string>Total Vlans</string>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="IntEdit" name="vlanCount" >
<property name="enabled" >
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2" >
<widget class="QLabel" name="label_13" >
<property name="text" >
<string>Devices Per Vlan</string>
</property>
<property name="buddy" >
<cstring>devicePerVlanCount</cstring>
</property>
</widget>
</item>
<item row="0" column="4" >
<widget class="QSpinBox" name="devicePerVlanCount" />
</item>
<item row="0" column="5" >
<widget class="QLabel" name="label_14" >
<property name="text" >
<string>Total Devices</string>
</property>
</widget>
</item>
<item row="0" column="6" >
<widget class="IntEdit" name="totalDeviceCount" >
<property name="enabled" >
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_3" >
<property name="text" >
<string>Mac Address</string>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="MacEdit" name="macAddress" />
</item>
<item row="1" column="2" >
<widget class="QLabel" name="label_5" >
<property name="text" >
<string>Step</string>
</property>
</widget>
</item>
<item row="1" column="3" colspan="2" >
<widget class="MacEdit" name="macStep" />
</item>
<item row="1" column="5" >
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>IP Stack</string>
</property>
</widget>
</item>
<item row="1" column="6" >
<widget class="QComboBox" name="ipStack" />
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="ip4" >
<property name="frameShape" >
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Plain</enum>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_9" >
<property name="text" >
<string>IPv4 Address</string>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="Ip4Edit" name="ip4Address" />
</item>
<item row="0" column="2" >
<widget class="QLabel" name="label_12" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Fixed" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>/</string>
</property>
</widget>
</item>
<item row="0" column="5" >
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>Step</string>
</property>
</widget>
</item>
<item row="0" column="6" >
<widget class="Ip4Edit" name="ip4Step" />
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_7" >
<property name="text" >
<string>Gateway</string>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="Ip4Edit" name="ip4Gateway" />
</item>
<item row="0" column="3" >
<widget class="QSpinBox" name="ip4PrefixLength" >
<property name="minimum" >
<number>1</number>
</property>
<property name="maximum" >
<number>32</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="ip6" >
<property name="frameShape" >
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Plain</enum>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_10" >
<property name="text" >
<string>IPv6 Address</string>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="Ip6Edit" name="ip6Address" />
</item>
<item row="0" column="2" >
<widget class="QLabel" name="label_15" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Fixed" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>/</string>
</property>
</widget>
</item>
<item row="0" column="5" >
<widget class="QLabel" name="label_16" >
<property name="text" >
<string>Step</string>
</property>
</widget>
</item>
<item row="0" column="6" >
<widget class="Ip6Edit" name="ip6Step" />
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_17" >
<property name="text" >
<string>Gateway</string>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="Ip6Edit" name="ip6Gateway" />
</item>
<item row="0" column="3" >
<widget class="QSpinBox" name="ip6PrefixLength" >
<property name="minimum" >
<number>1</number>
</property>
<property name="maximum" >
<number>128</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QToolButton" name="prev" >
<property name="text" >
<string>&lt;</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="next" >
<property name="text" >
<string>></string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>MacEdit</class>
<extends>QLineEdit</extends>
<header>macedit.h</header>
</customwidget>
<customwidget>
<class>Ip4Edit</class>
<extends>QLineEdit</extends>
<header>ip4edit.h</header>
</customwidget>
<customwidget>
<class>Ip6Edit</class>
<extends>QLineEdit</extends>
<header>ip6edit.h</header>
</customwidget>
<customwidget>
<class>IntEdit</class>
<extends>QSpinBox</extends>
<header>intedit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>name</tabstop>
<tabstop>vlanTagCount</tabstop>
<tabstop>vlans</tabstop>
<tabstop>devicePerVlanCount</tabstop>
<tabstop>macAddress</tabstop>
<tabstop>macStep</tabstop>
<tabstop>ipStack</tabstop>
<tabstop>ip4Address</tabstop>
<tabstop>ip4PrefixLength</tabstop>
<tabstop>ip4Step</tabstop>
<tabstop>ip4Gateway</tabstop>
<tabstop>ip6Address</tabstop>
<tabstop>ip6PrefixLength</tabstop>
<tabstop>ip6Step</tabstop>
<tabstop>ip6Gateway</tabstop>
<tabstop>prev</tabstop>
<tabstop>next</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DeviceGroupDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>227</x>
<y>289</y>
</hint>
<hint type="destinationlabel" >
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DeviceGroupDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>295</x>
<y>295</y>
</hint>
<hint type="destinationlabel" >
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

282
client/devicegroupmodel.cpp Normal file
View File

@ -0,0 +1,282 @@
/*
Copyright (C) 2016 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 "devicegroupmodel.h"
#include "port.h"
#include "emulproto.pb.h"
#include "uint128.h"
#include <QHostAddress>
enum {
kName,
kVlanCount,
kDeviceCount, // Across all vlans
kIp,
kIp4Address,
kIp6Address,
kFieldCount
};
static QStringList columns_ = QStringList()
<< "Name"
<< "Vlans"
<< "Devices"
<< "IP Stack"
<< "IPv4 Address"
<< "IPv6 Address";
DeviceGroupModel::DeviceGroupModel(QObject *parent)
: QAbstractTableModel(parent)
{
port_ = NULL;
}
int DeviceGroupModel::rowCount(const QModelIndex &parent) const
{
if (!port_ || parent.isValid())
return 0;
return port_->numDeviceGroups();
}
int DeviceGroupModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return columns_.size();
}
QVariant DeviceGroupModel::headerData(
int section,
Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
switch (orientation) {
case Qt::Horizontal:
return columns_[section];
case Qt::Vertical:
return QString("%1").arg(section + 1);
default:
Q_ASSERT(false); // Unreachable
}
return QVariant();
}
QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const
{
if (!port_ || !index.isValid())
return QVariant();
int dgIdx = index.row();
int field = index.column();
Q_ASSERT(dgIdx < port_->numDeviceGroups());
Q_ASSERT(field < kFieldCount);
const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(dgIdx);
Q_ASSERT(devGrp);
switch (field) {
case kName:
switch (role) {
case Qt::DisplayRole:
return QString::fromStdString(devGrp->core().name());
default:
break;
}
return QVariant();
case kVlanCount:
switch (role) {
case Qt::DisplayRole:
if (int v = vlanCount(devGrp))
return v;
return QString("None");
case Qt::TextAlignmentRole:
return Qt::AlignRight;
default:
break;
}
return QVariant();
case kDeviceCount:
switch (role) {
case Qt::DisplayRole:
return qMax(vlanCount(devGrp), 1)*devGrp->device_count();
case Qt::TextAlignmentRole:
return Qt::AlignRight;
default:
break;
}
return QVariant();
case kIp:
switch (role) {
case Qt::DisplayRole:
if (devGrp->HasExtension(OstEmul::ip4))
if (devGrp->HasExtension(OstEmul::ip6))
return QString("Dual Stack");
else
return QString("IPv4");
else if (devGrp->HasExtension(OstEmul::ip6))
return QString("IPv6");
else
return QString("None");
default:
break;
}
return QVariant();
case kIp4Address:
switch (role) {
case Qt::DisplayRole:
if (devGrp->HasExtension(OstEmul::ip4))
return QHostAddress(
devGrp->GetExtension(OstEmul::ip4)
.address()).toString();
else
return QString("--");
default:
break;
}
return QVariant();
case kIp6Address:
switch (role) {
case Qt::DisplayRole:
if (devGrp->HasExtension(OstEmul::ip6)) {
OstEmul::Ip6Address ip = devGrp->GetExtension(
OstEmul::ip6).address();
return QHostAddress(
UInt128(ip.hi(), ip.lo()).toArray())
.toString();
}
else
return QString("--");
default:
break;
}
return QVariant();
default:
Q_ASSERT(false); // unreachable!
break;
}
qWarning("%s: Unsupported field #%d", __FUNCTION__, field);
return QVariant();
}
bool DeviceGroupModel::setData(
const QModelIndex &index,
const QVariant &value,
int role)
{
if (!port_)
return false;
// TODO; when implementing also implement flags() to
// return ItemIsEditable
return false;
}
bool DeviceGroupModel::insertRows(
int row,
int count,
const QModelIndex &parent)
{
int c = 0;
Q_ASSERT(!parent.isValid());
beginInsertRows(parent, row, row+count-1);
for (int i = 0; i < count; i++) {
if (port_->newDeviceGroupAt(row))
c++;
}
endInsertRows();
if (c != count) {
qWarning("failed to insert rows in DeviceGroupModel at row %d; "
"requested = %d, actual = %d", row, count, c);
return false;
}
return true;
}
bool DeviceGroupModel::removeRows(
int row,
int count,
const QModelIndex &parent)
{
int c = 0;
Q_ASSERT(!parent.isValid());
beginRemoveRows(parent, row, row+count-1);
for (int i = 0; i < count; i++) {
if (port_->deleteDeviceGroupAt(row))
c++;
}
endRemoveRows();
if (c != count) {
qWarning("failed to delete rows in DeviceGroupModel at row %d; "
"requested = %d, actual = %d", row, count, c);
return false;
}
return true;
}
void DeviceGroupModel::setPort(Port *port)
{
port_ = port;
reset();
}
//
// ---------------------- Private Methods -----------------------
//
int DeviceGroupModel::vlanCount(const OstProto::DeviceGroup *deviceGroup) const
{
if (!deviceGroup->has_encap()
|| !deviceGroup->encap().HasExtension(OstEmul::vlan))
return 0;
OstEmul::VlanEmulation vlan = deviceGroup->encap()
.GetExtension(OstEmul::vlan);
int numTags = vlan.stack_size();
int count = 1;
for (int i = 0; i < numTags; i++)
count *= vlan.stack(i).count();
return count;
}

59
client/devicegroupmodel.h Normal file
View File

@ -0,0 +1,59 @@
/*
Copyright (C) 2016 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 _DEVICE_GROUP_MODEL_H
#define _DEVICE_GROUP_MODEL_H
#include <QAbstractTableModel>
#include <QStringList>
class Port;
namespace OstProto {
class DeviceGroup;
};
class DeviceGroupModel: public QAbstractTableModel
{
Q_OBJECT
public:
DeviceGroupModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
bool insertRows (int row, int count,
const QModelIndex &parent = QModelIndex());
bool removeRows (int row, int count,
const QModelIndex &parent = QModelIndex());
void setPort(Port *port);
private:
int vlanCount(const OstProto::DeviceGroup *deviceGroup) const;
Port *port_;
};
#endif

291
client/devicemodel.cpp Normal file
View File

@ -0,0 +1,291 @@
/*
Copyright (C) 2016 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 "devicemodel.h"
#include "arpstatusmodel.h"
#include "ndpstatusmodel.h"
#include "port.h"
#include "emulproto.pb.h"
#include "uint128.h"
#include <QBrush>
#include <QColor>
#include <QFont>
#include <QHostAddress>
enum {
kMacAddress,
kVlans,
kIp4Address,
kIp4Gateway,
kIp6Address,
kIp6Gateway,
kArpInfo,
kNdpInfo,
kFieldCount
};
static QStringList columns_ = QStringList()
<< "Mac"
<< "Vlans"
<< "IPv4 Address"
<< "IPv4 Gateway"
<< "IPv6 Address"
<< "IPv6 Gateway"
<< "ARP"
<< "NDP";
DeviceModel::DeviceModel(QObject *parent)
: QAbstractTableModel(parent)
{
port_ = NULL;
arpStatusModel_ = new ArpStatusModel(this);
ndpStatusModel_ = new NdpStatusModel(this);
}
int DeviceModel::rowCount(const QModelIndex &parent) const
{
if (!port_ || parent.isValid())
return 0;
return port_->numDevices();
}
int DeviceModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return columns_.size();
}
QVariant DeviceModel::headerData(
int section,
Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
switch (orientation) {
case Qt::Horizontal:
return columns_[section];
case Qt::Vertical:
return QString("%1").arg(section + 1);
default:
Q_ASSERT(false); // Unreachable
}
return QVariant();
}
QVariant DeviceModel::data(const QModelIndex &index, int role) const
{
QString str;
if (!port_ || !index.isValid())
return QVariant();
int devIdx = index.row();
int field = index.column();
Q_ASSERT(devIdx < port_->numDevices());
Q_ASSERT(field < kFieldCount);
const OstEmul::Device *dev = port_->deviceByIndex(devIdx);
Q_ASSERT(dev);
switch (field) {
case kMacAddress:
switch (role) {
case Qt::DisplayRole:
return QString("%1").arg(dev->mac(), 6*2, 16, QChar('0'))
.replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:")
.toUpper();
default:
break;
}
return QVariant();
case kVlans:
switch (role) {
case Qt::DisplayRole:
if (!dev->vlan_size())
return QString("None");
for (int i = 0; i < dev->vlan_size(); i++)
str.append(i == 0 ? "" : ", ")
.append(QString::number(dev->vlan(i) & 0xfff));
return str;
default:
break;
}
return QVariant();
case kIp4Address:
switch (role) {
case Qt::DisplayRole:
if (dev->has_ip4_prefix_length())
return QString("%1/%2")
.arg(QHostAddress(dev->ip4()).toString())
.arg(dev->ip4_prefix_length());
else
return QString("--");
default:
break;
}
return QVariant();
case kIp4Gateway:
switch (role) {
case Qt::DisplayRole:
if (dev->has_ip4_prefix_length())
return QHostAddress(dev->ip4_default_gateway())
.toString();
else
return QString("--");
default:
break;
}
return QVariant();
case kIp6Address:
switch (role) {
case Qt::DisplayRole:
if (dev->has_ip6_prefix_length()) {
OstEmul::Ip6Address ip = dev->ip6();
return QHostAddress(
UInt128(ip.hi(), ip.lo()).toArray())
.toString();
}
else
return QString("--");
default:
break;
}
return QVariant();
case kIp6Gateway:
switch (role) {
case Qt::DisplayRole:
if (dev->has_ip6_prefix_length()) {
OstEmul::Ip6Address ip = dev->ip6_default_gateway();
return QHostAddress(
UInt128(ip.hi(), ip.lo()).toArray())
.toString();
}
else
return QString("--");
default:
break;
}
return QVariant();
case kArpInfo:
switch (role) {
case Qt::DisplayRole:
if (dev->has_ip4_prefix_length())
return QString("%1/%2")
.arg(port_->numArpResolved(devIdx))
.arg(port_->numArp(devIdx));
else
return QString("--");
default:
if (dev->has_ip4_prefix_length())
return drillableStyle(role);
break;
}
return QVariant();
case kNdpInfo:
switch (role) {
case Qt::DisplayRole:
if (dev->has_ip6_prefix_length())
return QString("%1/%2")
.arg(port_->numNdpResolved(devIdx))
.arg(port_->numNdp(devIdx));
else
return QString("--");
default:
if (dev->has_ip6_prefix_length())
return drillableStyle(role);
break;
}
return QVariant();
default:
Q_ASSERT(false); // unreachable!
break;
}
qWarning("%s: Unsupported field #%d", __FUNCTION__, field);
return QVariant();
}
void DeviceModel::setPort(Port *port)
{
port_ = port;
if (port_)
connect(port_, SIGNAL(deviceInfoChanged()), SLOT(updateDeviceList()));
reset();
}
QAbstractItemModel* DeviceModel::detailModel(const QModelIndex &index)
{
if (!index.isValid())
return NULL;
switch(index.column()) {
case kArpInfo:
arpStatusModel_->setDeviceIndex(port_, index.row());
return arpStatusModel_;
case kNdpInfo:
ndpStatusModel_->setDeviceIndex(port_, index.row());
return ndpStatusModel_;
default:
return NULL;
}
}
void DeviceModel::updateDeviceList()
{
reset();
}
// Style roles for drillable fields
QVariant DeviceModel::drillableStyle(int role) const
{
QFont f;
switch (role) {
case Qt::ToolTipRole:
return QString("Click for details ...");
case Qt::ForegroundRole:
return QBrush(QColor(Qt::blue));
case Qt::FontRole:
f.setUnderline(true);
return f;
default:
break;
}
return QVariant();
}

57
client/devicemodel.h Normal file
View File

@ -0,0 +1,57 @@
/*
Copyright (C) 2016 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 _DEVICE_MODEL_H
#define _DEVICE_MODEL_H
#include <QAbstractTableModel>
class ArpStatusModel;
class NdpStatusModel;
class Port;
class DeviceModel: public QAbstractTableModel
{
Q_OBJECT
public:
DeviceModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role) const;
void setPort(Port *port);
QAbstractItemModel* detailModel(const QModelIndex &index);
public slots:
void updateDeviceList();
private:
QVariant drillableStyle(int role) const;
Port *port_;
ArpStatusModel *arpStatusModel_;
NdpStatusModel *ndpStatusModel_;
};
#endif

249
client/deviceswidget.cpp Normal file
View File

@ -0,0 +1,249 @@
/*
Copyright (C) 2016 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 "deviceswidget.h"
#include "devicegroupdialog.h"
#include "port.h"
#include "portgrouplist.h"
#include <QHeaderView>
#include <QKeyEvent>
DevicesWidget::DevicesWidget(QWidget *parent)
: QWidget(parent), portGroups_(NULL)
{
setupUi(this);
deviceGroupList->setVisible(deviceConfig->isChecked());
deviceList->setVisible(deviceInfo->isChecked());
refresh->setVisible(deviceInfo->isChecked());
deviceDetail->hide();
deviceGroupList->verticalHeader()->setDefaultSectionSize(
deviceGroupList->verticalHeader()->minimumSectionSize());
deviceList->verticalHeader()->setDefaultSectionSize(
deviceList->verticalHeader()->minimumSectionSize());
deviceDetail->verticalHeader()->setDefaultSectionSize(
deviceDetail->verticalHeader()->minimumSectionSize());
// Populate DeviceGroup Context Menu Actions
deviceGroupList->addAction(actionNewDeviceGroup);
deviceGroupList->addAction(actionEditDeviceGroup);
deviceGroupList->addAction(actionDeleteDeviceGroup);
// DevicesWidget's actions is an aggegate of all sub-widget's actions
addActions(deviceGroupList->actions());
}
void DevicesWidget::setPortGroupList(PortGroupList *portGroups)
{
portGroups_ = portGroups;
deviceGroupList->setModel(portGroups_->getDeviceGroupModel());
deviceList->setModel(portGroups_->getDeviceModel());
connect(portGroups_->getDeviceGroupModel(),
SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updateDeviceViewActions()));
connect(portGroups_->getDeviceGroupModel(),
SIGNAL(rowsRemoved(QModelIndex, int, int)),
SLOT(updateDeviceViewActions()));
connect(deviceGroupList->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
SLOT(updateDeviceViewActions()));
connect(deviceGroupList->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
SLOT(updateDeviceViewActions()));
connect(deviceList->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
SLOT(when_deviceList_currentChanged(const QModelIndex&)));
// FIXME: hardcoding
deviceGroupList->resizeColumnToContents(1); // Vlan Count
deviceGroupList->resizeColumnToContents(2); // Device Count
// FIXME: hardcoding
deviceList->resizeColumnToContents(1); // Vlan Id(s)
deviceList->resizeColumnToContents(6); // ARP Info
deviceList->resizeColumnToContents(7); // NDP Info
updateDeviceViewActions();
}
void DevicesWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
deviceDetail->hide();
event->accept();
}
else
event->ignore();
}
void DevicesWidget::setCurrentPortIndex(const QModelIndex &portIndex)
{
Port *port = NULL;
if (!portGroups_)
return;
// XXX: We assume portIndex corresponds to sourceModel, not proxyModel
if (portGroups_->isPort(portIndex))
port = &portGroups_->port(portIndex);
portGroups_->getDeviceGroupModel()->setPort(port);
portGroups_->getDeviceModel()->setPort(port);
currentPortIndex_ = portIndex;
updateDeviceViewActions();
}
void DevicesWidget::updateDeviceViewActions()
{
QItemSelectionModel *devSel = deviceGroupList->selectionModel();
if (!portGroups_)
return;
// For some reason hasSelection() returns true even if selection size is 0
// so additional check for size introduced
if (devSel->hasSelection() && (devSel->selection().size() > 0)) {
// If more than one non-contiguous ranges selected,
// disable "New" and "Edit"
if (devSel->selection().size() > 1) {
actionNewDeviceGroup->setDisabled(true);
actionEditDeviceGroup->setDisabled(true);
}
else {
actionNewDeviceGroup->setEnabled(true);
// Enable "Edit" only if the single range has a single row
if (devSel->selection().at(0).height() > 1)
actionEditDeviceGroup->setDisabled(true);
else
actionEditDeviceGroup->setEnabled(true);
}
// Delete is always enabled as long as we have a selection
actionDeleteDeviceGroup->setEnabled(true);
}
else {
qDebug("No device selection");
if (portGroups_->isPort(currentPortIndex_))
actionNewDeviceGroup->setEnabled(true);
else
actionNewDeviceGroup->setDisabled(true);
actionEditDeviceGroup->setDisabled(true);
actionDeleteDeviceGroup->setDisabled(true);
}
}
//
// DeviceGroup slots
//
void DevicesWidget::on_deviceInfo_toggled(bool checked)
{
refresh->setVisible(checked);
deviceGroupList->setHidden(checked);
deviceList->setVisible(checked);
deviceDetail->hide();
}
void DevicesWidget::on_actionNewDeviceGroup_triggered()
{
// In case nothing is selected, insert 1 row at the top
int row = 0, count = 1;
QItemSelection selection = deviceGroupList->selectionModel()->selection();
if (!portGroups_)
return;
// In case we have a single range selected; insert as many rows as
// in the singe selected range before the top of the selected range
if (selection.size() == 1) {
row = selection.at(0).top();
count = selection.at(0).height();
}
portGroups_->getDeviceGroupModel()->insertRows(row, count);
}
void DevicesWidget::on_actionDeleteDeviceGroup_triggered()
{
QModelIndex index;
if (!portGroups_)
return;
if (deviceGroupList->selectionModel()->hasSelection()) {
while(deviceGroupList->selectionModel()->selectedRows().size()) {
index = deviceGroupList->selectionModel()->selectedRows().at(0);
portGroups_->getDeviceGroupModel()->removeRows(index.row(), 1);
}
}
}
void DevicesWidget::on_actionEditDeviceGroup_triggered()
{
QItemSelection selection = deviceGroupList->selectionModel()->selection();
// Ensure we have only one range selected which contains only one row
if ((selection.size() == 1) && (selection.at(0).height() == 1))
on_deviceGroupList_activated(selection.at(0).topLeft());
}
void DevicesWidget::on_deviceGroupList_activated(const QModelIndex &index)
{
if (!index.isValid() || !portGroups_)
return;
DeviceGroupDialog dgd(&portGroups_->port(currentPortIndex_), index.row());
dgd.exec();
}
void DevicesWidget::on_refresh_clicked()
{
if (!portGroups_)
return;
Q_ASSERT(portGroups_->isPort(currentPortIndex_));
QModelIndex curPortGroup = portGroups_->getPortModel()
->parent(currentPortIndex_);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(portGroups_->isPortGroup(curPortGroup));
deviceDetail->hide();
portGroups_->portGroup(curPortGroup)
.getDeviceInfo(portGroups_->port(currentPortIndex_).id());
}
void DevicesWidget::when_deviceList_currentChanged(const QModelIndex &index)
{
if (!index.isValid() || !portGroups_)
return;
QAbstractItemModel *detailModel = portGroups_->getDeviceModel()
->detailModel(index);
deviceDetail->setModel(detailModel);
deviceDetail->setVisible(detailModel != NULL);
}

61
client/deviceswidget.h Normal file
View File

@ -0,0 +1,61 @@
/*
Copyright (C) 2016 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 _DEVICES_WIDGET_H
#define _DEVICES_WIDGET_H
#include "ui_deviceswidget.h"
#include <QWidget>
class PortGroupList;
class DevicesWidget: public QWidget, private Ui::DevicesWidget
{
Q_OBJECT
public:
DevicesWidget(QWidget *parent = NULL);
void setPortGroupList(PortGroupList *portGroups);
virtual void keyPressEvent(QKeyEvent *event);
public slots:
void setCurrentPortIndex(const QModelIndex &portIndex);
private slots:
void updateDeviceViewActions();
void on_deviceInfo_toggled(bool checked);
void on_actionNewDeviceGroup_triggered();
void on_actionDeleteDeviceGroup_triggered();
void on_actionEditDeviceGroup_triggered();
void on_deviceGroupList_activated(const QModelIndex &index);
void on_refresh_clicked();
void when_deviceList_currentChanged(const QModelIndex &index);
private:
PortGroupList *portGroups_;
QModelIndex currentPortIndex_;
};
#endif

138
client/deviceswidget.ui Normal file
View File

@ -0,0 +1,138 @@
<ui version="4.0" >
<class>DevicesWidget</class>
<widget class="QWidget" name="DevicesWidget" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>675</width>
<height>328</height>
</rect>
</property>
<property name="windowTitle" >
<string>Form</string>
</property>
<layout class="QVBoxLayout" >
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QRadioButton" name="deviceConfig" >
<property name="text" >
<string>Configuration</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="deviceInfo" >
<property name="text" >
<string>Information</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>131</width>
<height>23</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refresh" >
<property name="text" >
<string>Refresh</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="deviceGroupList" >
<property name="contextMenuPolicy" >
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="lineWidth" >
<number>1</number>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="deviceList" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="deviceDetail" >
<property name="selectionMode" >
<enum>QAbstractItemView::SingleSelection</enum>
</property>
</widget>
</item>
</layout>
<action name="actionNewDeviceGroup" >
<property name="icon" >
<iconset resource="ostinato.qrc" >:/icons/devicegroup_add.png</iconset>
</property>
<property name="text" >
<string>New Device Group</string>
</property>
</action>
<action name="actionDeleteDeviceGroup" >
<property name="icon" >
<iconset resource="ostinato.qrc" >:/icons/devicegroup_delete.png</iconset>
</property>
<property name="text" >
<string>Delete Device Group</string>
</property>
</action>
<action name="actionEditDeviceGroup" >
<property name="icon" >
<iconset resource="ostinato.qrc" >:/icons/devicegroup_edit.png</iconset>
</property>
<property name="text" >
<string>Edit Device Group</string>
</property>
</action>
</widget>
<resources>
<include location="ostinato.qrc" />
</resources>
<connections/>
</ui>

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

View File

@ -25,8 +25,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "settings.h" #include "settings.h"
#include <QApplication> #include <QApplication>
#include <QDateTime>
#include <QFile> #include <QFile>
#include <QSettings> #include <QSettings>
#include <QtGlobal>
#include <google/protobuf/stubs/common.h> #include <google/protobuf/stubs/common.h>
@ -68,6 +70,7 @@ int main(int argc, char* argv[])
appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString());
Preferences::initDefaults(); Preferences::initDefaults();
qsrand(QDateTime::currentDateTime().toTime_t());
mainWindow = new MainWindow; mainWindow = new MainWindow;
mainWindow->show(); mainWindow->show();

View File

@ -27,12 +27,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portstatswindow.h" #include "portstatswindow.h"
#include "portswindow.h" #include "portswindow.h"
#include "preferences.h" #include "preferences.h"
#include "sessionfileformat.h"
#include "settings.h" #include "settings.h"
#include "ui_about.h" #include "ui_about.h"
#include "updater.h" #include "updater.h"
#include "fileformat.pb.h"
#include <QDockWidget> #include <QDockWidget>
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess> #include <QProcess>
#include <QProgressDialog>
extern const char* version; extern const char* version;
extern const char* revision; extern const char* revision;
@ -75,7 +81,7 @@ MainWindow::MainWindow(QWidget *parent)
setupUi(this); setupUi(this);
menuFile->insertActions(menuFile->actions().at(0), portsWindow->actions()); menuFile->insertActions(menuFile->actions().at(3), portsWindow->actions());
statsDock->setWidget(statsWindow); statsDock->setWidget(statsWindow);
addDockWidget(Qt::BottomDockWidgetArea, statsDock); addDockWidget(Qt::BottomDockWidgetArea, statsDock);
@ -133,6 +139,102 @@ MainWindow::~MainWindow()
delete localServer_; delete localServer_;
} }
void MainWindow::on_actionOpenSession_triggered()
{
qDebug("Open Session Action");
static QString dirName;
QString fileName;
QStringList fileTypes = SessionFileFormat::supportedFileTypes(
SessionFileFormat::kOpenFile);
QString fileType;
QString errorStr;
bool ret;
if (portsWindow->portGroupCount()) {
if (QMessageBox::question(this,
tr("Open Session"),
tr("Existing session will be lost. Proceed?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No) == QMessageBox::No)
goto _exit;
}
if (fileTypes.size())
fileType = fileTypes.at(0);
fileName = QFileDialog::getOpenFileName(this, tr("Open Session"),
dirName, fileTypes.join(";;"), &fileType);
if (fileName.isEmpty())
goto _exit;
ret = openSession(fileName, errorStr);
if (!ret || !errorStr.isEmpty()) {
QMessageBox msgBox(this);
QStringList str = errorStr.split("\n\n\n\n");
msgBox.setIcon(ret ? QMessageBox::Warning : QMessageBox::Critical);
msgBox.setWindowTitle(qApp->applicationName());
msgBox.setText(str.at(0));
if (str.size() > 1)
msgBox.setDetailedText(str.at(1));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
}
dirName = QFileInfo(fileName).absolutePath();
_exit:
return;
}
void MainWindow::on_actionSaveSession_triggered()
{
qDebug("Save Session Action");
static QString fileName;
QStringList fileTypes = SessionFileFormat::supportedFileTypes(
SessionFileFormat::kSaveFile);
QString fileType;
QString errorStr;
QFileDialog::Options options;
if (portsWindow->reservedPortCount()) {
QString myself = appSettings->value(kUserKey, kUserDefaultValue)
.toString();
if (QMessageBox::question(this,
tr("Save Session"),
QString("Some ports are reserved!\n\nOnly ports reserved by %1 will be saved. Proceed?").arg(myself),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No) == QMessageBox::No)
goto _exit;
}
// On Mac OS with Native Dialog, getSaveFileName() ignores fileType.
// Although currently there's only one supported file type, we may
// have more in the future
#if defined(Q_OS_MAC)
options |= QFileDialog::DontUseNativeDialog;
#endif
if (fileTypes.size())
fileType = fileTypes.at(0);
fileName = QFileDialog::getSaveFileName(this, tr("Save Session"),
fileName, fileTypes.join(";;"), &fileType, options);
if (fileName.isEmpty())
goto _exit;
if (!saveSession(fileName, fileType, errorStr))
QMessageBox::critical(this, qApp->applicationName(), errorStr);
else if (!errorStr.isEmpty())
QMessageBox::warning(this, qApp->applicationName(), errorStr);
fileName = QFileInfo(fileName).absolutePath();
_exit:
return;
}
void MainWindow::on_actionPreferences_triggered() void MainWindow::on_actionPreferences_triggered()
{ {
Preferences *preferences = new Preferences(); Preferences *preferences = new Preferences();
@ -172,3 +274,120 @@ void MainWindow::onNewVersion(QString newVersion)
statusBar()->showMessage(QString("New Ostinato version %1 available. " statusBar()->showMessage(QString("New Ostinato version %1 available. "
"Visit http://ostinato.org to download").arg(newVersion)); "Visit http://ostinato.org to download").arg(newVersion));
} }
//! Returns true on success (or user cancel) and false on failure
bool MainWindow::openSession(QString fileName, QString &error)
{
bool ret = false;
QDialog *optDialog;
QProgressDialog progress("Opening Session", "Cancel", 0, 0, this);
OstProto::SessionContent session;
SessionFileFormat *fmt = SessionFileFormat::fileFormatFromFile(fileName);
if (fmt == NULL) {
error = tr("Unknown session file format");
goto _fail;
}
if ((optDialog = fmt->openOptionsDialog()))
{
int ret;
optDialog->setParent(this, Qt::Dialog);
ret = optDialog->exec();
optDialog->setParent(0, Qt::Dialog);
if (ret == QDialog::Rejected)
goto _user_opt_cancel;
}
progress.setAutoReset(false);
progress.setAutoClose(false);
progress.setMinimumDuration(0);
progress.show();
setDisabled(true);
progress.setEnabled(true); // to override the mainWindow disable
connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString)));
connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int)));
connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int)));
connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel()));
fmt->openAsync(fileName, session, error);
qDebug("after open async");
while (!fmt->isFinished())
qApp->processEvents();
qDebug("wait over for async operation");
if (!fmt->result())
goto _fail;
// process any remaining events posted from the thread
for (int i = 0; i < 10; i++)
qApp->processEvents();
// XXX: user can't cancel operation from here on!
progress.close();
portsWindow->openSession(&session, error);
_user_opt_cancel:
ret = true;
_fail:
progress.close();
setEnabled(true);
return ret;
}
bool MainWindow::saveSession(QString fileName, QString fileType, QString &error)
{
bool ret = false;
QProgressDialog progress("Saving Session", "Cancel", 0, 0, this);
SessionFileFormat *fmt = SessionFileFormat::fileFormatFromType(fileType);
OstProto::SessionContent session;
if (fmt == NULL)
goto _fail;
progress.setAutoReset(false);
progress.setAutoClose(false);
progress.setMinimumDuration(0);
progress.show();
setDisabled(true);
progress.setEnabled(true); // to override the mainWindow disable
// Fill in session
ret = portsWindow->saveSession(&session, error, &progress);
if (!ret)
goto _user_cancel;
connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString)));
connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int)));
connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int)));
connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel()));
fmt->saveAsync(session, fileName, error);
qDebug("after save async");
while (!fmt->isFinished())
qApp->processEvents();
qDebug("wait over for async operation");
ret = fmt->result();
goto _exit;
_user_cancel:
goto _exit;
_fail:
error = QString("Unsupported File Type - %1").arg(fileType);
goto _exit;
_exit:
progress.close();
setEnabled(true);
return ret;
}

View File

@ -34,6 +34,9 @@ class MainWindow : public QMainWindow, private Ui::MainWindow
Q_OBJECT Q_OBJECT
private: private:
bool openSession(QString fileName, QString &error);
bool saveSession(QString fileName, QString fileType, QString &error);
QProcess *localServer_; QProcess *localServer_;
PortsWindow *portsWindow; PortsWindow *portsWindow;
PortStatsWindow *statsWindow; PortStatsWindow *statsWindow;
@ -48,6 +51,8 @@ public:
~MainWindow(); ~MainWindow();
public slots: public slots:
void on_actionOpenSession_triggered();
void on_actionSaveSession_triggered();
void on_actionPreferences_triggered(); void on_actionPreferences_triggered();
void on_actionViewRestoreDefaults_triggered(); void on_actionViewRestoreDefaults_triggered();
void on_actionHelpAbout_triggered(); void on_actionHelpAbout_triggered();

View File

@ -5,8 +5,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>700</width> <width>1024</width>
<height>550</height> <height>600</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
@ -17,18 +17,12 @@
</property> </property>
<widget class="QWidget" name="centralwidget" /> <widget class="QWidget" name="centralwidget" />
<widget class="QMenuBar" name="menubar" > <widget class="QMenuBar" name="menubar" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile" > <widget class="QMenu" name="menuFile" >
<property name="title" > <property name="title" >
<string>&amp;File</string> <string>&amp;File</string>
</property> </property>
<addaction name="actionOpenSession" />
<addaction name="actionSaveSession" />
<addaction name="separator" /> <addaction name="separator" />
<addaction name="actionPreferences" /> <addaction name="actionPreferences" />
<addaction name="actionFileExit" /> <addaction name="actionFileExit" />
@ -97,6 +91,16 @@
<string>Restore &amp;Defaults</string> <string>Restore &amp;Defaults</string>
</property> </property>
</action> </action>
<action name="actionOpenSession" >
<property name="text" >
<string>Open Session ...</string>
</property>
</action>
<action name="actionSaveSession" >
<property name="text" >
<string>Save Session ...</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="ostinato.qrc" /> <include location="ostinato.qrc" />

155
client/ndpstatusmodel.cpp Normal file
View File

@ -0,0 +1,155 @@
/*
Copyright (C) 2016 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 "ndpstatusmodel.h"
#include "port.h"
#include "emulproto.pb.h"
#include "uint128.h"
#include <QHostAddress>
enum {
kIp4Address,
kMacAddress,
kStatus,
kFieldCount
};
static QStringList columns_ = QStringList()
<< "IPv6 Address"
<< "Mac Address"
<< "Status";
NdpStatusModel::NdpStatusModel(QObject *parent)
: QAbstractTableModel(parent)
{
port_ = NULL;
deviceIndex_ = -1;
neighbors_ = NULL;
}
int NdpStatusModel::rowCount(const QModelIndex &parent) const
{
if (!port_ || deviceIndex_ < 0 || parent.isValid())
return 0;
return port_->numNdp(deviceIndex_);
}
int NdpStatusModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return columns_.size();
}
QVariant NdpStatusModel::headerData(
int section,
Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
switch (orientation) {
case Qt::Horizontal:
return columns_[section];
case Qt::Vertical:
return QString("%1").arg(section + 1);
default:
Q_ASSERT(false); // Unreachable
}
return QVariant();
}
QVariant NdpStatusModel::data(const QModelIndex &index, int role) const
{
QString str;
if (!port_ || deviceIndex_ < 0 || !index.isValid())
return QVariant();
int ndpIdx = index.row();
int field = index.column();
Q_ASSERT(ndpIdx < port_->numNdp(deviceIndex_));
Q_ASSERT(field < kFieldCount);
const OstEmul::NdpEntry &ndp = neighbors_->ndp(ndpIdx);
switch (field) {
case kIp4Address:
switch (role) {
case Qt::DisplayRole:
return QHostAddress(
UInt128(ndp.ip6().hi(),
ndp.ip6().lo()).toArray())
.toString();
default:
break;
}
return QVariant();
case kMacAddress:
switch (role) {
case Qt::DisplayRole:
return QString("%1").arg(ndp.mac(), 6*2, 16, QChar('0'))
.replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:")
.toUpper();
default:
break;
}
return QVariant();
case kStatus:
switch (role) {
case Qt::DisplayRole:
return ndp.mac() ?
QString("Resolved") : QString("Failed");
default:
break;
}
return QVariant();
default:
Q_ASSERT(false); // unreachable!
break;
}
qWarning("%s: Unsupported field #%d", __FUNCTION__, field);
return QVariant();
}
void NdpStatusModel::setDeviceIndex(Port *port, int deviceIndex)
{
port_ = port;
deviceIndex_ = deviceIndex;
if (port_)
neighbors_ = port_->deviceNeighbors(deviceIndex);
reset();
}
void NdpStatusModel::updateNdpStatus()
{
reset();
}

55
client/ndpstatusmodel.h Normal file
View File

@ -0,0 +1,55 @@
/*
Copyright (C) 2016 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 _NDP_STATUS_MODEL_H
#define _NDP_STATUS_MODEL_H
#include <QAbstractTableModel>
class Port;
namespace OstEmul {
class DeviceNeighborList;
}
class NdpStatusModel: public QAbstractTableModel
{
Q_OBJECT
public:
NdpStatusModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role) const;
void setDeviceIndex(Port *port, int deviceIndex);
public slots:
void updateNdpStatus();
private:
Port *port_;
int deviceIndex_;
const OstEmul::DeviceNeighborList *neighbors_;
};
#endif

View File

@ -33,9 +33,15 @@ LIBS += -lprotobuf
LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2 LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2
RESOURCES += ostinato.qrc RESOURCES += ostinato.qrc
HEADERS += \ HEADERS += \
arpstatusmodel.h \
devicegroupdialog.h \
devicegroupmodel.h \
devicemodel.h \
deviceswidget.h \
dumpview.h \ dumpview.h \
hexlineedit.h \ hexlineedit.h \
mainwindow.h \ mainwindow.h \
ndpstatusmodel.h \
packetmodel.h \ packetmodel.h \
port.h \ port.h \
portconfigdialog.h \ portconfigdialog.h \
@ -57,6 +63,8 @@ HEADERS += \
FORMS += \ FORMS += \
about.ui \ about.ui \
devicegroupdialog.ui \
deviceswidget.ui \
mainwindow.ui \ mainwindow.ui \
portconfigdialog.ui \ portconfigdialog.ui \
portstatsfilter.ui \ portstatsfilter.ui \
@ -67,11 +75,17 @@ FORMS += \
variablefieldswidget.ui variablefieldswidget.ui
SOURCES += \ SOURCES += \
arpstatusmodel.cpp \
devicegroupdialog.cpp \
devicegroupmodel.cpp \
devicemodel.cpp \
deviceswidget.cpp \
dumpview.cpp \ dumpview.cpp \
stream.cpp \ stream.cpp \
hexlineedit.cpp \ hexlineedit.cpp \
main.cpp \ main.cpp \
mainwindow.cpp \ mainwindow.cpp \
ndpstatusmodel.cpp \
packetmodel.cpp \ packetmodel.cpp \
port.cpp \ port.cpp \
portconfigdialog.cpp \ portconfigdialog.cpp \

View File

@ -15,11 +15,16 @@
<file>icons/control_stop.png</file> <file>icons/control_stop.png</file>
<file>icons/deco_exclusive.png</file> <file>icons/deco_exclusive.png</file>
<file>icons/delete.png</file> <file>icons/delete.png</file>
<file>icons/devicegroup_add.png</file>
<file>icons/devicegroup_delete.png</file>
<file>icons/devicegroup_edit.png</file>
<file>icons/exit.png</file> <file>icons/exit.png</file>
<file>icons/gaps.png</file> <file>icons/gaps.png</file>
<file>icons/logo.png</file> <file>icons/logo.png</file>
<file>icons/magnifier.png</file> <file>icons/magnifier.png</file>
<file>icons/name.png</file> <file>icons/name.png</file>
<file>icons/neighbor_clear.png</file>
<file>icons/neighbor_resolve.png</file>
<file>icons/portgroup_add.png</file> <file>icons/portgroup_add.png</file>
<file>icons/portgroup_connect.png</file> <file>icons/portgroup_connect.png</file>
<file>icons/portgroup_delete.png</file> <file>icons/portgroup_delete.png</file>

View File

@ -19,7 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "port.h" #include "port.h"
#include "abstractfileformat.h" #include "emulation.h"
#include "streamfileformat.h"
#include <QApplication> #include <QApplication>
#include <QMainWindow> #include <QMainWindow>
@ -31,11 +32,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
extern QMainWindow *mainWindow; extern QMainWindow *mainWindow;
uint Port::mAllocStreamId = 0; uint Port::mAllocStreamId = 0;
uint Port::allocDeviceGroupId_ = 1;
static const int kEthOverhead = 20; static const int kEthOverhead = 20;
uint Port::newStreamId() uint Port::newStreamId()
{ {
// We use a monotonically increasing class variable to generate
// unique id. To ensure that we take into account the already
// existing ids at drone, we update this variable to be greater
// than the last id that we fetched
// FIXME: In some cases e.g. wrap around or more likely multiple
// clients, we will still run into trouble - to fix this we should
// check here that the same id does not already exist; but currently
// we can only do a linear search - fix this when the lookup/search
// is optimized
return mAllocStreamId++; return mAllocStreamId++;
} }
@ -55,6 +66,11 @@ Port::~Port()
delete mStreams.takeFirst(); delete mStreams.takeFirst();
} }
void Port::protoDataCopyInto(OstProto::Port *data)
{
data->CopyFrom(d);
}
void Port::updatePortConfig(OstProto::Port *port) void Port::updatePortConfig(OstProto::Port *port)
{ {
bool recalc = false; bool recalc = false;
@ -65,6 +81,10 @@ void Port::updatePortConfig(OstProto::Port *port)
d.MergeFrom(*port); d.MergeFrom(*port);
// Setup a user-friendly alias for Win32 ports
if (name().startsWith("\\Device\\NPF_"))
setAlias(QString("if%1").arg(id()));
if (recalc) if (recalc)
recalculateAverageRates(); recalculateAverageRates();
} }
@ -408,6 +428,70 @@ void Port::getModifiedStreamsSinceLastSync(
qDebug("Done %s", __FUNCTION__); qDebug("Done %s", __FUNCTION__);
} }
void Port::getDeletedDeviceGroupsSinceLastSync(
OstProto::DeviceGroupIdList &deviceGroupIdList)
{
deviceGroupIdList.clear_device_group_id();
foreach(int id, lastSyncDeviceGroupList_) {
if (!deviceGroupById(id))
deviceGroupIdList.add_device_group_id()->set_id(id);
}
}
void Port::getNewDeviceGroupsSinceLastSync(
OstProto::DeviceGroupIdList &deviceGroupIdList)
{
deviceGroupIdList.clear_device_group_id();
foreach(OstProto::DeviceGroup *dg, deviceGroups_) {
quint32 dgid = dg->device_group_id().id();
if (!lastSyncDeviceGroupList_.contains(dgid))
deviceGroupIdList.add_device_group_id()->set_id(dgid);
}
}
void Port::getModifiedDeviceGroupsSinceLastSync(
OstProto::DeviceGroupConfigList &deviceGroupConfigList)
{
deviceGroupConfigList.clear_device_group();
foreach(quint32 id, modifiedDeviceGroupList_)
deviceGroupConfigList.add_device_group()
->CopyFrom(*deviceGroupById(id));
}
/*!
* Finds the user modifiable fields in 'config' that are different from
* the current configuration on the port and modifes 'config' such that
* only those fields are set and returns true. If 'config' is same as
* current port config, 'config' is unmodified and false is returned
*/
bool Port::modifiablePortConfig(OstProto::Port &config) const
{
bool change = false;
OstProto::Port modCfg;
if (config.is_exclusive_control() != d.is_exclusive_control()) {
modCfg.set_is_exclusive_control(config.is_exclusive_control());
change = true;
}
if (config.transmit_mode() != d.transmit_mode()) {
modCfg.set_transmit_mode(config.transmit_mode());
change = true;
}
if (config.user_name() != d.user_name()) {
modCfg.set_user_name(config.user_name());
change = true;
}
if (change) {
modCfg.mutable_port_id()->set_id(id());
config.CopyFrom(modCfg);
return true;
}
return false;
}
void Port::when_syncComplete() void Port::when_syncComplete()
{ {
//reorderStreamsByOrdinals(); //reorderStreamsByOrdinals();
@ -415,6 +499,13 @@ void Port::when_syncComplete()
mLastSyncStreamList.clear(); mLastSyncStreamList.clear();
for (int i=0; i<mStreams.size(); i++) for (int i=0; i<mStreams.size(); i++)
mLastSyncStreamList.append(mStreams[i]->id()); mLastSyncStreamList.append(mStreams[i]->id());
lastSyncDeviceGroupList_.clear();
for (int i = 0; i < deviceGroups_.size(); i++) {
lastSyncDeviceGroupList_.append(
deviceGroups_.at(i)->device_group_id().id());
}
modifiedDeviceGroupList_.clear();
} }
void Port::updateStats(OstProto::PortStats *portStats) void Port::updateStats(OstProto::PortStats *portStats)
@ -462,10 +553,12 @@ bool Port::openStreams(QString fileName, bool append, QString &error)
QDialog *optDialog; QDialog *optDialog;
QProgressDialog progress("Opening Streams", "Cancel", 0, 0, mainWindow); QProgressDialog progress("Opening Streams", "Cancel", 0, 0, mainWindow);
OstProto::StreamConfigList streams; OstProto::StreamConfigList streams;
AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromFile(fileName); StreamFileFormat *fmt = StreamFileFormat::fileFormatFromFile(fileName);
if (fmt == NULL) if (fmt == NULL) {
error = tr("Unknown streams file format");
goto _fail; goto _fail;
}
if ((optDialog = fmt->openOptionsDialog())) if ((optDialog = fmt->openOptionsDialog()))
{ {
@ -490,12 +583,12 @@ bool Port::openStreams(QString fileName, bool append, QString &error)
connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int))); connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int)));
connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel())); connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel()));
fmt->openStreamsOffline(fileName, streams, error); fmt->openAsync(fileName, streams, error);
qDebug("after open offline"); qDebug("after open async");
while (!fmt->isFinished()) while (!fmt->isFinished())
qApp->processEvents(); qApp->processEvents();
qDebug("wait over for offline operation"); qDebug("wait over for async operation");
if (!fmt->result()) if (!fmt->result())
goto _fail; goto _fail;
@ -549,7 +642,7 @@ bool Port::saveStreams(QString fileName, QString fileType, QString &error)
{ {
bool ret = false; bool ret = false;
QProgressDialog progress("Saving Streams", "Cancel", 0, 0, mainWindow); QProgressDialog progress("Saving Streams", "Cancel", 0, 0, mainWindow);
AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromType(fileType); StreamFileFormat *fmt = StreamFileFormat::fileFormatFromType(fileType);
OstProto::StreamConfigList streams; OstProto::StreamConfigList streams;
if (fmt == NULL) if (fmt == NULL)
@ -583,12 +676,12 @@ bool Port::saveStreams(QString fileName, QString fileType, QString &error)
qApp->processEvents(); qApp->processEvents();
} }
fmt->saveStreamsOffline(streams, fileName, error); fmt->saveAsync(streams, fileName, error);
qDebug("after save offline"); qDebug("after save async");
while (!fmt->isFinished()) while (!fmt->isFinished())
qApp->processEvents(); qApp->processEvents();
qDebug("wait over for offline operation"); qDebug("wait over for async operation");
ret = fmt->result(); ret = fmt->result();
goto _exit; goto _exit;
@ -605,3 +698,243 @@ _exit:
mainWindow->setEnabled(true); mainWindow->setEnabled(true);
return ret; return ret;
} }
// ------------ Device Group ----------- //
uint Port::newDeviceGroupId()
{
// We use a monotonically increasing class variable to generate
// unique id. To ensure that we take into account the already
// existing ids at drone, we update this variable to be greater
// than the last id that we fetched
// FIXME: In some cases e.g. wrap around or more likely multiple
// clients, we will still run into trouble - to fix this we should
// check here that the same id does not already exist; but currently
// we can only do a linear search - fix this when the lookup/search
// is optimized
return allocDeviceGroupId_++;
}
int Port::numDeviceGroups() const
{
return deviceGroups_.size();
}
const OstProto::DeviceGroup* Port::deviceGroupByIndex(int index) const
{
if ((index < 0) || (index >= numDeviceGroups())) {
qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__,
index, numDeviceGroups() - 1);
return NULL;
}
return deviceGroups_.at(index);
}
OstProto::DeviceGroup* Port::mutableDeviceGroupByIndex(int index)
{
if ((index < 0) || (index >= numDeviceGroups())) {
qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__,
index, numDeviceGroups() - 1);
return NULL;
}
OstProto::DeviceGroup *devGrp = deviceGroups_.at(index);
// Caller can modify DeviceGroup - assume she will
modifiedDeviceGroupList_.insert(devGrp->device_group_id().id());
return devGrp;
}
OstProto::DeviceGroup* Port::deviceGroupById(uint deviceGroupId)
{
for (int i = 0; i < deviceGroups_.size(); i++) {
OstProto::DeviceGroup *devGrp = deviceGroups_.at(i);
if (devGrp->device_group_id().id() == deviceGroupId)
return devGrp;
}
return NULL;
}
bool Port::newDeviceGroupAt(int index, const OstProto::DeviceGroup *deviceGroup)
{
if (index < 0 || index > numDeviceGroups())
return false;
OstProto::DeviceGroup *devGrp = newDeviceGroup(id());
if (!devGrp) {
qWarning("failed allocating a new device group");
return false;
}
if (deviceGroup)
devGrp->CopyFrom(*deviceGroup);
devGrp->mutable_device_group_id()->set_id(newDeviceGroupId());
deviceGroups_.insert(index, devGrp);
modifiedDeviceGroupList_.insert(devGrp->device_group_id().id());
return true;
}
bool Port::deleteDeviceGroupAt(int index)
{
if (index < 0 || index >= deviceGroups_.size())
return false;
OstProto::DeviceGroup *devGrp = deviceGroups_.takeAt(index);
modifiedDeviceGroupList_.remove(devGrp->device_group_id().id());
delete devGrp;
return true;
}
bool Port::insertDeviceGroup(uint deviceGroupId)
{
OstProto::DeviceGroup *devGrp;
if (deviceGroupById(deviceGroupId)) {
qDebug("%s: deviceGroup id %u already exists", __FUNCTION__,
deviceGroupId);
return false;
}
devGrp = newDeviceGroup(id());
devGrp->mutable_device_group_id()->set_id(deviceGroupId);
deviceGroups_.append(devGrp);
// Update allocDeviceGroupId_ to take into account the deviceGroup id
// received from server - this is required to make sure newly allocated
// deviceGroup ids are unique
if (allocDeviceGroupId_ <= deviceGroupId)
allocDeviceGroupId_ = deviceGroupId + 1;
return true;
}
bool Port::updateDeviceGroup(
uint deviceGroupId,
OstProto::DeviceGroup *deviceGroup)
{
OstProto::DeviceGroup *devGrp = deviceGroupById(deviceGroupId);
if (!devGrp) {
qDebug("%s: deviceGroup id %u does not exist", __FUNCTION__,
deviceGroupId);
return false;
}
devGrp->CopyFrom(*deviceGroup);
return true;
}
// ------------ Devices ----------- //
int Port::numDevices()
{
return devices_.size();
}
const OstEmul::Device* Port::deviceByIndex(int index)
{
if ((index < 0) || (index >= numDevices())) {
qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__,
index, numDevices() - 1);
return NULL;
}
return devices_.at(index);
}
void Port::clearDeviceList()
{
while (devices_.size())
delete devices_.takeFirst();
}
void Port::insertDevice(const OstEmul::Device &device)
{
OstEmul::Device *dev = new OstEmul::Device(device);
devices_.append(dev);
}
// ------------- Device Neighbors (ARP/NDP) ------------- //
const OstEmul::DeviceNeighborList* Port::deviceNeighbors(int deviceIndex)
{
if ((deviceIndex < 0) || (deviceIndex >= numDevices())) {
qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__,
deviceIndex, numDevices() - 1);
return NULL;
}
return deviceNeighbors_.value(deviceIndex);
}
int Port::numArp(int deviceIndex)
{
if (deviceNeighbors_.contains(deviceIndex))
return deviceNeighbors_.value(deviceIndex)->arp_size();
return 0;
}
int Port::numArpResolved(int deviceIndex)
{
if (arpResolvedCount_.contains(deviceIndex))
return arpResolvedCount_.value(deviceIndex);
return 0;
}
int Port::numNdp(int deviceIndex)
{
if (deviceNeighbors_.contains(deviceIndex))
return deviceNeighbors_.value(deviceIndex)->ndp_size();
return 0;
}
int Port::numNdpResolved(int deviceIndex)
{
if (ndpResolvedCount_.contains(deviceIndex))
return ndpResolvedCount_.value(deviceIndex);
return 0;
}
void Port::clearDeviceNeighbors()
{
arpResolvedCount_.clear();
ndpResolvedCount_.clear();
qDeleteAll(deviceNeighbors_);
deviceNeighbors_.clear();
}
void Port::insertDeviceNeighbors(const OstEmul::DeviceNeighborList &neighList)
{
int count;
OstEmul::DeviceNeighborList *neighbors =
new OstEmul::DeviceNeighborList(neighList);
deviceNeighbors_.insert(neighList.device_index(), neighbors);
count = 0;
for (int i = 0; i < neighbors->arp_size(); i++)
if (neighbors->arp(i).mac())
count++;
arpResolvedCount_.insert(neighbors->device_index(), count);
count = 0;
for (int i = 0; i < neighbors->ndp_size(); i++)
if (neighbors->ndp(i).mac())
count++;
ndpResolvedCount_.insert(neighbors->device_index(), count);
}
void Port::deviceInfoRefreshed()
{
emit deviceInfoChanged();
}

View File

@ -21,19 +21,26 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#define _PORT_H #define _PORT_H
#include <QDir> #include <QDir>
#include <QHash>
#include <QList> #include <QList>
#include <QSet>
#include <QString> #include <QString>
#include <QTemporaryFile> #include <QTemporaryFile>
#include "stream.h" #include "stream.h"
//class StreamModel; //class StreamModel;
namespace OstEmul {
class Device;
class DeviceNeighborList;
}
class Port : public QObject { class Port : public QObject {
Q_OBJECT Q_OBJECT
static uint mAllocStreamId; static uint mAllocStreamId;
static uint allocDeviceGroupId_;
OstProto::Port d; OstProto::Port d;
OstProto::PortStats stats; OstProto::PortStats stats;
@ -51,6 +58,14 @@ class Port : public QObject {
QList<quint32> mLastSyncStreamList; QList<quint32> mLastSyncStreamList;
QList<Stream*> mStreams; // sorted by stream's ordinal value QList<Stream*> mStreams; // sorted by stream's ordinal value
QList<quint32> lastSyncDeviceGroupList_;
QSet<quint32> modifiedDeviceGroupList_;
QList<OstProto::DeviceGroup*> deviceGroups_;
QList<OstEmul::Device*> devices_;
QHash<quint32, OstEmul::DeviceNeighborList*> deviceNeighbors_;
QHash<quint32, quint32> arpResolvedCount_;
QHash<quint32, quint32> ndpResolvedCount_;
uint newStreamId(); uint newStreamId();
void updateStreamOrdinalsFromIndex(); void updateStreamOrdinalsFromIndex();
void reorderStreamsByOrdinals(); void reorderStreamsByOrdinals();
@ -64,7 +79,8 @@ public:
~Port(); ~Port();
quint32 portGroupId() const { return mPortGroupId; } quint32 portGroupId() const { return mPortGroupId; }
const QString& userAlias() const { return mUserAlias; } const QString userAlias() const
{ return mUserAlias.isEmpty() ? name() : mUserAlias; }
quint32 id() const quint32 id() const
{ return d.port_id().id(); } { return d.port_id().id(); }
@ -88,7 +104,7 @@ public:
{ return avgBitsPerSec_; } { return avgBitsPerSec_; }
//void setAdminEnable(AdminStatus status) { mAdminStatus = status; } //void setAdminEnable(AdminStatus status) { mAdminStatus = status; }
void setAlias(QString &alias) { mUserAlias = alias; } void setAlias(QString alias) { mUserAlias = alias; }
//void setExclusive(bool flag); //void setExclusive(bool flag);
int numStreams() { return mStreams.size(); } int numStreams() { return mStreams.size(); }
@ -111,6 +127,8 @@ public:
return capFile_; return capFile_;
} }
void protoDataCopyInto(OstProto::Port *data);
// FIXME(MED): naming inconsistency - PortConfig/Stream; also retVal // FIXME(MED): naming inconsistency - PortConfig/Stream; also retVal
void updatePortConfig(OstProto::Port *port); void updatePortConfig(OstProto::Port *port);
@ -131,6 +149,15 @@ public:
void getModifiedStreamsSinceLastSync( void getModifiedStreamsSinceLastSync(
OstProto::StreamConfigList &streamConfigList); OstProto::StreamConfigList &streamConfigList);
void getDeletedDeviceGroupsSinceLastSync(
OstProto::DeviceGroupIdList &streamIdList);
void getNewDeviceGroupsSinceLastSync(
OstProto::DeviceGroupIdList &streamIdList);
void getModifiedDeviceGroupsSinceLastSync(
OstProto::DeviceGroupConfigList &streamConfigList);
bool modifiablePortConfig(OstProto::Port &config) const;
void when_syncComplete(); void when_syncComplete();
void setAveragePacketRate(double packetsPerSec); void setAveragePacketRate(double packetsPerSec);
@ -145,10 +172,54 @@ public:
bool openStreams(QString fileName, bool append, QString &error); bool openStreams(QString fileName, bool append, QString &error);
bool saveStreams(QString fileName, QString fileType, QString &error); bool saveStreams(QString fileName, QString fileType, QString &error);
// ------------ Device Group ----------- //
uint newDeviceGroupId();
int numDeviceGroups() const;
const OstProto::DeviceGroup* deviceGroupByIndex(int index) const;
OstProto::DeviceGroup* mutableDeviceGroupByIndex(int index);
OstProto::DeviceGroup* deviceGroupById(uint deviceGroupId);
//! Used by StreamModel
//@{
bool newDeviceGroupAt(int index,
const OstProto::DeviceGroup *deviceGroup = NULL);
bool deleteDeviceGroupAt(int index);
//@}
//! Used by MyService::Stub to update from config received from server
//@{
bool insertDeviceGroup(uint deviceGroupId);
bool updateDeviceGroup(uint deviceGroupId,
OstProto::DeviceGroup *deviceGroup);
//@}
// ------------ Device ----------- //
int numDevices();
const OstEmul::Device* deviceByIndex(int index);
//! Used by MyService::Stub to update from config received from server
void clearDeviceList();
void insertDevice(const OstEmul::Device &device);
const OstEmul::DeviceNeighborList* deviceNeighbors(int deviceIndex);
int numArp(int deviceIndex);
int numArpResolved(int deviceIndex);
int numNdp(int deviceIndex);
int numNdpResolved(int deviceIndex);
//! Used by MyService::Stub to update from config received from server
void clearDeviceNeighbors();
void insertDeviceNeighbors(const OstEmul::DeviceNeighborList &neighList);
void deviceInfoRefreshed();
signals: signals:
void portRateChanged(int portGroupId, int portId); void portRateChanged(int portGroupId, int portId);
void portDataChanged(int portGroupId, int portId); void portDataChanged(int portGroupId, int portId);
void streamListChanged(int portGroupId, int portId); void streamListChanged(int portGroupId, int portId);
void deviceInfoChanged();
}; };

View File

@ -21,6 +21,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "settings.h" #include "settings.h"
#include "emulproto.pb.h"
#include "fileformat.pb.h"
#include <QApplication> #include <QApplication>
#include <QCursor> #include <QCursor>
#include <QMainWindow> #include <QMainWindow>
@ -48,6 +51,8 @@ PortGroup::PortGroup(QString serverName, quint16 port)
statsController = new PbRpcController(portIdList_, portStatsList_); statsController = new PbRpcController(portIdList_, portStatsList_);
isGetStatsPending_ = false; isGetStatsPending_ = false;
atConnectConfig_ = NULL;
compat = kUnknown; compat = kUnknown;
reconnect = false; reconnect = false;
@ -89,8 +94,39 @@ PortGroup::~PortGroup()
delete serviceStub; delete serviceStub;
delete rpcChannel; delete rpcChannel;
delete statsController; delete statsController;
delete atConnectConfig_;
} }
void PortGroup::setConfigAtConnect(const OstProto::PortGroupContent *config)
{
if (!config) {
delete atConnectConfig_;
atConnectConfig_ = NULL;
return;
}
if (!atConnectConfig_)
atConnectConfig_ = new OstProto::PortGroupContent;
atConnectConfig_->CopyFrom(*config);
}
int PortGroup::numReservedPorts() const
{
int count = 0;
for (int i = 0; i < mPorts.size(); i++)
{
if (!mPorts[i]->userName().isEmpty())
count++;
}
return count;
}
const QString PortGroup::serverFullName() const
{
return serverPort() == DEFAULT_SERVER_PORT ?
serverName() : QString("%1:%2").arg(serverName()).arg(serverPort());
}
// ------------------------------------------------ // ------------------------------------------------
// Slots // Slots
@ -187,6 +223,7 @@ void PortGroup::on_rpcChannel_disconnected()
while (!mPorts.isEmpty()) while (!mPorts.isEmpty())
delete mPorts.takeFirst(); delete mPorts.takeFirst();
atConnectPortConfig_.clear();
emit portListChanged(mPortGroupId); emit portListChanged(mPortGroupId);
emit portGroupDataChanged(mPortGroupId); emit portGroupDataChanged(mPortGroupId);
@ -304,6 +341,7 @@ void PortGroup::processPortIdList(PbRpcController *controller)
this, SIGNAL(portGroupDataChanged(int, int))); this, SIGNAL(portGroupDataChanged(int, int)));
qDebug("before port append\n"); qDebug("before port append\n");
mPorts.append(p); mPorts.append(p);
atConnectPortConfig_.append(NULL); // will be filled later
} }
emit portListChanged(mPortGroupId); emit portListChanged(mPortGroupId);
@ -365,8 +403,52 @@ void PortGroup::processPortConfigList(PbRpcController *controller)
// design // design
emit portListChanged(mPortGroupId); emit portListChanged(mPortGroupId);
if (numPorts() > 0) if (numPorts() > 0) {
// XXX: The open session code (atConnectConfig_ related) assumes
// the following two RPCs are invoked in the below order
// Any change here without coressponding change in that code
// will break stuff
getDeviceGroupIdList();
getStreamIdList(); getStreamIdList();
}
// Now that we have the port details, let's identify which ports
// need to be re-configured based on atConnectConfig_
if (atConnectConfig_ && numPorts() > 0)
{
QString myself = appSettings->value(kUserKey, kUserDefaultValue)
.toString();
for (int i = 0; i < atConnectConfig_->ports_size(); i++)
{
const OstProto::PortContent *pc = &atConnectConfig_->ports(i);
for (int j = 0; j < mPorts.size(); j++)
{
Port *port = mPorts[j];
if (port->name() == pc->port_config().name().c_str())
{
if (!port->userName().isEmpty() // rsvd?
&& port->userName() != myself) // by someone else?
{
QString warning =
QString("%1 - %2: %3 is reserved by %4.\n\n"
"Port will not be reconfigured.")
.arg(serverFullName())
.arg(j)
.arg(port->userAlias())
.arg(port->userName());
QMessageBox::warning(NULL, tr("Open Session"), warning);
qWarning(qPrintable(warning));
continue;
}
atConnectPortConfig_[j] = pc;
qDebug("port %d will be reconfigured", j);
break;
}
}
}
}
_error_exit: _error_exit:
delete controller; delete controller;
@ -387,6 +469,74 @@ void PortGroup::when_configApply(int portIndex)
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
mainWindow->setDisabled(true); mainWindow->setDisabled(true);
// FIXME: as currently written this code will make unnecessary RPCs
// even if the request contains no data; the fix will need to take
// care to identify when sync is complete
// NOTE: DeviceGroup RPCs are no longer called unnecessarily;
// Stream RPCs need to be fixed similarly
// Also, drone currently updates its packet list at the end of
// modifyStream() implicitly assuming that will be the last API
// called - this will also need to be fixed
//
// Update/Sync DeviceGroups
//
OstProto::DeviceGroupIdList *deviceGroupIdList;
OstProto::DeviceGroupConfigList *deviceGroupConfigList;
bool refreshReqd = false;
qDebug("applying 'deleted deviceGroups' ...");
deviceGroupIdList = new OstProto::DeviceGroupIdList;
deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getDeletedDeviceGroupsSinceLastSync(*deviceGroupIdList);
if (deviceGroupIdList->device_group_id_size()) {
ack = new OstProto::Ack;
controller = new PbRpcController(deviceGroupIdList, ack);
serviceStub->deleteDeviceGroup(controller, deviceGroupIdList, ack,
NewCallback(this, &PortGroup::processDeleteDeviceGroupAck,
controller));
refreshReqd = true;
}
else
delete deviceGroupIdList;
qDebug("applying 'new deviceGroups' ...");
deviceGroupIdList = new OstProto::DeviceGroupIdList;
deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getNewDeviceGroupsSinceLastSync(*deviceGroupIdList);
if (deviceGroupIdList->device_group_id_size()) {
ack = new OstProto::Ack;
controller = new PbRpcController(deviceGroupIdList, ack);
serviceStub->addDeviceGroup(controller, deviceGroupIdList, ack,
NewCallback(this, &PortGroup::processAddDeviceGroupAck,
controller));
refreshReqd = true;
}
else
delete deviceGroupIdList;
qDebug("applying 'modified deviceGroups' ...");
deviceGroupConfigList = new OstProto::DeviceGroupConfigList;
deviceGroupConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getModifiedDeviceGroupsSinceLastSync(
*deviceGroupConfigList);
if (deviceGroupConfigList->device_group_size()) {
ack = new OstProto::Ack;
controller = new PbRpcController(deviceGroupConfigList, ack);
serviceStub->modifyDeviceGroup(controller, deviceGroupConfigList, ack,
NewCallback(this, &PortGroup::processModifyDeviceGroupAck,
portIndex, controller));
refreshReqd = true;
}
else
delete deviceGroupConfigList;
if (refreshReqd)
getDeviceInfo(portIndex);
//
// Update/Sync Streams
//
qDebug("applying 'deleted streams' ..."); qDebug("applying 'deleted streams' ...");
streamIdList = new OstProto::StreamIdList; streamIdList = new OstProto::StreamIdList;
ack = new OstProto::Ack; ack = new OstProto::Ack;
@ -395,7 +545,7 @@ void PortGroup::when_configApply(int portIndex)
streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getDeletedStreamsSinceLastSync(*streamIdList); mPorts[portIndex]->getDeletedStreamsSinceLastSync(*streamIdList);
serviceStub->deleteStream(controller, streamIdList, ack, serviceStub->deleteStream(controller, streamIdList, ack,
NewCallback(this, &PortGroup::processDeleteStreamAck, controller)); NewCallback(this, &PortGroup::processDeleteStreamAck, controller));
qDebug("applying 'new streams' ..."); qDebug("applying 'new streams' ...");
@ -406,7 +556,7 @@ void PortGroup::when_configApply(int portIndex)
streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getNewStreamsSinceLastSync(*streamIdList); mPorts[portIndex]->getNewStreamsSinceLastSync(*streamIdList);
serviceStub->addStream(controller, streamIdList, ack, serviceStub->addStream(controller, streamIdList, ack,
NewCallback(this, &PortGroup::processAddStreamAck, controller)); NewCallback(this, &PortGroup::processAddStreamAck, controller));
qDebug("applying 'modified streams' ..."); qDebug("applying 'modified streams' ...");
@ -417,9 +567,29 @@ void PortGroup::when_configApply(int portIndex)
streamConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id()); streamConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getModifiedStreamsSinceLastSync(*streamConfigList); mPorts[portIndex]->getModifiedStreamsSinceLastSync(*streamConfigList);
serviceStub->modifyStream(controller, streamConfigList, ack, serviceStub->modifyStream(controller, streamConfigList, ack,
NewCallback(this, &PortGroup::processModifyStreamAck, NewCallback(this, &PortGroup::processModifyStreamAck,
portIndex, controller)); portIndex, controller));
}
void PortGroup::processAddDeviceGroupAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
delete controller;
}
void PortGroup::processDeleteDeviceGroupAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
delete controller;
}
void PortGroup::processModifyDeviceGroupAck(int portIndex,
PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
delete controller;
} }
void PortGroup::processAddStreamAck(PbRpcController *controller) void PortGroup::processAddStreamAck(PbRpcController *controller)
@ -434,7 +604,7 @@ void PortGroup::processDeleteStreamAck(PbRpcController *controller)
delete controller; delete controller;
} }
void PortGroup::processModifyStreamAck(int portIndex, void PortGroup::processModifyStreamAck(int portIndex,
PbRpcController *controller) PbRpcController *controller)
{ {
qDebug("In %s", __FUNCTION__); qDebug("In %s", __FUNCTION__);
@ -444,7 +614,106 @@ void PortGroup::processModifyStreamAck(int portIndex,
mainWindow->setEnabled(true); mainWindow->setEnabled(true);
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
delete controller;
}
void PortGroup::getDeviceInfo(int portIndex)
{
OstProto::PortId *portId;
OstProto::PortDeviceList *deviceList;
OstProto::PortNeighborList *neighList;
PbRpcController *controller;
Q_ASSERT(portIndex < mPorts.size());
if (state() != QAbstractSocket::ConnectedState)
return;
portId = new OstProto::PortId;
portId->set_id(mPorts[portIndex]->id());
deviceList = new OstProto::PortDeviceList;
controller = new PbRpcController(portId, deviceList);
serviceStub->getDeviceList(controller, portId, deviceList,
NewCallback(this, &PortGroup::processDeviceList,
portIndex, controller));
portId = new OstProto::PortId;
portId->set_id(mPorts[portIndex]->id());
neighList = new OstProto::PortNeighborList;
controller = new PbRpcController(portId, neighList);
serviceStub->getDeviceNeighbors(controller, portId, neighList,
NewCallback(this, &PortGroup::processDeviceNeighbors,
portIndex, controller));
}
void PortGroup::processDeviceList(int portIndex, PbRpcController *controller)
{
OstProto::PortDeviceList *deviceList
= static_cast<OstProto::PortDeviceList*>(controller->response());
qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex);
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
goto _exit;
}
Q_ASSERT(portIndex < numPorts());
if (deviceList->port_id().id() != mPorts[portIndex]->id())
{
qDebug("Invalid portId %d (expected %d) received for portIndex %d",
deviceList->port_id().id(), mPorts[portIndex]->id(), portIndex);
goto _exit;
}
mPorts[portIndex]->clearDeviceList();
for(int i = 0; i < deviceList->ExtensionSize(OstEmul::device); i++) {
mPorts[portIndex]->insertDevice(
deviceList->GetExtension(OstEmul::device, i));
}
_exit:
delete controller;
}
void PortGroup::processDeviceNeighbors(
int portIndex, PbRpcController *controller)
{
OstProto::PortNeighborList *neighList
= static_cast<OstProto::PortNeighborList*>(controller->response());
qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex);
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
goto _exit;
}
Q_ASSERT(portIndex < numPorts());
if (neighList->port_id().id() != mPorts[portIndex]->id())
{
qDebug("Invalid portId %d (expected %d) received for portIndex %d",
neighList->port_id().id(), mPorts[portIndex]->id(), portIndex);
goto _exit;
}
mPorts[portIndex]->clearDeviceNeighbors();
for(int i=0; i < neighList->ExtensionSize(OstEmul::device_neighbor); i++) {
mPorts[portIndex]->insertDeviceNeighbors(
neighList->GetExtension(OstEmul::device_neighbor, i));
}
mPorts[portIndex]->deviceInfoRefreshed();
_exit:
delete controller; delete controller;
} }
@ -466,10 +735,10 @@ void PortGroup::modifyPort(int portIndex, OstProto::Port portConfig)
PbRpcController *controller = new PbRpcController(portConfigList, ack); PbRpcController *controller = new PbRpcController(portConfigList, ack);
serviceStub->modifyPort(controller, portConfigList, ack, serviceStub->modifyPort(controller, portConfigList, ack,
NewCallback(this, &PortGroup::processModifyPortAck, controller)); NewCallback(this, &PortGroup::processModifyPortAck, true, controller));
} }
void PortGroup::processModifyPortAck(PbRpcController *controller) void PortGroup::processModifyPortAck(bool restoreUi,PbRpcController *controller)
{ {
qDebug("In %s", __FUNCTION__); qDebug("In %s", __FUNCTION__);
@ -479,8 +748,10 @@ void PortGroup::processModifyPortAck(PbRpcController *controller)
qPrintable(controller->ErrorString())); qPrintable(controller->ErrorString()));
} }
mainWindow->setEnabled(true); if (restoreUi) {
QApplication::restoreOverrideCursor(); mainWindow->setEnabled(true);
QApplication::restoreOverrideCursor();
}
delete controller; delete controller;
} }
@ -534,6 +805,8 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
{ {
OstProto::StreamIdList *streamIdList OstProto::StreamIdList *streamIdList
= static_cast<OstProto::StreamIdList*>(controller->response()); = static_cast<OstProto::StreamIdList*>(controller->response());
const OstProto::PortContent *newPortContent
= atConnectPortConfig_.at(portIndex);
qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex); qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex);
@ -553,50 +826,192 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
goto _exit; goto _exit;
} }
for(int i = 0; i < streamIdList->stream_id_size(); i++) if (newPortContent)
{ {
uint streamId; // This port needs to configured with new content - to do this
// we'll insert the following RPC sequence at this point and once
// this sequence is over, return to the regular RPC sequence by
// re-requesting getStreamId()
// * delete (existing) deviceGroups
// (already done by processDeviceIdList)
// * delete (existing) streams
// * modify port
// * add (new) deviceGroup ids
// * modify (new) deviceGroups
// * add (new) stream ids
// * modify (new) streams
// XXX: This assumes getDeviceGroupIdList() was invoked before
// getStreamIdList() - if the order changes this code will break!
streamId = streamIdList->stream_id(i).id(); // XXX: same name as input param, but shouldn't cause any problem
mPorts[portIndex]->insertStream(streamId); PbRpcController *controller;
quint32 portId = mPorts[portIndex]->id();
QString myself = appSettings->value(kUserKey, kUserDefaultValue)
.toString();
// delete all existing streams
if (streamIdList->stream_id_size())
{
OstProto::StreamIdList *streamIdList2 = new OstProto::StreamIdList;
streamIdList2->CopyFrom(*streamIdList);
OstProto::Ack *ack = new OstProto::Ack;
controller = new PbRpcController(streamIdList2, ack);
serviceStub->deleteStream(controller, streamIdList2, ack,
NewCallback(this, &PortGroup::processDeleteStreamAck,
controller));
}
OstProto::Port portCfg = newPortContent->port_config();
if (mPorts[portIndex]->modifiablePortConfig(portCfg))
{
OstProto::PortConfigList *portConfigList =
new OstProto::PortConfigList;
OstProto::Port *port = portConfigList->add_port();
port->CopyFrom(portCfg);
if (port->has_user_name())
port->set_user_name(qPrintable(myself)); // overwrite
OstProto::Ack *ack = new OstProto::Ack;
controller = new PbRpcController(portConfigList, ack);
serviceStub->modifyPort(controller, portConfigList, ack,
NewCallback(this, &PortGroup::processModifyPortAck,
false, controller));
}
// add/modify deviceGroups
if (newPortContent->device_groups_size())
{
OstProto::DeviceGroupIdList *deviceGroupIdList
= new OstProto::DeviceGroupIdList;
OstProto::DeviceGroupConfigList *deviceGroupConfigList
= new OstProto::DeviceGroupConfigList;
deviceGroupIdList->mutable_port_id()->set_id(portId);
deviceGroupConfigList->mutable_port_id()->set_id(portId);
for (int i = 0; i < newPortContent->device_groups_size(); i++)
{
const OstProto::DeviceGroup &dg
= newPortContent->device_groups(i);
deviceGroupIdList->add_device_group_id()->set_id(
dg.device_group_id().id());
deviceGroupConfigList->add_device_group()->CopyFrom(dg);
}
OstProto::Ack *ack = new OstProto::Ack;
controller = new PbRpcController(deviceGroupIdList, ack);
serviceStub->addDeviceGroup(controller, deviceGroupIdList, ack,
NewCallback(this, &PortGroup::processAddDeviceGroupAck,
controller));
ack = new OstProto::Ack;
controller = new PbRpcController(deviceGroupConfigList, ack);
serviceStub->modifyDeviceGroup(controller,
deviceGroupConfigList, ack,
NewCallback(this, &PortGroup::processModifyDeviceGroupAck,
portIndex, controller));
}
// add/modify streams
if (newPortContent->streams_size())
{
OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList;
OstProto::StreamConfigList *streamConfigList =
new OstProto::StreamConfigList;
streamIdList->mutable_port_id()->set_id(portId);
streamConfigList->mutable_port_id()->set_id(portId);
for (int i = 0; i < newPortContent->streams_size(); i++)
{
const OstProto::Stream &s = newPortContent->streams(i);
streamIdList->add_stream_id()->set_id(s.stream_id().id());
streamConfigList->add_stream()->CopyFrom(s);
}
OstProto::Ack *ack = new OstProto::Ack;
controller = new PbRpcController(streamIdList, ack);
serviceStub->addStream(controller, streamIdList, ack,
NewCallback(this, &PortGroup::processAddStreamAck,
controller));
ack = new OstProto::Ack;
controller = new PbRpcController(streamConfigList, ack);
serviceStub->modifyStream(controller, streamConfigList, ack,
NewCallback(this, &PortGroup::processModifyStreamAck,
portIndex, controller));
}
// delete newPortConfig
atConnectPortConfig_[portIndex] = NULL;
// return to normal sequence re-starting from
// getDeviceGroupIdList() and getStreamIdList()
OstProto::PortId *portId2 = new OstProto::PortId;
portId2->set_id(portId);
OstProto::DeviceGroupIdList *devGrpIdList
= new OstProto::DeviceGroupIdList;
controller = new PbRpcController(portId2, devGrpIdList);
serviceStub->getDeviceGroupIdList(controller, portId2, devGrpIdList,
NewCallback(this, &PortGroup::processDeviceGroupIdList,
portIndex, controller));
portId2 = new OstProto::PortId;
portId2->set_id(portId);
OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList;
controller = new PbRpcController(portId2, streamIdList);
serviceStub->getStreamIdList(controller, portId2, streamIdList,
NewCallback(this, &PortGroup::processStreamIdList,
portIndex, controller));
} }
else
mPorts[portIndex]->when_syncComplete();
// Are we done for all ports?
if (numPorts() && portIndex >= (numPorts()-1))
{ {
// FIXME(HI): some way to reset streammodel for(int i = 0; i < streamIdList->stream_id_size(); i++)
getStreamConfigList(); {
uint streamId;
streamId = streamIdList->stream_id(i).id();
mPorts[portIndex]->insertStream(streamId);
}
mPorts[portIndex]->when_syncComplete();
getStreamConfigList(portIndex);
} }
_exit: _exit:
delete controller; delete controller;
} }
void PortGroup::getStreamConfigList() void PortGroup::getStreamConfigList(int portIndex)
{ {
qDebug("requesting stream config list ..."); if (mPorts[portIndex]->numStreams() == 0)
return;
for (int portIndex = 0; portIndex < numPorts(); portIndex++) qDebug("requesting stream config list (port %d)...", portIndex);
OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList;
OstProto::StreamConfigList *streamConfigList
= new OstProto::StreamConfigList;
PbRpcController *controller = new PbRpcController(
streamIdList, streamConfigList);
streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
for (int j = 0; j < mPorts[portIndex]->numStreams(); j++)
{ {
OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; OstProto::StreamId *s = streamIdList->add_stream_id();
OstProto::StreamConfigList *streamConfigList s->set_id(mPorts[portIndex]->streamByIndex(j)->id());
= new OstProto::StreamConfigList;
PbRpcController *controller = new PbRpcController(
streamIdList, streamConfigList);
streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
for (int j = 0; j < mPorts[portIndex]->numStreams(); j++)
{
OstProto::StreamId *s = streamIdList->add_stream_id();
s->set_id(mPorts[portIndex]->streamByIndex(j)->id());
}
serviceStub->getStreamConfig(controller, streamIdList, streamConfigList,
NewCallback(this, &PortGroup::processStreamConfigList,
portIndex, controller));
} }
serviceStub->getStreamConfig(controller, streamIdList, streamConfigList,
NewCallback(this, &PortGroup::processStreamConfigList,
portIndex, controller));
} }
void PortGroup::processStreamConfigList(int portIndex, void PortGroup::processStreamConfigList(int portIndex,
@ -634,11 +1049,180 @@ void PortGroup::processStreamConfigList(int portIndex,
streamConfigList->mutable_stream(i)); streamConfigList->mutable_stream(i));
} }
#if 0
// FIXME: incorrect check - will never be true if last port does not have any streams configured
// Are we done for all ports? // Are we done for all ports?
if (portIndex >= numPorts()) if (portIndex >= (numPorts()-1))
{ {
// FIXME(HI): some way to reset streammodel // FIXME(HI): some way to reset streammodel
} }
#endif
_exit:
delete controller;
}
void PortGroup::getDeviceGroupIdList()
{
using OstProto::PortId;
using OstProto::DeviceGroupIdList;
for (int portIndex = 0; portIndex < numPorts(); portIndex++)
{
PortId *portId = new PortId;
DeviceGroupIdList *devGrpIdList = new DeviceGroupIdList;
PbRpcController *controller = new PbRpcController(portId, devGrpIdList);
portId->set_id(mPorts[portIndex]->id());
serviceStub->getDeviceGroupIdList(controller, portId, devGrpIdList,
NewCallback(this, &PortGroup::processDeviceGroupIdList,
portIndex, controller));
}
}
void PortGroup::processDeviceGroupIdList(
int portIndex,
PbRpcController *controller)
{
using OstProto::DeviceGroupIdList;
DeviceGroupIdList *devGrpIdList = static_cast<DeviceGroupIdList*>(
controller->response());
const OstProto::PortContent *newPortContent = atConnectPortConfig_.at(
portIndex);
qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex);
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
goto _exit;
}
Q_ASSERT(portIndex < numPorts());
if (devGrpIdList->port_id().id() != mPorts[portIndex]->id())
{
qDebug("Invalid portId %d (expected %d) received for portIndex %d",
devGrpIdList->port_id().id(), mPorts[portIndex]->id(), portIndex);
goto _exit;
}
if (newPortContent)
{
// We delete all existing deviceGroups
// Remaining stuff is done in processStreamIdList() - see notes there
if (devGrpIdList->device_group_id_size())
{
OstProto::DeviceGroupIdList *devGrpIdList2
= new OstProto::DeviceGroupIdList;
devGrpIdList2->CopyFrom(*devGrpIdList);
OstProto::Ack *ack = new OstProto::Ack;
PbRpcController *controller
= new PbRpcController(devGrpIdList2, ack);
serviceStub->deleteDeviceGroup(controller, devGrpIdList2, ack,
NewCallback(this, &PortGroup::processDeleteDeviceGroupAck,
controller));
}
}
else
{
for(int i = 0; i < devGrpIdList->device_group_id_size(); i++)
{
uint devGrpId;
devGrpId = devGrpIdList->device_group_id(i).id();
mPorts[portIndex]->insertDeviceGroup(devGrpId);
}
getDeviceGroupConfigList(portIndex);
}
_exit:
delete controller;
}
void PortGroup::getDeviceGroupConfigList(int portIndex)
{
using OstProto::DeviceGroupId;
using OstProto::DeviceGroupIdList;
using OstProto::DeviceGroupConfigList;
if (mPorts[portIndex]->numDeviceGroups() == 0)
return;
qDebug("requesting device group config list (port %d) ...", portIndex);
DeviceGroupIdList *devGrpIdList = new DeviceGroupIdList;
DeviceGroupConfigList *devGrpCfgList = new DeviceGroupConfigList;
PbRpcController *controller = new PbRpcController(
devGrpIdList, devGrpCfgList);
devGrpIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
for (int j = 0; j < mPorts[portIndex]->numDeviceGroups(); j++)
{
DeviceGroupId *dgid = devGrpIdList->add_device_group_id();
dgid->set_id(mPorts[portIndex]->deviceGroupByIndex(j)
->device_group_id().id());
}
serviceStub->getDeviceGroupConfig(controller,
devGrpIdList, devGrpCfgList,
NewCallback(this, &PortGroup::processDeviceGroupConfigList,
portIndex, controller));
}
void PortGroup::processDeviceGroupConfigList(int portIndex,
PbRpcController *controller)
{
using OstProto::DeviceGroupConfigList;
DeviceGroupConfigList *devGrpCfgList =
static_cast<OstProto::DeviceGroupConfigList*>(controller->response());
qDebug("In %s", __PRETTY_FUNCTION__);
Q_ASSERT(portIndex < numPorts());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
goto _exit;
}
Q_ASSERT(portIndex < numPorts());
if (devGrpCfgList->port_id().id() != mPorts[portIndex]->id())
{
qDebug("Invalid portId %d (expected %d) received for portIndex %d",
devGrpCfgList->port_id().id(), mPorts[portIndex]->id(), portIndex);
goto _exit;
}
for(int i = 0; i < devGrpCfgList->device_group_size(); i++)
{
uint dgid = devGrpCfgList->device_group(i).device_group_id().id();
mPorts[portIndex]->updateDeviceGroup(dgid,
devGrpCfgList->mutable_device_group(i));
}
if (devGrpCfgList->device_group_size())
getDeviceInfo(portIndex);
#if 0
// FIXME: incorrect check - will never be true if last port does not have any deviceGroups configured
// Are we done for all ports?
if (portIndex >= (numPorts()-1))
{
// FIXME: reset deviceGroupModel?
}
#endif
_exit: _exit:
delete controller; delete controller;
@ -842,6 +1426,78 @@ _exit:
delete controller; delete controller;
} }
void PortGroup::resolveDeviceNeighbors(QList<uint> *portList)
{
qDebug("In %s", __FUNCTION__);
if (state() != QAbstractSocket::ConnectedState)
return;
if ((portList == NULL) || (portList->size() == 0))
goto _exit;
{
OstProto::PortIdList *portIdList = new OstProto::PortIdList;
OstProto::Ack *ack = new OstProto::Ack;
PbRpcController *controller = new PbRpcController(portIdList, ack);
for (int i = 0; i < portList->size(); i++)
{
OstProto::PortId *portId = portIdList->add_port_id();
portId->set_id(portList->at(i));
}
serviceStub->resolveDeviceNeighbors(controller, portIdList, ack,
NewCallback(this, &PortGroup::processResolveDeviceNeighborsAck,
controller));
}
_exit:
return;
}
void PortGroup::processResolveDeviceNeighborsAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
delete controller;
}
void PortGroup::clearDeviceNeighbors(QList<uint> *portList)
{
qDebug("In %s", __FUNCTION__);
if (state() != QAbstractSocket::ConnectedState)
return;
if ((portList == NULL) || (portList->size() == 0))
goto _exit;
{
OstProto::PortIdList *portIdList = new OstProto::PortIdList;
OstProto::Ack *ack = new OstProto::Ack;
PbRpcController *controller = new PbRpcController(portIdList, ack);
for (int i = 0; i < portList->size(); i++)
{
OstProto::PortId *portId = portIdList->add_port_id();
portId->set_id(portList->at(i));
}
serviceStub->clearDeviceNeighbors(controller, portIdList, ack,
NewCallback(this, &PortGroup::processClearDeviceNeighborsAck,
controller));
}
_exit:
return;
}
void PortGroup::processClearDeviceNeighborsAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
delete controller;
}
void PortGroup::getPortStats() void PortGroup::getPortStats()
{ {
//qDebug("In %s", __FUNCTION__); //qDebug("In %s", __FUNCTION__);

View File

@ -36,6 +36,11 @@ LOW
#define DEFAULT_SERVER_PORT 7878 #define DEFAULT_SERVER_PORT 7878
namespace OstProto {
class PortContent;
class PortGroupContent;
}
class QFile; class QFile;
class QTimer; class QTimer;
@ -62,6 +67,9 @@ private:
OstProto::PortIdList *portIdList_; OstProto::PortIdList *portIdList_;
OstProto::PortStatsList *portStatsList_; OstProto::PortStatsList *portStatsList_;
OstProto::PortGroupContent *atConnectConfig_;
QList<const OstProto::PortContent*> atConnectPortConfig_;
public: // FIXME(HIGH): member access public: // FIXME(HIGH): member access
QList<Port*> mPorts; QList<Port*> mPorts;
@ -82,7 +90,10 @@ public:
} }
void disconnectFromHost() { reconnect = false; rpcChannel->tearDown(); } void disconnectFromHost() { reconnect = false; rpcChannel->tearDown(); }
void setConfigAtConnect(const OstProto::PortGroupContent *config);
int numPorts() const { return mPorts.size(); } int numPorts() const { return mPorts.size(); }
int numReservedPorts() const;
quint32 id() const { return mPortGroupId; } quint32 id() const { return mPortGroupId; }
const QString& userAlias() const { return mUserAlias; } const QString& userAlias() const { return mUserAlias; }
@ -92,6 +103,7 @@ public:
{ return rpcChannel->serverName(); } { return rpcChannel->serverName(); }
quint16 serverPort() const quint16 serverPort() const
{ return rpcChannel->serverPort(); } { return rpcChannel->serverPort(); }
const QString serverFullName() const;
QAbstractSocket::SocketState state() const { QAbstractSocket::SocketState state() const {
if (compat == kIncompatible) if (compat == kIncompatible)
return QAbstractSocket::SocketState(-1); return QAbstractSocket::SocketState(-1);
@ -106,17 +118,31 @@ public:
void processDeleteStreamAck(PbRpcController *controller); void processDeleteStreamAck(PbRpcController *controller);
void processModifyStreamAck(int portIndex, PbRpcController *controller); void processModifyStreamAck(int portIndex, PbRpcController *controller);
void processAddDeviceGroupAck(PbRpcController *controller);
void processDeleteDeviceGroupAck(PbRpcController *controller);
void processModifyDeviceGroupAck(int portIndex, PbRpcController *controller);
void processDeviceList(int portIndex, PbRpcController *controller);
void processDeviceNeighbors(int portIndex, PbRpcController *controller);
void modifyPort(int portId, OstProto::Port portConfig); void modifyPort(int portId, OstProto::Port portConfig);
void processModifyPortAck(PbRpcController *controller); void processModifyPortAck(bool restoreUi, PbRpcController *controller);
void processUpdatedPortConfig(PbRpcController *controller); void processUpdatedPortConfig(PbRpcController *controller);
void getStreamIdList(); void getStreamIdList();
void processStreamIdList(int portIndex, PbRpcController *controller); void processStreamIdList(int portIndex, PbRpcController *controller);
void getStreamConfigList(); void getStreamConfigList(int portIndex);
void processStreamConfigList(int portIndex, PbRpcController *controller); void processStreamConfigList(int portIndex, PbRpcController *controller);
void processModifyStreamAck(OstProto::Ack *ack); void processModifyStreamAck(OstProto::Ack *ack);
void getDeviceGroupIdList();
void processDeviceGroupIdList(int portIndex, PbRpcController *controller);
void getDeviceGroupConfigList(int portIndex);
void processDeviceGroupConfigList(
int portIndex,
PbRpcController *controller);
void startTx(QList<uint> *portList = NULL); void startTx(QList<uint> *portList = NULL);
void processStartTxAck(PbRpcController *controller); void processStartTxAck(PbRpcController *controller);
void stopTx(QList<uint> *portList = NULL); void stopTx(QList<uint> *portList = NULL);
@ -129,6 +155,11 @@ public:
void viewCapture(QList<uint> *portList = NULL); void viewCapture(QList<uint> *portList = NULL);
void processViewCaptureAck(PbRpcController *controller); void processViewCaptureAck(PbRpcController *controller);
void resolveDeviceNeighbors(QList<uint> *portList = NULL);
void processResolveDeviceNeighborsAck(PbRpcController *controller);
void clearDeviceNeighbors(QList<uint> *portList = NULL);
void processClearDeviceNeighborsAck(PbRpcController *controller);
void getPortStats(); void getPortStats();
void processPortStatsList(); void processPortStatsList();
void clearPortStats(QList<uint> *portList = NULL); void clearPortStats(QList<uint> *portList = NULL);
@ -154,6 +185,7 @@ private slots:
public slots: public slots:
void when_configApply(int portIndex); void when_configApply(int portIndex);
void getDeviceInfo(int portIndex);
}; };

View File

@ -25,7 +25,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
PortGroupList::PortGroupList() PortGroupList::PortGroupList()
: mPortGroupListModel(this), : mPortGroupListModel(this),
mStreamListModel(this), mStreamListModel(this),
mPortStatsModel(this, this) mPortStatsModel(this, this),
mDeviceGroupModel(this),
mDeviceModel(this)
{ {
PortGroup *pg; PortGroup *pg;
@ -33,10 +35,14 @@ PortGroupList::PortGroupList()
streamModelTester_ = NULL; streamModelTester_ = NULL;
portModelTester_ = NULL; portModelTester_ = NULL;
portStatsModelTester_ = NULL; portStatsModelTester_ = NULL;
deviceGroupModelTester_ = NULL;
deviceModelTester_ = NULL;
#else #else
streamModelTester_ = new ModelTest(getStreamModel()); streamModelTester_ = new ModelTest(getStreamModel());
portModelTester_ = new ModelTest(getPortModel()); portModelTester_ = new ModelTest(getPortModel());
portStatsModelTester_ = new ModelTest(getPortStatsModel()); portStatsModelTester_ = new ModelTest(getPortStatsModel());
deviceGroupModelTester_ = new ModelTest(getDeviceGroupModel());
deviceModelTester_ = new ModelTest(getDeviceModel());
#endif #endif
// Add the "Local" Port Group // Add the "Local" Port Group
@ -49,10 +55,10 @@ PortGroupList::~PortGroupList()
delete portStatsModelTester_; delete portStatsModelTester_;
delete portModelTester_; delete portModelTester_;
delete streamModelTester_; delete streamModelTester_;
delete deviceGroupModelTester_;
while (!mPortGroups.isEmpty()) while (!mPortGroups.isEmpty())
delete mPortGroups.takeFirst(); delete mPortGroups.takeFirst();
} }
bool PortGroupList::isPortGroup(const QModelIndex& index) bool PortGroupList::isPortGroup(const QModelIndex& index)
@ -120,6 +126,23 @@ void PortGroupList::removePortGroup(PortGroup &portGroup)
mPortStatsModel.when_portListChanged(); mPortStatsModel.when_portListChanged();
} }
void PortGroupList::removeAllPortGroups()
{
if (mPortGroups.isEmpty())
return;
do {
PortGroup *pg = mPortGroups.at(0);
mPortGroupListModel.portGroupAboutToBeRemoved(pg);
mPortGroups.removeFirst();
delete pg;
} while (!mPortGroups.isEmpty());
mPortGroupListModel.portGroupRemoved();
mPortGroupListModel.when_portListChanged();
mPortStatsModel.when_portListChanged();
}
//.................... //....................
// Private Methods // Private Methods
//.................... //....................

View File

@ -20,12 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#ifndef _PORT_GROUP_LIST_H #ifndef _PORT_GROUP_LIST_H
#define _PORT_GROUP_LIST_H #define _PORT_GROUP_LIST_H
#include "devicegroupmodel.h"
#include "devicemodel.h"
#include "portgroup.h" #include "portgroup.h"
#include <QAbstractItemModel>
#include <QItemSelection>
#include "portmodel.h" #include "portmodel.h"
#include "streammodel.h"
#include "portstatsmodel.h" #include "portstatsmodel.h"
#include "streammodel.h"
class PortModel; class PortModel;
class StreamModel; class StreamModel;
@ -42,10 +42,14 @@ class PortGroupList : public QObject {
PortModel mPortGroupListModel; PortModel mPortGroupListModel;
StreamModel mStreamListModel; StreamModel mStreamListModel;
PortStatsModel mPortStatsModel; PortStatsModel mPortStatsModel;
DeviceGroupModel mDeviceGroupModel;
DeviceModel mDeviceModel;
QObject *streamModelTester_; QObject *streamModelTester_;
QObject *portModelTester_; QObject *portModelTester_;
QObject *portStatsModelTester_; QObject *portStatsModelTester_;
QObject *deviceGroupModelTester_;
QObject *deviceModelTester_;
// Methods // Methods
public: public:
@ -55,6 +59,8 @@ public:
PortModel* getPortModel() { return &mPortGroupListModel; } PortModel* getPortModel() { return &mPortGroupListModel; }
PortStatsModel* getPortStatsModel() { return &mPortStatsModel; } PortStatsModel* getPortStatsModel() { return &mPortStatsModel; }
StreamModel* getStreamModel() { return &mStreamListModel; } StreamModel* getStreamModel() { return &mStreamListModel; }
DeviceGroupModel* getDeviceGroupModel() { return &mDeviceGroupModel; }
DeviceModel* getDeviceModel() { return &mDeviceModel; }
bool isPortGroup(const QModelIndex& index); bool isPortGroup(const QModelIndex& index);
bool isPort(const QModelIndex& index); bool isPort(const QModelIndex& index);
@ -66,6 +72,7 @@ public:
void addPortGroup(PortGroup &portGroup); void addPortGroup(PortGroup &portGroup);
void removePortGroup(PortGroup &portGroup); void removePortGroup(PortGroup &portGroup);
void removeAllPortGroups();
private: private:
int indexOfPortGroup(quint32 portGroupId); int indexOfPortGroup(quint32 portGroupId);

View File

@ -179,7 +179,7 @@ QVariant PortModel::data(const QModelIndex &index, int role) const
return QString("Port %1: %2 %3(%4)") return QString("Port %1: %2 %3(%4)")
.arg(port->id()) .arg(port->id())
.arg(port->name()) .arg(port->userAlias())
.arg(rsvdBy) .arg(rsvdBy)
.arg(port->description()); .arg(port->description());
} }

View File

@ -57,6 +57,7 @@ private:
QIcon portIconFactory[kLinkStatesCount][kExclusiveStatesCount]; QIcon portIconFactory[kLinkStatesCount][kExclusiveStatesCount];
private slots: private slots:
// FIXME: these are invoked from outside - how come they are "private"?
void when_portGroupDataChanged(int portGroupId, int portId); void when_portGroupDataChanged(int portGroupId, int portId);
void portGroupAboutToBeAppended(); void portGroupAboutToBeAppended();

View File

@ -148,6 +148,36 @@ void PortStatsWindow::on_tbViewCapture_clicked()
} }
} }
void PortStatsWindow::on_tbResolveNeighbors_clicked()
{
QList<PortStatsModel::PortGroupAndPortList> portList;
// Get selected ports
model->portListFromIndex(selectedColumns(), portList);
// Resolve ARP/ND for selected ports, portgroup by portgroup
for (int i = 0; i < portList.size(); i++)
{
pgl->portGroupByIndex(portList.at(i).portGroupId).
resolveDeviceNeighbors(&portList[i].portList);
}
}
void PortStatsWindow::on_tbClearNeighbors_clicked()
{
QList<PortStatsModel::PortGroupAndPortList> portList;
// Get selected ports
model->portListFromIndex(selectedColumns(), portList);
// Clear ARP/ND for ports, portgroup by portgroup
for (int i = 0; i < portList.size(); i++)
{
pgl->portGroupByIndex(portList.at(i).portGroupId).
clearDeviceNeighbors(&portList[i].portList);
}
}
void PortStatsWindow::on_tbClear_clicked() void PortStatsWindow::on_tbClear_clicked()
{ {
QList<PortStatsModel::PortGroupAndPortList> portList; QList<PortStatsModel::PortGroupAndPortList> portList;

View File

@ -50,6 +50,9 @@ private slots:
void on_tbClear_clicked(); void on_tbClear_clicked();
void on_tbClearAll_clicked(); void on_tbClearAll_clicked();
void on_tbResolveNeighbors_clicked();
void on_tbClearNeighbors_clicked();
void on_tbFilter_clicked(); void on_tbFilter_clicked();
private: private:

View File

@ -141,6 +141,45 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="tbResolveNeighbors" >
<property name="toolTip" >
<string>Resolve Neighbors</string>
</property>
<property name="statusTip" >
<string>Resolve Device Neighbors on selected port(s)</string>
</property>
<property name="text" >
<string>Resolve Neighbors</string>
</property>
<property name="icon" >
<iconset resource="ostinato.qrc" >:/icons/neighbor_resolve.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tbClearNeighbors" >
<property name="toolTip" >
<string>Clear Neighbors</string>
</property>
<property name="statusTip" >
<string>Clear Device Neighbors on selected port(s)</string>
</property>
<property name="text" >
<string>Clear Neighbors</string>
</property>
<property name="icon" >
<iconset resource="ostinato.qrc" >:/icons/neighbor_clear.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line2" >
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item> <item>
<spacer> <spacer>
<property name="orientation" > <property name="orientation" >

View File

@ -19,18 +19,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portswindow.h" #include "portswindow.h"
#include "abstractfileformat.h" #include "deviceswidget.h"
#include "portconfigdialog.h" #include "portconfigdialog.h"
#include "settings.h" #include "settings.h"
#include "streamconfigdialog.h" #include "streamconfigdialog.h"
#include "streamfileformat.h"
#include "streamlistdelegate.h" #include "streamlistdelegate.h"
#include "fileformat.pb.h"
#include <QFileInfo> #include <QFileInfo>
#include <QInputDialog> #include <QInputDialog>
#include <QItemSelectionModel> #include <QItemSelectionModel>
#include <QMainWindow>
#include <QMessageBox> #include <QMessageBox>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
extern QMainWindow *mainWindow;
PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
: QWidget(parent), proxyPortModel(NULL) : QWidget(parent), proxyPortModel(NULL)
{ {
@ -44,6 +50,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
plm = pgl; plm = pgl;
setupUi(this); setupUi(this);
devicesWidget->setPortGroupList(plm);
tvPortList->header()->hide(); tvPortList->header()->hide();
@ -61,7 +68,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvPortList->addAction(actionExclusive_Control); tvPortList->addAction(actionExclusive_Control);
tvPortList->addAction(actionPort_Configuration); tvPortList->addAction(actionPort_Configuration);
// Populate StramList Context Menu Actions // Populate StreamList Context Menu Actions
tvStreamList->addAction(actionNew_Stream); tvStreamList->addAction(actionNew_Stream);
tvStreamList->addAction(actionEdit_Stream); tvStreamList->addAction(actionEdit_Stream);
tvStreamList->addAction(actionDuplicate_Stream); tvStreamList->addAction(actionDuplicate_Stream);
@ -74,12 +81,17 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvStreamList->addAction(actionOpen_Streams); tvStreamList->addAction(actionOpen_Streams);
tvStreamList->addAction(actionSave_Streams); tvStreamList->addAction(actionSave_Streams);
// PortList and StreamList actions combined make this window's actions // PortList, StreamList, DeviceWidget actions combined
// make this window's actions
addActions(tvPortList->actions()); addActions(tvPortList->actions());
sep = new QAction(this); sep = new QAction(this);
sep->setSeparator(true); sep->setSeparator(true);
addAction(sep); addAction(sep);
addActions(tvStreamList->actions()); addActions(tvStreamList->actions());
sep = new QAction(this);
sep->setSeparator(true);
addAction(sep);
addActions(devicesWidget->actions());
tvStreamList->setModel(plm->getStreamModel()); tvStreamList->setModel(plm->getStreamModel());
@ -112,6 +124,9 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
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,
SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)),
devicesWidget, SLOT(setCurrentPortIndex(const QModelIndex&)));
connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updateStreamViewActions())); SLOT(updateStreamViewActions()));
@ -128,7 +143,8 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvStreamList->resizeColumnToContents(StreamModel::StreamIcon); tvStreamList->resizeColumnToContents(StreamModel::StreamIcon);
tvStreamList->resizeColumnToContents(StreamModel::StreamStatus); tvStreamList->resizeColumnToContents(StreamModel::StreamStatus);
// Initially we don't have any ports/streams - so send signal triggers // Initially we don't have any ports/streams/devices
// - so send signal triggers
when_portView_currentChanged(QModelIndex(), QModelIndex()); when_portView_currentChanged(QModelIndex(), QModelIndex());
updateStreamViewActions(); updateStreamViewActions();
@ -157,6 +173,116 @@ PortsWindow::~PortsWindow()
delete proxyPortModel; delete proxyPortModel;
} }
int PortsWindow::portGroupCount()
{
return plm->numPortGroups();
}
int PortsWindow::reservedPortCount()
{
int count = 0;
int n = portGroupCount();
for (int i = 0; i < n; i++)
count += plm->portGroupByIndex(i).numReservedPorts();
return count;
}
//! Always return true
bool PortsWindow::openSession(
const OstProto::SessionContent *session,
QString &error)
{
QProgressDialog progress("Opening Session", NULL,
0, session->port_groups_size(), mainWindow);
progress.show();
progress.setEnabled(true); // since parent (mainWindow) is disabled
plm->removeAllPortGroups();
for (int i = 0; i < session->port_groups_size(); i++) {
const OstProto::PortGroupContent &pgc = session->port_groups(i);
PortGroup *pg = new PortGroup(QString::fromStdString(
pgc.server_name()),
quint16(pgc.server_port()));
pg->setConfigAtConnect(&pgc);
plm->addPortGroup(*pg);
progress.setValue(i+1);
}
return true;
}
/*!
* Prepare content to be saved for a session
*
* If port reservation is in use, saves only 'my' reserved ports
*
* Returns false, if user cancels op; true, otherwise
*/
bool PortsWindow::saveSession(
OstProto::SessionContent *session, // OUT param
QString &error,
QProgressDialog *progress)
{
int n = portGroupCount();
QString myself;
if (progress) {
progress->setLabelText("Preparing Ports and PortGroups ...");
progress->setRange(0, n);
}
if (reservedPortCount())
myself = appSettings->value(kUserKey, kUserDefaultValue).toString();
for (int i = 0; i < n; i++)
{
PortGroup &pg = plm->portGroupByIndex(i);
OstProto::PortGroupContent *pgc = session->add_port_groups();
pgc->set_server_name(pg.serverName().toStdString());
pgc->set_server_port(pg.serverPort());
for (int j = 0; j < pg.numPorts(); j++)
{
if (myself != pg.mPorts.at(j)->userName())
continue;
OstProto::PortContent *pc = pgc->add_ports();
OstProto::Port *p = pc->mutable_port_config();
// XXX: We save the entire OstProto::Port even though some
// fields may be ephemeral; while opening we use only relevant
// fields
pg.mPorts.at(j)->protoDataCopyInto(p);
for (int k = 0; k < pg.mPorts.at(j)->numStreams(); k++)
{
OstProto::Stream *s = pc->add_streams();
pg.mPorts.at(j)->streamByIndex(k)->protoDataCopyInto(*s);
}
for (int k = 0; k < pg.mPorts.at(j)->numDeviceGroups(); k++)
{
OstProto::DeviceGroup *dg = pc->add_device_groups();
dg->CopyFrom(*(pg.mPorts.at(j)->deviceGroupByIndex(k)));
}
}
if (progress) {
if (progress->wasCanceled())
return false;
progress->setValue(i);
}
if (i % 2 == 0)
qApp->processEvents();
}
return true;
}
void PortsWindow::showMyReservedPortsOnly(bool enabled) void PortsWindow::showMyReservedPortsOnly(bool enabled)
{ {
if (!proxyPortModel) if (!proxyPortModel)
@ -240,6 +366,8 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
SLOT(updatePortRates())); SLOT(updatePortRates()));
} }
} }
emit currentPortChanged(current, previous);
} }
void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft, void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft,
@ -684,6 +812,9 @@ void PortsWindow::on_actionOpen_Streams_triggered()
{ {
qDebug("Open Streams Action"); qDebug("Open Streams Action");
QStringList fileTypes = StreamFileFormat::supportedFileTypes(
StreamFileFormat::kOpenFile);
QString fileType;
QModelIndex current = tvPortList->selectionModel()->currentIndex(); QModelIndex current = tvPortList->selectionModel()->currentIndex();
static QString dirName; static QString dirName;
QString fileName; QString fileName;
@ -696,7 +827,11 @@ void PortsWindow::on_actionOpen_Streams_triggered()
Q_ASSERT(plm->isPort(current)); Q_ASSERT(plm->isPort(current));
fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"), dirName); if (fileTypes.size())
fileType = fileTypes.at(0);
fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"),
dirName, fileTypes.join(";;"), &fileType);
if (fileName.isEmpty()) if (fileName.isEmpty())
goto _exit; goto _exit;
@ -750,7 +885,8 @@ void PortsWindow::on_actionSave_Streams_triggered()
QModelIndex current = tvPortList->selectionModel()->currentIndex(); QModelIndex current = tvPortList->selectionModel()->currentIndex();
static QString fileName; static QString fileName;
QStringList fileTypes = AbstractFileFormat::supportedFileTypes(); QStringList fileTypes = StreamFileFormat::supportedFileTypes(
StreamFileFormat::kSaveFile);
QString fileType; QString fileType;
QString errorStr; QString errorStr;
QFileDialog::Options options; QFileDialog::Options options;

View File

@ -25,15 +25,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "ui_portswindow.h" #include "ui_portswindow.h"
#include "portgrouplist.h" #include "portgrouplist.h"
/* TODO
HIGH
MED
LOW
*/
class QAbstractItemDelegate; class QAbstractItemDelegate;
class QProgressDialog;
class QSortFilterProxyModel; class QSortFilterProxyModel;
namespace OstProto {
class SessionContent;
}
class PortsWindow : public QWidget, private Ui::PortsWindow class PortsWindow : public QWidget, private Ui::PortsWindow
{ {
Q_OBJECT Q_OBJECT
@ -45,6 +44,19 @@ public:
PortsWindow(PortGroupList *pgl, QWidget *parent = 0); PortsWindow(PortGroupList *pgl, QWidget *parent = 0);
~PortsWindow(); ~PortsWindow();
int portGroupCount();
int reservedPortCount();
bool openSession(const OstProto::SessionContent *session,
QString &error);
bool saveSession(OstProto::SessionContent *session,
QString &error,
QProgressDialog *progress = NULL);
signals:
void currentPortChanged(const QModelIndex &current,
const QModelIndex &previous);
private: private:
QString lastNewPortGroup; QString lastNewPortGroup;
QAbstractItemDelegate *delegate; QAbstractItemDelegate *delegate;

View File

@ -5,7 +5,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>710</width> <width>663</width>
<height>352</height> <height>352</height>
</rect> </rect>
</property> </property>
@ -22,6 +22,12 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<widget class="QTreeView" name="tvPortList" > <widget class="QTreeView" name="tvPortList" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy" > <property name="contextMenuPolicy" >
<enum>Qt::ActionsContextMenu</enum> <enum>Qt::ActionsContextMenu</enum>
</property> </property>
@ -30,11 +36,17 @@
</property> </property>
</widget> </widget>
<widget class="QStackedWidget" name="swDetail" > <widget class="QStackedWidget" name="swDetail" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex" > <property name="currentIndex" >
<number>0</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="portDetail" > <widget class="QWidget" name="portDetail" >
<layout class="QGridLayout" > <layout class="QVBoxLayout" >
<property name="leftMargin" > <property name="leftMargin" >
<number>0</number> <number>0</number>
</property> </property>
@ -47,115 +59,141 @@
<property name="bottomMargin" > <property name="bottomMargin" >
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0" > <item>
<layout class="QHBoxLayout" > <widget class="QFrame" name="frame" >
<item> <property name="frameShape" >
<widget class="QFrame" name="frAggregate" > <enum>QFrame::Panel</enum>
<property name="sizePolicy" > </property>
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" > <property name="frameShadow" >
<horstretch>0</horstretch> <enum>QFrame::Raised</enum>
<verstretch>0</verstretch> </property>
</sizepolicy> <layout class="QHBoxLayout" >
</property> <property name="leftMargin" >
<property name="frameShape" > <number>3</number>
<enum>QFrame::StyledPanel</enum> </property>
</property> <property name="topMargin" >
<property name="frameShadow" > <number>3</number>
<enum>QFrame::Sunken</enum> </property>
</property> <property name="rightMargin" >
<layout class="QGridLayout" > <number>3</number>
<item row="0" column="0" > </property>
<widget class="QRadioButton" name="radioButton" > <property name="bottomMargin" >
<property name="text" > <number>3</number>
<string>Avg pps</string> </property>
</property> <item>
<property name="checked" > <spacer>
<bool>true</bool> <property name="orientation" >
</property> <enum>Qt::Horizontal</enum>
</widget> </property>
</item> <property name="sizeHint" >
<item row="0" column="1" > <size>
<widget class="QLineEdit" name="averagePacketsPerSec" /> <width>40</width>
</item> <height>20</height>
<item row="1" column="0" > </size>
<widget class="QRadioButton" name="radioButton_2" > </property>
<property name="text" > </spacer>
<string>Avg bps</string> </item>
</property> <item>
</widget> <widget class="QPushButton" name="pbApply" >
</item> <property name="text" >
<item row="1" column="1" > <string>Apply</string>
<widget class="QLineEdit" name="averageBitsPerSec" > </property>
<property name="enabled" > </widget>
<bool>false</bool> </item>
</property> </layout>
</widget> </widget>
</item> </item>
<item row="0" column="2" > <item>
<spacer> <widget class="QTabWidget" name="portConfig" >
<property name="orientation" > <property name="currentIndex" >
<enum>Qt::Horizontal</enum> <number>0</number>
</property> </property>
<property name="sizeHint" > <widget class="QWidget" name="streamsTab" >
<size> <attribute name="title" >
<width>40</width> <string>Streams</string>
<height>20</height> </attribute>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" >
<item> <item>
<widget class="QPushButton" name="pbApply" > <layout class="QHBoxLayout" >
<property name="text" > <item>
<string>Apply</string> <widget class="QRadioButton" name="radioButton" >
<property name="text" >
<string>Avg pps</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="averagePacketsPerSec" />
</item>
<item>
<widget class="QRadioButton" name="radioButton_2" >
<property name="text" >
<string>Avg bps</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="averageBitsPerSec" >
<property name="enabled" >
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="tvStreamList" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy" >
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="lineWidth" >
<number>1</number>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property> </property>
</widget> </widget>
</item> </item>
</layout>
</widget>
<widget class="QWidget" name="devicesTab" >
<attribute name="title" >
<string>Devices</string>
</attribute>
<layout class="QVBoxLayout" >
<item> <item>
<spacer> <widget class="DevicesWidget" native="1" name="devicesWidget" />
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item> </item>
</layout> </layout>
</item> </widget>
</layout>
</item>
<item row="1" column="0" >
<widget class="QTableView" name="tvStreamList" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy" >
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="lineWidth" >
<number>1</number>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -267,6 +305,14 @@
</property> </property>
</action> </action>
</widget> </widget>
<customwidgets>
<customwidget>
<class>DevicesWidget</class>
<extends>QWidget</extends>
<header>deviceswidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="ostinato.qrc" /> <include location="ostinato.qrc" />
</resources> </resources>
@ -278,12 +324,12 @@
<slot>setEnabled(bool)</slot> <slot>setEnabled(bool)</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel" >
<x>313</x> <x>326</x>
<y>28</y> <y>80</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel" >
<x>380</x> <x>454</x>
<y>28</y> <y>79</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>
@ -294,12 +340,12 @@
<slot>setEnabled(bool)</slot> <slot>setEnabled(bool)</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel" >
<x>333</x> <x>523</x>
<y>55</y> <y>80</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel" >
<x>395</x> <x>651</x>
<y>56</y> <y>88</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>

View File

@ -49,3 +49,13 @@ void Stream::storeProtocolWidgets()
qWarning("%s: DOES NOTHING", __PRETTY_FUNCTION__); qWarning("%s: DOES NOTHING", __PRETTY_FUNCTION__);
return; return;
} }
quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex)
{
return 0;
}
quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex)
{
return 0;
}

55
common/emulation.h Normal file
View File

@ -0,0 +1,55 @@
/*
Copyright (C) 2016 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 _EMULATION_H
#define _EMULATION_H
#include "emulproto.pb.h"
#include <QtGlobal>
static inline OstProto::DeviceGroup* newDeviceGroup(uint portId)
{
OstProto::DeviceGroup *devGrp = new OstProto::DeviceGroup;
// To ensure that DeviceGroup have a unique key, we assign
// a random mac address upon creation; ideally, it would
// have been good to inherit OstProto::DeviceGroup and define
// a new constructor, but ProtoBuf forbids against inheriting
// generated classes, so we use this helper function instead
//
// Create a mac address as per RFC 4814 Sec 4.2
// (RR & 0xFC):PP:PP:RR:RR:RR
// where RR is a random number, PP:PP is 1-indexed port index
// NOTE: although qrand() return type is a int, the max value
// is RAND_MAX (stdlib.h) which is often 16-bit only, so we
// use two random numbers
quint32 r1 = qrand(), r2 = qrand();
quint64 mac;
mac = quint64(r1 & 0xfc00) << 32
| quint64(portId + 1) << 24
| quint64((r1 & 0xff) << 16 | (r2 & 0xffff));
devGrp->MutableExtension(OstEmul::mac)->set_address(mac);
return devGrp;
}
#endif

122
common/emulproto.proto Normal file
View File

@ -0,0 +1,122 @@
/*
Copyright (C) 2015 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 OstEmul;
// =======
// Encap
// =======
message VlanEmulation {
message Vlan {
optional uint32 tpid = 1 [default = 0x8100];
// includes prio, cfi and vlanid
optional uint32 vlan_tag = 2 [default = 100];
optional uint32 count = 10 [default = 1];
optional uint32 step = 11 [default = 1];
}
repeated Vlan stack = 1; // outer to inner
}
extend OstProto.EncapEmulation {
optional VlanEmulation vlan = 1000;
}
// ===========
// Protocols
// ===========
message MacEmulation {
optional uint64 address = 1; // no default - need unique value
optional uint64 step = 10 [default = 1];
}
// No default values for IP addresses - user needs to explicitly set that
// 'coz we derive if a device has a single/dual or no IP stack on the basis
// of whether OstProto.DeviceGroup.ip[46] is set
message Ip4Emulation {
optional uint32 address = 1;
optional uint32 prefix_length = 2 [default = 24];
optional uint32 default_gateway = 3;
optional uint32 step = 10 [default = 1];
// FIXME: step for gateway?
}
message Ip6Address {
optional uint64 hi = 1;
optional uint64 lo = 2;
}
message Ip6Emulation {
optional Ip6Address address = 1;
optional uint32 prefix_length = 2 [default = 64];
optional Ip6Address default_gateway = 3;
optional Ip6Address step = 10;
// FIXME: step for gateway?
}
extend OstProto.DeviceGroup {
optional MacEmulation mac = 2001;
optional Ip4Emulation ip4 = 3000;
optional Ip6Emulation ip6 = 3001;
}
message Device {
optional uint64 mac = 1;
repeated uint32 vlan = 2; // includes tpid 'n vlan tag
optional uint32 ip4 = 10;
optional uint32 ip4_prefix_length = 11;
optional uint32 ip4_default_gateway = 12;
optional Ip6Address ip6 = 20;
optional uint32 ip6_prefix_length = 21;
optional Ip6Address ip6_default_gateway = 22;
}
extend OstProto.PortDeviceList {
repeated Device device = 100;
}
message ArpEntry {
optional uint32 ip4 = 1;
optional uint64 mac = 2;
}
message NdpEntry {
optional Ip6Address ip6 = 1;
optional uint64 mac = 2;
}
message DeviceNeighborList {
optional uint32 device_index = 1;
repeated ArpEntry arp = 2;
repeated NdpEntry ndp = 3;
}
extend OstProto.PortNeighborList {
repeated DeviceNeighborList device_neighbor = 100;
}

View File

@ -24,6 +24,7 @@ package OstProto;
enum FileType { enum FileType {
kReservedFileType = 0; kReservedFileType = 0;
kStreamsFileType = 1; kStreamsFileType = 1;
kSessionFileType = 10;
} }
message FileMetaData { message FileMetaData {
@ -36,8 +37,30 @@ message FileMetaData {
required string generator_revision = 7; required string generator_revision = 7;
} }
message PortContent {
optional Port port_config = 1;
repeated Stream streams = 2;
repeated DeviceGroup device_groups = 3;
}
message PortGroupContent {
optional string server_name = 1;
optional uint32 server_port = 2;
repeated PortContent ports = 15;
}
message SessionContent {
repeated PortGroupContent port_groups = 1;
}
message FileContentMatter { message FileContentMatter {
optional StreamConfigList streams = 1; optional StreamConfigList streams = 1;
// TODO: optional DeviceGroupConfigList device_groups = 2;
// TODO: optional PortContent port = 3;
// FIXME: (single) portgroup? is there a usecase for this?
optional SessionContent session = 10;
} }
/* /*

41
common/intedit.h Normal file
View File

@ -0,0 +1,41 @@
/*
Copyright (C) 2016 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 _INT_EDIT_H
#define _INT_EDIT_H
#include <QSpinBox>
#include <limits.h>
class IntEdit: public QSpinBox
{
public:
IntEdit(QWidget *parent = 0);
};
inline IntEdit::IntEdit(QWidget *parent)
: QSpinBox(parent)
{
setRange(INT_MIN, INT_MAX);
setButtonSymbols(QAbstractSpinBox::NoButtons);
}
#endif

52
common/ip4edit.h Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright (C) 2016 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 _IP4_EDIT_H
#define _IP4_EDIT_H
#include <QHostAddress>
#include <QLineEdit>
class Ip4Edit: public QLineEdit
{
public:
Ip4Edit(QWidget *parent = 0);
quint32 value();
void setValue(quint32 val);
};
inline Ip4Edit::Ip4Edit(QWidget *parent)
: QLineEdit(parent)
{
setInputMask(QString("000.000.000.000; "));
}
inline quint32 Ip4Edit::value()
{
return QHostAddress(text()).toIPv4Address();
}
inline void Ip4Edit::setValue(quint32 val)
{
setText(QHostAddress(val).toString());
}
#endif

79
common/ip6edit.h Normal file
View File

@ -0,0 +1,79 @@
/*
Copyright (C) 2016 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 _IP6_EDIT_H
#define _IP6_EDIT_H
#include "ipv6addressvalidator.h"
#include "uint128.h"
#include <QHostAddress>
#include <QLineEdit>
class Ip6Edit: public QLineEdit
{
public:
Ip6Edit(QWidget *parent = 0);
UInt128 value();
quint64 valueHi64();
quint64 valueLo64();
void setValue(UInt128 val);
void setValue(quint64 hi, quint64 lo);
void setValue(const QString &val);
};
inline Ip6Edit::Ip6Edit(QWidget *parent)
: QLineEdit(parent)
{
setValidator(new IPv6AddressValidator(this));
}
inline UInt128 Ip6Edit::value()
{
Q_IPV6ADDR addr = QHostAddress(text()).toIPv6Address();
return UInt128((quint8*)&addr);
}
inline quint64 Ip6Edit::valueHi64()
{
return value().hi64();
}
inline quint64 Ip6Edit::valueLo64()
{
return value().lo64();
}
inline void Ip6Edit::setValue(UInt128 val)
{
setText(QHostAddress(val.toArray()).toString());
}
inline void Ip6Edit::setValue(quint64 hi, quint64 lo)
{
UInt128 ip(hi, lo);
setValue(ip);
}
inline void Ip6Edit::setValue(const QString &val)
{
setText(QHostAddress(val).toString());
}
#endif

View File

@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "mac.h" #include "mac.h"
#include "../common/streambase.h"
#include <QRegExp> #include <QRegExp>
#define uintToMacStr(num) \ #define uintToMacStr(num) \
@ -28,6 +30,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
MacProtocol::MacProtocol(StreamBase *stream, AbstractProtocol *parent) MacProtocol::MacProtocol(StreamBase *stream, AbstractProtocol *parent)
: AbstractProtocol(stream, parent) : AbstractProtocol(stream, parent)
{ {
forResolve_ = false;
} }
MacProtocol::~MacProtocol() MacProtocol::~MacProtocol()
@ -124,6 +127,15 @@ QVariant MacProtocol::fieldData(int index, FieldAttrib attrib,
data.dst_mac_step(); data.dst_mac_step();
dstMac = data.dst_mac() - u; dstMac = data.dst_mac() - u;
break; break;
case OstProto::Mac::e_mm_resolve:
if (forResolve_)
dstMac = 0;
else {
forResolve_ = true;
dstMac = mpStream->neighborMacAddress(streamIndex);
forResolve_ = false;
}
break;
default: default:
qWarning("Unhandled dstMac_mode %d", data.dst_mac_mode()); qWarning("Unhandled dstMac_mode %d", data.dst_mac_mode());
} }
@ -169,6 +181,15 @@ QVariant MacProtocol::fieldData(int index, FieldAttrib attrib,
data.src_mac_step(); data.src_mac_step();
srcMac = data.src_mac() - u; srcMac = data.src_mac() - u;
break; break;
case OstProto::Mac::e_mm_resolve:
if (forResolve_)
srcMac = 0;
else {
forResolve_ = true;
srcMac = mpStream->deviceMacAddress(streamIndex);
forResolve_ = false;
}
break;
default: default:
qWarning("Unhandled srcMac_mode %d", data.src_mac_mode()); qWarning("Unhandled srcMac_mode %d", data.src_mac_mode());
} }
@ -331,11 +352,23 @@ int MacProtocol::protocolFrameVariableCount() const
{ {
int count = AbstractProtocol::protocolFrameVariableCount(); int count = AbstractProtocol::protocolFrameVariableCount();
if (data.dst_mac_mode() != OstProto::Mac::e_mm_fixed) switch (data.dst_mac_mode()) {
count = AbstractProtocol::lcm(count, data.dst_mac_count()); case OstProto::Mac::e_mm_inc:
case OstProto::Mac::e_mm_dec:
count = AbstractProtocol::lcm(count, data.dst_mac_count());
break;
default:
break;
}
if (data.src_mac_mode() != OstProto::Mac::e_mm_fixed) switch (data.src_mac_mode()) {
count = AbstractProtocol::lcm(count, data.src_mac_count()); case OstProto::Mac::e_mm_inc:
case OstProto::Mac::e_mm_dec:
count = AbstractProtocol::lcm(count, data.src_mac_count());
break;
default:
break;
}
return count; return count;
} }

View File

@ -67,6 +67,7 @@ public:
private: private:
OstProto::Mac data; OstProto::Mac data;
mutable bool forResolve_;
}; };
#endif #endif

View File

@ -28,6 +28,7 @@ message Mac {
e_mm_fixed = 0; e_mm_fixed = 0;
e_mm_inc = 1; e_mm_inc = 1;
e_mm_dec = 2; e_mm_dec = 2;
e_mm_resolve = 3; // dst: resolve neighbor; src: from device config
} }
// Dst Mac // Dst Mac

View File

@ -5,8 +5,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>391</width> <width>400</width>
<height>116</height> <height>200</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
@ -81,6 +81,11 @@
<string>Decrement</string> <string>Decrement</string>
</property> </property>
</item> </item>
<item>
<property name="text" >
<string>Resolve</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="1" column="3" > <item row="1" column="3" >
@ -143,6 +148,11 @@
<string>Decrement</string> <string>Decrement</string>
</property> </property>
</item> </item>
<item>
<property name="text" >
<string>Resolve</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="2" column="3" > <item row="2" column="3" >
@ -168,7 +178,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" > <item row="3" column="0" colspan="5" >
<widget class="QLabel" name="resolveInfo" >
<property name="text" >
<string>Please ensure that a corresponding device is configured on the port to enable source/destination mac address resolution. A corresponding device is one which has VLANs and source/gateway IP corresponding to this stream.</string>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0" >
<spacer> <spacer>
<property name="orientation" > <property name="orientation" >
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>

View File

@ -28,6 +28,12 @@ MacConfigForm::MacConfigForm(QWidget *parent)
QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"); QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}");
setupUi(this); setupUi(this);
resolveInfo->hide();
#if 0
// not working for some reason
resolveInfo->setPixmap(resolveInfo->style()->standardIcon(
QStyle::SP_MessageBoxInformation).pixmap(128));
#endif
leDstMac->setValidator(new QRegExpValidator(reMac, this)); leDstMac->setValidator(new QRegExpValidator(reMac, this));
leSrcMac->setValidator(new QRegExpValidator(reMac, this)); leSrcMac->setValidator(new QRegExpValidator(reMac, this));
leDstMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this)); leDstMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this));
@ -46,30 +52,50 @@ MacConfigForm* MacConfigForm::createInstance()
void MacConfigForm::on_cmbDstMacMode_currentIndexChanged(int index) void MacConfigForm::on_cmbDstMacMode_currentIndexChanged(int index)
{ {
if (index == OstProto::Mac::e_mm_fixed) switch (index) {
{ case OstProto::Mac::e_mm_resolve:
leDstMacCount->setEnabled(false); leDstMac->setEnabled(false);
leDstMacStep->setEnabled(false); leDstMacCount->setEnabled(false);
} leDstMacStep->setEnabled(false);
else break;
{ case OstProto::Mac::e_mm_fixed:
leDstMacCount->setEnabled(true); leDstMac->setEnabled(true);
leDstMacStep->setEnabled(true); leDstMacCount->setEnabled(false);
leDstMacStep->setEnabled(false);
break;
default:
leDstMac->setEnabled(true);
leDstMacCount->setEnabled(true);
leDstMacStep->setEnabled(true);
break;
} }
resolveInfo->setVisible(
cmbDstMacMode->currentIndex() == OstProto::Mac::e_mm_resolve
|| cmbSrcMacMode->currentIndex() == OstProto::Mac::e_mm_resolve);
} }
void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index) void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index)
{ {
if (index == OstProto::Mac::e_mm_fixed) switch (index) {
{ case OstProto::Mac::e_mm_resolve:
leSrcMacCount->setEnabled(false); leSrcMac->setEnabled(false);
leSrcMacStep->setEnabled(false); leSrcMacCount->setEnabled(false);
} leSrcMacStep->setEnabled(false);
else break;
{ case OstProto::Mac::e_mm_fixed:
leSrcMacCount->setEnabled(true); leSrcMac->setEnabled(true);
leSrcMacStep->setEnabled(true); leSrcMacCount->setEnabled(false);
leSrcMacStep->setEnabled(false);
break;
default:
leSrcMac->setEnabled(true);
leSrcMacCount->setEnabled(true);
leSrcMacStep->setEnabled(true);
break;
} }
resolveInfo->setVisible(
cmbDstMacMode->currentIndex() == OstProto::Mac::e_mm_resolve
|| cmbSrcMacMode->currentIndex() == OstProto::Mac::e_mm_resolve);
} }
void MacConfigForm::loadWidget(AbstractProtocol *proto) void MacConfigForm::loadWidget(AbstractProtocol *proto)

54
common/macedit.h Normal file
View File

@ -0,0 +1,54 @@
/*
Copyright (C) 2016 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 _MAC_EDIT_H
#define _MAC_EDIT_H
#include <QLineEdit>
class MacEdit: public QLineEdit
{
public:
MacEdit(QWidget *parent = 0);
quint64 value();
void setValue(quint64 val);
};
inline MacEdit::MacEdit(QWidget *parent)
: QLineEdit(parent)
{
QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}");
setValidator(new QRegExpValidator(reMac, this));
}
inline quint64 MacEdit::value()
{
return text().remove(QChar(':')).toULongLong(NULL, 16);
}
inline void MacEdit::setValue(quint64 val)
{
setText(QString("%1").arg(val, 6*2, 16, QChar('0'))
.replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:").toUpper());
}
#endif

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2010 Srivats P. Copyright (C) 2010, 2016 Srivats P.
This file is part of "Ostinato" This file is part of "Ostinato"
@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/> along with this program. If not, see <http://www.gnu.org/licenses/>
*/ */
#include "fileformat.h" #include "nativefileformat.h"
#include "crc32c.h" #include "crc32c.h"
@ -25,15 +25,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QFile> #include <QFile>
#include <QVariant> #include <QVariant>
#include <string> #define tr(str) QObject::tr(str)
const std::string FileFormat::kFileMagicValue = "\xa7\xb7OSTINATO"; const std::string NativeFileFormat::kFileMagicValue = "\xa7\xb7OSTINATO";
FileFormat fileFormat; static const int kBaseHex = 16;
const int kBaseHex = 16; static QString fileTypeStr(OstProto::FileType fileType)
{
switch (fileType) {
case OstProto::kReservedFileType:
return QString("Reserved");
case OstProto::kStreamsFileType:
return QString("Streams");
case OstProto::kSessionFileType:
return QString("Streams");
default:
Q_ASSERT(false);
}
FileFormat::FileFormat() return QString("Unknown");
}
NativeFileFormat::NativeFileFormat()
{ {
/* /*
* We don't have any "real" work to do here in the constructor. * We don't have any "real" work to do here in the constructor.
@ -54,20 +68,18 @@ FileFormat::FileFormat()
Q_ASSERT(cksum.ByteSize() == kFileChecksumSize); Q_ASSERT(cksum.ByteSize() == kFileChecksumSize);
} }
FileFormat::~FileFormat() bool NativeFileFormat::open(
{ const QString fileName,
} OstProto::FileType fileType,
OstProto::FileMeta &meta,
bool FileFormat::openStreams(const QString fileName, OstProto::FileContent &content,
OstProto::StreamConfigList &streams, QString &error) QString &error)
{ {
QFile file(fileName); QFile file(fileName);
QByteArray buf; QByteArray buf;
int size, contentOffset, contentSize; int size, contentOffset, contentSize;
quint32 calcCksum; quint32 calcCksum;
OstProto::FileMagic magic; OstProto::FileMagic magic;
OstProto::FileMeta meta;
OstProto::FileContent content;
OstProto::FileChecksum cksum, zeroCksum; OstProto::FileChecksum cksum, zeroCksum;
if (!file.open(QIODevice::ReadOnly)) if (!file.open(QIODevice::ReadOnly))
@ -95,7 +107,7 @@ bool FileFormat::openStreams(const QString fileName,
// Parse and verify magic // Parse and verify magic
if (!magic.ParseFromArray( if (!magic.ParseFromArray(
(void*)(buf.constData() + kFileMagicOffset), (void*)(buf.constData() + kFileMagicOffset),
kFileMagicSize)) kFileMagicSize))
{ {
goto _magic_parse_fail; goto _magic_parse_fail;
@ -105,7 +117,7 @@ bool FileFormat::openStreams(const QString fileName,
// Parse and verify checksum // Parse and verify checksum
if (!cksum.ParseFromArray( if (!cksum.ParseFromArray(
(void*)(buf.constData() + size - kFileChecksumSize), (void*)(buf.constData() + size - kFileChecksumSize),
kFileChecksumSize)) kFileChecksumSize))
{ {
goto _cksum_parse_fail; goto _cksum_parse_fail;
@ -118,7 +130,7 @@ bool FileFormat::openStreams(const QString fileName,
{ {
goto _zero_cksum_serialize_fail; goto _zero_cksum_serialize_fail;
} }
calcCksum = checksumCrc32C((quint8*) buf.constData(), size); calcCksum = checksumCrc32C((quint8*) buf.constData(), size);
qDebug("checksum \nExpected:%x Actual:%x", qDebug("checksum \nExpected:%x Actual:%x",
@ -129,17 +141,18 @@ bool FileFormat::openStreams(const QString fileName,
// Parse the metadata first before we parse the full contents // Parse the metadata first before we parse the full contents
if (!meta.ParseFromArray( if (!meta.ParseFromArray(
(void*)(buf.constData() + kFileMetaDataOffset), (void*)(buf.constData() + kFileMetaDataOffset),
size - kFileMetaDataOffset)) fileMetaSize((quint8*)buf.constData(), size)))
{ {
goto _metadata_parse_fail; goto _metadata_parse_fail;
} }
qDebug("%s: File MetaData (INFORMATION) - \n%s", __FUNCTION__, qDebug("%s: File MetaData (INFORMATION) - \n%s", __FUNCTION__,
QString().fromStdString(meta.DebugString()).toAscii().constData()); QString().fromStdString(meta.DebugString()).toAscii().constData());
qDebug("%s: END MetaData", __FUNCTION__);
// MetaData Validation(s) // MetaData Validation(s)
if (meta.data().file_type() != OstProto::kStreamsFileType) if (meta.data().file_type() != fileType)
goto _unexpected_file_type; goto _unexpected_file_type;
if (meta.data().format_version_major() != kFileFormatVersionMajor) if (meta.data().format_version_major() != kFileFormatVersionMajor)
@ -165,34 +178,26 @@ bool FileFormat::openStreams(const QString fileName,
// ByteSize() does not include the Tag/Key, so we add 2 for that // ByteSize() does not include the Tag/Key, so we add 2 for that
contentOffset = kFileMetaDataOffset + meta.data().ByteSize() + 2; contentOffset = kFileMetaDataOffset + meta.data().ByteSize() + 2;
contentSize = size - contentOffset - kFileChecksumSize; contentSize = size - contentOffset - kFileChecksumSize;
qDebug("%s: content offset/size = %d/%d", __FUNCTION__,
contentOffset, contentSize);
// Parse full contents // Parse full contents
if (!content.ParseFromArray( if (!content.ParseFromArray(
(void*)(buf.constData() + contentOffset), (void*)(buf.constData() + contentOffset),
contentSize)) contentSize))
{ {
goto _content_parse_fail; goto _content_parse_fail;
} }
if (!content.matter().has_streams())
goto _missing_streams;
postParseFixup(meta.data(), content);
streams.CopyFrom(content.matter().streams());
return true; return true;
_missing_streams:
error = QString(tr("%1 does not contain any streams")).arg(fileName);
goto _fail;
_content_parse_fail: _content_parse_fail:
error = QString(tr("Failed parsing %1 contents")).arg(fileName); error = QString(tr("Failed parsing %1 contents")).arg(fileName);
qDebug("Error: %s", QString().fromStdString( qDebug("Error: %s", QString().fromStdString(
content.matter().InitializationErrorString()) content.InitializationErrorString())
.toAscii().constData()); .toAscii().constData());
qDebug("Debug: %s", QString().fromStdString( qDebug("Debug: %s", QString().fromStdString(
content.matter().DebugString()).toAscii().constData()); content.DebugString()).toAscii().constData());
goto _fail; goto _fail;
_incompatible_file_version: _incompatible_file_version:
error = QString(tr("%1 is in an incompatible format version - %2.%3.%4" error = QString(tr("%1 is in an incompatible format version - %2.%3.%4"
@ -206,7 +211,9 @@ _incompatible_file_version:
.arg(kFileFormatVersionRevision); .arg(kFileFormatVersionRevision);
goto _fail; goto _fail;
_unexpected_file_type: _unexpected_file_type:
error = QString(tr("%1 is not a streams file")).arg(fileName); error = QString(tr("%1 is not a %2 file"))
.arg(fileName)
.arg(fileTypeStr(fileType));
goto _fail; goto _fail;
_metadata_parse_fail: _metadata_parse_fail:
error = QString(tr("Failed parsing %1 meta data")).arg(fileName); error = QString(tr("Failed parsing %1 meta data")).arg(fileName);
@ -260,12 +267,14 @@ _fail:
return false; return false;
} }
bool FileFormat::saveStreams(const OstProto::StreamConfigList streams, bool NativeFileFormat::save(
const QString fileName, QString &error) OstProto::FileType fileType,
const OstProto::FileContent &content,
const QString fileName,
QString &error)
{ {
OstProto::FileMagic magic; OstProto::FileMagic magic;
OstProto::FileMeta meta; OstProto::FileMeta meta;
OstProto::FileContent content;
OstProto::FileChecksum cksum; OstProto::FileChecksum cksum;
QFile file(fileName); QFile file(fileName);
int metaSize, contentSize; int metaSize, contentSize;
@ -280,13 +289,12 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
Q_ASSERT(cksum.IsInitialized()); Q_ASSERT(cksum.IsInitialized());
initFileMetaData(*(meta.mutable_data())); initFileMetaData(*(meta.mutable_data()));
meta.mutable_data()->set_file_type(OstProto::kStreamsFileType); meta.mutable_data()->set_file_type(fileType);
Q_ASSERT(meta.IsInitialized()); Q_ASSERT(meta.IsInitialized());
if (!streams.IsInitialized()) if (!content.IsInitialized())
goto _stream_not_init; goto _content_not_init;
content.mutable_matter()->mutable_streams()->CopyFrom(streams);
Q_ASSERT(content.IsInitialized()); Q_ASSERT(content.IsInitialized());
metaSize = meta.ByteSize(); metaSize = meta.ByteSize();
@ -323,7 +331,7 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
goto _zero_cksum_serialize_fail; goto _zero_cksum_serialize_fail;
} }
emit status("Calculating checksum..."); // TODO: emit status("Calculating checksum...");
// Calculate and write checksum // Calculate and write checksum
calcCksum = checksumCrc32C((quint8*)buf.constData(), buf.size()); calcCksum = checksumCrc32C((quint8*)buf.constData(), buf.size());
@ -338,7 +346,7 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
qDebug("Writing %d bytes", buf.size()); qDebug("Writing %d bytes", buf.size());
//qDebug("%s", QString(buf.toHex()).toAscii().constData()); //qDebug("%s", QString(buf.toHex()).toAscii().constData());
emit status("Writing to disk..."); // TODO: emit status("Writing to disk...");
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
goto _open_fail; goto _open_fail;
@ -346,7 +354,7 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
goto _write_fail; goto _write_fail;
file.close(); file.close();
return true; return true;
_write_fail: _write_fail:
@ -387,18 +395,20 @@ _magic_serialize_fail:
magic.InitializationErrorString())) magic.InitializationErrorString()))
.arg(QString().fromStdString(magic.DebugString())); .arg(QString().fromStdString(magic.DebugString()));
goto _fail; goto _fail;
_stream_not_init: _content_not_init:
error = QString(tr("Internal Error: Streams not initialized\n%1\n%2")) error = QString(tr("Internal Error: Content not initialized\n%1\n%2"))
.arg(QString().fromStdString( .arg(QString().fromStdString(
streams.InitializationErrorString())) content.InitializationErrorString()))
.arg(QString().fromStdString(streams.DebugString())); .arg(QString().fromStdString(content.DebugString()));
goto _fail; goto _fail;
_fail: _fail:
qDebug("%s", error.toAscii().constData()); qDebug("%s", error.toAscii().constData());
return false; return false;
} }
bool FileFormat::isMyFileFormat(const QString fileName) bool NativeFileFormat::isNativeFileFormat(
const QString fileName,
OstProto::FileType fileType)
{ {
bool ret = false; bool ret = false;
QFile file(fileName); QFile file(fileName);
@ -408,13 +418,25 @@ bool FileFormat::isMyFileFormat(const QString fileName)
if (!file.open(QIODevice::ReadOnly)) if (!file.open(QIODevice::ReadOnly))
goto _exit; goto _exit;
buf = file.peek(kFileMagicOffset + kFileMagicSize); // Assume tag/length for MetaData will fit in 8 bytes
if (!magic.ParseFromArray((void*)(buf.constData() + kFileMagicOffset), buf = file.peek(kFileMagicOffset + kFileMagicSize + 8);
if (!magic.ParseFromArray((void*)(buf.constData() + kFileMagicOffset),
kFileMagicSize)) kFileMagicSize))
goto _close_exit; goto _close_exit;
if (magic.value() == kFileMagicValue) if (magic.value() == kFileMagicValue) {
ret = true; OstProto::FileMeta meta;
int metaSize = fileMetaSize((quint8*)buf.constData(), buf.size());
buf = file.peek(kFileMagicOffset + kFileMagicSize + metaSize);
if (!meta.ParseFromArray(
(void*)(buf.constData() + kFileMetaDataOffset), metaSize)) {
qDebug("%s: File MetaData\n%s", __FUNCTION__,
QString().fromStdString(meta.DebugString()).toAscii().constData());
goto _close_exit;
}
if (meta.data().file_type() == fileType)
ret = true;
}
_close_exit: _close_exit:
file.close(); file.close();
@ -422,15 +444,7 @@ _exit:
return ret; return ret;
} }
bool FileFormat::isMyFileType(const QString fileType) void NativeFileFormat::initFileMetaData(OstProto::FileMetaData &metaData)
{
if (fileType.startsWith("Ostinato"))
return true;
else
return false;
}
void FileFormat::initFileMetaData(OstProto::FileMetaData &metaData)
{ {
// 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);
@ -445,9 +459,53 @@ void FileFormat::initFileMetaData(OstProto::FileMetaData &metaData)
qApp->property("revision").toString().toUtf8().constData()); qApp->property("revision").toString().toUtf8().constData());
} }
int NativeFileFormat::fileMetaSize(const quint8* file, int size)
{
int i = kFileMetaDataOffset;
uint result, shift;
const int kWireTypeLengthDelimited = 2;
// An embedded Message field is encoded as
// <Key> <Length> <Serialized-Value>
// See Protobuf Encoding for more details
// Decode 'Key' varint
result = 0;
shift = 0;
while (i < size) {
quint8 byte = file[i++];
result |= (byte & 0x7f) << shift;
if (!(byte & 0x80)) // MSB == 0?
break;
shift += 7;
}
if (i >= size)
return 0;
Q_ASSERT(result == ((OstProto::File::kMetaDataFieldNumber << 3)
| kWireTypeLengthDelimited));
// Decode 'Length' varint
result = 0;
shift = 0;
while (i < size) {
quint8 byte = file[i++];
result |= (byte & 0x7f) << shift;
if (!(byte & 0x80)) // MSB == 0?
break;
shift += 7;
}
if (i >= size)
return 0;
return int(result+(i-kFileMetaDataOffset));
}
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
/*! Fixup content to what is expected in the native version */ /*! Fixup content to what is expected in the native version */
void FileFormat::postParseFixup(OstProto::FileMetaData metaData, void NativeFileFormat::postParseFixup(OstProto::FileMetaData metaData,
OstProto::FileContent &content) OstProto::FileContent &content)
{ {
Q_ASSERT(metaData.format_version_major() == kFileFormatVersionMajor); Q_ASSERT(metaData.format_version_major() == kFileFormatVersionMajor);
@ -460,7 +518,7 @@ void FileFormat::postParseFixup(OstProto::FileMetaData metaData,
int n = content.matter().streams().stream_size(); int n = content.matter().streams().stream_size();
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
{ {
OstProto::StreamControl *sctl = OstProto::StreamControl *sctl =
content.mutable_matter()->mutable_streams()->mutable_stream(i)->mutable_control(); content.mutable_matter()->mutable_streams()->mutable_stream(i)->mutable_control();
sctl->set_packets_per_sec(sctl->obsolete_packets_per_sec()); sctl->set_packets_per_sec(sctl->obsolete_packets_per_sec());
sctl->set_bursts_per_sec(sctl->obsolete_bursts_per_sec()); sctl->set_bursts_per_sec(sctl->obsolete_bursts_per_sec());
@ -473,7 +531,7 @@ void FileFormat::postParseFixup(OstProto::FileMetaData metaData,
case 0: case 0:
default: default:
qWarning("%s: minor version %u unhandled", __FUNCTION__, qWarning("%s: minor version %u unhandled", __FUNCTION__,
metaData.format_version_minor()); metaData.format_version_minor());
Q_ASSERT_X(false, "postParseFixup", "unhandled minor version"); Q_ASSERT_X(false, "postParseFixup", "unhandled minor version");
} }

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2010 Srivats P. Copyright (C) 2010, 2016 Srivats P.
This file is part of "Ostinato" This file is part of "Ostinato"
@ -16,31 +16,48 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/> along with this program. If not, see <http://www.gnu.org/licenses/>
*/ */
#ifndef _FILE_FORMAT_H #ifndef _NATIVE_FILE_FORMAT_H
#define _FILE_FORMAT_H #define _NATIVE_FILE_FORMAT_H
#include "abstractfileformat.h" /*
* This file contains helper functions for the native file format
* defined in fileformat.proto
*
* The actual file format classes - (Ostm)FileFormat and OssnFileFormat
* use multiple inheritance from the abstract interface class and this
* helper class
*
* The primary reason for the existence of this class is to have a common
* code for dealing with native file formats
*/
#include "fileformat.pb.h" #include "fileformat.pb.h"
class FileFormat : public AbstractFileFormat #include <QString>
class NativeFileFormat
{ {
public: public:
FileFormat(); NativeFileFormat();
~FileFormat();
virtual bool openStreams(const QString fileName, bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error); OstProto::FileType fileType,
virtual bool saveStreams(const OstProto::StreamConfigList streams, OstProto::FileMeta &meta,
const QString fileName, QString &error); OstProto::FileContent &content,
QString &error);
bool save(OstProto::FileType fileType,
const OstProto::FileContent &content,
const QString fileName,
QString &error);
bool isMyFileFormat(const QString fileName); bool isNativeFileFormat(const QString fileName,
bool isMyFileType(const QString fileType); OstProto::FileType fileType);
void postParseFixup(OstProto::FileMetaData metaData,
OstProto::FileContent &content);
private: private:
void initFileMetaData(OstProto::FileMetaData &metaData); void initFileMetaData(OstProto::FileMetaData &metaData);
void postParseFixup(OstProto::FileMetaData metaData, int fileMetaSize(const quint8* file, int size);
OstProto::FileContent &content);
static const int kFileMagicSize = 12; static const int kFileMagicSize = 12;
static const int kFileChecksumSize = 5; static const int kFileChecksumSize = 5;
@ -50,13 +67,11 @@ private:
static const int kFileMetaDataOffset = kFileMagicSize; static const int kFileMetaDataOffset = kFileMagicSize;
static const std::string kFileMagicValue; static const std::string kFileMagicValue;
// Native file format version // Native file format version
static const uint kFileFormatVersionMajor = 0; static const uint kFileFormatVersionMajor = 0;
static const uint kFileFormatVersionMinor = 2; static const uint kFileFormatVersionMinor = 2;
static const uint kFileFormatVersionRevision = 4; static const uint kFileFormatVersionRevision = 4;
}; };
extern FileFormat fileFormat;
#endif #endif

91
common/ossnfileformat.cpp Normal file
View File

@ -0,0 +1,91 @@
/*
Copyright (C) 2016 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 "ossnfileformat.h"
OssnFileFormat ossnFileFormat;
OssnFileFormat::OssnFileFormat()
: SessionFileFormat(), NativeFileFormat()
{
// Do Nothing
}
bool OssnFileFormat::open(const QString fileName,
OstProto::SessionContent &session, QString &error)
{
OstProto::FileMeta meta;
OstProto::FileContent content;
bool ret = NativeFileFormat::open(fileName, OstProto::kSessionFileType,
meta, content, error);
if (!ret)
goto _exit;
if (!content.matter().has_session())
goto _missing_session;
postParseFixup(meta.data(), content);
session.CopyFrom(content.matter().session());
return true;
_missing_session:
error = QString(tr("%1 does not contain a session")).arg(fileName);
goto _fail;
_fail:
qDebug("%s", error.toAscii().constData());
_exit:
return false;
}
bool OssnFileFormat::save(const OstProto::SessionContent &session,
const QString fileName, QString &error)
{
OstProto::FileContent content;
if (!session.IsInitialized())
goto _session_not_init;
content.mutable_matter()->mutable_session()->CopyFrom(session);
Q_ASSERT(content.IsInitialized());
return NativeFileFormat::save(OstProto::kSessionFileType, content,
fileName, error);
_session_not_init:
error = QString(tr("Internal Error: Session not initialized\n%1\n%2"))
.arg(QString().fromStdString(
session.InitializationErrorString()))
.arg(QString().fromStdString(session.DebugString()));
goto _fail;
_fail:
qDebug("%s", error.toAscii().constData());
return false;
}
bool OssnFileFormat::isMyFileFormat(const QString fileName)
{
return isNativeFileFormat(fileName, OstProto::kSessionFileType);
}
bool OssnFileFormat::isMyFileType(const QString fileType)
{
return fileType.contains("(*.ossn)") ? true : false;
}

43
common/ossnfileformat.h Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright (C) 2016 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 _OSSN_FILE_FORMAT_H
#define _OSSN_FILE_FORMAT_H
#include "nativefileformat.h"
#include "sessionfileformat.h"
class OssnFileFormat : public SessionFileFormat, public NativeFileFormat
{
public:
OssnFileFormat();
virtual bool open(const QString fileName,
OstProto::SessionContent &session, QString &error);
virtual bool save(const OstProto::SessionContent &session,
const QString fileName, QString &error);
virtual bool isMyFileFormat(const QString fileName);
virtual bool isMyFileType(const QString fileType);
};
extern OssnFileFormat ossnFileFormat;
#endif

94
common/ostmfileformat.cpp Normal file
View File

@ -0,0 +1,94 @@
/*
Copyright (C) 2010 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 "ostmfileformat.h"
OstmFileFormat fileFormat;
OstmFileFormat::OstmFileFormat()
: StreamFileFormat(), NativeFileFormat()
{
// Do Nothing!
}
bool OstmFileFormat::open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error)
{
OstProto::FileMeta meta;
OstProto::FileContent content;
bool ret = NativeFileFormat::open(fileName, OstProto::kStreamsFileType,
meta, content, error);
if (!ret)
goto _fail;
if (!content.matter().has_streams())
goto _missing_streams;
postParseFixup(meta.data(), content);
streams.CopyFrom(content.matter().streams());
return true;
_missing_streams:
error = QString(tr("%1 does not contain any streams")).arg(fileName);
goto _fail;
_fail:
qDebug("%s", error.toAscii().constData());
return false;
}
bool OstmFileFormat::save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error)
{
OstProto::FileContent content;
if (!streams.IsInitialized())
goto _stream_not_init;
content.mutable_matter()->mutable_streams()->CopyFrom(streams);
Q_ASSERT(content.IsInitialized());
return NativeFileFormat::save(OstProto::kStreamsFileType, content,
fileName, error);
_stream_not_init:
error = QString(tr("Internal Error: Streams not initialized\n%1\n%2"))
.arg(QString().fromStdString(
streams.InitializationErrorString()))
.arg(QString().fromStdString(streams.DebugString()));
goto _fail;
_fail:
qDebug("%s", error.toAscii().constData());
return false;
}
bool OstmFileFormat::isMyFileFormat(const QString fileName)
{
return isNativeFileFormat(fileName, OstProto::kStreamsFileType);
}
bool OstmFileFormat::isMyFileType(const QString fileType)
{
if (fileType.startsWith("Ostinato"))
return true;
else
return false;
}

43
common/ostmfileformat.h Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright (C) 2010 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 _OSTM_FILE_FORMAT_H
#define _OSTM_FILE_FORMAT_H
#include "nativefileformat.h"
#include "streamfileformat.h"
#include "fileformat.pb.h"
class OstmFileFormat : public StreamFileFormat, public NativeFileFormat
{
public:
OstmFileFormat();
virtual bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error);
virtual bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error);
bool isMyFileFormat(const QString fileName);
bool isMyFileType(const QString fileType);
};
extern OstmFileFormat fileFormat;
#endif

View File

@ -7,6 +7,9 @@ LIBS += \
PROTOS = \ PROTOS = \
protocol.proto \ protocol.proto \
emulproto.proto
PROTOS += \
mac.proto \ mac.proto \
payload.proto \ payload.proto \
eth2.proto \ eth2.proto \
@ -35,7 +38,7 @@ PROTOS = \
textproto.proto \ textproto.proto \
userscript.proto \ userscript.proto \
hexdump.proto \ hexdump.proto \
sample.proto sample.proto
HEADERS = \ HEADERS = \
abstractprotocol.h \ abstractprotocol.h \

View File

@ -35,16 +35,20 @@ PROTOS = \
# TODO: Move fileformat related stuff into a different library - why? # TODO: Move fileformat related stuff into a different library - why?
HEADERS = \ HEADERS = \
ostprotolib.h \ ostprotolib.h \
abstractfileformat.h \
fileformat.h \
ipv4addressdelegate.h \ ipv4addressdelegate.h \
ipv6addressdelegate.h \ ipv6addressdelegate.h \
nativefileformat.h \
ossnfileformat.h \
ostmfileformat.h \
pcapfileformat.h \ pcapfileformat.h \
pdmlfileformat.h \ pdmlfileformat.h \
pythonfileformat.h \ pythonfileformat.h \
pdmlprotocol.h \ pdmlprotocol.h \
pdmlprotocols.h \ pdmlprotocols.h \
pdmlreader.h pdmlreader.h \
sessionfileformat.h \
streamfileformat.h \
spinboxdelegate.h
HEADERS += \ HEADERS += \
abstractprotocolconfig.h \ abstractprotocolconfig.h \
@ -79,14 +83,18 @@ HEADERS += \
SOURCES += \ SOURCES += \
ostprotolib.cpp \ ostprotolib.cpp \
abstractfileformat.cpp \ nativefileformat.cpp \
fileformat.cpp \ ossnfileformat.cpp \
ostmfileformat.cpp \
pcapfileformat.cpp \ pcapfileformat.cpp \
pdmlfileformat.cpp \ pdmlfileformat.cpp \
pythonfileformat.cpp \ pythonfileformat.cpp \
pdmlprotocol.cpp \ pdmlprotocol.cpp \
pdmlprotocols.cpp \ pdmlprotocols.cpp \
pdmlreader.cpp \ pdmlreader.cpp \
sessionfileformat.cpp \
streamfileformat.cpp \
spinboxdelegate.cpp
SOURCES += \ SOURCES += \
protocolwidgetfactory.cpp \ protocolwidgetfactory.cpp \

View File

@ -91,7 +91,7 @@ PcapFileFormat::~PcapFileFormat()
delete importDialog_; delete importDialog_;
} }
bool PcapFileFormat::openStreams(const QString fileName, bool PcapFileFormat::open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error) OstProto::StreamConfigList &streams, QString &error)
{ {
bool isOk = false; bool isOk = false;
@ -325,7 +325,7 @@ bool PcapFileFormat::openStreams(const QString fileName,
goto _diff_fail; goto _diff_fail;
} }
if (!saveStreams(streams, importedPcapFile.fileName(), error)) if (!save(streams, importedPcapFile.fileName(), error))
{ {
error.append("Error saving imported streams as PCAP for diff"); error.append("Error saving imported streams as PCAP for diff");
goto _diff_fail; goto _diff_fail;
@ -553,7 +553,7 @@ bool PcapFileFormat::readPacket(PcapPacketHeader &pktHdr, QByteArray &pktBuf)
return true; return true;
} }
bool PcapFileFormat::saveStreams(const OstProto::StreamConfigList streams, bool PcapFileFormat::save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error) const QString fileName, QString &error)
{ {
bool isOk = false; bool isOk = false;

View File

@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#ifndef _PCAP_FILE_FORMAT_H #ifndef _PCAP_FILE_FORMAT_H
#define _PCAP_FILE_FORMAT_H #define _PCAP_FILE_FORMAT_H
#include "abstractfileformat.h" #include "streamfileformat.h"
#include "ui_pcapfileimport.h" #include "ui_pcapfileimport.h"
#include <QDataStream> #include <QDataStream>
@ -39,7 +39,7 @@ private:
}; };
class PdmlReader; class PdmlReader;
class PcapFileFormat : public AbstractFileFormat class PcapFileFormat : public StreamFileFormat
{ {
friend class PdmlReader; friend class PdmlReader;
@ -47,9 +47,9 @@ public:
PcapFileFormat(); PcapFileFormat();
~PcapFileFormat(); ~PcapFileFormat();
bool openStreams(const QString fileName, bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error); OstProto::StreamConfigList &streams, QString &error);
bool saveStreams(const OstProto::StreamConfigList streams, bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error); const QString fileName, QString &error);
virtual QDialog* openOptionsDialog(); virtual QDialog* openOptionsDialog();

View File

@ -35,7 +35,7 @@ PdmlFileFormat::~PdmlFileFormat()
{ {
} }
bool PdmlFileFormat::openStreams(const QString fileName, bool PdmlFileFormat::open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error) OstProto::StreamConfigList &streams, QString &error)
{ {
bool isOk = false; bool isOk = false;
@ -75,12 +75,12 @@ _exit:
return isOk; return isOk;
} }
bool PdmlFileFormat::saveStreams(const OstProto::StreamConfigList streams, bool PdmlFileFormat::save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error) const QString fileName, QString &error)
{ {
bool isOk = false; bool isOk = false;
QTemporaryFile pcapFile; QTemporaryFile pcapFile;
AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromType("PCAP"); StreamFileFormat *fmt = StreamFileFormat::fileFormatFromType("PCAP");
QProcess tshark; QProcess tshark;
Q_ASSERT(fmt); Q_ASSERT(fmt);
@ -97,7 +97,7 @@ bool PdmlFileFormat::saveStreams(const OstProto::StreamConfigList streams,
connect(fmt, SIGNAL(progress(int)), this, SIGNAL(progress(int))); connect(fmt, SIGNAL(progress(int)), this, SIGNAL(progress(int)));
emit status("Writing intermediate PCAP file..."); emit status("Writing intermediate PCAP file...");
isOk = fmt->saveStreams(streams, pcapFile.fileName(), error); isOk = fmt->save(streams, pcapFile.fileName(), error);
qDebug("generating PDML %s", fileName.toAscii().constData()); qDebug("generating PDML %s", fileName.toAscii().constData());
emit status("Converting PCAP to PDML..."); emit status("Converting PCAP to PDML...");

View File

@ -19,17 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#ifndef _PDML_FILE_FORMAT_H #ifndef _PDML_FILE_FORMAT_H
#define _PDML_FILE_FORMAT_H #define _PDML_FILE_FORMAT_H
#include "abstractfileformat.h" #include "streamfileformat.h"
class PdmlFileFormat : public AbstractFileFormat class PdmlFileFormat : public StreamFileFormat
{ {
public: public:
PdmlFileFormat(); PdmlFileFormat();
~PdmlFileFormat(); ~PdmlFileFormat();
virtual bool openStreams(const QString fileName, virtual bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error); OstProto::StreamConfigList &streams, QString &error);
virtual bool saveStreams(const OstProto::StreamConfigList streams, virtual bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error); const QString fileName, QString &error);
bool isMyFileFormat(const QString fileName); bool isMyFileFormat(const QString fileName);

View File

@ -274,6 +274,55 @@ message Notification {
optional PortIdList port_id_list = 6; optional PortIdList port_id_list = 6;
} }
/*
* Protocol Emulation
*/
message DeviceGroupId {
required uint32 id = 1;
}
message DeviceGroupCore {
optional string name = 1;
}
message DeviceGroupIdList {
required PortId port_id = 1;
repeated DeviceGroupId device_group_id = 2;
}
message EncapEmulation {
// Encap Protocols implemented as extensions
extensions 1000 to 1999;
}
message DeviceGroup {
required DeviceGroupId device_group_id = 1;
optional DeviceGroupCore core = 2;
optional EncapEmulation encap = 3;
optional uint32 device_count = 4 [default = 1]; // per-encap
// Device Protocols implemented as extensions
extensions 2000 to 5999;
}
message DeviceGroupConfigList {
required PortId port_id = 1;
repeated DeviceGroup device_group = 2;
}
message PortDeviceList {
required PortId port_id = 1;
extensions 100 to 199;
}
message PortNeighborList {
required PortId port_id = 1;
extensions 100 to 199;
}
service OstService { service OstService {
rpc getPortIdList(Void) returns (PortIdList); rpc getPortIdList(Void) returns (PortIdList);
rpc getPortConfig(PortIdList) returns (PortConfigList); rpc getPortConfig(PortIdList) returns (PortConfigList);
@ -296,5 +345,18 @@ service OstService {
rpc clearStats(PortIdList) returns (Ack); rpc clearStats(PortIdList) returns (Ack);
rpc checkVersion(VersionInfo) returns (VersionCompatibility); rpc checkVersion(VersionInfo) returns (VersionCompatibility);
// Device Emulation
rpc getDeviceGroupIdList(PortId) returns (DeviceGroupIdList);
rpc getDeviceGroupConfig(DeviceGroupIdList) returns (DeviceGroupConfigList);
rpc addDeviceGroup(DeviceGroupIdList) returns (Ack);
rpc deleteDeviceGroup(DeviceGroupIdList) returns (Ack);
rpc modifyDeviceGroup(DeviceGroupConfigList) returns (Ack);
rpc getDeviceList(PortId) returns (PortDeviceList);
rpc resolveDeviceNeighbors(PortIdList) returns (Ack);
rpc clearDeviceNeighbors(PortIdList) returns (Ack);
rpc getDeviceNeighbors(PortId) returns (PortNeighborList);
} }

View File

@ -46,14 +46,14 @@ PythonFileFormat::~PythonFileFormat()
// Nothing to do // Nothing to do
} }
bool PythonFileFormat::openStreams(const QString /*fileName*/, bool PythonFileFormat::open(const QString /*fileName*/,
OstProto::StreamConfigList &/*streams*/, QString &/*error*/) OstProto::StreamConfigList &/*streams*/, QString &/*error*/)
{ {
// NOT SUPPORTED! // NOT SUPPORTED!
return false; return false;
} }
bool PythonFileFormat::saveStreams(const OstProto::StreamConfigList streams, bool PythonFileFormat::save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error) const QString fileName, QString &error)
{ {
QFile file(fileName); QFile file(fileName);

View File

@ -20,19 +20,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#ifndef _PYTHON_FILE_FORMAT_H #ifndef _PYTHON_FILE_FORMAT_H
#define _PYTHON_FILE_FORMAT_H #define _PYTHON_FILE_FORMAT_H
#include "abstractfileformat.h" #include "streamfileformat.h"
#include <QTextStream> #include <QTextStream>
class PythonFileFormat : public AbstractFileFormat class PythonFileFormat : public StreamFileFormat
{ {
public: public:
PythonFileFormat(); PythonFileFormat();
~PythonFileFormat(); ~PythonFileFormat();
virtual bool openStreams(const QString fileName, virtual bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error); OstProto::StreamConfigList &streams, QString &error);
virtual bool saveStreams(const OstProto::StreamConfigList streams, virtual bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error); const QString fileName, QString &error);
bool isMyFileFormat(const QString fileName); bool isMyFileFormat(const QString fileName);

View File

@ -0,0 +1,117 @@
/*
Copyright (C) 2016 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 "sessionfileformat.h"
#include "ossnfileformat.h"
#include <QStringList>
SessionFileFormat::SessionFileFormat()
{
stop_ = false;
}
SessionFileFormat::~SessionFileFormat()
{
}
QDialog* SessionFileFormat::openOptionsDialog()
{
return NULL;
}
QDialog* SessionFileFormat::saveOptionsDialog()
{
return NULL;
}
QStringList SessionFileFormat::supportedFileTypes(Operation op)
{
QStringList fileTypes;
fileTypes << "Ostinato Session (*.ossn)";
if (op == kOpenFile)
fileTypes << "All files (*)";
return fileTypes;
}
void SessionFileFormat::openAsync(const QString fileName,
OstProto::SessionContent &session, QString &error)
{
fileName_ = fileName;
openSession_ = &session;
error_ = &error;
op_ = kOpenFile;
stop_ = false;
start();
}
void SessionFileFormat::saveAsync(
const OstProto::SessionContent &session,
const QString fileName, QString &error)
{
saveSession_ = &session;
fileName_ = fileName;
error_ = &error;
op_ = kSaveFile;
stop_ = false;
start();
}
bool SessionFileFormat::result()
{
return result_;
}
SessionFileFormat* SessionFileFormat::fileFormatFromFile(
const QString fileName)
{
if (ossnFileFormat.isMyFileFormat(fileName))
return &ossnFileFormat;
return NULL;
}
SessionFileFormat* SessionFileFormat::fileFormatFromType(
const QString fileType)
{
if (ossnFileFormat.isMyFileType(fileType))
return &ossnFileFormat;
return NULL;
}
void SessionFileFormat::cancel()
{
stop_ = true;
}
void SessionFileFormat::run()
{
if (op_ == kOpenFile)
result_ = open(fileName_, *openSession_, *error_);
else if (op_ == kSaveFile)
result_ = save(*saveSession_, fileName_, *error_);
}

View File

@ -0,0 +1,87 @@
/*
Copyright (C) 2016 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 _SESSION_FILE_FORMAT_H
#define _SESSION_FILE_FORMAT_H
#include "fileformat.pb.h"
#include "protocol.pb.h"
#include <QThread>
#include <QString>
class QDialog;
class SessionFileFormat : public QThread
{
Q_OBJECT
public:
enum Operation { kOpenFile, kSaveFile };
SessionFileFormat();
virtual ~SessionFileFormat();
virtual bool open(const QString fileName,
OstProto::SessionContent &session, QString &error) = 0;
virtual bool save(const OstProto::SessionContent &session,
const QString fileName, QString &error) = 0;
virtual QDialog* openOptionsDialog();
virtual QDialog* saveOptionsDialog();
void openAsync(const QString fileName,
OstProto::SessionContent &session, QString &error);
void saveAsync(const OstProto::SessionContent &session,
const QString fileName, QString &error);
bool result();
static QStringList supportedFileTypes(Operation op);
static SessionFileFormat* fileFormatFromFile(const QString fileName);
static SessionFileFormat* fileFormatFromType(const QString fileType);
virtual bool isMyFileFormat(const QString fileName) = 0;
virtual bool isMyFileType(const QString fileType) = 0;
signals:
void status(QString text);
void target(int value);
void progress(int value);
public slots:
void cancel();
protected:
void run();
bool stop_;
private:
QString fileName_;
OstProto::SessionContent *openSession_;
const OstProto::SessionContent *saveSession_;
QString *error_;
Operation op_;
bool result_;
};
#endif

112
common/spinboxdelegate.cpp Normal file
View File

@ -0,0 +1,112 @@
/*
Copyright (C) 2016 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/>
This file incorporates work covered by the following copyright and
permission notice:
Copyright (C) 2015 The Qt Company Ltd.
Contact: http://www.qt.io/licensing/
This file is part of the examples of the Qt Toolkit.
$QT_BEGIN_LICENSE:BSD$
You may use this file under the terms of the BSD license as follows:
"Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
Neither the name of The Qt Company Ltd nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
$QT_END_LICENSE$
*/
//#include <QtGui>
#include "spinboxdelegate.h"
#include <limits.h>
SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
: QItemDelegate(parent)
{
}
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &index) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setMinimum(colMin_.contains(index.column()) ?
colMin_.value(index.column()) : 0);
editor->setMaximum(colMax_.contains(index.column()) ?
colMax_.value(index.column()) : INT_MAX);
return editor;
}
void SpinBoxDelegate::setColumnRange(int col, int min, int max)
{
colMin_.insert(col, min);
colMax_.insert(col, max);
}
void SpinBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::EditRole).toInt();
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->setValue(value);
}
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->interpretText();
int value = spinBox->value();
model->setData(index, value, Qt::EditRole);
}
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}

93
common/spinboxdelegate.h Normal file
View File

@ -0,0 +1,93 @@
/*
Copyright (C) 2016 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/>
This file incorporates work covered by the following copyright and
permission notice:
Copyright (C) 2015 The Qt Company Ltd.
Contact: http://www.qt.io/licensing/
This file is part of the examples of the Qt Toolkit.
$QT_BEGIN_LICENSE:BSD$
You may use this file under the terms of the BSD license as follows:
"Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
Neither the name of The Qt Company Ltd nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
$QT_END_LICENSE$
*/
#ifndef _SPIN_BOX_DELEGATE_H
#define _SPIN_BOX_DELEGATE_H
#include <QHash>
#include <QItemDelegate>
#include <QModelIndex>
#include <QObject>
#include <QSize>
#include <QSpinBox>
class SpinBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject *parent = 0);
void setColumnRange(int col, int min, int max);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &index) const;
private:
QHash<int, int> colMin_;
QHash<int, int> colMax_;
};
#endif

View File

@ -24,8 +24,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "protocolmanager.h" #include "protocolmanager.h"
extern ProtocolManager *OstProtocolManager; extern ProtocolManager *OstProtocolManager;
extern quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex);
extern quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex);
StreamBase::StreamBase() : StreamBase::StreamBase(int portId) :
portId_(portId),
mStreamId(new OstProto::StreamId), mStreamId(new OstProto::StreamId),
mCore(new OstProto::StreamCore), mCore(new OstProto::StreamCore),
mControl(new OstProto::StreamControl) mControl(new OstProto::StreamControl)
@ -522,18 +525,22 @@ int StreamBase::frameCount() const
return count; return count;
} }
// Returns packet length - if bufMaxSize < frameLen(), returns truncated
// length i.e. bufMaxSize
int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const
{ {
int pktLen, len = 0; int maxSize, size, pktLen, len = 0;
pktLen = frameLen(frameIndex); pktLen = frameLen(frameIndex);
// pktLen is adjusted for CRC/FCS which will be added by the NIC // pktLen is adjusted for CRC/FCS which will be added by the NIC
pktLen -= kFcsSize; pktLen -= kFcsSize;
if ((pktLen < 0) || (pktLen > bufMaxSize)) if (pktLen <= 0)
return 0; return 0;
maxSize = qMin(pktLen, bufMaxSize);
ProtocolListIterator *iter; ProtocolListIterator *iter;
iter = createProtocolListIterator(); iter = createProtocolListIterator();
@ -545,17 +552,33 @@ int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const
proto = iter->next(); proto = iter->next();
ba = proto->protocolFrameValue(frameIndex); ba = proto->protocolFrameValue(frameIndex);
if (len + ba.size() < bufMaxSize) size = qMin(ba.size(), maxSize-len);
memcpy(buf+len, ba.constData(), ba.size()); memcpy(buf+len, ba.constData(), size);
len += ba.size(); len += size;
if (len == maxSize)
break;
} }
delete iter; delete iter;
// Pad with zero, if required // Pad with zero, if required and if we have space
if (len < pktLen) if (len < maxSize) {
memset(buf+len, 0, pktLen-len); size = maxSize-len;
memset(buf+len, 0, size);
len += size;
}
return pktLen; return len;
}
quint64 StreamBase::deviceMacAddress(int frameIndex) const
{
return getDeviceMacAddress(portId_, int(mStreamId->id()), frameIndex);
}
quint64 StreamBase::neighborMacAddress(int frameIndex) const
{
return getNeighborMacAddress(portId_, int(mStreamId->id()), frameIndex);
} }
bool StreamBase::preflightCheck(QString &result) const bool StreamBase::preflightCheck(QString &result) const

View File

@ -33,15 +33,8 @@ class ProtocolListIterator;
class StreamBase class StreamBase
{ {
private:
OstProto::StreamId *mStreamId;
OstProto::StreamCore *mCore;
OstProto::StreamControl *mControl;
ProtocolList *currentFrameProtocols;
public: public:
StreamBase(); StreamBase(int portId = -1);
~StreamBase(); ~StreamBase();
void protoDataCopyFrom(const OstProto::Stream &stream); void protoDataCopyFrom(const OstProto::Stream &stream);
@ -143,9 +136,22 @@ public:
int frameProtocolLength(int frameIndex) const; int frameProtocolLength(int frameIndex) const;
int frameCount() const; int frameCount() const;
int frameValue(uchar *buf, int bufMaxSize, int frameIndex) const; int frameValue(uchar *buf, int bufMaxSize, int frameIndex) const;
quint64 deviceMacAddress(int frameIndex) const;
quint64 neighborMacAddress(int frameIndex) const;
bool preflightCheck(QString &result) const; bool preflightCheck(QString &result) const;
static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2); static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2);
private:
int portId_;
OstProto::StreamId *mStreamId;
OstProto::StreamCore *mCore;
OstProto::StreamControl *mControl;
ProtocolList *currentFrameProtocols;
}; };
#endif #endif

View File

@ -17,74 +17,82 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/> along with this program. If not, see <http://www.gnu.org/licenses/>
*/ */
#include "abstractfileformat.h" #include "streamfileformat.h"
#include "fileformat.h" #include "ostmfileformat.h"
#include "pcapfileformat.h" #include "pcapfileformat.h"
#include "pdmlfileformat.h" #include "pdmlfileformat.h"
#include "pythonfileformat.h" #include "pythonfileformat.h"
#include <QStringList> #include <QStringList>
AbstractFileFormat::AbstractFileFormat() StreamFileFormat::StreamFileFormat()
{ {
stop_ = false; stop_ = false;
} }
AbstractFileFormat::~AbstractFileFormat() StreamFileFormat::~StreamFileFormat()
{ {
} }
QDialog* AbstractFileFormat::openOptionsDialog() QDialog* StreamFileFormat::openOptionsDialog()
{ {
return NULL; return NULL;
} }
QDialog* AbstractFileFormat::saveOptionsDialog() QDialog* StreamFileFormat::saveOptionsDialog()
{ {
return NULL; return NULL;
} }
QStringList AbstractFileFormat::supportedFileTypes() QStringList StreamFileFormat::supportedFileTypes(Operation op)
{ {
return QStringList() QStringList fileTypes;
<< "Ostinato (*)"
fileTypes
<< "Ostinato (*.ostm)"
<< "PCAP (*)" << "PCAP (*)"
<< "PDML (*.pdml)" << "PDML (*.pdml)";
<< "PythonScript (*.py)";
if (op == kSaveFile)
fileTypes << "PythonScript (*.py)";
else if (op == kOpenFile)
fileTypes << "All files (*)";
return fileTypes;
} }
void AbstractFileFormat::openStreamsOffline(const QString fileName, void StreamFileFormat::openAsync(const QString fileName,
OstProto::StreamConfigList &streams, QString &error) OstProto::StreamConfigList &streams, QString &error)
{ {
fileName_ = fileName; fileName_ = fileName;
openStreams_ = &streams; openStreams_ = &streams;
error_ = &error; error_ = &error;
op_ = kOpen; op_ = kOpenFile;
stop_ = false; stop_ = false;
start(); start();
} }
void AbstractFileFormat::saveStreamsOffline( void StreamFileFormat::saveAsync(
const OstProto::StreamConfigList streams, const OstProto::StreamConfigList streams,
const QString fileName, QString &error) const QString fileName, QString &error)
{ {
saveStreams_ = streams; saveStreams_ = streams;
fileName_ = fileName; fileName_ = fileName;
error_ = &error; error_ = &error;
op_ = kSave; op_ = kSaveFile;
stop_ = false; stop_ = false;
start(); start();
} }
bool AbstractFileFormat::result() bool StreamFileFormat::result()
{ {
return result_; return result_;
} }
AbstractFileFormat* AbstractFileFormat::fileFormatFromFile( StreamFileFormat* StreamFileFormat::fileFormatFromFile(
const QString fileName) const QString fileName)
{ {
if (fileFormat.isMyFileFormat(fileName)) if (fileFormat.isMyFileFormat(fileName))
@ -99,7 +107,7 @@ AbstractFileFormat* AbstractFileFormat::fileFormatFromFile(
return NULL; return NULL;
} }
AbstractFileFormat* AbstractFileFormat::fileFormatFromType( StreamFileFormat* StreamFileFormat::fileFormatFromType(
const QString fileType) const QString fileType)
{ {
@ -118,15 +126,15 @@ AbstractFileFormat* AbstractFileFormat::fileFormatFromType(
return NULL; return NULL;
} }
void AbstractFileFormat::cancel() void StreamFileFormat::cancel()
{ {
stop_ = true; stop_ = true;
} }
void AbstractFileFormat::run() void StreamFileFormat::run()
{ {
if (op_ == kOpen) if (op_ == kOpenFile)
result_ = openStreams(fileName_, *openStreams_, *error_); result_ = open(fileName_, *openStreams_, *error_);
else if (op_ == kSave) else if (op_ == kSaveFile)
result_ = saveStreams(saveStreams_, fileName_, *error_); result_ = save(saveStreams_, fileName_, *error_);
} }

View File

@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/> along with this program. If not, see <http://www.gnu.org/licenses/>
*/ */
#ifndef _ABSTRACT_FILE_FORMAT_H #ifndef _STREAM_FILE_FORMAT_H
#define _ABSTRACT_FILE_FORMAT_H #define _STREAM_FILE_FORMAT_H
#include "protocol.pb.h" #include "protocol.pb.h"
@ -27,32 +27,34 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
class QDialog; class QDialog;
class AbstractFileFormat : public QThread class StreamFileFormat : public QThread
{ {
Q_OBJECT Q_OBJECT
public: public:
AbstractFileFormat(); enum Operation { kOpenFile, kSaveFile };
virtual ~AbstractFileFormat();
virtual bool openStreams(const QString fileName, StreamFileFormat();
virtual ~StreamFileFormat();
virtual bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error) = 0; OstProto::StreamConfigList &streams, QString &error) = 0;
virtual bool saveStreams(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 QDialog* openOptionsDialog();
virtual QDialog* saveOptionsDialog(); virtual QDialog* saveOptionsDialog();
void openStreamsOffline(const QString fileName, void openAsync(const QString fileName,
OstProto::StreamConfigList &streams, QString &error); OstProto::StreamConfigList &streams, QString &error);
void saveStreamsOffline(const OstProto::StreamConfigList streams, void saveAsync(const OstProto::StreamConfigList streams,
const QString fileName, QString &error); const QString fileName, QString &error);
bool result(); bool result();
static QStringList supportedFileTypes(); static QStringList supportedFileTypes(Operation op);
static AbstractFileFormat* fileFormatFromFile(const QString fileName); static StreamFileFormat* fileFormatFromFile(const QString fileName);
static AbstractFileFormat* fileFormatFromType(const QString fileType); static StreamFileFormat* fileFormatFromType(const QString fileType);
#if 0 #if 0
bool isMyFileFormat(const QString fileName) = 0; bool isMyFileFormat(const QString fileName) = 0;
@ -73,16 +75,11 @@ protected:
bool stop_; bool stop_;
private: private:
enum kOp
{
kOpen,
kSave
};
QString fileName_; QString fileName_;
OstProto::StreamConfigList *openStreams_; OstProto::StreamConfigList *openStreams_;
OstProto::StreamConfigList saveStreams_; OstProto::StreamConfigList saveStreams_;
QString *error_; QString *error_;
kOp op_; Operation op_;
bool result_; bool result_;
}; };

185
common/uint128.h Normal file
View File

@ -0,0 +1,185 @@
/*
Copyright (C) 2015 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 _UINT128_H
#define _UINT128_H
#include <QHash>
#include <QtGlobal>
#include <qendian.h>
class UInt128
{
public:
UInt128();
UInt128(quint64 hi, quint64 lo);
UInt128(quint8 *value);
quint64 hi64() const;
quint64 lo64() const;
quint8* toArray() const;
bool operator==(const UInt128 &other) const;
bool operator!=(const UInt128 &other) const;
UInt128 operator+(const UInt128 &other) const;
UInt128 operator*(const uint &other) const;
UInt128 operator<<(const int &shift) const;
UInt128 operator~() const;
UInt128 operator&(const UInt128 &other) const;
UInt128 operator|(const UInt128 &other) const;
private:
quint64 hi_;
quint64 lo_;
quint8 array_[16];
};
inline UInt128::UInt128()
{
// Do nothing - value will be garbage like any other uint
}
inline UInt128::UInt128(quint64 hi, quint64 lo)
{
hi_ = hi;
lo_ = lo;
}
inline UInt128::UInt128(quint8 *value)
{
hi_ = (quint64(value[0]) << 56)
| (quint64(value[1]) << 48)
| (quint64(value[2]) << 40)
| (quint64(value[3]) << 32)
| (quint64(value[4]) << 24)
| (quint64(value[5]) << 16)
| (quint64(value[6]) << 8)
| (quint64(value[7]) << 0);
lo_ = (quint64(value[ 8]) << 56)
| (quint64(value[ 9]) << 48)
| (quint64(value[10]) << 40)
| (quint64(value[11]) << 32)
| (quint64(value[12]) << 24)
| (quint64(value[13]) << 16)
| (quint64(value[14]) << 8)
| (quint64(value[15]) << 0);
}
inline quint64 UInt128::hi64() const
{
return hi_;
}
inline quint64 UInt128::lo64() const
{
return lo_;
}
inline quint8* UInt128::toArray() const
{
*(quint64*)(array_ + 0) = qToBigEndian<quint64>(hi_);
*(quint64*)(array_ + 8) = qToBigEndian<quint64>(lo_);
return (quint8*)array_;
}
inline bool UInt128::operator==(const UInt128 &other) const
{
return ((hi_ == other.hi_) && (lo_ == other.lo_));
}
inline bool UInt128::operator!=(const UInt128 &other) const
{
return ((hi_ != other.hi_) || (lo_ != other.lo_));
}
inline UInt128 UInt128::operator+(const UInt128 &other) const
{
UInt128 sum;
sum.lo_ = lo_ + other.lo_;
sum.hi_ = hi_ + other.hi_ + (sum.lo_ < lo_);
return sum;
}
inline UInt128 UInt128::operator*(const uint &other) const
{
UInt128 product;
// FIXME
product.hi_ = 0;
product.lo_ = lo_ * other;
return product;
}
inline UInt128 UInt128::operator<<(const int &shift) const
{
UInt128 shifted;
if (shift < 64)
return UInt128((hi_<<shift) | (lo_>>(64-shift)), lo_ << shift);
return UInt128(hi_<<(shift-64), 0);
}
inline UInt128 UInt128::operator~() const
{
return UInt128(~hi_, ~lo_);
}
inline UInt128 UInt128::operator&(const UInt128 &other) const
{
return UInt128(hi_ & other.hi_, lo_ & other.lo_);
}
inline UInt128 UInt128::operator|(const UInt128 &other) const
{
return UInt128(hi_ | other.hi_, lo_ | other.lo_);
}
template <> inline UInt128 qFromBigEndian<UInt128>(const uchar *src)
{
quint64 hi, lo;
hi = qFromBigEndian<quint64>(src);
lo = qFromBigEndian<quint64>(src+8);
return UInt128(hi, lo);
}
template <> inline UInt128 qToBigEndian<UInt128>(const UInt128 src)
{
quint64 hi, lo;
hi = qToBigEndian<quint64>(src.hi64());
lo = qToBigEndian<quint64>(src.lo64());
return UInt128(hi, lo);
}
inline uint qHash(const UInt128 &key)
{
return qHash(key.hi64()) ^ qHash(key.lo64());
}
#endif

View File

@ -32,6 +32,7 @@ PbRpcChannel::PbRpcChannel(QString serverName, quint16 port,
isPending = false; isPending = false;
pendingMethodId = -1; // don't care as long as isPending is false pendingMethodId = -1; // don't care as long as isPending is false
method = NULL;
controller = NULL; controller = NULL;
done = NULL; done = NULL;
response = NULL; response = NULL;
@ -107,9 +108,10 @@ void PbRpcChannel::CallMethod(
{ {
RpcCall call; RpcCall call;
qDebug("RpcChannel: queueing rpc since method %d is pending;<----\n " qDebug("RpcChannel: queueing rpc since method %d is pending;<----\n "
"queued method = %d\n" "queued method = %d:%s\n"
"queued message = \n%s\n---->", "queued message = \n%s\n---->",
pendingMethodId, method->index(), req->DebugString().c_str()); pendingMethodId, method->index(), method->name().c_str(),
req->DebugString().c_str());
call.method = method; call.method = method;
call.controller = controller; call.controller = controller;
@ -128,7 +130,8 @@ void PbRpcChannel::CallMethod(
if (!req->IsInitialized()) if (!req->IsInitialized())
{ {
qWarning("RpcChannel: missing required fields in request <----"); qWarning("RpcChannel: missing required fields in request <----");
qDebug("req = \n%s", req->DebugString().c_str()); qDebug("req = %s\n%s", method->input_type()->name().c_str(),
req->DebugString().c_str());
qDebug("error = \n%s\n--->", req->InitializationErrorString().c_str()); qDebug("error = \n%s\n--->", req->InitializationErrorString().c_str());
controller->SetFailed("Required fields missing"); controller->SetFailed("Required fields missing");
@ -137,6 +140,7 @@ void PbRpcChannel::CallMethod(
} }
pendingMethodId = method->index(); pendingMethodId = method->index();
this->method=method;
this->controller=controller; this->controller=controller;
this->done=done; this->done=done;
this->response=response; this->response=response;
@ -153,8 +157,10 @@ void PbRpcChannel::CallMethod(
qDebug("client(%s) sending %d bytes <----", __FUNCTION__, qDebug("client(%s) sending %d bytes <----", __FUNCTION__,
PB_HDR_SIZE + len); PB_HDR_SIZE + len);
BUFDUMP(msg, PB_HDR_SIZE); BUFDUMP(msg, PB_HDR_SIZE);
qDebug("method = %d\n req = \n%s\n---->", qDebug("method = %d:%s\n req = %s\n%s\n---->",
method->index(), req->DebugString().c_str()); method->index(), method->name().c_str(),
method->input_type()->name().c_str(),
req->DebugString().c_str());
} }
mpSocket->write(msg, PB_HDR_SIZE); mpSocket->write(msg, PB_HDR_SIZE);
@ -308,14 +314,18 @@ _top:
if (method != 13) if (method != 13)
{ {
qDebug("client(%s): Received Msg <---- ", __FUNCTION__); qDebug("client(%s): Received Msg <---- ", __FUNCTION__);
qDebug("method = %d\nresp = \n%s\n---->", qDebug("method = %d:%s\nresp = %s\n%s\n---->",
method, response->DebugString().c_str()); method, this->method->name().c_str(),
this->method->output_type()->name().c_str(),
response->DebugString().c_str());
} }
if (!response->IsInitialized()) if (!response->IsInitialized())
{ {
qWarning("RpcChannel: missing required fields in response <----"); qWarning("RpcChannel: missing required fields in response <----");
qDebug("resp = \n%s", response->DebugString().c_str()); qDebug("resp = %s\n%s",
this->method->output_type()->name().c_str(),
response->DebugString().c_str());
qDebug("error = \n%s\n--->", qDebug("error = \n%s\n--->",
response->InitializationErrorString().c_str()); response->InitializationErrorString().c_str());
@ -419,6 +429,7 @@ _top:
done->Run(); done->Run();
pendingMethodId = -1; pendingMethodId = -1;
this->method = NULL;
controller = NULL; controller = NULL;
response = NULL; response = NULL;
isPending = false; isPending = false;
@ -428,9 +439,11 @@ _top:
{ {
RpcCall call = pendingCallList.takeFirst(); RpcCall call = pendingCallList.takeFirst();
qDebug("RpcChannel: executing queued method <----\n" qDebug("RpcChannel: executing queued method <----\n"
"method = %d\n" "method = %d:%s\n"
"req = \n%s\n---->", "req = %s\n%s\n---->",
call.method->index(), call.request->DebugString().c_str()); call.method->index(), call.method->name().c_str(),
call.method->input_type()->name().c_str(),
call.request->DebugString().c_str());
CallMethod(call.method, call.controller, call.request, call.response, CallMethod(call.method, call.controller, call.request, call.response,
call.done); call.done);
} }
@ -475,6 +488,7 @@ void PbRpcChannel::on_mpSocket_disconnected()
qDebug("In %s", __FUNCTION__); qDebug("In %s", __FUNCTION__);
pendingMethodId = -1; pendingMethodId = -1;
method = NULL;
controller = NULL; controller = NULL;
response = NULL; response = NULL;
isPending = false; isPending = false;

View File

@ -49,6 +49,7 @@ class PbRpcChannel : public QObject, public ::google::protobuf::RpcChannel
/*! \todo (MED) : change controller, done and response to references /*! \todo (MED) : change controller, done and response to references
instead of pointers? */ instead of pointers? */
const ::google::protobuf::MethodDescriptor *method;
::google::protobuf::RpcController *controller; ::google::protobuf::RpcController *controller;
::google::protobuf::Closure *done; ::google::protobuf::Closure *done;
::google::protobuf::Message *response; ::google::protobuf::Message *response;

View File

@ -28,6 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <google/protobuf/service.h> #include <google/protobuf/service.h>
#include <google/protobuf/io/zero_copy_stream_impl.h> #include <google/protobuf/io/zero_copy_stream_impl.h>
#include <QDateTime>
#include <QHostAddress> #include <QHostAddress>
#include <QString> #include <QString>
#include <QTcpSocket> #include <QTcpSocket>
@ -82,6 +83,7 @@ void RpcConnection::start()
return; return;
} }
qDebug("clientSock Thread = %p", clientSock->thread()); qDebug("clientSock Thread = %p", clientSock->thread());
qsrand(QDateTime::currentDateTime().toTime_t());
connId.setLocalData(new QString(id.arg(clientSock->peerAddress().toString()) connId.setLocalData(new QString(id.arg(clientSock->peerAddress().toString())
.arg(clientSock->peerPort()))); .arg(clientSock->peerPort())));

View File

@ -21,8 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "abstractport.h" #include "abstractport.h"
#include "../common/streambase.h"
#include "../common/abstractprotocol.h" #include "../common/abstractprotocol.h"
#include "../common/streambase.h"
#include "devicemanager.h"
#include "packetbuffer.h"
#include <QString> #include <QString>
#include <QIODevice> #include <QIODevice>
@ -47,6 +49,8 @@ AbstractPort::AbstractPort(int id, const char *device)
linkState_ = OstProto::LinkStateUnknown; linkState_ = OstProto::LinkStateUnknown;
minPacketSetSize_ = 1; minPacketSetSize_ = 1;
deviceManager_ = new DeviceManager(this);
maxStatsValue_ = ULLONG_MAX; // assume 64-bit stats maxStatsValue_ = ULLONG_MAX; // assume 64-bit stats
memset((void*) &stats_, 0, sizeof(stats_)); memset((void*) &stats_, 0, sizeof(stats_));
resetStats(); resetStats();
@ -54,6 +58,7 @@ AbstractPort::AbstractPort(int id, const char *device)
AbstractPort::~AbstractPort() AbstractPort::~AbstractPort()
{ {
delete deviceManager_;
} }
void AbstractPort::init() void AbstractPort::init()
@ -84,6 +89,11 @@ bool AbstractPort::modify(const OstProto::Port &port)
return ret; return ret;
} }
DeviceManager* AbstractPort::deviceManager()
{
return deviceManager_;
}
StreamBase* AbstractPort::streamAtIndex(int index) StreamBase* AbstractPort::streamAtIndex(int index)
{ {
Q_ASSERT(index < streamList_.size()); Q_ASSERT(index < streamList_.size());
@ -581,8 +591,8 @@ void AbstractPort::stats(PortStats *stats)
stats->rxBytes = (stats_.rxBytes >= epochStats_.rxBytes) ? stats->rxBytes = (stats_.rxBytes >= epochStats_.rxBytes) ?
stats_.rxBytes - epochStats_.rxBytes : stats_.rxBytes - epochStats_.rxBytes :
stats_.rxBytes + (maxStatsValue_ - epochStats_.rxBytes); stats_.rxBytes + (maxStatsValue_ - epochStats_.rxBytes);
stats->rxPps = stats_.rxPps; stats->rxPps = stats_.rxPps;
stats->rxBps = stats_.rxBps; stats->rxBps = stats_.rxBps;
stats->txPkts = (stats_.txPkts >= epochStats_.txPkts) ? stats->txPkts = (stats_.txPkts >= epochStats_.txPkts) ?
stats_.txPkts - epochStats_.txPkts : stats_.txPkts - epochStats_.txPkts :
@ -590,8 +600,8 @@ void AbstractPort::stats(PortStats *stats)
stats->txBytes = (stats_.txBytes >= epochStats_.txBytes) ? stats->txBytes = (stats_.txBytes >= epochStats_.txBytes) ?
stats_.txBytes - epochStats_.txBytes : stats_.txBytes - epochStats_.txBytes :
stats_.txBytes + (maxStatsValue_ - epochStats_.txBytes); stats_.txBytes + (maxStatsValue_ - epochStats_.txBytes);
stats->txPps = stats_.txPps; stats->txPps = stats_.txPps;
stats->txBps = stats_.txBps; stats->txBps = stats_.txBps;
stats->rxDrops = (stats_.rxDrops >= epochStats_.rxDrops) ? stats->rxDrops = (stats_.rxDrops >= epochStats_.rxDrops) ?
stats_.rxDrops - epochStats_.rxDrops : stats_.rxDrops - epochStats_.rxDrops :
@ -606,3 +616,82 @@ void AbstractPort::stats(PortStats *stats)
stats_.rxFrameErrors - epochStats_.rxFrameErrors : stats_.rxFrameErrors - epochStats_.rxFrameErrors :
stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors); stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors);
} }
void AbstractPort::clearDeviceNeighbors()
{
deviceManager_->clearDeviceNeighbors();
isSendQueueDirty_ = true;
}
void AbstractPort::resolveDeviceNeighbors()
{
// For a user triggered 'Resolve Neighbors', the behaviour we want is
// IP not in cache - send ARP/NDP request
// IP present in cache, but unresolved - re-send ARP/NDP request
// IP present in cache and resolved - don't sent ARP/NDP
//
// Device does not resend ARP/NDP requests if the IP address is
// already present in the cache, irrespective of whether it is
// resolved or not (this is done to avoid sending duplicate requests).
//
// So, to get the behaviour we want, let's clear all unresolved neighbors
// before calling resolve
deviceManager_->clearDeviceNeighbors(Device::kUnresolvedNeighbors);
// Resolve gateway for each device first ...
deviceManager_->resolveDeviceGateways();
// ... then resolve neighbor for each unique frame of each stream
// NOTE:
// 1. All the frames may have the same destination ip,but may have
// different source ip so may belong to a different emulated device;
// so we cannot optimize and send only one ARP
// 2. For a unidirectional stream, at egress, this will create ARP
// entries on the DUT for each of the source addresses
//
// TODO(optimization): Identify if stream does not vary in srcIp or dstIp
// - in which case resolve for only one frame of the stream
for (int i = 0; i < streamList_.size(); i++)
{
const StreamBase *stream = streamList_.at(i);
int frameCount = stream->frameVariableCount();
for (int j = 0; j < frameCount; j++) {
// we need the packet contents only uptil the L3 header
int pktLen = stream->frameValue(pktBuf_, kMaxL3PktSize, j);
if (pktLen) {
PacketBuffer pktBuf(pktBuf_, pktLen);
deviceManager_->resolveDeviceNeighbor(&pktBuf);
}
}
}
isSendQueueDirty_ = true;
}
quint64 AbstractPort::deviceMacAddress(int streamId, int frameIndex)
{
// we need the packet contents only uptil the L3 header
StreamBase *s = stream(streamId);
int pktLen = s->frameValue(pktBuf_, kMaxL3PktSize, frameIndex);
if (pktLen) {
PacketBuffer pktBuf(pktBuf_, pktLen);
return deviceManager_->deviceMacAddress(&pktBuf);
}
return 0;
}
quint64 AbstractPort::neighborMacAddress(int streamId, int frameIndex)
{
// we need the packet contents only uptil the L3 header
StreamBase *s = stream(streamId);
int pktLen = s->frameValue(pktBuf_, kMaxL3PktSize, frameIndex);
if (pktLen) {
PacketBuffer pktBuf(pktBuf_, pktLen);
return deviceManager_->neighborMacAddress(&pktBuf);
}
return 0;
}

View File

@ -25,9 +25,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "../common/protocol.pb.h" #include "../common/protocol.pb.h"
class DeviceManager;
class StreamBase; class StreamBase;
class PacketBuffer;
class QIODevice; class QIODevice;
// TODO: send notification back to client(s)
#define notify qWarning
class AbstractPort class AbstractPort
{ {
public: public:
@ -106,6 +111,17 @@ public:
void stats(PortStats *stats); void stats(PortStats *stats);
void resetStats() { epochStats_ = stats_; } void resetStats() { epochStats_ = stats_; }
DeviceManager* deviceManager();
virtual void startDeviceEmulation() = 0;
virtual void stopDeviceEmulation() = 0;
virtual int sendEmulationPacket(PacketBuffer *pktBuf) = 0;
void clearDeviceNeighbors();
void resolveDeviceNeighbors();
quint64 deviceMacAddress(int streamId, int frameIndex);
quint64 neighborMacAddress(int streamId, int frameIndex);
protected: protected:
void addNote(QString note); void addNote(QString note);
@ -122,12 +138,20 @@ protected:
struct PortStats stats_; struct PortStats stats_;
//! \todo Need lock for stats access/update //! \todo Need lock for stats access/update
DeviceManager *deviceManager_;
private: private:
bool isSendQueueDirty_; bool isSendQueueDirty_;
static const int kMaxPktSize = 16384; static const int kMaxPktSize = 16384;
uchar pktBuf_[kMaxPktSize]; uchar pktBuf_[kMaxPktSize];
// When finding a corresponding device for a packet, we need to inspect
// only uptil the L3 header; in the worst case this would be -
// mac (12) + 4 x vlan (16) + ethType (2) + ipv6 (40) = 74 bytes
// let's round it up to 80 bytes
static const int kMaxL3PktSize = 80;
/*! \note StreamBase::id() and index into streamList[] are NOT same! */ /*! \note StreamBase::id() and index into streamList[] are NOT same! */
QList<StreamBase*> streamList_; QList<StreamBase*> streamList_;

1118
server/device.cpp Normal file

File diff suppressed because it is too large Load Diff

124
server/device.h Normal file
View File

@ -0,0 +1,124 @@
/*
Copyright (C) 2015 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 _DEVICE_H
#define _DEVICE_H
#include "../common/emulproto.pb.h"
#include "../common/protocol.pb.h"
#include "../common/uint128.h"
#include <QByteArray>
#include <QHash>
class DeviceManager;
class PacketBuffer;
class DeviceKey: public QByteArray
{
};
class Device
{
public:
static const quint16 kVlanTpid = 0x8100;
enum NeighborSet {
kAllNeighbors,
kUnresolvedNeighbors
};
public:
Device(DeviceManager *deviceManager);
void setVlan(int index, quint16 vlan, quint16 tpid = kVlanTpid);
quint64 mac();
void setMac(quint64 mac);
void setIp4(quint32 address, int prefixLength, quint32 gateway);
void setIp6(UInt128 address, int prefixLength, UInt128 gateway);
void getConfig(OstEmul::Device *deviceConfig);
QString config();
DeviceKey key();
void clearKey();
int encapSize();
void encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type);
void receivePacket(PacketBuffer *pktBuf);
void transmitPacket(PacketBuffer *pktBuf);
void resolveGateway();
void clearNeighbors(Device::NeighborSet set);
void resolveNeighbor(PacketBuffer *pktBuf);
void getNeighbors(OstEmul::DeviceNeighborList *neighbors);
bool isOrigin(const PacketBuffer *pktBuf);
quint64 neighborMac(const PacketBuffer *pktBuf);
private: // methods
void receiveArp(PacketBuffer *pktBuf);
void sendArpRequest(PacketBuffer *pktBuf);
void sendArpRequest(quint32 tgtIp);
void receiveIp4(PacketBuffer *pktBuf);
void sendIp4Reply(PacketBuffer *pktBuf);
void receiveIcmp4(PacketBuffer *pktBuf);
void receiveIp6(PacketBuffer *pktBuf);
bool sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol);
void sendIp6Reply(PacketBuffer *pktBuf);
void receiveIcmp6(PacketBuffer *pktBuf);
void receiveNdp(PacketBuffer *pktBuf);
void sendNeighborSolicit(PacketBuffer *pktBuf);
void sendNeighborSolicit(UInt128 tgtIp);
void sendNeighborAdvertisement(PacketBuffer *pktBuf);
private: // data
static const int kMaxVlan = 4;
DeviceManager *deviceManager_;
int numVlanTags_;
quint32 vlan_[kMaxVlan];
quint64 mac_;
bool hasIp4_;
quint32 ip4_;
int ip4PrefixLength_;
quint32 ip4Gateway_;
bool hasIp6_;
UInt128 ip6_;
int ip6PrefixLength_;
UInt128 ip6Gateway_;
DeviceKey key_;
QHash<quint32, quint64> arpTable_;
QHash<UInt128, quint64> ndpTable_;
};
bool operator<(const DeviceKey &a1, const DeviceKey &a2);
#endif

494
server/devicemanager.cpp Normal file
View File

@ -0,0 +1,494 @@
/*
Copyright (C) 2015 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 "devicemanager.h"
#include "abstractport.h"
#include "device.h"
#include "../common/emulation.h"
#include "packetbuffer.h"
#include "../common/emulproto.pb.h"
#include <qendian.h>
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
const quint64 kBcastMac = 0xffffffffffffULL;
inline UInt128 UINT128(OstEmul::Ip6Address x)
{
return UInt128(x.hi(), x.lo());
}
inline bool isMacMcast(quint64 mac)
{
return (mac >> 40) & 0x01 == 0x01;
}
// XXX: Port owning DeviceManager already uses locks, so we don't use any
// locks within DeviceManager to protect deviceGroupList_ et.al.
DeviceManager::DeviceManager(AbstractPort *parent)
{
port_ = parent;
}
DeviceManager::~DeviceManager()
{
foreach(Device *dev, deviceList_)
delete dev;
foreach(OstProto::DeviceGroup *devGrp, deviceGroupList_)
delete devGrp;
}
int DeviceManager::deviceGroupCount()
{
return deviceGroupList_.size();
}
const OstProto::DeviceGroup* DeviceManager::deviceGroupAtIndex(int index)
{
if ((index < 0) || (index >= deviceGroupCount())) {
qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__,
index, deviceGroupCount() - 1);
return NULL;
}
// Sort List by 'id', get the id at 'index' and then corresponding devGrp
return deviceGroupList_.value(deviceGroupList_.uniqueKeys().value(index));
}
const OstProto::DeviceGroup* DeviceManager::deviceGroup(uint deviceGroupId)
{
return deviceGroupList_.value(deviceGroupId);
}
bool DeviceManager::addDeviceGroup(uint deviceGroupId)
{
OstProto::DeviceGroup *deviceGroup;
if (deviceGroupList_.contains(deviceGroupId)) {
qWarning("%s: deviceGroup id %u already exists", __FUNCTION__,
deviceGroupId);
return false;
}
deviceGroup = newDeviceGroup(port_->id());
deviceGroup->mutable_device_group_id()->set_id(deviceGroupId);
deviceGroupList_.insert(deviceGroupId, deviceGroup);
enumerateDevices(deviceGroup, kAdd);
// Start emulation when first device is added
if ((deviceCount() == 1) && port_)
port_->startDeviceEmulation();
return true;
}
bool DeviceManager::deleteDeviceGroup(uint deviceGroupId)
{
OstProto::DeviceGroup *deviceGroup;
if (!deviceGroupList_.contains(deviceGroupId)) {
qWarning("%s: deviceGroup id %u does not exist", __FUNCTION__,
deviceGroupId);
return false;
}
deviceGroup = deviceGroupList_.take(deviceGroupId);
enumerateDevices(deviceGroup, kDelete);
delete deviceGroup;
// Stop emulation if no devices remain
if ((deviceCount() == 0) && port_)
port_->stopDeviceEmulation();
return true;
}
bool DeviceManager::modifyDeviceGroup(const OstProto::DeviceGroup *deviceGroup)
{
quint32 id = deviceGroup->device_group_id().id();
OstProto::DeviceGroup *myDeviceGroup = deviceGroupList_.value(id);
if (!myDeviceGroup) {
qWarning("%s: deviceGroup id %u does not exist", __FUNCTION__, id);
return false;
}
enumerateDevices(myDeviceGroup, kDelete);
myDeviceGroup->CopyFrom(*deviceGroup);
// If mac step is 0, silently override to 1 - otherwise we won't have
// unique DeviceKeys
if (myDeviceGroup->GetExtension(OstEmul::mac).step() == 0)
myDeviceGroup->MutableExtension(OstEmul::mac)->set_step(1);
// Default value for ip6 step should be 1 (not 0)
if (myDeviceGroup->HasExtension(OstEmul::ip6)
&& !myDeviceGroup->GetExtension(OstEmul::ip6).has_step())
myDeviceGroup->MutableExtension(OstEmul::ip6)
->mutable_step()->set_lo(1);
enumerateDevices(myDeviceGroup, kAdd);
return true;
}
int DeviceManager::deviceCount()
{
return deviceList_.size();
}
void DeviceManager::getDeviceList(
OstProto::PortDeviceList *deviceList)
{
foreach(Device *device, sortedDeviceList_) {
OstEmul::Device *dev =
deviceList->AddExtension(OstEmul::device);
device->getConfig(dev);
}
}
void DeviceManager::receivePacket(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->data();
int offset = 0;
Device dk(this);
Device *device;
quint64 dstMac;
quint16 ethType;
quint16 vlan;
int idx = 0;
// We assume pkt is ethernet
// TODO: extend for other link layer types
// All frames we are interested in should be at least 32 bytes
if (pktBuf->length() < 32) {
qWarning("short frame of %d bytes, skipping ...", pktBuf->length());
goto _exit;
}
// Extract dstMac
dstMac = qFromBigEndian<quint32>(pktData + offset);
offset += 4;
dstMac = (dstMac << 16) | qFromBigEndian<quint16>(pktData + offset);
qDebug("dstMac %012" PRIx64, dstMac);
// XXX: Treat multicast as bcast
if (isMacMcast(dstMac))
dstMac = kBcastMac;
dk.setMac(dstMac);
offset += 2;
// Skip srcMac - don't care
offset += 6;
_eth_type:
// Extract EthType
ethType = qFromBigEndian<quint16>(pktData + offset);
qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
if (tpidList_.contains(ethType)) {
offset += 2;
vlan = qFromBigEndian<quint16>(pktData + offset);
dk.setVlan(idx++, vlan);
offset += 2;
qDebug("%s: idx: %d vlan %d", __FUNCTION__, idx, vlan);
goto _eth_type;
}
pktBuf->pull(offset);
if (dstMac == kBcastMac) {
QList<Device*> list = bcastList_.values(dk.key());
// FIXME: We need to clone the pktBuf before passing to each
// device, otherwise only the first device gets the original
// packet - all subsequent ones get the modified packet!
// NOTE: modification may not be in the pkt data buffer but
// in the HDTE pointers - which is bad as well!
foreach(Device *device, list)
device->receivePacket(pktBuf);
goto _exit;
}
// Is it destined for us?
device = deviceList_.value(dk.key());
if (!device) {
qDebug("%s: dstMac %012llx is not us", __FUNCTION__, dstMac);
goto _exit;
}
device->receivePacket(pktBuf);
_exit:
delete pktBuf;
}
void DeviceManager::transmitPacket(PacketBuffer *pktBuf)
{
port_->sendEmulationPacket(pktBuf);
}
void DeviceManager::resolveDeviceGateways()
{
foreach(Device *device, deviceList_) {
device->resolveGateway();
}
}
void DeviceManager::clearDeviceNeighbors(Device::NeighborSet set)
{
foreach(Device *device, deviceList_)
device->clearNeighbors(set);
}
void DeviceManager::getDeviceNeighbors(
OstProto::PortNeighborList *neighborList)
{
int count = 0;
foreach(Device *device, sortedDeviceList_) {
OstEmul::DeviceNeighborList *neighList =
neighborList->AddExtension(OstEmul::device_neighbor);
neighList->set_device_index(count++);
device->getNeighbors(neighList);
}
}
void DeviceManager::resolveDeviceNeighbor(PacketBuffer *pktBuf)
{
Device *device = originDevice(pktBuf);
if (device)
device->resolveNeighbor(pktBuf);
}
quint64 DeviceManager::deviceMacAddress(PacketBuffer *pktBuf)
{
Device *device = originDevice(pktBuf);
return device ? device->mac() : 0;
}
quint64 DeviceManager::neighborMacAddress(PacketBuffer *pktBuf)
{
Device *device = originDevice(pktBuf);
return device ? device->neighborMac(pktBuf) : 0;
}
// ------------------------------------ //
// Private Methods
// ------------------------------------ //
Device* DeviceManager::originDevice(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->data();
int offset = 12; // start parsing after mac addresses
Device dk(this);
quint16 ethType;
quint16 vlan;
int idx = 0;
// Do we have any devices at all?
if (!deviceCount())
return NULL;
// pktBuf will not have the correct dstMac populated, so use bcastMac
// and search for device by IP
dk.setMac(kBcastMac);
_eth_type:
ethType = qFromBigEndian<quint16>(pktData + offset);
qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
if (tpidList_.contains(ethType)) {
offset += 2;
vlan = qFromBigEndian<quint16>(pktData + offset);
dk.setVlan(idx++, vlan);
offset += 2;
qDebug("%s: idx: %d vlan %d", __FUNCTION__, idx, vlan);
goto _eth_type;
}
pktBuf->pull(offset);
foreach(Device *device, bcastList_.values(dk.key())) {
if (device->isOrigin(pktBuf))
return device;
}
qDebug("couldn't find origin device for packet");
return NULL;
}
void DeviceManager::enumerateDevices(
const OstProto::DeviceGroup *deviceGroup,
Operation oper)
{
Device dk(this);
OstEmul::VlanEmulation pbVlan = deviceGroup->encap()
.GetExtension(OstEmul::vlan);
int numTags = pbVlan.stack_size();
int n = 1;
QList<int> vlanCount;
bool hasIp4 = deviceGroup->HasExtension(OstEmul::ip4);
bool hasIp6 = deviceGroup->HasExtension(OstEmul::ip6);
OstEmul::MacEmulation mac = deviceGroup->GetExtension(OstEmul::mac);
OstEmul::Ip4Emulation ip4 = deviceGroup->GetExtension(OstEmul::ip4);
OstEmul::Ip6Emulation ip6 = deviceGroup->GetExtension(OstEmul::ip6);
/*
* vlanCount[] stores the number of unique vlans at each tag level
* e.g. for a 3-tag config with 2, 3, 4 vlans at each level respectively
* vlanCount = [24, 12, 4]
* 0 - 0, 0, 0
* 1 - 0, 0, 1
* 2 - 0, 0, 2
* 3 - 0, 0, 3
* 4 - 0, 1, 0
* 5 - 0, 1, 1
* 6 - 0, 1, 2
* 7 - 0, 1, 3
* 8 - 0, 2, 0
* 9 - 0, 2, 1
* 10 - 0, 2, 2
* 11 - 0, 2, 3
* 12 - 1, 0, 0
* 13 - 1, 0, 1
* 14 - 1, 0, 2
* 15 - 1, 0, 3
* 16 - 1, 1, 0
* 17 - 1, 1, 1
* 18 - 1, 1, 2
* 19 - 1, 1, 3
* 21 - 1, 2, 0
* 21 - 1, 2, 1
* 22 - 1, 2, 2
* 23 - 1, 2, 3
*
* Note that vlanCount[0] repesents total-number-of-vlans
*
* Another way to think about this is that at a particular vlan tag
* level, we need to repeat a particular vlan-id as many times as the
* next level's count before we can increment the vlan-id at that level
*
* We use this list to calculate the vlan ids for each tag level for
* all the vlans.
*
* For implementation convenience we append a '1' as the last element
*/
vlanCount.append(n);
for (int i = numTags - 1; i >= 0 ; i--) {
OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(i);
n *= vlan.count();
vlanCount.prepend(n);
// Update TPID list
switch (oper) {
case kAdd:
tpidList_[vlan.tpid()]++;
break;
case kDelete:
tpidList_[vlan.tpid()]--;
if (tpidList_[vlan.tpid()] == 0)
tpidList_.remove(vlan.tpid());
break;
default:
Q_ASSERT(0); // Unreachable
}
}
QHash<quint16, uint>::const_iterator iter = tpidList_.constBegin();
qDebug("Port %s TPID List:", port_->name());
while (iter != tpidList_.constEnd()) {
qDebug("tpid: %x (%d)", iter.key(), iter.value());
iter++;
}
for (int i = 0; i < vlanCount.at(0); i++) {
for (int j = 0; j < numTags; j++) {
OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(j);
quint16 vlanAdd = (i/vlanCount.at(j+1) % vlan.count())*vlan.step();
dk.setVlan(j, vlan.vlan_tag() + vlanAdd, vlan.tpid());
}
for (uint k = 0; k < deviceGroup->device_count(); k++) {
Device *device;
quint64 macAdd = k * mac.step();
quint32 ip4Add = k * ip4.step();
UInt128 ip6Add = UINT128(ip6.step()) * k;
dk.setMac(mac.address() + macAdd);
if (hasIp4)
dk.setIp4(ip4.address() + ip4Add,
ip4.prefix_length(),
ip4.default_gateway());
if (hasIp6)
dk.setIp6(UINT128(ip6.address()) + ip6Add,
ip6.prefix_length(),
UINT128(ip6.default_gateway()));
switch (oper) {
case kAdd:
if (deviceList_.contains(dk.key())) {
qWarning("%s: error adding device %s (EEXIST)",
__FUNCTION__, qPrintable(dk.config()));
break;
}
device = new Device(this);
*device = dk;
deviceList_.insert(dk.key(), device);
sortedDeviceList_.insert(dk.key(), device);
dk.setMac(kBcastMac);
bcastList_.insert(dk.key(), device);
qDebug("enumerate(add): %s", qPrintable(device->config()));
break;
case kDelete:
device = deviceList_.take(dk.key());
if (!device) {
qWarning("%s: error deleting device %s (NOTFOUND)",
__FUNCTION__, qPrintable(dk.config()));
break;
}
qDebug("enumerate(del): %s", qPrintable(device->config()));
delete device;
sortedDeviceList_.take(dk.key()); // already freed above
dk.setMac(kBcastMac);
bcastList_.take(dk.key()); // device already freed above
break;
default:
Q_ASSERT(0); // Unreachable
}
} // foreach device
} // foreach vlan
}

82
server/devicemanager.h Normal file
View File

@ -0,0 +1,82 @@
/*
Copyright (C) 2015 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 _DEVICE_MANAGER_H
#define _DEVICE_MANAGER_H
#include "device.h"
#include <QHash>
#include <QMap>
#include <QMultiHash>
#include <QtGlobal>
class AbstractPort;
class PacketBuffer;
namespace OstProto {
class DeviceGroup;
};
class DeviceManager
{
public:
DeviceManager(AbstractPort *parent = 0);
~DeviceManager();
int deviceGroupCount();
const OstProto::DeviceGroup* deviceGroupAtIndex(int index);
const OstProto::DeviceGroup* deviceGroup(uint deviceGroupId);
bool addDeviceGroup(uint deviceGroupId);
bool deleteDeviceGroup(uint deviceGroupId);
bool modifyDeviceGroup(const OstProto::DeviceGroup *deviceGroup);
int deviceCount();
void getDeviceList(OstProto::PortDeviceList *deviceList);
void receivePacket(PacketBuffer *pktBuf);
void transmitPacket(PacketBuffer *pktBuf);
void resolveDeviceGateways();
void clearDeviceNeighbors(Device::NeighborSet set = Device::kAllNeighbors);
void resolveDeviceNeighbor(PacketBuffer *pktBuf);
void getDeviceNeighbors(OstProto::PortNeighborList *neighborList);
quint64 deviceMacAddress(PacketBuffer *pktBuf);
quint64 neighborMacAddress(PacketBuffer *pktBuf);
private:
enum Operation { kAdd, kDelete };
Device* originDevice(PacketBuffer *pktBuf);
void enumerateDevices(
const OstProto::DeviceGroup *deviceGroup,
Operation oper);
AbstractPort *port_;
QHash<uint, OstProto::DeviceGroup*> deviceGroupList_;
QHash<DeviceKey, Device*> deviceList_; // fast access to devices
QMap<DeviceKey, Device*> sortedDeviceList_; // sorted access to devices
QMultiHash<DeviceKey, Device*> bcastList_;
QHash<quint16, uint> tpidList_; // Key: TPID, Value: RefCount
};
#endif

View File

@ -69,3 +69,8 @@ bool Drone::init()
return true; return true;
} }
MyService* Drone::rpcService()
{
return service;
}

View File

@ -32,6 +32,7 @@ public:
Drone(QObject *parent = 0); Drone(QObject *parent = 0);
~Drone(); ~Drone();
bool init(); bool init();
MyService* rpcService();
private: private:
RpcServer *rpcServer; RpcServer *rpcServer;

View File

@ -33,6 +33,8 @@ LIBS += -lprotobuf
HEADERS += drone.h \ HEADERS += drone.h \
myservice.h myservice.h
SOURCES += \ SOURCES += \
devicemanager.cpp \
device.cpp \
drone_main.cpp \ drone_main.cpp \
drone.cpp \ drone.cpp \
portmanager.cpp \ portmanager.cpp \
@ -43,6 +45,7 @@ SOURCES += \
winpcapport.cpp winpcapport.cpp
SOURCES += myservice.cpp SOURCES += myservice.cpp
SOURCES += pcapextra.cpp SOURCES += pcapextra.cpp
SOURCES += packetbuffer.cpp
QMAKE_DISTCLEAN += object_script.* QMAKE_DISTCLEAN += object_script.*

View File

@ -35,6 +35,7 @@ extern ProtocolManager *OstProtocolManager;
extern char *version; extern char *version;
extern char *revision; extern char *revision;
Drone *drone;
QSettings *appSettings; QSettings *appSettings;
int myport; int myport;
@ -47,7 +48,6 @@ int main(int argc, char *argv[])
{ {
int exitCode = 0; int exitCode = 0;
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
Drone *drone;
// TODO: command line options // TODO: command line options
// -v (--version) // -v (--version)

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2010 Srivats P. Copyright (C) 2010-2015 Srivats P.
This file is part of "Ostinato" This file is part of "Ostinato"
@ -20,6 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "myservice.h" #include "myservice.h"
#include "drone.h"
#if 0 #if 0
#include <qglobal.h> #include <qglobal.h>
#include <qendian.h> #include <qendian.h>
@ -31,11 +33,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "../common/streambase.h" #include "../common/streambase.h"
#include "../rpc/pbrpccontroller.h" #include "../rpc/pbrpccontroller.h"
#include "device.h"
#include "devicemanager.h"
#include "portmanager.h" #include "portmanager.h"
#include <QStringList> #include <QStringList>
extern Drone *drone;
extern char *version; extern char *version;
MyService::MyService() MyService::MyService()
@ -239,7 +244,7 @@ void MyService::addStream(::google::protobuf::RpcController* controller,
// Append a new "default" stream - actual contents of the new stream is // Append a new "default" stream - actual contents of the new stream is
// expected in a subsequent "modifyStream" request - set the stream id // expected in a subsequent "modifyStream" request - set the stream id
// now itself however!!! // now itself however!!!
stream = new StreamBase; stream = new StreamBase(portId);
stream->setId(request->stream_id(i).id()); stream->setId(request->stream_id(i).id());
portInfo[portId]->addStream(stream); portInfo[portId]->addStream(stream);
} }
@ -358,6 +363,8 @@ void MyService::startTransmit(::google::protobuf::RpcController* /*controller*/,
continue; //! \todo (LOW): partial RPC? continue; //! \todo (LOW): partial RPC?
portLock[portId]->lockForWrite(); portLock[portId]->lockForWrite();
if (portInfo[portId]->isDirty())
portInfo[portId]->updatePacketList();
portInfo[portId]->startTransmit(); portInfo[portId]->startTransmit();
portLock[portId]->unlock(); portLock[portId]->unlock();
} }
@ -591,3 +598,378 @@ _invalid_version:
controller->SetFailed("invalid version information"); controller->SetFailed("invalid version information");
done->Run(); done->Run();
} }
/*
* ===================================================================
* Device Emulation
* ===================================================================
* XXX: Streams and Devices are largely non-overlapping from a RPC
* point of view but they *do* intersect e.g. when a stream is trying to
* find its associated device and info from that device such as src/dst
* mac addresses. For this reason, both set of RPCs share the common
* port level locking
* ===================================================================
*/
void MyService::getDeviceGroupIdList(
::google::protobuf::RpcController* controller,
const ::OstProto::PortId* request,
::OstProto::DeviceGroupIdList* response,
::google::protobuf::Closure* done)
{
DeviceManager *devMgr;
int portId;
qDebug("In %s", __PRETTY_FUNCTION__);
portId = request->id();
if ((portId < 0) || (portId >= portInfo.size()))
goto _invalid_port;
devMgr = portInfo[portId]->deviceManager();
response->mutable_port_id()->set_id(portId);
portLock[portId]->lockForRead();
for (int i = 0; i < devMgr->deviceGroupCount(); i++)
{
OstProto::DeviceGroupId *dgid;
dgid = response->add_device_group_id();
dgid->CopyFrom(devMgr->deviceGroupAtIndex(i)->device_group_id());
}
portLock[portId]->unlock();
done->Run();
return;
_invalid_port:
controller->SetFailed("Invalid Port Id");
done->Run();
}
void MyService::getDeviceGroupConfig(
::google::protobuf::RpcController* controller,
const ::OstProto::DeviceGroupIdList* request,
::OstProto::DeviceGroupConfigList* response,
::google::protobuf::Closure* done)
{
DeviceManager *devMgr;
int portId;
qDebug("In %s", __PRETTY_FUNCTION__);
portId = request->port_id().id();
if ((portId < 0) || (portId >= portInfo.size()))
goto _invalid_port;
devMgr = portInfo[portId]->deviceManager();
response->mutable_port_id()->set_id(portId);
portLock[portId]->lockForRead();
for (int i = 0; i < request->device_group_id_size(); i++)
{
const OstProto::DeviceGroup *dg;
dg = devMgr->deviceGroup(request->device_group_id(i).id());
if (!dg)
continue; //! \todo(LOW): Partial status of RPC
response->add_device_group()->CopyFrom(*dg);
}
portLock[portId]->unlock();
done->Run();
return;
_invalid_port:
controller->SetFailed("invalid portid");
done->Run();
}
void MyService::addDeviceGroup(
::google::protobuf::RpcController* controller,
const ::OstProto::DeviceGroupIdList* request,
::OstProto::Ack* /*response*/,
::google::protobuf::Closure* done)
{
DeviceManager *devMgr;
int portId;
qDebug("In %s", __PRETTY_FUNCTION__);
portId = request->port_id().id();
if ((portId < 0) || (portId >= portInfo.size()))
goto _invalid_port;
devMgr = portInfo[portId]->deviceManager();
if (portInfo[portId]->isTransmitOn())
goto _port_busy;
portLock[portId]->lockForWrite();
for (int i = 0; i < request->device_group_id_size(); i++)
{
quint32 id = request->device_group_id(i).id();
const OstProto::DeviceGroup *dg = devMgr->deviceGroup(id);
// If device group with same id as in request exists already ==> error!
if (dg)
continue; //! \todo (LOW): Partial status of RPC
devMgr->addDeviceGroup(id);
}
portLock[portId]->unlock();
//! \todo (LOW): fill-in response "Ack"????
done->Run();
return;
_port_busy:
controller->SetFailed("Port Busy");
goto _exit;
_invalid_port:
controller->SetFailed("invalid portid");
_exit:
done->Run();
}
void MyService::deleteDeviceGroup(
::google::protobuf::RpcController* controller,
const ::OstProto::DeviceGroupIdList* request,
::OstProto::Ack* /*response*/,
::google::protobuf::Closure* done)
{
DeviceManager *devMgr;
int portId;
qDebug("In %s", __PRETTY_FUNCTION__);
portId = request->port_id().id();
if ((portId < 0) || (portId >= portInfo.size()))
goto _invalid_port;
devMgr = portInfo[portId]->deviceManager();
if (portInfo[portId]->isTransmitOn())
goto _port_busy;
portLock[portId]->lockForWrite();
for (int i = 0; i < request->device_group_id_size(); i++)
devMgr->deleteDeviceGroup(request->device_group_id(i).id());
portLock[portId]->unlock();
//! \todo (LOW): fill-in response "Ack"????
done->Run();
return;
_port_busy:
controller->SetFailed("Port Busy");
goto _exit;
_invalid_port:
controller->SetFailed("invalid portid");
_exit:
done->Run();
}
void MyService::modifyDeviceGroup(
::google::protobuf::RpcController* controller,
const ::OstProto::DeviceGroupConfigList* request,
::OstProto::Ack* /*response*/,
::google::protobuf::Closure* done)
{
DeviceManager *devMgr;
int portId;
qDebug("In %s", __PRETTY_FUNCTION__);
portId = request->port_id().id();
if ((portId < 0) || (portId >= portInfo.size()))
goto _invalid_port;
devMgr = portInfo[portId]->deviceManager();
if (portInfo[portId]->isTransmitOn())
goto _port_busy;
portLock[portId]->lockForWrite();
for (int i = 0; i < request->device_group_size(); i++)
devMgr->modifyDeviceGroup(&request->device_group(i));
portLock[portId]->unlock();
//! \todo(LOW): fill-in response "Ack"????
done->Run();
return;
_port_busy:
controller->SetFailed("Port Busy");
goto _exit;
_invalid_port:
controller->SetFailed("invalid portid");
_exit:
done->Run();
}
void MyService::getDeviceList(
::google::protobuf::RpcController* controller,
const ::OstProto::PortId* request,
::OstProto::PortDeviceList* response,
::google::protobuf::Closure* done)
{
DeviceManager *devMgr;
int portId;
qDebug("In %s", __PRETTY_FUNCTION__);
portId = request->id();
if ((portId < 0) || (portId >= portInfo.size()))
goto _invalid_port;
devMgr = portInfo[portId]->deviceManager();
response->mutable_port_id()->set_id(portId);
portLock[portId]->lockForRead();
devMgr->getDeviceList(response);
portLock[portId]->unlock();
done->Run();
return;
_invalid_port:
controller->SetFailed("Invalid Port Id");
done->Run();
}
void MyService::resolveDeviceNeighbors(
::google::protobuf::RpcController* controller,
const ::OstProto::PortIdList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done)
{
qDebug("In %s", __PRETTY_FUNCTION__);
for (int i = 0; i < request->port_id_size(); i++)
{
int portId;
portId = request->port_id(i).id();
if ((portId < 0) || (portId >= portInfo.size()))
continue; //! \todo (LOW): partial RPC?
portLock[portId]->lockForWrite();
portInfo[portId]->resolveDeviceNeighbors();
portLock[portId]->unlock();
}
//! \todo (LOW): fill-in response "Ack"????
done->Run();
}
void MyService::clearDeviceNeighbors(
::google::protobuf::RpcController* controller,
const ::OstProto::PortIdList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done)
{
qDebug("In %s", __PRETTY_FUNCTION__);
for (int i = 0; i < request->port_id_size(); i++)
{
int portId;
portId = request->port_id(i).id();
if ((portId < 0) || (portId >= portInfo.size()))
continue; //! \todo (LOW): partial RPC?
portLock[portId]->lockForWrite();
portInfo[portId]->clearDeviceNeighbors();
portLock[portId]->unlock();
}
//! \todo (LOW): fill-in response "Ack"????
done->Run();
}
void MyService::getDeviceNeighbors(
::google::protobuf::RpcController* controller,
const ::OstProto::PortId* request,
::OstProto::PortNeighborList* response,
::google::protobuf::Closure* done)
{
DeviceManager *devMgr;
int portId;
qDebug("In %s", __PRETTY_FUNCTION__);
portId = request->id();
if ((portId < 0) || (portId >= portInfo.size()))
goto _invalid_port;
devMgr = portInfo[portId]->deviceManager();
response->mutable_port_id()->set_id(portId);
portLock[portId]->lockForRead();
devMgr->getDeviceNeighbors(response);
portLock[portId]->unlock();
done->Run();
return;
_invalid_port:
controller->SetFailed("Invalid Port Id");
done->Run();
}
/*
* ===================================================================
* Friends
* TODO: Encap these global functions into a DeviceBroker singleton?
* ===================================================================
*/
quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex)
{
MyService *service = drone->rpcService();
DeviceManager *devMgr = NULL;
quint64 mac;
if (!service)
return 0;
if ((portId >= 0) && (portId < service->portInfo.size()))
devMgr = service->portInfo[portId]->deviceManager();
if (!devMgr || !devMgr->deviceCount())
return 0;
service->portLock[portId]->lockForWrite();
mac = service->portInfo[portId]->deviceMacAddress(streamId, frameIndex);
service->portLock[portId]->unlock();
return mac;
}
quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex)
{
MyService *service = drone->rpcService();
DeviceManager *devMgr = NULL;
quint64 mac;
if (!service)
return 0;
if ((portId >= 0) && (portId < service->portInfo.size()))
devMgr = service->portInfo[portId]->deviceManager();
if (!devMgr || !devMgr->deviceCount())
return 0;
service->portLock[portId]->lockForWrite();
mac = service->portInfo[portId]->neighborMacAddress(streamId, frameIndex);
service->portLock[portId]->unlock();
return mac;
}

View File

@ -105,6 +105,59 @@ public:
::OstProto::VersionCompatibility* response, ::OstProto::VersionCompatibility* response,
::google::protobuf::Closure* done); ::google::protobuf::Closure* done);
// DeviceGroup and Protocol Emulation
virtual void getDeviceGroupIdList(
::google::protobuf::RpcController* controller,
const ::OstProto::PortId* request,
::OstProto::DeviceGroupIdList* response,
::google::protobuf::Closure* done);
virtual void getDeviceGroupConfig(
::google::protobuf::RpcController* controller,
const ::OstProto::DeviceGroupIdList* request,
::OstProto::DeviceGroupConfigList* response,
::google::protobuf::Closure* done);
virtual void addDeviceGroup(
::google::protobuf::RpcController* controller,
const ::OstProto::DeviceGroupIdList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done);
virtual void deleteDeviceGroup(
::google::protobuf::RpcController* controller,
const ::OstProto::DeviceGroupIdList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done);
virtual void modifyDeviceGroup(
::google::protobuf::RpcController* controller,
const ::OstProto::DeviceGroupConfigList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done);
virtual void getDeviceList(
::google::protobuf::RpcController* controller,
const ::OstProto::PortId* request,
::OstProto::PortDeviceList* response,
::google::protobuf::Closure* done);
virtual void resolveDeviceNeighbors(
::google::protobuf::RpcController* controller,
const ::OstProto::PortIdList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done);
virtual void clearDeviceNeighbors(
::google::protobuf::RpcController* controller,
const ::OstProto::PortIdList* request,
::OstProto::Ack* response,
::google::protobuf::Closure* done);
virtual void getDeviceNeighbors(
::google::protobuf::RpcController* controller,
const ::OstProto::PortId* request,
::OstProto::PortNeighborList* response,
::google::protobuf::Closure* done);
friend quint64 getDeviceMacAddress(
int portId, int streamId, int frameIndex);
friend quint64 getNeighborMacAddress(
int portId, int streamId, int frameIndex);
signals: signals:
void notification(int notifType, SharedProtobufMessage notifData); void notification(int notifType, SharedProtobufMessage notifData);

110
server/packetbuffer.cpp Normal file
View File

@ -0,0 +1,110 @@
/*
Copyright (C) 2015 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 "packetbuffer.h"
// PacketBuffer with full control
PacketBuffer::PacketBuffer(int size)
{
if (size == 0)
size = 1600;
buffer_ = new uchar[size];
is_own_buffer_ = true;
head_ = data_ = tail_ = buffer_;
end_ = head_ + size;
}
// PacketBuffer wrapping already existing const buffer
PacketBuffer::PacketBuffer(const uchar *buffer, int size)
{
// FIXME: ugly const_cast hack!!
buffer_ = const_cast<uchar*>(buffer);
is_own_buffer_ = false;
head_ = data_ = buffer_;
tail_ = end_ = buffer_ + size;
}
PacketBuffer::~PacketBuffer()
{
if (is_own_buffer_)
delete[] buffer_;
}
int PacketBuffer::length() const
{
return tail_ - data_;
}
uchar* PacketBuffer::head() const
{
return head_;
}
uchar* PacketBuffer::data() const
{
return data_;
}
uchar* PacketBuffer::tail() const
{
return tail_;
}
uchar* PacketBuffer::end() const
{
return end_;
}
void PacketBuffer::reserve(int len)
{
data_ += len;
tail_ += len;
}
uchar* PacketBuffer::pull(int len)
{
if ((tail_ - data_) < len)
return NULL;
data_ += len;
return data_;
}
uchar* PacketBuffer::push(int len)
{
if ((data_ - head_) < len)
return NULL;
data_ -= len;
return data_;
}
uchar* PacketBuffer::put(int len)
{
uchar *oldTail = tail_;
if ((end_ - tail_) < len)
return NULL;
tail_ += len;
return oldTail;
}

51
server/packetbuffer.h Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright (C) 2015 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_BUFFER_H
#define _PACKET_BUFFER_H
#include <QtGlobal>
class PacketBuffer
{
public:
PacketBuffer(int size = 0);
PacketBuffer(const uchar *buffer, int size);
~PacketBuffer();
int length() const;
uchar* head() const;
uchar* data() const;
uchar* tail() const;
uchar* end() const;
void reserve(int len);
uchar* pull(int len);
uchar* push(int len);
uchar* put(int len);
private:
uchar *buffer_;
bool is_own_buffer_;
uchar *head_, *data_, *tail_, *end_;
};
#endif

View File

@ -19,6 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "pcapport.h" #include "pcapport.h"
#include "devicemanager.h"
#include "packetbuffer.h"
#include <QtGlobal> #include <QtGlobal>
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
@ -81,6 +84,7 @@ PcapPort::PcapPort(int id, const char *device)
monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_); monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_);
transmitter_ = new PortTransmitter(device); transmitter_ = new PortTransmitter(device);
capturer_ = new PortCapturer(device); capturer_ = new PortCapturer(device);
emulXcvr_ = new EmulationTransceiver(device, deviceManager_);
if (!monitorRx_->handle() || !monitorTx_->handle()) if (!monitorRx_->handle() || !monitorTx_->handle())
isUsable_ = false; isUsable_ = false;
@ -97,12 +101,8 @@ PcapPort::PcapPort(int id, const char *device)
{ {
if (strcmp(device, dev->name) == 0) if (strcmp(device, dev->name) == 0)
{ {
#ifdef Q_OS_WIN32
data_.set_name(QString("if%1").arg(id).toStdString());
#else
if (dev->name) if (dev->name)
data_.set_name(dev->name); data_.set_name(dev->name);
#endif
if (dev->description) if (dev->description)
data_.set_description(dev->description); data_.set_description(dev->description);
@ -133,6 +133,7 @@ PcapPort::~PcapPort()
if (monitorTx_) if (monitorTx_)
monitorTx_->stop(); monitorTx_->stop();
delete emulXcvr_;
delete capturer_; delete capturer_;
delete transmitter_; delete transmitter_;
@ -176,6 +177,26 @@ bool PcapPort::setRateAccuracy(AbstractPort::Accuracy accuracy)
return false; return false;
} }
void PcapPort::startDeviceEmulation()
{
emulXcvr_->start();
}
void PcapPort::stopDeviceEmulation()
{
emulXcvr_->stop();
}
int PcapPort::sendEmulationPacket(PacketBuffer *pktBuf)
{
return emulXcvr_->transmitPacket(pktBuf);
}
/*
* ------------------------------------------------------------------- *
* Port Monitor
* ------------------------------------------------------------------- *
*/
PcapPort::PortMonitor::PortMonitor(const char *device, Direction direction, PcapPort::PortMonitor::PortMonitor(const char *device, Direction direction,
AbstractPort::PortStats *stats) AbstractPort::PortStats *stats)
{ {
@ -321,6 +342,11 @@ void PcapPort::PortMonitor::stop()
pcap_breakloop(handle()); pcap_breakloop(handle());
} }
/*
* ------------------------------------------------------------------- *
* Port Transmitter
* ------------------------------------------------------------------- *
*/
PcapPort::PortTransmitter::PortTransmitter(const char *device) PcapPort::PortTransmitter::PortTransmitter(const char *device)
{ {
char errbuf[PCAP_ERRBUF_SIZE] = ""; char errbuf[PCAP_ERRBUF_SIZE] = "";
@ -750,6 +776,11 @@ void PcapPort::PortTransmitter::udelay(unsigned long usec)
#endif #endif
} }
/*
* ------------------------------------------------------------------- *
* Port Capturer
* ------------------------------------------------------------------- *
*/
PcapPort::PortCapturer::PortCapturer(const char *device) PcapPort::PortCapturer::PortCapturer(const char *device)
{ {
device_ = QString::fromAscii(device); device_ = QString::fromAscii(device);
@ -884,3 +915,218 @@ QFile* PcapPort::PortCapturer::captureFile()
{ {
return &capFile_; return &capFile_;
} }
/*
* ------------------------------------------------------------------- *
* Transmit+Receiver for Device/ProtocolEmulation
* ------------------------------------------------------------------- *
*/
PcapPort::EmulationTransceiver::EmulationTransceiver(const char *device,
DeviceManager *deviceManager)
{
device_ = QString::fromAscii(device);
deviceManager_ = deviceManager;
stop_ = false;
state_ = kNotStarted;
handle_ = NULL;
}
PcapPort::EmulationTransceiver::~EmulationTransceiver()
{
stop();
}
void PcapPort::EmulationTransceiver::run()
{
int flags = PCAP_OPENFLAG_PROMISCUOUS;
char errbuf[PCAP_ERRBUF_SIZE] = "";
struct bpf_program bpf;
#if 0
const char *capture_filter =
"arp or icmp or icmp6 or "
"(vlan and (arp or icmp or icmp6)) or "
"(vlan and vlan and (arp or icmp or icmp6)) or "
"(vlan and vlan and vlan and (arp or icmp or icmp6)) or "
"(vlan and vlan and vlan and vlan and (arp or icmp or icmp6))";
/*
Ideally we should use the above filter, but the 'vlan' capture filter
in libpcap is implemented as a kludge. From the pcap-filter man page -
vlan [vlan_id]
Note that the first vlan keyword encountered in expression changes
the decoding offsets for the remainder of expression on the
assumption that the packet is a VLAN packet.
The vlan [vlan_id] expression may be used more than once, to filter on
VLAN hierarchies. Each use of that expression increments the filter
offsets by 4.
See https://ask.wireshark.org/questions/31953/unusual-behavior-with-stacked-vlan-tags-and-capture-filter
So we use the modified filter expression that works as we intend. If ever
libpcap changes their implementation, this will need to change as well.
*/
#else
const char *capture_filter =
"arp or icmp or icmp6 or "
"(vlan and (arp or icmp or icmp6)) or "
"(vlan and (arp or icmp or icmp6)) or "
"(vlan and (arp or icmp or icmp6)) or "
"(vlan and (arp or icmp or icmp6))";
#endif
const int optimize = 1;
qDebug("In %s", __PRETTY_FUNCTION__);
#ifdef Q_OS_WIN32
flags |= PCAP_OPENFLAG_NOCAPTURE_LOCAL;
#endif
_retry:
#ifdef Q_OS_WIN32
// NOCAPTURE_LOCAL needs windows only pcap_open()
handle_ = pcap_open(qPrintable(device_), 65535,
flags, 100 /* ms */, NULL, errbuf);
#else
handle_ = pcap_open_live(qPrintable(device_), 65535,
flags, 100 /* ms */, errbuf);
#endif
if (handle_ == NULL)
{
if (flags && QString(errbuf).contains("promiscuous"))
{
notify("Unable to set promiscuous mode on <%s> - "
"device emulation will not work", qPrintable(device_));
goto _exit;
}
#ifdef Q_OS_WIN32
else if ((flags & PCAP_OPENFLAG_NOCAPTURE_LOCAL)
&& QString(errbuf).contains("loopback"))
{
qDebug("Can't set no local capture mode %s", qPrintable(device_));
flags &= ~PCAP_OPENFLAG_NOCAPTURE_LOCAL;
goto _retry;
}
#endif
else
{
notify("Unable to open <%s> [%s] - device emulation will not work",
qPrintable(device_), errbuf);
goto _exit;
}
}
// TODO: for now the filter is hardcoded to accept tagged/untagged
// ARP/NDP or ICMPv4/v6; when more protocols are added, we may need
// to derive this filter based on which protocols are configured
// on the devices
if (pcap_compile(handle_, &bpf, capture_filter, optimize, 0) < 0)
{
qWarning("%s: error compiling filter: %s", qPrintable(device_),
pcap_geterr(handle_));
goto _skip_filter;
}
if (pcap_setfilter(handle_, &bpf) < 0)
{
qWarning("%s: error setting filter: %s", qPrintable(device_),
pcap_geterr(handle_));
goto _skip_filter;
}
_skip_filter:
state_ = kRunning;
while (1)
{
int ret;
struct pcap_pkthdr *hdr;
const uchar *data;
ret = pcap_next_ex(handle_, &hdr, &data);
switch (ret)
{
case 1:
{
PacketBuffer *pktBuf = new PacketBuffer(data, hdr->caplen);
#if 0
for (int i = 0; i < 64; i++) {
printf("%02x ", data[i]);
if (i % 16 == 0)
printf("\n");
}
printf("\n");
#endif
// XXX: deviceManager should free pktBuf before returning
// from this call; if it needs to process the pkt async
// it should make a copy as the pktBuf's data buffer is
// owned by libpcap which does not guarantee data will
// persist across calls to pcap_next_ex()
deviceManager_->receivePacket(pktBuf);
break;
}
case 0:
// timeout: just go back to the loop
break;
case -1:
qWarning("%s: error reading packet (%d): %s",
__PRETTY_FUNCTION__, ret, pcap_geterr(handle_));
break;
case -2:
default:
qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
ret);
}
if (stop_)
{
qDebug("user requested receiver stop\n");
break;
}
}
pcap_close(handle_);
handle_ = NULL;
stop_ = false;
_exit:
state_ = kFinished;
}
void PcapPort::EmulationTransceiver::start()
{
if (state_ == kRunning) {
qWarning("Receive start requested but is already running!");
return;
}
state_ = kNotStarted;
QThread::start();
while (state_ == kNotStarted)
QThread::msleep(10);
}
void PcapPort::EmulationTransceiver::stop()
{
if (state_ == kRunning) {
stop_ = true;
while (state_ == kRunning)
QThread::msleep(10);
}
else {
qWarning("Receive stop requested but is not running!");
return;
}
}
bool PcapPort::EmulationTransceiver::isRunning()
{
return (state_ == kRunning);
}
int PcapPort::EmulationTransceiver::transmitPacket(PacketBuffer *pktBuf)
{
return pcap_sendpacket(handle_, pktBuf->data(), pktBuf->length());
}

View File

@ -70,6 +70,10 @@ public:
virtual bool isCaptureOn() { return capturer_->isRunning(); } virtual bool isCaptureOn() { return capturer_->isRunning(); }
virtual QIODevice* captureData() { return capturer_->captureFile(); } virtual QIODevice* captureData() { return capturer_->captureFile(); }
virtual void startDeviceEmulation();
virtual void stopDeviceEmulation();
virtual int sendEmulationPacket(PacketBuffer *pktBuf);
protected: protected:
enum Direction enum Direction
{ {
@ -227,6 +231,32 @@ protected:
volatile State state_; volatile State state_;
}; };
class EmulationTransceiver: public QThread
{
public:
EmulationTransceiver(const char *device, DeviceManager *deviceManager);
~EmulationTransceiver();
void run();
void start();
void stop();
bool isRunning();
int transmitPacket(PacketBuffer *pktBuf);
private:
enum State
{
kNotStarted,
kRunning,
kFinished
};
QString device_;
DeviceManager *deviceManager_;
volatile bool stop_;
pcap_t *handle_;
volatile State state_;
};
PortMonitor *monitorRx_; PortMonitor *monitorRx_;
PortMonitor *monitorTx_; PortMonitor *monitorTx_;
@ -235,6 +265,7 @@ protected:
private: private:
PortTransmitter *transmitter_; PortTransmitter *transmitter_;
PortCapturer *capturer_; PortCapturer *capturer_;
EmulationTransceiver *emulXcvr_;
static pcap_if_t *deviceList_; static pcap_if_t *deviceList_;
}; };

46
test/TODO.md Normal file
View File

@ -0,0 +1,46 @@
# TODO - Test Cases
## Session Save/Open
* Verify save session prompts before opening Save Dialog about only reserved ports being saved if there are some reserved ports and not prompted at all if no ports are reserved
* Verify each save session triggers the file dialog at the last path used
* Verify saved session file is correct
* All portgroups are saved
* All suitable (wrt reservation) ports are saved
* All port configuration is saved
* For each port -
* All streams are saved with correct contents
* All deviceGroups are saved with correct contents
* OSSN Session file format tests (TODO: expand)
* Verify no file is saved if user clicks 'Cancel' on the progress dialog while saving session file
* On open session, verify user is prompted before opening the file dialog if there are existing portgroups and not prompted at all if there are no port groups
* Verify each open session triggers the file dialog at the last path used
* Verify open file dialog file filter has `(*.ssn *.*)`
* Verify opening a unsupported format file triggers an error and existing session is not changed
* Verify all existing portgroups are removed before new ones from the file are created and configured
* Verify that only ports in the opened session file are overwritten and other ports are not changed
* Verify that if port in the opened session file was reserved at save time, and the same port is now reserved by someone else, it is not changed and user is informed; if current port reservation is by self, port is overwritten with contents from the session file; all reservations made by open session are with self username, not the username who had reserved the port during save time (in other words, allow session files to be exchanged between users)
* Verify no unnecessary RPCs during open session
* if port has no existing streams, deleteStreams() is not invoked
* if port has no existing deviceGroups, deleteDeviceGroups() is not invoked
* if port config has no change, modifyPort() is not invoked
* if opened port has no streams, addStreams()/modifyStreams() is not invoked
* if opened port has no deviceGroups, addDeviceGroups()/modifyDeviceGroups() is not invoked
* Verify open session is successful
* All streams are restored with correct contents
* All deviceGroups are restored with correct contents
* Port config (TxMode, ExclusiveMode, UserName) is changed, if required
* OSSN Session file format tests (TODO: expand)
* Verify no change in existing port groups if user clicks 'Cancel' on the options dialog
* Verify no change in existing port groups if user clicks 'Cancel' on the progress dialog while opening session file
* Verify all old portgroups are removed before new portgroups from the session file are added
* Verify config of portGroups loaded from the session file are correct
* Verify config of a portGroup is NOT restored to the config saved in session file after a open session - disconnect - connect
## Streams Save/Open
* Verify save file dialog file filter has all supported types but no `*.*`
* Stream file format tests (TODO: expand)
* Verify open file dialog file filter has all supported types except `*.py` and `*.*`
* Verify opening a unsupported format file triggers an error and existing streams are not changed
* Stream file format tests (TODO: expand)

1265
test/emultest.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,12 @@ class TestSuite:
def passed(self): def passed(self):
return passed == total and self.completed return passed == total and self.completed
class TestPreRequisiteError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
def extract_column(text, col): def extract_column(text, col):
"""Given a text table, return items in the specified column as a list""" """Given a text table, return items in the specified column as a list"""
lines = text.splitlines() lines = text.splitlines()

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