コード例 #1
0
ファイル: ChunkWriter.cs プロジェクト: romkatv/ChunkIO
        public async Task WriteAsync(UserData userData, byte[] array, int offset, int count)
        {
            if (array == null)
            {
                throw new ArgumentNullException(nameof(array));
            }
            if (offset < 0 || count < 0 || array.Length - offset < count)
            {
                throw new Exception($"Invalid range for array of length {array.Length}: [{offset}, {offset} + {count})");
            }
            if (count > MaxContentLength)
            {
                throw new Exception($"Chunk too big: {count}");
            }
            if (_torn)
            {
                await WritePadding();

                _torn = false;
            }
            var meter = new Meter()
            {
                ChunkBeginPosition = _writer.Position
            };
            var header = new ChunkHeader()
            {
                UserData      = userData,
                ContentLength = count,
                ContentHash   = SipHash.ComputeHash(array, offset, count),
            };

            if (!header.EndPosition(meter.ChunkBeginPosition).HasValue)
            {
                throw new Exception($"File too big: {meter.ChunkBeginPosition}");
            }
            meter.WriteTo(_meter);
            header.WriteTo(_header);
            try {
                await WriteMetered(_header, 0, _header.Length);
                await WriteMetered(array, offset, count);
            }
            catch {
                _torn = true;
                throw;
            }
        }
コード例 #2
0
        // Returns true if it's safe to read the chunk after the specified chunk. There are
        // two cases where blindly reading the next chunk can backfire:
        //
        //   1. By reading the next chunk you'll actually skip valid chunks. So the chunk you'll read
        //      won't really be the next.
        //   2. Even if the next chunk decodes correctly (both its header and content hashes match),
        //      it may be not a real chunk but a part of some larger chunk's content.
        //
        // To see these horrors in action, replace the implementation of this method with `return true`
        // and run tests. TrickyTruncateTest() and TrickyEmbedTest() should fail. They correspond to the
        // two cases described above. Note that there is no malicious action in these tests. The chunkio
        // files are produced by ChunkWriter. There are also file truncations, but they can naturally
        // happen when a processing with ChunkWriter crashes.
        async Task <bool> IsSkippable(long begin, ChunkHeader header)
        {
            long end = header.EndPosition(begin).Value;

            if (begin / MeterInterval == (end - 1) / MeterInterval)
            {
                return(true);
            }
            Meter?meter = await ReadMeter(MeterBefore(end - 1));

            if (meter.HasValue)
            {
                return(meter.Value.ChunkBeginPosition == begin);
            }
            var  content = new byte[header.ContentLength];
            long pos     = MeteredPosition(begin, ChunkHeader.Size).Value;

            return(await ReadMetered(pos, content, 0, content.Length) && SipHash.ComputeHash(content) == header.ContentHash);
        }
コード例 #3
0
        async Task <Meter?> ReadMeter(long pos)
        {
            Debug.Assert(pos >= 0 && pos % MeterInterval == 0);
            if (pos == 0)
            {
                return new Meter()
                       {
                           ChunkBeginPosition = 0
                       }
            }
            ;
            _reader.Seek(pos);
            if (await _reader.ReadAsync(_meter, 0, _meter.Length) != _meter.Length)
            {
                return(null);
            }
            var res = new Meter();

            if (!res.ReadFrom(_meter))
            {
                return(null);
            }
            if (res.ChunkBeginPosition > pos)
            {
                return(null);
            }
            return(res);
        }

        async Task <ChunkHeader?> ReadChunkHeader(long pos)
        {
            if (!await ReadMetered(pos, _header, 0, _header.Length))
            {
                return(null);
            }
            var res = new ChunkHeader();

            if (!res.ReadFrom(_header))
            {
                return(null);
            }
            if (!res.EndPosition(pos).HasValue)
            {
                return(null);
            }
            return(res);
        }

        async Task <bool> ReadMetered(long pos, byte[] array, int offset, int count)
        {
            Debug.Assert(array != null);
            Debug.Assert(offset >= 0);
            Debug.Assert(array.Length - offset >= count);
            if (!(MeteredPosition(pos, count) <= Length))
            {
                return(false);
            }
            while (count > 0)
            {
                Debug.Assert(IsValidPosition(pos));
                if (pos % MeterInterval == 0)
                {
                    pos += Meter.Size;
                }
                _reader.Seek(pos);
                int n = Math.Min(count, MeterInterval - (int)(pos % MeterInterval));
                if (await _reader.ReadAsync(array, offset, n) != n)
                {
                    return(false);
                }
                pos    += n;
                offset += n;
                count  -= n;
            }
            return(true);
        }