Skip to content
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

Add support for sending bytearrays. #622

Closed
wants to merge 3 commits into from
Closed

Conversation

Lukasa
Copy link
Member

@Lukasa Lukasa commented Apr 25, 2017

Resolves #621.

This patch adds support for sending bytearrays by using ffi.from_buffer for both bytearrays and byte strings.

In general this patch is a good idea, however, it has some caveats. Most notable is that bytearray and byte string support for from_buffer only landed in cffi 1.8, but our lowest supported cryptography version only requires cffi 1.4. That may make this patch unacceptable. The minimum we could get away with would be cffi 1.7, and only then if we add separate code paths for byte strings and byte arrays.

On the other hand, if we could go up to requiring cffi 1.10 then we could remove the conditionals for handling memoryview and buffer objects, as ffi.from_buffer also supports them directly.

Anyway, here's a PoC, we can discuss whether it's acceptable or not.

@codecov
Copy link

codecov bot commented Apr 25, 2017

Codecov Report

Merging #622 into master will increase coverage by 0.02%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master    #622      +/-   ##
=========================================
+ Coverage   96.78%   96.8%   +0.02%     
=========================================
  Files          16      16              
  Lines        5628    5667      +39     
  Branches      392     391       -1     
=========================================
+ Hits         5447    5486      +39     
  Misses        121     121              
  Partials       60      60
Impacted Files Coverage Δ
src/OpenSSL/SSL.py 94.82% <100%> (+0.05%) ⬆️
tests/test_ssl.py 99.1% <100%> (+0.01%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7706e14...3c4357d. Read the comment docs.

@hynek
Copy link
Contributor

hynek commented Apr 25, 2017

Hm. I would assume most people run the latest cffi anyways. Since we already have a conditional, how about we externalize it into a function _is_acceptable_for_from_buffer() whose implementation depends on the cffi version?

N.B. I’m not telling, I’m asking. :)

@Lukasa
Copy link
Member Author

Lukasa commented Apr 25, 2017

I'm certainly open to that approach. It'd be interesting for @alex or @reaperhulk to weigh in with some relative usage data of different cffi versions, or at least with gut feelings, to work out how valuable that code will be, especially as it will suck to test. 😉

@hynek
Copy link
Contributor

hynek commented Apr 25, 2017

I think we could make it suck a lot less with something like this:

_is_acceptable_for_from_buffer = _make_buffer_checker(cffi.__version__)

@Lukasa
Copy link
Member Author

Lukasa commented Apr 25, 2017

Yeah, that's true, that could absolutely work.

@reaperhulk
Copy link
Member

We already have some code in cryptography dependent on cffi >= 1.7, but we don't require it so we do this:

https://github.com/pyca/cryptography/blob/master/src/cryptography/hazmat/primitives/ciphers/base.py#L148-L160

We get coverage on that branch by testing an old pypy with coverage enabled. https://github.com/pyca/cryptography/blob/master/.travis.yml#L27

