Skip to content

Commit

Permalink
Hash masking (#677)
Browse files Browse the repository at this point in the history
  • Loading branch information
therealryan authored Jan 4, 2024
1 parent 65b9af8 commit f0efdb8
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 15 deletions.
3 changes: 3 additions & 0 deletions doc/src/main/markdown/further.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ For an example of this usage, see the [`ExampleSystemTest.hashes()`][ExampleSyst

This test will fail if the expected hash values are not updated as the system model is changed, so the change reviewer can look at the diff on this test to see at a glance which parts of the system have been impacted by a change.

Note that [field-masking operations can be supplied][MessageHash.hashing(Actor,Include,Consumer)] when you specify your message hashes - this allows you remove or overwrite dynamic fields in your model to achieve consistent hashing results.

### Report diff tool

The flow framework uses an inheritance mechanism (deriving a new flow from an existing one) to effectively compress the set of test data that we use to exercise a tested system. This compression makes it easier to make sweeping changes to test data with small edits to the flow construction code, but by the same token it can be difficult to understand the impact of those small changes.
Expand All @@ -338,6 +340,7 @@ Full instructions for using the diff tool are provided [here](../../../../report

[MessageHash]: ../../../../validation/validation-core/src/main/java/com/mastercard/test/flow/validation/MessageHash.java
[ExampleSystemTest]: ../../../../example/app-model/src/test/java/com/mastercard/test/flow/example/app/model/ExampleSystemTest.java
[MessageHash.hashing(Actor,Include,Consumer)]: ../../../../validation/validation-core/src/main/java/com/mastercard/test/flow/validation/MessageHash.java#L140-L154,140-154

<!-- code_link_end -->

Expand Down
8 changes: 4 additions & 4 deletions message/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ Implementations of the Message interface

* [../flow](https://github.com/Mastercard/flow) Testing framework
* [message-core](message-core) Message implementation utilities
* [message-http](message-http) HypterText Transfer Protocol messages
* [message-json](message-json) JavaScript Object Notation Messages
* [message-http](message-http) HyperText Transfer Protocol messages
* [message-json](message-json) JavaScript Object Notation messages
* [message-sql](message-sql) Structured Query Language messages
* [message-text](message-text) Freeform text Message
* [message-text](message-text) Freeform text messages
* [message-web](message-web) Browser interaction messages
* [message-xml](message-xml) Extensible Markup Language Messages
* [message-xml](message-xml) Extensible Markup Language messages

<!-- title end -->

Expand Down
2 changes: 1 addition & 1 deletion message/message-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# message-http

HypterText Transfer Protocol messages
HyperText Transfer Protocol messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-http/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-http)

Expand Down
2 changes: 1 addition & 1 deletion message/message-http/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>message-http</artifactId>
<packaging>jar</packaging>
<description>HypterText Transfer Protocol messages</description>
<description>HyperText Transfer Protocol messages</description>

<dependencies>
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion message/message-json/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# message-json

JavaScript Object Notation Messages
JavaScript Object Notation messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-json/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-json)

Expand Down
2 changes: 1 addition & 1 deletion message/message-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>message-json</artifactId>
<packaging>jar</packaging>
<description>JavaScript Object Notation Messages</description>
<description>JavaScript Object Notation messages</description>

<dependencies>
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion message/message-text/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# message-text

Freeform text Message
Freeform text messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-text/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-text)

Expand Down
2 changes: 1 addition & 1 deletion message/message-text/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>message-text</artifactId>
<packaging>jar</packaging>
<description>Freeform text Message</description>
<description>Freeform text messages</description>

<dependencies>
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion message/message-xml/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# message-xml

Extensible Markup Language Messages
Extensible Markup Language messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-xml/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-xml)

Expand Down
2 changes: 1 addition & 1 deletion message/message-xml/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>message-xml</artifactId>
<packaging>jar</packaging>
<description>Extensible Markup Language Messages</description>
<description>Extensible Markup Language messages</description>

<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public MessageHash hashing( String name,
Predicate<Interaction> interaction,
Function<Interaction, Stream<Message>> messages,
Function<Message, byte[]> content ) {
hashes.add( new Hash( name, flows, interaction, messages ) );
hashes.add( new Hash( name, flows, interaction, messages, content ) );
return this;
}

Expand Down Expand Up @@ -181,6 +181,29 @@ public MessageHash hashing( Actor responder, Include messages ) {
Message::content );
}

/**
* Adds a comparison to hash requests to and/or responses from a particular
* {@link Actor}, while masking out dynamic fields that should not be included
* in the hash.
*
* @param responder The actor
* @param messages The message type of interest
* @param mask How to mask out fields that should not be included in the
* hash
* @return <code>this</code>
*/
public MessageHash hashing( Actor responder, Include messages, Consumer<Message> mask ) {
return hashing( messages.name() + " " + messages.actorInfix + " " + responder.name(),
f -> true,
i -> i.responder() == responder,
messages,
m -> {
Message child = m.child();
mask.accept( child );
return child.content();
} );
}

/**
* Computes the actual hashes and compares them against the supplied
* expectations
Expand Down Expand Up @@ -226,13 +249,15 @@ private static class Hash {
public final Predicate<Flow> flows;
public final Predicate<Interaction> interaction;
public final Function<Interaction, Stream<Message>> messages;
public final Function<Message, byte[]> content;

public Hash( String name, Predicate<Flow> flows, Predicate<Interaction> interaction,
Function<Interaction, Stream<Message>> messages ) {
Function<Interaction, Stream<Message>> messages, Function<Message, byte[]> content ) {
this.name = name;
this.flows = flows;
this.interaction = interaction;
this.messages = messages;
this.content = content;
}

public void compute( Model model,
Expand All @@ -248,7 +273,7 @@ public void compute( Model model,
.flatMap( Flows::interactions )
.filter( interaction )
.flatMap( messages )
.map( Message::content )
.map( content )
.mapToInt( b -> {
byte[] h = digest.digest( b );
for( int i = 0; i < h.length; i++ ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package com.mastercard.test.flow.validation;

import static com.mastercard.test.flow.validation.MessageHash.Include.REQUESTS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

Expand All @@ -18,6 +25,7 @@
import com.mastercard.test.flow.Interaction;
import com.mastercard.test.flow.Message;
import com.mastercard.test.flow.Model;
import com.mastercard.test.flow.util.Bytes;
import com.mastercard.test.flow.validation.MessageHash.Include;

/**
Expand Down Expand Up @@ -127,6 +135,30 @@ void responses() {
"8D777F385D3DFEC8815D20F7496026DC 0001 4 B" );
}

/**
* Showing that when masking operations are requested, they are applied to a
* child of the messages in the model
*
* @throws NoSuchAlgorithmException This would be surprising
*/
@Test
void masking() throws NoSuchAlgorithmException {

String expectedHashedContent = "child of 'hash this!' with updates {dynamic field=static value}";
int expectedLength = expectedHashedContent.length();
MessageDigest digest = MessageDigest.getInstance( "MD5" );
String expectedHash = Bytes.toHex( digest.digest( expectedHashedContent.getBytes( UTF_8 ) ) );

MessageHash masking = new MessageHash( Assertions::assertEquals )
.hashing( BEN, REQUESTS, m -> m.set( "dynamic field", "static value" ) );

masking.expect( model(
flow( ntr( AVA, "hash this!", BEN, "but not this" ) ),
flow( ntr( BEN, "or this", CHE, "and definitley not this" ) ) ),
"REQUESTS --> BEN",
expectedHash + " 0001 " + expectedLength + " B" );
}

/**
* Demonstrates what happens when you have even numbers of identical messages
* being hashed together
Expand Down Expand Up @@ -270,6 +302,27 @@ private static Interaction ntr( Actor requester, String req, Actor responder, St
private static Message msg( String content ) {
Message msg = mock( Message.class );
when( msg.content() ).thenReturn( content.getBytes( UTF_8 ) );

Map<String, String> childUpdates = new TreeMap<>();
Message child = mock( Message.class );

doAnswer( inv -> {
childUpdates.put( inv.getArgument( 0 ), inv.getArgument( 1 ) );
return null;
} )
.when( child )
.set( any(), any() );

when( child.content() )
.thenAnswer( inv -> {
String s = String.format(
"child of '%s' with updates %s",
content, childUpdates );
return s.getBytes( UTF_8 );
} );

when( msg.child() ).thenReturn( child );

return msg;
}
}

0 comments on commit f0efdb8

Please sign in to comment.