| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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. |
| # |
| |
| ## |
| # Define the current protocol version. Any messages that do not contain version |
| # information shall be considered to be coming from routers using version 0. |
| ## |
| |
| from __future__ import unicode_literals |
| from __future__ import division |
| from __future__ import absolute_import |
| from __future__ import print_function |
| |
| from ..compat import PY_LONG_TYPE |
| from ..compat import LONG |
| from ..compat import PY_TEXT_TYPE |
| |
| ProtocolVersion = LONG(1) |
| |
| |
| def getMandatory(data, key, cls=None): |
| """ |
| Get the value mapped to the requested key. If it's not present, raise an exception. |
| """ |
| if key in data: |
| value = data[key] |
| if cls and value.__class__ != cls: |
| raise Exception("Protocol field has wrong data type: '%s' type=%r expected=%r" % (key, value.__class__, cls)) |
| return value |
| raise Exception("Mandatory protocol field missing: '%s'" % key) |
| |
| |
| def getOptional(data, key, default=None, cls=None): |
| """ |
| Get the value mapped to the requested key. If it's not present, return the default value. |
| """ |
| if key in data: |
| value = data[key] |
| if cls and value.__class__ != cls: |
| raise Exception("Protocol field has wrong data type: '%s' type=%r expected=%r" % (key, value.__class__, cls)) |
| return value |
| return default |
| |
| |
| def isCompatibleVersion(body): |
| """ |
| Return True iff the version of the message body is compatible with this protocol implementation. |
| """ |
| version = 0 |
| try: |
| version = getOptional(body, 'pv', 0, PY_LONG_TYPE) |
| except Exception: |
| pass |
| |
| return version <= ProtocolVersion |
| |
| |
| def getIdAndVersion(body): |
| """ |
| Return a tuple of id and version from a message body |
| """ |
| result = ('<unknown>', 0) |
| try: |
| result = (getOptional(body, 'id', '<unknown>', PY_TEXT_TYPE), getOptional(body, 'pv', 0, PY_LONG_TYPE)) |
| except Exception: |
| pass |
| return result |
| |
| |
| class LinkState(object): |
| """ |
| The link-state of a single router. The link state consists of a list of neighbor routers reachable from |
| the reporting router. The link-state-sequence number is incremented each time the link state changes. |
| """ |
| |
| def __init__(self, body, _id=None, _ls_seq=None, _peers=None): |
| self.last_seen = 0 |
| if body: |
| self.id = getMandatory(body, 'id', PY_TEXT_TYPE) |
| self.area = '0' |
| self.ls_seq = getMandatory(body, 'ls_seq', PY_LONG_TYPE) |
| self.peers = getMandatory(body, 'peers', dict) |
| else: |
| self.id = _id |
| self.area = '0' |
| self.ls_seq = LONG(_ls_seq) |
| self.peers = _peers |
| |
| def __repr__(self): |
| return "LS(id=%s area=%s ls_seq=%d peers=%r)" % (self.id, self.area, self.ls_seq, self.peers) |
| |
| def to_dict(self): |
| return {'id' : self.id, |
| 'area' : self.area, |
| 'ls_seq' : self.ls_seq, |
| 'peers' : self.peers} |
| |
| def add_peer(self, _id, _cost): |
| if _id not in self.peers: |
| self.peers[_id] = _cost |
| return True |
| return False |
| |
| def del_peer(self, _id): |
| if _id in self.peers: |
| self.peers.pop(_id) |
| return True |
| return False |
| |
| def del_all_peers(self): |
| self.peers = {} |
| self.ls_seq = 0 |
| |
| def has_peers(self): |
| return len(self.peers) > 0 |
| |
| def is_peer(self, _id): |
| return _id in self.peers |
| |
| def bump_sequence(self): |
| self.ls_seq += 1 |
| |
| |
| class MessageHELLO(object): |
| """ |
| HELLO Message |
| scope: neighbors only - HELLO messages travel at most one hop |
| This message is used by directly connected routers to determine with whom they have |
| bidirectional connectivity. |
| """ |
| |
| def __init__(self, body, _id=None, _seen_peers=None, _instance=LONG(0)): |
| if body: |
| self.id = getMandatory(body, 'id', PY_TEXT_TYPE) |
| self.area = '0' |
| self.seen_peers = getMandatory(body, 'seen', list) |
| self.instance = getOptional(body, 'instance', 0, PY_LONG_TYPE) |
| self.version = getOptional(body, 'pv', 0, PY_LONG_TYPE) |
| else: |
| self.id = _id |
| self.area = '0' |
| self.seen_peers = _seen_peers |
| self.instance = _instance |
| self.version = ProtocolVersion |
| |
| def __repr__(self): |
| return "HELLO(id=%s pv=%d area=%s inst=%d seen=%r)" % (self.id, self.version, self.area, self.instance, self.seen_peers) |
| |
| def get_opcode(self): |
| return 'HELLO' |
| |
| def to_dict(self): |
| return {'id' : self.id, |
| 'pv' : self.version, |
| 'area' : self.area, |
| 'instance' : self.instance, |
| 'seen' : self.seen_peers} |
| |
| def is_seen(self, _id): |
| return self.seen_peers.count(_id) > 0 |
| |
| |
| class MessageRA(object): |
| """ |
| Router Advertisement (RA) Message |
| scope: all routers in the area and all designated routers |
| This message is sent periodically to indicate the originating router's sequence numbers |
| for link-state and mobile-address-state. |
| """ |
| |
| def __init__(self, body, _id=None, _ls_seq=None, _mobile_seq=None, _instance=LONG(0)): |
| if body: |
| self.id = getMandatory(body, 'id', PY_TEXT_TYPE) |
| self.area = '0' |
| self.ls_seq = getMandatory(body, 'ls_seq', PY_LONG_TYPE) |
| self.mobile_seq = getMandatory(body, 'mobile_seq', PY_LONG_TYPE) |
| self.instance = getOptional(body, 'instance', 0, PY_LONG_TYPE) |
| self.version = getOptional(body, 'pv', 0, PY_LONG_TYPE) |
| else: |
| self.id = _id |
| self.area = '0' |
| self.ls_seq = LONG(_ls_seq) |
| self.mobile_seq = LONG(_mobile_seq) |
| self.instance = _instance |
| self.version = ProtocolVersion |
| |
| def get_opcode(self): |
| return 'RA' |
| |
| def __repr__(self): |
| return "RA(id=%s pv=%d area=%s inst=%d ls_seq=%d mobile_seq=%d)" % \ |
| (self.id, self.version, self.area, self.instance, self.ls_seq, self.mobile_seq) |
| |
| def to_dict(self): |
| return {'id' : self.id, |
| 'pv' : self.version, |
| 'area' : self.area, |
| 'instance' : self.instance, |
| 'ls_seq' : self.ls_seq, |
| 'mobile_seq' : self.mobile_seq} |
| |
| |
| class MessageLSU(object): |
| """ |
| """ |
| |
| def __init__(self, body, _id=None, _ls_seq=None, _ls=None, _instance=LONG(0)): |
| if body: |
| self.id = getMandatory(body, 'id', PY_TEXT_TYPE) |
| self.area = '0' |
| self.ls_seq = getMandatory(body, 'ls_seq', PY_LONG_TYPE) |
| self.ls = LinkState(getMandatory(body, 'ls', dict)) |
| self.instance = getOptional(body, 'instance', 0, PY_LONG_TYPE) |
| self.version = getOptional(body, 'pv', 0, PY_LONG_TYPE) |
| else: |
| self.id = _id |
| self.area = '0' |
| self.ls_seq = LONG(_ls_seq) |
| self.ls = _ls |
| self.instance = _instance |
| self.version = ProtocolVersion |
| |
| def get_opcode(self): |
| return 'LSU' |
| |
| def __repr__(self): |
| return "LSU(id=%s pv=%d area=%s inst=%d ls_seq=%d ls=%r)" % \ |
| (self.id, self.version, self.area, self.instance, self.ls_seq, self.ls) |
| |
| def to_dict(self): |
| return {'id' : self.id, |
| 'pv' : self.version, |
| 'area' : self.area, |
| 'instance' : self.instance, |
| 'ls_seq' : self.ls_seq, |
| 'ls' : self.ls.to_dict()} |
| |
| |
| class MessageLSR(object): |
| """ |
| """ |
| |
| def __init__(self, body, _id=None): |
| if body: |
| self.id = getMandatory(body, 'id', PY_TEXT_TYPE) |
| self.version = getOptional(body, 'pv', 0, PY_LONG_TYPE) |
| self.area = '0' |
| else: |
| self.id = _id |
| self.version = ProtocolVersion |
| self.area = '0' |
| |
| def get_opcode(self): |
| return 'LSR' |
| |
| def __repr__(self): |
| return "LSR(id=%s pv=%d area=%s)" % (self.id, self.version, self.area) |
| |
| def to_dict(self): |
| return {'id' : self.id, |
| 'pv' : self.version, |
| 'area' : self.area} |
| |
| |
| class MessageMAU(object): |
| """ |
| """ |
| |
| def __init__(self, body, _id=None, _seq=None, _add_list=None, _del_list=None, _exist_list=None, _hints=None): |
| if body: |
| self.id = getMandatory(body, 'id', PY_TEXT_TYPE) |
| self.version = getOptional(body, 'pv', 0, PY_LONG_TYPE) |
| self.area = '0' |
| self.mobile_seq = getMandatory(body, 'mobile_seq', PY_LONG_TYPE) |
| self.add_list = getOptional(body, 'add', None, list) |
| self.del_list = getOptional(body, 'del', None, list) |
| self.exist_list = getOptional(body, 'exist', None, list) |
| self.hints = getOptional(body, 'hints', None, list) |
| else: |
| self.id = _id |
| self.version = ProtocolVersion |
| self.area = '0' |
| self.mobile_seq = LONG(_seq) |
| self.add_list = _add_list |
| self.del_list = _del_list |
| self.exist_list = _exist_list |
| self.hints = _hints |
| |
| def get_opcode(self): |
| return 'MAU' |
| |
| def __repr__(self): |
| _add = '' |
| _del = '' |
| _exist = '' |
| _hints = '' |
| if self.add_list is not None: |
| _add = ' add=%r' % self.add_list |
| if self.del_list is not None: |
| _del = ' del=%r' % self.del_list |
| if self.exist_list is not None: |
| _exist = ' exist=%r' % self.exist_list |
| if self.hints is not None: |
| _hints = ' hints=%r' % self.hints |
| return "MAU(id=%s pv=%d area=%s mobile_seq=%d%s%s%s%s)" % \ |
| (self.id, self.version, self.area, self.mobile_seq, _add, _del, _exist, _hints) |
| |
| def to_dict(self): |
| body = {'id' : self.id, |
| 'pv' : self.version, |
| 'area' : self.area, |
| 'mobile_seq' : self.mobile_seq} |
| if self.add_list is not None: |
| body['add'] = self.add_list |
| if self.del_list is not None: |
| body['del'] = self.del_list |
| if self.exist_list is not None: |
| body['exist'] = self.exist_list |
| if self.hints is not None: |
| body['hints'] = self.hints |
| return body |
| |
| |
| class MessageMAR(object): |
| """ |
| """ |
| |
| def __init__(self, body, _id=None, _have_seq=None): |
| if body: |
| self.id = getMandatory(body, 'id', PY_TEXT_TYPE) |
| self.version = getOptional(body, 'pv', 0, PY_LONG_TYPE) |
| self.area = '0' |
| self.have_seq = getMandatory(body, 'have_seq', PY_LONG_TYPE) |
| else: |
| self.id = _id |
| self.version = ProtocolVersion |
| self.area = '0' |
| self.have_seq = LONG(_have_seq) |
| |
| def get_opcode(self): |
| return 'MAR' |
| |
| def __repr__(self): |
| return "MAR(id=%s pv=%d area=%s have_seq=%d)" % (self.id, self.version, self.area, self.have_seq) |
| |
| def to_dict(self): |
| return {'id' : self.id, |
| 'pv' : self.version, |
| 'area' : self.area, |
| 'have_seq' : self.have_seq} |