unpack(x; strict::Tuple=()) = unpack(x, Any; strict=strict)

"""
    unpack(bytes, T::Type = Any; strict::Tuple=())

Return `unpack(IOBuffer(bytes), T; strict=strict)`.
"""
unpack(bytes, ::Type{T}; strict::Tuple=()) where {T} = unpack(IOBuffer(bytes), T; strict=strict)

"""
    unpack(msgpack_byte_stream::IO, T::Type = Any; strict::Tuple=())

Return the Julia value of type `T` deserialized from `msgpack_byte_stream`.

`T` is assumed to have valid [`msgpack_type`](@ref) and [`from_msgpack`](@ref)
definitions.

If `msgpack_type(T) === AnyType()`, `unpack` will deserialize the next
MessagePack object from `msgpack_byte_stream` into the default Julia
representation corresponding to the object's MessagePack type. For details on
default Julia representations, see [`AbstractMsgPackType`](@ref).

The `strict` keyword argument is a `Tuple` of `DataType`s where each element
`S` must obey `msgpack_type(S) === StructType()`. `unpack` will assume that
encountered MessagePack Maps to be deserialized to e.g. `S` will always contain
fields that correspond strictly to the fields of `S`. In other words, the `i`th
key in the Map must correspond to `fieldname(S, i)`, and the `i`th value must
correspond to `getfield(::S, i)`.

See also: [`pack`](@ref), [`construct`](@ref)
"""
unpack(io::IO, ::Type{T}; strict::Tuple=()) where {T} = unpack_type(io, read(io, UInt8), msgpack_type(T), T; strict=strict)

#####
##### `AnyType`
#####

function unpack_type(io, byte, t::AnyType, U::Union; strict)
    A, B = U.a, U.b # Unions are sorted, so `Nothing`/`Missing` would be first
    if A === Nothing || A === Missing
        byte === magic_byte(NilFormat) && return from_msgpack(A, nothing)
        return unpack_type(io, byte, msgpack_type(B), B; strict=strict)
    end
    return _unpack_any(io, byte, U; strict=strict)
end

@inline unpack_type(io, byte, ::AnyType, T::Type; strict) = _unpack_any(io, byte, T; strict=strict)

function _unpack_any(io, byte, ::Type{T}; strict) where {T}
    if byte <= magic_byte_max(IntFixPositiveFormat)
        return unpack_format(io, IntFixPositiveFormat(byte), T)
    elseif byte <= magic_byte_max(MapFixFormat)
        return unpack_format(io, MapFixFormat(byte), T, strict)
    elseif byte <= magic_byte_max(ArrayFixFormat)
        return unpack_format(io, ArrayFixFormat(byte), T, strict)
    elseif byte <= magic_byte_max(StrFixFormat)
        return unpack_format(io, StrFixFormat(byte), T)
    elseif byte === magic_byte(UInt8Format)
        return unpack_format(io, UInt8Format(), T)
    elseif byte === magic_byte(UInt16Format)
        return unpack_format(io, UInt16Format(), T)
    elseif byte === magic_byte(UInt32Format)
        return unpack_format(io, UInt32Format(), T)
    elseif byte === magic_byte(UInt64Format)
        return unpack_format(io, UInt64Format(), T)
    elseif byte === magic_byte(Int8Format)
        return unpack_format(io, Int8Format(), T)
    elseif byte === magic_byte(Int16Format)
        return unpack_format(io, Int16Format(), T)
    elseif byte === magic_byte(Int32Format)
        return unpack_format(io, Int32Format(), T)
    elseif byte === magic_byte(Int64Format)
        return unpack_format(io, Int64Format(), T)
    elseif byte === magic_byte(Float32Format)
        return unpack_format(io, Float32Format(), T)
    elseif byte === magic_byte(Float64Format)
        return unpack_format(io, Float64Format(), T)
    elseif byte === magic_byte(Str8Format)
        return unpack_format(io, Str8Format(), T)
    elseif byte === magic_byte(Str16Format)
        return unpack_format(io, Str16Format(), T)
    elseif byte === magic_byte(Str32Format)
        return unpack_format(io, Str32Format(), T)
    elseif byte === magic_byte(TrueFormat)
        return unpack_format(io, TrueFormat(), T)
    elseif byte === magic_byte(FalseFormat)
        return unpack_format(io, FalseFormat(), T)
    elseif byte === magic_byte(NilFormat)
        return unpack_format(io, NilFormat(), T)
    elseif byte === magic_byte(Array16Format)
        return unpack_format(io, Array16Format(), T, strict)
    elseif byte === magic_byte(Array32Format)
        return unpack_format(io, Array32Format(), T, strict)
    elseif byte === magic_byte(Map16Format)
        return unpack_format(io, Map16Format(), T, strict)
    elseif byte === magic_byte(Map32Format)
        return unpack_format(io, Map32Format(), T, strict)
    elseif byte === magic_byte(Ext8Format)
        return unpack_format(io, Ext8Format(), T)
    elseif byte === magic_byte(Ext16Format)
        return unpack_format(io, Ext16Format(), T)
    elseif byte === magic_byte(Ext32Format)
        return unpack_format(io, Ext32Format(), T)
    elseif byte === magic_byte(ExtFix1Format)
        return unpack_format(io, ExtFix1Format(), T)
    elseif byte === magic_byte(ExtFix2Format)
        return unpack_format(io, ExtFix2Format(), T)
    elseif byte === magic_byte(ExtFix4Format)
        return unpack_format(io, ExtFix4Format(), T)
    elseif byte === magic_byte(ExtFix8Format)
        return unpack_format(io, ExtFix8Format(), T)
    elseif byte === magic_byte(ExtFix16Format)
        return unpack_format(io, ExtFix16Format(), T)
    elseif byte === magic_byte(Bin8Format)
        return unpack_format(io, Bin8Format(), T)
    elseif byte === magic_byte(Bin16Format)
        return unpack_format(io, Bin16Format(), T)
    elseif byte === magic_byte(Bin32Format)
        return unpack_format(io, Bin32Format(), T)
    elseif byte >= magic_byte_min(IntFixNegativeFormat)
        return unpack_format(io, IntFixNegativeFormat(reinterpret(Int8, byte)), T)
    end
    invalid_unpack(io, byte, AnyType(), T) # should be unreachable, but ensures error is thrown if reached
end

#####
##### `StructType`
#####

struct FieldNotFound end

"""
    construct(T::Type, args...)

Return an instance of `T` given `args`; defaults to `T(args...)`.

This function is meant to be overloaded for types `T` where `msgpack_type(T) === StructType()`.

This function is called by [`unpack`](@ref) when deserializing `T` objects. When
called for this purpose, `args` are field values for the given `T` instance and
are passed in the order specified by `fieldname(T, i)`. If a given field of `T`
wasn't found in the object's MessagePack Map representation, the corresponding
value in `args` will be `MsgPack.FieldNotFound()`.
"""
construct(::Type{T}, args...) where {T} = T(args...)

function unpack_type(io, byte, t::StructType, ::Type{T}; strict) where {T}
    if any(T <: S for S in strict)
        if byte > magic_byte_max(MapFixFormat)
            if byte === magic_byte(Map16Format)
                read(io, UInt16)
            elseif byte === magic_byte(Map32Format)
                read(io, UInt32)
            end
        end
        N = fieldcount(T)
        constructor = (args...) -> construct(T, args...)
        Base.@nexprs 32 i -> begin
            F_i = fieldtype(T, i)
            unpack_type(io, read(io, UInt8), StringType(), Skip{Symbol}; strict=strict)
            x_i = unpack_type(io, read(io, UInt8), msgpack_type(F_i), F_i; strict=strict)
            N == i && return Base.@ncall i constructor x
        end
        others = Any[]
        for i in 33:N
            F_i = fieldtype(T, i)
            unpack_type(io, read(io, UInt8), StringType(), Skip{Symbol}; strict=strict)
            push!(others, unpack_type(io, read(io, UInt8), msgpack_type(F_i), F_i; strict=strict))
        end
        return constructor(x_1, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9, x_10, x_11, x_12, x_13,
                           x_14, x_15, x_16, x_17, x_18, x_19, x_20, x_21, x_22, x_23, x_24, x_25,
                           x_26, x_27, x_28, x_29, x_30, x_31, x_32, others...)
    else
        if byte <= magic_byte_max(MapFixFormat)
            pair_count = xor(byte, magic_byte_min(MapFixFormat))
        elseif byte === magic_byte(Map16Format)
            pair_count = ntoh(read(io, UInt16))
        elseif byte === magic_byte(Map32Format)
            pair_count = ntoh(read(io, UInt32))
        else
            invalid_unpack(io, byte, t, T)
        end
        N = fieldcount(T)
        fields = Any[FieldNotFound() for _ in 1:N]
        for _ in 1:pair_count
            key = unpack(io, Symbol) # TODO validation check?
            Base.@nif(33, # `i` in range 1:(33-1)
                      i -> i <= N && fieldname(T, i) === key,
                      i -> setindex!(fields, unpack(io, fieldtype(T, i); strict=strict), i),
                      i -> begin
                          is_field_still_unread = true
                          for j in 33:N
                              fieldname(T, j) === key || continue
                              setindex!(fields, unpack(io, fieldtype(T, j); strict=strict), j)
                              is_field_still_unread = false
                              break
                          end
                          is_field_still_unread && unpack(io)
                      end)
        end
        return construct(T, fields...)
    end
end

function unpack_type(io, byte, ::StructType, ::Type{Skip{T}}; strict) where {T}
    if any(T <: S for S in strict)
        N = fieldcount(T)
        byte > magic_byte_max(MapFixFormat) && read(io, UInt8)
        Base.@nexprs 32 i -> begin
            F_i = fieldtype(T, i)
            unpack_type(io, read(io, UInt8), StringType(), Skip{Symbol}; strict=strict)
            unpack_type(io, read(io, UInt8), msgpack_type(F_i), Skip{F_i}; strict=strict)
            N == i && return Skip{T}()
        end
        for i in 33:N
            F_i = fieldtype(T, i)
            unpack_type(io, read(io, UInt8), msgpack_type(F_i), Skip{F_i}; strict=strict)
        end
    else
        unpack_type(io, byte, MapType(), Skip{Dict{Symbol,Any}}; strict=strict)
    end
    return Skip{T}()
end

#####
##### `IntegerType`
#####

function unpack_type(io, byte, t::IntegerType, ::Type{T}; strict) where {T}
    if byte <= magic_byte_max(IntFixPositiveFormat)
        return unpack_format(io, IntFixPositiveFormat(byte), T)
    elseif byte === magic_byte(UInt8Format)
        return unpack_format(io, UInt8Format(), T)
    elseif byte === magic_byte(UInt16Format)
        return unpack_format(io, UInt16Format(), T)
    elseif byte === magic_byte(UInt32Format)
        return unpack_format(io, UInt32Format(), T)
    elseif byte === magic_byte(UInt64Format)
        return unpack_format(io, UInt64Format(), T)
    elseif byte === magic_byte(Int8Format)
        return unpack_format(io, Int8Format(), T)
    elseif byte === magic_byte(Int16Format)
        return unpack_format(io, Int16Format(), T)
    elseif byte === magic_byte(Int32Format)
        return unpack_format(io, Int32Format(), T)
    elseif byte === magic_byte(Int64Format)
        return unpack_format(io, Int64Format(), T)
    elseif byte >= magic_byte_min(IntFixNegativeFormat)
        return unpack_format(io, IntFixNegativeFormat(reinterpret(Int8, byte)), T)
    end
    invalid_unpack(io, byte, t, T) # should be unreachable, but ensures error is thrown if reached
end

