Ejemplo n.º 1
0
    def enterSetAvatar(self):
        channel = self.mgr.GetAccountConnectionChannel(self.target)

        datagramCleanup = PyDatagram()
        datagramCleanup.addServerHeader(self.playerId, channel, STATESERVER_OBJECT_DELETE_RAM)
        datagramCleanup.addUint32(self.playerId)
        datagram = PyDatagram()
        datagram.addServerHeader(channel, self.air.ourChannel, CLIENTAGENT_ADD_POST_REMOVE)
        datagram.addUint16(datagramCleanup.getLength())
        datagram.appendData(datagramCleanup.getMessage())
        self.air.send(datagram)

        # Activate the avatar on the DBSS:
        self.air.sendActivate(self.playerId, 0, 0, self.air.dclassesByName['DistributedPlayerUD'],
                              fields={'setName': [self.token]})

        # Next, add them to the avatar channel:
        datagram = PyDatagram()
        datagram.addServerHeader(channel, self.air.ourChannel, CLIENTAGENT_OPEN_CHANNEL)
        datagram.addChannel(self.mgr.GetPuppetConnectionChannel(self.target))
        self.air.send(datagram)

        # Finally, grant ownership and shut down.
        self.air.setOwner(self.playerId, self.target)

        self.air.clientAddSessionObject(self.target, self.playerId)

        # Now set their sender channel to represent their account affiliation:
        datagram = PyDatagram()
        datagram.addServerHeader(self.target, self.air.ourChannel, CLIENTAGENT_SET_CLIENT_ID)
        datagram.addChannel(self.mgr.GetPuppetConnectionChannel(self.playerId))
        self.air.send(datagram)
        self.demand('Done')
Ejemplo n.º 2
0
    def process_modules(self):
        '''
        This method is called when it's time to write the output.

        For nuggets.nri, we use an encrypted datagram.
        The datagram is read by core.cxx, which populates Python frozen array.

        Datagram format:

        uint32 numModules
        for each module:
            string name
            int32 size *
            data(abs(size))

        * Negative size means the file was an __init__
        '''

        dg = PyDatagram()
        dg.addUint32(len(self.modules))

        for moduleName in self.modules:
            data, size = self.modules[moduleName]

            dg.addString(moduleName)
            dg.addInt32(size)
            dg.appendData(data)

        data = dg.getMessage()

        iv = '\0' * 16
        key = 'AmazingNuggetKey'
        return aes.encrypt(data, key, iv)
Ejemplo n.º 3
0
    def enterSetAccount(self):
        channel = self.mgr.GetAccountConnectionChannel(self.accountId)

        datagram = PyDatagram()
        datagram.addServerHeader(channel, self.mgr.air.ourChannel, CLIENTAGENT_EJECT)
        datagram.addUint16(100)
        datagram.appendData(b'This account has been logged in from elsewhere.')
        self.mgr.air.send(datagram)

        # Next, add this connection to the account channel.
        datagram = PyDatagram()
        datagram.addServerHeader(self.target, self.mgr.air.ourChannel, CLIENTAGENT_OPEN_CHANNEL)
        datagram.addChannel(channel)
        self.mgr.air.send(datagram)

        # when this dg is added, the accId is correct, but avId=0
        datagram = PyDatagram()
        datagram.addServerHeader(self.target, self.air.ourChannel, CLIENTAGENT_SET_CLIENT_ID)
        datagram.addChannel(self.accountId << 32)
        self.air.send(datagram)

        # Un-sandbox them!
        datagram = PyDatagram()
        datagram.addServerHeader(self.target, self.mgr.air.ourChannel, CLIENTAGENT_SET_STATE)
        datagram.addUint16(2)  # ESTABLISHED
        self.mgr.air.send(datagram)

        self.mgr.sendUpdateToChannel(self.target, 'loginResponse', [LOGIN_SUCCESS])
        self.demand('SetAvatar')
Ejemplo n.º 4
0
 def route_message(self, di, datagram, reciever_channel, sender_channel): 
     dg = PyDatagram()
     dg.addUint64(reciever_channel)
     dg.addUint64(sender_channel)
     dg.addUint16(di.getUint16())
     dg.appendData(di.getRemainingBytes())
     self.cw.send(dg, self.interface.registeredParticipants[reciever_channel])
     dg.clear()
Ejemplo n.º 5
0
 def route_message(self, di, datagram, reciever_channel, sender_channel):
     dg = PyDatagram()
     dg.addUint64(reciever_channel)
     dg.addUint64(sender_channel)
     dg.addUint16(di.getUint16())
     dg.appendData(di.getRemainingBytes())
     self.cw.send(dg,
                  self.interface.registeredParticipants[reciever_channel])
     dg.clear()
Ejemplo n.º 6
0
    class ClientSnapshotObject:
        def __init__(self, doId):
            self.doId = doId
            self.dg = PyDatagram()
            self.numUpdates = 0

        def addUpdate(self, dg):
            self.dg.appendData(dg.getData(), dg.getLength())
            self.numUpdates += 1
Ejemplo n.º 7
0
    def sendActivate(self, doId, parentId, zoneId, dclass=None, fields=None):
        """
        Activate a DBSS object, given its doId, into the specified parentId/zoneId.

        If both dclass and fields are specified, an ACTIVATE_WITH_DEFAULTS_OTHER
        will be sent instead. In other words, the specified fields will be
        auto-applied during the activation.
        """

        fieldPacker = DCPacker()
        fieldCount = 0
        if dclass and fields:
            for k, v in fields.items():
                field = dclass.getFieldByName(k)
                if not field:
                    self.notify.error(
                        'Activation request for %s object contains '
                        'invalid field named %s' % (dclass.getName(), k))

                fieldPacker.rawPackUint16(field.getNumber())
                fieldPacker.beginPack(field)
                field.packArgs(fieldPacker, v)
                fieldPacker.endPack()
                fieldCount += 1

            dg = PyDatagram()
            dg.addServerHeader(doId, self.ourChannel,
                               DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS)
            dg.addUint32(doId)
            dg.addUint32(0)
            dg.addUint32(0)
            self.send(dg)
            # DEFAULTS_OTHER isn't implemented yet, so we chase it with a SET_FIELDS
            dg = PyDatagram()
            dg.addServerHeader(doId, self.ourChannel,
                               STATESERVER_OBJECT_SET_FIELDS)
            dg.addUint32(doId)
            dg.addUint16(fieldCount)
            dg.appendData(fieldPacker.getString())
            self.send(dg)
            # Now slide it into the zone we expect to see it in (so it
            # generates onto us with all of the fields in place)
            dg = PyDatagram()
            dg.addServerHeader(doId, self.ourChannel,
                               STATESERVER_OBJECT_SET_LOCATION)
            dg.addUint32(parentId)
            dg.addUint32(zoneId)
            self.send(dg)
        else:
            dg = PyDatagram()
            dg.addServerHeader(doId, self.ourChannel,
                               DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS)
            dg.addUint32(doId)
            dg.addUint32(parentId)
            dg.addUint32(zoneId)
            self.send(dg)
Ejemplo n.º 8
0
    def handleClientCreateObject(self, datagram, dgi):
        """ client wants to create an object, so we store appropriate
        data, and then pass message along to corresponding zones """

        connection = datagram.getConnection()
        zoneId = dgi.getUint32()
        classId = dgi.getUint16()
        doId = dgi.getUint32()

        client = self.clientsByConnection[connection]

        if self.getDoIdBase(doId) != client.doIdBase:
            self.notify.warning(
                "Ignoring attempt to create invalid doId %s from client %s" %
                (doId, client.doIdBase))
            return

        dclass = self.dclassesByNumber[classId]

        object = client.objectsByDoId.get(doId)
        if object:
            # This doId is already in use; thus, this message is
            # really just an update.
            if object.dclass != dclass:
                self.notify.warning(
                    "Ignoring attempt to change object %s from %s to %s by client %s"
                    % (doId, object.dclass.getName(), dclass.getName(),
                       client.doIdBase))
                return
            self.setObjectZone(client, object, zoneId)
        else:
            if self.notify.getDebug():
                self.notify.debug(
                    "Creating object %s of type %s by client %s" %
                    (doId, dclass.getName(), client.doIdBase))

            object = self.Object(doId, zoneId, dclass)
            client.objectsByDoId[doId] = object
            client.objectsByZoneId.setdefault(zoneId, set()).add(object)
            self.objectsByZoneId.setdefault(zoneId, set()).add(object)

            self.updateClientInterestZones(client)

        # Rebuild the new datagram that we'll send on.  We shim in the
        # doIdBase of the owner.
        dg = PyDatagram()
        dg.addUint16(OBJECT_GENERATE_CMU)
        dg.addUint32(client.doIdBase)
        dg.addUint32(zoneId)
        dg.addUint16(classId)
        dg.addUint32(doId)
        dg.appendData(dgi.getRemainingBytes())

        self.sendToZoneExcept(zoneId, dg, [client])
Ejemplo n.º 9
0
    def sendClientSnapshots(self):
        for client, snapshot in self.clientSnapshots.items():

            dg = PyDatagram()
            dg.addUint16(AI_SNAPSHOT_CMU)
            numObjects = len(snapshot.objects)
            dg.addUint16(numObjects)
            for doId, obj in snapshot.objects.items():
                dg.addUint32(doId)
                dg.addUint16(obj.numUpdates)
                dg.appendData(obj.dg.getData(), obj.dg.getLength())
Ejemplo n.º 10
0
    def handleClientCreateObject(self, datagram, dgi):
        """ client wants to create an object, so we store appropriate
        data, and then pass message along to corresponding zones """

        connection = datagram.getConnection()
        zoneId  = dgi.getUint32()
        classId = dgi.getUint16()
        doId    = dgi.getUint32()

        client = self.clientsByConnection[connection]

        if self.getDoIdBase(doId) != client.doIdBase:
            self.notify.warning(
                "Ignoring attempt to create invalid doId %s from client %s" % (doId, client.doIdBase))
            return

        dclass = self.dclassesByNumber[classId]

        object = client.objectsByDoId.get(doId)
        if object:
            # This doId is already in use; thus, this message is
            # really just an update.
            if object.dclass != dclass:
                self.notify.warning(
                    "Ignoring attempt to change object %s from %s to %s by client %s" % (
                    doId, object.dclass.getName(), dclass.getName(), client.doIdBase))
                return
            self.setObjectZone(client, object, zoneId)
        else:
            if self.notify.getDebug():
                self.notify.debug(
                    "Creating object %s of type %s by client %s" % (
                    doId, dclass.getName(), client.doIdBase))

            object = self.Object(doId, zoneId, dclass)
            client.objectsByDoId[doId] = object
            client.objectsByZoneId.setdefault(zoneId, set()).add(object)
            self.objectsByZoneId.setdefault(zoneId, set()).add(object)

            self.updateClientInterestZones(client)


        # Rebuild the new datagram that we'll send on.  We shim in the
        # doIdBase of the owner.
        dg = PyDatagram()
        dg.addUint16(OBJECT_GENERATE_CMU)
        dg.addUint32(client.doIdBase)
        dg.addUint32(zoneId)
        dg.addUint16(classId)
        dg.addUint32(doId)
        dg.appendData(dgi.getRemainingBytes())

        self.sendToZoneExcept(zoneId, dg, [client])
Ejemplo n.º 11
0
    def addPostRemove(self, dg):
        """
        Register a datagram with the Message Director that gets sent out if the
        connection is ever lost.

        This is useful for registering cleanup messages: If the Panda3D process
        ever crashes unexpectedly, the Message Director will detect the socket
        close and automatically process any post-remove datagrams.
        """

        dg2 = PyDatagram()
        dg2.addServerControlHeader(CONTROL_ADD_POST_REMOVE)
        dg2.addUint64(self.ourChannel)
        dg2.appendData(dg.getMessage())
        self.send(dg2)
Ejemplo n.º 12
0
	def routeMessage(self, channel, datagram):
		if not datagram:
			return

		di = PyDatagramIterator(datagram)
		# The message director used this value for determining which process the connection requested, but
		# we don't want to send this value over the network because its unnecessary.
		di.skipBytes(16)
		# This is the channel the data is being routed to. We need this but, does the client really need
		# to be informed of its own channel?
		di.skipBytes(64)

		datagram = PyDatagram()
		# Pack the remaining bytes and ship the message out.
		datagram.appendData(di.getRemainingBytes())
		self.routeMessageToChannel(channel, datagram)
Ejemplo n.º 13
0
    def sendUpdateToChannel(self, distObj, channelId, fieldName, args):
        """ Sends a targeted update of a single field to a particular
        client.  The top 32 bits of channelId is ignored; the lower 32
        bits should be the client Id of the recipient (i.e. the
        client's doIdbase).  The field update will be sent to the
        indicated client only.  The field must be marked clsend or
        p2p, and may not be marked broadcast. """

        datagram = distObj.dclass.clientFormatUpdate(fieldName, distObj.doId,
                                                     args)
        dgi = PyDatagramIterator(datagram)

        # Reformat the packed datagram to change the message type and
        # add the target id.
        dgi.getUint16()

        dg = PyDatagram()
        dg.addUint16(CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU)
        dg.addUint32(channelId & 0xffffffff)
        dg.appendData(dgi.getRemainingBytes())

        self.send(dg)
    def createObject(self, databaseId, dclass, fields={}, callback=None):
        """
        Create an object in the specified database.

        databaseId specifies the control channel of the target database.
        dclass specifies the class of the object to be created.
        fields is a dict with any fields that should be stored in the object on creation.
        callback will be called with callback(doId) if specified. On failure, doId is 0.
        """

        # Save the callback:
        ctx = self.air.getContext()
        self._callbacks[ctx] = callback

        # Pack up/count valid fields.
        fieldPacker = DCPacker()
        fieldCount = 0
        for k, v in fields.items():
            field = dclass.getFieldByName(k)
            if not field:
                self.notify.error('Creation request for %s object contains '
                                  'invalid field named %s' %
                                  (dclass.getName(), k))

            fieldPacker.rawPackUint16(field.getNumber())
            fieldPacker.beginPack(field)
            field.packArgs(fieldPacker, v)
            fieldPacker.endPack()
            fieldCount += 1

        # Now generate and send the datagram:
        dg = PyDatagram()
        dg.addServerHeader(databaseId, self.air.ourChannel,
                           DBSERVER_CREATE_OBJECT)
        dg.addUint32(ctx)
        dg.addUint16(dclass.getNumber())
        dg.addUint16(fieldCount)
        dg.appendData(fieldPacker.getString())
        self.air.send(dg)
