class PathSegment(SCIONPayloadBaseProto): NAME = "PathSegment" PAYLOAD_CLASS = PayloadClass.PCB P_CLS = P.PathSegment VER = len(P_CLS.schema.fields) - 1 def __init__(self, p): # pragma: no cover super().__init__(p) self._min_exp = float("inf") self._setup() def _setup(self): self.info = InfoOpaqueField(self.p.info) self._calc_min_exp() self.sibra_ext = None if self.is_sibra(): self.sibra_ext = SibraPCBExt(self.p.exts.sibra) @classmethod def from_values(cls, info, sibra_ext=None): # pragma: no cover p = cls.P_CLS.new_message(info=info.pack()) if sibra_ext: p.exts.sibra = sibra_ext.p return cls(p) def _calc_min_exp(self): # NB: only the expiration time of the first pcbm is considered. for asm in self.iter_asms(): self._min_exp = min(self._min_exp, asm.pcbm(0).hof().exp_time) def asm(self, idx): # pragma: no cover return ASMarking(self.p.asms[idx]) def iter_asms(self, start=0): # pragma: no cover for i in range(start, len(self.p.asms)): yield self.asm(i) def is_sibra(self): # pragma: no cover return bool(self.p.exts.sibra.id) def sig_pack3(self): """ Pack for signing version 3 (defined by highest field number). """ if self.VER != 3: raise SCIONSigVerError("PathSegment.sig_pack3 cannot support version %s", self.VER) b = [] b.append(self.p.info) # ifID field is changed on the fly, and so is ignored. for asm in self.iter_asms(): b.append(asm.sig_pack8()) if self.is_sibra(): b.append(self.sibra_ext.sig_pack3()) return b"".join(b) def sign(self, key, set_=True): # pragma: no cover sig = sign(self.sig_pack3(), key) if set_: self.p.asms[-1].sig = sig return sig def add_asm(self, asm): # pragma: no cover """ Appends a new ASMarking block. """ d = self.p.to_dict() d.setdefault('asms', []).append(asm.p) self.p.from_dict(d) self._update_info() self._min_exp = min(self._min_exp, asm.pcbm(0).hof().exp_time) def _update_info(self): # pragma: no cover self.info.hops = len(self.p.asms) self.p.info = self.info.pack() def add_sibra_ext(self, ext_p): # pragma: no cover self.p.exts.sibra = ext_p.copy() self.sibra_ext = SibraPCBExt(self.p.exts.sibra) def get_trcs_certs(self): """ Returns a dict of all trcs' versions and a dict of all certificates' versions used in this PCB. """ trcs = defaultdict(set) certs = defaultdict(set) for asm in self.iter_asms(): isd_as = asm.isd_as() isd = isd_as[0] trcs[isd].add(asm.p.trcVer) certs[isd_as].add(asm.p.certVer) return trcs, certs def get_path(self, reverse_direction=False): """ Returns the list of HopOpaqueFields in the path. """ hofs = [] info = InfoOpaqueField(self.p.info) asms = list(self.iter_asms()) if reverse_direction: asms = reversed(asms) info.up_flag ^= True for asm in asms: hofs.append(asm.pcbm(0).hof()) return SCIONPath.from_values(info, hofs) def first_ia(self): # pragma: no cover return self.asm(0).isd_as() def last_ia(self): # pragma: no cover return self.asm(-1).isd_as() def last_hof(self): # pragma: no cover if self.p.asms: return self.asm(-1).pcbm(0).hof() return None def get_hops_hash(self, hex=False): # pragma: no cover """ Returns the hash over all triples (ISD_AS, IG_IF, EG_IF) included in the path segment. """ data = [] for asm in self.iter_asms(): pcbm = asm.pcbm(0) data.append(asm.isd_as().pack()) data.append(struct.pack("!QQ", pcbm.p.inIF, pcbm.p.outIF)) data = b"".join(data) if hex: return crypto_hash(data).hex() return crypto_hash(data) def get_n_peer_links(self): # pragma: no cover """Return the total number of peer links in the PathSegment.""" n = 0 for asm in self.p.asms: n += len(asm.pcbms) - 1 return n def get_n_hops(self): # pragma: no cover """Return the number of hops in the PathSegment.""" return len(self.p.asms) def get_timestamp(self): # pragma: no cover """Returns the creation timestamp of this PathSegment.""" return self.info.timestamp def set_timestamp(self, timestamp): # pragma: no cover """Updates the timestamp in the IOF.""" assert timestamp < 2 ** 32 - 1 self.info.timestamp = timestamp self._update_info() def get_expiration_time(self): # pragma: no cover """ Returns the expiration time of the path segment in real time. If a PCB extension in the last ASMarking supplies an expiration time, use that. Otherwise fall-back to the standard expiration time calculation. """ if self.is_sibra(): return self.sibra_ext.exp_ts() return self.info.timestamp + int(self._min_exp * EXP_TIME_UNIT) def flags(self): # pragma: no cover f = 0 if self.is_sibra(): f |= PSF.SIBRA return f def short_id(self): # pragma: no cover """ Return a 12-byte hex ID identifying the PCB (mostly for logging purposes). """ return self.get_hops_hash(hex=True)[:12] def short_desc(self): # pragma: no cover """ Return a short description string of the PathSegment, consisting of a truncated hash, the IOF timestamp, and the list of hops. """ desc = [] desc.append("%s, %s, " % (self.short_id(), iso_timestamp(self.get_timestamp()))) hops = [] for asm in self.iter_asms(): hop = [] hof = asm.pcbm(0).hof() if hof.ingress_if: hop.append("%d " % hof.ingress_if) hop.append("%s" % asm.isd_as()) if hof.egress_if: hop.append(" %d" % hof.egress_if) hops.append("".join(hop)) exts = [] if self.is_sibra(): exts.append(" %s" % self.sibra_ext.short_desc()) desc.append(">".join(hops)) if exts: return "%s\n%s" % ("".join(desc), "\n".join(exts)) return "".join(desc) def __str__(self): s = [] s.append("%s:" % self.NAME) s.append(" %s" % self.info) for asm in self.iter_asms(): for line in asm.short_desc().splitlines(): s.append(" %s" % line) if self.sibra_ext: for line in str(self.sibra_ext).splitlines(): s.append(" %s" % line) return "\n".join(s) def __eq__(self, other): return self.__hash__() == hash(other) def __hash__(self): # pragma: no cover return hash(self.get_hops_hash()) # FIMXE(PSz): should add timestamp?
class PathSegment(SCIONPayloadBaseProto): NAME = "PathSegment" PAYLOAD_CLASS = PayloadClass.PCB P_CLS = P.PathSegment def __init__(self, p): # pragma: no cover super().__init__(p) self._min_exp = float("inf") self._setup() def _setup(self): self.info = InfoOpaqueField(self.p.info) self._calc_min_exp() self.sibra_ext = None if self.is_sibra(): self.sibra_ext = SibraPCBExt(self.p.exts.sibra) @classmethod def from_values(cls, info, rev_infos=None, sibra_ext=None): # pragma: no cover p = cls.P_CLS.new_message(info=info.pack()) if sibra_ext: p.exts.sibra = sibra_ext.p if rev_infos: p.exts.init("revInfos", len(rev_infos)) for i, info in enumerate(rev_infos): p.exts.revInfos[i] = info.copy() return cls(p) def _calc_min_exp(self): # NB: only the expiration time of the first pcbm is considered. for asm in self.iter_asms(): self._min_exp = min(self._min_exp, asm.pcbm(0).hof().exp_time) def asm(self, idx): # pragma: no cover return ASMarking(self.p.asms[idx]) def iter_asms(self, start=0): # pragma: no cover for i in range(start, len(self.p.asms)): yield self.asm(i) def is_sibra(self): # pragma: no cover return bool(self.p.exts.sibra.id) def sig_pack(self, ver=3): b = [] if ver >= 3: b.append(self.p.info) # ifID field is changed on the fly, and so is ignored. for asm in self.iter_asms(): b.append(asm.sig_pack(9)) if self.is_sibra(): b.append(self.sibra_ext.sig_pack(2)) return b"".join(b) def sign(self, key, set_=True): # pragma: no cover assert not self.p.asms[-1].sig sig = sign(self.sig_pack(3), key) if set_: self.p.asms[-1].sig = sig return sig def add_asm(self, asm): # pragma: no cover """ Appends a new ASMarking block. """ d = self.p.to_dict() d.setdefault('asms', []).append(asm.p) self.p.from_dict(d) self._update_info() self._min_exp = min(self._min_exp, asm.pcbm(0).hof().exp_time) def _update_info(self): # pragma: no cover self.info.hops = len(self.p.asms) self.p.info = self.info.pack() def add_sibra_ext(self, ext_p): # pragma: no cover self.p.exts.sibra = ext_p.copy() self.sibra_ext = SibraPCBExt(self.p.exts.sibra) def add_rev_infos(self, rev_infos): # pragma: no cover """ Appends a list of revocations to the PCB. Replaces existing revocations with newer ones. """ if not rev_infos: return existing = {} current_epoch = ConnectedHashTree.get_current_epoch() for i in range(len(self.p.exts.revInfos)): orphan = self.p.exts.revInfos.disown(i) info_p = orphan.get() if info_p.epoch >= current_epoch: existing[(info_p.isdas, info_p.ifID)] = orphan # Remove revocations for which we already have a newer one. filtered = [] for info in rev_infos: if (info.p.epoch >= current_epoch and (info.p.isdas, info.p.ifID) not in existing): filtered.append(info) self.p.exts.init("revInfos", len(existing) + len(filtered)) for i, orphan in enumerate(existing.values()): self.p.exts.revInfos.adopt(i, orphan) n_existing = len(existing) for i, info in enumerate(filtered): self.p.exts.revInfos[n_existing + i] = info.p def remove_crypto(self): # pragma: no cover """ Removes the signatures and certificates from each AS block. """ for asm in self.iter_asms(): asm.remove_sig() asm.remove_chain() def get_path(self, reverse_direction=False): """ Returns the list of HopOpaqueFields in the path. """ hofs = [] info = InfoOpaqueField(self.p.info) asms = list(self.iter_asms()) if reverse_direction: asms = reversed(asms) info.up_flag ^= True for asm in asms: hofs.append(asm.pcbm(0).hof()) return SCIONPath.from_values(info, hofs) def first_ia(self): # pragma: no cover return self.asm(0).isd_as() def last_ia(self): # pragma: no cover return self.asm(-1).isd_as() def last_hof(self): # pragma: no cover if self.p.asms: return self.asm(-1).pcbm(0).hof() return None def get_hops_hash(self, hex=False): # pragma: no cover """ Returns the hash over all triples (ISD_AS, IG_IF, EG_IF) included in the path segment. """ h = SHA256.new() for asm in self.iter_asms(): pcbm = asm.pcbm(0) h.update(asm.isd_as().pack() + struct.pack("!QQ", pcbm.p.inIF, pcbm.p.outIF)) if hex: return h.hexdigest() return h.digest() def get_n_peer_links(self): # pragma: no cover """Return the total number of peer links in the PathSegment.""" n = 0 for asm in self.p.asms: n += len(asm.pcbms) - 1 return n def get_n_hops(self): # pragma: no cover """Return the number of hops in the PathSegment.""" return len(self.p.asms) def get_timestamp(self): # pragma: no cover """Returns the creation timestamp of this PathSegment.""" return self.info.timestamp def set_timestamp(self, timestamp): # pragma: no cover """Updates the timestamp in the IOF.""" assert timestamp < 2 ** 32 - 1 self.info.timestamp = timestamp self._update_info() def get_expiration_time(self): # pragma: no cover """ Returns the expiration time of the path segment in real time. If a PCB extension in the last ASMarking supplies an expiration time, use that. Otherwise fall-back to the standard expiration time calculation. """ if self.is_sibra(): return self.sibra_ext.exp_ts() return self.info.timestamp + int(self._min_exp * EXP_TIME_UNIT) def flags(self): # pragma: no cover f = 0 if self.is_sibra(): f |= PSF.SIBRA return f def rev_info(self, idx): return RevocationInfo(self.p.exts.revInfos[idx]) def iter_rev_infos(self, start=0): for i in range(start, len(self.p.exts.revInfos)): yield self.rev_info(i) def get_rev_map(self): """ Returns a dict (ISD_AS, IF) -> RevocationInfo, if there are any revocations in the PCB extensions, otherwise an empty dict. """ result = {} for rev_info in self.iter_rev_infos(): key = (rev_info.isd_as(), rev_info.p.ifID) result[key] = rev_info return result def short_desc(self): # pragma: no cover """ Return a short description string of the PathSegment, consisting of a truncated hash, the IOF timestamp, and the list of hops. """ desc = [] desc.append("%s, %s, " % ( self.get_hops_hash(hex=True)[:12], iso_timestamp(self.get_timestamp()), )) hops = [] for asm in self.iter_asms(): hops.append(str(asm.isd_as())) exts = [] if self.is_sibra(): exts.append(" %s" % self.sibra_ext.short_desc()) for rev_info in self.iter_rev_infos(): exts.append(" %s" % rev_info.short_desc()) desc.append(" > ".join(hops)) if exts: return "%s\n%s" % ("".join(desc), "\n".join(exts)) return "".join(desc) def __str__(self): s = [] s.append("%s:" % self.NAME) s.append(" %s" % self.info) for asm in self.iter_asms(): for line in asm.short_desc().splitlines(): s.append(" %s" % line) if self.sibra_ext: for line in str(self.sibra_ext).splitlines(): s.append(" %s" % line) for rev_info in self.iter_rev_infos(): for line in rev_info.short_desc().splitlines(): s.append(" %s" % line) return "\n".join(s) def __hash__(self): # pragma: no cover return hash(self.get_hops_hash()) # FIMXE(PSz): should add timestamp?
class PathSegment(SCIONPayloadBaseProto): NAME = "PathSegment" PAYLOAD_CLASS = PayloadClass.PCB PAYLOAD_TYPE = PCBType.SEGMENT P_CLS = P.PathSegment def __init__(self, p): # pragma: no cover super().__init__(p) self._min_exp = float("inf") self._setup() def _setup(self): self.info = InfoOpaqueField(self.p.info) self._calc_min_exp() self.sibra_ext = None if self.is_sibra(): self.sibra_ext = SibraPCBExt(self.p.exts.sibra) @classmethod def from_values(cls, info, sibra_ext=None): # pragma: no cover p = cls.P_CLS.new_message(info=info.pack()) if sibra_ext: p.exts.sibra = sibra_ext.p return cls(p) def _calc_min_exp(self): # NB: only the expiration time of the first pcbm is considered. for asm in self.iter_asms(): self._min_exp = min(self._min_exp, asm.pcbm(0).hof().exp_time) def asm(self, idx): # pragma: no cover return ASMarking(self.p.asms[idx]) def iter_asms(self, start=0): # pragma: no cover for i in range(start, len(self.p.asms)): yield self.asm(i) def is_sibra(self): # pragma: no cover return bool(self.p.exts.sibra.id) def sig_pack(self, ver=3): b = [] if ver >= 3: b.append(self.p.info) # ifID field is changed on the fly, and so is ignored. for asm in self.iter_asms(): b.append(asm.sig_pack(9)) if self.is_sibra(): b.append(self.sibra_ext.sig_pack(2)) return b"".join(b) def sign(self, key, set_=True): # pragma: no cover assert not self.p.asms[-1].sig sig = sign(self.sig_pack(3), key) if set_: self.p.asms[-1].sig = sig return sig def add_asm(self, asm): # pragma: no cover """ Appends a new ASMarking block. """ d = self.p.to_dict() d.setdefault('asms', []).append(asm.p) self.p.from_dict(d) self._update_info() self._min_exp = min(self._min_exp, asm.pcbm(0).hof().exp_time) def _update_info(self): # pragma: no cover self.info.hops = len(self.p.asms) self.p.info = self.info.pack() def add_sibra_ext(self, ext_p): # pragma: no cover self.p.exts.sibra = ext_p.copy() self.sibra_ext = SibraPCBExt(self.p.exts.sibra) def remove_crypto(self): # pragma: no cover """ Remover the signatures and certificates from each AS block. """ for asm in self.iter_asms(): asm.remove_sig() asm.remove_chain() def get_path(self, reverse_direction=False): """ Returns the list of HopOpaqueFields in the path. """ hofs = [] info = InfoOpaqueField(self.p.info) asms = list(self.iter_asms()) if reverse_direction: asms = reversed(asms) info.up_flag ^= True for asm in asms: hofs.append(asm.pcbm(0).hof()) return SCIONPath.from_values(info, hofs) def first_ia(self): # pragma: no cover return self.asm(0).isd_as() def last_ia(self): # pragma: no cover return self.asm(-1).isd_as() def last_hof(self): # pragma: no cover if self.p.asms: return self.asm(-1).pcbm(0).hof() return None def get_hops_hash(self, hex=False): """ Returns the hash over all the interface revocation tokens included in the path segment. """ h = SHA256.new() for token in self.get_all_iftokens(): h.update(token) if hex: return h.hexdigest() return h.digest() def get_n_peer_links(self): # pragma: no cover """Return the total number of peer links in the PathSegment.""" n = 0 for asm in self.p.asms: n += len(asm.pcbms) - 1 return n def get_n_hops(self): # pragma: no cover """Return the number of hops in the PathSegment.""" return len(self.p.asms) def get_timestamp(self): # pragma: no cover """Returns the creation timestamp of this PathSegment.""" return self.info.timestamp def set_timestamp(self, timestamp): # pragma: no cover """Updates the timestamp in the IOF.""" assert timestamp < 2**32 - 1 self.info.timestamp = timestamp self._update_info() def get_expiration_time(self): # pragma: no cover """ Returns the expiration time of the path segment in real time. If a PCB extension in the last ASMarking supplies an expiration time, use that. Otherwise fall-back to the standard expiration time calculation. """ if self.is_sibra(): return self.sibra_ext.exp_ts() return self.info.timestamp + int(self._min_exp * EXP_TIME_UNIT) def get_all_iftokens(self): """ Returns all interface revocation tokens included in the path segment. """ tokens = [] for asm in self.p.asms: for pcbm in asm.pcbms: tokens.append(pcbm.igRevToken) tokens.append(asm.egRevToken) return tokens def flags(self): # pragma: no cover f = 0 if self.is_sibra(): f |= PSF.SIBRA return f def short_desc(self): # pragma: no cover """ Return a short description string of the PathSegment, consisting of a truncated hash, the IOF timestamp, and the list of hops. """ desc = [] desc.append("%s, %s, " % ( self.get_hops_hash(hex=True)[:12], iso_timestamp(self.get_timestamp()), )) hops = [] for asm in self.iter_asms(): hops.append(str(asm.isd_as())) exts = [] if self.is_sibra(): exts.append(" %s" % self.sibra_ext.short_desc()) desc.append(" > ".join(hops)) if exts: return "%s\n%s" % ("".join(desc), "\n".join(exts)) return "".join(desc) def __str__(self): s = [] s.append("%s:" % self.NAME) s.append(" %s" % self.info) for asm in self.iter_asms(): for line in asm.short_desc().splitlines(): s.append(" %s" % line) if self.sibra_ext: for line in str(self.sibra_ext).splitlines(): s.append(" %s" % line) return "\n".join(s) def __hash__(self): # pragma: no cover return hash(self.get_hops_hash()) # FIMXE(PSz): should add timestamp?