TreeHash.php
6.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
<?php
/**
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
namespace Aws\Common\Hash;
use Aws\Common\Enum\Size;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\LogicException;
use Guzzle\Http\EntityBody;
/**
* Encapsulates the creation of a tree hash from streamed chunks of data
*/
class TreeHash implements ChunkHashInterface
{
/**
* @var string The algorithm used for hashing
*/
protected $algorithm;
/**
* @var array Set of binary checksums from which the tree hash is derived
*/
protected $checksums = array();
/**
* @var string The resulting hash in hex form
*/
protected $hash;
/**
* @var string The resulting hash in binary form
*/
protected $hashRaw;
/**
* Create a tree hash from an array of existing tree hash checksums
*
* @param array $checksums Set of checksums
* @param bool $inBinaryForm Whether or not the checksums are already in binary form
* @param string $algorithm A valid hash algorithm name as returned by `hash_algos()`
*
* @return TreeHash
*/
public static function fromChecksums(array $checksums, $inBinaryForm = false, $algorithm = self::DEFAULT_ALGORITHM)
{
$treeHash = new self($algorithm);
// Convert checksums to binary form if provided in hex form and add them to the tree hash
$treeHash->checksums = $inBinaryForm ? $checksums : array_map('Aws\Common\Hash\HashUtils::hexToBin', $checksums);
// Pre-calculate hash
$treeHash->getHash();
return $treeHash;
}
/**
* Create a tree hash from a content body
*
* @param string|resource|EntityBody $content Content to create a tree hash for
* @param string $algorithm A valid hash algorithm name as returned by `hash_algos()`
*
* @return TreeHash
*/
public static function fromContent($content, $algorithm = self::DEFAULT_ALGORITHM)
{
$treeHash = new self($algorithm);
// Read the data in 1MB chunks and add to tree hash
$content = EntityBody::factory($content);
while ($data = $content->read(Size::MB)) {
$treeHash->addData($data);
}
// Pre-calculate hash
$treeHash->getHash();
return $treeHash;
}
/**
* Validates an entity body with a tree hash checksum
*
* @param string|resource|EntityBody $content Content to create a tree hash for
* @param string $checksum The checksum to use for validation
* @param string $algorithm A valid hash algorithm name as returned by `hash_algos()`
*
* @return bool
*/
public static function validateChecksum($content, $checksum, $algorithm = self::DEFAULT_ALGORITHM)
{
$treeHash = self::fromContent($content, $algorithm);
return ($checksum === $treeHash->getHash());
}
/**
* {@inheritdoc}
*/
public function __construct($algorithm = self::DEFAULT_ALGORITHM)
{
HashUtils::validateAlgorithm($algorithm);
$this->algorithm = $algorithm;
}
/**
* {@inheritdoc}
* @throws LogicException if the root tree hash is already calculated
* @throws InvalidArgumentException if the data is larger than 1MB
*/
public function addData($data)
{
// Error if hash is already calculated
if ($this->hash) {
throw new LogicException('You may not add more data to a finalized tree hash.');
}
// Make sure that only 1MB chunks or smaller get passed in
if (strlen($data) > Size::MB) {
throw new InvalidArgumentException('The chunk of data added is too large for tree hashing.');
}
// Store the raw hash of this data segment
$this->checksums[] = hash($this->algorithm, $data, true);
return $this;
}
/**
* Add a checksum to the tree hash directly
*
* @param string $checksum The checksum to add
* @param bool $inBinaryForm Whether or not the checksum is already in binary form
*
* @return self
* @throws LogicException if the root tree hash is already calculated
*/
public function addChecksum($checksum, $inBinaryForm = false)
{
// Error if hash is already calculated
if ($this->hash) {
throw new LogicException('You may not add more checksums to a finalized tree hash.');
}
// Convert the checksum to binary form if necessary
$this->checksums[] = $inBinaryForm ? $checksum : HashUtils::hexToBin($checksum);
return $this;
}
/**
* {@inheritdoc}
*/
public function getHash($returnBinaryForm = false)
{
if (!$this->hash) {
// Perform hashes up the tree to arrive at the root checksum of the tree hash
$hashes = $this->checksums;
while (count($hashes) > 1) {
$sets = array_chunk($hashes, 2);
$hashes = array();
foreach ($sets as $set) {
$hashes[] = (count($set) === 1) ? $set[0] : hash($this->algorithm, $set[0] . $set[1], true);
}
}
$this->hashRaw = $hashes[0];
$this->hash = HashUtils::binToHex($this->hashRaw);
}
return $returnBinaryForm ? $this->hashRaw : $this->hash;
}
/**
* @return array Array of raw checksums composing the tree hash
*/
public function getChecksums()
{
return $this->checksums;
}
}