TreeHash.php 6.0 KB

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