Skip to content
This repository has been archived by the owner on Sep 1, 2022. It is now read-only.

Commit

Permalink
Merge pull request #323 from dnp3/feature/unsol-num-retries
Browse files Browse the repository at this point in the history
Add support for configurable unsolicited response retries
  • Loading branch information
jadamcrain committed Jul 25, 2019
2 parents 903185b + 047b7f7 commit b230cc0
Show file tree
Hide file tree
Showing 30 changed files with 708 additions and 79 deletions.
2 changes: 2 additions & 0 deletions cpp/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ set(opendnp3_public_headers
./include/opendnp3/outstation/IOutstationApplication.h
./include/opendnp3/outstation/IUpdateHandler.h
./include/opendnp3/outstation/MeasurementConfig.h
./include/opendnp3/outstation/NumRetries.h
./include/opendnp3/outstation/OutstationConfig.h
./include/opendnp3/outstation/OutstationParams.h
./include/opendnp3/outstation/OutstationStackConfig.h
Expand Down Expand Up @@ -639,6 +640,7 @@ set(opendnp3_src
./src/outstation/FreezeRequestHandler.cpp
./src/outstation/IINHelpers.cpp
./src/outstation/IOutstationApplication.cpp
./src/outstation/NumRetries.cpp
./src/outstation/OctetStringSerializer.cpp
./src/outstation/OutstationContext.cpp
./src/outstation/OutstationStack.cpp
Expand Down
50 changes: 50 additions & 0 deletions cpp/lib/include/opendnp3/outstation/NumRetries.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2013-2019 Automatak, LLC
*
* Licensed to Green Energy Corp (www.greenenergycorp.com) and Automatak
* LLC (www.automatak.com) under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Green Energy Corp and Automatak LLC license
* this file to you under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You may obtain
* a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OPENDNP3_NUMRETRIES_H
#define OPENDNP3_NUMRETRIES_H

#include <cstddef>

namespace opendnp3
{

/**
* Unsolicited response number of retries
*/
class NumRetries final
{
public:
static NumRetries Fixed(std::size_t maxNumRetries);
static NumRetries Infinite();

bool Retry();
void Reset();

private:
NumRetries(std::size_t maxNumRetries, bool isInfinite);

std::size_t numRetries;
std::size_t maxNumRetries;
bool isInfinite;
};

} // namespace opendnp3

#endif
4 changes: 2 additions & 2 deletions cpp/lib/include/opendnp3/outstation/OutstationParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "opendnp3/util/TimeDuration.h"
#include "opendnp3/app/AppConstants.h"
#include "opendnp3/app/ClassField.h"
#include "opendnp3/outstation/NumRetries.h"
#include "opendnp3/outstation/StaticTypeBitfield.h"

namespace opendnp3
Expand All @@ -45,8 +46,7 @@ struct OutstationParams
/// Timeout for unsolicited confirms
TimeDuration unsolConfirmTimeout = DEFAULT_APP_TIMEOUT;

/// Timeout for unsolicited retries
TimeDuration unsolRetryTimeout = DEFAULT_APP_TIMEOUT;
NumRetries numUnsolRetries = NumRetries::Infinite();

/// The maximum fragment size the outstation will use for fragments it sends
uint32_t maxTxFragSize = DEFAULT_MAX_APDU_SIZE;
Expand Down
56 changes: 56 additions & 0 deletions cpp/lib/src/outstation/NumRetries.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2013-2019 Automatak, LLC
*
* Licensed to Green Energy Corp (www.greenenergycorp.com) and Automatak
* LLC (www.automatak.com) under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Green Energy Corp and Automatak LLC license
* this file to you under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You may obtain
* a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "opendnp3/outstation/NumRetries.h"

namespace opendnp3
{

NumRetries::NumRetries(std::size_t maxNumRetries, bool isInfinite)
: numRetries(0),
maxNumRetries(maxNumRetries),
isInfinite(isInfinite)
{

}

NumRetries NumRetries::Fixed(std::size_t maxNumRetries)
{
return NumRetries(maxNumRetries, false);
}

NumRetries NumRetries::Infinite()
{
return NumRetries(0, true);
}

bool NumRetries::Retry()
{
this->numRetries++;

return this->isInfinite || this->numRetries <= this->maxNumRetries;
}

void NumRetries::Reset()
{
this->numRetries = 0;
}

}; // namespace opendnp3
108 changes: 74 additions & 34 deletions cpp/lib/src/outstation/OutstationContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ OContext::OContext(const Addresses& addresses,
staticIIN(IINBit::DEVICE_RESTART),
deferred(config.params.maxRxFragSize),
sol(config.params.maxTxFragSize),
unsol(config.params.maxTxFragSize)
unsol(config.params.maxTxFragSize),
unsolRetries(config.params.numUnsolRetries),
shouldCheckForUnsolicited(false)
{
}

Expand All @@ -81,6 +83,7 @@ bool OContext::OnLowerLayerUp()
}

isOnline = true;
this->shouldCheckForUnsolicited = true;
this->CheckForTaskStart();
return true;
}
Expand Down Expand Up @@ -239,7 +242,7 @@ OutstationState& OContext::BeginResponseTx(uint16_t destination, APDUResponse& r

if (response.GetControl().CON)
{
this->RestartConfirmTimer();
this->RestartSolConfirmTimer();
return StateSolicitedConfirmWait::Inst();
}

Expand All @@ -251,6 +254,11 @@ void OContext::BeginRetransmitLastResponse(uint16_t destination)
this->BeginTx(destination, this->sol.tx.GetLastResponse());
}

void OContext::BeginRetransmitLastUnsolicitedResponse()
{
this->BeginTx(this->addresses.destination, this->unsol.tx.GetLastResponse());
}

void OContext::BeginUnsolTx(APDUResponse& response)
{
CheckForBroadcastConfirmation(response);
Expand All @@ -269,6 +277,17 @@ void OContext::BeginTx(uint16_t destination, const ser4cpp::rseq_t& message)
this->lower->BeginTransmit(Message(Addresses(this->addresses.source, destination), message));
}

void OContext::CheckForTaskStart()
{
// do these checks in order of priority
this->CheckForDeferredRequest();
this->CheckForUnsolicitedNull();
if(this->shouldCheckForUnsolicited)
{
this->CheckForUnsolicited();
}
}

void OContext::CheckForDeferredRequest()
{
if (this->CanTransmit() && this->deferred.IsSet())
Expand All @@ -278,35 +297,28 @@ void OContext::CheckForDeferredRequest()
}
}

bool OContext::ProcessDeferredRequest(const ParsedRequest& request)
void OContext::CheckForUnsolicitedNull()
{
if (request.header.function == FunctionCode::CONFIRM)
{
this->ProcessConfirm(request);
return true;
}

if (request.header.function == FunctionCode::READ)
if (this->CanTransmit() && this->state->IsIdle() && this->params.allowUnsolicited)
{
if (this->state->IsIdle())
if (!this->unsol.completedNull)
{
this->ProcessRequest(request);
return true;
// send a NULL unsolcited message
auto response = this->unsol.tx.Start();
build::NullUnsolicited(response, this->unsol.seq.num, this->GetResponseIIN());
this->RestartUnsolConfirmTimer();
this->state = &StateUnsolicitedConfirmWait::Inst();
this->BeginUnsolTx(response);
}

return false;
}
else
{
this->ProcessRequest(request);
return true;
}
}

void OContext::CheckForUnsolicited()
{
if (this->CanTransmit() && this->state->IsIdle() && this->params.allowUnsolicited)
if (this->shouldCheckForUnsolicited && this->CanTransmit() && this->state->IsIdle() && this->params.allowUnsolicited)
{
this->shouldCheckForUnsolicited = false;

if (this->unsol.completedNull)
{
// are there events to be reported?
Expand All @@ -316,29 +328,57 @@ void OContext::CheckForUnsolicited()
auto response = this->unsol.tx.Start();
auto writer = response.GetWriter();

this->unsolRetries.Reset();
this->eventBuffer.Unselect();
this->eventBuffer.SelectAllByClass(this->params.unsolClassMask);
this->eventBuffer.Load(writer);

build::NullUnsolicited(response, this->unsol.seq.num, this->GetResponseIIN());
this->RestartConfirmTimer();
this->RestartUnsolConfirmTimer();
this->state = &StateUnsolicitedConfirmWait::Inst();
this->BeginUnsolTx(response);
}
}
else
}
}

