Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Add Span-based overloads to HashAlgorithm and IncrementalHash #23076

Merged
merged 2 commits into from
Aug 10, 2017

Conversation

stephentoub
Copy link
Member

  • Added IncrementalHash.AppendData/TryGetHashAndReset span-based methods
  • Added HashAlgorithm.TryComputeHash/HashCore/TryHashFinal span-based methods
  • Overrode methods on MD5, SHA1, SHA1Managed, SHA256, SHA256Managed, SHA384, SHA384Managed, SHA512, SHA512Managed, HMACMD5, HMACSHA1, HMACSHA256, HMACSHA384, HMACSHA512, MD5CryptoServiceProvider, SHA1CryptoServiceProvider, SHA256CryptoServiceProvider, SHA512CryptoServiceProvider
  • Added tests
  • Some minor formatting cleanup along the way to make things more consistent with the new code being added

Fixes https://github.com/dotnet/corefx/issues/22613
Fixes https://github.com/dotnet/corefx/issues/22614
cc: @bartonjs, @KrzysztofCwalina

{
return _hashProvider.FinalizeHashAndReset();
}
protected override void HashCore(ReadOnlySpan<byte> source) =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is someone overriding HashCore now? If not, let's be consistent and include the "sealed" keyword. (Same comment applies generally through out this PR.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type is sealed. Each method doesn't need to be. I agree we should be consistent, but we should be consistent by removing sealed from all of the methods.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be consistent by removing sealed from all of the methods.

I disagree. It's extra work for the reader to scroll up to see whether the class is sealed. Furthermore, if the class is ever unsealed, every method now becomes unsealed when it should be the opposite: methods should only be unsealed when there's a specific need.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to agree to disagree then. The rest of corefx does not do what you're proposing, and we should be consistent with that.

Copy link
Member Author

@stephentoub stephentoub Aug 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With regards to the passive-aggressive thumbs down, if you don't like my agree-to-disagree comment, then what exactly do you suggest we do? You don't like the way I've suggested, and I don't like the way you've suggested. My default is to make the entire codebase as consistent as possible, not just this small corner of it, and most of the rest of the codebase relies on a type being sealed to indicate that its methods are also sealed. If someone unseals a type, they and the code reviewers of that change need to understand all of the ramifications of that, not just but including that any unsealed members become overridable (which is of course often why someone chooses to unseal a type, to override its members).

Copy link

@ghost ghost Aug 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you don't like my agree-to-disagree comment, then what exactly do you suggest we do?

That depends on what you mean by "agree-to-disagree."

If you mean "I don't want to do that work for this PR because I like it the way it is", that's fine - I don't work much on crypto stuff anymore so I'm not interested in pushing harder on that.

