Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Sep 1, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cpp/lib/include/opendnp3/outstation/IOutstationApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ class IOutstationApplication : public ILinkListener, public IDnpTimeSource
return 65535;
}

/// This method notifies that application code that an expected CONFIRM has been
/// received, and events may have cleared from the event buffer. It is informational
/// only.
///
/// @param is_unsolicited true, if the confirm is for an unsolicited response, false for a solicited response
/// @param num_class1 number of Class 1 events remaining in the event buffer after processing the confirm
/// @param num_class2 number of Class 2 events remaining in the event buffer after processing the confirm
/// @param num_class3 number of Class 3 events remaining in the event buffer after processing the confirm
virtual void OnConfirmProcessed(bool is_unsolicited, uint32_t num_class1, uint32_t num_class2, uint32_t num_class3) {}

virtual ~IOutstationApplication() = default;
};

Expand Down
16 changes: 16 additions & 0 deletions cpp/lib/src/outstation/OutstationStates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ OutstationState& StateSolicitedConfirmWait::OnConfirm(OContext& ctx, const Parse
ctx.eventBuffer.ClearWritten();
ctx.lastBroadcastMessageReceived.clear();

// information the application about the confirm
ctx.application->OnConfirmProcessed(
false,
ctx.eventBuffer.NumEvents(EventClass::EC1),
ctx.eventBuffer.NumEvents(EventClass::EC2),
ctx.eventBuffer.NumEvents(EventClass::EC3)
);

if (ctx.rspContext.HasSelection())
{
return ctx.ContinueMultiFragResponse(request.addresses, AppSeqNum(request.header.control.SEQ).Next());
Expand Down Expand Up @@ -170,6 +178,14 @@ OutstationState& StateUnsolicitedConfirmWait::OnConfirm(OContext& ctx, const Par
ctx.confirmTimer.cancel();
ctx.lastBroadcastMessageReceived.clear();

// information the application about the confirm
ctx.application->OnConfirmProcessed(
true,
ctx.eventBuffer.NumEvents(EventClass::EC1),
ctx.eventBuffer.NumEvents(EventClass::EC2),
ctx.eventBuffer.NumEvents(EventClass::EC3)
);

if (ctx.unsol.completedNull)
{
ctx.eventBuffer.ClearWritten();
Expand Down
5 changes: 5 additions & 0 deletions cpp/lib/src/outstation/event/EventBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,9 @@ void EventBuffer::ClearWritten()
this->storage.ClearWritten();
}

uint32_t EventBuffer::NumEvents(EventClass ec) const
{
return this->storage.NumUnwritten(ec);
}

} // namespace opendnp3
3 changes: 3 additions & 0 deletions cpp/lib/src/outstation/event/EventBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class EventBuffer final : public IEventReceiver, public IEventSelector, public I

void SelectAllByClass(const ClassField& clazz);

uint32_t NumEvents(EventClass ec) const;


private:
bool overflow = false;
EventStorage storage;
Expand Down
19 changes: 19 additions & 0 deletions cpp/tests/dnp3mocks/include/dnp3mocks/MockOutstationApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
#include <deque>
#include <tuple>

struct ConfirmResult
{
bool is_unsolicited;
uint32_t num_class1;
uint32_t num_class2;
uint32_t num_class3;
};

class MockOutstationApplication : public opendnp3::IOutstationApplication
{
public:
Expand Down Expand Up @@ -119,6 +127,16 @@ class MockOutstationApplication : public opendnp3::IOutstationApplication
return warmRestartTimeDelay;
}

void OnConfirmProcessed(bool is_unsolicited, uint32_t num_class1, uint32_t num_class2, uint32_t num_class3) final
{
ConfirmResult confirm{};
confirm.is_unsolicited = is_unsolicited;
confirm.num_class1 = num_class1;
confirm.num_class2 = num_class2;
confirm.num_class3 = num_class3;
this->confirms.push_back(confirm);
}

void SetTime(opendnp3::DNPTime time)
{
this->currentTime = time;
Expand All @@ -143,6 +161,7 @@ class MockOutstationApplication : public opendnp3::IOutstationApplication
std::deque<opendnp3::UTCTimestamp> timestamps;
std::deque<std::tuple<opendnp3::AssignClassType, opendnp3::PointClass, uint16_t, uint16_t>> classAssignments;
std::deque<opendnp3::Indexed<opendnp3::TimeAndInterval>> timeAndIntervals;
std::deque<ConfirmResult> confirms;
};

#endif
7 changes: 7 additions & 0 deletions cpp/tests/unit/TestOutstationEventResponses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ TEST_CASE(SUITE("EventBufferOverflowAndClear"))
t.OnTxReady();
t.SendToOutstation(hex::SolicitedConfirm(1));

// Check that the confirm is reported to the IOutstationApplication
REQUIRE(t.application->confirms.size() == 1);
REQUIRE(t.application->confirms[0].is_unsolicited == false);
REQUIRE(t.application->confirms[0].num_class1 == 1);
REQUIRE(t.application->confirms[0].num_class2 == 0);
REQUIRE(t.application->confirms[0].num_class3 == 0);

t.SendToOutstation("C0 01");
REQUIRE("C0 81 82 00" == t.lower->PopWriteAsHex());
}
Expand Down
35 changes: 35 additions & 0 deletions cpp/tests/unit/TestOutstationUnsolicitedResponses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,13 @@ TEST_CASE(SUITE("UnsolData"))
t.OnTxReady();
t.SendToOutstation(hex::UnsolConfirm(0));

// Check that the confirm is reported to the IOutstationApplication
REQUIRE(t.application->confirms.size() == 1);
REQUIRE(t.application->confirms[0].is_unsolicited == true);
REQUIRE(t.application->confirms[0].num_class1 == 0);
REQUIRE(t.application->confirms[0].num_class2 == 0);
REQUIRE(t.application->confirms[0].num_class3 == 0);

// do a transaction before the layer comes online to prove that the null transaction
// is occuring before unsol data is sent
t.Transaction([](IUpdateHandler& db) { db.Update(Binary(false, Flags(0x01)), 2); });
Expand All @@ -287,6 +294,13 @@ TEST_CASE(SUITE("UnsolData"))
t.OnTxReady();
t.SendToOutstation(hex::UnsolConfirm(1));
REQUIRE(t.lower->PopWriteAsHex().empty());

// Check that the confirm is reported to the IOutstationApplication
REQUIRE(t.application->confirms.size() == 2);
REQUIRE(t.application->confirms[1].is_unsolicited == true);
REQUIRE(t.application->confirms[1].num_class1 == 0);
REQUIRE(t.application->confirms[1].num_class2 == 0);
REQUIRE(t.application->confirms[1].num_class3 == 0);
}

