diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index aa57f76598..07e99348bb 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3708,13 +3708,13 @@ bool GUI_App::on_init_network(bool try_backup) } } else { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed"; - // A failed install can leave the config naming a build that never made it to - // disk (download_plugin() adopts the downloaded version up front so that - // install_plugin() can name the library after it). If the whitelisted latest - // is still installed, fall back to it instead of dropping the user into the - // re-download flow without networking. + // A failed install can leave the config naming a build that never made it to disk; + // fall back to the installed latest instead of dropping the user into the re-download + // flow. Only when the configured library is genuinely absent, though - a pinned series + // that is on disk but failed to load once must keep its pin, not be rewritten for good. std::string latest = get_latest_network_version(); - if (config_version != latest && BBLNetworkPlugin::versioned_library_exists(latest)) { + if (config_version != latest && !BBLNetworkPlugin::versioned_library_exists(config_version) + && BBLNetworkPlugin::versioned_library_exists(latest)) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": falling back to installed " << latest; config_version = latest; app_config->set_network_plugin_version(latest); @@ -3818,7 +3818,14 @@ bool GUI_App::on_init_network(bool try_backup) m_user_manager = new Slic3r::UserManager(); } - if (should_load_networking_plugin && m_networking_compatible && !use_legacy_network_plugin()) { + // A version pinned to something other than the latest series is a deliberate choice, so it + // is exempt from the upgrade prompt the same way the legacy pin already is - otherwise the + // dialog reappears on every launch for as long as the pin is held. + const std::string pinned_version = app_config->get_network_plugin_version(); + const bool pinned_to_older_series = !pinned_version.empty() && + network_plugin_series(pinned_version) != network_plugin_series(get_latest_network_version()); + + if (should_load_networking_plugin && m_networking_compatible && !pinned_to_older_series) { app_config->clear_remind_network_update_later(); if (has_network_update_available()) { diff --git a/src/slic3r/Utils/BBLNetworkPlugin.cpp b/src/slic3r/Utils/BBLNetworkPlugin.cpp index b0dd277a79..607e7d16d1 100644 --- a/src/slic3r/Utils/BBLNetworkPlugin.cpp +++ b/src/slic3r/Utils/BBLNetworkPlugin.cpp @@ -17,6 +17,20 @@ namespace Slic3r { #define BAMBU_SOURCE_LIBRARY "BambuSource" +namespace { + +// Named in the load log: the bound generation is what ties a crash report to an ABI choice. +// The label is the whitelist row's series, so it can never drift from the dispatch table. +const char* network_abi_name(NetworkAbi abi) +{ + for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) + if (AVAILABLE_NETWORK_VERSIONS[i].abi == abi) + return AVAILABLE_NETWORK_VERSIONS[i].version; + return "unsupported"; +} + +} // namespace + // ============================================================================ // Singleton Implementation // ============================================================================ @@ -162,19 +176,20 @@ int BBLNetworkPlugin::initialize(bool using_backup, const std::string& version) // Load all function pointers load_all_function_pointers(); - // Sync legacy network flag from loaded plugin - m_use_legacy_network = is_legacy_version(version); + // Key the generation on the library that actually loaded, not the version asked for: + // resolve_library_path() serves any same-series build. m_get_version is read directly, not + // via get_version(), which would substitute the "00.00.00.00" sentinel and pick no generation. + const std::string loaded_version = m_get_version ? m_get_version() : std::string(); + m_network_abi = network_plugin_abi(loaded_version.empty() ? version : loaded_version); - std::string loaded_version; - if (m_get_version) { - loaded_version = m_get_version(); - if (!loaded_version.empty()) { - m_use_legacy_network = is_legacy_version(loaded_version); - } + // A library reporting a series this build has no ABI for stays loaded but uncallable - + // check_networking_version() then reports it as incompatible and offers the update flow. + if (m_network_abi == NetworkAbi::Unsupported) { + BOOST_LOG_TRIVIAL(warning) << "BBLNetworkPlugin::initialize: no ABI for version " + << (loaded_version.empty() ? version : loaded_version) << ", plug-in calls are disabled"; } - BOOST_LOG_TRIVIAL(info) << "BBLNetworkPlugin::initialize: legacy_mode=" - << (m_use_legacy_network ? "true" : "false") + BOOST_LOG_TRIVIAL(info) << "BBLNetworkPlugin::initialize: abi=" << network_abi_name(m_network_abi) << ", library=" << library << ", version=" << (loaded_version.empty() ? "unknown" : loaded_version) << ", send_message=" << (m_send_message ? "loaded" : "null") @@ -217,7 +232,9 @@ int BBLNetworkPlugin::unload() clear_all_function_pointers(); - m_use_legacy_network = false; + // Safe to reset only because every pointer was nulled just above and every dispatcher is + // guarded on a non-null pointer, so no stale generation is reachable. + m_network_abi = NetworkAbi::Unsupported; return 0; } @@ -520,7 +537,7 @@ void BBLNetworkPlugin::set_load_error(const std::string& message, } // ============================================================================ -// Legacy Helper +// ABI Conversion Helpers // ============================================================================ PrintParams_Legacy BBLNetworkPlugin::as_legacy(PrintParams& param) @@ -564,6 +581,57 @@ PrintParams_Legacy BBLNetworkPlugin::as_legacy(PrintParams& param) return l; } +// Every PrintParams field except the four the 02.08.01 series added +// (task_timelapse_use_internal, extruder_cali_manual_mode, svc_context, slicer_uid). +PrintParams_0203 BBLNetworkPlugin::as_0203(PrintParams& param) +{ + PrintParams_0203 p; + + p.dev_id = std::move(param.dev_id); + p.task_name = std::move(param.task_name); + p.project_name = std::move(param.project_name); + p.preset_name = std::move(param.preset_name); + p.filename = std::move(param.filename); + p.config_filename = std::move(param.config_filename); + p.plate_index = param.plate_index; + p.ftp_folder = std::move(param.ftp_folder); + p.ftp_file = std::move(param.ftp_file); + p.ftp_file_md5 = std::move(param.ftp_file_md5); + p.nozzle_mapping = std::move(param.nozzle_mapping); + p.ams_mapping = std::move(param.ams_mapping); + p.ams_mapping2 = std::move(param.ams_mapping2); + p.ams_mapping_info = std::move(param.ams_mapping_info); + p.nozzles_info = std::move(param.nozzles_info); + p.connection_type = std::move(param.connection_type); + p.comments = std::move(param.comments); + p.origin_profile_id = param.origin_profile_id; + p.stl_design_id = param.stl_design_id; + p.origin_model_id = std::move(param.origin_model_id); + p.print_type = std::move(param.print_type); + p.dst_file = std::move(param.dst_file); + p.dev_name = std::move(param.dev_name); + p.dev_ip = std::move(param.dev_ip); + p.use_ssl_for_ftp = param.use_ssl_for_ftp; + p.use_ssl_for_mqtt = param.use_ssl_for_mqtt; + p.username = std::move(param.username); + p.password = std::move(param.password); + p.task_bed_leveling = param.task_bed_leveling; + p.task_flow_cali = param.task_flow_cali; + p.task_vibration_cali = param.task_vibration_cali; + p.task_layer_inspect = param.task_layer_inspect; + p.task_record_timelapse = param.task_record_timelapse; + p.task_use_ams = param.task_use_ams; + p.task_bed_type = std::move(param.task_bed_type); + p.extra_options = std::move(param.extra_options); + p.auto_bed_leveling = param.auto_bed_leveling; + p.auto_flow_cali = param.auto_flow_cali; + p.auto_offset_cali = param.auto_offset_cali; + p.task_ext_change_assist = param.task_ext_change_assist; + p.try_emmc_print = param.try_emmc_print; + + return p; +} + // ============================================================================ // Function Pointer Loading // ============================================================================ @@ -669,7 +737,8 @@ void BBLNetworkPlugin::load_all_function_pointers() m_get_mw_user_preference = reinterpret_cast(get_function("bambu_network_get_mw_user_preference")); m_get_mw_user_4ulist = reinterpret_cast(get_function("bambu_network_get_mw_user_4ulist")); - // Added by the 02.08.01.52 plugin ABI; resolve to null on older plugins so callers no-op. + // Bound late; anything a generation does not export resolves to null so callers no-op. + // See the typedefs for which generation introduced each of these. m_set_on_user_login_fn = reinterpret_cast(get_function("bambu_network_set_on_user_login_fn")); m_get_studio_info_url = reinterpret_cast(get_function("bambu_network_get_studio_info_url")); m_report_consent = reinterpret_cast(get_function("bambu_network_report_consent")); diff --git a/src/slic3r/Utils/BBLNetworkPlugin.hpp b/src/slic3r/Utils/BBLNetworkPlugin.hpp index ed94b23f85..3788568e37 100644 --- a/src/slic3r/Utils/BBLNetworkPlugin.hpp +++ b/src/slic3r/Utils/BBLNetworkPlugin.hpp @@ -119,7 +119,7 @@ typedef int (*func_get_model_mall_rating_result)(void *agent, int job_id, std::s typedef int (*func_get_mw_user_preference)(void *agent, std::function callback); typedef int (*func_get_mw_user_4ulist)(void *agent, int seed, int limit, std::function callback); -// Legacy function pointer types (for older DLL versions) +// Legacy function pointer types (for the 01.10.01 DLL) typedef int (*func_start_print_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); typedef int (*func_start_local_print_with_record_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); typedef int (*func_start_send_gcode_to_sdcard_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); @@ -128,10 +128,25 @@ typedef int (*func_start_sdcard_print_legacy)(void* agent, PrintParams_Legacy pa typedef int (*func_send_message_legacy)(void* agent, std::string dev_id, std::string json_str, int qos); typedef int (*func_send_message_to_printer_legacy)(void* agent, std::string dev_id, std::string json_str, int qos); -// Added by the 02.08.01.52 plugin ABI (null on older plugins). +// 02.03.00 function pointer types. Only PrintParams differs from the current ABI; send_message +// and send_message_to_printer already take the flag argument in this series. +typedef int (*func_start_print_0203)(void *agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); +typedef int (*func_start_local_print_with_record_0203)(void *agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); +typedef int (*func_start_send_gcode_to_sdcard_0203)(void *agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); +typedef int (*func_start_local_print_0203)(void *agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); +typedef int (*func_start_sdcard_print_0203)(void* agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); + +// bind() gained dev_model in 02.08.01; the legacy and 02.03.00 series share the older form. +typedef int (*func_bind_pre0208)(void *agent, std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn); + +// Exported by every supported generation, but only bound here. typedef int (*func_set_on_user_login_fn)(void *agent, OnUserLoginFn fn); typedef std::string (*func_get_studio_info_url)(void *agent); + +// Present since 02.03.00, null on the legacy plugin. typedef int (*func_report_consent)(void *agent, std::string expand); + +// Added by the 02.08.01.52 plugin ABI (null on older plugins). typedef int (*func_get_camera_url_for_golive)(void *agent, std::string dev_id, std::string sdev_id, std::function callback); typedef int (*func_get_hms_snapshot)(void *agent, std::string& dev_id, std::string& file_name, std::function callback); typedef int (*func_get_filament_spools)(void *agent, FilamentQueryParams params, std::string* http_body); @@ -279,12 +294,13 @@ public: const std::string& attempted_path); // ======================================================================== - // Legacy Network Flag + // Plug-in ABI Generation // ======================================================================== static bool is_legacy_version(const std::string& version) { return version == BAMBU_NETWORK_AGENT_VERSION_LEGACY; } - bool use_legacy_network() const { return m_use_legacy_network; } - void set_use_legacy_network(bool legacy) { m_use_legacy_network = legacy; } + // The generation the loaded library speaks - what every call must dispatch on. + NetworkAbi network_abi() const { return m_network_abi; } + bool use_legacy_network() const { return m_network_abi == NetworkAbi::Legacy; } // ======================================================================== // Function Pointer Accessors @@ -401,10 +417,12 @@ public: func_sync_ams_filaments get_sync_ams_filaments() const { return m_sync_ams_filaments; } // ======================================================================== - // Legacy Helper + // ABI Conversion Helpers // ======================================================================== + // Both move out of `param`, so convert only inside the branch that will actually run. static PrintParams_Legacy as_legacy(PrintParams& param); + static PrintParams_0203 as_0203(PrintParams& param); private: // Singleton instance pointer (heap-allocated for explicit lifetime control) @@ -431,8 +449,8 @@ private: // Load error state NetworkLibraryLoadError m_load_error; - // Legacy network compatibility flag - bool m_use_legacy_network{false}; + // ABI generation of the currently loaded library + NetworkAbi m_network_abi{NetworkAbi::Unsupported}; // Function pointers func_check_debug_consistent m_check_debug_consistent{nullptr}; diff --git a/src/slic3r/Utils/BBLPrinterAgent.cpp b/src/slic3r/Utils/BBLPrinterAgent.cpp index 57f763487c..ef85e0a1ff 100644 --- a/src/slic3r/Utils/BBLPrinterAgent.cpp +++ b/src/slic3r/Utils/BBLPrinterAgent.cpp @@ -26,11 +26,19 @@ int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int auto agent = plugin.get_agent(); auto func = plugin.get_send_message(); if (func && agent) { - if (plugin.use_legacy_network()) { + // Only the legacy plug-in lacks `flag`; 02.03.00 already takes it, and routing that + // series through the legacy form would silently drop MessageFlag sign/encrypt. + switch (plugin.network_abi()) { + case NetworkAbi::Legacy: { auto legacy_func = reinterpret_cast(func); - return legacy_func(agent, dev_id, json_str, qos); + return legacy_func(agent, std::move(dev_id), std::move(json_str), qos); + } + case NetworkAbi::V0203: + case NetworkAbi::Current: + return func(agent, std::move(dev_id), std::move(json_str), qos, flag); + default: + return -1; } - return func(agent, dev_id, json_str, qos, flag); } return -1; } @@ -63,11 +71,17 @@ int BBLPrinterAgent::send_message_to_printer(std::string dev_id, std::string jso auto agent = plugin.get_agent(); auto func = plugin.get_send_message_to_printer(); if (func && agent) { - if (plugin.use_legacy_network()) { + switch (plugin.network_abi()) { + case NetworkAbi::Legacy: { auto legacy_func = reinterpret_cast(func); - return legacy_func(agent, dev_id, json_str, qos); + return legacy_func(agent, std::move(dev_id), std::move(json_str), qos); + } + case NetworkAbi::V0203: + case NetworkAbi::Current: + return func(agent, std::move(dev_id), std::move(json_str), qos, flag); + default: + return -1; } - return func(agent, dev_id, json_str, qos, flag); } return -1; } @@ -144,7 +158,19 @@ int BBLPrinterAgent::bind(std::string dev_ip, std::string dev_id, std::string de auto agent = plugin.get_agent(); auto func = plugin.get_bind(); if (func && agent) { - return func(agent, dev_ip, dev_id, dev_model, sec_link, timezone, improved, update_fn); + // dev_model was added in 02.08.01. Passing it to a plug-in that takes the 7-argument + // form shifts every following argument, so the older generations get the older call. + switch (plugin.network_abi()) { + case NetworkAbi::Legacy: + case NetworkAbi::V0203: { + auto older_func = reinterpret_cast(func); + return older_func(agent, dev_ip, dev_id, sec_link, timezone, improved, update_fn); + } + case NetworkAbi::Current: + return func(agent, dev_ip, dev_id, dev_model, sec_link, timezone, improved, update_fn); + default: + return -1; + } } return -1; } @@ -281,84 +307,62 @@ AgentInfo BBLPrinterAgent::get_agent_info_static() // Print Job Operations // ============================================================================ -int BBLPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) +namespace { + +// Shared dispatcher for the start_* operations, whose params layout differs per generation. +// The per-generation typedefs are template arguments so a swapped pair fails to compile +// (each arm's converted params must match the casted signature). Each arm converts and calls +// in one step: as_legacy()/as_0203() move out of `params` and their prvalue result lands in +// the by-value ABI argument without another copy; the Current arm moves `params` outright. +template +int dispatch_start(CurrentFn func, PrintParams& params, const CallbackFns&... callbacks) { auto& plugin = BBLNetworkPlugin::instance(); auto agent = plugin.get_agent(); - auto func = plugin.get_start_print(); - if (func && agent) { - if (plugin.use_legacy_network()) { - auto legacy_func = reinterpret_cast(func); - auto legacy_params = BBLNetworkPlugin::as_legacy(params); - return legacy_func(agent, legacy_params, update_fn, cancel_fn, wait_fn); - } - return func(agent, params, update_fn, cancel_fn, wait_fn); + if (!func || !agent) + return -1; + switch (plugin.network_abi()) { + case NetworkAbi::Legacy: + return reinterpret_cast(func)(agent, BBLNetworkPlugin::as_legacy(params), callbacks...); + case NetworkAbi::V0203: + return reinterpret_cast(func)(agent, BBLNetworkPlugin::as_0203(params), callbacks...); + case NetworkAbi::Current: + return func(agent, std::move(params), callbacks...); + default: + return -1; } - return -1; +} + +} // namespace + +int BBLPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) +{ + return dispatch_start( + BBLNetworkPlugin::instance().get_start_print(), params, update_fn, cancel_fn, wait_fn); } int BBLPrinterAgent::start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) { - auto& plugin = BBLNetworkPlugin::instance(); - auto agent = plugin.get_agent(); - auto func = plugin.get_start_local_print_with_record(); - if (func && agent) { - if (plugin.use_legacy_network()) { - auto legacy_func = reinterpret_cast(func); - auto legacy_params = BBLNetworkPlugin::as_legacy(params); - return legacy_func(agent, legacy_params, update_fn, cancel_fn, wait_fn); - } - return func(agent, params, update_fn, cancel_fn, wait_fn); - } - return -1; + return dispatch_start( + BBLNetworkPlugin::instance().get_start_local_print_with_record(), params, update_fn, cancel_fn, wait_fn); } int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) { - auto& plugin = BBLNetworkPlugin::instance(); - auto agent = plugin.get_agent(); - auto func = plugin.get_start_send_gcode_to_sdcard(); - if (func && agent) { - if (plugin.use_legacy_network()) { - auto legacy_func = reinterpret_cast(func); - auto legacy_params = BBLNetworkPlugin::as_legacy(params); - return legacy_func(agent, legacy_params, update_fn, cancel_fn, wait_fn); - } - return func(agent, params, update_fn, cancel_fn, wait_fn); - } - return -1; + return dispatch_start( + BBLNetworkPlugin::instance().get_start_send_gcode_to_sdcard(), params, update_fn, cancel_fn, wait_fn); } int BBLPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) { - auto& plugin = BBLNetworkPlugin::instance(); - auto agent = plugin.get_agent(); - auto func = plugin.get_start_local_print(); - if (func && agent) { - if (plugin.use_legacy_network()) { - auto legacy_func = reinterpret_cast(func); - auto legacy_params = BBLNetworkPlugin::as_legacy(params); - return legacy_func(agent, legacy_params, update_fn, cancel_fn); - } - return func(agent, params, update_fn, cancel_fn); - } - return -1; + return dispatch_start( + BBLNetworkPlugin::instance().get_start_local_print(), params, update_fn, cancel_fn); } int BBLPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) { - auto& plugin = BBLNetworkPlugin::instance(); - auto agent = plugin.get_agent(); - auto func = plugin.get_start_sdcard_print(); - if (func && agent) { - if (plugin.use_legacy_network()) { - auto legacy_func = reinterpret_cast(func); - auto legacy_params = BBLNetworkPlugin::as_legacy(params); - return legacy_func(agent, legacy_params, update_fn, cancel_fn); - } - return func(agent, params, update_fn, cancel_fn); - } - return -1; + return dispatch_start( + BBLNetworkPlugin::instance().get_start_sdcard_print(), params, update_fn, cancel_fn); } // ============================================================================ diff --git a/src/slic3r/Utils/bambu_networking.hpp b/src/slic3r/Utils/bambu_networking.hpp index 1093392423..c514daffff 100644 --- a/src/slic3r/Utils/bambu_networking.hpp +++ b/src/slic3r/Utils/bambu_networking.hpp @@ -230,6 +230,59 @@ struct PrintParams_Legacy { std::string extra_options; }; +/* print job, as the 02.03.00 series expects it. The 02.08.01 series inserted + task_timelapse_use_internal, extruder_cali_manual_mode, svc_context and slicer_uid into + PrintParams; two of them sit mid-struct, so an older plug-in misreads every field from + task_use_ams onwards if handed the current layout. Rebuild it with as_0203() instead. */ +struct PrintParams_0203 { + /* basic info */ + std::string dev_id; + std::string task_name; + std::string project_name; + std::string preset_name; + std::string filename; + std::string config_filename; + int plate_index; + std::string ftp_folder; + std::string ftp_file; + std::string ftp_file_md5; + std::string nozzle_mapping; + std::string ams_mapping; + std::string ams_mapping2; + std::string ams_mapping_info; + std::string nozzles_info; + std::string connection_type; + std::string comments; + int origin_profile_id = 0; + int stl_design_id = 0; + std::string origin_model_id; + std::string print_type; + std::string dst_file; + std::string dev_name; + + /* access options */ + std::string dev_ip; + bool use_ssl_for_ftp; + bool use_ssl_for_mqtt; + std::string username; + std::string password; + + /*user options */ + bool task_bed_leveling; /* bed leveling of task */ + bool task_flow_cali; /* flow calibration of task */ + bool task_vibration_cali; /* vibration calibration of task */ + bool task_layer_inspect; /* first layer inspection of task */ + bool task_record_timelapse; /* record timelapse of task */ + bool task_use_ams; + std::string task_bed_type; + std::string extra_options; + int auto_bed_leveling{ 0 }; + int auto_flow_cali{ 0 }; + int auto_offset_cali{ 0 }; + bool task_ext_change_assist; + bool try_emmc_print; +}; + /* print job*/ struct PrintParams { /* basic info */ @@ -351,21 +404,34 @@ struct CertificateInformation { std::string serial_number; }; +// The plug-in ABI generation a library speaks. Generations differ in by-value struct layouts and +// function signatures, so a call must go through the matching typedefs (see BBLPrinterAgent) - +// the wrong one corrupts the stack rather than failing cleanly. +enum class NetworkAbi { + Unsupported, // no generation in this build can call it - never dispatch through it + Legacy, // 01.10.01: PrintParams_Legacy; send_message/send_message_to_printer take no flag + V0203, // 02.03.00: PrintParams_0203; bind takes no dev_model + Current, // 02.08.01: the layouts and signatures this build declares directly +}; + struct NetworkLibraryVersion { const char* version; const char* display_name; const char* url_override; bool is_latest; const char* warning; + NetworkAbi abi; }; -// Only the latest series and the legacy build are offered/loadable: the host binds the -// modern ABI (by-value struct layouts, function signatures) of exactly one series, plus -// a dedicated shim for the legacy build. Older 02.0x series expect different layouts -// and must not be loaded - see is_supported_network_version(). +// Every row names the generation that can call it, so a series can never be offered without a +// host-side ABI for it. Series with no generation - 02.01.01, 02.00.02 and older - must stay out; +// is_supported_network_version() is the gate that keeps them from loading. static const NetworkLibraryVersion AVAILABLE_NETWORK_VERSIONS[] = { - {"02.08.01", "02.08.01", nullptr, true, nullptr}, - {BAMBU_NETWORK_AGENT_VERSION_LEGACY, BAMBU_NETWORK_AGENT_VERSION_LEGACY " (legacy)", nullptr, false, nullptr}, + {"02.08.01", "02.08.01", nullptr, true, nullptr, NetworkAbi::Current}, + {"02.03.00", "02.03.00", nullptr, false, + "An older plug-in series. Features that need newer plug-in support, such as print-failure " + "snapshots in the device error dialog, are unavailable.", NetworkAbi::V0203}, + {BAMBU_NETWORK_AGENT_VERSION_LEGACY, BAMBU_NETWORK_AGENT_VERSION_LEGACY " (legacy)", nullptr, false, nullptr, NetworkAbi::Legacy}, }; static const size_t AVAILABLE_NETWORK_VERSIONS_COUNT = sizeof(AVAILABLE_NETWORK_VERSIONS) / sizeof(AVAILABLE_NETWORK_VERSIONS[0]); @@ -378,23 +444,43 @@ inline const char* get_latest_network_version() { return AVAILABLE_NETWORK_VERSIONS[0].version; } -// True when the version can be loaded through the ABI this build was compiled against: -// an exact whitelist entry, or a build from the same AA.BB.CC series as a non-legacy -// whitelist entry (the plugin ABI is stable within a series, and the OTA sync only ever -// installs same-series updates). Anything else - in particular older 02.0x series a -// previous Orca release whitelisted - expects different by-value struct layouts and -// function signatures and must not be loaded. -inline bool is_supported_network_version(const std::string& version) { +// The AA.BB.CC series of a modern version string - the plug-in's stored identity. The 4th +// component is only which build of the series happens to be installed and is read live from +// the loaded plug-in for display. Legacy keeps its exact string (the shim matches exactly). +inline std::string network_plugin_series(const std::string& version) { + if (version.empty() || version == BAMBU_NETWORK_AGENT_VERSION_LEGACY) + return version; + return version.size() >= 8 ? version.substr(0, 8) : version; +} + +// Index of the whitelist entry that can load this version: an exact match, or a build of the same +// AA.BB.CC series as a non-legacy entry (the ABI is stable within a series, and the OTA sync only +// installs same-series updates). Legacy matches exactly only - a sibling build of that series +// would come through the modern layout. AVAILABLE_NETWORK_VERSIONS_COUNT when nothing matches. +inline size_t find_network_version_index(const std::string& version) { + const std::string series = network_plugin_series(version); for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) { const std::string base = AVAILABLE_NETWORK_VERSIONS[i].version; if (version == base) - return true; + return i; if (base == BAMBU_NETWORK_AGENT_VERSION_LEGACY) continue; - if (version.size() >= 8 && base.size() >= 8 && version.compare(0, 8, base, 0, 8) == 0) - return true; + if (series == base) + return i; } - return false; + return AVAILABLE_NETWORK_VERSIONS_COUNT; +} + +// True when a whitelisted series can load the version through an ABI this build implements. +inline bool is_supported_network_version(const std::string& version) { + return find_network_version_index(version) < AVAILABLE_NETWORK_VERSIONS_COUNT; +} + +// The generation to call a loaded library through. Unsupported for anything the load gate rejects, +// so a mislabelled library reaches no plug-in call instead of a layout it does not share. +inline NetworkAbi network_plugin_abi(const std::string& version) { + const size_t i = find_network_version_index(version); + return i < AVAILABLE_NETWORK_VERSIONS_COUNT ? AVAILABLE_NETWORK_VERSIONS[i].abi : NetworkAbi::Unsupported; } struct NetworkLibraryVersionInfo { @@ -441,15 +527,6 @@ inline std::string extract_suffix(const std::string& full_version) { return (pos == std::string::npos) ? "" : full_version.substr(pos + 1); } -// The AA.BB.CC series of a modern version string - the plug-in's stored identity. The 4th -// component is only which build of the series happens to be installed and is read live from -// the loaded plug-in for display. Legacy keeps its exact string (the shim matches exactly). -inline std::string network_plugin_series(const std::string& version) { - if (version.empty() || version == BAMBU_NETWORK_AGENT_VERSION_LEGACY) - return version; - return version.size() >= 8 ? version.substr(0, 8) : version; -} - // True when the version is a pure dotted-numeric build (AA.BB.CC or AA.BB.CC.DD) whose identity // collapses to its series - the managed/OTA build. Legacy and any custom-named build // (02.08.01_custom, 02.08.01.52-dev) are NOT managed: they are genuinely distinct files kept diff --git a/tests/slic3rutils/test_network_versions.cpp b/tests/slic3rutils/test_network_versions.cpp index af8a639025..efe8b5d831 100644 --- a/tests/slic3rutils/test_network_versions.cpp +++ b/tests/slic3rutils/test_network_versions.cpp @@ -85,7 +85,8 @@ TEST_CASE_METHOD(PluginFolderFixture, "Managed builds fold into the series; cust { add_plugin("02.08.01.55"); // managed, same series -> folded into the 02.08.01 row add_plugin("02.09.00.10"); // managed, unknown series -> not listed - add_plugin("02.03.00.62"); // managed, series no longer whitelisted -> not listed + add_plugin("02.03.00.62"); // managed, older whitelisted series -> folded into 02.03.00 + add_plugin("02.01.01.52"); // managed, series with no ABI in this build -> not listed add_plugin("02.08.01_custom"); // custom, whitelisted series -> listed under it add_plugin("02.08.01.52-dev"); // custom (dash-suffixed), whitelisted series -> listed @@ -96,17 +97,24 @@ TEST_CASE_METHOD(PluginFolderFixture, "Managed builds fold into the series; cust REQUIRE(count_version(versions, "02.08.01") == 1); REQUIRE(count_version(versions, "02.09.00.10") == 0); REQUIRE(count_version(versions, "02.03.00.62") == 0); + REQUIRE(count_version(versions, "02.03.00") == 1); + REQUIRE(count_version(versions, "02.01.01.52") == 0); // Custom-named builds are distinct files kept under their own name. REQUIRE(count_version(versions, "02.08.01_custom") == 1); REQUIRE(count_version(versions, "02.08.01.52-dev") == 1); // Newest series first, its customs nested under it (suffix sort: "" < ".52-dev" < "_custom"), - // legacy last. + // then older series, legacy last. REQUIRE(versions[0].version == "02.08.01"); REQUIRE(versions[1].version == "02.08.01.52-dev"); REQUIRE(versions[2].version == "02.08.01_custom"); + REQUIRE(versions[3].version == "02.03.00"); REQUIRE(versions.back().version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); + // An older whitelisted series is a flat row of its own, and never holds "(Latest)". + REQUIRE(versions[3].suffix.empty()); + REQUIRE_FALSE(versions[3].is_latest); + // Customs sort/render nested under their series (non-empty suffix, base = the series). REQUIRE(versions[1].base_version == "02.08.01"); REQUIRE_FALSE(versions[1].suffix.empty()); @@ -137,6 +145,16 @@ TEST_CASE_METHOD(PluginFolderFixture, "Only the loaded series is marked installe REQUIRE(marked == 1); } + // An older series is marked the same way, and never bleeds onto the latest row. + { + add_plugin("02.03.00.62"); + auto versions = get_all_available_versions("02.03.00.62"); + int marked = 0; + for (const auto& info : versions) + if (info.is_loaded) { ++marked; REQUIRE(info.version == "02.03.00"); } + REQUIRE(marked == 1); + } + // A loaded custom build matches its own row, never the bare series. { auto versions = get_all_available_versions("02.08.01_custom"); @@ -153,19 +171,25 @@ TEST_CASE_METHOD(PluginFolderFixture, "Only the loaded series is marked installe TEST_CASE("Only whitelisted series pass the load gate", "[NetworkVersions]") { - // The whitelisted series, its builds, and custom-named builds of that series. + // Each whitelisted series, its builds, and custom-named builds of that series. REQUIRE(is_supported_network_version("02.08.01")); REQUIRE(is_supported_network_version("02.08.01.52")); REQUIRE(is_supported_network_version("02.08.01.55")); REQUIRE(is_supported_network_version("02.08.01_custom")); REQUIRE(is_supported_network_version("02.08.01.52-dev")); + REQUIRE(is_supported_network_version("02.03.00")); + REQUIRE(is_supported_network_version("02.03.00.62")); + REQUIRE(is_supported_network_version("02.03.00.70")); + REQUIRE(is_supported_network_version("02.03.00_custom")); REQUIRE(is_supported_network_version(BAMBU_NETWORK_AGENT_VERSION_LEGACY)); - // Series whitelisted by previous Orca releases - their ABI no longer matches. - REQUIRE_FALSE(is_supported_network_version("02.03.00.62")); + // Series whitelisted by previous Orca releases that no generation here can call. REQUIRE_FALSE(is_supported_network_version("02.01.01.52")); REQUIRE_FALSE(is_supported_network_version("02.00.02.50")); + // A neighbouring series of a whitelisted one is still its own ABI. + REQUIRE_FALSE(is_supported_network_version("02.03.01.51")); + // Unknown series, legacy siblings, and malformed values. REQUIRE_FALSE(is_supported_network_version("02.09.00.10")); std::string legacy = BAMBU_NETWORK_AGENT_VERSION_LEGACY; @@ -175,6 +199,30 @@ TEST_CASE("Only whitelisted series pass the load gate", "[NetworkVersions]") REQUIRE_FALSE(is_supported_network_version("02.08")); } +TEST_CASE("Each version resolves to the ABI generation that can call it", "[NetworkVersions]") +{ + // The generation is keyed on the series, so every build of a series - including the + // custom-named ones - resolves to the same one. + CHECK(network_plugin_abi("02.08.01") == NetworkAbi::Current); + CHECK(network_plugin_abi("02.08.01.55") == NetworkAbi::Current); + CHECK(network_plugin_abi("02.08.01.52-dev") == NetworkAbi::Current); + CHECK(network_plugin_abi("02.03.00") == NetworkAbi::V0203); + CHECK(network_plugin_abi("02.03.00.62") == NetworkAbi::V0203); + CHECK(network_plugin_abi("02.03.00_custom") == NetworkAbi::V0203); + CHECK(network_plugin_abi(BAMBU_NETWORK_AGENT_VERSION_LEGACY) == NetworkAbi::Legacy); + + // Anything the load gate rejects must dispatch through nothing at all, rather than + // defaulting to a layout it does not share. + CHECK(network_plugin_abi("02.01.01.52") == NetworkAbi::Unsupported); + CHECK(network_plugin_abi("02.00.02.50") == NetworkAbi::Unsupported); + CHECK(network_plugin_abi("02.09.00.10") == NetworkAbi::Unsupported); + CHECK(network_plugin_abi("") == NetworkAbi::Unsupported); + + // A series may only be offered once the dispatch layer implements its generation. + for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) + CHECK(AVAILABLE_NETWORK_VERSIONS[i].abi != NetworkAbi::Unsupported); +} + TEST_CASE_METHOD(PluginFolderFixture, "Legacy series never adopts discovered builds", "[NetworkVersions]") { // A different build of the legacy series must not be surfaced: is_legacy_version()