bool OContext::ProcessDeferredRequest(const ParsedRequest& request)
{
if (request.header.function == FunctionCode::CONFIRM)
{
this->ProcessConfirm(request);
return true;
}

if (request.header.function == FunctionCode::READ)
{
if (this->state->IsIdle())
{
// send a NULL unsolcited message
auto response = this->unsol.tx.Start();
build::NullUnsolicited(response, this->unsol.seq.num, this->GetResponseIIN());
this->RestartConfirmTimer();
this->state = &StateUnsolicitedConfirmWait::Inst();
this->BeginUnsolTx(response);
this->ProcessRequest(request);
return true;
}

return false;
}
else
{
this->ProcessRequest(request);
return true;
}
}

void OContext::RestartConfirmTimer()
void OContext::RestartSolConfirmTimer()
{
auto timeout = [&]() {
this->state = &this->state->OnConfirmTimeout(*this);
this->CheckForTaskStart();
};

this->confirmTimer.cancel();
this->confirmTimer = this->executor->start(this->params.solConfirmTimeout.value, timeout);
}

void OContext::RestartUnsolConfirmTimer()
{
auto timeout = [&]() {
this->state = &this->state->OnConfirmTimeout(*this);
Expand Down Expand Up @@ -495,11 +535,10 @@ bool OContext::ProcessMessage(const Message& message)
return this->ProcessObjects(ParsedRequest(message.addresses, result.header, result.objects));
}

void OContext::CheckForTaskStart()
void OContext::HandleNewEvents()
{
// do these checks in order of priority
this->CheckForDeferredRequest();
this->CheckForUnsolicited();
this->shouldCheckForUnsolicited = true;
this->CheckForTaskStart();
}

void OContext::SetRestartIIN()
Expand Down Expand Up @@ -826,6 +865,7 @@ IINField OContext::HandleEnableUnsolicited(const ser4cpp::rseq_t& objects, Heade
if (result == ParseResult::OK)
{
this->params.unsolClassMask.Set(handler.GetClassField());
shouldCheckForUnsolicited = true;
return handler.Errors();
}

Expand Down
16 changes: 13 additions & 3 deletions cpp/lib/src/outstation/OutstationContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class OContext : public IUpperLayer

/// --- Other public members ----

void CheckForTaskStart();
void HandleNewEvents();

IUpdateHandler& GetUpdateHandler();

Expand Down Expand Up @@ -120,17 +120,25 @@ class OContext : public IUpperLayer

void BeginRetransmitLastResponse(uint16_t destination);

void BeginRetransmitLastUnsolicitedResponse();

void BeginUnsolTx(APDUResponse& response);

void BeginTx(uint16_t destination, const ser4cpp::rseq_t& message);

void CheckForTaskStart();

void CheckForDeferredRequest();

void CheckForUnsolicitedNull();

void CheckForUnsolicited();

bool ProcessDeferredRequest(const ParsedRequest& request);

void RestartConfirmTimer();
void RestartSolConfirmTimer();

void CheckForUnsolicited();
void RestartUnsolConfirmTimer();

bool CanTransmit() const;

Expand Down Expand Up @@ -201,6 +209,8 @@ class OContext : public IUpperLayer
// ------ Dynamic state related to solicited and unsolicited modes ------
OutstationSolState sol;
OutstationUnsolState unsol;
NumRetries unsolRetries;
bool shouldCheckForUnsolicited;
OutstationState* state = &StateIdle::Inst();

// ------ Dynamic state related to broadcast messages ------
Expand Down
2 changes: 1 addition & 1 deletion cpp/lib/src/outstation/OutstationStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ void OutstationStack::Apply(const Updates& updates)

auto task = [self = this->shared_from_this(), updates]() {
updates.Apply(self->ocontext.GetUpdateHandler());
self->ocontext.CheckForTaskStart(); // force the outstation to check for updates
self->ocontext.HandleNewEvents(); // force the outstation to check for updates
};

this->executor->post(task);
Expand Down

0 comments on commit b230cc0

Please sign in to comment.