TEST_CASE(SUITE("UnsolEventBufferOverflow"))
Expand Down Expand Up @@ -316,6 +330,13 @@ TEST_CASE(SUITE("UnsolEventBufferOverflow"))
t.OnTxReady();
t.SendToOutstation(hex::UnsolConfirm(1));

// Check that the confirm is reported to the IOutstationApplication
REQUIRE(t.application->confirms.size() == 2);
REQUIRE(t.application->confirms[1].is_unsolicited == true);
REQUIRE(t.application->confirms[1].num_class1 == 0);
REQUIRE(t.application->confirms[1].num_class2 == 0);
REQUIRE(t.application->confirms[1].num_class3 == 0);

REQUIRE(t.lower->PopWriteAsHex().empty());
}

Expand Down Expand Up @@ -345,12 +366,26 @@ TEST_CASE(SUITE("UnsolMultiFragments"))
t.OnTxReady();
t.SendToOutstation(hex::UnsolConfirm(1));

// Check that the confirm is reported to the IOutstationApplication
REQUIRE(t.application->confirms.size() == 2);
REQUIRE(t.application->confirms[1].is_unsolicited == true);
REQUIRE(t.application->confirms[1].num_class1 == 1);
REQUIRE(t.application->confirms[1].num_class2 == 0);
REQUIRE(t.application->confirms[1].num_class3 == 0);

// should immediately try to send another unsol packet
REQUIRE(t.lower->PopWriteAsHex() == "F2 82 80 00 20 01 28 01 00 03 00 01 0D 00 00 00");
t.OnTxReady();
t.SendToOutstation(hex::UnsolConfirm(2));

REQUIRE(t.lower->PopWriteAsHex().empty());

// Check that the confirm is reported to the IOutstationApplication
REQUIRE(t.application->confirms.size() == 3);
REQUIRE(t.application->confirms[2].is_unsolicited == true);
REQUIRE(t.application->confirms[2].num_class1 == 0);
REQUIRE(t.application->confirms[2].num_class2 == 0);
REQUIRE(t.application->confirms[2].num_class3 == 0);
}

void WriteDuringUnsol(bool beforeTx)
Expand Down
4 changes: 4 additions & 0 deletions dotnet/CLRAdapter/src/OutstationApplicationAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ namespace Automatak
return proxy->WarmRestart();
}

void OutstationApplicationAdapter::OnConfirmProcessed(bool is_unsolicited, uint32_t num_class1, uint32_t num_class2, uint32_t num_class3)
{
proxy->OnConfirmProcessed(is_unsolicited, num_class1, num_class2, num_class3);
}
}
}
}
12 changes: 7 additions & 5 deletions dotnet/CLRAdapter/src/OutstationApplicationAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ namespace Automatak
public:
OutstationApplicationAdapter(Automatak::DNP3::Interface::IOutstationApplication^ proxy);