The only real consequence to upping minimum cffi version is that you leave behind older PyPys (since they can't upgrade it). cryptography decided against doing that, but this project can make its own call.

@reaperhulk
Copy link
Member

Here are the number of downloads by implementation for the last month for pyopenssl:

1	2044707	CPython
2	108733	null	 
3	2238	PyPy	 
4	26	Jython	 
5	1	IronPython	 

@reaperhulk
Copy link
Member

And here's the top 10 versions within that PyPy umbrella:

1	816	5.4.1	 
2	536	5.6.0	 
3	134	5.3.1	 
4	122	4.0.1	 
5	117	5.7.0	 
6	110	5.7.1	 
7	62	2.4.0	 
8	61	5.0.1	 
9	50	5.4.0	 
10	50	2.5.0

A (very) quick check shows that PyPy2 5.2 shipped with cffi 1.8 (5.1 shipped with 1.6).

@hynek
Copy link
Contributor

hynek commented Apr 26, 2017

I would just go the hybrid way I’ve sketched out. It seems like something we can test easily and we’ve learned the hard way, that dependencies don’t get auto-updated. :|

@Lukasa Lukasa force-pushed the bytearray branch 4 times, most recently from 2d47b8b to c80ed17 Compare April 26, 2017 15:24
"""
cffi_version = tuple(
int(component) for component in
_CFFI_VERSION_RE.match(cffi_version_string).group(1, 2)
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just cffi_version_string.split(".")? A RE seems overkill here?

Copy link
Member

Choose a reason for hiding this comment

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

Why not just from packaging.version import parse? Having pyOpenSSL depend on packaging (which is a dependency of both setuptools and cryptography) doesn't seem unreasonable.

Copy link
Member Author

Choose a reason for hiding this comment

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

@hynek The main reason is that you can conceivably have a version string like 1.8dev4, which will not behave nicely under .split('.'). It's possible I'm being too defensive here, but I was trying to avoid having everything explode in situations I didn't entirely forsee.

@reaperhulk That seems reasonable to me, if it's ok with @hynek I'll happily do that instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Go ahead!

buffer_factory = _make_buffer_checker(cffi_version)
cdata_buffer = buffer_factory(data)
# We need an explicit length here because cffi on recent PyPy doesn't
# 't correctly construct buffers when it knows the length of the cdata
Copy link
Contributor

Choose a reason for hiding this comment

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

't looks like an oops :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Resolved.

@hynek
Copy link
Contributor

hynek commented Apr 28, 2017

@njsmith would this make you happy?

:param cffi_version_string: The version of CFFI in use, from
ffi.__version__.
"""
cffi_version = parse(cffi_version_string)
Copy link
Contributor

Choose a reason for hiding this comment

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

A quick check shows that cffi has had a pre-parsed version tuple at cffi.__version_info__ for some time (I checked 1.0.0 and it had it). That might be nicer than pulling in a dependency on packaging?

if isinstance(buf, _memoryview):
buf = buf.tobytes()
if isinstance(buf, _buffer):
buf = str(buf)
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. It may not matter much in practice, but I think on recent CFFI these lines are unnecessary and are inefficient in that they impose an extra copy?
  2. I might add a comment on the str line noting that this branch can only be taken on py2 (because py3 has no buffer objects)

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. They are, but the code refactor required to fix it and keep the tests in place was not something I wanted to undertake on this PR.
  2. I don't think it matters really?

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. Yeah, not really; I only mention it because I had a moment of "wtf you can't call str on a buffer!" that took a few minutes to resolve :-)

ffi.__version__.
"""
cffi_version = parse(cffi_version_string)
if cffi_version >= Version("1.7"):
Copy link
Contributor

Choose a reason for hiding this comment

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

The docs say "bytearray objects were supported in version 1.7 onwards ... and byte strings were supported in version 1.8 onwards." So the minimum version should probably be 1.8, not 1.7?

@njsmith
Copy link
Contributor

njsmith commented Apr 29, 2017

Some comments above to do with as you wish. In general I am not too picky though, because I'm just using PyOpenSSL in my test suite, so I don't care about speed or dependencies or any of that :-). I was just surprised when I had to add a call to bytes(...) so I figured I'd be a good citizen and report it upstream...

@Lukasa
Copy link
Member Author

Lukasa commented Apr 29, 2017

Aside from the two points I addressed the rest are things I want to take action on, I'll do that sometime soon.

@hynek
Copy link
Contributor

hynek commented May 31, 2017

oh looks like this only needs docs + changelog? completely lost track of it!

@dholth
Copy link
Contributor

dholth commented Jul 8, 2019

I was about to suggest this same bugfix! I guess you don't like just letting cffi decide whether the buffer passed is acceptable or not? In SSL send() e.g. try: buf = _ffi.from_buffer(buf) except: pyopenssl error about buffers

It almost certainly calls from_buffer internally as part of any wrapped function that takes char*?

@reaperhulk
Copy link
Member

@dholth I think at this point if you want to submit a new PR based on this we can look at that (@Lukasa is working on other projects and this is no longer an area of focus). When this PR was originally filed cryptography didn't require cffi >=1.8, but it does now so from_buffer will Just Work™.

@dholth
Copy link
Contributor

dholth commented Jul 8, 2019

Obsoleted by #852

Base automatically changed from master to main February 13, 2021 17:50
@mhils
Copy link
Member

mhils commented May 20, 2022

This is fixed indeed, thanks @dholth! 🍰

@mhils mhils closed this May 20, 2022
@dholth
Copy link
Contributor

dholth commented May 20, 2022

Hooray!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

bytearray is a bytes-like object
6 participants