def build_request(request, headers, device): """ Helper method to organized required headers into the CoAP Request. :param request: Request being build :param headers: Data from AMQP message which contains data to forward OpenC2 Command. :param device: Device specific data from headers sent by O.I.F. """ orc_host, orc_port = headers["transport"]["socket"].split( ":", 1) # location of orchestrator-side CoAP server request.source = (orc_host, safe_cast(orc_port, int, 5683)) dev_host, dev_port = device["socket"].split( ":", 1) # location of device-side CoAP server request.destination = (dev_host, safe_cast(dev_port, int, 5683)) encoding = f"application/{device['encoding']}" # Content Serialization request.content_type = defines.Content_types[ encoding] # using application/json, TODO: add define to openc2+json request.mid = int("0x" + headers["correlationID"], 16) # 16-bit correlationID request.timestamp = headers[ "date"] # time message was sent from orchestrator # Add OIF-unique value used for routing to the desired actuator profile = Option() profile.number = 8 profile.value = device.get("profile", "")[0] request.add_option(profile) source_socket = Option() source_socket.number = 3 source_socket.value = headers["transport"]["socket"] request.add_option(source_socket) return request
def block2(self, value): """ Set the Block2 option. :param value: the Block2 value """ option = Option() option.number = defines.OptionRegistry.BLOCK2.number num, m, size = value if size > 512: szx = 6 elif 256 < size <= 512: szx = 5 elif 128 < size <= 256: szx = 4 elif 64 < size <= 128: szx = 3 elif 32 < size <= 64: szx = 2 elif 16 < size <= 32: szx = 1 else: szx = 0 value = (num << 4) value |= (m << 3) value |= szx option.value = value self.add_option(option)
def if_match(self, values): assert isinstance(values, list) for v in values: option = Option() option.number = defines.OptionRegistry.IF_MATCH.number option.value = v self.add_option(option)
def location_query(self, value): del self.location_query queries = value.split("&") for q in queries: option = Option() option.number = defines.OptionRegistry.LOCATION_QUERY.number option.value = str(q) self.add_option(option)
def add_if_none_match(self): """ Add the if-none-match option to the request. """ option = Option() option.number = defines.OptionRegistry.IF_NONE_MATCH.number option.value = None self.add_option(option)
def add_no_response(self): """ Add the no-response option to the request # https://tools.ietf.org/html/rfc7967#section-2.1 """ option = Option() option.number = defines.OptionRegistry.NO_RESPONSE.number option.value = 26 self.add_option(option)
def size2(self, value): """ Set the Size2 option. :param value: the Block2 value """ option = Option() option.number = defines.OptionRegistry.SIZE2.number option.value = value self.add_option(option)
def proxy_uri(self, value): """ Set the Proxy-Uri option of a request. :param value: the Proxy-Uri value """ option = Option() option.number = defines.OptionRegistry.PROXY_URI.number option.value = str(value) self.add_option(option)
def proxy_schema(self, value): """ Set the Proxy-Schema option of a request. :param value: the Proxy-Schema value """ option = Option() option.number = defines.OptionRegistry.PROXY_SCHEME.number option.value = str(value) self.add_option(option)
def location_path(self, path): path = path.strip("/") tmp = path.split("?") path = tmp[0] paths = path.split("/") for p in paths: option = Option() option.number = defines.OptionRegistry.LOCATION_PATH.number option.value = p self.add_option(option)
def uri_query(self, value): """ Set the uri_query option of a request. :param value: the uri_query value """ option = Option() option.number = defines.OptionRegistry.URI_QUERY.number option.value = str(value) self.add_option(option)
def max_age(self, value): """ Set the Max-Age option of a request. :param value: the Proxy-Schema value """ option = Option() option.number = defines.OptionRegistry.MAX_AGE.number option.value = int(value) self.add_option(option)
def ocf_content_format_version(self, value): """ Add the ocf_content_format_version option to the request. """ option = Option() #print (" setting ocf_content_format_version :", int(value)) option.number = defines.OptionRegistry.OCF_CONTENT_FORMAT_VERSION.number option.value = int(value) print("request: ocf_content_format_version (setter)", option) self.add_option(option)
def post(self, path, payload, callback=None, timeout=None, *options): request = self.mk_request(defines.Codes.POST, path) request.token = generate_random_token(2) request.payload = payload for number, value in options: option = Option() option.number = number option.value = value request.add_option(option) return self.send_request(request, callback, timeout)
def add_no_response(self,value=26): """ Add the no-response option to the request # https://tools.ietf.org/html/rfc7967#section-2.1 """ option = Option() option.number = defines.OptionRegistry.NO_RESPONSE.number #26 means 16+8+2: not interested in all responses option.value = value self.add_option(option)
def accept(self, value): """ Add an Accept option to a request. :param value: the Accept value """ if value in defines.Content_types.values(): option = Option() option.number = defines.OptionRegistry.ACCEPT.number option.value = value self.add_option(option)
def observe(self, ob): """ Add the Observe option. :param ob: observe count """ option = Option() option.number = defines.OptionRegistry.OBSERVE.number option.value = ob self.del_option_by_number(defines.OptionRegistry.OBSERVE.number) self.add_option(option)
def content_type(self, content_type): """ Set the Content-Type option of a response. :type content_type: int :param content_type: the Content-Type """ option = Option() option.number = defines.OptionRegistry.CONTENT_TYPE.number option.value = int(content_type) self.add_option(option)
def if_none_match(self, value): """ Set the If-Match option of a request. :param value: True/False :type values : bool """ assert isinstance(value, bool) option = Option() option.number = defines.OptionRegistry.IF_NONE_MATCH.number option.value = None self.add_option(option)
def max_age(self, value): """ Set the MaxAge of the response. :type value: int :param value: the MaxAge option """ option = Option() option.number = defines.OptionRegistry.MAX_AGE.number option.value = int(value) self.del_option_by_number(defines.OptionRegistry.MAX_AGE.number) self.add_option(option)
def ocf_accept_content_format_version(self, value): """ Set the OCF_ACCEPT_CONTENT_FORMAT_VERSION of the response. :type value: int :param value: the OCF_ACCEPT_CONTENT_FORMAT_VERSION option """ option = Option() option.number = defines.OptionRegistry.OCF_ACCEPT_CONTENT_FORMAT_VERSION.number option.value = int(value) self.del_option_by_number(defines.OptionRegistry.OCF_ACCEPT_CONTENT_FORMAT_VERSION.number) print ("response: ocf_accept_content_format_version (setter)",option) self.add_option(option)
def etag(self, etag): """ Add an ETag option to the message. :param etag: the etag """ if not isinstance(etag, list): etag = [etag] for e in etag: option = Option() option.number = defines.OptionRegistry.ETAG.number option.value = e self.add_option(option)
def uri_query(self, value): """ Adds a query. :param value: the query """ del self.uri_query queries = value.split("&") for q in queries: option = Option() option.number = defines.OptionRegistry.URI_QUERY.number option.value = str(q) self.add_option(option)
def if_match(self, values): """ Set the If-Match option of a request. :param values: the If-Match values :type values : list """ assert isinstance(values, list) for v in values: option = Option() option.number = defines.OptionRegistry.IF_MATCH.number option.value = v self.add_option(option)
def location_query(self, value): """ Set the Location-Query of the response. :type path: String :param path: the Location-Query as a string """ del self.location_query queries = value.split("&") for q in queries: option = Option() option.number = defines.OptionRegistry.LOCATION_QUERY.number option.value = str(q) self.add_option(option)
def etag(self, etag): """ Add an ETag option to the message. :param etag: the etag """ if not isinstance(etag, list): etag = [etag] for e in etag: option = Option() option.number = defines.OptionRegistry.ETAG.number if isinstance(e, str): # if etag was accidentally set to str, convert it to bytes e = bytes(e, "utf-8") option.value = e self.add_option(option)
def location_path(self, path): """ Set the Location-Path of the response. :type path: String :param path: the Location-Path as a string """ path = path.strip("/") tmp = path.split("?") path = tmp[0] paths = path.split("/") for p in paths: option = Option() option.number = defines.OptionRegistry.LOCATION_PATH.number option.value = p self.add_option(option)
def uri_path(self, path): """ Set the Uri-Path of a request. :param path: the Uri-Path """ path = path.strip("/") tmp = path.split("?") path = tmp[0] paths = path.split("/") for p in paths: option = Option() option.number = defines.OptionRegistry.URI_PATH.number option.value = p self.add_option(option) if len(tmp) > 1: query = tmp[1] self.uri_query = query
def deserialize(datagram, source): """ De-serialize a stream of byte to a message. :param datagram: the incoming udp message :param source: the source address and port (ip, port) :return: the message :rtype: Message """ try: fmt = "!BBH" pos = struct.calcsize(fmt) s = struct.Struct(fmt) values = s.unpack_from(datagram) first = values[0] code = values[1] mid = values[2] version = (first & 0xC0) >> 6 message_type = (first & 0x30) >> 4 token_length = (first & 0x0F) if Serializer.is_response(code): message = Response() message.code = code elif Serializer.is_request(code): message = Request() message.code = code else: message = Message() message.source = source message.destination = None message.version = version message.type = message_type message.mid = mid if token_length > 0: fmt = "%ss" % token_length s = struct.Struct(fmt) token_value = s.unpack_from(datagram[pos:])[0] message.token = token_value.decode("utf-8") else: message.token = None pos += token_length current_option = 0 values = datagram[pos:] length_packet = len(values) pos = 0 while pos < length_packet: next_byte = struct.unpack("B", values[pos].to_bytes(1, "big"))[0] pos += 1 if next_byte != int(defines.PAYLOAD_MARKER): # the first 4 bits of the byte represent the option delta # delta = self._reader.read(4).uint num, option_length, pos = Serializer.read_option_value_len_from_byte(next_byte, pos, values) current_option += num # read option try: option_item = defines.OptionRegistry.LIST[current_option] except KeyError: (opt_critical, _, _) = defines.OptionRegistry.get_option_flags(current_option) if opt_critical: raise AttributeError("Critical option %s unknown" % current_option) else: # If the non-critical option is unknown # (vendor-specific, proprietary) - just skip it #log.err("unrecognized option %d" % current_option) pass else: if option_length == 0: value = None elif option_item.value_type == defines.INTEGER: tmp = values[pos: pos + option_length] value = 0 for b in tmp: value = (value << 8) | struct.unpack("B", b.to_bytes(1, "big"))[0] elif option_item.value_type == defines.OPAQUE: tmp = values[pos: pos + option_length] value = tmp else: value = values[pos: pos + option_length] option = Option() option.number = current_option option.value = Serializer.convert_to_raw(current_option, value, option_length) message.add_option(option) if option.number == defines.OptionRegistry.CONTENT_TYPE.number: message.payload_type = option.value finally: pos += option_length else: if length_packet <= pos: # log.err("Payload Marker with no payload") raise AttributeError("Packet length %s, pos %s" % (length_packet, pos)) message.payload = "" payload = values[pos:] try: if message.payload_type == defines.Content_types["application/octet-stream"]: message.payload = payload else: message.payload = payload.decode("utf-8") except AttributeError: message.payload = payload.decode("utf-8") pos += len(payload) return message except AttributeError: return defines.Codes.BAD_REQUEST.number except struct.error: return defines.Codes.BAD_REQUEST.number
def max_age(self, value): option = Option() option.number = defines.OptionRegistry.MAX_AGE.number option.value = int(value) self.add_option(option)