virtual void OnStateChange(opendnp3::LinkStatus value) override;
virtual void OnStateChange(opendnp3::LinkStatus value) override final;

virtual void OnUnknownDestinationAddress(uint16_t destination) override;
virtual void OnUnknownDestinationAddress(uint16_t destination) override final;

virtual void OnUnknownSourceAddress(uint16_t source) override;
virtual void OnUnknownSourceAddress(uint16_t source) override final;

virtual void OnKeepAliveInitiated() override final;

Expand All @@ -70,9 +70,11 @@ namespace Automatak

virtual opendnp3::RestartMode WarmRestartSupport() const override final;

virtual uint16_t ColdRestart();
virtual uint16_t ColdRestart() override final;

virtual uint16_t WarmRestart();
virtual uint16_t WarmRestart() override final;

virtual void OnConfirmProcessed(bool is_unsolicited, uint32_t num_class1, uint32_t num_class2, uint32_t num_class3) override final;

private:

Expand Down
12 changes: 12 additions & 0 deletions dotnet/CLRInterface/src/IOutstationApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ ApplicationIIN ApplicationIndications
/// @return number of seconds or milliseconds until restart is complete. The value
/// is interpreted based on the Restart Mode returned from WarmRestartSupport()
UInt16 WarmRestart();

/// This method notifies that application code that an expected CONFIRM has been
/// received, and events may have cleared from the event buffer. It is informational
/// only.
///
/// <param name="is_unsolicited">true, if the confirm is for an unsolicited response, false for a solicited response</param>
/// <param name="num_class1">number of Class 1 events remaining in the event buffer after processing the confirm</param>
/// <param name="num_class2">number of Class 1 events remaining in the event buffer after processing the confirm</param>
/// <param name="num_class3">number of Class 3 events remaining in the event buffer after processing the confirm</param>
void OnConfirmProcessed(bool is_unsolicited, uint num_class1, uint num_class2, uint num_class3);
}

public class DefaultOutstationApplication : IOutstationApplication
Expand Down Expand Up @@ -177,6 +187,8 @@ ushort IOutstationApplication.WarmRestart()
return UInt16.MaxValue;
}

void IOutstationApplication.OnConfirmProcessed(bool is_unsolicited, uint num_class1, uint num_class2, uint num_class3) {}

