blob: eb23b0dbc891a1814f690fcf819d764c06979759 [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name of the copyright holder 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 HOLDER 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.
*/
#include "pch.h"
#include <utility>
#include "DatagramClientContext.h"
using namespace ot;
using namespace Concurrency;
using namespace Platform;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Networking::Sockets;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Navigation;
DatagramClientContext::DatagramClientContext(
IAsyncThreadNotify^ notify,
DatagramSocket^ client,
ClientArgs^ args) :
_notify{ std::move(notify) },
_client{ std::move(client) },
_args{ std::move(args) }
{
}
DatagramClientContext::~DatagramClientContext()
{
// A Client can be closed in two ways:
// - explicitly: using the 'delete' keyword (client is closed even if there are outstanding references to it).
// - implicitly: removing the last reference to it (i.e., falling out-of-scope).
//
// When a Socket is closed implicitly, it can take several seconds for the local port being used
// by it to be freed/reclaimed by the lower networking layers. During that time, other sockets on the machine
// will not be able to use the port. Thus, it is strongly recommended that Socket instances be explicitly
// closed before they go out of scope(e.g., before application exit). The call below explicitly closes the socket.
if (_client != nullptr)
{
delete _client;
_client = nullptr;
}
}
void
DatagramClientContext::Connect_Click(
Object^ sender,
RoutedEventArgs^ e)
{
task<void> removeContext;
if (CoreApplication::Properties->HasKey("clientContext"))
{
auto clientContext = dynamic_cast<IClientContext^>(
CoreApplication::Properties->Lookup("clientContext"));
if (clientContext == nullptr)
{
throw ref new FailureException(L"No clientContext");
}
removeContext = create_task(clientContext->CancelIO()).then(
[]()
{
CoreApplication::Properties->Remove("clientContext");
});
}
else
{
removeContext = create_task([]() {});
}
_client->MessageReceived += ref new MessageHandler(
this, &DatagramClientContext::OnMessage);
removeContext.then([this](task<void> prevTask)
{
try
{
// Try getting an exception.
prevTask.get();
// Events cannot be hooked up directly to the ScenarioInput2 object, as the object can fall out-of-scope and be
// deleted. This would render any event hooked up to the object ineffective. The ClientContext guarantees that
// both the socket and object that serves its events have the same lifetime.
CoreApplication::Properties->Insert("clientContext", this);
}
catch (Exception^ ex)
{
_notify->NotifyFromAsyncThread(
"Remove clientContext error: " + ex->Message,
NotifyType::Error);
}
catch (task_canceled&)
{
}
}).then([this]()
{
auto endpointPair = ref new EndpointPair(_args->ClientHostName, _args->ClientPort,
_args->ServerHostName, _args->ServerPort);
_notify->NotifyFromAsyncThread("Start connecting", NotifyType::Status);
create_task(_client->ConnectAsync(endpointPair)).then(
[this, endpointPair](task<void> prevTask)
{
try
{
// Try getting an exception.
prevTask.get();
_notify->NotifyFromAsyncThread(
"Connect from " + endpointPair->LocalHostName->CanonicalName +
" to " + endpointPair->RemoteHostName->CanonicalName,
NotifyType::Status);
SetConnected(true);
}
catch (Exception^ ex)
{
_notify->NotifyFromAsyncThread(
"Start binding failed with error: " + ex->Message,
NotifyType::Error);
CoreApplication::Properties->Remove("clientContext");
}
catch (task_canceled&)
{
CoreApplication::Properties->Remove("clientContext");
}
});
});
}
void
DatagramClientContext::Send_Click(
Object^ sender,
RoutedEventArgs^ e,
String^ input)
{
SendMessage(GetDataWriter(), input);
}
IAsyncAction^
DatagramClientContext::CancelIO()
{
return _client->CancelIOAsync();
}
void
DatagramClientContext::SetConnected(
bool connected)
{
_connected = connected;
}
bool
ot::DatagramClientContext::IsConnected() const
{
return _connected;
}
void
ot::DatagramClientContext::OnMessage(
DatagramSocket^ socket,
MessageReceivedEventArgs^ eventArgs)
{
try
{
auto dataReader = eventArgs->GetDataReader();
Receive(dataReader, dataReader->UnconsumedBufferLength);
}
catch (Exception^ ex)
{
auto socketError = SocketError::GetStatus(ex->HResult);
if (socketError == SocketErrorStatus::ConnectionResetByPeer)
{
// This error would indicate that a previous send operation resulted in an ICMP "Port Unreachable" message.
_notify->NotifyFromAsyncThread(
"Peer does not listen on the specific port. Please make sure that you run step 1 first "
"or you have a server properly working on a remote server.",
NotifyType::Error);
}
else if (socketError != SocketErrorStatus::Unknown)
{
_notify->NotifyFromAsyncThread(
"Error happened when receiving a datagram: " + socketError.ToString(),
NotifyType::Error);
}
else
{
throw;
}
}
}
void
DatagramClientContext::Receive(
DataReader^ dataReader,
unsigned int strLen)
{
if (!strLen)
{
return;
}
auto msg = dataReader->ReadString(strLen);
_notify->NotifyFromAsyncThread("Received data from server: \"" + msg + "\"",
NotifyType::Status);
}
void
DatagramClientContext::SendMessage(
DataWriter^ dataWriter,
String^ msg)
{
if (!IsConnected())
{
_notify->NotifyFromAsyncThread("This socket is not yet connected.", NotifyType::Error);
return;
}
try
{
dataWriter->WriteString(msg);
_notify->NotifyFromAsyncThread("Sending - " + msg, NotifyType::Status);
}
catch (Exception^ ex)
{
_notify->NotifyFromAsyncThread("Sending failed with error: " + ex->Message, NotifyType::Error);
}
// Write the locally buffered data to the network. Please note that write operation will succeed
// even if the server is not listening.
create_task(dataWriter->StoreAsync()).then(
[this](task<unsigned int> writeTask)
{
try
{
// Try getting an exception.
writeTask.get();
}
catch (Exception^ ex)
{
_notify->NotifyFromAsyncThread("Send failed with error: " + ex->Message, NotifyType::Error);
}
});
}
Windows::Storage::Streams::DataWriter^
DatagramClientContext::GetDataWriter()
{
if (_dataWriter == nullptr)
{
_dataWriter = ref new DataWriter(_client->OutputStream);
}
return _dataWriter;
}