/// <summary>
        /// Check a canonical formatted tree for errors.
        /// </summary>
        /// <param name="raw">The raw tree data. The array is never modified.</param>
        /// <exception cref="CorruptObjectException">If any error was detected.</exception>
        public void checkTree(byte[] raw)
        {
            int sz = raw.Length;
            int ptr = 0;
            int lastNameB = 0, lastNameE = 0, lastMode = 0;

            while (ptr < sz)
            {
                int thisMode = 0;
                for (; ;)
                {
                    if (ptr == sz)
                    {
                        throw new CorruptObjectException("truncated in mode");
                    }
                    byte c = raw[ptr++];
                    if (' ' == c)
                    {
                        break;
                    }
                    if (c < '0' || c > '7')
                    {
                        throw new CorruptObjectException("invalid mode character");
                    }
                    if (thisMode == 0 && c == '0')
                    {
                        throw new CorruptObjectException("mode starts with '0'");
                    }
                    thisMode <<= 3;
                    thisMode  += (c - (byte)'0');
                }

                if (FileMode.FromBits(thisMode).ObjectType == ObjectType.Bad)
                {
                    throw new CorruptObjectException("invalid mode " + NB.DecimalToBase(thisMode, 8));
                }

                int thisNameB = ptr;
                for (; ;)
                {
                    if (ptr == sz)
                    {
                        throw new CorruptObjectException("truncated in name");
                    }
                    byte c = raw[ptr++];
                    if (c == '\0')
                    {
                        break;
                    }
                    if (c == '/')
                    {
                        throw new CorruptObjectException("name contains '/'");
                    }
                }
                if (thisNameB + 1 == ptr)
                {
                    throw new CorruptObjectException("zero length name");
                }
                if (raw[thisNameB] == '.')
                {
                    int nameLen = (ptr - 1) - thisNameB;
                    if (nameLen == 1)
                    {
                        throw new CorruptObjectException("invalid name '.'");
                    }
                    if (nameLen == 2 && raw[thisNameB + 1] == '.')
                    {
                        throw new CorruptObjectException("invalid name '..'");
                    }
                }
                if (duplicateName(raw, thisNameB, ptr - 1))
                {
                    throw new CorruptObjectException("duplicate entry names");
                }

                if (lastNameB != 0)
                {
                    int cmp = pathCompare(raw, lastNameB, lastNameE,
                                          lastMode, thisNameB, ptr - 1, thisMode);
                    if (cmp > 0)
                    {
                        throw new CorruptObjectException("incorrectly sorted");
                    }
                }

                lastNameB = thisNameB;
                lastNameE = ptr - 1;
                lastMode  = thisMode;

                ptr += Constants.OBJECT_ID_LENGTH;
                if (ptr > sz)
                {
                    throw new CorruptObjectException("truncated in object id");
                }
            }
        }