class ReplicaTopologyProvider implements TopologyProvider {

    public static final Pattern ROLE_PATTERN = Pattern.compile("^role\\:([a-z]+)$", Pattern.MULTILINE);

    public static final Pattern SLAVE_PATTERN = Pattern.compile("^slave(\\d+)\\:([a-zA-Z\\,\\=\\d\\.\\:\\-]+)$",
            Pattern.MULTILINE);

    public static final Pattern MASTER_HOST_PATTERN = Pattern.compile("^master_host\\:([a-zA-Z\\,\\=\\d\\.\\:\\-]+)$",
            Pattern.MULTILINE);

    public static final Pattern MASTER_PORT_PATTERN = Pattern.compile("^master_port\\:(\\d+)$", Pattern.MULTILINE);

    public static final Pattern IP_PATTERN = Pattern.compile("ip\\=([a-zA-Z\\d\\.\\:\\-]+)");

    public static final Pattern PORT_PATTERN = Pattern.compile("port\\=([\\d]+)");

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReplicaTopologyProvider.class);

    private final StatefulRedisConnection<?, ?> connection;

    private final RedisURI redisURI;

    /**
     * Creates a new {@link ReplicaTopologyProvider}.
     *
     * @param connection must not be {@code null}
     * @param redisURI must not be {@code null}
     */
    public ReplicaTopologyProvider(StatefulRedisConnection<?, ?> connection, RedisURI redisURI) {

        LettuceAssert.notNull(connection, "Redis Connection must not be null");
        LettuceAssert.notNull(redisURI, "RedisURI must not be null");

        this.connection = connection;
        this.redisURI = redisURI;
    }

    @Override
    public List<RedisNodeDescription> getNodes() {

        logger.debug("Performing topology lookup");

        String info = connection.sync().info("replication");
        try {
            return getNodesFromInfo(info);
        } catch (RuntimeException e) {
            throw Exceptions.bubble(e);
        }
    }

    @Override
    public CompletableFuture<List<RedisNodeDescription>> getNodesAsync() {

        logger.debug("Performing topology lookup");

        RedisFuture<String> info = connection.async().info("replication");

        try {
            return Mono.fromCompletionStage(info).timeout(redisURI.getTimeout()).map(this::getNodesFromInfo).toFuture();
        } catch (RuntimeException e) {
            throw Exceptions.bubble(e);
        }
    }

    protected List<RedisNodeDescription> getNodesFromInfo(String info) {

        List<RedisNodeDescription> result = new ArrayList<>();

        RedisNodeDescription currentNodeDescription = getCurrentNodeDescription(info);

        result.add(currentNodeDescription);

        if (currentNodeDescription.getRole().isUpstream()) {
            result.addAll(getReplicasFromInfo(info));
        } else {
            result.add(getMasterFromInfo(info));
        }

        return result;
    }

    private RedisNodeDescription getCurrentNodeDescription(String info) {

        Matcher matcher = ROLE_PATTERN.matcher(info);

        if (!matcher.find()) {
            throw new IllegalStateException("No role property in info " + info);
        }

        return getRedisNodeDescription(matcher);
    }

    private List<RedisNodeDescription> getReplicasFromInfo(String info) {

        List<RedisNodeDescription> replicas = new ArrayList<>();

        Matcher matcher = SLAVE_PATTERN.matcher(info);
        while (matcher.find()) {

            String group = matcher.group(2);
            String ip = getNested(IP_PATTERN, group, 1);
            String port = getNested(PORT_PATTERN, group, 1);

            replicas.add(new RedisMasterReplicaNode(ip, Integer.parseInt(port), redisURI, RedisInstance.Role.SLAVE));
        }

        return replicas;
    }

    private RedisNodeDescription getMasterFromInfo(String info) {<FILL_FUNCTION_BODY>}

    private String getNested(Pattern pattern, String string, int group) {

        Matcher matcher = pattern.matcher(string);
        if (matcher.find()) {
            return matcher.group(group);
        }

        throw new IllegalArgumentException("Cannot extract group " + group + " with pattern " + pattern + " from " + string);

    }

    private RedisNodeDescription getRedisNodeDescription(Matcher matcher) {

        String roleString = matcher.group(1);
        RedisInstance.Role role = null;

        if (RedisInstance.Role.MASTER.name().equalsIgnoreCase(roleString)) {
            role = RedisInstance.Role.UPSTREAM;
        }

        if (RedisInstance.Role.SLAVE.name().equalsIgnoreCase(roleString)
                | RedisInstance.Role.REPLICA.name().equalsIgnoreCase(roleString)) {
            role = RedisInstance.Role.REPLICA;
        }

        if (role == null) {
            throw new IllegalStateException("Cannot resolve role " + roleString + " to " + RedisInstance.Role.UPSTREAM + " or "
                    + RedisInstance.Role.REPLICA);
        }

        return new RedisMasterReplicaNode(redisURI.getHost(), redisURI.getPort(), redisURI, role);
    }

}


        Matcher masterHostMatcher = MASTER_HOST_PATTERN.matcher(info);
        Matcher masterPortMatcher = MASTER_PORT_PATTERN.matcher(info);

        boolean foundHost = masterHostMatcher.find();
        boolean foundPort = masterPortMatcher.find();

        if (!foundHost || !foundPort) {
            throw new IllegalStateException("Cannot resolve master from info " + info);
        }

        String host = masterHostMatcher.group(1);
        int port = Integer.parseInt(masterPortMatcher.group(1));

        return new RedisMasterReplicaNode(host, port, redisURI, RedisInstance.Role.UPSTREAM);
    
<no_super_class>