diff --git a/bin/rdpy-rdpclient b/bin/rdpy-rdpclient index eb1103d..ff1a6c6 100755 --- a/bin/rdpy-rdpclient +++ b/bin/rdpy-rdpclient @@ -138,5 +138,4 @@ if __name__ == '__main__': from twisted.internet import reactor reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain)) reactor.runReturn() - app.exec_() - reactor.stop() \ No newline at end of file + app.exec_() \ No newline at end of file diff --git a/bin/rdpy-rdpproxy b/bin/rdpy-rdpproxy index 33d957a..0823375 100755 --- a/bin/rdpy-rdpproxy +++ b/bin/rdpy-rdpproxy @@ -21,8 +21,6 @@ """ RDP proxy recorder and spyer Proxy RDP protocol -With admin account you can watch all currently session -For special session you can record """ import sys, os @@ -43,14 +41,6 @@ class ProxyServer(rdp.RDPServerObserver): rdp.RDPServerObserver.__init__(self, controller) self._client = None - def onReady(self): - """ - Event use to inform state of server stack - Use to connect client - """ - width, height = self._controller.getScreen() - reactor.connectTCP("wav-glw-013", 3389, ProxyClientFactory(self, width, height)) - def clientConnected(self, client): """ Event throw by client when it's ready @@ -58,6 +48,52 @@ class ProxyServer(rdp.RDPServerObserver): """ self._client = client self._controller.setColorDepth(self._client._controller.getColorDepth()) + + def onReady(self): + """ + Event use to inform state of server stack + Use to connect client + """ + width, height = self._controller.getScreen() + reactor.connectTCP("si-hyperv-002", 3389, ProxyClientFactory(self, width, height)) + + def onKeyEventScancode(self, code, isPressed): + """ + Event call when a keyboard event is catch in scan code format + @param code: scan code of key + @param isPressed: True if key is down + """ + #no client connected + if self._client is None: + return + + self._client._controller.sendKeyEventScancode(code, isPressed) + + def onKeyEventUnicode(self, code, isPressed): + """ + Event call when a keyboard event is catch in unicode format + @param code: unicode of key + @param isPressed: True if key is down + """ + #no client connected + if self._client is None: + return + + self._client._controller.sendKeyEventUnicode(code, isPressed) + + def onPointerEvent(self, x, y, button, isPressed): + """ + Event call on mouse event + @param x: x position + @param y: y position + @param button: 1, 2 or 3 button + @param isPressed: True if mouse button is pressed + """ + #no client connected + if self._client is None: + return + + self._client._controller.sendPointerEvent(x, y, button, isPressed) class ProxyClient(rdp.RDPClientObserver): """ diff --git a/rdpy/network/layer.py b/rdpy/network/layer.py index 1109fe2..e6e6242 100644 --- a/rdpy/network/layer.py +++ b/rdpy/network/layer.py @@ -151,6 +151,12 @@ class RawLayer(protocol.Protocol, LayerAutomata, StreamSender): """ #join two scheme self.connect() + + def close(self): + """ + Close raw layer + """ + self.transport.loseConnection() def expect(self, expectedLen, callback = None): """ diff --git a/rdpy/protocol/rdp/gcc.py b/rdpy/protocol/rdp/gcc.py index 58d25e7..a934e71 100644 --- a/rdpy/protocol/rdp/gcc.py +++ b/rdpy/protocol/rdp/gcc.py @@ -240,8 +240,8 @@ class ClientCoreData(CompositeType): self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP) self.clientProductId = UInt16Le(1) self.serialNumber = UInt32Le(0) - self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_15BPP) - self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT) + self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP) + self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT) self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU) self.clientDigProductId = String("\x00"*64, readLen = UInt8(64)) self.connectionType = UInt8() diff --git a/rdpy/protocol/rdp/mcs.py b/rdpy/protocol/rdp/mcs.py index b70a83c..a60cb76 100644 --- a/rdpy/protocol/rdp/mcs.py +++ b/rdpy/protocol/rdp/mcs.py @@ -25,7 +25,7 @@ The main channel is the graphical channel. It exist channel for file system order, audio channel, clipboard etc... """ from rdpy.network.layer import LayerAutomata, StreamSender, Layer -from rdpy.network.type import sizeof, Stream, UInt8, UInt16Le +from rdpy.network.type import sizeof, Stream, UInt8, UInt16Le, String from rdpy.base.error import InvalidExpectedDataException, InvalidValue, InvalidSize from rdpy.protocol.rdp.ber import writeLength import rdpy.base.log as log @@ -137,6 +137,14 @@ class MCSLayer(LayerAutomata): #receive opcode self._receiveOpcode = receiveOpcode + def close(self): + """ + Send disconnect provider ultimatum + """ + self._transport.send((UInt8(self.writeMCSPDUHeader(DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM, 1)), + per.writeEnumerates(0x80), String("\x00" * 6))) + self._transport.close() + def allChannelConnected(self): """ All channels are connected to MCS layer diff --git a/rdpy/protocol/rdp/pdu/data.py b/rdpy/protocol/rdp/pdu/data.py index 2e35aea..0413231 100644 --- a/rdpy/protocol/rdp/pdu/data.py +++ b/rdpy/protocol/rdp/pdu/data.py @@ -783,8 +783,8 @@ class ClientInputEventPDU(CompositeType): class ShutdownRequestPDU(CompositeType): """ - PDU use to signal that the session will be closzed are connected - server -> client + PDU use to signal that the session will be closed + client -> server """ _PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST def __init__(self): @@ -792,7 +792,7 @@ class ShutdownRequestPDU(CompositeType): class ShutdownDeniedPDU(CompositeType): """ - PDU use to signal that the session will be closzed are connected + PDU use to signal which the session will be closed is connected server -> client """ _PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED @@ -842,17 +842,17 @@ class FastPathUpdatePDU(CompositeType): CompositeType.__init__(self) self.updateHeader = UInt8(lambda:updateData.__class__._FASTPATH_UPDATE_TYPE_) self.compressionFlags = UInt8(conditional = lambda:((self.updateHeader.value >> 4) & FastPathOutputCompression.FASTPATH_OUTPUT_COMPRESSION_USED)) - self.size = UInt16Le() + self.size = UInt16Le(lambda:sizeof(self.updateData)) def UpdateDataFactory(): """ Create correct object in accordance to self.updateHeader field """ - if (self.updateHeader.value & 0xf) == FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: - return (UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True), BitmapUpdateDataPDU(readLen = self.size - 2)) - else: - log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf)) - return String() + for c in [FastPathBitmapUpdateDataPDU]: + if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_: + return c() + log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf)) + return String() if updateData is None: updateData = FactoryType(UpdateDataFactory) @@ -867,7 +867,6 @@ class BitmapUpdateDataPDU(CompositeType): @see: http://msdn.microsoft.com/en-us/library/dd306368.aspx """ _UPDATE_TYPE_ = UpdateType.UPDATETYPE_BITMAP - _FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP def __init__(self, readLen = None): """ @@ -946,7 +945,20 @@ class BitmapData(CompositeType): self.bitmapLength = UInt16Le(lambda:(sizeof(self.bitmapComprHdr) + sizeof(self.bitmapDataStream))) self.bitmapComprHdr = BitmapCompressedDataHeader(bodySize = lambda:sizeof(self.bitmapDataStream), scanWidth = lambda:self.width.value, uncompressedSize = lambda:(self.width.value * self.height.value * self.bitsPerPixel.value), conditional = lambda:((self.flags.value | BitmapFlag.BITMAP_COMPRESSION) and not (self.flags.value | BitmapFlag.NO_BITMAP_COMPRESSION_HDR))) self.bitmapDataStream = String(bitmapDataStream, readLen = UInt16Le(lambda:(self.bitmapLength.value if (self.flags.value | BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value))) - + +class FastPathBitmapUpdateDataPDU(CompositeType): + """ + Fast path version of bitmap update PDU + @see: http://msdn.microsoft.com/en-us/library/dd306368.aspx + """ + _FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP + + def __init__(self): + CompositeType.__init__(self) + self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True) + self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array)) + self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles) + class SlowPathInputEvent(CompositeType): """ PDU use in slow-path sending client inputs diff --git a/rdpy/protocol/rdp/pdu/layer.py b/rdpy/protocol/rdp/pdu/layer.py index 0067d36..8633c9d 100644 --- a/rdpy/protocol/rdp/pdu/layer.py +++ b/rdpy/protocol/rdp/pdu/layer.py @@ -64,6 +64,12 @@ class PDUServerListener(object): """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUServerListener")) + def onSlowPathInput(self, slowPathInputEvents): + """ + Event call when slow path input are available + @param slowPathInputEvents: [data.SlowPathInputEvent] + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSlowPathInput", "PDUServerListener")) class PDULayer(LayerAutomata): """ @@ -128,6 +134,8 @@ class Client(PDULayer, tpkt.FastPathListener): """ PDULayer.__init__(self) self._listener = listener + #enable or not fast path + self._fastPathSender = None def connect(self): """ @@ -138,12 +146,22 @@ class Client(PDULayer, tpkt.FastPathListener): self.sendInfoPkt() #next state is license info PDU self.setNextState(self.recvLicenceInfo) + #check if client support fast path message + self._clientFastPathSupported = False def close(self): """ Send PDU close packet and call close method on transport method """ - self.sendDataPDU(data.ShutdownRequestPDU()) + self._transport.close() + #self.sendDataPDU(data.ShutdownRequestPDU()) + + def setFastPathSender(self, fastPathSender): + """ + @param fastPathSender: tpkt.FastPathSender + @note: implement tpkt.FastPathListener + """ + self._fastPathSender = fastPathSender def recvLicenceInfo(self, s): """ @@ -290,7 +308,7 @@ class Client(PDULayer, tpkt.FastPathListener): fastPathPDU = data.FastPathUpdatePDU() fastPathS.readType(fastPathPDU) if fastPathPDU.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: - self._listener.onUpdate(fastPathPDU.updateData[1].rectangles._array) + self._listener.onUpdate(fastPathPDU.updateData.rectangles._array) def readDataPDU(self, dataPDU): """ @@ -333,7 +351,9 @@ class Client(PDULayer, tpkt.FastPathListener): generalCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT - generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED + generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR + if not self._fastPathSender is None: + generalCapability.extraFlags.value |= caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED #init bitmap capability bitmapCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_BITMAP].capability @@ -390,7 +410,7 @@ class Client(PDULayer, tpkt.FastPathListener): pdu.slowPathInputEvents._array = [data.SlowPathInputEvent(x) for x in pointerEvents] self.sendDataPDU(pdu) -class Server(PDULayer): +class Server(PDULayer, tpkt.FastPathListener): """ Server Automata of PDU layer """ @@ -400,6 +420,8 @@ class Server(PDULayer): """ PDULayer.__init__(self) self._listener = listener + #fast path layer + self._fastPathSender = None def connect(self): """ @@ -408,6 +430,13 @@ class Server(PDULayer): """ self.setNextState(self.recvInfoPkt) + def setFastPathSender(self, fastPathSender): + """ + @param fastPathSender: tpkt.FastPathSender + @note: implement tpkt.FastPathListener + """ + self._fastPathSender = fastPathSender + def recvInfoPkt(self, s): """ Receive info packet from client @@ -449,6 +478,9 @@ class Server(PDULayer): for cap in pdu.pduMessage.capabilitySets._array: self._clientCapabilities[cap.capabilitySetType] = cap + #find use full flag + self._clientFastPathSupported = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED + self.setNextState(self.recvClientSynchronizePDU) def recvClientSynchronizePDU(self, s): @@ -538,6 +570,18 @@ class Server(PDULayer): errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo] log.error("INFO PDU : %s"%errorMessage) + elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_INPUT: + self._listener.onSlowPathInput(dataPDU.pduData.slowPathInputEvents._array) + elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_REQUEST: + self._transport.close() + + def recvFastPath(self, fastPathS): + """ + Implement FastPathListener interface + Fast path is needed by RDP 8.0 + @param fastPathS: Stream that contain fast path data + """ + pass def sendLicensingErrorMessage(self): """ @@ -604,7 +648,16 @@ class Server(PDULayer): #check bitmap header for client that want it (very old client) if self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR: for bitmapData in bitmapDatas: - bitmapData.flags.value |= data.BitmapFlag.NO_BITMAP_COMPRESSION_HDR - updateDataPDU = data.BitmapUpdateDataPDU() - updateDataPDU.rectangles._array = bitmapDatas - self.sendDataPDU(data.UpdateDataPDU(updateDataPDU)) \ No newline at end of file + if bitmapData.flags.value & data.BitmapFlag.BITMAP_COMPRESSION: + bitmapData.flags.value |= data.BitmapFlag.NO_BITMAP_COMPRESSION_HDR + + if self._clientFastPathSupported and not self._fastPathSender is None: + #fast path case + fastPathUpdateDataPDU = data.FastPathBitmapUpdateDataPDU() + fastPathUpdateDataPDU.rectangles._array = bitmapDatas + self._fastPathSender.sendFastPath(data.FastPathUpdatePDU(fastPathUpdateDataPDU)) + else: + #slow path case + updateDataPDU = data.BitmapUpdateDataPDU() + updateDataPDU.rectangles._array = bitmapDatas + self.sendDataPDU(data.UpdateDataPDU(updateDataPDU)) \ No newline at end of file diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index 987a712..9a2742a 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -60,6 +60,12 @@ class RDPClientController(pdu.layer.PDUClientListener): @return: color depth set by the server (15, 16, 24) """ return self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value + + def getKeyEventUniCodeSupport(self): + """ + @return: True if server support unicode input + """ + return self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_INPUT].capability.inputFlags.value & pdu.caps.InputFlags.INPUT_FLAG_UNICODE def setPerformanceSession(self): """ @@ -298,6 +304,12 @@ class RDPServerController(pdu.layer.PDUServerListener): #restart connection sequence self._isReady = False self._pduLayer.sendPDU(pdu.data.DeactiveAllPDU()) + + def setKeyEventUnicodeSupport(self): + """ + Enable key event in unicode format + """ + self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_INPUT].capability.inputFlags.value |= pdu.caps.InputFlags.INPUT_FLAG_UNICODE def onReady(self): """ @@ -309,6 +321,31 @@ class RDPServerController(pdu.layer.PDUServerListener): self._sendReady = True for observer in self._serverObserver: observer.onReady() + + def onSlowPathInput(self, slowPathInputEvents): + """ + Event call when slow path input are available + @param slowPathInputEvents: [data.SlowPathInputEvent] + """ + for observer in self._serverObserver: + for event in slowPathInputEvents: + #scan code + if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE: + observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE)) + #unicode + elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE: + observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE)) + #mouse event + elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE: + isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN + button = 0 + if event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON1: + button = 1 + elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON2: + button = 2 + elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3: + button = 3 + observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed) def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): """ @@ -428,4 +465,30 @@ class RDPServerObserver(object): """ Stack is ready and connected """ - raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver")) \ No newline at end of file + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver")) + + def onKeyEventScancode(self, code, isPressed): + """ + Event call when a keyboard event is catch in scan code format + @param code: scan code of key + @param isPressed: True if key is down + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver")) + + def onKeyEventUnicode(self, code, isPressed): + """ + Event call when a keyboard event is catch in unicode format + @param code: unicode of key + @param isPressed: True if key is down + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventUnicode", "RDPServerObserver")) + + def onPointerEvent(self, x, y, button, isPressed): + """ + Event call on mouse event + @param x: x position + @param y: y position + @param button: 1, 2 or 3 button + @param isPressed: True if mouse button is pressed + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver")) \ No newline at end of file diff --git a/rdpy/protocol/rdp/tpkt.py b/rdpy/protocol/rdp/tpkt.py index 2e0fcb5..8b773b1 100644 --- a/rdpy/protocol/rdp/tpkt.py +++ b/rdpy/protocol/rdp/tpkt.py @@ -26,6 +26,14 @@ from rdpy.network.layer import RawLayer from rdpy.network.type import UInt8, UInt16Be, sizeof from rdpy.base.error import CallPureVirtualFuntion +class Action(object): + """ + @see: http://msdn.microsoft.com/en-us/library/cc240621.aspx + @see: http://msdn.microsoft.com/en-us/library/cc240589.aspx + """ + FASTPATH_ACTION_FASTPATH = 0x0 + FASTPATH_ACTION_X224 = 0x3 + class FastPathListener(object): """ Fast path packet listener @@ -36,19 +44,33 @@ class FastPathListener(object): Call when fast path packet is received @param fastPathS: Stream """ - raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "StreamListener")) + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvFastPath", "recvFastPath")) + + def setFastPathSender(self, fastPathSender): + """ + @param fastPathSender: FastPathSender + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "recvFastPath")) -class TPKT(RawLayer): +class FastPathSender(object): + """ + Fast path send capability + """ + def sendFastPath(self, fastPathS): + """ + @param fastPathS: type transform to stream and send as fastpath + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendFastPath", "FastPathSender")) + +class TPKT(RawLayer, FastPathSender): """ TPKT layer in RDP protocol stack This layer only handle size of packet and determine if is a fast path packet """ - #first byte of classic tpkt header - TPKT_PACKET = 3 - def __init__(self, presentation, fastPathListener): """ @param presentation: presentation layer, in RDP case is TPDU layer + @param fastPathListener: FastPathListener """ RawLayer.__init__(self, presentation) #last packet version read from header @@ -57,6 +79,8 @@ class TPKT(RawLayer): self._lastShortLength = UInt8() #fast path listener self._fastPathListener = fastPathListener + #set me as fast path sender + fastPathListener.setFastPathSender(self) def connect(self): """ @@ -77,7 +101,7 @@ class TPKT(RawLayer): #first read packet version data.readType(self._lastPacketVersion) #classic packet - if self._lastPacketVersion.value == TPKT.TPKT_PACKET: + if self._lastPacketVersion.value == Action.FASTPATH_ACTION_X224: #padding data.readType(UInt8()) #read end header @@ -136,4 +160,10 @@ class TPKT(RawLayer): Send encompassed data @param message: network.Type message to send """ - RawLayer.send(self, (UInt8(TPKT.TPKT_PACKET), UInt8(0), UInt16Be(sizeof(message) + 4), message)) \ No newline at end of file + RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_X224), UInt8(0), UInt16Be(sizeof(message) + 4), message)) + + def sendFastPath(self, fastPathS): + """ + @param fastPathS: type transform to stream and send as fastpath + """ + RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS)) \ No newline at end of file