Commit 00a9097e authored by Sylvain Berfini's avatar Sylvain Berfini 🎩

Removed stubs and first step to real registration

parent 171a42c7
......@@ -44,7 +44,6 @@ namespace Linphone.Agents
Debug.WriteLine("[{0}] Incoming call from caller {1}, number {2}", "KeepAliveAgent", callerName, callerNumber);
LinphoneCall call = new LinphoneCall(callerName, callerNumber);
Globals.Instance.LinphoneCore.IncomingCall = call;
Debug.WriteLine("[KeepAliveAgent] Incoming call added to LinphoneCore, notifyComplete");
base.NotifyComplete();
......
#include "LinphoneAuthInfo.h"
#include "Server.h"
#include "Utils.h"
Platform::String^ Linphone::Core::LinphoneAuthInfo::GetUsername()
{
......@@ -31,3 +31,23 @@ void Linphone::Core::LinphoneAuthInfo::SetRealm(Platform::String^ realm)
{
}
Linphone::Core::LinphoneAuthInfo::LinphoneAuthInfo(Platform::String^ username, Platform::String^ userid, Platform::String^ password, Platform::String^ ha1, Platform::String^ realm)
{
const char* cc_username = Utils::pstoccs(username);
const char* cc_password = Utils::pstoccs(password);
const char* cc_realm = Utils::pstoccs(realm);
const char* cc_userid = Utils::pstoccs(userid);
const char* cc_ha1 = Utils::pstoccs(ha1);
this->auth_info = linphone_auth_info_new(cc_username, cc_userid, cc_password, cc_ha1, cc_realm);
delete(cc_username);
delete(cc_userid);
delete(cc_password);
delete(cc_ha1);
delete(cc_realm);
}
Linphone::Core::LinphoneAuthInfo::~LinphoneAuthInfo()
{
delete(this->auth_info);
}
\ No newline at end of file
#pragma once
#include "LinphoneCore.h"
namespace Linphone
{
namespace Core
......@@ -28,6 +30,14 @@ namespace Linphone
Platform::String^ GetRealm();
void SetRealm(Platform::String^ realm);
private:
friend ref class Linphone::Core::LinphoneCore;
LinphoneAuthInfo(Platform::String^ username, Platform::String^ userid, Platform::String^ password, Platform::String^ ha1, Platform::String^ realm);
~LinphoneAuthInfo();
::LinphoneAuthInfo *auth_info;
};
}
}
\ No newline at end of file
......@@ -58,12 +58,11 @@ void Linphone::Core::LinphoneCore::SetContext(Platform::Object^ object)
void Linphone::Core::LinphoneCore::ClearProxyConfigs()
{
linphone_core_clear_proxy_config(this->lc);
}
void Linphone::Core::LinphoneCore::AddProxyConfig(Linphone::Core::LinphoneProxyConfig^ proxyCfg)
{
this->proxyCfgAdded = true;
linphone_core_add_proxy_config(this->lc, proxyCfg->proxy_config);
}
......@@ -79,7 +78,7 @@ Linphone::Core::LinphoneProxyConfig^ Linphone::Core::LinphoneCore::GetDefaultPro
Linphone::Core::LinphoneProxyConfig^ Linphone::Core::LinphoneCore::CreateEmptyProxyConfig()
{
Linphone::Core::LinphoneProxyConfig^ proxyConfig = ref new Linphone::Core::LinphoneProxyConfig(this->lc);
Linphone::Core::LinphoneProxyConfig^ proxyConfig = ref new Linphone::Core::LinphoneProxyConfig();
return proxyConfig;
}
......@@ -90,65 +89,24 @@ Windows::Foundation::Collections::IVector<Linphone::Core::LinphoneProxyConfig^>^
void Linphone::Core::LinphoneCore::ClearAuthInfos()
{
linphone_core_clear_all_auth_info(this->lc);
}
void Linphone::Core::LinphoneCore::AddAuthInfo(Linphone::Core::LinphoneAuthInfo^ info)
{
linphone_core_add_auth_info(this->lc, info->auth_info);
}
Linphone::Core::LinphoneAuthInfo^ Linphone::Core::LinphoneCore::CreateAuthInfo(Platform::String^ username, Platform::String^ userid, Platform::String^ password, Platform::String^ ha1, Platform::String^ realm)
{
Linphone::Core::LinphoneAuthInfo^ authInfo = ref new Linphone::Core::LinphoneAuthInfo(username, userid, password, ha1, realm);
return authInfo;
}
void Linphone::Core::LinphoneCore::Iterate()
{
std::lock_guard<std::recursive_mutex> lock(g_apiLock);
if (this->incomingcall != nullptr && this->listener != nullptr)
{
this->listener->CallState(this->incomingcall, Linphone::Core::LinphoneCallState::IncomingReceived);
this->call = incomingcall;
this->incomingcall = nullptr;
}
else if (this->call != nullptr && this->callAccepted && this->listener != nullptr)
{
this->callAccepted = false;
this->listener->CallState(this->call, Linphone::Core::LinphoneCallState::Connected);
this->callConnected = true;
}
else if (this->call != nullptr && this->callConnected && this->listener != nullptr)
{
this->callConnected = false;
this->listener->CallState(this->call, Linphone::Core::LinphoneCallState::StreamsRunning);
}
else if (this->call != nullptr && this->callEnded && this->listener != nullptr)
{
this->callEnded = false;
this->listener->CallState(this->call, Linphone::Core::LinphoneCallState::CallEnd);
this->call = nullptr;
}
if (this->listener != nullptr && this->startup)
{
this->listener->GlobalState(Linphone::Core::GlobalState::GlobalStartup, L"");
this->startup = false;
this->on = true;
}
else if (this->listener != nullptr && this->on)
{
this->listener->GlobalState(Linphone::Core::GlobalState::GlobalOn, L"");
this->on = false;
}
if (this->listener != nullptr && this->proxyCfgAdded && !this->on)
{
this->listener->RegistrationState(nullptr, Linphone::Core::RegistrationState::RegistrationInProgress, L"");
this->proxyCfgAdded = false;
this->proxyCfgRegistered = true;
}
else if (this->listener != nullptr && this->proxyCfgRegistered)
{
this->listener->RegistrationState(nullptr, Linphone::Core::RegistrationState::RegistrationOk, L"");
this->proxyCfgRegistered = false;
}
linphone_core_iterate(this->lc);
}
void Linphone::Core::LinphoneCore::Destroy()
......@@ -163,14 +121,7 @@ Linphone::Core::LinphoneAddress^ Linphone::Core::LinphoneCore::InterpretURL(Plat
Linphone::Core::LinphoneCall^ Linphone::Core::LinphoneCore::Invite(Platform::String^ destination)
{
std::lock_guard<std::recursive_mutex> lock(g_apiLock);
Linphone::Core::LinphoneCall^ call = ref new Linphone::Core::LinphoneCall("", destination);
if (this->listener != nullptr)
this->listener->CallState(call, Linphone::Core::LinphoneCallState::OutgoingInit);
this->callAccepted = true;
return call;
return nullptr;
}
Linphone::Core::LinphoneCall^ Linphone::Core::LinphoneCore::InviteAddress(Linphone::Core::LinphoneAddress^ to)
......@@ -185,12 +136,12 @@ Linphone::Core::LinphoneCall^ Linphone::Core::LinphoneCore::InviteAddressWithPar
void Linphone::Core::LinphoneCore::TerminateCall(Linphone::Core::LinphoneCall^ call)
{
this->callEnded = true;
}
Linphone::Core::LinphoneCall^ Linphone::Core::LinphoneCore::GetCurrentCall()
{
return this->call;
return nullptr;
}
Linphone::Core::LinphoneAddress^ Linphone::Core::LinphoneCore::GetRemoteAddress()
......@@ -210,8 +161,7 @@ Platform::Boolean Linphone::Core::LinphoneCore::IsIncomingInvitePending()
void Linphone::Core::LinphoneCore::AcceptCall(Linphone::Core::LinphoneCall^ call)
{
this->call = call;
this->callAccepted = true;
}
void Linphone::Core::LinphoneCore::AcceptCallWithParams(Linphone::Core::LinphoneCall^ call, Linphone::Core::LinphoneCallParams^ params)
......@@ -520,16 +470,12 @@ IVector<Linphone::Core::LinphoneCall^>^ Linphone::Core::LinphoneCore::GetCalls()
std::lock_guard<std::recursive_mutex> lock(g_apiLock);
Vector<Linphone::Core::LinphoneCall^>^ calls = ref new Vector<Linphone::Core::LinphoneCall^>();
calls->Append(this->call);
return calls;
}
int Linphone::Core::LinphoneCore::GetCallsNb()
{
if (this->Call != nullptr)
return 1;
else
return 0;
return 0;
}
Linphone::Core::LinphoneCall^ Linphone::Core::LinphoneCore::FindCallFromUri(Platform::String^ uri)
......@@ -687,63 +633,54 @@ Linphone::Core::LpConfig^ Linphone::Core::LinphoneCore::GetConfig()
return nullptr;
}
Linphone::Core::LinphoneCall^ Linphone::Core::LinphoneCore::Call::get()
Linphone::Core::LinphoneCoreListener^ Linphone::Core::LinphoneCore::CoreListener::get()
{
return this->call;
}
void Linphone::Core::LinphoneCore::Call::set(Linphone::Core::LinphoneCall^ call)
{
this->call = call;
}
Linphone::Core::LinphoneCall^ Linphone::Core::LinphoneCore::IncomingCall::get()
{
std::lock_guard<std::recursive_mutex> lock(g_apiLock);
return this->incomingcall;
}
void Linphone::Core::LinphoneCore::IncomingCall::set(Linphone::Core::LinphoneCall^ call)
{
std::lock_guard<std::recursive_mutex> lock(g_apiLock);
this->incomingcall = call;
return this->listener;
}
void call_state_changed(::LinphoneCore *lc, ::LinphoneCall *call, ::LinphoneCallState cstate, const char *msg)
{
/*Linphone::Core::LinphoneCoreListener^ listener = Linphone::Core::Globals::Instance->LinphoneCore->CoreListener;
if (listener != nullptr)
{
listener->CallState(call, cstate);
}*/
}
void registration_state_changed(::LinphoneCore *lc, ::LinphoneProxyConfig *cfg, ::LinphoneRegistrationState cstate, const char *message)
void registration_state_changed(::LinphoneCore *lc, ::LinphoneProxyConfig *cfg, ::LinphoneRegistrationState cstate, const char *msg)
{
/*Linphone::Core::LinphoneCoreListener^ listener = Linphone::Core::Globals::Instance->LinphoneCore->CoreListener;
if (listener != nullptr)
{
listener->RegistrationState(cfg, cstate, Linphone::Core::Utils::cctops(msg));
}*/
}
void global_state_changed(::LinphoneCore *lc, ::LinphoneGlobalState gstate, const char *msg)
{
Linphone::Core::LinphoneCoreListener^ listener = Linphone::Core::Globals::Instance->LinphoneCore->CoreListener;
if (listener != nullptr)
{
Linphone::Core::GlobalState state = (Linphone::Core::GlobalState) gstate;
listener->GlobalState(state, Linphone::Core::Utils::cctops(msg));
}
}
Linphone::Core::LinphoneCore::LinphoneCore(LinphoneCoreListener^ coreListener) :
call(nullptr),
incomingcall(nullptr),
callAccepted(false),
callEnded(false),
callConnected(false),
proxyCfgAdded(false),
proxyCfgRegistered(false),
startup(true),
on(false),
listener(coreListener),
lc(nullptr)
{
}
void Linphone::Core::LinphoneCore::Init()
{
LinphoneCoreVTable *vtable = (LinphoneCoreVTable*) malloc(sizeof(LinphoneCoreVTable));
memset (vtable, 0, sizeof(LinphoneCoreVTable));
vtable->global_state_changed = global_state_changed;
vtable->registration_state_changed = registration_state_changed;
vtable->call_state_changed = call_state_changed;
this->lc = linphone_core_new(vtable, NULL, "Assets/linphone_rc", NULL);
}
......
......@@ -2,7 +2,7 @@
#include "Enums.h"
#include "LinphoneCoreListener.h"
#include "Utils.h"
#include "coreapi\linphonecore.h"
namespace Linphone
......@@ -92,6 +92,11 @@ namespace Linphone
/// This information will be used during all SIP transactions which requieres authentication.
/// </summary>
void AddAuthInfo(LinphoneAuthInfo^ info);
/// <summary>
/// Creates an empty auth info.
/// </summary>
LinphoneAuthInfo^ CreateAuthInfo(Platform::String^ username, Platform::String^ userid, Platform::String^ password, Platform::String^ ha1, Platform::String^ realm);
/// <summary>
/// Main loop function. It is crucial that your application calls it periodically.
......@@ -405,36 +410,18 @@ namespace Linphone
/// </summary>
LpConfig^ GetConfig();
property LinphoneCall^ Call
{
LinphoneCall^ get();
void set(LinphoneCall^ call);
}
property LinphoneCall^ IncomingCall
property LinphoneCoreListener^ CoreListener
{
LinphoneCall^ get();
void set(LinphoneCall^ call);
LinphoneCoreListener^ get();
}
private:
friend ref class Linphone::Core::LinphoneCoreFactory;
LinphoneCoreListener^ listener;
LinphoneCall^ call;
LinphoneCall^ incomingcall;
bool callAccepted;
bool callEnded;
bool callConnected;
bool proxyCfgAdded;
bool proxyCfgRegistered;
bool startup;
bool on;
LinphoneCore(LinphoneCoreListener^ coreListener);
void Init();
~LinphoneCore();
::LinphoneCore *lc;
......
......@@ -42,6 +42,7 @@ void LinphoneCoreFactory::CreateLinphoneCore(Linphone::Core::LinphoneCoreListene
std::lock_guard<std::recursive_mutex> lock(g_apiLock);
linphone_core_enable_logs_with_cb(LinphoneNativeOutputTraceHandler);
this->linphoneCore = ref new Linphone::Core::LinphoneCore(listener);
this->linphoneCore->Init();
}
void LinphoneCoreFactory::CreateLinphoneCore(Linphone::Core::LinphoneCoreListener^ listener)
......
......@@ -13,11 +13,20 @@ void Linphone::Core::LinphoneProxyConfig::Done()
linphone_proxy_config_done(this->proxy_config);
}
void Linphone::Core::LinphoneProxyConfig::SetIdentity(Platform::String^ identity)
{
const char* cc = Utils::pstoccs(identity);
linphone_proxy_config_set_identity(this->proxy_config, cc);
delete(cc);
void Linphone::Core::LinphoneProxyConfig::SetIdentity(Platform::String^ displayname, Platform::String^ username, Platform::String^ domain)
{
const char* cc_username = Utils::pstoccs(username);
const char* cc_domain = Utils::pstoccs(domain);
const char* cc_displayname = Utils::pstoccs(displayname);
::LinphoneAddress *addr = linphone_address_new(NULL);
linphone_address_set_username(addr, cc_username);
linphone_address_set_domain(addr, cc_domain);
linphone_address_set_display_name(addr, cc_displayname);
linphone_proxy_config_set_identity(this->proxy_config, linphone_address_as_string(addr));
linphone_address_destroy(addr);
delete(cc_username);
delete(cc_domain);
delete(cc_displayname);
}
Platform::String^ Linphone::Core::LinphoneProxyConfig::GetIdentity()
......@@ -34,27 +43,36 @@ void Linphone::Core::LinphoneProxyConfig::SetProxy(Platform::String^ proxyUri)
void Linphone::Core::LinphoneProxyConfig::EnableRegister(Platform::Boolean enable)
{
linphone_proxy_config_enable_register(this->proxy_config, enable);
}
Platform::Boolean Linphone::Core::LinphoneProxyConfig::IsRegisterEnabled()
{
return false;
return linphone_proxy_config_register_enabled(this->proxy_config);
}
Platform::String^ Linphone::Core::LinphoneProxyConfig::NormalizePhoneNumber(Platform::String^ phoneNumber)
{
return nullptr;
const char* cc = Utils::pstoccs(phoneNumber);
char* result = (char*) malloc(phoneNumber->Length());
int result_size = 0;
linphone_proxy_config_normalize_number(this->proxy_config, cc, result, result_size);
Platform::String^ val = Utils::cctops(result);
delete(cc);
delete(result);
return val;
}
void Linphone::Core::LinphoneProxyConfig::SetDialPrefix(Platform::String^ prefix)
{
const char* cc = Utils::pstoccs(prefix);
linphone_proxy_config_set_dial_prefix(this->proxy_config, cc);
delete(cc);
}
void Linphone::Core::LinphoneProxyConfig::SetDialEscapePlus(Platform::Boolean value)
{
linphone_proxy_config_set_dial_escape_plus(this->proxy_config, value);
}
Platform::String^ Linphone::Core::LinphoneProxyConfig::GetDomain()
......@@ -64,7 +82,7 @@ Platform::String^ Linphone::Core::LinphoneProxyConfig::GetDomain()
Platform::Boolean Linphone::Core::LinphoneProxyConfig::IsRegistered()
{
return false;
return linphone_proxy_config_is_registered(this->proxy_config);
}
void Linphone::Core::LinphoneProxyConfig::SetRoute(Platform::String^ routeUri)
......@@ -81,7 +99,7 @@ Platform::String^ Linphone::Core::LinphoneProxyConfig::GetRoute()
void Linphone::Core::LinphoneProxyConfig::EnablePublish(Platform::Boolean enable)
{
linphone_proxy_config_enable_publish(this->proxy_config, enable);
}
Platform::Boolean Linphone::Core::LinphoneProxyConfig::IsPublishEnabled()
......@@ -96,12 +114,14 @@ Linphone::Core::RegistrationState Linphone::Core::LinphoneProxyConfig::GetState(
void Linphone::Core::LinphoneProxyConfig::SetExpires(int delay)
{
}
void Linphone::Core::LinphoneProxyConfig::SetContactParameters(Platform::String^ params)
{
const char* cc = Utils::pstoccs(params);
linphone_proxy_config_set_contact_parameters(this->proxy_config, cc);
delete(cc);
}
int Linphone::Core::LinphoneProxyConfig::LookupCCCFromIso(Platform::String^ iso)
......@@ -114,12 +134,12 @@ int Linphone::Core::LinphoneProxyConfig::LookupCCCFromE164(Platform::String^ e16
return -1;
}
Linphone::Core::LinphoneProxyConfig::LinphoneProxyConfig(::LinphoneCore *lc)
Linphone::Core::LinphoneProxyConfig::LinphoneProxyConfig()
{
this->proxy_config = linphone_core_create_proxy_config(lc);
this->proxy_config = linphone_proxy_config_new();
}
Linphone::Core::LinphoneProxyConfig::~LinphoneProxyConfig()
{
free(this->proxy_config);
delete(this->proxy_config);
}
\ No newline at end of file
......@@ -29,11 +29,7 @@ namespace Linphone
/// </summary>
void Done();
/// <param name="identity">
/// identity is normally formed with display name, username and domain, such as: Alice &lt;sip:alice@example.net&gt;.
/// The REGISTER messages will have from and to set to this identity.
/// </param>
void SetIdentity(Platform::String^ identity);
void SetIdentity(Platform::String^ displayname, Platform::String^ username, Platform::String^ domain);
Platform::String^ GetIdentity();
/// <summary>
......@@ -118,7 +114,7 @@ namespace Linphone
private:
friend ref class Linphone::Core::LinphoneCore;
LinphoneProxyConfig(::LinphoneCore *lc);
LinphoneProxyConfig();
~LinphoneProxyConfig();
::LinphoneProxyConfig *proxy_config;
......
......@@ -14,6 +14,9 @@ std::string Linphone::Core::Utils::pstos(Platform::String^ ps)
const char* Linphone::Core::Utils::pstoccs(Platform::String^ ps)
{
if (ps == nullptr || ps->Length() == 0)
return NULL;
std::string s = pstos(ps);
char* cc = (char*) malloc(s.length()+1);
memcpy(cc, s.c_str(), s.length());
......@@ -23,6 +26,9 @@ const char* Linphone::Core::Utils::pstoccs(Platform::String^ ps)
Platform::String^ Linphone::Core::Utils::cctops(const char* cc)
{
if (cc == NULL)
return nullptr;
std::string s_str = std::string(cc);
std::wstring wid_str = std::wstring(s_str.begin(), s_str.end());
const wchar_t* w_char = wid_str.c_str();
......
#pragma once
#include <string>
namespace Linphone
......@@ -6,14 +8,15 @@ namespace Linphone
{
class Utils
{
public:
static std::string wstos(std::wstring ws);
static std::string pstos(Platform::String^ ps);
public:
static const char* pstoccs(Platform::String^ ps);
static Platform::String^ Linphone::Core::Utils::cctops(const char*);
private:
static std::string wstos(std::wstring ws);
static std::string pstos(Platform::String^ ps);
};
}
}
\ No newline at end of file
No preview for this file type
[net]
mtu=1300
[sip]
[sip]
sip_port=5070
sip_tcp_port=5070
sip_tls_port=5071
default_proxy=0
ping_with_options=0
register_only_when_network_is_up=0
......
......@@ -161,7 +161,7 @@
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="Assets\linphone_rc" />
<Content Include="Assets\linphone_rc" />
<None Include="packages.config" />
<None Include="Properties\AppManifest.xml" />
<None Include="Properties\WMAppManifest.xml">
......
......@@ -13,7 +13,7 @@
<FlavorProperties GUID="{C089C8C0-30E0-4E22-80C0-CE093F111A43}">
<SilverlightMobileCSProjectFlavor>
<FullDeploy>False</FullDeploy>
<DebuggerType>Managed</DebuggerType>
<DebuggerType>Native</DebuggerType>
<Tombstone>False</Tombstone>
</SilverlightMobileCSProjectFlavor>
</FlavorProperties>
......
......@@ -179,13 +179,21 @@ namespace Linphone.Model
/// </summary>
public void InitProxyConfig()
{
server.LinphoneCore.ClearAuthInfos();
server.LinphoneCore.ClearProxyConfigs();
SettingsManager sm = new SettingsManager();
if (sm.Username != null && sm.Username.Length > 0 && sm.Domain != null && sm.Domain.Length > 0)
{
var proxy = server.LinphoneCore.CreateEmptyProxyConfig();
proxy.SetIdentity(sm.Username, sm.Username, sm.Domain);
proxy.EnableRegister(true);
server.LinphoneCore.AddProxyConfig(proxy);
server.LinphoneCore.SetDefaultProxyConfig(proxy);
// Can't set string to null: http://stackoverflow.com/questions/12980915/exception-when-trying-to-read-null-string-in-c-sharp-winrt-component-from-winjs
var auth = server.LinphoneCore.CreateAuthInfo(sm.Username, "", sm.Password, "", sm.Domain);
server.LinphoneCore.AddAuthInfo(auth);
}
}
......@@ -329,7 +337,6 @@ namespace Linphone.Model
call.NotifyCallActive();
LinphoneCall LCall = LinphoneCore.Invite(sipAddress);
LCall.CallContext = call;
LinphoneCore.Call = LCall;
}
/// <summary>
......@@ -374,7 +381,7 @@ namespace Linphone.Model
/// </summary>
public void AuthInfoRequested(string realm, string username)
{
Debug.WriteLine("[LinphoneManager] Auth info requested: realm=" + realm + ", username=" + username);
}
/// <summary>
......@@ -420,7 +427,6 @@ namespace Linphone.Model
});
call.CallContext = vcall;
LinphoneCore.Call = call;
});
}
else if (state == LinphoneCallState.StreamsRunning)
......
......@@ -44,7 +44,7 @@ namespace Linphone.Views
_appSettings.Username = Username.Text;
_appSettings.Password = Password.Password;
_appSettings.Domain = Domain.Text;
_appSettings.Proxy = Domain.Text;
_appSettings.Proxy = Proxy.Text;
_appSettings.OutboundProxy = OutboundProxy.IsChecked;
LinphoneManager.Instance.InitProxyConfig();
......
......@@ -11,4 +11,7 @@ the Linphone project to make calls to the C++ API
* Linphone: the interface project
If you have a FileNotFoundException is mscorlib during debug phase,
open DEBUG->Exceptions and uncheck Common Language Runtime Exceptions->System.IO.FileNotFoundException
\ No newline at end of file
open DEBUG->Exceptions and uncheck Common Language Runtime Exceptions->System.IO.FileNotFoundException
YOU CAN'T PASS A NULL STRING FROM C# TO C++/CX (it has to be empty) AND YOU CAN'T RECEIVE A NULL STRING FROM C++/CX (It will always be an empty string).
See http://stackoverflow.com/questions/12980915/exception-when-trying-to-read-null-string-in-c-sharp-winrt-component-from-winjs
\ No newline at end of file
linphone @ c87046dc
Subproject commit fa8135be0351dbaa115f72997886b2aa5258599c
Subproject commit c87046dcdf1e610a32fbda83b7d52fec0ff3f8a3
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment