H2C (firmware 01.02.00.00) had no per-model FTP profile and ran on the
Python-default TLS 1.3, hitting the same vsFTPd session-reuse fault the P2S
(#1401) and X2D (#1638) were already capped for. The intermittent FTPS
failure dropped prints to the no-3MF fallback archive, so slice data was
missing — hence no filament in the Print Log and no inventory deduction.
Add an H2C cap_tls_v1_2 profile plus its O1C/O1C2 SSDP aliases. H2D is left
on the default profile (negotiates TLS 1.3 without the fault).
Reporter @vasmarfas saw X2D archive cards land almost empty - only print
time, no filament weight / layers / MakerWorld link / thumbnail - and
Spoolman filament-usage tracking went silent on the same printer.
Support bundle traces the end-to-end: at print start
backend/app/main.py::on_print_start tries the usual FTP-download dance
for the 3MF, every implicit-FTPS connect attempt to the X2D fails with
`[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1032)`, ~2
minutes later "Could not find 3MF file for print" -> "Created fallback
archive". Fallback path writes file_path="", file_size=0,
content_hash=NULL, no layers / filament / model-link fields. Spoolman
tracking degrades from the same root cause - both depend on the 3MF
metadata parser.
Proximate cause: Python 3.13's default ssl.create_default_context()
negotiates TLS 1.3, the X2D's implicit-FTPS server on port 990 rejects
the ClientHello. Same family as the P2S 01.02.00.00 bug from #1401
(post-Python-3.13 TLS-1.3 breakage), different wire-level failure mode
(P2S completes the handshake and truncates with 426; X2D fails the
handshake outright).
Same fix shape: add X2D to backend/app/services/ftp_profiles.py with
cap_tls_v1_2=True, plus N6 -> X2D SSDP alias. Every other model stays
on negotiated TLS 1.3.
Honest caveat: hypothesis-driven trial, not a confirmed root-cause fix.
WRONG_VERSION_NUMBER could equally describe the X2D switching to
explicit FTPS (AUTH TLS on plaintext greeting) or moving FTPS to a
different port - either would need a different code path. Reporter has
been asked to test this build; if the cap doesn't clear it the registry
slot stays useful and the next diagnostic round goes to openssl
s_client from a network-adjacent host.
Python 3.13 negotiates TLS 1.3 by default. The P2S firmware 01.02.00.00
vsFTPd build doesn't tolerate TLS 1.3's async session-ticket model on
the FTPS data channel — session resumption races, the data channel gets
torn down mid-stream, uploads land truncated at a chunk boundary, and
the printer replies 426 instead of 226. Visible to the user as "unable
to parse 3mf file" 30 s into the print.
Capping the SSL context's maximum_version to TLS 1.2 makes session
resumption synchronous and uploads complete normally.
Follow the per-model pattern established by camera_profiles.py in the
#1395 follow-up: add backend/app/services/ftp_profiles.py with a frozen
FTPProfile dataclass and a per-model registry. Only P2S (display name
+ N7 SSDP code) gets the cap today. X1C, H2D, P1S, A1 stay on negotiated
TLS 1.3 — the maintainer's dogfooded printers see zero behaviour change.