Skip to content

ffi: add rnp_uid_get_photo() for Photo ID read support (#13) - #2430

Open
ronaldtse wants to merge 3 commits into
mainfrom
photo-id-support
Open

ffi: add rnp_uid_get_photo() for Photo ID read support (#13)#2430
ronaldtse wants to merge 3 commits into
mainfrom
photo-id-support

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

Summary

Foundation for issue #1947 (Photo ID support). Adds an FFI function that parses the User Attribute packet body and returns just the image bytes plus the detected format, so callers don't have to implement the subpacket length decoding and image-header walk themselves.

What's in this PR

  • include/rnp/rnp.h: new constants RNP_PHOTO_FORMAT_{UNKNOWN,JPEG,PNG} and new FFI function rnp_uid_get_photo(uid, &format, &data, &size) with thorough Doxygen.
  • src/lib/userid.{hpp,cpp}: new helper rnp::parse_photo_attribute(uid_body, image, format) that walks the variable-length subpacket prefix, finds the first Image Attribute subpacket (type 1), and returns its image bytes plus detected format. The image header length byte counts itself (RFC 4880 §5.12.1 / RFC 9580 §5.13.1), so the remaining header bytes to skip are hdr_len - 1, not hdr_len. Format is detected from magic bytes (JPEG: FF D8 FF, PNG: 89 50 4E 47), not from the UserAttr header's format byte — real-world keys frequently leave that byte at 0 even when the image data is a valid JPEG.
  • src/lib/rnp.cpp: FFI implementation. Returns RNP_ERROR_BAD_PARAMETERS if the UID is not a User Attribute packet, RNP_ERROR_BAD_STATE if the UserAttr body is malformed or contains no image subpacket.
  • src/tests/ffi.cpp: extends the existing test_ffi_load_userattr to exercise the new API: asserts the photo UID returns JPEG bytes with the correct magic header, and that calling rnp_uid_get_photo() on a non-photo UID is rejected.

Why

The "gpg can, rnp can't" feature gap is most visible to end users — Thunderbird users with photo IDs on their keys see them silently dropped when their keyring is read by rnp. The UserAttr packet was already loaded as an opaque UID with type=RNP_USER_ATTR and the raw bytes retrievable via rnp_uid_get_data(), but parsing those bytes was the caller's job. This PR makes the common case (a JPEG photo) one FFI call.

Out of scope (follow-up PRs)

  • rnp_key_add_photo() to attach a new photo to a key (needs serialiser work)
  • CLI: --show-photo flag, [photo] indicator in --list-keys
  • Negative tests with crafted malformed UserAttr bodies
  • PNG photo round-trip test (would need a PNG fixture)

Test plan

  • test_ffi_load_userattr (extended) passes: verifies JPEG magic, JPEG format constant, rejection of non-photo UID
  • Manual verification against data/test_stream_key_load/ecc-25519-photo-pub.asc — image bytes form a valid JPEG (starts with FF D8 FF E0, ends with FF D9)
  • No regressions in test_ffi_add_userid, test_ffi_key_userid_dump_has_no_special_chars, test_ffi_key_dump_edge_cases
  • CI green

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.87879% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.47%. Comparing base (ce33cc6) to head (a9b409b).

Files with missing lines Patch % Lines
src/lib/rnp.cpp 77.77% 4 Missing ⚠️
src/lib/userid.cpp 91.66% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2430      +/-   ##
==========================================
+ Coverage   85.44%   85.47%   +0.02%     
==========================================
  Files         125      125              
  Lines       22962    23028      +66     
==========================================
+ Hits        19620    19683      +63     
- Misses       3342     3345       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ronaldtse
ronaldtse requested review from antonsviridenko and ni4 July 31, 2026 16:20
@ronaldtse
ronaldtse force-pushed the photo-id-support branch 3 times, most recently from b879067 to 1e845f9 Compare August 22, 2026 05:25

@ni4 ni4 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

Foundation for issue #1947 (Photo ID support). Adds an FFI function
that parses the User Attribute packet body (RFC 4880 §5.12 /
RFC 9580 §5.13) and returns just the image bytes plus its detected
format (JPEG or PNG), saving callers from having to implement the
subpacket length decoding and image-header walk themselves.

The UserAttr packet was already loaded and stored as a UID with
type RNP_USER_ATTR; this PR adds the typed photo accessor on top.

Image format is detected from the bytes' magic header (FF D8 FF for
JPEG, 89 50 4E 47 for PNG), not from the UserAttr header's format
byte, since real-world keys frequently leave the format byte at 0
even when the data is a valid JPEG.

Out of scope (follow-up PRs):
- rnp_key_add_photo() to attach a new photo to a key
- CLI --show-photo flag
- Negative tests with crafted malformed UserAttr bodies
- Photo ID display in --list-keys output
…ollow-up)

The FFI test only exercises one fixture (the JPEG round-trip), so
codecov shows the parser's edge cases uncovered. Adds
test_parse_photo_attribute in cipher.cpp that calls the C++ API
directly with crafted UserAttr bodies to cover:

- JPEG happy path
- PNG happy path (different magic-byte detection branch)
- Unknown image format (still returns bytes, format = Unknown)
- Empty body
- Non-image subpacket type (must skip past)
- Header length 0 (must reject — RFC requires >= 16)
- Image too small to magic-check (< 3 bytes)
- 5-byte subpacket length form (b0 == 255)
- Partial-length encoding (b0 in 224-254) — must reject
- Multiple subpackets where the first is non-image and the second
  contains the photo
Main's lint now runs the clang-format 11.1.0 binary (a6b0b99); these
files were formatted under the previous 11.0.x pin and drifted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants