Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
J
jami-daemon
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
130
Issues
130
List
Boards
Labels
Service Desk
Milestones
Iterations
Requirements
Requirements
List
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Operations
Operations
Incidents
Analytics
Analytics
Insights
Issue
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
savoirfairelinux
jami-daemon
Commits
be8241d0
Commit
be8241d0
authored
Aug 03, 2019
by
Adrien Béraud
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
upnp: cleanup, avoid use-after-free
Change-Id: Ifac53c178d59434027d162be0400070c01144e81
parent
165f32ae
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
59 additions
and
98 deletions
+59
-98
src/upnp/protocol/natpmp/nat_pmp.cpp
src/upnp/protocol/natpmp/nat_pmp.cpp
+15
-27
src/upnp/protocol/natpmp/nat_pmp.h
src/upnp/protocol/natpmp/nat_pmp.h
+1
-1
src/upnp/protocol/natpmp/pmp_igd.h
src/upnp/protocol/natpmp/pmp_igd.h
+0
-1
src/upnp/protocol/pupnp/pupnp.cpp
src/upnp/protocol/pupnp/pupnp.cpp
+5
-5
src/upnp/protocol/pupnp/pupnp.h
src/upnp/protocol/pupnp/pupnp.h
+1
-1
src/upnp/protocol/upnp_protocol.h
src/upnp/protocol/upnp_protocol.h
+2
-2
src/upnp/upnp_context.cpp
src/upnp/upnp_context.cpp
+25
-48
src/upnp/upnp_context.h
src/upnp/upnp_context.h
+10
-13
No files found.
src/upnp/protocol/natpmp/nat_pmp.cpp
View file @
be8241d0
...
@@ -73,14 +73,12 @@ NatPmp::NatPmp()
...
@@ -73,14 +73,12 @@ NatPmp::NatPmp()
closenatpmp
(
&
natpmp
);
closenatpmp
(
&
natpmp
);
})
})
{
{
}
}
NatPmp
::~
NatPmp
()
NatPmp
::~
NatPmp
()
{
{
{
{
std
::
lock_guard
<
std
::
mutex
>
lock
(
validIgdMutex
);
std
::
lock_guard
<
std
::
mutex
>
lock
(
validIgdMutex
);
if
(
pmpIGD_
)
{
if
(
pmpIGD_
)
{
{
{
std
::
lock_guard
<
std
::
mutex
>
lk
(
pmpMutex_
);
std
::
lock_guard
<
std
::
mutex
>
lk
(
pmpMutex_
);
...
@@ -99,9 +97,8 @@ NatPmp::~NatPmp()
...
@@ -99,9 +97,8 @@ NatPmp::~NatPmp()
}
}
void
void
NatPmp
::
c
onnectivityChanged
()
NatPmp
::
c
learIGDs
()
{
{
{
// Lock valid IGD.
// Lock valid IGD.
std
::
lock_guard
<
std
::
mutex
>
lock
(
validIgdMutex
);
std
::
lock_guard
<
std
::
mutex
>
lock
(
validIgdMutex
);
...
@@ -112,24 +109,18 @@ NatPmp::connectivityChanged()
...
@@ -112,24 +109,18 @@ NatPmp::connectivityChanged()
pmpIGD_
.
reset
();
pmpIGD_
.
reset
();
}
}
validIgdMutex
.
unlock
();
// Notify.
pmpCv_
.
notify_all
();
pmpCv_
.
notify_all
();
}
}
}
void
void
NatPmp
::
searchForIGD
()
NatPmp
::
searchForIGD
()
{
{
pmpRun_
=
true
;
// Lock valid IGD.
std
::
lock_guard
<
std
::
mutex
>
lock
(
validIgdMutex
);
// Clear internal IGD (nat pmp only supports one).
if
(
pmpIGD_
)
{
if
(
pmpIGD_
)
{
// Clear internal IGD (nat pmp only supports one).
std
::
lock_guard
<
std
::
mutex
>
lk
(
pmpMutex_
);
std
::
lock_guard
<
std
::
mutex
>
lk
(
pmpMutex_
);
pmpIGD_
->
renewal_
=
clock
::
now
();
pmpIGD_
->
renewal_
=
clock
::
now
();
pmpIGD_
.
reset
();
}
}
pmpCv_
.
notify_all
();
pmpCv_
.
notify_all
();
}
}
...
@@ -250,14 +241,11 @@ NatPmp::searchForIGD(const std::shared_ptr<PMPIGD>& pmp_igd, natpmp_t& natpmp)
...
@@ -250,14 +241,11 @@ NatPmp::searchForIGD(const std::shared_ptr<PMPIGD>& pmp_igd, natpmp_t& natpmp)
if
(
not
pmpIGD_
)
{
if
(
not
pmpIGD_
)
{
JAMI_DBG
(
"NAT-PMP: Found device with external IP %s"
,
pmp_igd
->
publicIp_
.
toString
().
c_str
());
JAMI_DBG
(
"NAT-PMP: Found device with external IP %s"
,
pmp_igd
->
publicIp_
.
toString
().
c_str
());
{
{
// Store public Ip address.
std
::
string
publicIpStr
(
std
::
move
(
pmp_igd
.
get
()
->
publicIp_
.
toString
()));
// Add the igd to the upnp context class list.
// Add the igd to the upnp context class list.
if
(
updateIgdListCb_
(
this
,
std
::
move
(
pmp_igd
.
get
()),
std
::
move
(
pmp_igd
.
get
()
->
publicIp_
)
,
true
))
{
if
(
updateIgdListCb_
(
this
,
pmp_igd
.
get
(),
pmp_igd
->
publicIp_
,
true
))
{
JAMI_DBG
(
"NAT-PMP: IGD with public IP %s was added to the list"
,
p
ublicIpStr
.
c_str
());
JAMI_DBG
(
"NAT-PMP: IGD with public IP %s was added to the list"
,
p
mp_igd
->
publicIp_
.
toString
()
.
c_str
());
}
else
{
}
else
{
JAMI_DBG
(
"NAT-PMP: IGD with public IP %s is already in the list"
,
p
ublicIpStr
.
c_str
());
JAMI_DBG
(
"NAT-PMP: IGD with public IP %s is already in the list"
,
p
mp_igd
->
publicIp_
.
toString
()
.
c_str
());
}
}
// Keep IGD internally.
// Keep IGD internally.
...
...
src/upnp/protocol/natpmp/nat_pmp.h
View file @
be8241d0
...
@@ -55,7 +55,7 @@ public:
...
@@ -55,7 +55,7 @@ public:
Type
getType
()
const
override
{
return
Type
::
NAT_PMP
;
}
Type
getType
()
const
override
{
return
Type
::
NAT_PMP
;
}
// Notifies a change in network.
// Notifies a change in network.
void
c
onnectivityChanged
()
override
;
void
c
learIGDs
()
override
;
// Renew pmp_igd.
// Renew pmp_igd.
void
searchForIGD
()
override
;
void
searchForIGD
()
override
;
...
...
src/upnp/protocol/natpmp/pmp_igd.h
View file @
be8241d0
...
@@ -30,7 +30,6 @@
...
@@ -30,7 +30,6 @@
#include "noncopyable.h"
#include "noncopyable.h"
#include "ip_utils.h"
#include "ip_utils.h"
#include "string_utils.h"
#include <map>
#include <map>
#include <atomic>
#include <atomic>
...
...
src/upnp/protocol/pupnp/pupnp.cpp
View file @
be8241d0
...
@@ -142,7 +142,7 @@ PUPnP::~PUPnP()
...
@@ -142,7 +142,7 @@ PUPnP::~PUPnP()
}
}
void
void
PUPnP
::
c
onnectivityChanged
()
PUPnP
::
c
learIGDs
()
{
{
// Lock internal IGD list.
// Lock internal IGD list.
std
::
lock_guard
<
std
::
mutex
>
lk
(
validIgdMutex
);
std
::
lock_guard
<
std
::
mutex
>
lk
(
validIgdMutex
);
...
@@ -197,8 +197,7 @@ PUPnP::addMapping(IGD* igd, uint16_t port_external, uint16_t port_internal, Port
...
@@ -197,8 +197,7 @@ PUPnP::addMapping(IGD* igd, uint16_t port_external, uint16_t port_internal, Port
}
}
}
}
auto
pupnp_igd
=
dynamic_cast
<
const
UPnPIGD
*>
(
igd
);
if
(
auto
pupnp_igd
=
dynamic_cast
<
const
UPnPIGD
*>
(
igd
))
{
if
(
pupnp_igd
)
{
if
(
actionAddPortMapping
(
*
pupnp_igd
,
mapping
,
upnp_error
))
{
if
(
actionAddPortMapping
(
*
pupnp_igd
,
mapping
,
upnp_error
))
{
JAMI_WARN
(
"PUPnP: Opened port %s"
,
mapping
.
toString
().
c_str
());
JAMI_WARN
(
"PUPnP: Opened port %s"
,
mapping
.
toString
().
c_str
());
globalMappings
->
emplace
(
port_external
,
GlobalMapping
{
mapping
});
globalMappings
->
emplace
(
port_external
,
GlobalMapping
{
mapping
});
...
@@ -329,11 +328,12 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
...
@@ -329,11 +328,12 @@ PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
}
}
// Check if this device ID is already in the list.
// Check if this device ID is already in the list.
std
::
string
cpDeviceId
=
UpnpDiscovery_get_DeviceID_cstr
(
d_event
);
std
::
lock_guard
<
std
::
mutex
>
lk
(
validIgdMutex
);
std
::
lock_guard
<
std
::
mutex
>
lk
(
validIgdMutex
);
if
(
cpDeviceList_
.
count
(
std
::
string
(
UpnpDiscovery_get_DeviceID_cstr
(
d_event
))
)
>
0
)
{
if
(
cpDeviceList_
.
count
(
cpDeviceId
)
>
0
)
{
break
;
break
;
}
}
cpDeviceList_
.
emplace
(
std
::
pair
<
std
::
string
,
std
::
string
>
(
std
::
string
(
UpnpDiscovery_get_DeviceID_cstr
(
d_event
)),
""
)
);
cpDeviceList_
.
emplace
(
std
::
move
(
cpDeviceId
),
std
::
string
{}
);
/*
/*
* NOTE: This thing will block until success for the system socket timeout
* NOTE: This thing will block until success for the system socket timeout
...
...
src/upnp/protocol/pupnp/pupnp.h
View file @
be8241d0
...
@@ -75,7 +75,7 @@ public:
...
@@ -75,7 +75,7 @@ public:
Type
getType
()
const
override
{
return
Type
::
PUPNP
;
}
Type
getType
()
const
override
{
return
Type
::
PUPNP
;
}
// Notifies a change in network.
// Notifies a change in network.
void
c
onnectivityChanged
()
override
;
void
c
learIGDs
()
override
;
// Sends out async search for IGD.
// Sends out async search for IGD.
void
searchForIGD
()
override
;
void
searchForIGD
()
override
;
...
...
src/upnp/protocol/upnp_protocol.h
View file @
be8241d0
...
@@ -72,8 +72,8 @@ public:
...
@@ -72,8 +72,8 @@ public:
// Allows each protocol to return it's type.
// Allows each protocol to return it's type.
virtual
Type
getType
()
const
=
0
;
virtual
Type
getType
()
const
=
0
;
//
Signals a change in the network
.
//
Clear all known IGDs
.
virtual
void
c
onnectivityChanged
()
=
0
;
virtual
void
c
learIGDs
()
=
0
;
// Search for IGD.
// Search for IGD.
virtual
void
searchForIGD
()
=
0
;
virtual
void
searchForIGD
()
=
0
;
...
...
src/upnp/upnp_context.cpp
View file @
be8241d0
...
@@ -67,26 +67,27 @@ UPnPContext::~UPnPContext()
...
@@ -67,26 +67,27 @@ UPnPContext::~UPnPContext()
void
void
UPnPContext
::
connectivityChanged
()
UPnPContext
::
connectivityChanged
()
{
{
{
std
::
lock_guard
<
std
::
mutex
>
lock
(
igdListMutex_
);
for
(
auto
const
&
protocol
:
protocolList_
)
protocol
->
clearIGDs
();
if
(
not
igdList_
.
empty
())
{
if
(
not
igdList_
.
empty
())
{
// Clear main IGD list.
// Clear main IGD list.
std
::
lock_guard
<
std
::
mutex
>
lock
(
igdListMutex_
);
igdList_
.
clear
();
igdList_
.
clear
();
for
(
const
auto
&
listener
:
igdListeners_
)
{
for
(
const
auto
&
item
:
igdListeners_
)
{
listener
.
second
();
item
.
second
();
}
}
}
}
for
(
auto
const
&
item
:
protocolList_
)
{
item
->
connectivityChanged
();
item
->
searchForIGD
();
}
}
for
(
auto
const
&
protocol
:
protocolList_
)
protocol
->
searchForIGD
();
}
}
bool
bool
UPnPContext
::
hasValidIGD
()
UPnPContext
::
hasValidIGD
()
{
{
std
::
lock_guard
<
std
::
mutex
>
lock
(
igdListMutex_
);
return
not
igdList_
.
empty
();
return
not
igdList_
.
empty
();
}
}
...
@@ -153,7 +154,6 @@ UPnPContext::addMapping(uint16_t port_desired, uint16_t port_local, PortType typ
...
@@ -153,7 +154,6 @@ UPnPContext::addMapping(uint16_t port_desired, uint16_t port_local, PortType typ
// If we want a unique port, we must make sure the client isn't already using the port.
// If we want a unique port, we must make sure the client isn't already using the port.
if
(
unique
)
{
if
(
unique
)
{
bool
unique_found
=
false
;
bool
unique_found
=
false
;
// Keep generating random ports until we find a unique one.
// Keep generating random ports until we find a unique one.
...
@@ -256,24 +256,8 @@ UPnPContext::getExternalIP() const
...
@@ -256,24 +256,8 @@ UPnPContext::getExternalIP() const
}
}
bool
bool
UPnPContext
::
isIgdInList
(
IGD
*
igd
)
UPnPContext
::
isIgdInList
(
const
IpAddr
&
publicIpAddr
)
{
std
::
lock_guard
<
std
::
mutex
>
igdListLock
(
igdListMutex_
);
for
(
auto
const
&
item
:
igdList_
)
{
if
(
item
.
second
->
publicIp_
==
igd
->
publicIp_
)
{
return
true
;
}
}
return
false
;
}
bool
UPnPContext
::
isIgdInList
(
IpAddr
publicIpAddr
)
{
{
std
::
lock_guard
<
std
::
mutex
>
igdListLock
(
igdListMutex_
);
for
(
auto
const
&
item
:
igdList_
)
{
for
(
auto
const
&
item
:
igdList_
)
{
if
(
item
.
second
->
publicIp_
)
{
if
(
item
.
second
->
publicIp_
)
{
if
(
item
.
second
->
publicIp_
==
publicIpAddr
)
{
if
(
item
.
second
->
publicIp_
==
publicIpAddr
)
{
...
@@ -281,7 +265,6 @@ UPnPContext::isIgdInList(IpAddr publicIpAddr)
...
@@ -281,7 +265,6 @@ UPnPContext::isIgdInList(IpAddr publicIpAddr)
}
}
}
}
}
}
return
false
;
return
false
;
}
}
...
@@ -302,6 +285,7 @@ UPnPContext::getIgdProtocol(IGD* igd)
...
@@ -302,6 +285,7 @@ UPnPContext::getIgdProtocol(IGD* igd)
bool
bool
UPnPContext
::
igdListChanged
(
UPnPProtocol
*
protocol
,
IGD
*
igd
,
IpAddr
publicIpAddr
,
bool
added
)
UPnPContext
::
igdListChanged
(
UPnPProtocol
*
protocol
,
IGD
*
igd
,
IpAddr
publicIpAddr
,
bool
added
)
{
{
std
::
lock_guard
<
std
::
mutex
>
lock
(
igdListMutex_
);
if
(
added
)
{
if
(
added
)
{
return
addIgdToList
(
protocol
,
igd
);
return
addIgdToList
(
protocol
,
igd
);
}
else
{
}
else
{
...
@@ -317,15 +301,14 @@ bool
...
@@ -317,15 +301,14 @@ bool
UPnPContext
::
addIgdToList
(
UPnPProtocol
*
protocol
,
IGD
*
igd
)
UPnPContext
::
addIgdToList
(
UPnPProtocol
*
protocol
,
IGD
*
igd
)
{
{
// Check if IGD has a valid public IP.
// Check if IGD has a valid public IP.
if
(
not
igd
->
publicIp_
.
isValid
(
igd
->
publicIp_
.
toString
().
c_str
())
)
{
if
(
not
igd
->
publicIp_
)
{
JAMI_WARN
(
"UPnPContext: IGD trying to be added has invalid public IpAddress"
);
JAMI_WARN
(
"UPnPContext: IGD trying to be added has invalid public IpAddress"
);
return
false
;
return
false
;
}
}
if
(
isIgdInList
(
igd
))
{
if
(
isIgdInList
(
igd
->
publicIp_
))
{
// If the protocol of the IGD that is already in the list isn't NatPmp, then swap.
// If the protocol of the IGD that is already in the list isn't NatPmp, then swap.
if
(
getIgdProtocol
(
igd
)
!=
UPnPProtocol
::
Type
::
NAT_PMP
and
if
(
getIgdProtocol
(
igd
)
!=
UPnPProtocol
::
Type
::
NAT_PMP
and
protocol
->
getType
()
==
UPnPProtocol
::
Type
::
NAT_PMP
)
{
protocol
->
getType
()
==
UPnPProtocol
::
Type
::
NAT_PMP
)
{
JAMI_WARN
(
"UPnPContext: Attempting to swap IGD UPnP protocol"
);
JAMI_WARN
(
"UPnPContext: Attempting to swap IGD UPnP protocol"
);
if
(
!
removeIgdFromList
(
igd
))
{
if
(
!
removeIgdFromList
(
igd
))
{
JAMI_WARN
(
"UPnPContext: Failed to swap IGD UPnP protocol"
);
JAMI_WARN
(
"UPnPContext: Failed to swap IGD UPnP protocol"
);
...
@@ -336,8 +319,7 @@ UPnPContext::addIgdToList(UPnPProtocol* protocol, IGD* igd)
...
@@ -336,8 +319,7 @@ UPnPContext::addIgdToList(UPnPProtocol* protocol, IGD* igd)
}
}
}
}
IpAddr
publicIp
=
igd
->
publicIp_
;
igdList_
.
emplace_back
(
protocol
,
igd
);
igdList_
.
emplace_back
(
std
::
make_pair
(
protocol
,
igd
));
for
(
const
auto
&
item
:
igdListeners_
)
{
for
(
const
auto
&
item
:
igdListeners_
)
{
item
.
second
();
item
.
second
();
...
@@ -349,9 +331,7 @@ UPnPContext::addIgdToList(UPnPProtocol* protocol, IGD* igd)
...
@@ -349,9 +331,7 @@ UPnPContext::addIgdToList(UPnPProtocol* protocol, IGD* igd)
bool
bool
UPnPContext
::
removeIgdFromList
(
IGD
*
igd
)
UPnPContext
::
removeIgdFromList
(
IGD
*
igd
)
{
{
std
::
lock_guard
<
std
::
mutex
>
igdListLock
(
igdListMutex_
);
auto
it
=
igdList_
.
begin
();
std
::
list
<
std
::
pair
<
UPnPProtocol
*
,
IGD
*>>::
iterator
it
=
igdList_
.
begin
();
while
(
it
!=
igdList_
.
end
())
{
while
(
it
!=
igdList_
.
end
())
{
if
(
it
->
second
->
publicIp_
==
igd
->
publicIp_
)
{
if
(
it
->
second
->
publicIp_
==
igd
->
publicIp_
)
{
JAMI_WARN
(
"UPnPContext: IGD with public IP %s was removed from the list"
,
it
->
second
->
publicIp_
.
toString
().
c_str
());
JAMI_WARN
(
"UPnPContext: IGD with public IP %s was removed from the list"
,
it
->
second
->
publicIp_
.
toString
().
c_str
());
...
@@ -368,9 +348,7 @@ UPnPContext::removeIgdFromList(IGD* igd)
...
@@ -368,9 +348,7 @@ UPnPContext::removeIgdFromList(IGD* igd)
bool
bool
UPnPContext
::
removeIgdFromList
(
IpAddr
publicIpAddr
)
UPnPContext
::
removeIgdFromList
(
IpAddr
publicIpAddr
)
{
{
std
::
lock_guard
<
std
::
mutex
>
igdListLock
(
igdListMutex_
);
auto
it
=
igdList_
.
begin
();
std
::
list
<
std
::
pair
<
UPnPProtocol
*
,
IGD
*>>::
iterator
it
=
igdList_
.
begin
();
while
(
it
!=
igdList_
.
end
())
{
while
(
it
!=
igdList_
.
end
())
{
if
(
it
->
second
->
publicIp_
==
publicIpAddr
)
{
if
(
it
->
second
->
publicIp_
==
publicIpAddr
)
{
JAMI_WARN
(
"UPnPContext: IGD with public IP %s was removed from the list"
,
it
->
second
->
publicIp_
.
toString
().
c_str
());
JAMI_WARN
(
"UPnPContext: IGD with public IP %s was removed from the list"
,
it
->
second
->
publicIp_
.
toString
().
c_str
());
...
@@ -382,7 +360,6 @@ UPnPContext::removeIgdFromList(IpAddr publicIpAddr)
...
@@ -382,7 +360,6 @@ UPnPContext::removeIgdFromList(IpAddr publicIpAddr)
}
}
return
false
;
return
false
;
}
}
}}
// namespace jami::upnp
}}
// namespace jami::upnp
src/upnp/upnp_context.h
View file @
be8241d0
...
@@ -95,6 +95,16 @@ public:
...
@@ -95,6 +95,16 @@ public:
// Inform the UPnP context that the network status has changed. This clears the list of known
// Inform the UPnP context that the network status has changed. This clears the list of known
void
connectivityChanged
();
void
connectivityChanged
();
private:
// Checks if the IGD is in the list by checking the IGD's public Ip.
bool
isIgdInList
(
const
IpAddr
&
publicIpAddr
);
// Returns the protocol of the IGD.
UPnPProtocol
::
Type
getIgdProtocol
(
IGD
*
igd
);
// Returns a random port that is not yet used by the daemon for UPnP.
uint16_t
chooseRandomPort
(
const
IGD
&
igd
,
PortType
type
);
// Tries to add or remove IGD to the list via callback.
// Tries to add or remove IGD to the list via callback.
bool
igdListChanged
(
UPnPProtocol
*
protocol
,
IGD
*
igd
,
const
IpAddr
publicIpAddr
,
bool
added
);
bool
igdListChanged
(
UPnPProtocol
*
protocol
,
IGD
*
igd
,
const
IpAddr
publicIpAddr
,
bool
added
);
...
@@ -110,19 +120,6 @@ public:
...
@@ -110,19 +120,6 @@ public:
// Tries to add mapping. Assumes mutex is already locked.
// Tries to add mapping. Assumes mutex is already locked.
Mapping
addMapping
(
IGD
*
igd
,
uint16_t
port_external
,
uint16_t
port_internal
,
PortType
type
,
UPnPProtocol
::
UpnpError
&
upnp_error
);
Mapping
addMapping
(
IGD
*
igd
,
uint16_t
port_external
,
uint16_t
port_internal
,
PortType
type
,
UPnPProtocol
::
UpnpError
&
upnp_error
);
private:
// Checks if the IGD is in the list by checking the IGD itself.
bool
isIgdInList
(
IGD
*
igd
);
// Checks if the IGD is in the list by checking the IGD's public Ip.
bool
isIgdInList
(
IpAddr
publicIpAddr
);
// Returns the protocol of the IGD.
UPnPProtocol
::
Type
getIgdProtocol
(
IGD
*
igd
);
// Returns a random port that is not yet used by the daemon for UPnP.
uint16_t
chooseRandomPort
(
const
IGD
&
igd
,
PortType
type
);
public:
public:
constexpr
static
unsigned
MAX_RETRIES
=
20
;
constexpr
static
unsigned
MAX_RETRIES
=
20
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment