6 * Portable Windows Library
8 * Copyright (c) 2003 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Contributor(s): ______________________________________.
27 * Revision 1.13 2004/03/14 05:47:52 rjongbloed
28 * Fixed incorrect detection of symmetric NAT (eg Linux masquerading) and also
29 * some NAT systems which are partially blocked due to firewall rules.
31 * Revision 1.12 2004/02/24 11:15:48 rjongbloed
32 * Added function to get external router address, also did a bunch of documentation.
34 * Revision 1.11 2004/02/17 11:11:05 rjongbloed
35 * Added missing #pragma pack() to turn off byte alignment for the last class, thanks Ted Szoczei
37 * Revision 1.10 2004/01/17 17:54:02 rjongbloed
38 * Added function to get server name from STUN client.
40 * Revision 1.9 2003/10/08 22:00:18 dereksmithies
41 * Fix unsigned/signed warning message. Thanks to Craig Southeren.
43 * Revision 1.8 2003/10/05 00:56:25 rjongbloed
44 * Rewrite of STUN to not to use imported code with undesirable license.
46 * Revision 1.5 2003/02/05 06:26:49 robertj
47 * More work in making the STUN usable for Symmetric NAT systems.
49 * Revision 1.4 2003/02/04 07:02:17 robertj
50 * Added ip/port version of constructor.
51 * Removed creating sockets for Open type.
53 * Revision 1.3 2003/02/04 05:55:04 craigs
54 * Added socket pair function
56 * Revision 1.2 2003/02/04 05:06:24 craigs
59 * Revision 1.1 2003/02/04 03:31:04 robertj
65 #pragma implementation "pstun.h"
70 #include <ptclib/pstun.h>
71 #include <ptclib/random.h>
74 // Sample server is at larry.gloo.net
77 ///////////////////////////////////////////////////////////////////////
79 PSTUNClient::PSTUNClient(const PString
& server
,
80 WORD portBase
, WORD portMax
,
81 WORD portPairBase
, WORD portPairMax
)
83 cachedExternalAddress(0),
84 timeAddressObtained(0)
86 serverPort
= DefaultPort
;
90 SetPortRanges(portBase
, portMax
, portPairBase
, portPairMax
);
94 PSTUNClient::PSTUNClient(const PIPSocket::Address
& address
, WORD port
,
95 WORD portBase
, WORD portMax
,
96 WORD portPairBase
, WORD portPairMax
)
97 : serverAddress(address
),
99 cachedExternalAddress(0),
100 timeAddressObtained(0)
103 SetPortRanges(portBase
, portMax
, portPairBase
, portPairMax
);
107 void PSTUNClient::Construct()
109 singlePortInfo
.basePort
= 0;
110 singlePortInfo
.maxPort
= 0;
111 singlePortInfo
.currentPort
= 0;
112 pairedPortInfo
.basePort
= 0;
113 pairedPortInfo
.maxPort
= 0;
114 pairedPortInfo
.currentPort
= 0;
115 numSocketsForPairing
= 3;
116 natType
= UnknownNat
;
120 PString
PSTUNClient::GetServer() const
123 str
<< serverAddress
<< ':' << serverPort
;
128 BOOL
PSTUNClient::SetServer(const PString
& server
)
130 PINDEX colon
= server
.Find(':');
131 if (colon
== P_MAX_INDEX
) {
132 if (!PIPSocket::GetHostAddress(server
, serverAddress
))
136 if (!PIPSocket::GetHostAddress(server
.Left(colon
), serverAddress
))
138 serverPort
= PIPSocket::GetPortByService("udp", server
.Mid(colon
+1));
141 return serverAddress
.IsValid() && serverPort
!= 0;
145 BOOL
PSTUNClient::SetServer(const PIPSocket::Address
& address
, WORD port
)
147 serverAddress
= address
;
149 return serverAddress
.IsValid() && serverPort
!= 0;
153 void PSTUNClient::SetPortRanges(WORD portBase
, WORD portMax
,
154 WORD portPairBase
, WORD portPairMax
)
156 singlePortInfo
.mutex
.Wait();
158 singlePortInfo
.basePort
= portBase
;
160 singlePortInfo
.maxPort
= 0;
161 else if (portMax
== 0)
162 singlePortInfo
.maxPort
= (WORD
)(singlePortInfo
.basePort
+99);
163 else if (portMax
< portBase
)
164 singlePortInfo
.maxPort
= portBase
;
166 singlePortInfo
.maxPort
= portMax
;
168 singlePortInfo
.currentPort
= singlePortInfo
.basePort
;
170 singlePortInfo
.mutex
.Signal();
172 pairedPortInfo
.mutex
.Wait();
174 pairedPortInfo
.basePort
= (WORD
)((portPairBase
+1)&0xfffe);
175 if (portPairBase
== 0) {
176 pairedPortInfo
.basePort
= 0;
177 pairedPortInfo
.maxPort
= 0;
179 else if (portPairMax
== 0)
180 pairedPortInfo
.maxPort
= (WORD
)(pairedPortInfo
.basePort
+99);
181 else if (portPairMax
< portPairBase
)
182 pairedPortInfo
.maxPort
= portPairBase
;
184 pairedPortInfo
.maxPort
= portPairMax
;
186 pairedPortInfo
.currentPort
= pairedPortInfo
.basePort
;
188 pairedPortInfo
.mutex
.Signal();
194 struct PSTUNAttribute
197 MAPPED_ADDRESS
= 0x0001,
198 RESPONSE_ADDRESS
= 0x0002,
199 CHANGE_REQUEST
= 0x0003,
200 SOURCE_ADDRESS
= 0x0004,
201 CHANGED_ADDRESS
= 0x0005,
204 MESSAGE_INTEGRITY
= 0x0008,
206 UNKNOWN_ATTRIBUTES
= 0x000a,
207 REFLECTED_FROM
= 0x000b,
213 PSTUNAttribute
* GetNext() const { return (PSTUNAttribute
*)(((const BYTE
*)this)+length
+4); }
216 class PSTUNAddressAttribute
: public PSTUNAttribute
224 PIPSocket::Address
GetIP() const { return PIPSocket::Address(4, ip
); }
227 enum { SizeofAddressAttribute
= sizeof(BYTE
)+sizeof(BYTE
)+sizeof(WORD
)+sizeof(PIPSocket::Address
) };
228 void InitAddrAttr(Types newType
)
230 type
= (WORD
)newType
;
231 length
= SizeofAddressAttribute
;
235 bool IsValidAddrAttr(Types checkType
) const
237 return type
== checkType
&& length
== SizeofAddressAttribute
;
241 class PSTUNMappedAddress
: public PSTUNAddressAttribute
244 void Initialise() { InitAddrAttr(MAPPED_ADDRESS
); }
245 bool IsValid() const { return IsValidAddrAttr(MAPPED_ADDRESS
); }
248 class PSTUNChangedAddress
: public PSTUNAddressAttribute
251 void Initialise() { InitAddrAttr(CHANGED_ADDRESS
); }
252 bool IsValid() const { return IsValidAddrAttr(CHANGED_ADDRESS
); }
255 class PSTUNChangeRequest
: public PSTUNAttribute
260 PSTUNChangeRequest() { }
262 PSTUNChangeRequest(bool changeIP
, bool changePort
)
265 SetChangeIP(changeIP
);
266 SetChangePort(changePort
);
271 type
= CHANGE_REQUEST
;
272 length
= sizeof(flags
);
273 memset(flags
, 0, sizeof(flags
));
275 bool IsValid() const { return type
== CHANGE_REQUEST
&& length
== sizeof(flags
); }
277 bool GetChangeIP() const { return (flags
[3]&4) != 0; }
278 void SetChangeIP(bool on
) { if (on
) flags
[3] |= 4; else flags
[3] &= ~4; }
280 bool GetChangePort() const { return (flags
[3]&2) != 0; }
281 void SetChangePort(bool on
) { if (on
) flags
[3] |= 2; else flags
[3] &= ~2; }
284 class PSTUNMessageIntegrity
: public PSTUNAttribute
291 type
= MESSAGE_INTEGRITY
;
292 length
= sizeof(hmac
);
293 memset(hmac
, 0, sizeof(hmac
));
295 bool IsValid() const { return type
== MESSAGE_INTEGRITY
&& length
== sizeof(hmac
); }
298 struct PSTUNMessageHeader
302 BYTE transactionId
[16];
309 class PSTUNMessage
: public PBYTEArray
313 BindingRequest
= 0x0001,
314 BindingResponse
= 0x0101,
315 BindingError
= 0x0111,
317 SharedSecretRequest
= 0x0002,
318 SharedSecretResponse
= 0x0102,
319 SharedSecretError
= 0x0112,
325 PSTUNMessage(MsgType newType
, const BYTE
* id
= NULL
)
326 : PBYTEArray(sizeof(PSTUNMessageHeader
))
328 SetType(newType
, id
);
331 void SetType(MsgType newType
, const BYTE
* id
= NULL
)
333 SetMinSize(sizeof(PSTUNMessageHeader
));
334 PSTUNMessageHeader
* hdr
= (PSTUNMessageHeader
*)theArray
;
335 hdr
->msgType
= (WORD
)newType
;
336 for (PINDEX i
= 0; i
< ((PINDEX
)sizeof(hdr
->transactionId
)); i
++)
337 hdr
->transactionId
[i
] = id
!= NULL
? id
[i
] : (BYTE
)PRandom::Number();
340 const PSTUNMessageHeader
* operator->() const { return (PSTUNMessageHeader
*)theArray
; }
342 PSTUNAttribute
* GetFirstAttribute() { return (PSTUNAttribute
*)(theArray
+sizeof(PSTUNMessageHeader
)); }
346 int length
= ((PSTUNMessageHeader
*)theArray
)->msgLength
;
347 PSTUNAttribute
* attrib
= GetFirstAttribute();
349 length
-= attrib
->length
+ 4;
350 attrib
= attrib
->GetNext();
353 return length
== 0; // Exactly correct length
356 void AddAttribute(const PSTUNAttribute
& attribute
)
358 PSTUNMessageHeader
* hdr
= (PSTUNMessageHeader
*)theArray
;
359 int oldLength
= hdr
->msgLength
;
360 int attrSize
= attribute
.length
+ 4;
361 int newLength
= oldLength
+ attrSize
;
362 hdr
->msgLength
= (WORD
)newLength
;
363 // hdr pointer may be invalidated by next statement
364 SetMinSize(newLength
+sizeof(PSTUNMessageHeader
));
365 memcpy(theArray
+sizeof(PSTUNMessageHeader
)+oldLength
, &attribute
, attrSize
);
368 void SetAttribute(const PSTUNAttribute
& attribute
)
370 int length
= ((PSTUNMessageHeader
*)theArray
)->msgLength
;
371 PSTUNAttribute
* attrib
= GetFirstAttribute();
373 if (attrib
->type
== attribute
.type
) {
374 if (attrib
->length
== attribute
.length
)
382 length
-= attrib
->length
+ 4;
383 attrib
= attrib
->GetNext();
386 AddAttribute(attribute
);
389 PSTUNAttribute
* FindAttribute(PSTUNAttribute::Types type
)
391 int length
= ((PSTUNMessageHeader
*)theArray
)->msgLength
;
392 PSTUNAttribute
* attrib
= GetFirstAttribute();
394 if (attrib
->type
== type
)
397 length
-= attrib
->length
+ 4;
398 attrib
= attrib
->GetNext();
404 bool Read(PUDPSocket
& socket
)
406 if (!socket
.Read(GetPointer(1000), 1000))
408 SetSize(socket
.GetLastReadCount());
412 bool Write(PUDPSocket
& socket
) const
414 return socket
.Write(theArray
, ((PSTUNMessageHeader
*)theArray
)->msgLength
+sizeof(PSTUNMessageHeader
)) != FALSE
;
417 bool Poll(PUDPSocket
& socket
, const PSTUNMessage
& request
)
419 for (int retry
= 0; retry
< 3; retry
++) {
420 if (!request
.Write(socket
))
423 if (Read(socket
) && Validate() &&
424 memcmp(request
->transactionId
, (*this)->transactionId
, sizeof(request
->transactionId
)) == 0)
433 bool PSTUNClient::OpenSocket(PUDPSocket
& socket
, PortInfo
& portInfo
) const
435 PWaitAndSignal
mutex(portInfo
.mutex
);
437 WORD startPort
= portInfo
.currentPort
;
440 portInfo
.currentPort
++;
441 if (portInfo
.currentPort
> portInfo
.maxPort
)
442 portInfo
.currentPort
= portInfo
.basePort
;
444 if (socket
.Listen(1, portInfo
.currentPort
)) {
445 socket
.SetSendAddress(serverAddress
, serverPort
);
446 socket
.SetReadTimeout(500);
450 } while (portInfo
.currentPort
!= startPort
);
452 PTRACE(1, "STUN\tFailed to bind to local UDP port in range "
453 << portInfo
.currentPort
<< '-' << portInfo
.maxPort
);
458 PSTUNClient::NatTypes
PSTUNClient::GetNatType(BOOL force
)
460 if (!force
&& natType
!= UnknownNat
)
464 if (!OpenSocket(socket
, singlePortInfo
))
465 return natType
= UnknownNat
;
469 /* test I - the client sends a STUN Binding Request to a server, without
470 any flags set in the CHANGE-REQUEST attribute, and without the
471 RESPONSE-ADDRESS attribute. This causes the server to send the response
472 back to the address and port that the request came from. */
473 PSTUNMessage
requestI(PSTUNMessage::BindingRequest
);
474 requestI
.AddAttribute(PSTUNChangeRequest(false, false));
475 PSTUNMessage responseI
;
476 if (!responseI
.Poll(socket
, requestI
)) {
477 if (socket
.GetErrorCode(PChannel::LastWriteError
) != PChannel::NoError
) {
478 PTRACE(1, "STUN\tError writing to server " << serverAddress
<< ':' << serverPort
<< " - " << socket
.GetErrorText(PChannel::LastWriteError
));
479 return natType
= UnknownNat
; // No response usually means blocked
482 PTRACE(3, "STUN\tNo response to server " << serverAddress
<< ':' << serverPort
<< " - " << socket
.GetErrorText(PChannel::LastReadError
));
483 return natType
= BlockedNat
; // No response usually means blocked
486 PSTUNMappedAddress
* mappedAddress
= (PSTUNMappedAddress
*)responseI
.FindAttribute(PSTUNAttribute::MAPPED_ADDRESS
);
487 if (mappedAddress
== NULL
) {
488 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress
<< ':' << serverPort
);
489 return natType
= UnknownNat
; // Protocol error
492 PIPSocket::Address mappedAddressI
= mappedAddress
->GetIP();
493 WORD mappedPortI
= mappedAddress
->port
;
494 bool notNAT
= socket
.GetPort() == mappedPortI
&& PIPSocket::IsLocalHost(mappedAddressI
);
496 /* Test II - the client sends a Binding Request with both the "change IP"
497 and "change port" flags from the CHANGE-REQUEST attribute set. */
498 PSTUNMessage
requestII(PSTUNMessage::BindingRequest
);
499 requestII
.AddAttribute(PSTUNChangeRequest(true, true));
500 PSTUNMessage responseII
;
501 bool testII
= responseII
.Poll(socket
, requestII
);
504 // Is not NAT or symmetric firewall
505 return natType
= (testII
? OpenNat
: SymmetricFirewall
);
509 return natType
= ConeNat
;
511 PSTUNChangedAddress
* changedAddress
= (PSTUNChangedAddress
*)responseI
.FindAttribute(PSTUNAttribute::CHANGED_ADDRESS
);
512 if (changedAddress
== NULL
)
513 return natType
= UnknownNat
; // Protocol error
515 // Send test I to another server, to see if restricted or symmetric
516 PIPSocket::Address secondaryServer
= changedAddress
->GetIP();
517 WORD secondaryPort
= changedAddress
->port
;
518 socket
.SetSendAddress(secondaryServer
, secondaryPort
);
519 PSTUNMessage
requestI2(PSTUNMessage::BindingRequest
);
520 requestI2
.AddAttribute(PSTUNChangeRequest(false, false));
521 PSTUNMessage responseI2
;
522 if (!responseI2
.Poll(socket
, requestI2
)) {
523 PTRACE(2, "STUN\tPoll of secondary server " << secondaryServer
<< ':' << secondaryPort
524 << " failed, NAT partially blocked by firwall rules.");
525 return natType
= PartialBlockedNat
;
528 mappedAddress
= (PSTUNMappedAddress
*)responseI2
.FindAttribute(PSTUNAttribute::MAPPED_ADDRESS
);
529 if (mappedAddress
== NULL
) {
530 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress
<< ':' << serverPort
);
531 return UnknownNat
; // Protocol error
534 if (mappedAddress
->port
!= mappedPortI
|| mappedAddress
->GetIP() != mappedAddressI
)
535 return natType
= SymmetricNat
;
537 socket
.SetSendAddress(serverAddress
, serverPort
);
538 PSTUNMessage
requestIII(PSTUNMessage::BindingRequest
);
539 requestIII
.SetAttribute(PSTUNChangeRequest(false, true));
540 PSTUNMessage responseIII
;
541 return natType
= (responseIII
.Poll(socket
, requestIII
) ? RestrictedNat
: PortRestrictedNat
);
545 PString
PSTUNClient::GetNatTypeName(BOOL force
)
547 static const char * const Names
[NumNatTypes
] = {
552 "Port Restricted NAT",
554 "Symmetric Firewall",
559 return Names
[GetNatType(force
)];
563 BOOL
PSTUNClient::GetExternalAddress(PIPSocket::Address
& externalAddress
,
564 const PTimeInterval
& maxAge
)
566 if (cachedExternalAddress
.IsValid() && (PTime() - timeAddressObtained
> maxAge
)) {
567 externalAddress
= cachedExternalAddress
;
571 externalAddress
= 0; // Set to invalid address
574 if (!OpenSocket(socket
, singlePortInfo
))
577 PSTUNMessage
request(PSTUNMessage::BindingRequest
);
578 request
.AddAttribute(PSTUNChangeRequest(false, false));
579 PSTUNMessage response
;
580 if (!response
.Poll(socket
, request
))
582 PTRACE(1, "STUN\tServer " << serverAddress
<< ':' << serverPort
<< " unexpectedly went offline.");
586 PSTUNMappedAddress
* mappedAddress
= (PSTUNMappedAddress
*)response
.FindAttribute(PSTUNAttribute::MAPPED_ADDRESS
);
587 if (mappedAddress
== NULL
)
589 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress
<< ':' << serverPort
);
594 externalAddress
= cachedExternalAddress
= mappedAddress
->GetIP();
595 timeAddressObtained
= PTime();
600 BOOL
PSTUNClient::CreateSocket(PUDPSocket
* & socket
)
604 switch (GetNatType(FALSE
)) {
607 case PortRestrictedNat
:
611 if (singlePortInfo
.basePort
== 0 || singlePortInfo
.basePort
> singlePortInfo
.maxPort
)
613 PTRACE(1, "STUN\tInvalid local UDP port range "
614 << singlePortInfo
.currentPort
<< '-' << singlePortInfo
.maxPort
);
619 default : // UnknownNet, SymmetricFirewall, BlockedNat
620 PTRACE(1, "STUN\tCannot create socket using NAT type " << GetNatTypeName());
624 PSTUNUDPSocket
* stunSocket
= new PSTUNUDPSocket
;
625 if (OpenSocket(*stunSocket
, singlePortInfo
))
627 PSTUNMessage
request(PSTUNMessage::BindingRequest
);
628 request
.AddAttribute(PSTUNChangeRequest(false, false));
629 PSTUNMessage response
;
631 if (response
.Poll(*stunSocket
, request
))
633 PSTUNMappedAddress
* mappedAddress
= (PSTUNMappedAddress
*)response
.FindAttribute(PSTUNAttribute::MAPPED_ADDRESS
);
634 if (mappedAddress
!= NULL
)
636 stunSocket
->externalIP
= mappedAddress
->GetIP();
637 if (GetNatType(FALSE
) != SymmetricNat
)
638 stunSocket
->port
= mappedAddress
->port
;
639 stunSocket
->SetSendAddress(0, 0);
640 stunSocket
->SetReadTimeout(PMaxTimeInterval
);
645 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress
<< ':' << serverPort
);
648 PTRACE(1, "STUN\tServer " << serverAddress
<< ':' << serverPort
<< " unexpectedly went offline.");
656 BOOL
PSTUNClient::CreateSocketPair(PUDPSocket
* & socket1
,
657 PUDPSocket
* & socket2
)
662 switch (GetNatType(FALSE
)) {
665 case PortRestrictedNat
:
669 if (pairedPortInfo
.basePort
== 0 || pairedPortInfo
.basePort
> pairedPortInfo
.maxPort
)
671 PTRACE(1, "STUN\tInvalid local UDP port range "
672 << pairedPortInfo
.currentPort
<< '-' << pairedPortInfo
.maxPort
);
677 default : // UnknownNet, SymmetricFirewall, BlockedNat
678 PTRACE(1, "STUN\tCannot create socket pair using NAT type " << GetNatTypeName());
684 PList
<PSTUNUDPSocket
> stunSocket
;
685 PList
<PSTUNMessage
> request
;
686 PList
<PSTUNMessage
> response
;
688 for (i
= 0; i
< numSocketsForPairing
; i
++)
690 PINDEX idx
= stunSocket
.Append(new PSTUNUDPSocket
);
691 if (!OpenSocket(stunSocket
[idx
], pairedPortInfo
))
694 idx
= request
.Append(new PSTUNMessage(PSTUNMessage::BindingRequest
));
695 request
[idx
].AddAttribute(PSTUNChangeRequest(false, false));
697 response
.Append(new PSTUNMessage
);
700 for (i
= 0; i
< numSocketsForPairing
; i
++)
702 if (!response
[i
].Poll(stunSocket
[i
], request
[i
]))
704 PTRACE(1, "STUN\tServer " << serverAddress
<< ':' << serverPort
<< " unexpectedly went offline.");
709 for (i
= 0; i
< numSocketsForPairing
; i
++)
711 PSTUNMappedAddress
* mappedAddress
= (PSTUNMappedAddress
*)response
[i
].FindAttribute(PSTUNAttribute::MAPPED_ADDRESS
);
712 if (mappedAddress
== NULL
)
714 PTRACE(2, "STUN\tExpected mapped address attribute from server " << serverAddress
<< ':' << serverPort
);
717 if (GetNatType(FALSE
) != SymmetricNat
)
718 stunSocket
[i
].port
= mappedAddress
->port
;
719 stunSocket
[i
].externalIP
= mappedAddress
->GetIP();
722 for (i
= 0; i
< numSocketsForPairing
; i
++)
724 for (int j
= 0; j
< numSocketsForPairing
; j
++)
726 if (stunSocket
[i
].port
+1 == stunSocket
[j
].port
)
728 stunSocket
[i
].SetSendAddress(0, 0);
729 stunSocket
[i
].SetReadTimeout(PMaxTimeInterval
);
730 stunSocket
[j
].SetSendAddress(0, 0);
731 stunSocket
[j
].SetReadTimeout(PMaxTimeInterval
);
732 socket1
= &stunSocket
[i
];
733 socket2
= &stunSocket
[j
];
734 stunSocket
.DisallowDeleteObjects();
735 stunSocket
.Remove(socket1
);
736 stunSocket
.Remove(socket2
);
737 stunSocket
.AllowDeleteObjects();
743 PTRACE(2, "STUN\tCould not get a pair of adjacent port numbers from NAT");
748 ////////////////////////////////////////////////////////////////
750 PSTUNUDPSocket::PSTUNUDPSocket()
756 BOOL
PSTUNUDPSocket::GetLocalAddress(Address
& addr
)
758 if (!externalIP
.IsValid())
759 return PUDPSocket::GetLocalAddress(addr
);
766 BOOL
PSTUNUDPSocket::GetLocalAddress(Address
& addr
, WORD
& port
)
768 if (!externalIP
.IsValid())
769 return PUDPSocket::GetLocalAddress(addr
, port
);
777 // End of File ////////////////////////////////////////////////////////////////