Ejemplo n.º 15
0
    def handleClientObjectUpdateField(self, datagram, dgi, targeted=False):
        """ Received an update request from a client. """
        connection = datagram.getConnection()
        client = self.clientsByConnection[connection]

        if targeted:
            targetId = dgi.getUint32()
        doId = dgi.getUint32()
        fieldId = dgi.getUint16()

        doIdBase = self.getDoIdBase(doId)
        owner = self.clientsByDoIdBase.get(doIdBase)
        object = owner and owner.objectsByDoId.get(doId)
        if not object:
            self.notify.warning(
                "Ignoring update for unknown object %s from client %s" %
                (doId, client.doIdBase))
            return

        dcfield = object.dclass.getFieldByIndex(fieldId)
        if dcfield == None:
            self.notify.warning(
                "Ignoring update for field %s on object %s from client %s; no such field for class %s."
                % (fieldId, doId, client.doIdBase, object.dclass.getName()))

        if client != owner:
            # This message was not sent by the object's owner.
            if not dcfield.hasKeyword('clsend') and not dcfield.hasKeyword(
                    'p2p'):
                self.notify.warning(
                    "Ignoring update for %s.%s on object %s from client %s: not owner"
                    % (object.dclass.getName(), dcfield.getName(), doId,
                       client.doIdBase))
                return

        # We reformat the message slightly to insert the sender's
        # doIdBase.
        dg = PyDatagram()
        dg.addUint16(OBJECT_UPDATE_FIELD_CMU)
        dg.addUint32(client.doIdBase)
        dg.addUint32(doId)
        dg.addUint16(fieldId)
        dg.appendData(dgi.getRemainingBytes())

        if targeted:
            # A targeted update: only to the indicated client.
            target = self.clientsByDoIdBase.get(targetId)
            if not target:
                self.notify.warning(
                    "Ignoring targeted update to %s for %s.%s on object %s from client %s: target not known"
                    % (targetId, dclass.getName(), dcfield.getName(), doId,
                       client.doIdBase))
                return
            self.cw.send(dg, target.connection)
            self.needsFlush.add(target)

        elif dcfield.hasKeyword('p2p'):
            # p2p: to object owner only
            self.cw.send(dg, owner.connection)
            self.needsFlush.add(owner)

        elif dcfield.hasKeyword('broadcast'):
            # Broadcast: to everyone except orig sender
            self.sendToZoneExcept(object.zoneId, dg, [client])

        elif dcfield.hasKeyword('reflect'):
            # Reflect: broadcast to everyone including orig sender
            self.sendToZoneExcept(object.zoneId, dg, [])

        else:
            self.notify.warning("Message is not broadcast or p2p")
