tweaks
parent
2c34e3bf26
commit
f49de2c510
|
|
@ -207,12 +207,22 @@ class NormSocket
|
||||||
NormSessionHandle GetMulticastSession() const
|
NormSessionHandle GetMulticastSession() const
|
||||||
{return mcast_session;}
|
{return mcast_session;}
|
||||||
|
|
||||||
|
NormObjectHandle GetTxStream() const
|
||||||
|
{return tx_stream;}
|
||||||
|
|
||||||
void InitRxStream(NormObjectHandle rxStream)
|
void InitRxStream(NormObjectHandle rxStream)
|
||||||
{rx_stream = rxStream;}
|
{rx_stream = rxStream;}
|
||||||
NormObjectHandle GetRxStream() const
|
NormObjectHandle GetRxStream() const
|
||||||
{return rx_stream;}
|
{return rx_stream;}
|
||||||
|
|
||||||
|
|
||||||
|
void SetFlowControl(bool state)
|
||||||
|
{
|
||||||
|
tx_flow_control = state;
|
||||||
|
if (NORM_OBJECT_INVALID != tx_stream)
|
||||||
|
NormStreamSetPushEnable(tx_stream, state ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
void InitTxStream(NormObjectHandle txStream, unsigned int bufferSize, UINT16 segmentSize, UINT16 blockSize)
|
void InitTxStream(NormObjectHandle txStream, unsigned int bufferSize, UINT16 segmentSize, UINT16 blockSize)
|
||||||
{
|
{
|
||||||
tx_stream = txStream;
|
tx_stream = txStream;
|
||||||
|
|
@ -223,6 +233,7 @@ class NormSocket
|
||||||
tx_stream_bytes_remain = 0;
|
tx_stream_bytes_remain = 0;
|
||||||
tx_watermark_pending = false;
|
tx_watermark_pending = false;
|
||||||
tx_ready = true;
|
tx_ready = true;
|
||||||
|
NormStreamSetPushEnable(tx_stream, tx_flow_control ? false : true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Open(NormInstanceHandle instance = NORM_INSTANCE_INVALID);
|
bool Open(NormInstanceHandle instance = NORM_INSTANCE_INVALID);
|
||||||
|
|
@ -244,13 +255,9 @@ class NormSocket
|
||||||
// hard, immediate closure
|
// hard, immediate closure
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void SetSocketInfo(NormSocketInfo* socketInfo) // for server-side, client sockets only
|
void SetSocketInfo(NormSocketInfo* socketInfo) // for server-side, client sockets only
|
||||||
{socket_info = socketInfo;}
|
{socket_info = socketInfo;}
|
||||||
|
|
||||||
|
|
||||||
NormSocketInfo* FindSocketInfo(NormNodeHandle client)
|
NormSocketInfo* FindSocketInfo(NormNodeHandle client)
|
||||||
{return client_table.FindSocketInfo(client);}
|
{return client_table.FindSocketInfo(client);}
|
||||||
void RemoveSocketInfo(NormSocketInfo& socketInfo) // for server sockets only
|
void RemoveSocketInfo(NormSocketInfo& socketInfo) // for server sockets only
|
||||||
|
|
@ -350,6 +357,7 @@ class NormSocket
|
||||||
unsigned int tx_stream_buffer_count;
|
unsigned int tx_stream_buffer_count;
|
||||||
unsigned int tx_stream_bytes_remain;
|
unsigned int tx_stream_bytes_remain;
|
||||||
bool tx_watermark_pending;
|
bool tx_watermark_pending;
|
||||||
|
bool tx_flow_control;
|
||||||
// Receive stream state
|
// Receive stream state
|
||||||
NormObjectHandle rx_stream;
|
NormObjectHandle rx_stream;
|
||||||
const void* user_data; // for use by user application
|
const void* user_data; // for use by user application
|
||||||
|
|
@ -366,6 +374,7 @@ NormSocket::NormSocket(NormSessionHandle normSession)
|
||||||
tx_stream(NORM_OBJECT_INVALID), tx_ready(false), tx_segment_size(0),
|
tx_stream(NORM_OBJECT_INVALID), tx_ready(false), tx_segment_size(0),
|
||||||
tx_stream_buffer_max(0), tx_stream_buffer_count(0),
|
tx_stream_buffer_max(0), tx_stream_buffer_count(0),
|
||||||
tx_stream_bytes_remain(0), tx_watermark_pending(false),
|
tx_stream_bytes_remain(0), tx_watermark_pending(false),
|
||||||
|
tx_flow_control(true),
|
||||||
rx_stream(NORM_OBJECT_INVALID), user_data(NULL)
|
rx_stream(NORM_OBJECT_INVALID), user_data(NULL)
|
||||||
{
|
{
|
||||||
// For now we use the NormSession "user data" option to associate
|
// For now we use the NormSession "user data" option to associate
|
||||||
|
|
@ -732,7 +741,6 @@ bool NormSocket::Connect(const char* serverAddr,
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Set timeout for connect attempt (for "heavyweight" mcast connect, this would also be done)
|
// Set timeout for connect attempt (for "heavyweight" mcast connect, this would also be done)
|
||||||
TRACE("NormSetUserTimer(1) session: %p\n", norm_session);
|
|
||||||
NormSetUserTimer(norm_session, NORM_DEFAULT_CONNECT_TIMEOUT);
|
NormSetUserTimer(norm_session, NORM_DEFAULT_CONNECT_TIMEOUT);
|
||||||
}
|
}
|
||||||
server_socket = NULL; // this is a client-side socket
|
server_socket = NULL; // this is a client-side socket
|
||||||
|
|
@ -769,9 +777,14 @@ unsigned int NormSocket::Write(const char* buffer, unsigned int numBytes)
|
||||||
InitTxStream(tx_stream, 2*1024*1024, 1400, 16);
|
InitTxStream(tx_stream, 2*1024*1024, 1400, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method uses NormStreamWrite(), but limits writes by explicit ACK-based flow control status
|
if (!tx_flow_control)
|
||||||
if (tx_stream_buffer_count < tx_stream_buffer_max)
|
|
||||||
{
|
{
|
||||||
|
unsigned int bytesWritten = NormStreamWrite(tx_stream, buffer, numBytes);
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
else if (tx_stream_buffer_count < tx_stream_buffer_max)
|
||||||
|
{
|
||||||
|
// This method uses NormStreamWrite(), but limits writes by explicit ACK-based flow control status
|
||||||
// 1) How many buffer bytes are available?
|
// 1) How many buffer bytes are available?
|
||||||
unsigned int bytesAvailable = tx_segment_size * (tx_stream_buffer_max - tx_stream_buffer_count);
|
unsigned int bytesAvailable = tx_segment_size * (tx_stream_buffer_max - tx_stream_buffer_count);
|
||||||
bytesAvailable -= tx_stream_bytes_remain; // unflushed segment portiomn
|
bytesAvailable -= tx_stream_bytes_remain; // unflushed segment portiomn
|
||||||
|
|
@ -878,7 +891,6 @@ void NormSocket::Shutdown()
|
||||||
if ((IsServerSide() && IsMulticastClient()) || (NORM_OBJECT_INVALID == tx_stream))
|
if ((IsServerSide() && IsMulticastClient()) || (NORM_OBJECT_INVALID == tx_stream))
|
||||||
{
|
{
|
||||||
// Use a zero-timeout to immediately post NORM_SOCKET_CLOSE notification
|
// Use a zero-timeout to immediately post NORM_SOCKET_CLOSE notification
|
||||||
TRACE("NormSetUserTimer(2) session: %p\n", norm_session);
|
|
||||||
NormSetUserTimer(norm_session, 0.0);
|
NormSetUserTimer(norm_session, 0.0);
|
||||||
}
|
}
|
||||||
else if (NORM_OBJECT_INVALID != tx_stream)
|
else if (NORM_OBJECT_INVALID != tx_stream)
|
||||||
|
|
@ -892,7 +904,6 @@ void NormSocket::Shutdown()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Use a zero-timeout to immediately post NORM_SOCKET_CLOSE notification
|
// Use a zero-timeout to immediately post NORM_SOCKET_CLOSE notification
|
||||||
TRACE("NormSetUserTimer(6) session: %p\n", norm_session);
|
|
||||||
NormSetUserTimer(norm_session, 0.0);
|
NormSetUserTimer(norm_session, 0.0);
|
||||||
}
|
}
|
||||||
} // end NormSocket::Shutdown()
|
} // end NormSocket::Shutdown()
|
||||||
|
|
@ -914,7 +925,6 @@ void NormSocket::Close()
|
||||||
NormNodeHandle node = NormGetAckingNodeHandle(mcast_session, nodeId);
|
NormNodeHandle node = NormGetAckingNodeHandle(mcast_session, nodeId);
|
||||||
assert(NORM_NODE_INVALID != node);
|
assert(NORM_NODE_INVALID != node);
|
||||||
NormSocket* clientSocket = (NormSocket*)NormNodeGetUserData(node);
|
NormSocket* clientSocket = (NormSocket*)NormNodeGetUserData(node);
|
||||||
TRACE("NormSetUserTimer(3) session: %p\n", clientSocket->norm_session);
|
|
||||||
NormSetUserTimer(clientSocket->norm_session, 0.0);
|
NormSetUserTimer(clientSocket->norm_session, 0.0);
|
||||||
}
|
}
|
||||||
// for mcast server mcast_session == norm_session so it's destroyed below
|
// for mcast server mcast_session == norm_session so it's destroyed below
|
||||||
|
|
@ -1068,7 +1078,6 @@ void NormSocket::GetSocketEvent(const NormEvent& event, NormSocketEvent& socketE
|
||||||
// We use the session timer to dispatch a NORM_SOCKET_CLOSE per failed client
|
// We use the session timer to dispatch a NORM_SOCKET_CLOSE per failed client
|
||||||
// (This will also remove the client from this server's acking list)
|
// (This will also remove the client from this server's acking list)
|
||||||
clientSocket->socket_state = CLOSING;
|
clientSocket->socket_state = CLOSING;
|
||||||
TRACE("NormSetUserTimer(4) session: %p\n", clientSocket->norm_session);
|
|
||||||
NormSetUserTimer(clientSocket->norm_session, 0.0);
|
NormSetUserTimer(clientSocket->norm_session, 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1134,7 +1143,6 @@ void NormSocket::GetSocketEvent(const NormEvent& event, NormSocketEvent& socketE
|
||||||
case CONNECTING:
|
case CONNECTING:
|
||||||
// TBD - We should validate that it's the right remote sender
|
// TBD - We should validate that it's the right remote sender
|
||||||
// (i.e., by source address and/or nodeId)
|
// (i.e., by source address and/or nodeId)
|
||||||
TRACE("NormCancelUserTimer(1) norm_session: %p\n", norm_session);
|
|
||||||
NormCancelUserTimer(norm_session);
|
NormCancelUserTimer(norm_session);
|
||||||
socketEvent.type = NORM_SOCKET_CONNECT;
|
socketEvent.type = NORM_SOCKET_CONNECT;
|
||||||
NormSetSynStatus(norm_session, false);
|
NormSetSynStatus(norm_session, false);
|
||||||
|
|
@ -1628,6 +1636,19 @@ NormSessionHandle NormGetSocketSession(NormSocketHandle normSocket)
|
||||||
return s->GetSession();
|
return s->GetSession();
|
||||||
} // end NormGetSocketSession()
|
} // end NormGetSocketSession()
|
||||||
|
|
||||||
|
NormObjectHandle NormGetSocketTxStream(NormSocketHandle normSocket)
|
||||||
|
{
|
||||||
|
NormSocket* s = (NormSocket*)normSocket;
|
||||||
|
return s->GetTxStream();
|
||||||
|
} // end NormGetSocketTxStream()
|
||||||
|
|
||||||
|
NormObjectHandle NormGetSocketRxStream(NormSocketHandle normSocket)
|
||||||
|
{
|
||||||
|
NormSocket* s = (NormSocket*)normSocket;
|
||||||
|
return s->GetRxStream();
|
||||||
|
} // end NormGetSocketRxStream()
|
||||||
|
|
||||||
|
|
||||||
NormSessionHandle NormGetSocketMulticastSession(NormSocketHandle normSocket)
|
NormSessionHandle NormGetSocketMulticastSession(NormSocketHandle normSocket)
|
||||||
{
|
{
|
||||||
NormSocket* s = (NormSocket*)normSocket;
|
NormSocket* s = (NormSocket*)normSocket;
|
||||||
|
|
@ -1639,4 +1660,10 @@ void NormSetSocketTrace(NormSocketHandle normSocket, bool enable)
|
||||||
{
|
{
|
||||||
NormSocket* s = (NormSocket*)normSocket;
|
NormSocket* s = (NormSocket*)normSocket;
|
||||||
s->SetTrace(enable);
|
s->SetTrace(enable);
|
||||||
}
|
} // end NormSetSocketTrace()
|
||||||
|
|
||||||
|
void NormSetSocketFlowControl(NormSocketHandle normSocket, bool enable)
|
||||||
|
{
|
||||||
|
NormSocket* s = (NormSocket*)normSocket;
|
||||||
|
s->SetFlowControl(enable);
|
||||||
|
} // end NormSetSocketFlowControl()
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,10 @@ NormInstanceHandle NormGetSocketInstance(NormSocketHandle normSocket);
|
||||||
NormSessionHandle NormGetSocketSession(NormSocketHandle normSocket);
|
NormSessionHandle NormGetSocketSession(NormSocketHandle normSocket);
|
||||||
NormSessionHandle NormGetSocketMulticastSession(NormSocketHandle normSocket);
|
NormSessionHandle NormGetSocketMulticastSession(NormSocketHandle normSocket);
|
||||||
void NormGetPeerName(NormSocketHandle normSocket, char* addr, unsigned int* addrLen, UINT16* port);
|
void NormGetPeerName(NormSocketHandle normSocket, char* addr, unsigned int* addrLen, UINT16* port);
|
||||||
|
NormObjectHandle NormGetSocketTxStream(NormSocketHandle normSocket);
|
||||||
|
NormObjectHandle NormGetSocketRxStream(NormSocketHandle normSocket);
|
||||||
|
|
||||||
|
void NormSetSocketFlowControl(NormSocketHandle normSocket, bool enable);
|
||||||
void NormSetSocketTrace(NormSocketHandle normSocket, bool enable);
|
void NormSetSocketTrace(NormSocketHandle normSocket, bool enable);
|
||||||
|
|
||||||
typedef enum NormSocketEventType
|
typedef enum NormSocketEventType
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,10 @@ class NormObject
|
||||||
|
|
||||||
// This must be reset after each update
|
// This must be reset after each update
|
||||||
void SetNotifyOnUpdate(bool state)
|
void SetNotifyOnUpdate(bool state)
|
||||||
{notify_on_update = state;}
|
{
|
||||||
|
TRACE("enter SetNotifyOnUpdate(%d) ...\n", state);
|
||||||
|
notify_on_update = state;
|
||||||
|
}
|
||||||
|
|
||||||
// Object information
|
// Object information
|
||||||
NormObject::Type GetType() const {return type;}
|
NormObject::Type GetType() const {return type;}
|
||||||
|
|
|
||||||
2
protolib
2
protolib
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7cf50bddf3939e5bd78f7264e1baa8b8643d4cd7
|
Subproject commit 0e6731b7d61ea78f8ca9bfeb32e73c19bbeb40dd
|
||||||
|
|
@ -220,11 +220,8 @@ void NormInstance::Notify(NormController::Event event,
|
||||||
{
|
{
|
||||||
case SEND_OK:
|
case SEND_OK:
|
||||||
// Purge any pending NORM_SEND_ERROR notifications for session
|
// Purge any pending NORM_SEND_ERROR notifications for session
|
||||||
TRACE("purging SEND_ERROR ...\n");
|
|
||||||
PurgeNotifications(session, NORM_SEND_ERROR);
|
PurgeNotifications(session, NORM_SEND_ERROR);
|
||||||
return;
|
return;
|
||||||
case SEND_ERROR:
|
|
||||||
TRACE("got SEND_ERROR\n");
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -557,7 +554,6 @@ bool NormInstance::GetNextEvent(NormEvent* theEvent)
|
||||||
case NORM_SEND_ERROR:
|
case NORM_SEND_ERROR:
|
||||||
{
|
{
|
||||||
NormSession* session = (NormSession*)next->event.session;
|
NormSession* session = (NormSession*)next->event.session;
|
||||||
TRACE("calling ClearSendError() ....\n");
|
|
||||||
session->ClearSendError();
|
session->ClearSendError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1878,8 +1874,9 @@ NormObjectHandle NormStreamOpen(NormSessionHandle sessionHandle,
|
||||||
NormSession* session = (NormSession*)sessionHandle;
|
NormSession* session = (NormSession*)sessionHandle;
|
||||||
if (session)
|
if (session)
|
||||||
{
|
{
|
||||||
|
NormStreamObject* streamObj = session->QueueTxStream(bufferSize, true, infoPtr, infoLen);
|
||||||
NormObject* obj =
|
NormObject* obj =
|
||||||
static_cast<NormObject*>(session->QueueTxStream(bufferSize, true, infoPtr, infoLen));
|
static_cast<NormObject*>(streamObj);
|
||||||
if (obj) objectHandle = (NormObjectHandle)obj;
|
if (obj) objectHandle = (NormObjectHandle)obj;
|
||||||
}
|
}
|
||||||
instance->dispatcher.ResumeThread();
|
instance->dispatcher.ResumeThread();
|
||||||
|
|
|
||||||
|
|
@ -3521,14 +3521,20 @@ bool NormStreamObject::Read(char* buffer, unsigned int* buflen, bool seekMsgStar
|
||||||
bytesWanted = *buflen;
|
bytesWanted = *buflen;
|
||||||
}
|
}
|
||||||
bool result = ReadPrivate(buffer, buflen, seekMsgStart);
|
bool result = ReadPrivate(buffer, buflen, seekMsgStart);
|
||||||
if (!read_ready) notify_on_update = true;
|
if (!read_ready)
|
||||||
|
{
|
||||||
|
notify_on_update = true;
|
||||||
|
}
|
||||||
if (!seekMsgStart && result && (0 != *buflen) && (*buflen < bytesWanted))
|
if (!seekMsgStart && result && (0 != *buflen) && (*buflen < bytesWanted))
|
||||||
{
|
{
|
||||||
char dummyBuffer[8];
|
char dummyBuffer[8];
|
||||||
unsigned int dummyCount = 8;
|
unsigned int dummyCount = 8;
|
||||||
stream_broken = ReadPrivate(dummyBuffer, &dummyCount, false) ? false : true;
|
stream_broken = ReadPrivate(dummyBuffer, &dummyCount, false) ? false : true;
|
||||||
ASSERT(0 == dummyCount);
|
ASSERT(0 == dummyCount);
|
||||||
if (!read_ready) notify_on_update = true;
|
if (!read_ready)
|
||||||
|
{
|
||||||
|
notify_on_update = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} // end NormStreamObject::Read()
|
} // end NormStreamObject::Read()
|
||||||
|
|
@ -3553,7 +3559,7 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek
|
||||||
NormBlock* block = stream_buffer.Find(read_index.block);
|
NormBlock* block = stream_buffer.Find(read_index.block);
|
||||||
if (NULL == block)
|
if (NULL == block)
|
||||||
{
|
{
|
||||||
//DMSG(0, "NormStreamObject::ReadPrivate() stream buffer empty (1) (sbEmpty:%d)\n", stream_buffer.IsEmpty());
|
PLOG(PL_DETAIL, "NormStreamObject::ReadPrivate() stream buffer empty (1) (sbEmpty:%d)\n", stream_buffer.IsEmpty());
|
||||||
read_ready = false;
|
read_ready = false;
|
||||||
*buflen = bytesRead;
|
*buflen = bytesRead;
|
||||||
if (bytesRead > 0)
|
if (bytesRead > 0)
|
||||||
|
|
@ -3614,8 +3620,8 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek
|
||||||
|
|
||||||
if (NULL == segment)
|
if (NULL == segment)
|
||||||
{
|
{
|
||||||
//DMSG(0, "NormStreamObject::ReadPrivate(%lu:%hu) stream buffer empty (read_offset>%lu) (2)\n",
|
PLOG(PL_DETAIL, "NormStreamObject::ReadPrivate(%lu:%hu) stream buffer empty (read_offset>%lu) (2)\n",
|
||||||
// (unsigned long)read_index.block.GetValue(), read_index.segment, (unsigned long)read_offset);
|
(unsigned long)read_index.block.GetValue(), read_index.segment, (unsigned long)read_offset);
|
||||||
read_ready = false;
|
read_ready = false;
|
||||||
*buflen = bytesRead;
|
*buflen = bytesRead;
|
||||||
if (bytesRead > 0)
|
if (bytesRead > 0)
|
||||||
|
|
|
||||||
|
|
@ -254,8 +254,6 @@ bool NormSession::Open()
|
||||||
if (!tx_socket->SetFragmentation(fragmentation))
|
if (!tx_socket->SetFragmentation(fragmentation))
|
||||||
PLOG(PL_WARN, "NormSession::Open() warning: tx_socket.SetFragmentation() error\n");
|
PLOG(PL_WARN, "NormSession::Open() warning: tx_socket.SetFragmentation() error\n");
|
||||||
|
|
||||||
|
|
||||||
TRACE("NormSession::Open() address %s\n", address.GetHostString());
|
|
||||||
if (address.IsMulticast())
|
if (address.IsMulticast())
|
||||||
{
|
{
|
||||||
if (!tx_socket->SetTTL(ttl))
|
if (!tx_socket->SetTTL(ttl))
|
||||||
|
|
@ -2240,7 +2238,6 @@ void NormSession::TxSocketRecvHandler(ProtoSocket& theSocket,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TRACE("NormSession::TxSocketRecvHandler() RecvFrom error\n");
|
|
||||||
// Probably an ICMP "port unreachable" error
|
// Probably an ICMP "port unreachable" error
|
||||||
// Note we purposefull do _not_ set the "posted_send_error"
|
// Note we purposefull do _not_ set the "posted_send_error"
|
||||||
// status here because we do not want this notification
|
// status here because we do not want this notification
|
||||||
|
|
@ -2362,14 +2359,13 @@ void NormSession::RxSocketRecvHandler(ProtoSocket& theSocket,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TRACE("NormSession::RxSocketRecvHandler() RecvFrom error address:%s (unicast:%d)\n", Address().GetHostString(), Address().IsUnicast());
|
//TRACE("NormSession::RxSocketRecvHandler() RecvFrom error address:%s (unicast:%d)\n", Address().GetHostString(), Address().IsUnicast());
|
||||||
// Probably an ICMP "port unreachable" error
|
// Probably an ICMP "port unreachable" error
|
||||||
// Note we purposefull do _not_ set the "posted_send_error"
|
// Note we purposefull do _not_ set the "posted_send_error"
|
||||||
// status here because we do not want this notification
|
// status here because we do not want this notification
|
||||||
// cleared due to SEND_OK status since it's receiver driven
|
// cleared due to SEND_OK status since it's receiver driven
|
||||||
if (Address().IsUnicast())
|
if (Address().IsUnicast())
|
||||||
{
|
{
|
||||||
TRACE("posting send error ...\n");
|
|
||||||
Notify(NormController::SEND_ERROR, NULL, NULL);
|
Notify(NormController::SEND_ERROR, NULL, NULL);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue