示例#1
0
 def handle_pcbs_propagation(self):
     """
     Generate a new beacon or gets ready to forward the one received.
     """
     timestamp = int(SCIONTime.get_time())
     # Create beacon for downstream ASes.
     down_iof = InfoOpaqueField.from_values(timestamp, self.addr.isd_as[0])
     downstream_pcb = PathSegment.from_values(down_iof)
     propagated_pcbs = self.propagate_downstream_pcb(downstream_pcb)
     # Create beacon for core ASes.
     core_iof = InfoOpaqueField.from_values(timestamp, self.addr.isd_as[0])
     core_pcb = PathSegment.from_values(core_iof)
     propagated = self.propagate_core_pcb(core_pcb)
     for k, v in propagated.items():
         propagated_pcbs[k].extend(v)
     # Propagate received beacons. A core beacon server can only receive
     # beacons from other core beacon servers.
     beacons = []
     with self._rev_seg_lock:
         for ps in self.core_beacons.values():
             beacons.extend(ps.get_best_segments())
     for pcb in beacons:
         propagated = self.propagate_core_pcb(pcb)
         for k, v in propagated.items():
             propagated_pcbs[k].extend(v)
     self._log_propagations(propagated_pcbs)
示例#2
0
 def _create_one_hop_path(self, egress_if):
     ts = int(SCIONTime.get_time())
     info = InfoOpaqueField.from_values(ts, self.addr.isd_as[0], hops=2)
     hf1 = HopOpaqueField.from_values(self.HOF_EXP_TIME, 0, egress_if)
     hf1.set_mac(self.of_gen_key, ts, None)
     # Return a path where second HF is empty.
     return SCIONPath.from_values(info, [hf1, HopOpaqueField()])
示例#3
0
    def _parse_iof(self, data, label):
        """
        Parse a raw :any:`InfoOpaqueField`.

        :param Raw data: Raw instance.
        :param str label: OF label.
        """
        iof = InfoOpaqueField(data.pop(InfoOpaqueField.LEN))
        self._ofs.set(label, [iof])
        return iof
示例#4
0
文件: pcb.py 项目: sasjafor/scion
 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)
示例#5
0
 def handle_pcbs_propagation(self):
     """
     Generate a new beacon or gets ready to forward the one received.
     """
     timestamp = int(SCIONTime.get_time())
     # Create beacon for downstream ASes.
     down_iof = InfoOpaqueField.from_values(timestamp, self.addr.isd_as[0])
     downstream_pcb = PathSegment.from_values(down_iof)
     self.propagate_downstream_pcb(downstream_pcb)
     # Create beacon for core ASes.
     core_iof = InfoOpaqueField.from_values(timestamp, self.addr.isd_as[0])
     core_pcb = PathSegment.from_values(core_iof)
     core_count = self.propagate_core_pcb(core_pcb)
     # Propagate received beacons. A core beacon server can only receive
     # beacons from other core beacon servers.
     beacons = []
     for ps in self.core_beacons.values():
         beacons.extend(ps.get_best_segments())
     for pcb in beacons:
         core_count += self.propagate_core_pcb(pcb)
     if core_count:
         logging.info("Propagated %d Core PCBs", core_count)
示例#6
0
    def test(self):
        """
        Test the main functionalities of the path store.
        """
        path_policy_file = "topology/ISD1/path_policies/ISD1-AD10.json"
        path_policy = PathPolicy.from_file(path_policy_file)
        test_segments = PathStore(path_policy)
        print("Best paths: " + str(len(test_segments.get_best_segments())))
        print("Paths in path store: " + str(len(test_segments.candidates)))
        print("Paths in latest history snapshot: " +
              str(len(test_segments.get_latest_history_snapshot())) + "\n")

        path = 1
        for _ in range(1, 6):
            for _ in range(1, 6):
                pcb = PathSegment()
                pcb.iof = InfoOpaqueField.from_values(OFT.TDC_XOVR, False,
                                                      int(time.time()), path)
                ad_marking = self._create_ad_marking()
                pcb.add_ad(ad_marking)
                print("insert path " + str(path) + ", exp time: " +
                      str(pcb.get_expiration_time()))
                test_segments.add_segment(pcb)
                path += 1
            print("Best paths: " + str(len(test_segments.get_best_segments())))
            print("Paths in path store: " + str(len(test_segments.candidates)))
            print("Paths in latest history snapshot: " +
                  str(len(test_segments.get_latest_history_snapshot())))
            print("Time: " + str(int(time.time())) + "\n")
            time.sleep(5)

        print("Waiting for some paths to expire...")
        time.sleep(25)
        print("Best paths: " + str(len(test_segments.get_best_segments())))
        print("Paths in path store: " + str(len(test_segments.candidates)))
        print("Paths in latest history snapshot: " +
              str(len(test_segments.get_latest_history_snapshot())))
示例#7
0
文件: pcb.py 项目: sasjafor/scion
 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)
示例#8
0
文件: pcb.py 项目: sasjafor/scion
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?
示例#9
0
 def infoF(self):
     info = InfoOpaqueField(self.sdata.p.infoF)
     info.hops = len(self.p.asEntries)
     return info
示例#10
0
文件: pcb.py 项目: ercanucan/scion
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?
示例#11
0
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?