自作オブジェクトストレージ日記.part3

## part3
前回は1MBごとにファイルを分割し、マージしました。今回はデータ部分のハッシュ値を計算し、ファイルの最後に書き込みます。

まず、必要なパッケージをインストールします。
```sh
go get -u github.com/minio/highwayhash
```


### フッター構造体の追加
第２回ではフッターがありませんでしたが、第３回ではハッシュ値を格納するためのフッターを追加しました。
```go
type Footer struct {
    HashType uint8    //ハッシュの種類, 0:HighwayHash256
    Hash     [32]byte //ブロックのハッシュ値
}
```
ハッシュの方法を今後変更する可能性を考えてハッシュタイプも追加します。今のところ唯一登録されている、ハッシュタイプ0はhighway hashの256bitです。

### ハッシュ値の計算とフッターの書き込み
データを分割して書き込む際に、ハッシュ値を計算し、フッターとして書き込む処理を追加しました。

```go
key := make([]byte, 32) // Use a proper key for HighwayHash
highwayHash, err := highwayhash.New(key)
if err != nil {
    fmt.Println(err)
    return
}

//ハッシュ値を計算
highwayHash.Write(buf)
hash := highwayHash.Sum(nil)
//64バイトであることを確認
if len(hash) != HashSize {
    fmt.Println("hash size is invalid")
    return
}

//フッターを作成
footer := Footer{
    HashType: 0,
    Hash:     *(*[32]byte)(hash),
}

//フッターを書き込む
err = binary.Write(output, binary.BigEndian, footer)
if err != nil {
    fmt.Println(err)
    return
}
```


### ハッシュ値の検証
ファイルを復元する際に、ハッシュ値を検証する処理を追加しました。
```go
//フッターを読み込む
var footer Footer
err = binary.Read(input, binary.BigEndian, &footer)
if err != nil {
    fmt.Println(err)
    return
}

//ハッシュ値を計算
key := make([]byte, 32) // Use a proper key for HighwayHash
highwayHash, err := highwayhash.New(key)
if err != nil {
    fmt.Println(err)
    return
}
highwayHash.Write(buf)
hash := highwayHash.Sum(nil)
//64バイトであることを確認
if len(hash) != HashSize {
    fmt.Println("hash size is invalid")
    return
}

//ハッシュ値が一致するか確認
if footer.HashType != 0 {
    fmt.Println("HashType is invalid")
    return
}
if footer.Hash != *(*[32]byte)(hash) {
    fmt.Println("Hash is invalid, counter:", iCounter)
    return
}
```



### 全体のコード
最後に全体のコードと実行結果を乗せて今回は終わりです。