Ejemplo n.º 16
0
    def handleClientObjectUpdateField(self, datagram, dgi, targeted=False):
        """ Received an update request from a client. """
        connection = datagram.getConnection()
        client = self.clientsByConnection[connection]

        if targeted:
            targetId = dgi.getUint32()
        doId = dgi.getUint32()
        fieldId = dgi.getUint16()

        doIdBase = self.getDoIdBase(doId)
        owner = self.clientsByDoIdBase.get(doIdBase)
        object = owner and owner.objectsByDoId.get(doId)
        if not object:
            self.notify.warning("Ignoring update for unknown object %s from client %s" % (doId, client.doIdBase))
            return

        dcfield = object.dclass.getFieldByIndex(fieldId)
        if dcfield == None:
            self.notify.warning(
                "Ignoring update for field %s on object %s from client %s; no such field for class %s."
                % (fieldId, doId, client.doIdBase, object.dclass.getName())
            )

        if client != owner:
            # This message was not sent by the object's owner.
            if not dcfield.hasKeyword("clsend") and not dcfield.hasKeyword("p2p"):
                self.notify.warning(
                    "Ignoring update for %s.%s on object %s from client %s: not owner"
                    % (object.dclass.getName(), dcfield.getName(), doId, client.doIdBase)
                )
                return

        # We reformat the message slightly to insert the sender's
        # doIdBase.
        dg = PyDatagram()
        dg.addUint16(OBJECT_UPDATE_FIELD_CMU)
        dg.addUint32(client.doIdBase)
        dg.addUint32(doId)
        dg.addUint16(fieldId)
        dg.appendData(dgi.getRemainingBytes())

        if targeted:
            # A targeted update: only to the indicated client.
            target = self.clientsByDoIdBase.get(targetId)
            if not target:
                self.notify.warning(
                    "Ignoring targeted update to %s for %s.%s on object %s from client %s: target not known"
                    % (targetId, dclass.getName(), dcfield.getName(), doId, client.doIdBase)
                )
                return
            self.cw.send(dg, target.connection)
            self.needsFlush.add(target)

        elif dcfield.hasKeyword("p2p"):
            # p2p: to object owner only
            self.cw.send(dg, owner.connection)
            self.needsFlush.add(owner)

        elif dcfield.hasKeyword("broadcast"):
            # Broadcast: to everyone except orig sender
            self.sendToZoneExcept(object.zoneId, dg, [client])

        elif dcfield.hasKeyword("reflect"):
            # Reflect: broadcast to everyone including orig sender
            self.sendToZoneExcept(object.zoneId, dg, [])

        else:
            self.notify.warning("Message is not broadcast or p2p")
    def updateObject(self,
                     databaseId,
                     doId,
                     dclass,
                     newFields,
                     oldFields=None,
                     callback=None):
        """
        Update field(s) on an object, optionally with the requirement that the
        fields must match some old value.

        databaseId and doId represent the database control channel and object ID
        for the update request.
        newFields is to be a dict of fieldname->value, representing the fields
        to add/change on the database object.
        oldFields, if specified, is a similarly-formatted dict that contains the
        expected older values. If the values do not match, the database will
        refuse to process the update. This is useful for guarding against race
        conditions.

        On success, the callback is called as callback(None).
        On failure, the callback is called as callback(dict), where dict contains
        the current object values. This is so that the updater can try again,
        basing its updates off of the new values.
        """

        # Ensure that the keys in newFields and oldFields are the same if
        # oldFields is given...
        if oldFields is not None:
            if set(newFields.keys()) != set(oldFields.keys()):
                self.notify.error(
                    'newFields and oldFields must contain the same keys!')
                return

        fieldPacker = DCPacker()
        fieldCount = 0
        for k, v in newFields.items():
            field = dclass.getFieldByName(k)
            if not field:
                self.notify.error('Update for %s(%d) object contains invalid'
                                  ' field named %s' %
                                  (dclass.getName(), doId, k))

            fieldPacker.rawPackUint16(field.getNumber())

            if oldFields is not None:
                # Pack the old values:
                fieldPacker.beginPack(field)
                field.packArgs(fieldPacker, oldFields[k])
                fieldPacker.endPack()

            fieldPacker.beginPack(field)
            field.packArgs(fieldPacker, v)
            fieldPacker.endPack()

            fieldCount += 1

        # Generate and send the datagram:
        dg = PyDatagram()
        if oldFields is not None:
            ctx = self.air.getContext()
            self._callbacks[ctx] = callback
            if fieldCount == 1:
                dg.addServerHeader(databaseId, self.air.ourChannel,
                                   DBSERVER_OBJECT_SET_FIELD_IF_EQUALS)
            else:
                dg.addServerHeader(databaseId, self.air.ourChannel,
                                   DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS)
            dg.addUint32(ctx)
        else:
            if fieldCount == 1:
                dg.addServerHeader(databaseId, self.air.ourChannel,
                                   DBSERVER_OBJECT_SET_FIELD)
            else:
                dg.addServerHeader(databaseId, self.air.ourChannel,
                                   DBSERVER_OBJECT_SET_FIELDS)
        dg.addUint32(doId)
        if fieldCount != 1:
            dg.addUint16(fieldCount)
        dg.appendData(fieldPacker.getString())
        self.air.send(dg)

        if oldFields is None and callback is not None:
            # Why oh why did they ask for a callback if there's no oldFields?
            # Oh well, better honor their request:
            callback(None)