def get_coverage(router_features, rows, possible_args, extra_features=lambda feature: False):
    """
    Generates pairwise coverage information for a given set of router feature triplets as well as a list of rows
    containing parameter values
    :param router_features: List of router feature triplets
    :param rows: Rows which each map each router feature triplet to a parameter value
    :param possible_args: A dict mapping containing all possible parameter values for a given feature
    :param extra_features: function which returns True if the given feature should have an extra value representing
        "invalid" configurations. This is used only for the random baseline, in particular to represent for example
        using a route-map which is not actually defined on the router
    :return: The total number of unique pairs in router_features as well as the number of unique pairs covered by rows
    """
    covered_pairs = {(f1, f2): set() for f1 in router_features for f2 in router_features if f1 != f2}

    def num_feature_args(feature):
        if extra_features(feature):
            return len(possible_args[feature]) + 2
        else:
            return len(possible_args[feature]) + 1

    num_pairs = {((r1, f1, arg1), (r2, f2, arg2)): num_feature_args(f1) * num_feature_args(f2)
                 for (r1, f1, arg1) in router_features for (r2, f2, arg2) in router_features if
                 (r1, f1, arg1) != (r2, f2, arg2)}

    coverage = []

    for row in rows:
        for f1 in row:
            for f2 in row:
                if f1 != f2:
                    covered_pairs[f1, f2].add((row[f1], row[f2]))

        covered = 0
        for p in covered_pairs:
            covered = covered + len(covered_pairs[p])

        coverage.append(covered)

    total = 0
    for p in num_pairs:
        total = total + num_pairs[p]

    return total, coverage