unpack_format(io, f::IntFixPositiveFormat, ::Type{T}) where {T} = from_msgpack(T, UInt8(f.byte))
unpack_format(io, f::IntFixNegativeFormat, ::Type{T}) where {T} = from_msgpack(T, Int8(f.byte))

unpack_format(io, f::IntFixPositiveFormat, ::Type{T}) where {T<:Skip} = T()
unpack_format(io, f::IntFixNegativeFormat, ::Type{T}) where {T<:Skip} = T()

unpack_format(io, ::UInt8Format, ::Type{T}) where {T} = from_msgpack(T, read(io, UInt8))
unpack_format(io, ::UInt16Format, ::Type{T}) where {T} = from_msgpack(T, ntoh(read(io, UInt16)))
unpack_format(io, ::UInt32Format, ::Type{T}) where {T} = from_msgpack(T, ntoh(read(io, UInt32)))
unpack_format(io, ::UInt64Format, ::Type{T}) where {T} = from_msgpack(T, ntoh(read(io, UInt64)))

unpack_format(io, ::UInt8Format, ::Type{T}) where {T<:Skip} = (skip(io, 1); T())
unpack_format(io, ::UInt16Format, ::Type{T}) where {T<:Skip} = (skip(io, 2); T())
unpack_format(io, ::UInt32Format, ::Type{T}) where {T<:Skip} = (skip(io, 4); T())
unpack_format(io, ::UInt64Format, ::Type{T}) where {T<:Skip} = (skip(io, 8); T())

unpack_format(io, ::Int8Format, ::Type{T}) where {T} = from_msgpack(T, read(io, Int8))
unpack_format(io, ::Int16Format, ::Type{T}) where {T} = from_msgpack(T, ntoh(read(io, Int16)))
unpack_format(io, ::Int32Format, ::Type{T}) where {T} = from_msgpack(T, ntoh(read(io, Int32)))
unpack_format(io, ::Int64Format, ::Type{T}) where {T} = from_msgpack(T, ntoh(read(io, Int64)))

unpack_format(io, ::Int8Format, ::Type{T}) where {T<:Skip} = (skip(io, 1); T())
unpack_format(io, ::Int16Format, ::Type{T}) where {T<:Skip} = (skip(io, 2); T())
unpack_format(io, ::Int32Format, ::Type{T}) where {T<:Skip} = (skip(io, 4); T())
unpack_format(io, ::Int64Format, ::Type{T}) where {T<:Skip} = (skip(io, 8); T())

#####
##### `NilType`
#####

function unpack_type(io, byte, t::NilType, ::Type{T}; strict) where {T}
    byte === magic_byte(NilFormat) && return unpack_format(io, NilFormat(), T)
    invalid_unpack(io, byte, t, T)
end

unpack_type(io, byte, ::NilType, ::Type{T}; strict) where {T<:Skip} = T()

unpack_format(io, ::NilFormat, ::Type{T}) where {T} = from_msgpack(T, nothing)

#####
##### `BooleanType`
#####

function unpack_type(io, byte, t::BooleanType, ::Type{T}; strict) where {T}
    byte === magic_byte(TrueFormat) && return unpack_format(io, TrueFormat(), T)
    byte === magic_byte(FalseFormat) && return unpack_format(io, FalseFormat(), T)
    invalid_unpack(io, byte, t, T)
end

unpack_type(io, byte, ::BooleanType, ::Type{T}; strict) where {T<:Skip} = T()

unpack_format(io, ::TrueFormat, ::Type{T}) where {T} = from_msgpack(T, true)

unpack_format(io, ::FalseFormat, ::Type{T}) where {T} = from_msgpack(T, false)

#####
##### `FloatType`
#####

function unpack_type(io, byte, t::FloatType, ::Type{T}; strict) where {T}
    if byte === magic_byte(Float32Format)
        return unpack_format(io, Float32Format(), T)
    elseif byte === magic_byte(Float64Format)
        return unpack_format(io, Float64Format(), T)
    else
        invalid_unpack(io, byte, t, T)
    end
end

