def createResource(self, resource: Resource, parentResource: Resource = None, originator: str = None) -> Result: Logging.logDebug( f'Adding resource ri: {resource.ri}, type: {resource.ty:d}') if parentResource is not None: Logging.logDebug(f'Parent ri: {parentResource.ri}') if not parentResource.canHaveChild(resource): if resource.ty == T.SUB: err = 'Parent resource is not subscribable' Logging.logWarn(err) return Result(rsc=RC.targetNotSubscribable, dbg=err) else: err = f'Invalid child resource type: {T(resource.ty).value}' Logging.logWarn(err) return Result(rsc=RC.invalidChildResourceType, dbg=err) # if not already set: determine and add the srn if resource.__srn__ is None: resource[resource._srn] = Utils.structuredPath(resource) # add the resource to storage if (res := resource.dbCreate(overwrite=False)).rsc != RC.created: return res
def updateResource(self, resource: Resource, dct: JSON = None, doUpdateCheck: bool = True, originator: str = None) -> Result: Logging.logDebug( f'Updating resource ri: {resource.ri}, type: {resource.ty:d}') if doUpdateCheck: if not (res := resource.update(dct, originator)).status: return res.errorResult()
def _copyCSE2CSR(self, target:Resource, source:Resource, isUpdate:bool=False) -> None: def _copyAttribute(attr:str) -> None: if attr in source and attr not in self.excludeCSRAttributes: target[attr] = source[attr] if 'csb' in source and 'csb' not in self.excludeCSRAttributes: target['csb'] = self.registrarCSEURL _copyAttribute('csi') _copyAttribute('cst') _copyAttribute('csz') _copyAttribute('lbl') _copyAttribute('nl') _copyAttribute('poa') _copyAttribute('rr') _copyAttribute('srv') _copyAttribute('st') # if 'csi' in source: # target['csi'] = source.csi # if 'cst' in source: # target['cst'] = source.cst # if 'csz' in source: # target['csz'] = source.csz # if 'lbl' in source: # target['lbl'] = source.lbl # if 'nl' in source: # target['nl'] = source.nl # if 'poa' in source: # target['poa'] = source.poa # if 'rr' in source: # target['rr'] = source.rr # if 'srv' in source: # target['srv'] = source.srv # if 'st' in source: # target['st'] = source.st if 'cb' not in self.excludeCSRAttributes: target['cb'] = f'{source.csi}/{source.rn}' if 'dcse' not in self.excludeCSRAttributes: target['dcse'] = list(self.descendantCSR.keys()) # Always do this bc it might be different, even empty for an update # Always remove some attributes for attr in [ 'acpi' ]: if attr in target: target.delAttribute(attr, setNone = False) # Remove attributes in updates if isUpdate: # rm some attributes for attr in [ 'ri', 'rn', 'ct', 'lt', 'ty', 'cst', 'cb', 'csi']: if attr in target: target.delAttribute(attr, setNone = False)
def deleteResource(self, resource: Resource, originator: str = None, withDeregistration: bool = False, parentResource: Resource = None) -> Result: Logging.logDebug( f'Removing resource ri: {resource.ri}, type: {resource.ty:d}') resource.deactivate(originator) # deactivate it first # Check resource deletion if withDeregistration: if not (res := CSE.registration.checkResourceDeletion(resource)).status: return Result(rsc=RC.badRequest, dbg=res.dbg)
def _addAnnouncementToResource(self, resource: Resource, dct: JSON, csi: str) -> None: """ Add anouncement information to the resource. These are a list of tuples of the csi to which the resource is registered as well as the ri of the resource on the remote CSE. Also, add the reference in the at attribute. """ remoteRI = Utils.findXPath(dct, '{0}/ri') ats = resource[Resource._announcedTo] ats.append((csi, remoteRI)) resource.setAttribute(Resource._announcedTo, ats) # Modify the at attribute if len(at := resource.at) > 0 and csi in at: at[at.index( csi)] = f'{csi}/{remoteRI}' # replace the element in at resource.setAttribute('at', at)
def updateResourceOnCSR(self, resource: Resource, remoteCSR: Resource, remoteRI: str) -> None: """ Update an announced resource to a specific CSR. """ # retrieve the cse & poas for the remote CSR csi, poas = self._getCsiPoaForRemoteCSR(remoteCSR) # TODO: multi-hop announcement Logging.logDebug(f'Update announced resource: {resource.ri} to: {csi}') data = resource.createAnnouncedResourceDict(remoteCSR, isCreate=False, csi=csi) # Get target URL for request if poas is not None and len(poas) > 0: url = f'{poas[0]}/{remoteRI}' # TODO check all available poas. else: Logging.logWarn('Cannot get URL') return # Create the announed resource on the remote CSE Logging.logDebug(f'Updating announced resource at: {csi} url: {url}') res = CSE.request.sendUpdateRequest(url, CSE.cseCsi, data=data) if res.rsc not in [RC.updated, RC.OK]: Logging.logDebug( f'Error updating remote announced resource: {res.rsc:d}') # Ignore and fallthrough Logging.logDebug('Announced resource updated')
def _removeAnnouncementFromResource(self, resource: Resource, csi: str) -> None: """ Remove announcement details from a resource (internal attribute). Modify the internal as well the at attributes to remove the reference to the remote CSE. """ ats = resource[Resource._announcedTo] remoteRI: str = None for x in ats: if x[0] == csi: remoteRI = x[1] ats.remove(x) resource.setAttribute(Resource._announcedTo, ats) # # Modify the at attribute if remoteRI is not None: atCsi = f'{csi}/{remoteRI}' if (at := resource.at) is not None and len(at) > 0 and atCsi in at: at.remove(atCsi) resource.setAttribute('at', at)
def foptRequest(self, operation: int, fopt: Resource, request: Request, id: str, originator: str, ct: str = None, ty: int = None) -> Tuple[Union[Resource, dict], int, str]: """ Handle requests to a fanOutPoint. This method might be called recursivly, when there are groups in groups.""" # get parent / group and check permissions group = fopt.retrieveParentResource() if group is None: return None, C.rcNotFound, 'group resource not found' # get the permission flags for the request operation permission = operationsPermissions[operation] #check access rights for the originator through memberAccessControlPolicies if CSE.security.hasAccess(originator, group, requestedPermission=permission, ty=ty, isCreateRequest=True if operation == C.opCREATE else False) == False: return None, C.rcOriginatorHasNoPrivilege, 'access denied' # get the rqi header field _, _, _, rqi, _ = Utils.getRequestHeaders(request) # check whether there is something after the /fopt ... _, _, tail = id.partition('/fopt/') if '/fopt/' in id else (_, _, '') Logging.logDebug('Adding additional path elements: %s' % tail) # walk through all members result = [] tail = '/' + tail if len( tail) > 0 else '' # add remaining path, if any for mid in group.mid.copy( ): # copy mid list because it is changed in the loop # Try to get the SRN and add the tail if (srn := Utils.structuredPathFromRI(mid)) is not None: mid = srn + tail else: mid = mid + tail # Invoke the request if operation == C.opRETRIEVE: if (res := CSE.dispatcher.handleRetrieveRequest( request, mid, originator))[0] is None: return res
def _getAndCheckNUS(self, subscription: Resource, newJson: dict = None, previousNus: List[str] = None, originator: str = None) -> Tuple[List[str], int, str]: newNus = [] if newJson is None: # If there is no new JSON structure, get the one from the subscription to work with newJson = subscription.asJSON() # Resolve the URI's in the previousNus. if previousNus is not None: if (previousNus := self._getNotificationURLs( previousNus, originator)) is None: # Fail if any of the NU's cannot be retrieved return None, C.rcSubscriptionVerificationInitiationFailed, 'cannot retrieve all previous nu' 's'
def sender(url: str, serialization: ContentSerializationType, targetResource: Resource = None) -> bool: Logging.logDebug( f'Sending notification to: {url}, reason: {reason}') notificationRequest = { 'm2m:sgn': { 'nev': { 'rep': {}, 'net': NotificationEventType.resourceUpdate }, 'sur': Utils.fullRI(sub['ri']) } } data = None nct = sub['nct'] nct == NotificationContentType.all and (data := resource.asDict()) nct == NotificationContentType.ri and (data := { 'm2m:uri': resource.ri }) nct == NotificationContentType.modifiedAttributes and (data := { resource.tpe: modifiedAttributes }) # TODO nct == NotificationContentType.triggerPayload # Add some values to the notification reason is not None and Utils.setXPath(notificationRequest, 'm2m:sgn/nev/net', reason) data is not None and Utils.setXPath(notificationRequest, 'm2m:sgn/nev/rep', data) # Check for batch notifications if sub['bn'] is not None: # TODO implement hasPCH() if targetResource is not None and targetResource.hasPCH( ): # if the target resource has a PCH child resource then that will be the target later url = targetResource.ri return self._storeBatchNotification(url, sub, notificationRequest, serialization) else: return self._sendRequest(url, notificationRequest, serialization=serialization, targetResource=targetResource)
def handleCreator(self, resource: Resource, originator: str) -> Result: """ Check for set creator attribute as well as assign it to allowed resources. """ if resource.hasAttribute('cr'): if resource.ty not in C.creatorAllowed: return Result( rsc=RC.badRequest, dbg= f'"creator" attribute is not allowed for resource type: {resource.ty}' ) if resource.cr is not None: # Check whether cr is set to a value. This is wrong Logging.logWarn('Setting "creator" attribute is not allowed.') return Result(rsc=RC.badRequest, dbg='setting "creator" attribute is not allowed') else: resource['cr'] = originator # fall-through return Result() # implicit OK
def _checkNusInSubscription(self, subscription: Resource, newDict: JSON = None, previousNus: list[str] = None, originator: str = None) -> Result: """ Check all the notification URI's in a subscription. A verification request is sent to new URI's. """ newNus = [] if newDict is None: # If there is no new resource structure, get the one from the subscription to work with newDict = subscription.asDict() # Resolve the URI's in the previousNus. if previousNus is not None: if (previousNus := self._getNotificationURLs( previousNus, originator)) is None: # Fail if any of the NU's cannot be retrieved or accessed return Result(rsc=RC.subscriptionVerificationInitiationFailed, dbg='cannot retrieve all previous nu\'s')
def _sendRequest(self, nu: str, ri: str, jsn: dict, reason: int = None, resource: Resource = None, originator: str = None) -> bool: Utils.setXPath(jsn, 'm2m:sgn/sur', Utils.fullRI(ri)) # Add some values to the notification # TODO: switch statement: (x is not None and bla()) if reason is not None: Utils.setXPath(jsn, 'm2m:sgn/nev/net', reason) if resource is not None: Utils.setXPath(jsn, 'm2m:sgn/nev/rep', resource.asJSON()) if originator is not None: Utils.setXPath(jsn, 'm2m:sgn/cr', originator) _, rc, _ = CSE.httpServer.sendCreateRequest( nu, Configuration.get('cse.csi'), data=json.dumps(jsn)) return rc in [C.rcOK]
def __init__(self, address, name, failure_prob): Thread.__init__(self) super().__init__(address, name) self.resource = Resource(f"{name}-RESOURCE") self.interpreter = ArduinoInterpreter(self) self.failure_prob = failure_prob
class Arduino(Device, Thread): def __init__(self, address, name, failure_prob): Thread.__init__(self) super().__init__(address, name) self.resource = Resource(f"{name}-RESOURCE") self.interpreter = ArduinoInterpreter(self) self.failure_prob = failure_prob def set_resource_on(self): self.resource.set_on() def set_resource_off(self): self.set_resource_off() def update_device_keep_alive(self, device, timestamp): self.devices_status[device] = timestamp def add_new_device(self, new_device, timestamp): self.devices_status[new_device] = timestamp def update_devices_status_list(self, devices_list): self.devices_status.update(devices_list) def send_devices_status_list(self, destination): message = { 'action': 'deviceList', 'origin': self.address, 'list': self.parse_device_status() } self.tcp_client.send_message(destination, self.prepare_message(message)) def keep_alive_to_all(self): message = { 'action': 'keepAlive', 'origin': self.address, 'timestamp': round(time.time()) } device_status_copy = self.devices_status.copy() for device in device_status_copy: address = (device[0], device[1] + 1) self.udp_client.send_message(address, self.prepare_message(message)) with open('log', 'a') as log: log.write( f'{datetime.now()}: {self.name} (keep-alive) -> {address}\n' ) def call_to_action(self, message, client): self.interpreter.interprets_message(message, client) def parse_device_status(self): parsed = {} for device in self.devices_status.keys(): host, port = device key = ":".join([host, str(port)]) parsed[key] = self.devices_status[device] return parsed def run(self): while True: time.sleep(.5) self.keep_alive_to_all() if random.random() < self.failure_prob: with open('log', 'a') as log: log.write(f'{datetime.now()}: {self.name} (FAILED)\n') time.sleep(10)