### py_interface -- A Python-implementation of an Erlang node ### ### $Id: erl_epmd.py,v 1.4 2002/05/28 22:09:24 tab Exp $ ### ### Copyright (C) 2002 Tomas Abrahamsson ### ### Author: Tomas Abrahamsson ### ### This file is part of the Py-Interface library ### ### This library is free software; you can redistribute it and/or ### modify it under the terms of the GNU Library General Public ### License as published by the Free Software Foundation; either ### version 2 of the License, or (at your option) any later version. ### ### This library is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ### Library General Public License for more details. ### ### You should have received a copy of the GNU Library General Public ### License along with this library; if not, write to the Free ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ### erl_epmd.py -- handle the communication with the epmd ### (the Erlang portmapper daemon) import sys import types import string import socket import getopt import erl_common import erl_async_conn import erl_eventhandler NODETYPE_NORMAL = 77 NODETYPE_HIDDEN = 72 M = "erl_epmd" class ErlEpmd: """This class provides a connection to the Erlang Portmapper Daemon, EPMD. """ def __init__(self, hostName="localhost", portNum=4369): """Constructor. HOST-NAME = string Default is "localhost" PORT-NUM = integer Default is 4369 Creates an instance for communicating with the EPMD on HOST-NAME on PORT-NUM. Use the Connect method to establish the actual connection to the EPMD. """ self._hostName = hostName self._portNum = portNum self.connection = None def SetOwnPortNum(self, ownPortNum): """Specify the port number for the node OWN-PORT-NUM = integer Specify the port number for the socket that serves incoming connections to the node. This is the portnumber to be published to the EPMD. Other nodes looks up the nodes portnumber in the EPMD, then establishes connections to that port. """ self._ownPortNum = ownPortNum def SetOwnNodeName(self, nodeName): """Specify the node name for the node NODE-NAME = string Must be on the form alivename@hostname Sets the nodes name. This is the node name that is published in the EPMD. """ ## XXX Ought to change the scheme: get the host from the node-name ## instead of from an argument to the constructor? self._ownNodeName = nodeName def Connect(self, connectedCb, connectFailedCb): """Connect to the EPMD and issue an Alive2 request. CONNECTED-CB = CREATION = integer CONNECT-FAILED-CB = RESULT = integer Connects to the EPMD specified in the constructor, then publishes the own node name and port by issuing an Alive2 request. The SetOwnPortNum and SetOwnNodeName methods must have been called prior to calling this method. """ self._connectedCb = connectedCb self._connectFailedCb = connectFailedCb self._epmdConn = ErlEPMDStdConnection() if not self._epmdConn.Connect(self._hostName, self._portNum): raise "Connection to EPMD failed" self.Alive2Req(self._ownPortNum, NODETYPE_HIDDEN, (5, 5), self._ownNodeName, "", self._Alive2RespCb) def Close(self): """Close the connection to the EPMD.""" self.AliveCloseReq(self._AliveCloseSink) ## Requests ## def AliveReq(self, portNum, nodeName, cb): """Issue an Alive request. PORT-NUM = integer The port number of the socket that serves incoming connections to the node. NODE-NAME = string the name of the node (on the form alive@host) This request has no callback. """ self._epmdConn.AliveReq(portNum, nodeName, cb) def Alive2Req(self, portNum, nodeType, distrVSNRange, nodeName, extra, cb): """Issue an Alive2 request. PORT-NUM = integer The port number of the socket that serves incoming connections to the node. NODE-TYPE = integer DISTR-VSN-RANGE = tuple(LO, HI) LO = HI = integer NODE-NAME = string the name of the node (on the form alive@host) EXTRA = string CB = CREATION = integer Calls the callback CB when the answer is available. See the file distribution_handshake.txt in the erlang distribution for more info on the NODE-TYPE, DISTR-VSN-RANGE and EXTRA arguments. """ self._epmdConn.Alive2Req(portNum, nodeType, distrVSNRange, nodeName, extra, cb) def AliveCloseReq(self, cb): """Issue an AliveClose request. CB = This effectively closes the socket connection to the EPMD. """ self._epmdConn.AliveCloseReq(cb) def PortPleaseReq(self, nodeName, callback): """Issue a PortPlease request. NODE-NAME = string CALLBACK = PORT-NUMBER = integer Calls the CALLBACK function with argument PORT-NUMBER when the answer is available. """ e = ErlEPMDOneShotConnection(self._hostName, self._portNum) e.PortPleaseReq(nodeName, callback) def PortPlease2Req(self, nodeName, callback): """Issue a PortPlease2 request. NODE-NAME = string CALLBACK = RESULT = 1 | 0 NODE-TYPE = integer PROTOCOL = integer DISTR-VSN-RANGE = tuple(LO, HI) LO = HI = integer RNODE-NAME = string EXTRA = string Calls the CALLBACK function when the answer is available from the EPMD. If the RESULT is 0, then the values of the rest of the arguments are undefined. If the result is 1, then the rest of the arguments have the values as reported from the EPMD. Calls the CALLBACK function with argument PORT-NUMBER when the answer is available. """ e = ErlEPMDOneShotConnection(self._hostName, self._portNum) e.PortPlease2Req(nodeName, callback) def NamesReq(self, callback): """Issue a Names request CALLBACK = PORT-NUMBER = integer Calls the CALLBACK function with argument PORT-NUMBER when the answer is available. """ msg = self.PackInt1(self._PORT_PLEASE_REQ) + nodeName unpackcb = erl_common.Callback(self._UnpackPortPleaseResp, callback) self._SendOneShotReq(msg, unpackcb) def PortPlease2Req(self, nodeName, callback): """Issue a PortPlease2 request. NODE-NAME = string CALLBACK = RESULT = 1 | 0 NODE-TYPE = integer PROTOCOL = integer DISTR-VSN-RANGE = tuple(LO, HI) LO = HI = integer RNODE-NAME = string EXTRA = string Calls the CALLBACK function when the answer is available from the EPMD. If the RESULT is 0, then the values of the rest of the arguments are undefined. If the result is 1, then the rest of the arguments have the values as reported from the EPMD. Calls the CALLBACK function with argument PORT-NUMBER when the answer is available. """ msg = self.PackInt1(self._PORT_PLEASE2_REQ) + nodeName unpackcb = erl_common.Callback(self._UnpackPortPlease2Resp, callback) self._SendOneShotReq(msg, unpackcb) def NamesReq(self, callback): """Issue a Names request CALLBACK = CREATION = integer Calls the callback CB when the answer is available. See the file distribution_handshake.txt in the erlang distribution for more info on the NODE-TYPE, DISTR-VSN-RANGE and EXTRA arguments. """ aliveName = string.split(nodeName, "@")[0] msg = (self.PackInt1(self._ALIVE2_REQ) + self.PackInt2(portNum) + self.PackInt1(nodeType) + self.PackInt1(0) + # protocol: 0 = tcp/ip-v4 self.PackInt2(distrVSNRange[0]) + self.PackInt2(distrVSNRange[1]) + self.PackInt2(len(aliveName)) + aliveName + self.PackInt2(len(extra)) + extra) self._SendReq(msg, cb) def AliveReq(self, portNum, nodeName): """Issue an Alive request. PORT-NUM = integer The port number of the socket that serves incoming connections to the node. NODE-NAME = string the name of the node (on the form alive@host) This request has no callback. """ msg = (self.PackInt1(self._ALIVE_REQ) + self.PackInt2(portNum) + nodeName) self._SendReq(msg, cb) def AliveCloseReq(self, cb): """Issue an AliveClose request. CB = This effectively closes the socket connection to the EPMD. """ self.Close() cb() ## ## Internal routines ## ## ## Sending ## def _SendReq(self, req, cb): if not self._isConnected: raise "not connected to epmd" self._NewCurrReq(ord(req[0]), cb) msg = self.PackInt2(len(req)) + req self.Send(msg) def _NewCurrReq(self, reqId, cb): self._currentRequests.append((reqId, cb)) def _GetCurrReqId(self): if len(self._currentRequests) == 0: return None else: return self._currentRequests[0][0] def _GetCurrReqCb(self): if len(self._currentRequests) == 0: return None else: return self._currentRequests[0][1] def _CurrReqDone(self): self._currentRequests = self._currentRequests[1:] ## ## Handling incoming data ## def _In(self): is_closed = 0 data = self._connection.recv(100000) if len(data) == 0: # closed connection is_closed = 1 newInput = self._pendingInput + data # split into chunks separated by newline sequence # call the callback for each of these chunks newPendingInput = self._HandleMsgs(newInput) self._pendingInput = newPendingInput if is_closed: self._OtherEndClosedConnection() def _OtherEndClosedConnection(self): if self._GetCurrReqId() == self._ALIVE_REQ: # alive_req self.AliveNotOkResp() self._CurrReqDone() else: self.ConnectionClosed() ## close our end self.Close() def _HandleMsgs(self, input): toBeUnpacked = input while 1: (parsedOk, remainingInput) = self._HandleMsg(toBeUnpacked) if not parsedOk: return remainingInput else: self._CurrReqDone() toBeUnpacked = remainingInput def _HandleMsg(self, data): dataLen = len(data) if dataLen < 3: return (0, data) data0 = ord(data[0]) if data0 == self._ALIVE_OK_RESP and \ self._GetCurrReqId() == self._ALIVE_REQ: if dataLen < 3: return (0, data) creation = self.ReadInt2(data[1:3]) cb = self._GetCurrReqCb() cb(creation) self._CurrReqDone() return (1, data[3:]) elif data0 == self._ALIVE2_RESP and \ self._GetCurrReqId() == self._ALIVE2_REQ: if dataLen < 4: return (0, data) result = self.ReadInt1(data[1]) creation = self.ReadInt2(data[2:4]) cb = self._GetCurrReqCb() cb(result, creation) self._CurrReqDone() return (1, data[4:]) currReqTxt = "current request is %s" % `self._GetCurrReqId()` erl_common.DebugHex(M, "unexpected msg, trown away, "+currReqTxt, data) return (0, "")