-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix 0.5.0 API breakage of decoder.decode(substrateFun=...) #39
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work! Thank you!
Could you also, please, add more comments to the code? (mostly, it'll be fine to copy your explanations from the commits, I think)
pyasn1/codec/ber/decoder.py
Outdated
substrate_bytes = substrate.read() | ||
if length == -1: | ||
length = len(substrate_bytes) | ||
value, nextSubstrate = nonStreamingSubstrateFun(asn1Object, substrate_bytes, length) | ||
nbytes = substrate.write(nextSubstrate) | ||
substrate.truncate() | ||
substrate.seek(-nbytes, os.SEEK_CUR) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm worrying a bit about the lack of error handling here. I think we need to check for different possible issues as it's done in streaming.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
substrate
will always be of type io.BytesIO
when entering substrateFunWrapper
. That's an invariant ensured by substrate = asSeekableStream(substrate)
and should be treated as that (I've added an assertion). Do you see further unhandled error cases?
@@ -138,13 +138,13 @@ def testIndefModeChunked(self): | |||
def testDefModeChunkedSubst(self): | |||
assert decoder.decode( | |||
ints2octs((35, 8, 3, 2, 0, 169, 3, 2, 1, 138)), | |||
substrateFun=lambda a, b, c, d: streaming.readFromStream(b, c) | |||
substrateFun=lambda a, b, c: (b, b[c:]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep the old tests, so we can confirm that both approaches work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not only about tests, it also means there will be API variants to maintain. But it's doable. I've added it as two optional commits on top, so you can decide if you want to pick it or not.
@tiran please, when you have time, take a look at the PR too. You have more experience with the project so you may find more issues that I possibly missed. Also, I'll try my luck and cast summon @janpipek, who has done a lot of awesome work on the streaming pyasn1 feature. |
69856d6
to
025e020
Compare
Did that mostly in the form of docstrings, but have not yet checked if the resulting Sphinx docs make sense. Need to find some more time for it. Feel free to edit commits anyways. |
025e020
to
81d6a72
Compare
81d6a72
to
bd73330
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late review; I have so many things on my plate lately...
BTW, I also run the build with your changes here:
freeipa-pr-ci2/freeipa#2968
Just to see how it behaves in a large IdM environment.
bd73330
to
1e64537
Compare
No need to explain. I'm aware maintaining such projects is demanding and have great respect for those who do it - so thank you :-)
Nice idea. Will changed functions actually be triggered there? freeipa itself doesn't seem to use pyasn1.codec.ber.decoder - only encoder. |
Thank you for the kind words! And for your contributions!
Yep, still FreeIPA includes more projects as dependencies - so it's possible that it may affect one of them at some point of execution. I also plan to run pysnmp and python-ldap with this PR (different versions, used in Fedora, at least). And after that, I think, we are good. :) P.S. I also see that |
1e64537
to
f62ab81
Compare
Where can I watch it failing? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are a couple more thoughts I got while rechecking the code before merging.
P.S. The docs issue should be resolved soon! (it's not related to your PR but I'd like to run your test with the change - just to be sure)
08fdf9c
to
e55c777
Compare
@droideck The commit series was structured for optional cherry picking. If you're going to take the whole set, please tell me, then I'd squash some of them for nicer history. |
Yes, please, squash them the way you like (I'll be okay even with one combined commit). Besides that, I think we are good! Thank you for all of the work you've done! It's very structured and nicely executed. :) |
Partial BER decoding was possible in 0.4.8 and is no longer possible in 0.5.0. It's currently prevented by the strict bytesRead check in state stDecodeValue. We have to relax length check a bit. As a custom substrateFuns can be used for partial decoding, reading less than the definite length is fine and expected. Reading more is fishy. Treat the latter as error. If the length check breaks your existing code, please file a bug for pyasn1 and explain the use case for reading too much.
0.5.0 introduced streaming decoders with new API. decoder.decode() was meant as a compatibility layer, but API broke anyways if users passed a custom substrateFun to decoder.decode(). This happened because substrateFun API and semantics also changed with 0.5.0. To establish full backwards compatibility, we now let decode.decode() accept both v0.4 and v0.5 substrateFuns. v0.4 functions are detected and automatically wrapped to behave streaming-like. Detection of v0.4 style works by checking for TypeError stemming from a call with wrong number of arguments. The try-except approach was chosen over 'import inspect' for performance reasons. We avoid to interfere with user code TypeErrors by checking the traceback depth. Fixes pyasn1#28.
Use streaming semantics to avoid needless auto-conversion: - don't return value as 1st tuple item, but yield as scalar - don't return next substrate as 2nd tuple item; it's now what's left in the stream
e55c777
to
f91d5d6
Compare
Let's see... Will keep watching to help in case of regressions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The results are good (there are some failures, but they seem to be not related to this change - freeipa-pr-ci2/freeipa#3139)
And I ran pysnmp, python-ldap and 389-ds-base tests with the new scratch-build.
All good!
Let's go ahead and merge it then if you are okay with that. :)
Thank you for all of the great work! This contribution is VERY appreciated!
Tried to look into runner.log and report.html and couldn't find immediate hints about root causes. But it's a large test system. I can't really reason about it. This is up to you...
If you're confident then I'm too - let's go.
Thank you :) |
This fixes the API breakage regarding
decoder.decode(substrateFun=...)
in 0.5.0. See commit messages for details.A
substrateFun
passed todecoder.decode()
can now be either v0.4 Non-Streaming or v0.5 Streaming. pyasn1 will detect and handle both cases transparently.A
substrateFun
passed to one of the new streaming decoders is still expected to be v0.5 Streaming only.Non-Streaming means
Streaming means
I've considered pysnmp 4.4.12 verdec.py and @ralphje s example as real world cases, both should work.
Fixes #28