```go
// ハッシュ値を計算してファイル後方にくっつけます。

package main

import (
	"encoding/binary"
	"fmt"
	"os"

	"github.com/cheggaaa/pb/v3"
	"github.com/minio/highwayhash"
)

const (
	HashSize = 32 //ハッシュサイズ 256bit
)

type Header struct {
	FileSize  uint32 //ブロックのファイルサイズ
	I_Counter uint32 //Iのカウンタ. 0から始まる
}

type Footer struct {
	HashType uint8    //ハッシュの種類, 0:HighwayHash256
	Hash     [32]byte //ブロックのハッシュ値
}

func main() {
	fmt.Println("Hello World!")
	// binary/inputをバイナリファイルとして読み込み、1MBごとに分割してbinary/fragments/%dに書き込むコード
	//バイナリファイルの先頭には、ファイルサイズをint32で格納しておく(4GBまで対応可能なので1MBだと余裕なはず)
	inputfile := "binary/input.mp4"
	outputdir := "binary/fragments/"

	// inputfileを読み込む
	input, err := os.Open(inputfile)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 関数が終了する際にinputを閉じる
	defer input.Close()

	// inputfileのサイズを取得
	fileInfo, err := input.Stat()
	if err != nil {
		fmt.Println(err)
		return
	}

	fileSize := fileInfo.Size()
	fmt.Println("fileSize: ", fileSize)

	bar := pb.Simple.Start(0)
	bar.SetTotal(fileSize)
	for i := int64(0); i < fileSize; i += 1024 * 1024 {
		// outputfileを作成
		outputfile := fmt.Sprintf("%s%d", outputdir, i/(1024*1024))
		output, err := os.Create(outputfile)
		if err != nil {
			fmt.Println(err)
			return
		}
		// 関数が終了する際にoutputを閉じる
		defer output.Close()

		//残りのサイズを計算(1MB未満の場合は残りのサイズを読み込む)
		restSize := fileSize - i
		if restSize > 1024*1024 {
			restSize = 1024 * 1024
		}

		// 1MB読み込んで変数に保持
		buf := make([]byte, restSize)
		_, err = input.Read(buf)
		if err != nil {
			fmt.Println(err)
			return
		}

		//ヘッダーを作成
		header := Header{
			FileSize:  uint32(restSize),
			I_Counter: uint32(i / (1024 * 1024)),
		}

		//ヘッダーを書き込む
		err = binary.Write(output, binary.BigEndian, header)
		if err != nil {
			fmt.Println(err)
			return
		}

		key := make([]byte, 32) // Use a proper key for HighwayHash
		highwayHash, err := highwayhash.New(key)
		if err != nil {
			fmt.Println(err)
			return
		}

		//ハッシュ値を計算
		highwayHash.Write(buf)
		hash := highwayHash.Sum(nil)
		//64バイトであることを確認
		if len(hash) != HashSize {
			fmt.Println("hash size is invalid")
			return
		}

		//バイナリデータを書き込む
		_, err = output.Write(buf)
		if err != nil {
			fmt.Println(err)
			return
		}

		//フッターを作成
		footer := Footer{
			HashType: 0,
			Hash:     *(*[32]byte)(hash),
		}

		//フッターを書き込む
		err = binary.Write(output, binary.BigEndian, footer)
		if err != nil {
			fmt.Println(err)
			return
		}
		bar.Add64(restSize)

	}
	bar.Finish()
	fmt.Println("分割完了")
	fmt.Println("エンターキーを押して復元します")
	//キー入力待機
	fmt.Scanln()
	bar = pb.Simple.Start(0)
	bar.SetTotal(fileSize)
	//バイナリファイルを読み込んで、元のファイルを復元するコード

	//0から始まるIのカウンタを保持する変数
	iCounter := 0

	//復元したファイルを書き込むためのファイルを作成
	outputfile := "binary/output.mp4"
	output, err := os.Create(outputfile)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 関数が終了する際にoutputを閉じる
	defer output.Close()

	//binary/fragments/以下のファイルを読み込む
	for {
		inputfile := fmt.Sprintf("binary/fragments/%d", iCounter)
		input, err := os.Open(inputfile)
		if err != nil {
			break
		}
		// 関数が終了する際にinputを閉じる
		defer input.Close()

		//ヘッダーを読み込む
		var header Header
		err = binary.Read(input, binary.BigEndian, &header)
		if err != nil {
			fmt.Println(err)
			return
		}
		if header.I_Counter != uint32(iCounter) {
			fmt.Println("I_Counter is invalid")
			return
		}

		//バイナリデータを読み込む
		buf := make([]byte, header.FileSize)
		_, err = input.Read(buf)
		if err != nil {
			fmt.Println(err)
			return
		}

		//フッターを読み込む
		var footer Footer
		err = binary.Read(input, binary.BigEndian, &footer)
		if err != nil {
			fmt.Println(err)
			return
		}

		//ハッシュ値を計算
		key := make([]byte, 32) // Use a proper key for HighwayHash
		highwayHash, err := highwayhash.New(key)
		if err != nil {
			fmt.Println(err)
			return
		}
		highwayHash.Write(buf)
		hash := highwayHash.Sum(nil)
		//64バイトであることを確認
		if len(hash) != HashSize {
			fmt.Println("hash size is invalid")
			return
		}

		//ハッシュ値が一致するか確認
		if footer.HashType != 0 {
			fmt.Println("HashType is invalid")
			return
		}

		if footer.Hash != *(*[32]byte)(hash) {
			fmt.Println("Hash is invalid, counter:", iCounter)
			return
		}

		//バイナリデータをoutputに書き込む
		_, err = output.Write(buf)
		if err != nil {
			fmt.Println(err)
			return
		}

		iCounter++
		bar.Add64(int64(header.FileSize))
	}
	bar.Finish()
	fmt.Println("復元正常完了")
}

```




### 実行結果

```
Hello World!
fileSize:  86260725
86260725 / 86260725 [------------------------------------------------------] 100.00%
分割完了
エンターキーを押して復元します

Hash is invalid, counter: 79
```
分割が済んだら適当に80というファイルの中身を変更してみます。１文字変更したらEnterキーを押して復元処理を開始します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308674/6a74000d-08db-c03f-0afe-e7971ecde28c.png)

無事変更が検出されていますね。
[次回はイレイジャーコーディングです](https://qiita.com/itoho/items/416290868ef889a8b333)