# Copyright 2016 Anselm Binninger, Thomas Maier, Ralph Schaumann
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import struct
from abc import ABCMeta
from gossip.util.message_code import MESSAGE_CODE_ANNOUNCE, MESSAGE_CODE_PEER_REQUEST, MESSAGE_CODE_PEER_RESPONSE, \
MESSAGE_CODE_NOTIFICATION, MESSAGE_CODE_NOTIFY, MESSAGE_CODE_PEER_UPDATE, MESSAGE_CODE_VALIDATION, \
MESSAGE_CODE_PEER_INIT
from gossip.util.byte_formatting import short_to_bytes, bytes_to_short
__author__ = 'Anselm Binninger, Thomas Maier, Ralph Schaumann'
[docs]class MessageGossip:
"""
Baseclass for gossip messages
"""
__metaclass__ = ABCMeta
def __init__(self, code, data):
"""
C'tor
:param code: the code of this message
"""
self.code = code
self.data = data
[docs] def get_code(self):
"""
Method by which the code of this message is retrieved
:return: the code of this message
"""
return self.code
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: the values of this message
"""
pass
def __str__(self):
"""
to string method
:return: a string representation of this message
"""
return '%s' % self.get_values()
[docs] def encode(self):
"""
Encodes this message into a byte array
:return: a byte array with the encoded header and payload
"""
size = len(self.data) + 4
# encode the size into two bytes
b_size = short_to_bytes(size)
# encode the message code into two bytes
b_code = short_to_bytes(self.code)
# add all the bytes up to construct the message
b_msg = b_size + b_code + self.data
return b_msg
[docs]class MessageGossip51x(MessageGossip):
"""
Baseclass for gossip messages of code 510-519
"""
def __init__(self, code, data):
"""
C'Tor
:param code: the code of this message
"""
super().__init__(code, data)
[docs]class MessageOther:
"""
If a message can't be decoded properly a MessageOther will be returned.
"""
def __init__(self, message_code, message_data):
"""
C'Tor
:param message_code: the code of the message
:param message_data: the data of the message
"""
self.code = message_code
self.data = message_data
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: a dictionary with the values of this message (keys: code, message)
"""
return {'code': self.code, 'message': self.data}
[docs] def encode(self):
size = len(self.data) + 4
# encode the size into two bytes
b_size = short_to_bytes(size)
b_code = short_to_bytes(self.code)
# add all the bytes up to construct the message
b_msg = b_size + b_code + self.data
return b_msg
def __str__(self):
"""
to string method
:return: a string representation of this message
"""
return {'code': self.code, 'data': self.data}.__str__()
[docs]class MessageGossipAnnounce(MessageGossip):
"""
GossipAnnounceMessage
This messages are received from api connections. If you want to implement an api client for gossip
its messages need to be structured like that
"""
def __init__(self, data):
"""
C'Tor
:param bytes data: the data of the message
"""
super().__init__(MESSAGE_CODE_ANNOUNCE, data)
self.ttl = struct.unpack('{}B'.format(1), data[:1])[0]
data_type_fst, data_type_snd = struct.unpack('{}B'.format(2), data[2:4])
self.data_type = bytes_to_short(data_type_fst, data_type_snd)
self.msg = struct.unpack('{}B'.format(len(data) - 4), data[4:len(data)])
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: a dictionary with the values of this message (keys: code, message, TTL, type)
"""
return {'message': self.msg, 'code': self.code, 'TTL': self.ttl, 'type': self.data_type}
def __hash__(self):
return hash((self.msg, self.code, self.data_type))
def __eq__(self, other):
return (self.msg, self.code, self.data_type) == (other.msg, other.code, other.data_type)
def __ne__(self, other):
return not (self == other)
[docs]class MessageGossipNotify(MessageGossip):
"""
This message is sent from the gossip instance to all apis that registered for this specific datatype
An api client needs to implement this messageformat
"""
def __init__(self, data):
"""
C'Tor
:param bytes data: the data of the message
"""
super().__init__(MESSAGE_CODE_NOTIFY, data)
data_type_fst, data_type_snd = struct.unpack('{}B'.format(2), data[2:4])
self.data_type = bytes_to_short(data_type_fst, data_type_snd)
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: a dictionary with the values of this message (keys: code, type)
"""
return {'type': self.data_type, 'code': self.code}
[docs]class MessageGossipNotification(MessageGossip):
"""
Message that is sent from gossip to api clients which was received from other peers
"""
def __init__(self, data):
"""
C'Tor
:param bytes data: the data from this message
"""
super().__init__(MESSAGE_CODE_NOTIFICATION, data)
msg_id_fst, msg_id_snd = struct.unpack('{}B'.format(2), data[:2])
self.msg_id = bytes_to_short(msg_id_fst, msg_id_snd)
data_type_fst, data_type_snd = struct.unpack('{}B'.format(2), data[2:4])
self.data_type = bytes_to_short(data_type_fst, data_type_snd)
self.msg = struct.unpack('{}B'.format(len(data) - 4), data[4:len(data)])
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: a dictionary with the values of this message (keys: code, type, id, message)
"""
return {'id': self.msg_id, 'type': self.data_type, 'code': self.code, 'message': self.msg}
[docs]class MessageGossipValidation(MessageGossip):
"""
Message that is sent from gossip to api clients to know whether or not a received message is valid
"""
def __init__(self, data):
"""
C'Tor
:param data: the data from this message
"""
super().__init__(MESSAGE_CODE_VALIDATION, data)
msg_id_fst, msg_id_snd = struct.unpack('{}B'.format(2), data[:2])
self.msg_id = bytes_to_short(msg_id_fst, msg_id_snd)
_, valid = struct.unpack('{}B'.format(2), data[2:4])
if valid == 0:
self.valid = False
else:
self.valid = True
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: a dictionary with the values of this message (keys: id, valid, code)
"""
return {'id': self.msg_id, 'valid': self.valid, 'code': self.code}
[docs]class MessageGossipPeerRequest(MessageGossip51x):
"""
Message that is sent from one peer to another to get addresses of peers the other peer is connected to
"""
def __init__(self, data):
"""
C'Tor
:param data: the data from this message
"""
super().__init__(MESSAGE_CODE_PEER_REQUEST, data)
self.data = data
ipv4_part_1 = int(self.data[0])
ipv4_part_2 = int(self.data[1])
ipv4_part_3 = int(self.data[2])
ipv4_part_4 = int(self.data[3])
port = bytes_to_short(self.data[4], self.data[5])
self.address = '%s.%s.%s.%s:%s' % (ipv4_part_1, ipv4_part_2, ipv4_part_3, ipv4_part_4, port)
[docs] def get_values(self):
return {'code': MESSAGE_CODE_PEER_REQUEST, 'p2p_server_address': self.address}
[docs]class MessageGossipPeerUpdate(MessageGossip51x):
"""
Message that is sent recursively through the network as a respone to a new established connection
"""
def __init__(self, data):
"""
C'Tor
:param data: the data from this message
"""
super().__init__(MESSAGE_CODE_PEER_UPDATE, data)
self.data = data
ipv4_part_1 = int(self.data[0])
ipv4_part_2 = int(self.data[1])
ipv4_part_3 = int(self.data[2])
ipv4_part_4 = int(self.data[3])
port = bytes_to_short(self.data[4], self.data[5])
self.address = '%s.%s.%s.%s:%s' % (ipv4_part_1, ipv4_part_2, ipv4_part_3, ipv4_part_4, port)
self.ttl = int(self.data[6])
self.update_type = int(self.data[7])
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: a dictionary with the values of this message (keys: code, address, ttl, update_type)
"""
return {'code': MESSAGE_CODE_PEER_UPDATE, 'address': self.address, 'ttl': self.ttl,
'update_type': self.update_type}
def __hash__(self):
return hash((self.address, self.update_type))
def __eq__(self, other):
return (self.address, self.update_type) == (other.address, other.update_type)
def __ne__(self, other):
return not (self == other)
[docs]class MessageGossipPeerResponse(MessageGossip51x):
"""
Message that is sent as an answer to a previously sent peer request.
It contains a list of server identifiers (address:port)
"""
def __init__(self, data):
"""
C'Tor
:param data: the data from this message
"""
super().__init__(MESSAGE_CODE_PEER_RESPONSE, data)
self.data = data
self.connections = []
if len(self.data) > 0:
data = struct.unpack('{}B'.format(len(self.data)), self.data)
for i in range(0, len(self.data), 6):
ipv4_part_1 = int(data[i])
ipv4_part_2 = int(data[i + 1])
ipv4_part_3 = int(data[i + 2])
ipv4_part_4 = int(data[i + 3])
port = bytes_to_short(self.data[i + 4], self.data[i + 5])
address = '%s.%s.%s.%s:%s' % (ipv4_part_1, ipv4_part_2, ipv4_part_3, ipv4_part_4, port)
self.connections.append(address)
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: a dictionary with the values of this message (keys: code, data)
"""
return {'code': MESSAGE_CODE_PEER_RESPONSE, 'data': self.connections}
[docs]class MessageGossipPeerInit(MessageGossip51x):
"""
Initial message that is sent from the connection peer to the remote peer
to inform that peer about his server adderss (especially the port)
"""
def __init__(self, data):
"""
C'Tor
:param data: the data from this message
"""
super().__init__(MESSAGE_CODE_PEER_INIT, data)
self.data = data
ipv4_part_1 = int(self.data[0])
ipv4_part_2 = int(self.data[1])
ipv4_part_3 = int(self.data[2])
ipv4_part_4 = int(self.data[3])
port = bytes_to_short(self.data[4], self.data[5])
self.address = '%s.%s.%s.%s:%s' % (ipv4_part_1, ipv4_part_2, ipv4_part_3, ipv4_part_4, port)
[docs] def get_values(self):
"""
Method by which the values of this message are retrieved
:return: a dictionary with the values of this message (keys: code, p2p_server_address)
"""
return {'code': MESSAGE_CODE_PEER_INIT, 'p2p_server_address': self.address}
""" Dictionary of all known message types within Gossip """
GOSSIP_MESSAGE_TYPES = {MESSAGE_CODE_ANNOUNCE: MessageGossipAnnounce,
MESSAGE_CODE_NOTIFY: MessageGossipNotify,
MESSAGE_CODE_NOTIFICATION: MessageGossipNotification,
MESSAGE_CODE_VALIDATION: MessageGossipValidation,
MESSAGE_CODE_PEER_REQUEST: MessageGossipPeerRequest,
MESSAGE_CODE_PEER_RESPONSE: MessageGossipPeerResponse,
MESSAGE_CODE_PEER_UPDATE: MessageGossipPeerUpdate,
MESSAGE_CODE_PEER_INIT: MessageGossipPeerInit}