Is it Valid Modified UTF-8? UTF-8 is a relatively simple way to encode Unicode codepoints in a variable-width format such that it doesn't easily confuse code that isn't Unicode aware.

UTF-8 overview


Bytes in the range of 1-0x7F, inclusive, are normally valid
Bytes with the bit pattern 10XX XXXX are considered continuation bytes, with the six least significant bits being used to encode part of a codepoint. These must not appear unless they are expected by a preceding byte.
Bytes with the pattern 110X XXXX expect one continuation byte afterward
Bytes with the pattern 1110 XXXX expect two continuation bytes afterward
Bytes with the pattern 1111 0XXX expect three continuation bytes afterward
All other bytes are invalid and should not appear anywhere in a UTF-8 stream. 5, 6, and 7 byte clusters are possible in theory, but will not be allowed for the purposes of this challenge.


Overlong encodings

UTF-8 also requires that a codepoint should be represented with the minimum number of bytes. Any byte sequence that could be represented with fewer bytes is not valid. Modified UTF-8 adds one exception to this for null characters (U+0000), which should be represented as C0 80 (hex representation)), and instead disallows null bytes to appear anywhere in the stream. (This makes it compatible with null-terminated strings)

Challenge

You are to make a program that, when given a string of bytes, will determine if that string represents valid Modified UTF-8 and will return a truthy value if valid and a falsy value otherwise. Note that you must check for overlong encodings and null bytes (since this is Modified UTF-8). You do not need to decode the UTF-8 values.

Examples

41 42 43  ==&gt; yes (all bytes are in the 0-0x7F range)
00 01 02  ==&gt; no (there is a null byte in the stream)
80 7F 41  ==&gt; no (there is a continuation byte without a starter byte)
D9 84 10  ==&gt; yes (the correct number of continuation bytes follow a starter byte)
F0 81 82 41  ==&gt; no (there are not enough continuation bytes after F0)
EF 8A A7 91  ==&gt; no (too many continuation bytes)
E1 E1 01  ==&gt; no (starter byte where a continuation byte is expected)
E0 80 87  ==&gt; no (overlong encoding)
41 C0 80  ==&gt; yes (null byte encoded with the only legal overlong encoding)
F8 42 43  ==&gt; no (invalid byte 'F8')


Rules


Standard rules and loopholes apply
Input and output can be in any convenient format as long as all values in the unsigned byte range (0-255) can be read in.


You may need to use an array or file rather than a null-terminated string. You need to be able to read null bytes.

Shortest code wins!
Note that using builtins to decode the UTF-8 is not guaranteed to conform to the requirements given here. You may need to work around it and create special cases.


EDIT: added bonus for not using builtins that decode UTF-8

EDIT2: removed bonus since only the Rust answer qualified and it's awkward to define.
Elixir, 69 bytes

import String
&amp;valid? replace replace(&amp;1,&lt;&lt;0&gt;&gt;,"\xFF"),"\xC0\x80","0"


Try it online!

Makes use of built-in string validation function. Takes input as Elixir binary.