@VisibleForTesting
    static MessageId parseMessageId(String msgIdStr) {
        MessageId msgId;
        if ("latest".equals(msgIdStr)) {
            msgId = MessageId.latest;
        } else if ("earliest".equals(msgIdStr)) {
            msgId = MessageId.earliest;
        } else {
            Matcher matcher = MSG_ID_PATTERN.matcher(msgIdStr);
            if (matcher.find()) {
                msgId = new MessageIdImpl(Long.parseLong(matcher.group(1)), Long.parseLong(matcher.group(2)), -1);
            } else {
                throw new IllegalArgumentException("Message ID must be 'latest', 'earliest' or '<ledgerId>:<entryId>'");
            }
        }
        return msgId;
    }
@Test
    public void testParseMessageId() {
        assertEquals(CmdRead.parseMessageId("latest"), MessageId.latest);
        assertEquals(CmdRead.parseMessageId("earliest"), MessageId.earliest);
        assertEquals(CmdRead.parseMessageId("20:-1"), new MessageIdImpl(20, -1, -1));
        assertEquals(CmdRead.parseMessageId("30:0"), new MessageIdImpl(30, 0, -1));
        try {
            CmdRead.parseMessageId("invalid");
            fail("Should fail to parse invalid message ID");
        } catch (Throwable t) {
            assertTrue(t instanceof IllegalArgumentException);
        }
    }