|
INET Framework for OMNeT++/OMNEST
|
00001 // 00002 // Copyright (C) 2000 Institut fuer Telematik, Universitaet Karlsruhe 00003 // Copyright (C) 2004,2005 Andras Varga 00004 // 00005 // This program is free software; you can redistribute it and/or 00006 // modify it under the terms of the GNU Lesser General Public License 00007 // as published by the Free Software Foundation; either version 2 00008 // of the License, or (at your option) any later version. 00009 // 00010 // This program is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU Lesser General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU Lesser General Public License 00016 // along with this program; if not, see <http://www.gnu.org/licenses/>. 00017 // 00018 00019 00020 // 00021 // Author: Jochen Reber 00022 // Rewrite: Andras Varga 2004,2005 00023 // 00024 00025 #include <omnetpp.h> 00026 #include <string.h> 00027 #include "UDPPacket.h" 00028 #include "UDP.h" 00029 #include "IPControlInfo.h" 00030 #include "IPv6ControlInfo.h" 00031 #include "ICMPAccess.h" 00032 #include "ICMPv6Access.h" 00033 00034 // the following is only for ICMP error processing 00035 #include "ICMPMessage_m.h" 00036 #include "ICMPv6Message_m.h" 00037 #include "IPDatagram_m.h" 00038 #include "IPv6Datagram_m.h" 00039 00040 00041 #define EPHEMERAL_PORTRANGE_START 1024 00042 #define EPHEMERAL_PORTRANGE_END 5000 00043 00044 00045 Define_Module( UDP ); 00046 00047 00048 static std::ostream & operator<<(std::ostream & os, const UDP::SockDesc& sd) 00049 { 00050 os << "sockId=" << sd.sockId; 00051 os << " appGateIndex=" << sd.appGateIndex; 00052 os << " userId=" << sd.userId; 00053 os << " localPort=" << sd.localPort; 00054 if (sd.remotePort!=0) 00055 os << " remotePort=" << sd.remotePort; 00056 if (!sd.localAddr.isUnspecified()) 00057 os << " localAddr=" << sd.localAddr; 00058 if (!sd.remoteAddr.isUnspecified()) 00059 os << " remoteAddr=" << sd.remoteAddr; 00060 if (sd.interfaceId!=-1) 00061 os << " interfaceId=" << sd.interfaceId; 00062 00063 return os; 00064 } 00065 00066 static std::ostream & operator<<(std::ostream & os, const UDP::SockDescList& list) 00067 { 00068 for (UDP::SockDescList::const_iterator i=list.begin(); i!=list.end(); ++i) 00069 os << "sockId=" << (*i)->sockId << " "; 00070 return os; 00071 } 00072 00073 //-------- 00074 00075 UDP::~UDP() 00076 { 00077 for (SocketsByIdMap::iterator i=socketsByIdMap.begin(); i!=socketsByIdMap.end(); ++i) 00078 delete i->second; 00079 } 00080 00081 void UDP::initialize() 00082 { 00083 WATCH_PTRMAP(socketsByIdMap); 00084 WATCH_MAP(socketsByPortMap); 00085 00086 lastEphemeralPort = EPHEMERAL_PORTRANGE_START; 00087 icmp = NULL; 00088 icmpv6 = NULL; 00089 00090 numSent = 0; 00091 numPassedUp = 0; 00092 numDroppedWrongPort = 0; 00093 numDroppedBadChecksum = 0; 00094 WATCH(numSent); 00095 WATCH(numPassedUp); 00096 WATCH(numDroppedWrongPort); 00097 WATCH(numDroppedBadChecksum); 00098 } 00099 00100 void UDP::bind(int gateIndex, UDPControlInfo *ctrl) 00101 { 00102 // XXX checks could be added, of when the bind should be allowed to proceed 00103 00104 // create and fill in SockDesc 00105 SockDesc *sd = new SockDesc(); 00106 sd->sockId = ctrl->getSockId(); 00107 sd->userId = ctrl->getUserId(); 00108 sd->appGateIndex = gateIndex; 00109 sd->localAddr = ctrl->getSrcAddr(); 00110 sd->remoteAddr = ctrl->getDestAddr(); 00111 sd->localPort = ctrl->getSrcPort(); 00112 sd->remotePort = ctrl->getDestPort(); 00113 sd->interfaceId = ctrl->getInterfaceId(); 00114 00115 if (sd->sockId==-1) 00116 error("sockId in BIND message not filled in"); 00117 if (sd->localPort==0) 00118 sd->localPort = getEphemeralPort(); 00119 00120 sd->onlyLocalPortIsSet = sd->localAddr.isUnspecified() && 00121 sd->remoteAddr.isUnspecified() && 00122 sd->remotePort==0 && 00123 sd->interfaceId==-1; 00124 00125 EV << "Binding socket: " << *sd << "\n"; 00126 00127 // add to socketsByIdMap 00128 ASSERT(socketsByIdMap.find(sd->sockId)==socketsByIdMap.end()); 00129 socketsByIdMap[sd->sockId] = sd; 00130 00131 // add to socketsByPortMap 00132 SockDescList& list = socketsByPortMap[sd->localPort]; // create if doesn't exist 00133 list.push_back(sd); 00134 } 00135 00136 void UDP::connect(int sockId, IPvXAddress addr, int port) 00137 { 00138 SocketsByIdMap::iterator it = socketsByIdMap.find(sockId); 00139 if (it==socketsByIdMap.end()) 00140 error("socket id=%d doesn't exist (already closed?)", sockId); 00141 if (addr.isUnspecified()) 00142 opp_error("connect: unspecified remote address"); 00143 if (port<=0 || port>65535) 00144 opp_error("connect: invalid remote port number %d", port); 00145 00146 SockDesc *sd = it->second; 00147 sd->remoteAddr = addr; 00148 sd->remotePort = port; 00149 00150 sd->onlyLocalPortIsSet = false; 00151 00152 EV << "Connecting socket: " << *sd << "\n"; 00153 } 00154 00155 void UDP::unbind(int sockId) 00156 { 00157 // remove from socketsByIdMap 00158 SocketsByIdMap::iterator it = socketsByIdMap.find(sockId); 00159 if (it==socketsByIdMap.end()) 00160 error("socket id=%d doesn't exist (already closed?)", sockId); 00161 SockDesc *sd = it->second; 00162 socketsByIdMap.erase(it); 00163 00164 EV << "Unbinding socket: " << *sd << "\n"; 00165 00166 // remove from socketsByPortMap 00167 SockDescList& list = socketsByPortMap[sd->localPort]; 00168 for (SockDescList::iterator it=list.begin(); it!=list.end(); ++it) 00169 if (*it == sd) 00170 {list.erase(it); break;} 00171 if (list.empty()) 00172 socketsByPortMap.erase(sd->localPort); 00173 delete sd; 00174 } 00175 00176 ushort UDP::getEphemeralPort() 00177 { 00178 // start at the last allocated port number + 1, and search for an unused one 00179 ushort searchUntil = lastEphemeralPort++; 00180 if (lastEphemeralPort == EPHEMERAL_PORTRANGE_END) // wrap 00181 lastEphemeralPort = EPHEMERAL_PORTRANGE_START; 00182 00183 while (socketsByPortMap.find(lastEphemeralPort)!=socketsByPortMap.end()) 00184 { 00185 if (lastEphemeralPort == searchUntil) // got back to starting point? 00186 error("Ephemeral port range %d..%d exhausted, all ports occupied", EPHEMERAL_PORTRANGE_START, EPHEMERAL_PORTRANGE_END); 00187 lastEphemeralPort++; 00188 if (lastEphemeralPort == EPHEMERAL_PORTRANGE_END) // wrap 00189 lastEphemeralPort = EPHEMERAL_PORTRANGE_START; 00190 } 00191 00192 // found a free one, return it 00193 return lastEphemeralPort; 00194 } 00195 00196 void UDP::handleMessage(cMessage *msg) 00197 { 00198 // received from IP layer 00199 if (msg->arrivedOn("ipIn") || msg->arrivedOn("ipv6In")) 00200 { 00201 if (dynamic_cast<ICMPMessage *>(msg) || dynamic_cast<ICMPv6Message *>(msg)) 00202 processICMPError(PK(msg)); 00203 else 00204 processUDPPacket(check_and_cast<UDPPacket *>(msg)); 00205 } 00206 else // received from application layer 00207 { 00208 if (msg->getKind()==UDP_C_DATA) 00209 processMsgFromApp(PK(msg)); 00210 else 00211 processCommandFromApp(msg); 00212 } 00213 00214 if (ev.isGUI()) 00215 updateDisplayString(); 00216 } 00217 00218 void UDP::updateDisplayString() 00219 { 00220 char buf[80]; 00221 sprintf(buf, "passed up: %d pks\nsent: %d pks", numPassedUp, numSent); 00222 if (numDroppedWrongPort>0) 00223 { 00224 sprintf(buf+strlen(buf), "\ndropped (no app): %d pks", numDroppedWrongPort); 00225 getDisplayString().setTagArg("i",1,"red"); 00226 } 00227 getDisplayString().setTagArg("t",0,buf); 00228 } 00229 00230 bool UDP::matchesSocket(SockDesc *sd, UDPPacket *udp, IPControlInfo *ipCtrl) 00231 { 00232 // IPv4 version 00233 if (sd->remotePort!=0 && sd->remotePort!=udp->getSourcePort()) 00234 return false; 00235 if (!sd->localAddr.isUnspecified() && sd->localAddr.get4()!=ipCtrl->getDestAddr()) 00236 return false; 00237 if (!sd->remoteAddr.isUnspecified() && sd->remoteAddr.get4()!=ipCtrl->getSrcAddr()) 00238 return false; 00239 if (sd->interfaceId!=-1 && sd->interfaceId!=ipCtrl->getInterfaceId()) 00240 return false; 00241 return true; 00242 } 00243 00244 bool UDP::matchesSocket(SockDesc *sd, UDPPacket *udp, IPv6ControlInfo *ipCtrl) 00245 { 00246 // IPv6 version 00247 if (sd->remotePort!=0 && sd->remotePort!=udp->getSourcePort()) 00248 return false; 00249 if (!sd->localAddr.isUnspecified() && sd->localAddr.get6()!=ipCtrl->getDestAddr()) 00250 return false; 00251 if (!sd->remoteAddr.isUnspecified() && sd->remoteAddr.get6()!=ipCtrl->getSrcAddr()) 00252 return false; 00253 if (sd->interfaceId!=-1 && sd->interfaceId!=ipCtrl->getInterfaceId()) 00254 return false; 00255 return true; 00256 } 00257 00258 bool UDP::matchesSocket(SockDesc *sd, const IPvXAddress& localAddr, const IPvXAddress& remoteAddr, ushort remotePort) 00259 { 00260 return (sd->remotePort==0 || sd->remotePort!=remotePort) && 00261 (sd->localAddr.isUnspecified() || sd->localAddr==localAddr) && 00262 (sd->remoteAddr.isUnspecified() || sd->remoteAddr==remoteAddr); 00263 } 00264 00265 void UDP::sendUp(cPacket *payload, UDPPacket *udpHeader, IPControlInfo *ipCtrl, SockDesc *sd) 00266 { 00267 // send payload with UDPControlInfo up to the application -- IPv4 version 00268 UDPControlInfo *udpCtrl = new UDPControlInfo(); 00269 udpCtrl->setSockId(sd->sockId); 00270 udpCtrl->setUserId(sd->userId); 00271 udpCtrl->setSrcAddr(ipCtrl->getSrcAddr()); 00272 udpCtrl->setDestAddr(ipCtrl->getDestAddr()); 00273 udpCtrl->setSrcPort(udpHeader->getSourcePort()); 00274 udpCtrl->setDestPort(udpHeader->getDestinationPort()); 00275 udpCtrl->setInterfaceId(ipCtrl->getInterfaceId()); 00276 payload->setControlInfo(udpCtrl); 00277 00278 send(payload, "appOut", sd->appGateIndex); 00279 numPassedUp++; 00280 } 00281 00282 void UDP::sendUp(cPacket *payload, UDPPacket *udpHeader, IPv6ControlInfo *ipCtrl, SockDesc *sd) 00283 { 00284 // send payload with UDPControlInfo up to the application -- IPv6 version 00285 UDPControlInfo *udpCtrl = new UDPControlInfo(); 00286 udpCtrl->setSockId(sd->sockId); 00287 udpCtrl->setUserId(sd->userId); 00288 udpCtrl->setSrcAddr(ipCtrl->getSrcAddr()); 00289 udpCtrl->setDestAddr(ipCtrl->getDestAddr()); 00290 udpCtrl->setSrcPort(udpHeader->getSourcePort()); 00291 udpCtrl->setDestPort(udpHeader->getDestinationPort()); 00292 udpCtrl->setInterfaceId(ipCtrl->getInterfaceId()); 00293 payload->setControlInfo(udpCtrl); 00294 00295 send(payload, "appOut", sd->appGateIndex); 00296 numPassedUp++; 00297 } 00298 00299 void UDP::processUndeliverablePacket(UDPPacket *udpPacket, cPolymorphic *ctrl) 00300 { 00301 numDroppedWrongPort++; 00302 00303 // send back ICMP PORT_UNREACHABLE 00304 if (dynamic_cast<IPControlInfo *>(ctrl)!=NULL) 00305 { 00306 if (!icmp) 00307 icmp = ICMPAccess().get(); 00308 IPControlInfo *ctrl4 = (IPControlInfo *)ctrl; 00309 if (!ctrl4->getDestAddr().isMulticast()) 00310 icmp->sendErrorMessage(udpPacket, ctrl4, ICMP_DESTINATION_UNREACHABLE, ICMP_DU_PORT_UNREACHABLE); 00311 } 00312 else if (dynamic_cast<IPv6ControlInfo *>(udpPacket->getControlInfo())!=NULL) 00313 { 00314 if (!icmpv6) 00315 icmpv6 = ICMPv6Access().get(); 00316 IPv6ControlInfo *ctrl6 = (IPv6ControlInfo *)ctrl; 00317 if (!ctrl6->getDestAddr().isMulticast()) 00318 icmpv6->sendErrorMessage(udpPacket, ctrl6, ICMPv6_DESTINATION_UNREACHABLE, PORT_UNREACHABLE); 00319 } 00320 else 00321 { 00322 error("(%s)%s arrived from lower layer without control info", udpPacket->getClassName(), udpPacket->getName()); 00323 } 00324 } 00325 00326 void UDP::processICMPError(cPacket *msg) 00327 { 00328 // extract details from the error message, then try to notify socket that sent bogus packet 00329 int type, code; 00330 IPvXAddress localAddr, remoteAddr; 00331 ushort localPort, remotePort; 00332 00333 if (dynamic_cast<ICMPMessage *>(msg)) 00334 { 00335 ICMPMessage *icmpMsg = (ICMPMessage *)msg; 00336 type = icmpMsg->getType(); 00337 code = icmpMsg->getCode(); 00338 // Note: we must NOT use decapsulate() because payload in ICMP is conceptually truncated 00339 IPDatagram *datagram = check_and_cast<IPDatagram *>(icmpMsg->getEncapsulatedPacket()); 00340 UDPPacket *packet = check_and_cast<UDPPacket *>(datagram->getEncapsulatedPacket()); 00341 localAddr = datagram->getSrcAddress(); 00342 remoteAddr = datagram->getDestAddress(); 00343 localPort = packet->getSourcePort(); 00344 remotePort = packet->getDestinationPort(); 00345 delete icmpMsg; 00346 } 00347 else if (dynamic_cast<ICMPv6Message *>(msg)) 00348 { 00349 ICMPv6Message *icmpMsg = (ICMPv6Message *)msg; 00350 type = icmpMsg->getType(); 00351 code = -1; // FIXME this is dependent on getType()... 00352 // Note: we must NOT use decapsulate() because payload in ICMP is conceptually truncated 00353 IPv6Datagram *datagram = check_and_cast<IPv6Datagram *>(icmpMsg->getEncapsulatedPacket()); 00354 UDPPacket *packet = check_and_cast<UDPPacket *>(datagram->getEncapsulatedPacket()); 00355 localAddr = datagram->getSrcAddress(); 00356 remoteAddr = datagram->getDestAddress(); 00357 localPort = packet->getSourcePort(); 00358 remotePort = packet->getDestinationPort(); 00359 delete icmpMsg; 00360 } 00361 EV << "ICMP error received: type=" << type << " code=" << code 00362 << " about packet " << localAddr << ":" << localPort << " > " 00363 << remoteAddr << ":" << remotePort << "\n"; 00364 00365 // identify socket and report error to it 00366 SocketsByPortMap::iterator it = socketsByPortMap.find(localPort); 00367 if (it==socketsByPortMap.end()) 00368 { 00369 EV << "No socket on that local port, ignoring ICMP error\n"; 00370 return; 00371 } 00372 SockDescList& list = it->second; 00373 SockDesc *srcSocket = NULL; 00374 for (SockDescList::iterator it=list.begin(); it!=list.end(); ++it) 00375 { 00376 SockDesc *sd = *it; 00377 if (sd->onlyLocalPortIsSet || matchesSocket(sd, localAddr, remoteAddr, remotePort)) 00378 { 00379 srcSocket = sd; // FIXME what to do if there's more than one matching socket ??? 00380 } 00381 } 00382 if (!srcSocket) 00383 { 00384 EV << "No matching socket, ignoring ICMP error\n"; 00385 return; 00386 } 00387 00388 // send UDP_I_ERROR to socket 00389 EV << "Source socket is sockId=" << srcSocket->sockId << ", notifying.\n"; 00390 sendUpErrorNotification(srcSocket, UDP_I_ERROR, localAddr, remoteAddr, remotePort); 00391 } 00392 00393 void UDP::sendUpErrorNotification(SockDesc *sd, int msgkind, const IPvXAddress& localAddr, const IPvXAddress& remoteAddr, ushort remotePort) 00394 { 00395 cPacket *notifyMsg = new cPacket("ERROR", msgkind); 00396 UDPControlInfo *udpCtrl = new UDPControlInfo(); 00397 udpCtrl->setSockId(sd->sockId); 00398 udpCtrl->setUserId(sd->userId); 00399 udpCtrl->setSrcAddr(localAddr); 00400 udpCtrl->setDestAddr(remoteAddr); 00401 udpCtrl->setSrcPort(sd->localPort); 00402 udpCtrl->setDestPort(remotePort); 00403 notifyMsg->setControlInfo(udpCtrl); 00404 00405 send(notifyMsg, "appOut", sd->appGateIndex); 00406 } 00407 00408 void UDP::processUDPPacket(UDPPacket *udpPacket) 00409 { 00410 // simulate checksum: discard packet if it has bit error 00411 EV << "Packet " << udpPacket->getName() << " received from network, dest port " << udpPacket->getDestinationPort() << "\n"; 00412 if (udpPacket->hasBitError()) 00413 { 00414 EV << "Packet has bit error, discarding\n"; 00415 delete udpPacket; 00416 numDroppedBadChecksum++; 00417 return; 00418 } 00419 00420 int destPort = udpPacket->getDestinationPort(); 00421 cPolymorphic *ctrl = udpPacket->removeControlInfo(); 00422 00423 // send back ICMP error if no socket is bound to that port 00424 SocketsByPortMap::iterator it = socketsByPortMap.find(destPort); 00425 if (it==socketsByPortMap.end()) 00426 { 00427 EV << "No socket registered on port " << destPort << "\n"; 00428 processUndeliverablePacket(udpPacket, ctrl); 00429 return; 00430 } 00431 SockDescList& list = it->second; 00432 00433 int matches = 0; 00434 00435 // deliver a copy of the packet to each matching socket 00436 cPacket *payload = udpPacket->getEncapsulatedPacket(); 00437 if (dynamic_cast<IPControlInfo *>(ctrl)!=NULL) 00438 { 00439 IPControlInfo *ctrl4 = (IPControlInfo *)ctrl; 00440 for (SockDescList::iterator it=list.begin(); it!=list.end(); ++it) 00441 { 00442 SockDesc *sd = *it; 00443 if (sd->onlyLocalPortIsSet || matchesSocket(sd, udpPacket, ctrl4)) 00444 { 00445 EV << "Socket sockId=" << sd->sockId << " matches, sending up a copy.\n"; 00446 sendUp((cPacket*)payload->dup(), udpPacket, ctrl4, sd); 00447 matches++; 00448 } 00449 } 00450 } 00451 else if (dynamic_cast<IPv6ControlInfo *>(ctrl)!=NULL) 00452 { 00453 IPv6ControlInfo *ctrl6 = (IPv6ControlInfo *)ctrl; 00454 for (SockDescList::iterator it=list.begin(); it!=list.end(); ++it) 00455 { 00456 SockDesc *sd = *it; 00457 if (sd->onlyLocalPortIsSet || matchesSocket(sd, udpPacket, ctrl6)) 00458 { 00459 EV << "Socket sockId=" << sd->sockId << " matches, sending up a copy.\n"; 00460 sendUp((cPacket*)payload->dup(), udpPacket, ctrl6, sd); 00461 matches++; 00462 } 00463 } 00464 } 00465 else 00466 { 00467 error("(%s)%s arrived from lower layer without control info", udpPacket->getClassName(), udpPacket->getName()); 00468 } 00469 00470 // send back ICMP error if there is no matching socket 00471 if (matches==0) 00472 { 00473 EV << "None of the sockets on port " << destPort << " matches the packet\n"; 00474 processUndeliverablePacket(udpPacket, ctrl); 00475 return; 00476 } 00477 00478 delete udpPacket; 00479 delete ctrl; 00480 } 00481 00482 00483 void UDP::processMsgFromApp(cPacket *appData) 00484 { 00485 UDPControlInfo *udpCtrl = check_and_cast<UDPControlInfo *>(appData->removeControlInfo()); 00486 00487 UDPPacket *udpPacket = createUDPPacket(appData->getName()); 00488 udpPacket->setByteLength(UDP_HEADER_BYTES); 00489 udpPacket->encapsulate(appData); 00490 00491 // set source and destination port 00492 udpPacket->setSourcePort(udpCtrl->getSrcPort()); 00493 udpPacket->setDestinationPort(udpCtrl->getDestPort()); 00494 00495 if (!udpCtrl->getDestAddr().isIPv6()) 00496 { 00497 // send to IPv4 00498 EV << "Sending app packet " << appData->getName() << " over IPv4.\n"; 00499 IPControlInfo *ipControlInfo = new IPControlInfo(); 00500 ipControlInfo->setProtocol(IP_PROT_UDP); 00501 ipControlInfo->setSrcAddr(udpCtrl->getSrcAddr().get4()); 00502 ipControlInfo->setDestAddr(udpCtrl->getDestAddr().get4()); 00503 ipControlInfo->setInterfaceId(udpCtrl->getInterfaceId()); 00504 udpPacket->setControlInfo(ipControlInfo); 00505 delete udpCtrl; 00506 00507 send(udpPacket,"ipOut"); 00508 } 00509 else 00510 { 00511 // send to IPv6 00512 EV << "Sending app packet " << appData->getName() << " over IPv6.\n"; 00513 IPv6ControlInfo *ipControlInfo = new IPv6ControlInfo(); 00514 ipControlInfo->setProtocol(IP_PROT_UDP); 00515 ipControlInfo->setSrcAddr(udpCtrl->getSrcAddr().get6()); 00516 ipControlInfo->setDestAddr(udpCtrl->getDestAddr().get6()); 00517 // ipControlInfo->setInterfaceId(udpCtrl->InterfaceId()); FIXME extend IPv6 with this!!! 00518 udpPacket->setControlInfo(ipControlInfo); 00519 delete udpCtrl; 00520 00521 send(udpPacket,"ipv6Out"); 00522 } 00523 numSent++; 00524 } 00525 00526 UDPPacket *UDP::createUDPPacket(const char *name) 00527 { 00528 return new UDPPacket(name); 00529 } 00530 00531 void UDP::processCommandFromApp(cMessage *msg) 00532 { 00533 UDPControlInfo *udpCtrl = check_and_cast<UDPControlInfo *>(msg->removeControlInfo()); 00534 switch (msg->getKind()) 00535 { 00536 case UDP_C_BIND: 00537 bind(msg->getArrivalGate()->getIndex(), udpCtrl); 00538 break; 00539 case UDP_C_CONNECT: 00540 connect(udpCtrl->getSockId(), udpCtrl->getDestAddr(), udpCtrl->getDestPort()); 00541 break; 00542 case UDP_C_UNBIND: 00543 unbind(udpCtrl->getSockId()); 00544 break; 00545 default: 00546 error("unknown command code (message kind) %d received from app", msg->getKind()); 00547 } 00548 00549 delete udpCtrl; 00550 delete msg; 00551 } 00552 00553