// zlib/libpng License
//
// Copyright (c) 2019 Sinoa
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
namespace IceMilkTea.Core
{
#region IO処理中にハッシュ計算するストリーム
///
/// ストリームの入出力をそのままハッシュ計算機に流し込むストリームクラスです
///
public class HashStream : Stream
{
// メンバ変数定義
private HashAlgorithm hash;
private bool finish;
private bool leaveOpen;
private bool disposed;
#region プロパティ
///
/// 参照ストリームの CanRead をそのまま返します
///
public override bool CanRead => BaseStream.CanRead;
///
/// 参照ストリームの CanSeek をそのまま返します
///
public override bool CanSeek => BaseStream.CanSeek;
///
/// 参照ストリームの CanWrite をそのまま返します
///
public override bool CanWrite => BaseStream.CanWrite;
///
/// 参照ストリームの Length をそのまま返します
///
public override long Length => BaseStream.Length;
///
/// 参照ストリームの Position にそのままアクセスします
///
public override long Position { get => BaseStream.Position; set => BaseStream.Position = value; }
///
/// このインスタンスが参照している基本ストリームを取得します
///
public Stream BaseStream { get; private set; }
#endregion
#region 初期化&解放
///
/// HashStream クラスのインスタンスを初期化します
///
/// ハッシュ計算を行う対象となるストリーム
/// 使用するハッシュアルゴリズム
/// baseStream が null です
/// hash が null です
public HashStream(Stream baseStream, HashAlgorithm hash) : this(baseStream, hash, false)
{
}
///
/// HashStream クラスのインスタンスを初期化します
///
/// ハッシュ計算を行う対象となるストリーム
/// 使用するハッシュアルゴリズム
/// このインスタンスが破棄される時に baseStream を継続して開き続ける場合は true を、破棄する場合は false を指定。既定は false です。
/// baseStream が null です
/// hash が null です
public HashStream(Stream baseStream, HashAlgorithm hash, bool leaveOpen)
{
// 初期化をする
BaseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
this.hash = hash ?? throw new ArgumentNullException(nameof(hash));
this.leaveOpen = leaveOpen;
}
///
/// 現在使用しているリソースを破棄します
///
/// マネージオブジェクトの破棄の場合は true それ以外は false
protected override void Dispose(bool disposing)
{
// すでに破棄済みなら
if (disposed)
{
// 何もせず終了
return;
}
// マネージの破棄なら
if (disposing)
{
// もしこのインスタンスが破棄される時に参照ストリームを破棄するなら
if (!leaveOpen)
{
// 参照ストリームのDisposeも呼ぶ
BaseStream.Dispose();
}
// ハッシュインスタンスを破棄
hash.Dispose();
}
// 破棄済みをマーク
disposed = true;
// 基本クラスのDisposeも呼ぶ
base.Dispose(disposing);
}
#endregion
#region ハッシュ操作関数
///
/// すべてのストリーム操作が終わった時に、計算されたハッシュデータを取得します
///
/// 取得されたハッシュデータを受け取る配列の参照
public void GetHashData(out byte[] result)
{
// 新しい計算済みハッシュコードの受け取り配列を生成する
ThrowExceptionIfObjectDisposed();
// すでに計算済みなら
if (finish)
{
// ハッシュデータのコピーだけを行う
result = (byte[])hash.Hash.Clone();
return;
}
// ハッシュ計算を最後の処理をしてコピーを行う
hash.TransformFinalBlock(Array.Empty(), 0, 0);
result = (byte[])hash.Hash.Clone();
finish = true;
}
#endregion
#region IO関数
///
/// 参照ストリームの Write をそのまま呼び出します
///
/// ストリームに書き込むデータを保持したバッファ
/// ストリームに書き込むバッファの開始位置
/// ストリームに書き込むデータの数
public override void Write(byte[] buffer, int offset, int count)
{
// ハッシュ計算に流し込んで本来の関数を呼ぶ
ThrowExceptionIfObjectDisposed();
hash.TransformBlock(buffer, offset, count, null, 0);
BaseStream.Write(buffer, offset, count);
}
///
/// 参照ストリームの Read をそのまま呼び出します
///
/// ストリームから読みだした結果を受け取るバッファ
/// バッファが受け取る開始位置
/// 受け取るデータの数
/// 参照ストリームの結果をそのまま返します
public override int Read(byte[] buffer, int offset, int count)
{
// 本来の関数を呼んでもし読み取りサイズが有効なら
ThrowExceptionIfObjectDisposed();
var readSize = BaseStream.Read(buffer, offset, count);
if (readSize > 0)
{
// ハッシュ計算に流し込む
hash.TransformBlock(buffer, offset, readSize, null, 0);
}
// 読み込んだ結果を返す
return readSize;
}
///
/// 参照ストリームの Seek をそのまま呼び出します
///
/// origin を基準にしたシークする長さのオフセット値
/// シークする基準
/// 参照ストリームの結果をそのまま返します
public override long Seek(long offset, SeekOrigin origin)
{
// そのまま受け取ってそのまま返す
ThrowExceptionIfObjectDisposed();
return BaseStream.Seek(offset, origin);
}
///
/// 参照ストリームの SetLength をそのまま呼び出します
///
/// ストリームに設定する新しい長さ
public override void SetLength(long value)
{
// そのまま受け取る
ThrowExceptionIfObjectDisposed();
BaseStream.SetLength(value);
}
///
/// 参照ストリームの Flush を呼び出します
///
public override void Flush()
{
// そのまま呼び出す
ThrowExceptionIfObjectDisposed();
BaseStream.Flush();
}
#endregion
#region 例外スロー関数
///
/// もし、このクラスのインスタンスが破棄済みなら例外をスローします
///
/// インスタンスが破棄済みです
private void ThrowExceptionIfObjectDisposed()
{
// 既に破棄済みなら
if (disposed)
{
// 破棄済み例外を吐く
throw new ObjectDisposedException(null);
}
}
#endregion
}
#endregion
#region
///
/// ストリームの入出力をそのままCRC64計算機に流し込むストリームクラスです
///
/// 使用するCRC64
public class Crc64Stream : Stream where TCrc : Crc64Base
{
// メンバ変数定義
private readonly TCrc crc;
private readonly bool leaveOpen;
private bool disposed;
private ulong hash;
#region プロパティ
///
/// 参照ストリームの CanRead をそのまま返します
///
public override bool CanRead => BaseStream.CanRead;
///
/// 参照ストリームの CanSeek をそのまま返します
///
public override bool CanSeek => BaseStream.CanSeek;
///
/// 参照ストリームの CanWrite をそのまま返します
///
public override bool CanWrite => BaseStream.CanWrite;
///
/// 参照ストリームの Length をそのまま返します
///
public override long Length => BaseStream.Length;
///
/// 参照ストリームの Position にそのままアクセスします
///
public override long Position { get => BaseStream.Position; set => BaseStream.Position = value; }
///
/// このインスタンスが参照している基本ストリームを取得します
///
public Stream BaseStream { get; private set; }
#endregion
#region 初期化&解放
///
/// Crc64Stream クラスのインスタンスを初期化します
///
/// CRC計算を行う対象となるストリーム
/// 使用するCRCアルゴリズム
/// baseStream が null です
/// crc が null です
public Crc64Stream(Stream baseStream, TCrc crc) : this(baseStream, crc, false)
{
}
///
/// Crc64Stream クラスのインスタンスを初期化します
///
/// CRC計算を行う対象となるストリーム
/// 使用するCRCアルゴリズム
/// このインスタンスが破棄される時に baseStream を継続して開き続ける場合は true を、破棄する場合は false を指定。既定は false です。
/// baseStream が null です
/// crc が null です
public Crc64Stream(Stream baseStream, TCrc crc, bool leaveOpen)
{
// 初期化をする
BaseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
this.crc = crc ?? throw new ArgumentNullException(nameof(crc));
this.leaveOpen = leaveOpen;
hash = ulong.MaxValue;
}
///
/// 現在使用しているリソースを破棄します
///
/// マネージオブジェクトの破棄の場合は true それ以外は false
protected override void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing && !leaveOpen)
{
BaseStream.Dispose();
}
disposed = true;
base.Dispose(disposing);
}
#endregion
#region CRC操作関数
///
/// 現在のCRCハッシュ値を取得します
///
/// 現在のCRCハッシュ値を返します
public ulong GetHashData()
{
// 新しい計算済みハッシュコードの受け取り配列を生成する
ThrowExceptionIfObjectDisposed();
return hash ^ ulong.MaxValue;
}
#endregion
#region IO関数
///
/// 参照ストリームの Write をそのまま呼び出します
///
/// ストリームに書き込むデータを保持したバッファ
/// ストリームに書き込むバッファの開始位置
/// ストリームに書き込むデータの数
public override void Write(byte[] buffer, int offset, int count)
{
// ハッシュ計算に流し込んで本来の関数を呼ぶ
ThrowExceptionIfObjectDisposed();
hash = crc.Calculate(hash, buffer, offset, count);
BaseStream.Write(buffer, offset, count);
}
///
/// 参照ストリームの Read をそのまま呼び出します
///
/// ストリームから読みだした結果を受け取るバッファ
/// バッファが受け取る開始位置
/// 受け取るデータの数
/// 参照ストリームの結果をそのまま返します
public override int Read(byte[] buffer, int offset, int count)
{
// 本来の関数を呼んでもし読み取りサイズが有効なら
ThrowExceptionIfObjectDisposed();
var readSize = BaseStream.Read(buffer, offset, count);
if (readSize > 0)
{
// ハッシュ計算に流し込む
hash = crc.Calculate(hash, buffer, offset, readSize);
}
// 読み込んだ結果を返す
return readSize;
}
///
/// 参照ストリームの Seek をそのまま呼び出します
///
/// origin を基準にしたシークする長さのオフセット値
/// シークする基準
/// 参照ストリームの結果をそのまま返します
public override long Seek(long offset, SeekOrigin origin)
{
// そのまま受け取ってそのまま返す
ThrowExceptionIfObjectDisposed();
return BaseStream.Seek(offset, origin);
}
///
/// 参照ストリームの SetLength をそのまま呼び出します
///
/// ストリームに設定する新しい長さ
public override void SetLength(long value)
{
// そのまま受け取る
ThrowExceptionIfObjectDisposed();
BaseStream.SetLength(value);
}
///
/// 参照ストリームの Flush を呼び出します
///
public override void Flush()
{
// そのまま呼び出す
ThrowExceptionIfObjectDisposed();
BaseStream.Flush();
}
#endregion
#region 例外スロー関数
///
/// もし、このクラスのインスタンスが破棄済みなら例外をスローします
///
/// インスタンスが破棄済みです
private void ThrowExceptionIfObjectDisposed()
{
// 既に破棄済みなら
if (disposed)
{
// 破棄済み例外を吐く
throw new ObjectDisposedException(null);
}
}
#endregion
}
#endregion
#region 監視可能なストリーム
///
/// 指定したストリームの監視を可能にするストリームクラスです
///
public class MonitorableStream : Stream
{
// 定数定義
public const int DefaultMeasureUpdateIntervalTime = 1000;
// メンバ変数定義
private Stopwatch readStopwatch;
private Stopwatch writeStopwatch;
private long previousTotalReadSize;
private long previousTotalWriteSize;
private bool leaveOpen;
private bool disposed;
#region プロパティ
///
/// 参照ストリームの CanRead をそのまま返します
///
public override bool CanRead => BaseStream.CanRead;
///
/// 参照ストリームの CanSeek をそのまま返します
///
public override bool CanSeek => BaseStream.CanSeek;
///
/// 参照ストリームの CanWrite をそのまま返します
///
public override bool CanWrite => BaseStream.CanWrite;
///
/// 参照ストリームの Length をそのまま返します
///
public override long Length => BaseStream.Length;
///
/// 参照ストリームの Position にそのままアクセスします
///
public override long Position { get => BaseStream.Position; set => BaseStream.Position = value; }
///
/// このインスタンスが参照している基本ストリームを取得します
///
public Stream BaseStream { get; private set; }
///
/// 参照ストリームによる合計の読み込みサイズを取得します
///
public long TotalReadSize { get; private set; }
///
/// 参照ストリームによる合計の書き込みサイズを取得します
///
public long TotalWriteSize { get; private set; }
///
/// 参照ストリームによる前回の計測合計読み込みサイズの変化量を取得します
///
public long DeltaReadSize { get; private set; }
///
/// 参照ストリームによる前回の計測合計書き込みサイズの変化量を取得します
///
public long DeltaWriteSize { get; private set; }
///
/// 参照ストリームによる読み込みビットレート(bps)を取得します
///
public long ReadBitRate { get; private set; }
///
/// 参照ストリームによる書き込みビットレート(bps)を取得します
///
public long WriteBitRate { get; private set; }
///
/// 計測更新間隔時間をミリ秒で指定できます。(更新間隔が短いほど誤差が広くなる傾向があります)
///
public int MeasureUpdateIntervalTime { get; set; }
#endregion
#region 初期化&破棄
///
/// MonitorableStream クラスのインスタンスを初期化します
///
/// 監視対象となる Stream の参照
/// baseStream が null です
public MonitorableStream(Stream baseStream) : this(baseStream, DefaultMeasureUpdateIntervalTime, false)
{
}
///
/// MonitorableStream クラスのインスタンスを初期化します
///
/// 監視対象となる Stream の参照
/// 計測更新間隔時間をミリ秒で指定。既定は DefaultMeasureUpdateIntervalTime です。
/// baseStream が null です
public MonitorableStream(Stream baseStream, int measureUpdateIntervalTime) : this(baseStream, measureUpdateIntervalTime, false)
{
}
///
/// MonitorableStream クラスのインスタンスを初期化します
///
/// 監視対象となる Stream の参照
/// 計測更新間隔時間をミリ秒で指定。既定は DefaultMeasureUpdateIntervalTime です。
/// このインスタンスが破棄される時に baseStream を継続して開き続ける場合は true を、破棄する場合は false を指定。既定は false です。
/// baseStream が null です
public MonitorableStream(Stream baseStream, int measureUpdateIntervalTime, bool leaveOpen)
{
// 計測用ストップウォッチのインスタンスを作っておく
readStopwatch = Stopwatch.StartNew();
writeStopwatch = Stopwatch.StartNew();
// 受け取ったパラメータをそのまま覚える
BaseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
this.leaveOpen = leaveOpen;
// 計測更新間隔の既定値を設定
MeasureUpdateIntervalTime = measureUpdateIntervalTime;
}
///
/// 現在使用しているリソースを破棄します
///
/// マネージオブジェクトの破棄の場合は true それ以外は false
protected override void Dispose(bool disposing)
{
// すでに破棄済みなら
if (disposed)
{
// 何もせず終了
return;
}
// マネージの破棄なら
if (disposing)
{
// もしこのインスタンスが破棄される時に参照ストリームを破棄するなら
if (!leaveOpen)
{
// 参照ストリームのDisposeも呼ぶ
BaseStream.Dispose();
}
}
// 破棄済みをマーク
disposed = true;
// 基本クラスのDisposeも呼ぶ
base.Dispose(disposing);
}
#endregion
#region IO関数
///
/// 参照ストリームの Write をそのまま呼び出します
///
/// ストリームに書き込むデータを保持したバッファ
/// ストリームに書き込むバッファの開始位置
/// ストリームに書き込むデータの数
public override void Write(byte[] buffer, int offset, int count)
{
// もし初めての書き込みなら
if (TotalWriteSize == 0)
{
// 書き込み計測ストップウォッチを再起動する
writeStopwatch.Restart();
}
// そのまま受け取って計測する
ThrowExceptionIfObjectDisposed();
BaseStream.Write(buffer, offset, count);
MeasureWriteInformation(count);
}
///
/// 参照ストリームの Read をそのまま呼び出します
///
/// ストリームから読みだした結果を受け取るバッファ
/// バッファが受け取る開始位置
/// 受け取るデータの数
/// 参照ストリームの結果をそのまま返します
public override int Read(byte[] buffer, int offset, int count)
{
// もし初めての読み込みなら
if (TotalReadSize == 0)
{
// 読み込み計測ストップウォッチを再起動する
readStopwatch.Restart();
}
// そのまま受け取って計測してからそのまま返す
ThrowExceptionIfObjectDisposed();
var readSize = BaseStream.Read(buffer, offset, count);
MeasureReadInformation(readSize);
return readSize;
}
///
/// 参照ストリームの Seek をそのまま呼び出します
///
/// origin を基準にしたシークする長さのオフセット値
/// シークする基準
/// 参照ストリームの結果をそのまま返します
public override long Seek(long offset, SeekOrigin origin)
{
// そのまま受け取ってそのまま返す
ThrowExceptionIfObjectDisposed();
return BaseStream.Seek(offset, origin);
}
///
/// 参照ストリームの SetLength をそのまま呼び出します
///
/// ストリームに設定する新しい長さ
public override void SetLength(long value)
{
// そのまま受け取る
ThrowExceptionIfObjectDisposed();
BaseStream.SetLength(value);
}
///
/// 参照ストリームの Flush を呼び出します
///
public override void Flush()
{
// そのまま呼び出す
ThrowExceptionIfObjectDisposed();
BaseStream.Flush();
}
#endregion
#region 計測関数
///
/// 参照ストリームの読み込み情報を計測します
///
/// ストリームが実際に読み込んだデータサイズ
private void MeasureReadInformation(int readSize)
{
// 初めての計測なのかを求めて合計読み込みサイズに加算
var firstMeasure = TotalReadSize == 0;
TotalReadSize += readSize;
// もし初めての計測または、計測更新時間間隔超過なら
var elapsed = readStopwatch.ElapsedMilliseconds;
if (firstMeasure || elapsed >= MeasureUpdateIntervalTime)
{
// 読み込みサイズの、差分、前回の合計値、ビットレートを計算する
DeltaReadSize = TotalReadSize - previousTotalReadSize;
previousTotalReadSize = TotalReadSize;
ReadBitRate = (long)(DeltaReadSize * (1.0 / (elapsed / 1000.0)) * 8.0);
// 読み込み計測更新用ストップウォッチを再起動
readStopwatch.Restart();
}
}
///
/// 参照ストリームの書き込み情報を計測します
///
/// ストリームが実際に書き込んだデータサイズ
private void MeasureWriteInformation(int writeSize)
{
// 初めての計測なのかを求めて合計書き込みサイズに加算
var firstMeasure = TotalWriteSize == 0;
TotalWriteSize += writeSize;
// もし初めての計測または、計測更新時間間隔超過なら
var elapsed = writeStopwatch.ElapsedMilliseconds;
if (firstMeasure || elapsed >= MeasureUpdateIntervalTime)
{
// 書き込みサイズの、差分、前回の合計値、ビットレートを計算する
DeltaWriteSize = TotalWriteSize - previousTotalWriteSize;
previousTotalWriteSize = TotalWriteSize;
WriteBitRate = (long)(DeltaWriteSize * (1.0 / (elapsed / 1000.0)) * 8.0);
// 書き込み計測更新用ストップウォッチを再起動
writeStopwatch.Restart();
}
}
#endregion
#region 例外スロー関数
///
/// もし、このクラスのインスタンスが破棄済みなら例外をスローします
///
/// インスタンスが破棄済みです
private void ThrowExceptionIfObjectDisposed()
{
// 既に破棄済みなら
if (disposed)
{
// 破棄済み例外を吐く
throw new ObjectDisposedException(null);
}
}
#endregion
}
#endregion
}