Adding new transfer protocols

A transfer protocol is derived from the Transfer class:

class Transfer:

   def send(self, msg, encoder):
      pass

  def receive(self, callback, encode):
     pass

A Transfer implementation is used for both sending and receiving OpenC2 messages, by using the the send() and receive() method, respectively. The receive() method is expected to be blocking and to wait for messages until termination of the application.

The send() takes an OpenC2 Message as first argument, which is expected to carry a Content. It returns the Response within another Message. The Encoder must be passed as second argument, and it is possible to use a different Encoder for each individual message. The send() message is used by the Producer.

The receive() takes a callback function from the Consumer, which is used to dispatch incoming messages to the corresponding Actuator. The Encoder must be passed as second argument, but it is only used when the encoding format is not present in the metadata; it is also used to answer messages in unknown formats.

The implementation of a Transfer is expected to: - perform any protocol-specific initialization, including loading configurations (e.g., certificates to be used in TLS/SSL handshakes); - manage the transmission and reception of Message metadata in a protocol-dependent way (this is defined by each corresponding OpenC2 specification).

Implementation of new Transfers included in otupy must be placed in the transfers folder and be self-contained in a single module. To make the Transfer available, use the @transfer decorator and provide a meaningful name (e.g., the protocol implemented). Also remember to import the main class in the transfers/__init__.py.