DNPTime IDnpTimeSource.Now()
{
return DNPTime.Unset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ public interface OutstationApplication extends LinkStatusListener {
*/
int warmRestart();

/**
* This method notifies that application code that an expected CONFIRM has been
* received, and events may have cleared from the event buffer. It is informational
* only.
*
* @param isUnsolicited true if the confirm is for an unsolicited response, false for a solicited response
* @param numClass1 number of Class 1 events remaining in the event buffer after processing the confirm
* @param numClass2 number of Class 2 events remaining in the event buffer after processing the confirm
* @param numClass3 number of Class 3 events remaining in the event buffer after processing the confirm
*/
void onConfirmProcessed(boolean isUnsolicited, long numClass1, long numClass2, long numClass3);

/**
* Return the current time and the synchronization status.
* This value is used when freezing counters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ public int warmRestart() {
return 65535;
}

@Override
public void onConfirmProcessed(boolean isUnsolicited, long numClass1, long numClass2, long numClass3) {
// do nothing in the default implementation
}

@Override
public DNPTime now()
{
Expand Down
6 changes: 6 additions & 0 deletions java/cpp/adapters/OutstationApplicationAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ uint16_t OutstationApplicationAdapter::WarmRestart()
return static_cast<uint16_t>(JCache::OutstationApplication.warmRestart(env, proxy));
}

void OutstationApplicationAdapter::OnConfirmProcessed(bool is_unsolicited, uint32_t num_class1, uint32_t num_class2, uint32_t num_class3)
{
const auto env = JNI::GetEnv();
JCache::OutstationApplication.onConfirmProcessed(env, proxy, is_unsolicited, num_class1, num_class2, num_class3);
}

DNPTime OutstationApplicationAdapter::Now()
{
const auto env = JNI::GetEnv();
Expand Down
2 changes: 2 additions & 0 deletions java/cpp/adapters/OutstationApplicationAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class OutstationApplicationAdapter : public opendnp3::IOutstationApplication

uint16_t WarmRestart() override;

void OnConfirmProcessed(bool is_unsolicited, uint32_t num_class1, uint32_t num_class2, uint32_t num_class3) override;

opendnp3::DNPTime Now() override;

private:
Expand Down
20 changes: 10 additions & 10 deletions java/cpp/jni/JNICommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,19 @@ namespace jni
this->method1 = env->GetMethodID(this->clazz, "end", "()V");
if(!this->method1) return false;

this->method2 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/AnalogOutputDouble64;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
this->method2 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/AnalogOutputInt16;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
if(!this->method2) return false;

this->method3 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/AnalogOutputFloat32;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
this->method3 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/AnalogOutputInt32;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
if(!this->method3) return false;

this->method4 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/AnalogOutputInt16;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
this->method4 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/AnalogOutputFloat32;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
if(!this->method4) return false;

this->method5 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/AnalogOutputInt32;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
this->method5 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/ControlRelayOutputBlock;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
if(!this->method5) return false;

this->method6 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/ControlRelayOutputBlock;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
this->method6 = env->GetMethodID(this->clazz, "operate", "(Lcom/automatak/dnp3/AnalogOutputDouble64;ILcom/automatak/dnp3/Database;Lcom/automatak/dnp3/enums/OperateType;)Lcom/automatak/dnp3/enums/CommandStatus;");
if(!this->method6) return false;

this->method7 = env->GetMethodID(this->clazz, "select", "(Lcom/automatak/dnp3/AnalogOutputInt32;I)Lcom/automatak/dnp3/enums/CommandStatus;");
Expand Down Expand Up @@ -96,27 +96,27 @@ namespace jni
env->CallVoidMethod(instance, this->method1);
}

LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputDouble64 arg0, jint arg1, JDatabase arg2, JOperateType arg3)
LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputInt16 arg0, jint arg1, JDatabase arg2, JOperateType arg3)
{
return LocalRef<JCommandStatus>(env, env->CallObjectMethod(instance, this->method2, arg0, arg1, arg2, arg3));
}

LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputFloat32 arg0, jint arg1, JDatabase arg2, JOperateType arg3)
LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputInt32 arg0, jint arg1, JDatabase arg2, JOperateType arg3)
{
return LocalRef<JCommandStatus>(env, env->CallObjectMethod(instance, this->method3, arg0, arg1, arg2, arg3));
}

LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputInt16 arg0, jint arg1, JDatabase arg2, JOperateType arg3)
LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputFloat32 arg0, jint arg1, JDatabase arg2, JOperateType arg3)
{
return LocalRef<JCommandStatus>(env, env->CallObjectMethod(instance, this->method4, arg0, arg1, arg2, arg3));
}

LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputInt32 arg0, jint arg1, JDatabase arg2, JOperateType arg3)
LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JControlRelayOutputBlock arg0, jint arg1, JDatabase arg2, JOperateType arg3)
{
return LocalRef<JCommandStatus>(env, env->CallObjectMethod(instance, this->method5, arg0, arg1, arg2, arg3));
}

LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JControlRelayOutputBlock arg0, jint arg1, JDatabase arg2, JOperateType arg3)
LocalRef<JCommandStatus> CommandHandler::operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputDouble64 arg0, jint arg1, JDatabase arg2, JOperateType arg3)
{
return LocalRef<JCommandStatus>(env, env->CallObjectMethod(instance, this->method6, arg0, arg1, arg2, arg3));
}
Expand Down
4 changes: 2 additions & 2 deletions java/cpp/jni/JNICommandHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ namespace jni
// methods
void begin(JNIEnv* env, JCommandHandler instance);
void end(JNIEnv* env, JCommandHandler instance);
LocalRef<JCommandStatus> operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputDouble64 arg0, jint arg1, JDatabase arg2, JOperateType arg3);
LocalRef<JCommandStatus> operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputFloat32 arg0, jint arg1, JDatabase arg2, JOperateType arg3);
LocalRef<JCommandStatus> operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputInt16 arg0, jint arg1, JDatabase arg2, JOperateType arg3);
LocalRef<JCommandStatus> operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputInt32 arg0, jint arg1, JDatabase arg2, JOperateType arg3);
LocalRef<JCommandStatus> operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputFloat32 arg0, jint arg1, JDatabase arg2, JOperateType arg3);
LocalRef<JCommandStatus> operate(JNIEnv* env, JCommandHandler instance, JControlRelayOutputBlock arg0, jint arg1, JDatabase arg2, JOperateType arg3);
LocalRef<JCommandStatus> operate(JNIEnv* env, JCommandHandler instance, JAnalogOutputDouble64 arg0, jint arg1, JDatabase arg2, JOperateType arg3);
LocalRef<JCommandStatus> select(JNIEnv* env, JCommandHandler instance, JAnalogOutputInt32 arg0, jint arg1);
LocalRef<JCommandStatus> select(JNIEnv* env, JCommandHandler instance, JAnalogOutputInt16 arg0, jint arg1);
LocalRef<JCommandStatus> select(JNIEnv* env, JCommandHandler instance, JAnalogOutputFloat32 arg0, jint arg1);
Expand Down
Loading