If you mean "You will not use explicit 'sealed' anywhere, and and you will remove them even in sections of the codebase that have maintained this discipline for far longer than corefx has been around", then yes, expect very strong pushback. (That's also not "agree-to-disagree".)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to do that work for this PR

It has nothing to do with not wanting to do work. It has everything to do with disagreeing with the style.

You will not use explicit 'sealed' anywhere

I have no intention of removing them everywhere; that's a larger discussion that would require more than just my opinion on the matter. Due to the request for consistency, I removed them from the existing methods on the classes to which more methods were being added, so that we're not in a weird state where some methods on these types are explicitly sealed and others aren't.

Copy link
Member Author

@stephentoub stephentoub Aug 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the name of making forward progress here, I'll update this with the predominant style of the particular project (which, fwiw, was already inconsistent), and will open an issue to track removing the sealed modifiers from across corefx.

maintained this discipline for far longer than corefx has been around

That doesn't matter. Lots of projects have come together with lots of different styles into corefx, and we're trying to maintain a consistent style across the whole repo.

Debug.Assert(offset >= 0);
Debug.Assert(offset < data.Length);
Debug.Assert(count >= 0);
Debug.Assert(data.Length - offset > count);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing all of these asserts is my favorite thing about Span.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

if (destination.Length < HashSizeInBytes)
{
bytesWritten = 0;
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably do this block before if (!_running). It's not a huge difference, but this block doesn't depend on any side effects from SetKey...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

{
byte* pbData = pData + offset;
ret = Interop.AppleCrypto.DigestUpdate(_ctx, pbData, count);
ret = Interop.AppleCrypto.DigestUpdate(_ctx, pData, data.Length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is a dumb question, but: Can't the Marshaller do the DangerousGetPinnableReference for us? Like, should the signature here just be a ReadOnlySpan for the P/Invoke?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't the Marshaller do the DangerousGetPinnableReference for us?

Currently, no. It's on the list of things to do to make span more first-class.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If those plans are fairly firm, it might be worth putting a ReadOnlySpan overload in the interop file rather than in the crypto code. That way, the extra DGPR calls are in one folder and are more likely to be removed once the marshaling support is there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If those plans are fairly firm

I don't know if or when it'll actually happen. But I'm fine moving that into the interop file. I'll do so in a subsequent PR.

@@ -235,6 +249,29 @@ public override unsafe byte[] FinalizeHashAndReset()
return hash;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This is as close as I can get to the line I care about)

Instead of having this written twice, once for "return array" and once for "write to span", can you make the existing/array one call the new/span one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. We pay for the unnecessary length check, but that's fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this got fixed in the mac version, but the Windows and OpenSsl/Unix versions still have parallel constructions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I fixed the others. Maybe I neglected to save :( Will look again.

Assert.True(hash.TryComputeHash(input, actual, out bytesWritten));
Assert.Equal(expected.Length, bytesWritten);
Assert.Equal(expected, actual);
Assert.Throws<NullReferenceException>(() => hash.Hash); // byte[] not initialized
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do better than NullReferenceException. Simply making the property return null when HashValue is null seems legit to me. (MSDN says it's a CryptographicUnexpectedOperationException... though I certainly don't see code backing that up 😄 https://msdn.microsoft.com/en-us/library/system.security.cryptography.hashalgorithm.hash(v=vs.110).aspx)

We should also have a test which codifies what happens to the Hash property when you call ComputeHash(array) followed by TryComputeHash(span). I'm sort of thinking we need to null it out (or whatever) in TryComputeHash.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simply making the property return null when HashValue is null seems legit to me

Ok... I had that and then undid it, but I've put it back.

We should also have a test which codifies what happens to the Hash property when you call ComputeHash(array) followed by TryComputeHash(span). I'm sort of thinking we need to null it out (or whatever) in TryComputeHash.

Done


using (HashAlgorithm hash = Create())
{
actual = new byte[expected.Length - 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a test of array-too-big, value-preserving for the excess values, and bytesWritten being correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

while (position < s_inputBytes.Length - StepB)
{
incrementalHash.AppendData(new ReadOnlySpan<byte>(s_inputBytes, position, StepA));
position += StepA;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you copied my test as-was... but I'm thinking this while should have used StepB instead of StepA 😄.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the original and the copy :)

if (destination.Length >= HashSizeValue/8)
{
byte[] final = HashFinal();
if (final.Length != HashSizeValue/8)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Save the hashSizeInBytes value to a variable?

Also, it feels like all the other methods were "if too short return false" and this is "if big enough return true". Just seems odd that the style isn't consistent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Save the hashSizeInBytes value to a variable?

Done

Just seems odd that the style isn't consistent.

Reversed

@stephentoub stephentoub force-pushed the hash_spans branch 2 times, most recently from 4235371 to a786b2a Compare August 9, 2017 19:58
- Added IncrementalHash.AppendData/TryGetHashAndReset span-based methods
- Added HashAlgorithm.TryComputeHash/HashCore/TryHashFinal span-based methods
- Overrode methods on MD5, SHA1, SHA1Managed, SHA256, SHA256Managed, SHA384, SHA384Managed, SHA512, SHA512Managed, HMACMD5, HMACSHA1, HMACSHA256, HMACSHA384, HMACSHA512, MD5CryptoServiceProvider, SHA1CryptoServiceProvider, SHA256CryptoServiceProvider, SHA512CryptoServiceProvider
- Added tests
- Some minor formatting cleanup along the way to make things more consistent with the new code being added
- Rewrite HashProviderDispenser (OSX) FinalizeHashAndReset in terms of TryFinalizeHashAndReset
- Reorder _running/SetKey check in HashProviderDispenser
- Add bigger-than-needed-span test for HashAlgorithm
- Fix incorrect const usage in original and copied test
- Change HashAlgorithm.Hash to avoid throwing if HashValue is null
- Make TryComputeHash null out HashValue on success
- Cache HashSizeValue/8 into a local in TryHashFinal rather than recomputing
@stephentoub stephentoub merged commit 154c2a3 into dotnet:master Aug 10, 2017
@stephentoub stephentoub deleted the hash_spans branch August 10, 2017 13:05
@steji113
Copy link
Contributor

@stephentoub I am wondering if you could help clarify a few bullet points in the CoreFX contributing guide here. For example, I have noticed many PRs tend to mix code style / cleanup changes with actual functional changes, i.e. using expression bodied members, null-conditionals, etc. Particularly at issue seem to be the following bullets:

  • DO NOT send PRs for style changes. For example, do not send PRs that are focused on changing usage of Int32 to int.
  • DO NOT send PRs for upgrading code to use newer language features, though it's ok to use newer language features as part of new code that's written. For example, it's ok to use expression-bodied members as part of new code you write, but do not send a PR focused on changing existing properties or methods to use the feature.
  • DO NOT mix independent, unrelated changes in one PR. Separate real product/test code changes from larger code formatting/dead code removal changes. Separate unrelated fixes into separate PRs, especially if they are in different assemblies.

Is the contributing guide still accurate in these cases, such that contributors should be strictly following these? Also, in a related question, are there still plans to bring the CoreFX code base up to the latest style guidelines in addition to leveraging newer language features as referenced here?:

We intend to bring dotnet/corefx into full conformance with the style guidelines described in Coding Style. We plan to do that with tooling, in a holistic way.

I just want to make sure I am following the appropriate guidelines when making contributions and also am willing to help out with targeted style / language feature improvements. Thanks!

@stephentoub
Copy link
Member Author

Is the contributing guide still accurate in these cases

We've gotten much more lax in this regard since the guidance was originally written. We'd still prefer not to get a PR focused on such style changes, but we're more accepting of such changes being made in the same PR as other changes for code near or impacted by those changes.

@steji113
Copy link
Contributor

OK great thanks for the clarification!

@karelz karelz modified the milestone: 2.1.0 Aug 14, 2017
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…/corefx#23076)

* Add Span-based overloads to HashAlgorithm and IncrementalHash

- Added IncrementalHash.AppendData/TryGetHashAndReset span-based methods
- Added HashAlgorithm.TryComputeHash/HashCore/TryHashFinal span-based methods
- Overrode methods on MD5, SHA1, SHA1Managed, SHA256, SHA256Managed, SHA384, SHA384Managed, SHA512, SHA512Managed, HMACMD5, HMACSHA1, HMACSHA256, HMACSHA384, HMACSHA512, MD5CryptoServiceProvider, SHA1CryptoServiceProvider, SHA256CryptoServiceProvider, SHA512CryptoServiceProvider
- Added tests
- Some minor formatting cleanup along the way to make things more consistent with the new code being added

* Address PR feedback

- Rewrite HashProviderDispenser (OSX) FinalizeHashAndReset in terms of TryFinalizeHashAndReset
- Reorder _running/SetKey check in HashProviderDispenser
- Add bigger-than-needed-span test for HashAlgorithm
- Fix incorrect const usage in original and copied test
- Change HashAlgorithm.Hash to avoid throwing if HashValue is null
- Make TryComputeHash null out HashValue on success
- Cache HashSizeValue/8 into a local in TryHashFinal rather than recomputing


Commit migrated from dotnet/corefx@154c2a3
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants