Decode a utf-8 string encoded as described in MQTT Version
    3.1.1 section 1.5.3 line 177.  This is a 16-bit unsigned length
    followed by a utf-8 encoded string.

    Parameters
    ----------
    f: file
        File-like object with read method.

    Raises
    ------
    UnderflowDecodeError
        Raised when a read failed to extract enough bytes from the
        underlying stream to decode the string.
    Utf8DecodeError
        When any code point in the utf-8 string is invalid.

    Returns
    -------
    int
        Number of bytes consumed.
    str
        A string utf-8 decoded from ``f``.

def decode_utf8(f):
    """Decode a utf-8 string encoded as described in MQTT Version
    3.1.1 section 1.5.3 line 177.  This is a 16-bit unsigned length
    followed by a utf-8 encoded string.

    Parameters
    ----------
    f: file
        File-like object with read method.

    Raises
    ------
    UnderflowDecodeError
        Raised when a read failed to extract enough bytes from the
        underlying stream to decode the string.
    Utf8DecodeError
        When any code point in the utf-8 string is invalid.

    Returns
    -------
    int
        Number of bytes consumed.
    str
        A string utf-8 decoded from ``f``.
    """
    decode = codecs.getdecoder('utf8')

    buf = f.read(FIELD_U16.size)
    if len(buf) < FIELD_U16.size:
        raise UnderflowDecodeError()

    (num_utf8_bytes,) = FIELD_U16.unpack_from(buf)
    num_bytes_consumed = FIELD_U16.size + num_utf8_bytes

    buf = f.read(num_utf8_bytes)
    if len(buf) < num_utf8_bytes:
        raise UnderflowDecodeError()

    try:
        s, num_chars = decode(buf, 'strict')
    except UnicodeError as e:
        raise Utf8DecodeError(e)

    return num_bytes_consumed, s