unpack_format(io, ::Float32Format, ::Type{T}) where {T} = from_msgpack(T, ntoh(read(io, Float32)))
unpack_format(io, ::Float32Format, ::Type{T}) where {T<:Skip} = (skip(io, 4); T())
unpack_format(io, ::Float64Format, ::Type{T}) where {T} = from_msgpack(T, ntoh(read(io, Float64)))
unpack_format(io, ::Float64Format, ::Type{T}) where {T<:Skip} = (skip(io, 8); T())

#####
##### `StringType`
#####

function unpack_type(io, byte, t::StringType, ::Type{T}; strict) where {T}
    if byte <= magic_byte_max(StrFixFormat)
        return unpack_format(io, StrFixFormat(byte), T)
    elseif byte === magic_byte(Str8Format)
        return unpack_format(io, Str8Format(), T)
    elseif byte === magic_byte(Str16Format)
        return unpack_format(io, Str16Format(), T)
    elseif byte === magic_byte(Str32Format)
        return unpack_format(io, Str32Format(), T)
    else
        invalid_unpack(io, byte, t, T)
    end
end

unpack_format(io, ::Str8Format, ::Type{T}) where {T} = _unpack_string(io, read(io, UInt8), T)
unpack_format(io, ::Str16Format, ::Type{T}) where {T} = _unpack_string(io, ntoh(read(io, UInt16)), T)
unpack_format(io, ::Str32Format, ::Type{T}) where {T} = _unpack_string(io, ntoh(read(io, UInt32)), T)
unpack_format(io, f::StrFixFormat, ::Type{T}) where {T} = _unpack_string(io, xor(f.byte, magic_byte_min(StrFixFormat)), T)

_unpack_string(io, n, ::Type{T}) where {T} = from_msgpack(T, String(read(io, n)))
_unpack_string(io, n, ::Type{T}) where {T<:Skip} = (skip(io, n); T())

# Below is a nice optimization we can apply when `io` wraps an indexable byte
# buffer; this is really nice for skipping an extra copy when we deserialize
# the field names of `T` where `msgpack_type(T)::MutableStruct`. Like much of
# this package, this trick can be traced back to Jacob Quinn's magnificent
# JSON3.jl!
function _unpack_string(io::Base.GenericIOBuffer, n, ::Type{T}) where {T}
    result = from_msgpack(T, PointerString(pointer(io.data, io.ptr), n))
    skip(io, n)
    return result
end

# necessary to resolve ambiguity
_unpack_string(io::Base.GenericIOBuffer, n, ::Type{T}) where {T<:Skip} = (skip(io, n); T())

#####
##### `BinaryType`
#####

function unpack_type(io, byte, t::BinaryType, ::Type{T}; strict) where {T}
    if byte === magic_byte(Bin8Format)
        return unpack_format(io, Bin8Format(), T)
    elseif byte === magic_byte(Bin16Format)
        return unpack_format(io, Bin16Format(), T)
    elseif byte === magic_byte(Bin32Format)
        return unpack_format(io, Bin32Format(), T)
    else
        invalid_unpack(io, byte, t, T)
    end
end

unpack_format(io, ::Bin8Format, ::Type{T}) where {T} = _unpack_binary(io, read(io, UInt8), T)
unpack_format(io, ::Bin16Format, ::Type{T}) where {T} = _unpack_binary(io, ntoh(read(io, UInt16)), T)
unpack_format(io, ::Bin32Format, ::Type{T}) where {T} = _unpack_binary(io, ntoh(read(io, UInt32)), T)

_unpack_binary(io, n, ::Type{T}) where {T} = from_msgpack(T, read(io, n))
_unpack_binary(io, n, ::Type{T}) where {T<:Skip} = (skip(io, n); T())

#####
##### `ArrayType`
#####

function unpack_type(io, byte, t::ArrayType, ::Type{T}; strict) where {T}
    if byte <= magic_byte_max(ArrayFixFormat)
        return unpack_format(io, ArrayFixFormat(byte), T, strict)
    elseif byte === magic_byte(Array16Format)
        return unpack_format(io, Array16Format(), T, strict)
    elseif byte === magic_byte(Array32Format)
        return unpack_format(io, Array32Format(), T, strict)
    else
        invalid_unpack(io, byte, t, T)
    end
end

unpack_format(io, ::Array16Format, ::Type{T}, strict) where {T} = _unpack_array(io, ntoh(read(io, UInt16)), T, strict)
unpack_format(io, ::Array32Format, ::Type{T}, strict) where {T} = _unpack_array(io, ntoh(read(io, UInt32)), T, strict)
unpack_format(io, f::ArrayFixFormat, ::Type{T}, strict) where {T} = _unpack_array(io, xor(f.byte, magic_byte_min(ArrayFixFormat)), T, strict)

_eltype(T) = eltype(T)

function _unpack_array(io, n, ::Type{T}, strict) where {T}
    E = _eltype(T)
    e = msgpack_type(E)
    result = Vector{E}(undef, n)
    for i in 1:n
        result[i] = unpack_type(io, read(io, UInt8), e, E; strict=strict)
    end
    return from_msgpack(T, result)
end

function _unpack_array(io, n, ::Type{Skip{T}}, strict) where {T}
    E = _eltype(T)
    e = msgpack_type(E)
    for _ in 1:n
        unpack_type(io, read(io, UInt8), e, Skip{E}; strict=strict)
    end
    return Skip{T}()
end

function _unpack_array(io, n, ::Type{T}, strict) where {N, T<:Tuple{Vararg{Any, N}}}
    return T(tuple((
        unpack_type(io, read(io, UInt8), msgpack_type(fieldtype(T, i)), fieldtype(T, i); strict=strict)
        for i in 1:N
    )...))
end

function _unpack_array(io, n, ::Type{Skip{T}}, strict) where {T<:Tuple}
    for i in 1:N
        unpack_type(io, read(io, UInt8), msgpack_type(fieldtype(T, i)), fieldtype(T, i); strict=strict)
    end
    return Skip{T}()
end

function _unpack_array(io::Base.GenericIOBuffer, n, ::Type{T}, strict) where {T<:ArrayView}
    E = _eltype(T)
    e = msgpack_type(E)
    start = position(io)
    positions = Vector{UInt64}(undef, n)
    for i in 1:length(positions)
        positions[i] = (position(io) - start) + 1
        unpack_type(io, read(io, UInt8), e, Skip{E}; strict=strict)
    end
    bytes = view(io.data, (start + 1):position(io))
    return ArrayView{E}(bytes, positions, strict)
end

#####
##### `MapType`
#####

function unpack_type(io, byte, t::MapType, ::Type{T}; strict) where {T}
    if byte <= magic_byte_max(MapFixFormat)
        return unpack_format(io, MapFixFormat(byte), T, strict)
    elseif byte === magic_byte(Map16Format)
        return unpack_format(io, Map16Format(), T, strict)
    elseif byte === magic_byte(Map32Format)
        return unpack_format(io, Map32Format(), T, strict)
    else
        invalid_unpack(io, byte, t, T)
    end
end

unpack_format(io, ::Map16Format, ::Type{T}, strict) where {T} = _unpack_map(io, ntoh(read(io, UInt16)), T, strict)
unpack_format(io, ::Map32Format, ::Type{T}, strict) where {T} = _unpack_map(io, ntoh(read(io, UInt32)), T, strict)
unpack_format(io, f::MapFixFormat, ::Type{T}, strict) where {T} = _unpack_map(io, xor(f.byte, magic_byte_min(MapFixFormat)), T, strict)

_keytype(T) = Any
_keytype(::Type{T}) where {K,V,T<:AbstractDict{K,V}} = keytype(T)

_valtype(T) = Any
_valtype(::Type{T}) where {K,V,T<:AbstractDict{K,V}} = valtype(T)

function _unpack_map(io, n, ::Type{T}, strict) where {T}
    K = _keytype(T)
    k = msgpack_type(K)
    V = _valtype(T)
    v = msgpack_type(V)
    dict = Dict{K,V}()
    for _ in 1:n
        key = unpack_type(io, read(io, UInt8), k, K; strict=strict)
        val = unpack_type(io, read(io, UInt8), v, V; strict=strict)
        dict[key] = val
    end
    return from_msgpack(T, dict)
end

function _unpack_map(io, n, ::Type{Skip{T}}, strict) where {T}
    K = _keytype(T)
    k = msgpack_type(K)
    V = _valtype(T)
    v = msgpack_type(V)
    for i in 1:n
        unpack_type(io, read(io, UInt8), k, Skip{K}; strict=strict)
        unpack_type(io, read(io, UInt8), v, Skip{V}; strict=strict)
    end
    return Skip{T}()
end

function _unpack_map(io::Base.GenericIOBuffer, n, ::Type{T}, strict) where {T<:MapView}
    K = _keytype(T)
    k = msgpack_type(K)
    V = _valtype(T)
    v = msgpack_type(V)
    start = position(io)
    positions = Dict{K,UnitRange{UInt64}}()
    for _ in 1:n
        key = unpack_type(io, read(io, UInt8), k, K; strict=strict)
        value_start = (position(io) - start) + 1
        unpack_type(io, read(io, UInt8), v, Skip{V}; strict=strict)
        positions[key] = value_start:(position(io) - start)
    end
    bytes = view(io.data, (start + 1):position(io))
    return MapView{K,V}(bytes, positions, strict)
end

#####
##### `ExtensionType`
#####

function unpack_type(io, byte, t::ExtensionType, ::Type{T}; strict) where {T}
    byte === magic_byte(ExtFix1Format) && return unpack_format(io, ExtFix1Format(), T)
    byte === magic_byte(ExtFix2Format) && return unpack_format(io, ExtFix2Format(), T)
    byte === magic_byte(ExtFix4Format) && return unpack_format(io, ExtFix4Format(), T)
    byte === magic_byte(ExtFix8Format) && return unpack_format(io, ExtFix8Format(), T)
    byte === magic_byte(ExtFix16Format) && return unpack_format(io, ExtFix16Format(), T)
    byte === magic_byte(Ext8Format) && return unpack_format(io, Ext8Format(), T)
    byte === magic_byte(Ext16Format) && return unpack_format(io, Ext16Format(), T)
    byte === magic_byte(Ext32Format) && return unpack_format(io, Ext32Format(), T)
    invalid_unpack(io, byte, t, T)
end

unpack_format(io, ::ExtFix1Format, ::Type{T}) where {T} = _unpack_extension(io, 1, T)
unpack_format(io, ::ExtFix2Format, ::Type{T}) where {T} = _unpack_extension(io, 2, T)
unpack_format(io, ::ExtFix4Format, ::Type{T}) where {T} = _unpack_extension(io, 4, T)
unpack_format(io, ::ExtFix8Format, ::Type{T}) where {T} = _unpack_extension(io, 8, T)
unpack_format(io, ::ExtFix16Format, ::Type{T}) where {T} = _unpack_extension(io, 16, T)
unpack_format(io, ::Ext8Format, ::Type{T}) where {T} = _unpack_extension(io, read(io, UInt8), T)
unpack_format(io, ::Ext16Format, ::Type{T}) where {T} = _unpack_extension(io, ntoh(read(io, UInt16)), T)
unpack_format(io, ::Ext32Format, ::Type{T}) where {T} = _unpack_extension(io, ntoh(read(io, UInt32)), T)

function _unpack_extension(io, n, ::Type{T}) where {T}
    type = read(io, Int8)
    data = read(io, n)
    return from_msgpack(T, Extension(type, data))
end

#####
##### utilities
#####

@noinline function invalid_unpack(io, byte, m, T)
    error("invalid byte $(repr(byte)) encountered in $(io) attempting to read a MsgPack $(m) into a Julia $(T) at position $(position(io))")
end