1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/profile_resetter/jtl_interpreter.h"
9 #include "base/memory/scoped_vector.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "chrome/browser/profile_resetter/jtl_foundation.h"
13 #include "crypto/hmac.h"
14 #include "crypto/sha2.h"
15 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
20 class ExecutionContext
;
22 // An operation in an interpreted program.
25 virtual ~Operation() {}
26 // Executes the operation on the specified context and instructs the context
27 // to continue execution with the next instruction if appropriate.
28 // Returns true if we should continue with any potential backtracking that
30 virtual bool Execute(ExecutionContext
* context
) = 0;
33 // An execution context of operations.
34 class ExecutionContext
{
36 // |input| is the root of a dictionary that stores the information the
37 // sentence is evaluated on.
38 ExecutionContext(const jtl_foundation::Hasher
* hasher
,
39 const std::vector
<Operation
*>& sentence
,
40 const base::DictionaryValue
* input
,
41 base::DictionaryValue
* working_memory
)
44 next_instruction_index_(0u),
45 working_memory_(working_memory
),
47 stack_
.push_back(input
);
49 ~ExecutionContext() {}
51 // Returns true in case of success.
52 bool ContinueExecution() {
53 if (error_
|| stack_
.empty()) {
57 if (next_instruction_index_
>= sentence_
.size())
60 Operation
* op
= sentence_
[next_instruction_index_
];
61 next_instruction_index_
++;
62 bool continue_traversal
= op
->Execute(this);
63 next_instruction_index_
--;
64 return continue_traversal
;
67 std::string
GetHash(const std::string
& input
) {
68 return hasher_
->GetHash(input
);
71 // Calculates the |hash| of a string, integer or double |value|, and returns
72 // true. Returns false otherwise.
73 bool GetValueHash(const base::Value
& value
, std::string
* hash
) {
75 std::string value_as_string
;
77 double tmp_double
= 0.0;
78 if (value
.GetAsInteger(&tmp_int
))
79 value_as_string
= base::IntToString(tmp_int
);
80 else if (value
.GetAsDouble(&tmp_double
))
81 value_as_string
= base::DoubleToString(tmp_double
);
82 else if (!value
.GetAsString(&value_as_string
))
84 *hash
= GetHash(value_as_string
);
88 const base::Value
* current_node() const { return stack_
.back(); }
89 std::vector
<const base::Value
*>* stack() { return &stack_
; }
90 base::DictionaryValue
* working_memory() { return working_memory_
; }
91 bool error() const { return error_
; }
94 // A hasher used to hash node names in a dictionary.
95 const jtl_foundation::Hasher
* hasher_
;
96 // The sentence to be executed.
97 const std::vector
<Operation
*> sentence_
;
98 // Position in |sentence_|.
99 size_t next_instruction_index_
;
100 // A stack of Values, indicating a navigation path from the root node of
101 // |input| (see constructor) to the current node on which the
102 // sentence_[next_instruction_index_] is evaluated.
103 std::vector
<const base::Value
*> stack_
;
104 // Memory into which values can be stored by the program.
105 base::DictionaryValue
* working_memory_
;
106 // Whether a runtime error occurred.
108 DISALLOW_COPY_AND_ASSIGN(ExecutionContext
);
111 class NavigateOperation
: public Operation
{
113 explicit NavigateOperation(const std::string
& hashed_key
)
114 : hashed_key_(hashed_key
) {}
115 virtual ~NavigateOperation() {}
116 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
117 const base::DictionaryValue
* dict
= NULL
;
118 if (!context
->current_node()->GetAsDictionary(&dict
)) {
119 // Just ignore this node gracefully as this navigation is a dead end.
120 // If this NavigateOperation occurred after a NavigateAny operation, those
121 // may still be fulfillable, so we allow continuing the execution of the
122 // sentence on other nodes.
125 for (base::DictionaryValue::Iterator
i(*dict
); !i
.IsAtEnd(); i
.Advance()) {
126 if (context
->GetHash(i
.key()) != hashed_key_
)
128 context
->stack()->push_back(&i
.value());
129 bool continue_traversal
= context
->ContinueExecution();
130 context
->stack()->pop_back();
131 if (!continue_traversal
)
138 std::string hashed_key_
;
139 DISALLOW_COPY_AND_ASSIGN(NavigateOperation
);
142 class NavigateAnyOperation
: public Operation
{
144 NavigateAnyOperation() {}
145 virtual ~NavigateAnyOperation() {}
146 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
147 const base::DictionaryValue
* dict
= NULL
;
148 const base::ListValue
* list
= NULL
;
149 if (context
->current_node()->GetAsDictionary(&dict
)) {
150 for (base::DictionaryValue::Iterator
i(*dict
);
151 !i
.IsAtEnd(); i
.Advance()) {
152 context
->stack()->push_back(&i
.value());
153 bool continue_traversal
= context
->ContinueExecution();
154 context
->stack()->pop_back();
155 if (!continue_traversal
)
158 } else if (context
->current_node()->GetAsList(&list
)) {
159 for (base::ListValue::const_iterator i
= list
->begin();
160 i
!= list
->end(); ++i
) {
161 context
->stack()->push_back(*i
);
162 bool continue_traversal
= context
->ContinueExecution();
163 context
->stack()->pop_back();
164 if (!continue_traversal
)
168 // Do nothing, just ignore this node.
174 DISALLOW_COPY_AND_ASSIGN(NavigateAnyOperation
);
177 class NavigateBackOperation
: public Operation
{
179 NavigateBackOperation() {}
180 virtual ~NavigateBackOperation() {}
181 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
182 const base::Value
* current_node
= context
->current_node();
183 context
->stack()->pop_back();
184 bool continue_traversal
= context
->ContinueExecution();
185 context
->stack()->push_back(current_node
);
186 return continue_traversal
;
190 DISALLOW_COPY_AND_ASSIGN(NavigateBackOperation
);
193 class StoreValue
: public Operation
{
195 StoreValue(const std::string
& hashed_name
, scoped_ptr
<base::Value
> value
)
196 : hashed_name_(hashed_name
),
197 value_(value
.Pass()) {
198 DCHECK(IsStringUTF8(hashed_name
));
201 virtual ~StoreValue() {}
202 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
203 context
->working_memory()->Set(hashed_name_
, value_
->DeepCopy());
204 return context
->ContinueExecution();
208 std::string hashed_name_
;
209 scoped_ptr
<base::Value
> value_
;
210 DISALLOW_COPY_AND_ASSIGN(StoreValue
);
213 class CompareStoredValue
: public Operation
{
215 CompareStoredValue(const std::string
& hashed_name
,
216 scoped_ptr
<base::Value
> value
,
217 scoped_ptr
<base::Value
> default_value
)
218 : hashed_name_(hashed_name
),
219 value_(value
.Pass()),
220 default_value_(default_value
.Pass()) {
221 DCHECK(IsStringUTF8(hashed_name
));
223 DCHECK(default_value_
);
225 virtual ~CompareStoredValue() {}
226 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
227 const base::Value
* actual_value
= NULL
;
228 if (!context
->working_memory()->Get(hashed_name_
, &actual_value
))
229 actual_value
= default_value_
.get();
230 if (!value_
->Equals(actual_value
))
232 return context
->ContinueExecution();
236 std::string hashed_name_
;
237 scoped_ptr
<base::Value
> value_
;
238 scoped_ptr
<base::Value
> default_value_
;
239 DISALLOW_COPY_AND_ASSIGN(CompareStoredValue
);
242 template<bool ExpectedTypeIsBooleanNotHashable
>
243 class StoreNodeValue
: public Operation
{
245 explicit StoreNodeValue(const std::string
& hashed_name
)
246 : hashed_name_(hashed_name
) {
247 DCHECK(IsStringUTF8(hashed_name
));
249 virtual ~StoreNodeValue() {}
250 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
251 scoped_ptr
<base::Value
> value
;
252 if (ExpectedTypeIsBooleanNotHashable
) {
253 if (!context
->current_node()->IsType(base::Value::TYPE_BOOLEAN
))
255 value
.reset(context
->current_node()->DeepCopy());
258 if (!context
->GetValueHash(*context
->current_node(), &hash
))
260 value
.reset(new base::StringValue(hash
));
262 context
->working_memory()->Set(hashed_name_
, value
.release());
263 return context
->ContinueExecution();
267 std::string hashed_name_
;
268 DISALLOW_COPY_AND_ASSIGN(StoreNodeValue
);
271 // Stores the hash of the registerable domain name -- as in, the portion of the
272 // domain that is registerable, as opposed to controlled by a registrar; without
273 // subdomains -- of the URL represented by the current node into working memory.
274 class StoreNodeRegisterableDomain
: public Operation
{
276 explicit StoreNodeRegisterableDomain(const std::string
& hashed_name
)
277 : hashed_name_(hashed_name
) {
278 DCHECK(IsStringUTF8(hashed_name
));
280 virtual ~StoreNodeRegisterableDomain() {}
281 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
282 std::string possibly_invalid_url
;
284 if (!context
->current_node()->GetAsString(&possibly_invalid_url
) ||
285 !GetRegisterableDomain(possibly_invalid_url
, &domain
))
287 context
->working_memory()->Set(
288 hashed_name_
, new base::StringValue(context
->GetHash(domain
)));
289 return context
->ContinueExecution();
293 // If |possibly_invalid_url| is a valid URL having a registerable domain name
294 // part, outputs that in |registerable_domain| and returns true. Otherwise,
296 static bool GetRegisterableDomain(const std::string
& possibly_invalid_url
,
297 std::string
* registerable_domain
) {
298 namespace domains
= net::registry_controlled_domains
;
299 DCHECK(registerable_domain
);
300 GURL
url(possibly_invalid_url
);
303 std::string registry_plus_one
= domains::GetDomainAndRegistry(
304 url
.host(), domains::INCLUDE_PRIVATE_REGISTRIES
);
305 size_t registry_length
= domains::GetRegistryLength(
307 domains::INCLUDE_UNKNOWN_REGISTRIES
,
308 domains::INCLUDE_PRIVATE_REGISTRIES
);
309 // Fail unless (1.) the URL has a host part; and (2.) that host part is a
310 // well-formed domain name consisting of at least one subcomponent; followed
311 // by either a recognized registry identifier, or exactly one subcomponent,
312 // which is then assumed to be the unknown registry identifier.
313 if (registry_length
== std::string::npos
|| registry_length
== 0)
315 DCHECK_LT(registry_length
, registry_plus_one
.size());
316 // Subtract one to cut off the dot separating the SLD and the registry.
317 registerable_domain
->assign(
318 registry_plus_one
, 0, registry_plus_one
.size() - registry_length
- 1);
322 std::string hashed_name_
;
323 DISALLOW_COPY_AND_ASSIGN(StoreNodeRegisterableDomain
);
326 class CompareNodeBool
: public Operation
{
328 explicit CompareNodeBool(bool value
) : value_(value
) {}
329 virtual ~CompareNodeBool() {}
330 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
331 bool actual_value
= false;
332 if (!context
->current_node()->GetAsBoolean(&actual_value
))
334 if (actual_value
!= value_
)
336 return context
->ContinueExecution();
341 DISALLOW_COPY_AND_ASSIGN(CompareNodeBool
);
344 class CompareNodeHash
: public Operation
{
346 explicit CompareNodeHash(const std::string
& hashed_value
)
347 : hashed_value_(hashed_value
) {}
348 virtual ~CompareNodeHash() {}
349 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
350 std::string actual_hash
;
351 if (!context
->GetValueHash(*context
->current_node(), &actual_hash
) ||
352 actual_hash
!= hashed_value_
)
354 return context
->ContinueExecution();
358 std::string hashed_value_
;
359 DISALLOW_COPY_AND_ASSIGN(CompareNodeHash
);
362 class CompareNodeHashNot
: public Operation
{
364 explicit CompareNodeHashNot(const std::string
& hashed_value
)
365 : hashed_value_(hashed_value
) {}
366 virtual ~CompareNodeHashNot() {}
367 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
368 std::string actual_hash
;
369 if (context
->GetValueHash(*context
->current_node(), &actual_hash
) &&
370 actual_hash
== hashed_value_
)
372 return context
->ContinueExecution();
376 std::string hashed_value_
;
377 DISALLOW_COPY_AND_ASSIGN(CompareNodeHashNot
);
380 template<bool ExpectedTypeIsBooleanNotHashable
>
381 class CompareNodeToStored
: public Operation
{
383 explicit CompareNodeToStored(const std::string
& hashed_name
)
384 : hashed_name_(hashed_name
) {}
385 virtual ~CompareNodeToStored() {}
386 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
387 const base::Value
* stored_value
= NULL
;
388 if (!context
->working_memory()->Get(hashed_name_
, &stored_value
))
390 if (ExpectedTypeIsBooleanNotHashable
) {
391 if (!context
->current_node()->IsType(base::Value::TYPE_BOOLEAN
) ||
392 !context
->current_node()->Equals(stored_value
))
395 std::string actual_hash
;
396 std::string stored_hash
;
397 if (!context
->GetValueHash(*context
->current_node(), &actual_hash
) ||
398 !stored_value
->GetAsString(&stored_hash
) ||
399 actual_hash
!= stored_hash
)
402 return context
->ContinueExecution();
406 std::string hashed_name_
;
407 DISALLOW_COPY_AND_ASSIGN(CompareNodeToStored
);
410 class CompareNodeSubstring
: public Operation
{
412 explicit CompareNodeSubstring(const std::string
& hashed_pattern
,
413 size_t pattern_length
,
415 : hashed_pattern_(hashed_pattern
),
416 pattern_length_(pattern_length
),
417 pattern_sum_(pattern_sum
) {
418 DCHECK(pattern_length_
);
420 virtual ~CompareNodeSubstring() {}
421 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
422 std::string value_as_string
;
423 if (!context
->current_node()->GetAsString(&value_as_string
) ||
424 !pattern_length_
|| value_as_string
.size() < pattern_length_
)
426 // Go over the string with a sliding window. Meanwhile, maintain the sum in
427 // an incremental fashion, and only calculate the SHA-256 hash when the sum
428 // checks out so as to improve performance.
429 std::string::const_iterator window_begin
= value_as_string
.begin();
430 std::string::const_iterator window_end
= window_begin
+ pattern_length_
- 1;
432 std::accumulate(window_begin
, window_end
, static_cast<uint32
>(0u));
433 while (window_end
!= value_as_string
.end()) {
434 window_sum
+= *window_end
++;
435 if (window_sum
== pattern_sum_
&& context
->GetHash(std::string(
436 window_begin
, window_end
)) == hashed_pattern_
)
437 return context
->ContinueExecution();
438 window_sum
-= *window_begin
++;
444 std::string hashed_pattern_
;
445 size_t pattern_length_
;
447 DISALLOW_COPY_AND_ASSIGN(CompareNodeSubstring
);
450 class StopExecutingSentenceOperation
: public Operation
{
452 StopExecutingSentenceOperation() {}
453 virtual ~StopExecutingSentenceOperation() {}
454 virtual bool Execute(ExecutionContext
* context
) OVERRIDE
{
459 DISALLOW_COPY_AND_ASSIGN(StopExecutingSentenceOperation
);
464 explicit Parser(const std::string
& program
)
466 next_instruction_index_(0u) {}
468 bool ParseNextSentence(ScopedVector
<Operation
>* output
) {
469 ScopedVector
<Operation
> operators
;
470 bool sentence_ended
= false;
471 while (next_instruction_index_
< program_
.size() && !sentence_ended
) {
473 if (!ReadOpCode(&op_code
))
475 switch (static_cast<jtl_foundation::OpCodes
>(op_code
)) {
476 case jtl_foundation::NAVIGATE
: {
477 std::string hashed_key
;
478 if (!ReadHash(&hashed_key
))
480 operators
.push_back(new NavigateOperation(hashed_key
));
483 case jtl_foundation::NAVIGATE_ANY
:
484 operators
.push_back(new NavigateAnyOperation
);
486 case jtl_foundation::NAVIGATE_BACK
:
487 operators
.push_back(new NavigateBackOperation
);
489 case jtl_foundation::STORE_BOOL
: {
490 std::string hashed_name
;
491 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
494 if (!ReadBool(&value
))
496 operators
.push_back(new StoreValue(
498 scoped_ptr
<base::Value
>(new base::FundamentalValue(value
))));
501 case jtl_foundation::COMPARE_STORED_BOOL
: {
502 std::string hashed_name
;
503 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
506 if (!ReadBool(&value
))
508 bool default_value
= false;
509 if (!ReadBool(&default_value
))
511 operators
.push_back(new CompareStoredValue(
513 scoped_ptr
<base::Value
>(new base::FundamentalValue(value
)),
514 scoped_ptr
<base::Value
>(
515 new base::FundamentalValue(default_value
))));
518 case jtl_foundation::STORE_HASH
: {
519 std::string hashed_name
;
520 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
522 std::string hashed_value
;
523 if (!ReadHash(&hashed_value
))
525 operators
.push_back(new StoreValue(
527 scoped_ptr
<base::Value
>(new base::StringValue(hashed_value
))));
530 case jtl_foundation::COMPARE_STORED_HASH
: {
531 std::string hashed_name
;
532 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
534 std::string hashed_value
;
535 if (!ReadHash(&hashed_value
))
537 std::string hashed_default_value
;
538 if (!ReadHash(&hashed_default_value
))
540 operators
.push_back(new CompareStoredValue(
542 scoped_ptr
<base::Value
>(new base::StringValue(hashed_value
)),
543 scoped_ptr
<base::Value
>(
544 new base::StringValue(hashed_default_value
))));
547 case jtl_foundation::STORE_NODE_BOOL
: {
548 std::string hashed_name
;
549 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
551 operators
.push_back(new StoreNodeValue
<true>(hashed_name
));
554 case jtl_foundation::STORE_NODE_HASH
: {
555 std::string hashed_name
;
556 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
558 operators
.push_back(new StoreNodeValue
<false>(hashed_name
));
561 case jtl_foundation::STORE_NODE_REGISTERABLE_DOMAIN_HASH
: {
562 std::string hashed_name
;
563 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
565 operators
.push_back(new StoreNodeRegisterableDomain(hashed_name
));
568 case jtl_foundation::COMPARE_NODE_BOOL
: {
570 if (!ReadBool(&value
))
572 operators
.push_back(new CompareNodeBool(value
));
575 case jtl_foundation::COMPARE_NODE_HASH
: {
576 std::string hashed_value
;
577 if (!ReadHash(&hashed_value
))
579 operators
.push_back(new CompareNodeHash(hashed_value
));
582 case jtl_foundation::COMPARE_NODE_HASH_NOT
: {
583 std::string hashed_value
;
584 if (!ReadHash(&hashed_value
))
586 operators
.push_back(new CompareNodeHashNot(hashed_value
));
589 case jtl_foundation::COMPARE_NODE_TO_STORED_BOOL
: {
590 std::string hashed_name
;
591 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
593 operators
.push_back(new CompareNodeToStored
<true>(hashed_name
));
596 case jtl_foundation::COMPARE_NODE_TO_STORED_HASH
: {
597 std::string hashed_name
;
598 if (!ReadHash(&hashed_name
) || !IsStringUTF8(hashed_name
))
600 operators
.push_back(new CompareNodeToStored
<false>(hashed_name
));
603 case jtl_foundation::COMPARE_NODE_SUBSTRING
: {
604 std::string hashed_pattern
;
605 uint32 pattern_length
= 0, pattern_sum
= 0;
606 if (!ReadHash(&hashed_pattern
))
608 if (!ReadUint32(&pattern_length
) || pattern_length
== 0)
610 if (!ReadUint32(&pattern_sum
))
612 operators
.push_back(new CompareNodeSubstring(
613 hashed_pattern
, pattern_length
, pattern_sum
));
616 case jtl_foundation::STOP_EXECUTING_SENTENCE
:
617 operators
.push_back(new StopExecutingSentenceOperation
);
619 case jtl_foundation::END_OF_SENTENCE
:
620 sentence_ended
= true;
626 output
->swap(operators
);
630 bool HasNextSentence() const {
631 return next_instruction_index_
< program_
.size();
635 // Reads an uint8 and returns whether this operation was successful.
636 bool ReadUint8(uint8
* out
) {
638 if (next_instruction_index_
+ 1u > program_
.size())
640 *out
= static_cast<uint8
>(program_
[next_instruction_index_
]);
641 ++next_instruction_index_
;
645 // Reads an uint32 and returns whether this operation was successful.
646 bool ReadUint32(uint32
* out
) {
648 if (next_instruction_index_
+ 4u > program_
.size())
651 for (int i
= 0; i
< 4; ++i
) {
653 *out
|= static_cast<uint8
>(program_
[next_instruction_index_
]) << 24;
654 ++next_instruction_index_
;
659 // Reads an operator code and returns whether this operation was successful.
660 bool ReadOpCode(uint8
* out
) { return ReadUint8(out
); }
662 bool ReadHash(std::string
* out
) {
664 if (next_instruction_index_
+ jtl_foundation::kHashSizeInBytes
>
667 *out
= program_
.substr(next_instruction_index_
,
668 jtl_foundation::kHashSizeInBytes
);
669 next_instruction_index_
+= jtl_foundation::kHashSizeInBytes
;
670 DCHECK(jtl_foundation::Hasher::IsHash(*out
));
674 bool ReadBool(bool* out
) {
677 if (!ReadUint8(&value
))
688 std::string program_
;
689 size_t next_instruction_index_
;
690 DISALLOW_COPY_AND_ASSIGN(Parser
);
695 JtlInterpreter::JtlInterpreter(
696 const std::string
& hasher_seed
,
697 const std::string
& program
,
698 const base::DictionaryValue
* input
)
699 : hasher_seed_(hasher_seed
),
702 working_memory_(new base::DictionaryValue
),
704 DCHECK(input
->IsType(base::Value::TYPE_DICTIONARY
));
707 JtlInterpreter::~JtlInterpreter() {}
709 void JtlInterpreter::Execute() {
710 jtl_foundation::Hasher
hasher(hasher_seed_
);
711 Parser
parser(program_
);
712 while (parser
.HasNextSentence()) {
713 ScopedVector
<Operation
> sentence
;
714 if (!parser
.ParseNextSentence(&sentence
)) {
715 result_
= PARSE_ERROR
;
718 ExecutionContext
context(
719 &hasher
, sentence
.get(), input_
, working_memory_
.get());
720 context
.ContinueExecution();
721 if (context
.error()) {
722 result_
= RUNTIME_ERROR
;
728 bool JtlInterpreter::GetOutputBoolean(const std::string
& unhashed_key
,
729 bool* output
) const {
730 std::string hashed_key
=
731 jtl_foundation::Hasher(hasher_seed_
).GetHash(unhashed_key
);
732 return working_memory_
->GetBoolean(hashed_key
, output
);
735 bool JtlInterpreter::GetOutputString(const std::string
& unhashed_key
,
736 std::string
* output
) const {
737 std::string hashed_key
=
738 jtl_foundation::Hasher(hasher_seed_
).GetHash(unhashed_key
);
739 return working_memory_
->GetString(hashed_key
, output
);
742 int JtlInterpreter::CalculateProgramChecksum() const {
743 uint8 digest
[3] = {};
744 crypto::SHA256HashString(program_
, digest
, arraysize(digest
));
745 return static_cast<uint32
>(digest
[0]) << 16 |
746 static_cast<uint32
>(digest
[1]) << 8 |
747 static_cast<uint32
>(digest
[2]);