Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libnitrokey for openSUSE:Factory 
checked in at 2022-05-06 18:59:47
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libnitrokey (Old)
 and      /work/SRC/openSUSE:Factory/.libnitrokey.new.1538 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libnitrokey"

Fri May  6 18:59:47 2022 rev:2 rq:975343 version:3.7

Changes:
--------
--- /work/SRC/openSUSE:Factory/libnitrokey/libnitrokey.changes  2022-04-25 
23:35:27.622410827 +0200
+++ /work/SRC/openSUSE:Factory/.libnitrokey.new.1538/libnitrokey.changes        
2022-05-06 19:00:09.813410604 +0200
@@ -1,0 +2,9 @@
+Tue May  3 05:57:51 UTC 2022 - Martin Sirringhaus <martin.sirringh...@suse.com>
+
+- Update to 3.7
+  * Nitrokey Pro v0.14 support;
+  * Udev rules update;
+  * Stability fixes.
+- drop udev-rules-add-nitrokey3.patch which is now upstream
+
+-------------------------------------------------------------------

Old:
----
  libnitrokey-v3.6.tar.gz
  libnitrokey-v3.6.tar.gz.sig
  udev-rules-add-nitrokey3.patch

New:
----
  libnitrokey-v3.7.tar.gz
  libnitrokey-v3.7.tar.gz.sig

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libnitrokey.spec ++++++
--- /var/tmp/diff_new_pack.TK50il/_old  2022-05-06 19:00:11.373412307 +0200
+++ /var/tmp/diff_new_pack.TK50il/_new  2022-05-06 19:00:11.381412317 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package libnitrokey
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2022 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,7 +12,7 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
@@ -22,7 +22,7 @@
 %define lib_name %{name}3
 
 Name:           libnitrokey
-Version:        3.6
+Version:        3.7
 Release:        0
 Summary:        Communicate with Nitrokey stick devices in a clean and easy 
manner
 License:        LGPL-3.0-only
@@ -30,7 +30,6 @@
 URL:            https://github.com/Nitrokey/libnitrokey
 Source:         
https://github.com/Nitrokey/libnitrokey/releases/download/v%{version}/libnitrokey-v%{version}.tar.gz
 Source1:        
https://github.com/Nitrokey/libnitrokey/releases/download/v%{version}/libnitrokey-v%{version}.tar.gz.sig
-Patch1:         udev-rules-add-nitrokey3.patch
 BuildRequires:  cmake
 BuildRequires:  gcc%{?force_gcc_version}-c++ >= 5
 %if 0%{?force_gcc_version}
@@ -38,9 +37,9 @@
 %endif
 BuildRequires:  pkgconfig
 BuildRequires:  python3-devel
-BuildRequires:  pkgconfig(udev)
 BuildRequires:  pkgconfig(hidapi-libusb)
 BuildRequires:  pkgconfig(libusb-1.0)
+BuildRequires:  pkgconfig(udev)
 
 %description
 Libnitrokey is a project to communicate with Nitrokey Pro and Storage devices
@@ -57,11 +56,11 @@
 
 This package holds the shared library.
 
-
 %package udev
 Summary:        udev rules for libnitrokey
 Group:          Development/Libraries/C and C++
 BuildArch:      noarch
+
 %description udev
 Libnitrokey is a project to communicate with Nitrokey Pro and Storage devices
 in a clean and easy manner.
@@ -81,7 +80,6 @@
 
 %prep
 %setup -q -n %{name}-v%{version}
-%patch1 -p1
 
 %build
 %if 0%{?force_gcc_version}

++++++ libnitrokey-v3.6.tar.gz -> libnitrokey-v3.7.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/.gitignore 
new/libnitrokey-v3.7/.gitignore
--- old/libnitrokey-v3.6/.gitignore     2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/.gitignore     2022-04-27 14:35:24.000000000 +0200
@@ -1,6 +1,7 @@
 *.sw*
 *.log
 *.o
+build
 unittest/build/
 unittest/.pytest_cache/
 *.pyc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/.gitlab-ci.yml 
new/libnitrokey-v3.7/.gitlab-ci.yml
--- old/libnitrokey-v3.6/.gitlab-ci.yml 1970-01-01 01:00:00.000000000 +0100
+++ new/libnitrokey-v3.7/.gitlab-ci.yml 2022-04-27 14:35:24.000000000 +0200
@@ -0,0 +1,75 @@
+include: 
'https://raw.githubusercontent.com/Nitrokey/common-ci-jobs/master/common_jobs.yml'
+
+stages:
+  - pull-github
+  - fetch
+  - build
+  - deploy
+
+variables:
+  #Repo for shared scripts (pull.sh release.sh, nightly_upload.sh):
+  GIT_STRATEGY: clone     #This seems to have no effect also set in 
webinterface
+  GIT_DEPTH: 0          #This seems to have no effect also set in webinterface
+  GIT_SUBMODULE_STRATEGY: recursive #This seems to have no effect also set in 
webinterfac
+  SCRIPTS_REPO: g...@git.dotplex.com:nitrokey/gitlab-ci.git
+  REPO_USER: nitrokey
+  REPO_NAME: libnitrokey
+  MAIN_BRANCH: master
+  COMMON_PULL: "true"
+  COMMON_UPLOAD_NIGHTLY: "true"
+  COMMON_GITHUB_RELEASE: "true"
+  COMMON_UPLOAD_FILES: "false"
+
+fetch-and-package:
+  image: 
registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc8:latest
+  rules:
+    - if: '$CI_PIPELINE_SOURCE == "push"'
+    - if: '$CI_PIPELINE_SOURCE == "schedule"'
+    - if: '$CI_PIPELINE_SOURCE == "web"'
+
+  tags:
+    - docker
+  stage: fetch
+  script: 
+    - ci-script/package.sh
+  after_script:
+    - wget 
$icon_server/checkmark/$CI_COMMIT_REF_NAME/$CI_COMMIT_SHA/$CI_JOB_NAME/$CI_JOB_STATUS/${CI_JOB_URL#*/*/*/}
+  artifacts:
+    paths:
+      - artifacts
+      - libnitrokey-source-metadata
+    expire_in: 2 weeks
+
+.build:
+  rules:
+    - if: '$CI_PIPELINE_SOURCE == "push"'
+    - if: '$CI_PIPELINE_SOURCE == "schedule"'
+    - if: '$CI_PIPELINE_SOURCE == "web"'
+  tags:
+    - docker
+  stage: build
+  script: 
+    - ci-script/build.sh
+  after_script:
+    - wget 
$icon_server/checkmark/$CI_COMMIT_REF_NAME/$CI_COMMIT_SHA/$CI_JOB_NAME/$CI_JOB_STATUS/${CI_JOB_URL#*/*/*/}
+
+build-bionic-gcc8:
+  extends: .build
+  image: 
registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc8:latest
+
+build-bionic-gcc7:
+  extends: .build
+  image: 
registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc7:latest
+
+build-bionic-gcc6:
+  extends: .build
+  image: 
registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc6:latest
+
+build-bionic-gcc5:
+  extends: .build
+  image: 
registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc5:latest
+
+build-bionic-llvm7:
+  extends: .build
+  image: 
registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-llvm7:latest
+    
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/CMakeLists.txt 
new/libnitrokey-v3.7/CMakeLists.txt
--- old/libnitrokey-v3.6/CMakeLists.txt 2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/CMakeLists.txt 2022-04-27 14:35:24.000000000 +0200
@@ -21,7 +21,7 @@
     ENDIF()
 ENDIF()
 
-project(libnitrokey LANGUAGES C CXX VERSION 3.6.0)
+project(libnitrokey LANGUAGES C CXX VERSION 3.7.0)
 set(CMAKE_CXX_STANDARD 14)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_CXX_EXTENSIONS OFF)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/NK_C_API.cc 
new/libnitrokey-v3.7/NK_C_API.cc
--- old/libnitrokey-v3.6/NK_C_API.cc    2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/NK_C_API.cc    2022-04-27 14:35:24.000000000 +0200
@@ -426,6 +426,14 @@
                m->set_loglevel(level);
        }
 
+       NK_C_API void NK_set_log_function(NK_log_function fn) {
+               auto m = NitrokeyManager::instance();
+               std::function<void(const std::string&, Loglevel)> log_function 
= [fn](auto s, auto lvl) {
+                   fn(static_cast<int>(lvl), s.c_str());
+               };
+               m->set_log_function_raw(log_function);
+        }
+
        NK_C_API unsigned int NK_get_major_library_version() {
                return get_major_library_version();
        }
@@ -929,6 +937,20 @@
     });
   }
 
+  NK_C_API int NK_get_random(const uint8_t len, struct GetRandom_t *out){
+    if (out == nullptr) return -1;
+    auto m = NitrokeyManager::instance();
+    auto result = get_with_status([&]() {
+            return m->get_random_pro(len);
+        }, stick10::GetRandom::ResponsePayload());
+    auto error_code = std::get<0>(result);
+    if (error_code != 0) {
+        return error_code;
+    }
+    auto data = std::get<1>(result);
+    memmove(out, reinterpret_cast<void *>(&data), sizeof(struct GetRandom_t));
+    return 0;
+  }
 
   NK_C_API int NK_read_HOTP_slot(const uint8_t slot_num, struct ReadSlot_t* 
out){
   if (out == nullptr)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/NK_C_API.h 
new/libnitrokey-v3.7/NK_C_API.h
--- old/libnitrokey-v3.6/NK_C_API.h     2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/NK_C_API.h     2022-04-27 14:35:24.000000000 +0200
@@ -41,7 +41,7 @@
  * \mainpage
  *
  * **libnitrokey** provides access to Nitrokey Pro and Nitrokey Storage 
devices.
- * This documentation describes libnitrokey???s C API.  For a list of the
+ * This documentation describes libnitrokey's C API.  For a list of the
  * available functions, see the NK_C_API.h file.
  *
  * \section getting_started Example
@@ -331,6 +331,21 @@
        NK_C_API void NK_set_debug_level(const int level);
 
        /**
+        * Callback function for NK_set_log_function.  The first argument is
+        * the log level (0 = Error, 1 = Warn, 2 = Info, 3 = DebugL1,
+        * 4 = Debug, 5 = DebugL2) and the second argument is the log message.
+        */
+    NK_C_API typedef void (*NK_log_function)(int, const char*);
+
+       /**
+        * Set a custom log function.
+        *
+        * The log function is called for every log message that matches the
+        * log level settings (see NK_set_debug and NK_set_debug_level).
+        */
+       NK_C_API void NK_set_log_function(NK_log_function fn);
+
+       /**
         * Get the major library version, e. g. the 3 in v3.2.
         * @return the major library version
         */
@@ -974,7 +989,7 @@
        /**
         * Get SD card usage attributes. Usable during hidden volumes creation.
         * If the command was successful (return value 0), the usage data is
-        * written to the output pointer???s target.  The output pointer must
+        * written to the output pointer's target.  The output pointer must
         * not be null.
         * Storage only
         * @param out the output pointer for the usage data
@@ -1080,6 +1095,13 @@
   uint64_t slot_counter;
 };
 
+struct GetRandom_t {
+    uint8_t op_success;
+    uint8_t size_effective;
+    uint8_t data[51];
+};
+
+NK_C_API int NK_get_random(const uint8_t len, struct GetRandom_t *out);
 
 NK_C_API int NK_read_HOTP_slot(const uint8_t slot_num, struct ReadSlot_t* out);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/NitrokeyManager.cc 
new/libnitrokey-v3.7/NitrokeyManager.cc
--- old/libnitrokey-v3.6/NitrokeyManager.cc     2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/NitrokeyManager.cc     2022-04-27 14:35:24.000000000 
+0200
@@ -217,44 +217,44 @@
             }
         }
 
-       auto vendor_id = NITROKEY_VID;
-        auto info_ptr = hid_enumerate(vendor_id, 0);
-       if (!info_ptr) {
-         vendor_id = PURISM_VID;
-         info_ptr = hid_enumerate(vendor_id, 0);
-       }
-        auto first_info_ptr = info_ptr;
-        if (!info_ptr)
-          return false;
+        auto vendor_ids = { NITROKEY_VID, PURISM_VID };
+        for (auto vendor_id : vendor_ids) {
+          auto info_ptr = hid_enumerate(vendor_id, 0);
+          if (!info_ptr) {
+            continue;
+          }
+          auto first_info_ptr = info_ptr;
 
-        misc::Option<DeviceModel> model;
-        while (info_ptr && !model.has_value()) {
-            if (path == std::string(info_ptr->path)) {
-                model = product_id_to_model(info_ptr->vendor_id, 
info_ptr->product_id);
-            }
-            info_ptr = info_ptr->next;
-        }
-        hid_free_enumeration(first_info_ptr);
+          misc::Option<DeviceModel> model;
+          while (info_ptr && !model.has_value()) {
+              if (path == std::string(info_ptr->path)) {
+                  model = product_id_to_model(info_ptr->vendor_id, 
info_ptr->product_id);
+              }
+              info_ptr = info_ptr->next;
+          }
+          hid_free_enumeration(first_info_ptr);
 
-        if (!model.has_value())
-            return false;
+          if (!model.has_value())
+            continue;
 
-        auto p = Device::create(model.value());
-        if (!p)
-            return false;
-        p->set_path(path);
+          auto p = Device::create(model.value());
+          if (!p)
+            continue;
+          p->set_path(path);
 
-        if(!p->connect()) return false;
+          if(!p->connect()) continue;
 
-        if(cache_connections){
-            connected_devices [path] = p;
-        }
+          if(cache_connections){
+              connected_devices [path] = p;
+          }
 
-        device = p; //previous device will be disconnected automatically
-        current_device_id = path;
-        nitrokey::log::Log::setPrefix(path);
-        LOGD1("Device successfully changed");
-        return true;
+          device = p; //previous device will be disconnected automatically
+          current_device_id = path;
+          nitrokey::log::Log::setPrefix(path);
+          LOGD1("Device successfully changed");
+          return true;
+        }
+        return false;
     }
 
     bool NitrokeyManager::connect() {
@@ -273,10 +273,17 @@
 
 
     void NitrokeyManager::set_log_function(std::function<void(std::string)> 
log_function){
+      // FIXME use move and pass to log handler, instead of keeping here 
static variable
       static nitrokey::log::FunctionalLogHandler handler(log_function);
       nitrokey::log::Log::instance().set_handler(&handler);
     }
 
+    void NitrokeyManager::set_log_function_raw(std::function<void(const 
std::string&, Loglevel)> log_function) {
+      // FIXME use move and pass to log handler, instead of keeping here 
static variable
+      static nitrokey::log::RawFunctionalLogHandler handler(log_function);
+      nitrokey::log::Log::instance().set_handler(&handler);
+    }
+
     bool NitrokeyManager::set_default_commands_delay(int delay){
       if (delay < 20){
         LOG("Delay set too low: " + to_string(delay), Loglevel::WARNING);
@@ -571,6 +578,7 @@
     void NitrokeyManager::write_HOTP_slot_authorize(uint8_t slot_number, const 
char *slot_name, const char *secret,
                                                     uint64_t hotp_counter, 
bool use_8_digits, bool use_enter,
                                                     bool use_tokenID, const 
char *token_ID, const char *temporary_password) {
+      if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
       auto payload = get_payload<WriteToHOTPSlot>();
       payload.slot_number = slot_number;
       auto secret_bin = misc::hex_string_to_byte(secret);
@@ -739,6 +747,7 @@
 
     template <typename ProCommand, PasswordKind StoKind>
     void NitrokeyManager::change_PIN_general(const char *current_PIN, const 
char *new_PIN) {
+        if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
         switch (device->get_device_model()){
             case DeviceModel::LIBREM:
             case DeviceModel::PRO:
@@ -786,6 +795,7 @@
     }
 
     uint8_t NitrokeyManager::get_user_retry_count() {
+        if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
         if(device->get_device_model() == DeviceModel::STORAGE){
           stick20::GetDeviceStatus::CommandTransaction::run(device);
         }
@@ -794,6 +804,7 @@
     }
 
     uint8_t NitrokeyManager::get_admin_retry_count() {
+        if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
         if(device->get_device_model() == DeviceModel::STORAGE){
           stick20::GetDeviceStatus::CommandTransaction::run(device);
         }
@@ -861,6 +872,7 @@
     }
 
     void NitrokeyManager::build_aes_key(const char *admin_password) {
+        if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
         switch (device->get_device_model()) {
             case DeviceModel::LIBREM:
             case DeviceModel::PRO: {
@@ -886,6 +898,7 @@
     }
 
     void NitrokeyManager::unlock_user_password(const char *admin_password, 
const char *new_user_password) {
+      if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
       switch (device->get_device_model()){
         case DeviceModel::LIBREM:
         case DeviceModel::PRO: {
@@ -934,6 +947,7 @@
     }
 
     bool NitrokeyManager::is_authorization_command_supported(){
+        if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
       //authorization command is supported for versions equal or below:
         auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({
                                                {DeviceModel::PRO, 7},
@@ -944,6 +958,7 @@
     }
 
     bool NitrokeyManager::is_320_OTP_secret_supported(){
+        if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
         // 320 bit OTP secret is supported by version bigger or equal to:
         auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({
                                                {DeviceModel::PRO, 8},
@@ -971,6 +986,7 @@
     }
 
     uint8_t NitrokeyManager::get_minor_firmware_version(){
+      if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
       switch(device->get_device_model()){
         case DeviceModel::LIBREM:
         case DeviceModel::PRO:{
@@ -988,6 +1004,7 @@
       return 0;
     }
     uint8_t NitrokeyManager::get_major_firmware_version(){
+      if (device == nullptr) { throw DeviceNotConnected("device not 
connected"); }
       switch(device->get_device_model()){
         case DeviceModel::LIBREM:
         case DeviceModel::PRO:{
@@ -1163,6 +1180,7 @@
    * @return ReadSlot structure
    */
   stick10::ReadSlot::ResponsePayload NitrokeyManager::get_OTP_slot_data(const 
uint8_t slot_number) {
+    if (device == nullptr) { throw DeviceNotConnected("device not connected"); 
}
     auto p = get_payload<stick10::ReadSlot>();
     p.slot_number = slot_number;
     p.data_format = stick10::ReadSlot::CounterFormat::BINARY; // ignored for 
devices other than Storage v0.54+
@@ -1222,6 +1240,13 @@
     FirmwareUpdate::CommandTransaction::run(device, p);
   }
 
+  GetRandom::ResponsePayload NitrokeyManager::get_random_pro(uint8_t 
size_requested) {
+    auto p = get_payload<GetRandom>();
+    p.size_requested = size_requested;
+    auto data = GetRandom::CommandTransaction::run(device, p);
+    return data.data();
+  }
+
   void
   NitrokeyManager::change_firmware_update_password_pro(const char 
*firmware_pin_current, const char *firmware_pin_new) {
     auto p = get_payload<FirmwarePasswordChange>();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/README.md 
new/libnitrokey-v3.7/README.md
--- old/libnitrokey-v3.6/README.md      2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/README.md      2022-04-27 14:35:24.000000000 +0200
@@ -1,5 +1,5 @@
 [![Build 
Status](https://travis-ci.org/Nitrokey/libnitrokey.svg?branch=master)](https://travis-ci.org/Nitrokey/libnitrokey)
-[![Waffle.io - Columns and their card 
count](https://badge.waffle.io/Nitrokey/libnitrokey.svg?columns=ready,in%20progress,test,waiting%20for%20feedback)](https://waffle.io/Nitrokey/libnitrokey)
+[![FOSSA 
Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FNitrokey%2Flibnitrokey.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FNitrokey%2Flibnitrokey?ref=badge_shield)
 
 # libnitrokey
 libnitrokey is a project to communicate with Nitrokey Pro and Storage devices 
in a clean and easy manner. Written in C++14, testable with `py.test` and 
`Catch` frameworks, with C API, Python access (through CFFI and C API, in 
future with Pybind11).
@@ -8,7 +8,7 @@
 
 A C++14 complying compiler is required due to heavy use of variable templates. 
For feature support tables please check [table 
1](https://gcc.gnu.org/projects/cxx-status.html#cxx14) or [table 
2](http://en.cppreference.com/w/cpp/compiler_support).
 
-libnitrokey is developed and tested with the latest compilers: g++ 6.2, clang 
3.8. We use Travis CI to test builds also on g++ 5.4 and under OSX compilers 
starting up from xcode 8.2 environment. 
+libnitrokey is developed and tested with a variety of compilers, starting from 
g++ 6.2 and clang 3.8. We use Travis CI to test builds also on g++ 5.4 and 
under OSX compilers starting up from xcode 9 environment. 
 
 ## Getting sources
 This repository uses `git submodules`.
@@ -93,6 +93,11 @@
 ```
 ## Python2
 Just import it, read the C API header and it is done! You have access to the 
library. Here is an example (in Python 2) printing HOTP code for Pro or Storage 
device, assuming it is run in root directory [(full 
example)](python_bindings_example.py):
+
+<details>
+    <summary>Code snippet (click to show)</summary>
+
+
 ```python
 #!/usr/bin/env python2
 import cffi
@@ -159,11 +164,19 @@
 print(hotp_slot_code)
 libnitrokey.NK_logout()  # disconnect device
 ```
+
+</details>
+
 In case  no devices are connected, a friendly message will be printed.
 All available functions for C and Python are listed in 
[NK_C_API.h](NK_C_API.h). Please check `Documentation` section below.
 
 ## Python3
 Just import it, read the C API header and it is done! You have access to the 
library. Here is an example (in Python 3) printing HOTP code for Pro or Storage 
device, assuming it is run in root directory [(full 
example)](python3_bindings_example.py):
+
+<details>
+    <summary>Code snippet (click to show)</summary>
+
+
 ```python
 #!/usr/bin/env python3
 import cffi
@@ -242,6 +255,8 @@
 libnitrokey.NK_logout()  # disconnect device
 ```
 
+</details>
+
 In case  no devices are connected, a friendly message will be printed.
 All available functions for C and Python are listed in 
[NK_C_API.h](NK_C_API.h). Please check `Documentation` section below.
 
@@ -280,6 +295,10 @@
 
 ## C++ tests
 There are also some unit tests implemented in C++, placed in unittest 
directory. The only user-data safe online test set here is 
[test_safe.cpp](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test_safe.cpp),
 which tries to connect to the device, and collect its status data. Example run 
for Storage:
+
+<details>
+    <summary>Log (click to show)</summary>
+
 ```text
 # Storage device inserted, firmware version v0.53
 $ ./test_safe
@@ -340,6 +359,10 @@
 ===============================================================================
 All tests passed (18 assertions in 6 test cases)
 ```
+
+</details>
+
+
 Test's execution configuration and verbosity could be manipulated - please see 
`./test_safe --help` for details.
 
 The other tests sets are not written as extensively as Python tests and are 
rather more a C++ low level interface check used during the library 
development, using either low-level components, C API from `NK_C_API.cc`, or 
C++ API from `NitrokeyManager.cc`. Some of them are: 
[test_HOTP.cc](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test_HOTP.cc),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/ci-script/build.sh 
new/libnitrokey-v3.7/ci-script/build.sh
--- old/libnitrokey-v3.6/ci-script/build.sh     1970-01-01 01:00:00.000000000 
+0100
+++ new/libnitrokey-v3.7/ci-script/build.sh     2022-04-27 14:35:24.000000000 
+0200
@@ -0,0 +1,31 @@
+#!/bin/bash
+set -exuo pipefail
+export
+
+. ./libnitrokey-source-metadata/metadata
+tar xf artifacts/${LIBNITROKEY_BUILD_OUTNAME}.tar.gz
+
+
+pushd ${LIBNITROKEY_BUILD_OUTNAME}
+pip3 install --user -r unittest/requirements.txt
+
+## This is quite sketchy but will work for now - we're using a tarball 
prepared by git archive, so we can't pull submodules.
+## Instead, we download the Catch2 header manually. The version is hardcoded, 
which is bad.
+## TODO: figure out a better way to handle that
+## One possibility is to install Catch2 system-wide on builder images.
+mkdir -p unittest/Catch/single_include/catch2
+curl -L -o unittest/Catch/single_include/catch2/catch.hpp 
https://github.com/catchorg/Catch2/releases/download/v2.3.0/catch.hpp
+
+mkdir build
+mkdir install
+
+pushd build
+cmake .. -DERROR_ON_WARNING=OFF -DCOMPILE_TESTS=ON 
+make -j2
+ctest -VV
+make install DESTDIR=../install
+popd
+
+pushd unittest
+python3 -m pytest -sv test_offline.py
+popd
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/ci-script/package.sh 
new/libnitrokey-v3.7/ci-script/package.sh
--- old/libnitrokey-v3.6/ci-script/package.sh   1970-01-01 01:00:00.000000000 
+0100
+++ new/libnitrokey-v3.7/ci-script/package.sh   2022-04-27 14:35:24.000000000 
+0200
@@ -0,0 +1,42 @@
+#!/bin/bash
+set -exuo pipefail
+export
+mkdir -p artifacts
+OUTDIR="$(realpath artifacts)"
+
+BASENAME="libnitrokey"
+#pushd libnitrokey
+
+VERSION="$(git describe --abbrev=0)"
+BUILD="${VERSION}.${CI_COMMIT_SHORT_SHA}"
+DATE="$(date -Iseconds)"
+case "${CI_PIPELINE_SOURCE}" in
+  push)
+    OUTNAME="${BASENAME}-${BUILD}"
+    ;;
+  schedule)
+    OUTNAME="${BASENAME}-${DATE}"
+    ;;
+  web)
+    OUTNAME="${BASENAME}-${VERSION}"
+    ;;
+esac
+
+git archive --format tar --prefix ${OUTNAME}/ ${CI_COMMIT_SHA} | gzip > 
${OUTDIR}/${OUTNAME}.tar.gz
+echo size:
+gzip -l ${OUTDIR}/${OUTNAME}.tar.gz
+
+echo "LIBNITROKEY_BUILD_VERSION=\"${VERSION}\"" >> ./metadata
+echo "LIBNITROKEY_BUILD_ID=\"${BUILD}\"" >> ./metadata
+echo "LIBNITROKEY_BUILD_DATE=\"${DATE}\"" >> ./metadata
+echo "LIBNITROKEY_BUILD_TYPE=\"${CI_PIPELINE_SOURCE}\"" >> ./metadata
+echo "LIBNITROKEY_BUILD_OUTNAME=\"${OUTNAME}\"" >> ./metadata
+cat ./metadata
+pwd
+ls
+mkdir -p libnitrokey-source-metadata
+mv metadata libnitrokey-source-metadata/
+cat libnitrokey-source-metadata/metadata
+pushd ${OUTDIR}
+sha256sum *.tar.gz > SHA256SUM
+popd
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/command_id.cc 
new/libnitrokey-v3.7/command_id.cc
--- old/libnitrokey-v3.6/command_id.cc  2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/command_id.cc  2022-04-27 14:35:24.000000000 +0200
@@ -184,6 +184,8 @@
       return "SEND_OTP_DATA";
     case CommandID::WINK:
       return "WINK";
+    case CommandID::GET_RANDOM:
+      return "GET_RANDOM";
   }
   return "UNKNOWN";
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/data/41-nitrokey.rules 
new/libnitrokey-v3.7/data/41-nitrokey.rules
--- old/libnitrokey-v3.6/data/41-nitrokey.rules 2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/data/41-nitrokey.rules 2022-04-27 14:35:24.000000000 
+0200
@@ -31,7 +31,10 @@
 KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", 
ATTRS{idProduct}=="4287", TAG+="uaccess"
 # Nitrokey FIDO2
 KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", 
ATTRS{idProduct}=="42b1", TAG+="uaccess"
-
+# Nitrokey 3 NFC
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", 
ATTRS{idProduct}=="42b2", TAG+="uaccess"
+# Nitrokey 3 NFC Bootloader
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", 
ATTRS{idProduct}=="42dd", TAG+="uaccess"
 
 LABEL="u2f_end"
 
@@ -44,8 +47,12 @@
 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", 
ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess"
 ## Nitrokey Pro
 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4108", ENV{ID_SMARTCARD_READER}="1", 
ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess"
+## Nitrokey Pro Bootloader
+ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b4", TAG+="uaccess"
 ## Nitrokey Storage
 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4109", ENV{ID_SMARTCARD_READER}="1", 
ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess"
+## Nitrokey Storage Bootloader
+ATTR{idVendor}=="03eb", ATTR{idProduct}=="2ff1", TAG+="uaccess"
 ## Nitrokey Start
 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4211", ENV{ID_SMARTCARD_READER}="1", 
ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess"
 ## Nitrokey HSM
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/data/41-nitrokey_old.rules 
new/libnitrokey-v3.7/data/41-nitrokey_old.rules
--- old/libnitrokey-v3.6/data/41-nitrokey_old.rules     2020-09-19 
17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/data/41-nitrokey_old.rules     2022-04-27 
14:35:24.000000000 +0200
@@ -32,6 +32,11 @@
 KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", 
ATTRS{idProduct}=="4287", MODE="0660", GROUP+="plugdev"
 # Nitrokey FIDO2
 KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", 
ATTRS{idProduct}=="42b1", MODE="0660", GROUP+="plugdev"
+# Nitrokey 3 NFC
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", 
ATTRS{idProduct}=="42b2", MODE="0660", GROUP+="plugdev"
+# Nitrokey 3 NFC Bootloader
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", 
ATTRS{idProduct}=="42dd", MODE="0660", GROUP+="plugdev"
+
 LABEL="u2f_end"
 
 
@@ -43,8 +48,12 @@
 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", 
ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev"
 ## Nitrokey Pro
 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4108", ENV{ID_SMARTCARD_READER}="1", 
ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev"
+## Nitrokey Pro Bootloader
+ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b4", MODE="0660", 
GROUP+="plugdev"
 ## Nitrokey Storage
 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4109", ENV{ID_SMARTCARD_READER}="1", 
ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev"
+## Nitrokey Storage Bootloader
+ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff1", MODE="0660", 
GROUP+="plugdev"
 ## Nitrokey Start
 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4211", ENV{ID_SMARTCARD_READER}="1", 
ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev"
 ## Nitrokey HSM
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/device.cc 
new/libnitrokey-v3.7/device.cc
--- old/libnitrokey-v3.6/device.cc      2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/device.cc      2022-04-27 14:35:24.000000000 +0200
@@ -234,6 +234,7 @@
     auto pInfo_ = pInfo;
     while (pInfo != nullptr){
       if (pInfo->path == nullptr || pInfo->serial_number == nullptr) {
+      pInfo = pInfo->next;
       continue;
     }
     auto deviceModel = product_id_to_model(vendor_id, pInfo->product_id);
@@ -328,7 +329,7 @@
 }
 
 Stick10::Stick10():
-  Device(NITROKEY_VID, NITROKEY_PRO_PID, DeviceModel::PRO, 100ms, 5, 100ms)
+  Device(NITROKEY_VID, NITROKEY_PRO_PID, DeviceModel::PRO, 100ms, 15, 100ms)
   {
     setDefaultDelay();
   }
@@ -342,7 +343,7 @@
 
 
 LibremKey::LibremKey():
-  Device(PURISM_VID, LIBREM_KEY_PID, DeviceModel::LIBREM, 100ms, 5, 100ms)
+  Device(PURISM_VID, LIBREM_KEY_PID, DeviceModel::LIBREM, 100ms, 15, 100ms)
   {
     setDefaultDelay();
   }
@@ -354,6 +355,10 @@
   return "";
 #endif
 
+  if (total_comm_runs == 0) {
+      return "Statistics: no connection run";
+  }
+
   std::stringstream ss;
   p(total_comm_runs);
   p(communication_successful);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libnitrokey-v3.6/libnitrokey/DeviceCommunicationExceptions.h 
new/libnitrokey-v3.7/libnitrokey/DeviceCommunicationExceptions.h
--- old/libnitrokey-v3.6/libnitrokey/DeviceCommunicationExceptions.h    
2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/libnitrokey/DeviceCommunicationExceptions.h    
2022-04-27 14:35:24.000000000 +0200
@@ -34,7 +34,7 @@
   std::string message;
   static std::atomic_int occurred;
 public:
-  DeviceCommunicationException(std::string _msg): std::runtime_error(_msg), 
message(_msg){
+  explicit DeviceCommunicationException(const std::string& _msg): 
std::runtime_error(_msg), message(_msg){
     ++occurred;
   }
   uint8_t getType() const {return 1;};
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/NitrokeyManager.h 
new/libnitrokey-v3.7/libnitrokey/NitrokeyManager.h
--- old/libnitrokey-v3.6/libnitrokey/NitrokeyManager.h  2020-09-19 
17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/libnitrokey/NitrokeyManager.h  2022-04-27 
14:35:24.000000000 +0200
@@ -211,7 +211,7 @@
 
         int get_progress_bar_value();
 
-        ~NitrokeyManager();
+        virtual ~NitrokeyManager();
         bool is_authorization_command_supported();
         bool is_320_OTP_secret_supported();
 
@@ -222,6 +222,7 @@
 
         explicit NitrokeyManager();
         void set_log_function(std::function<void(std::string)> log_function);
+        void set_log_function_raw(std::function<void(const std::string&, 
Loglevel)> log_function);
     private:
 
         static shared_ptr <NitrokeyManager> _instance;
@@ -303,6 +304,8 @@
 
       void change_firmware_update_password_pro(const char 
*firmware_pin_current, const char *firmware_pin_new);
       bool is_internal_hotp_slot_number(uint8_t slot_number) const;
+
+        GetRandom::ResponsePayload get_random_pro(uint8_t size_requested);
     };
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/command.h 
new/libnitrokey-v3.7/libnitrokey/command.h
--- old/libnitrokey-v3.6/libnitrokey/command.h  2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/libnitrokey/command.h  2022-04-27 14:35:24.000000000 
+0200
@@ -26,6 +26,7 @@
 #include "cxx_semantics.h"
 
 #define print_to_ss(x) ( ss << " " << (#x) <<":\t" << (x) << std::endl );
+#define print_to_ss_int(x) ( ss << " " << (#x) <<":\t" << (int)(x) << 
std::endl );
 #ifdef LOG_VOLATILE_DATA
 #define print_to_ss_volatile(x) print_to_ss(x);
 #else
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/command_id.h 
new/libnitrokey-v3.7/libnitrokey/command_id.h
--- old/libnitrokey-v3.6/libnitrokey/command_id.h       2020-09-19 
17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/libnitrokey/command_id.h       2022-04-27 
14:35:24.000000000 +0200
@@ -90,6 +90,7 @@
   SEND_OTP_DATA = 0x17,
   FIRMWARE_UPDATE = 0x19,
   FIRMWARE_PASSWORD_CHANGE = 0x1A,
+  GET_RANDOM = 0x1B,
 
   ENABLE_CRYPTED_PARI = 0x20,
   DISABLE_CRYPTED_PARI = 0x20 + 1,
@@ -147,7 +148,7 @@
   PW_SAFE_SEND_DATA = 0x69, //@unused
   SD_CARD_HIGH_WATERMARK = 0x70,
   DETECT_SC_AES = 0x6a,
-  NEW_AES_KEY = 0x6b
+  NEW_AES_KEY = 0x6b,
 };
 
 const char *commandid_to_string(CommandID id);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/device.h 
new/libnitrokey-v3.7/libnitrokey/device.h
--- old/libnitrokey-v3.6/libnitrokey/device.h   2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/libnitrokey/device.h   2022-04-27 14:35:24.000000000 
+0200
@@ -141,8 +141,8 @@
 
   // lack of device is not actually an error,
   // so it doesn't throw
-  virtual bool connect();
-  virtual bool disconnect();
+  bool connect();
+  bool disconnect();
 
   /*
    *   Sends packet of HID_REPORT_SIZE.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/device_proto.h 
new/libnitrokey-v3.7/libnitrokey/device_proto.h
--- old/libnitrokey-v3.6/libnitrokey/device_proto.h     2020-09-19 
17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/libnitrokey/device_proto.h     2022-04-27 
14:35:24.000000000 +0200
@@ -187,7 +187,7 @@
               bzero(&p, sizeof(p));
             }
 
-            ~ClearingProxy() {
+            virtual ~ClearingProxy() {
               bzero(&packet, sizeof(packet));
             }
 
@@ -244,7 +244,7 @@
               LOG(__FUNCTION__, Loglevel::DEBUG_L2);
 
               if (dev == nullptr){
-                LOG(std::string("Throw: Device not initialized"), 
Loglevel::DEBUG_L1);
+                LOG(std::string("Connection not established yet"), 
Loglevel::DEBUG_L2);
                 throw DeviceNotConnected("Device not initialized");
               }
               dev->m_counters.total_comm_runs++;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/log.h 
new/libnitrokey-v3.7/libnitrokey/log.h
--- old/libnitrokey-v3.6/libnitrokey/log.h      2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/libnitrokey/log.h      2022-04-27 14:35:24.000000000 
+0200
@@ -67,6 +67,15 @@
 
     };
 
+    class RawFunctionalLogHandler : public LogHandler {
+      using log_function_type = std::function<void(const std::string&, 
Loglevel lvl)>;
+      log_function_type log_function;
+    public:
+      RawFunctionalLogHandler(log_function_type _log_function);
+      virtual void print(const std::string &, Loglevel lvl);
+
+    };
+
     extern StdlogHandler stdlog_handler;
 
     class Log {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/stick10_commands.h 
new/libnitrokey-v3.7/libnitrokey/stick10_commands.h
--- old/libnitrokey-v3.6/libnitrokey/stick10_commands.h 2020-09-19 
17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/libnitrokey/stick10_commands.h 2022-04-27 
14:35:24.000000000 +0200
@@ -887,6 +887,41 @@
 
 };
 
+class GetRandom : Command<CommandID::GET_RANDOM> {
+ public:
+    static const int DATA_SIZE_MAX = 64 - 13;
+    struct CommandPayload {
+        uint8_t size_requested;
+
+        bool isValid() const { return size_requested < DATA_SIZE_MAX; }
+        std::string dissect() const {
+            std::stringstream ss;
+            print_to_ss_int(size_requested);
+            return ss.str();
+        }
+    } __packed;
+
+    struct ResponsePayload {
+        uint8_t op_success;
+        uint8_t size_effective;
+        uint8_t data[DATA_SIZE_MAX];
+
+        bool isValid() const { return true; }
+        std::string dissect() const {
+            std::stringstream ss;
+            print_to_ss_int(op_success);
+            print_to_ss_int(size_effective);
+            hexdump_to_ss(data);
+            return ss.str();
+        }
+    } __packed;
+
+
+    typedef Transaction<command_id(), struct CommandPayload, struct 
ResponsePayload>
+      CommandTransaction;
+
+};
+
 class FirmwareUpdate : Command<CommandID::FIRMWARE_UPDATE> {
 public:
   struct CommandPayload {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey.pro 
new/libnitrokey-v3.7/libnitrokey.pro
--- old/libnitrokey-v3.6/libnitrokey.pro        2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/libnitrokey.pro        2022-04-27 14:35:24.000000000 
+0200
@@ -7,11 +7,14 @@
 TEMPLATE     = lib
 TARGET = nitrokey
 
-VERSION = 3.6.0
+VERSION = 3.7.0
+LIBNK_VERSION_MAJOR = 3
+LIBNK_VERSION_MINOR = 7
+
 QMAKE_TARGET_COMPANY = Nitrokey
 QMAKE_TARGET_PRODUCT = libnitrokey
 QMAKE_TARGET_DESCRIPTION = Communicate with Nitrokey stick devices in a clean 
and easy manner
-QMAKE_TARGET_COPYRIGHT = Copyright (c) 2015-2020 Nitrokey Gmbh
+QMAKE_TARGET_COPYRIGHT = Copyright (c) 2015-2022 Nitrokey Gmbh
 
 HEADERS = \
    $$PWD/hidapi/hidapi/hidapi.h \
@@ -79,8 +82,6 @@
     $$PWD/unittest \
     $$PWD/unittest/Catch/single_include
 
-#DEFINES = 
-
 unix:!macx{
         # Install rules for QMake (CMake is preffered though)
         udevrules.path = $$system(pkg-config --variable=udevdir udev)
@@ -100,3 +101,7 @@
         libbin.path = /usr/local/lib
         INSTALLS += libbin
 }
+
+DEFINES += LIBNK_GIT_VERSION=\"\\\"$$system(git describe --abbrev=4 
--always)\\\"\" LIBNK_VERSION="\\\"$${VERSION}\\\""
+DEFINES += LIBNK_VERSION_MAJOR=$${LIBNK_VERSION_MAJOR} 
LIBNK_VERSION_MINOR=$${LIBNK_VERSION_MINOR}
+message($$DEFINES)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/log.cc new/libnitrokey-v3.7/log.cc
--- old/libnitrokey-v3.6/log.cc 2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/log.cc 2022-04-27 14:35:24.000000000 +0200
@@ -55,6 +55,7 @@
 
     void Log::operator()(const std::string &logstr, Loglevel lvl) {
       if (mp_loghandler != nullptr){
+        // FIXME crashes on exit because static object under mp_loghandler is 
not valid anymore, see NitrokeyManager::set_log_function
         if ((int) lvl <= (int) m_loglevel) mp_loghandler->print(prefix+logstr, 
lvl);
       }
     }
@@ -77,6 +78,10 @@
       log_function(s);
     }
 
+    void RawFunctionalLogHandler::print(const std::string &str, Loglevel lvl) {
+      log_function(str, lvl);
+    }
+
     std::string LogHandler::format_message_to_string(const std::string &str, 
const Loglevel &lvl) {
       static bool last_short = false;
       if (str.length() == 1){
@@ -99,5 +104,9 @@
     FunctionalLogHandler::FunctionalLogHandler(log_function_type 
_log_function) {
       log_function = _log_function;
     }
+
+    RawFunctionalLogHandler::RawFunctionalLogHandler(log_function_type 
_log_function) {
+      log_function = _log_function;
+    }
   }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/meson.build 
new/libnitrokey-v3.7/meson.build
--- old/libnitrokey-v3.6/meson.build    2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/meson.build    2022-04-27 14:35:24.000000000 +0200
@@ -1,6 +1,6 @@
 project(
   'libnitrokey', 'cpp',
-  version : '3.6.0-rc1',
+  version : '3.7.0',
   license : 'LGPL-3.0+',
   default_options : [
     'cpp_std=c++14'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/unittest/conftest.py 
new/libnitrokey-v3.7/unittest/conftest.py
--- old/libnitrokey-v3.6/unittest/conftest.py   2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/unittest/conftest.py   2022-04-27 14:35:24.000000000 
+0200
@@ -76,8 +76,11 @@
     return get_library(request, allow_offline=True)
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture(scope="session")
 def C(request=None):
+    import platform
+    print(f"Python version: {platform.python_version()}")
+    print(f"OS: {platform.system()} {platform.release()} {platform.version()}")
     print("Getting library with connection initialized")
     return get_library(request)
 
@@ -164,6 +167,9 @@
 
 
 def pytest_addoption(parser):
+    parser.addoption(
+        "--runslow", action="store_true", default=False, help="run slow tests"
+    )
     parser.addoption("--run-skipped", action="store_true",
                      help="run the tests skipped by default, e.g. adding side 
effects")
 
@@ -171,6 +177,10 @@
     if 'skip_by_default' in item.keywords and not 
item.config.getoption("--run-skipped"):
         pytest.skip("need --run-skipped option to run this test")
 
+def pytest_configure(config):
+    config.addinivalue_line("markers", "slow: mark test as slow to run")
+
+
 
 def library_device_reconnect(C):
     C.NK_logout()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/unittest/constants.py 
new/libnitrokey-v3.7/unittest/constants.py
--- old/libnitrokey-v3.6/unittest/constants.py  2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/unittest/constants.py  2022-04-27 14:35:24.000000000 
+0200
@@ -18,7 +18,11 @@
 
 SPDX-License-Identifier: LGPL-3.0
 """
+from enum import Enum
+from sys import stderr
+
 from misc import to_hex, bb
+from conftest import print
 
 RFC_SECRET_HR = '12345678901234567890'
 RFC_SECRET = to_hex(RFC_SECRET_HR)  # '31323334353637383930...'
@@ -39,23 +43,52 @@
     UPDATE_TOO_SHORT = UPDATE_LONG[:7]
 
 
-class DeviceErrorCode:
+class DeviceErrorCode(Enum):
     STATUS_OK = 0
     BUSY = 1 # busy or busy progressbar in place of wrong_CRC status
     NOT_PROGRAMMED = 3
     WRONG_PASSWORD = 4
     STATUS_NOT_AUTHORIZED = 5
-    STATUS_AES_DEC_FAILED = 0xa
+    STATUS_AES_DEC_FAILED = 10
+    STATUS_WRONG_SLOT = 2
+    STATUS_TIMESTAMP_WARNING = 6
+    STATUS_NO_NAME_ERROR = 7
+    STATUS_NOT_SUPPORTED = 8
+    STATUS_UNKNOWN_COMMAND = 9
+    STATUS_AES_CREATE_KEY_FAILED = 11
+    STATUS_ERROR_CHANGING_USER_PASSWORD = 12
+    STATUS_ERROR_CHANGING_ADMIN_PASSWORD = 13
+    STATUS_ERROR_UNBLOCKING_PIN = 14
+
     STATUS_UNKNOWN_ERROR = 100
     STATUS_DISCONNECTED = 255
 
+    def __eq__(self, other):
+        other_name = 'Unknown'
+        try:
+            other_name = str(DeviceErrorCode(other).name)
+        except:
+            pass
+        result = self.value == other
+        print(f'Returned {other_name}, expected {self.name} => {result}')
+        return result
 
-class LibraryErrors:
+class LibraryErrors(Enum):
     TOO_LONG_STRING = 200
     INVALID_SLOT = 201
     INVALID_HEX_STRING = 202
     TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE = 203
 
+    def __eq__(self, other):
+        other_name = 'Unknown'
+        try:
+            other_name = str(LibraryErrors(other).name)
+        except:
+            pass
+        result = self.value == other
+        print(f'Returned {other_name}, expected {self.name} => {result}')
+        return result
+
 
 HOTP_slot_count = 3
 TOTP_slot_count = 15
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/unittest/pyproject.toml 
new/libnitrokey-v3.7/unittest/pyproject.toml
--- old/libnitrokey-v3.6/unittest/pyproject.toml        1970-01-01 
01:00:00.000000000 +0100
+++ new/libnitrokey-v3.7/unittest/pyproject.toml        2022-04-27 
14:35:24.000000000 +0200
@@ -0,0 +1,22 @@
+[tool.pytest.ini_options]
+markers = [
+    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
+    "serial",
+    "aes",
+    "encrypted",
+    "factory_reset",
+    "firmware",
+    "hidden",
+    "info",
+    "lock_device",
+    "other",
+    "otp",
+    "pin",
+    "PWS",
+    "skip",
+    "skip_by_default",
+    "slowtest",
+    "status",
+    "unencrypted",
+    "update",
+]
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/unittest/requirements.txt 
new/libnitrokey-v3.7/unittest/requirements.txt
--- old/libnitrokey-v3.6/unittest/requirements.txt      2020-09-19 
17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/unittest/requirements.txt      2022-04-27 
14:35:24.000000000 +0200
@@ -4,3 +4,4 @@
 pytest-randomly
 tqdm
 oath
+hypothesis
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/unittest/test_issues.py 
new/libnitrokey-v3.7/unittest/test_issues.py
--- old/libnitrokey-v3.6/unittest/test_issues.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/libnitrokey-v3.7/unittest/test_issues.py        2022-04-27 
14:35:24.000000000 +0200
@@ -0,0 +1,96 @@
+from enum import Enum
+
+import pytest
+
+from conftest import skip_if_device_version_lower_than
+from constants import DefaultPasswords, DeviceErrorCode
+from misc import gs, ffi
+from test_pro import check_HOTP_RFC_codes, test_random
+
+
+def test_destroy_encrypted_data_leaves_OTP_intact(C):
+    """
+    Make sure AES key regeneration will not remove OTP records.
+    Test for Nitrokey Storage.
+    Details: https://github.com/Nitrokey/libnitrokey/issues/199
+    """
+    skip_if_device_version_lower_than({'S': 55})
+
+    assert C.NK_enable_password_safe(DefaultPasswords.USER) == 
DeviceErrorCode.STATUS_OK
+    # write password safe slot
+    assert C.NK_write_password_safe_slot(0, b'slotname1', b'login1', b'pass1') 
== DeviceErrorCode.STATUS_OK
+    # read slot
+    assert gs(C.NK_get_password_safe_slot_name(0)) == b'slotname1'
+    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+    slot_login = C.NK_get_password_safe_slot_login(0)
+    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+    assert gs(slot_login) == b'login1'
+
+    # write OTP
+    use_8_digits = False
+    use_pin_protection = False
+    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 
DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+    assert C.NK_write_config(255, 255, 255, use_pin_protection, not 
use_pin_protection,
+                             DefaultPasswords.ADMIN_TEMP) == 
DeviceErrorCode.STATUS_OK
+    check_HOTP_RFC_codes(C, lambda x: gs(C.NK_get_hotp_code(x)), 
use_8_digits=use_8_digits)
+
+    # confirm OTP
+    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 
DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+    assert gs(C.NK_get_hotp_slot_name(1)) == b'python_test'
+
+    # destroy password safe by regenerating aes key
+    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+
+    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 
DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+    assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == 
DeviceErrorCode.STATUS_OK
+    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+    assert C.NK_enable_password_safe(DefaultPasswords.USER) == 
DeviceErrorCode.STATUS_OK
+    assert gs(C.NK_get_password_safe_slot_name(0)) != b'slotname1'
+    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+
+    # confirm OTP
+    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 
DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+    assert gs(C.NK_get_hotp_slot_name(1)) == b'python_test'
+
+
+class Modes(Enum):
+    EmptyBody = 0
+    FactoryResetWithAES = 1
+    FactoryReset = 2
+    AESGeneration = 3
+
+@pytest.mark.firmware
+@pytest.mark.factory_reset
+@pytest.mark.parametrize("mode", map(Modes, reversed(range(4))))
+def test_pro_factory_reset_breaks_update_password(C, mode: Modes):
+    from test_pro_bootloader import test_bootloader_password_change_pro, 
test_bootloader_password_change_pro_length
+    from test_pro import test_factory_reset
+    skip_if_device_version_lower_than({'P': 14})
+
+    func = {
+        Modes.EmptyBody: lambda: True,
+        Modes.FactoryResetWithAES: lambda: test_factory_reset(C) or True,
+        Modes.FactoryReset: lambda: C.NK_factory_reset(DefaultPasswords.ADMIN) 
== DeviceErrorCode.STATUS_OK,
+        Modes.AESGeneration: lambda: 
C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK,
+    }
+
+    def boot_test(C):
+        test_bootloader_password_change_pro(C)
+        # test_bootloader_password_change_pro_length(C)
+
+    def random(C):
+        data = ffi.new('struct GetRandom_t *')
+        req_count = 50
+        res = C.NK_get_random(req_count, data)
+        assert res == DeviceErrorCode.STATUS_OK
+        assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+        assert data.op_success == 1
+        assert data.size_effective == req_count
+
+    random(C)
+    boot_test(C)
+    random(C)
+    func[mode]()
+    random(C)  # fails here
+    boot_test(C)
+    random(C)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/unittest/test_pro.py 
new/libnitrokey-v3.7/unittest/test_pro.py
--- old/libnitrokey-v3.6/unittest/test_pro.py   2020-09-19 17:11:29.000000000 
+0200
+++ new/libnitrokey-v3.7/unittest/test_pro.py   2022-04-27 14:35:24.000000000 
+0200
@@ -18,16 +18,25 @@
 
 SPDX-License-Identifier: LGPL-3.0
 """
+import time
+from binascii import hexlify
+from math import floor
 
 import pytest
 
 from conftest import skip_if_device_version_lower_than
-from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, 
bbRFC_SECRET, LibraryErrors, HOTP_slot_count, \
+from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, 
bbRFC_SECRET, HOTP_slot_count, \
     TOTP_slot_count
 from helpers import helper_PWS_get_slotname, helper_PWS_get_loginname, 
helper_PWS_get_pass
 from misc import ffi, gs, wait, cast_pointer_to_tuple, has_binary_counter, bb
 from misc import is_storage
 
+
+@pytest.mark.aes
+def test_regenerate_aes_key_2(C):
+    test_regenerate_aes_key(C)
+
+
 @pytest.mark.lock_device
 @pytest.mark.PWS
 def test_enable_password_safe(C):
@@ -161,9 +170,13 @@
     if is_storage(C):
         assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == 
DeviceErrorCode.STATUS_OK
     enable_password_safe_result = 
C.NK_enable_password_safe(DefaultPasswords.USER)
-    assert enable_password_safe_result == 
DeviceErrorCode.STATUS_AES_DEC_FAILED \
-           or is_storage(C) and enable_password_safe_result in \
-           [DeviceErrorCode.WRONG_PASSWORD, 
DeviceErrorCode.STATUS_UNKNOWN_ERROR]  # UNKNOWN_ERROR since v0.51
+    pro_case = enable_password_safe_result in 
[DeviceErrorCode.STATUS_AES_DEC_FAILED,
+                                               
DeviceErrorCode.STATUS_AES_CREATE_KEY_FAILED]  # STATUS_AES_CREATE_KEY_FAILED 
since v0.14
+    storage_case = enable_password_safe_result in 
[DeviceErrorCode.WRONG_PASSWORD,
+                                                   
DeviceErrorCode.STATUS_UNKNOWN_ERROR]  # UNKNOWN_ERROR since v0.51
+    assert not is_storage(C) and pro_case \
+           or is_storage(C) and storage_case
+    C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
     assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == 
DeviceErrorCode.STATUS_OK
     assert C.NK_enable_password_safe(DefaultPasswords.USER) == 
DeviceErrorCode.STATUS_OK
 
@@ -243,6 +256,7 @@
 @pytest.mark.lock_device
 @pytest.mark.pin
 def test_user_retry_counts_change_PIN(C):
+    # TODO can get device with AES key not set, and fail because of that - 
generate the key or reorder tests
     assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) 
== DeviceErrorCode.STATUS_OK
     wrong_password = b'wrong_password'
     default_user_retry_count = 3
@@ -659,13 +673,13 @@
     assert not config_st.disable_user_password
 
     # use structs: write
-    config_st.numlock = 3
+    config_st.numlock = 0xFF
     assert C.NK_write_config_struct(config_st[0], DefaultPasswords.ADMIN_TEMP) 
== DeviceErrorCode.STATUS_OK
 
     # use structs: read II
     err = C.NK_read_config_struct(config_st)
     assert err == 0
-    assert config_st.numlock == 3
+    assert config_st.numlock == 0xFF
     assert config_st.capslock == 1
     assert config_st.scrolllock == 2
     assert config_st.enable_user_password
@@ -1070,3 +1084,35 @@
         all_codes.append(this_loop_codes)
     from pprint import pprint
     pprint(all_codes)
+
+
+@pytest.mark.parametrize("count",[16, 32, 50, 51])
+def test_random(C, count):
+    skip_if_device_version_lower_than({'P': 14, 'S': 99})
+    data = ffi.new('struct GetRandom_t *')
+    req_count = count
+    res = C.NK_get_random(req_count, data)
+    assert res == DeviceErrorCode.STATUS_OK
+    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+    assert data.op_success == 1
+    assert data.size_effective == req_count
+    print(f'{hexlify(bytes(data.data))}')
+
+
+def test_random_collect(C):
+    skip_if_device_version_lower_than({'P': 14, 'S': 99})
+    collected = b''
+    data = ffi.new('struct GetRandom_t *')
+    req_count = 50
+    tic = time.perf_counter()
+    for i in range(1024//req_count+1):
+        res = C.NK_get_random(req_count, data)
+        assert res == DeviceErrorCode.STATUS_OK
+        assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+        assert data.op_success == 1
+        assert data.size_effective == req_count
+        collected += bytes(data.data)
+    toc = time.perf_counter()
+    assert len(collected) > 1024
+    print(hexlify(collected))
+    print(f'Time used to collect {len(collected)} bytes: {round(toc-tic,2)} -> 
{floor(len(collected) / (toc-tic))} Bps')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/unittest/test_pro_bootloader.py 
new/libnitrokey-v3.7/unittest/test_pro_bootloader.py
--- old/libnitrokey-v3.6/unittest/test_pro_bootloader.py        2020-09-19 
17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/unittest/test_pro_bootloader.py        2022-04-27 
14:35:24.000000000 +0200
@@ -60,8 +60,9 @@
 
 @pytest.mark.skip_by_default
 @pytest.mark.firmware
-def test_bootloader_data_rention(C):
+def test_bootloader_data_retention(C):
     skip_if_device_version_lower_than({'P': 11})
+    # Not enabled due to lack of side-effect removal at this point
 
     assert helper_populate_device(C)
     assert C.NK_enable_firmware_update_pro(DefaultPasswords.UPDATE) == 
DeviceErrorCode.STATUS_DISCONNECTED
@@ -69,3 +70,20 @@
     C = library_device_reconnect(C)
     assert helper_check_device_for_data(C)
 
+
+@pytest.mark.firmware
+def test_factory_reset_does_not_change_update_password(C):
+    """
+    Check if factory reset changes the update password, which should not happen
+    """
+    skip_if_device_version_lower_than({'P': 13})
+    from test_pro import test_factory_reset
+    # Revert effects of a broken test run, if needed
+    C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, 
DefaultPasswords.UPDATE)
+
+    # actual test
+    assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, 
DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK
+    test_factory_reset(C)
+    assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, 
DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD
+    assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, 
DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/unittest/test_storage.py 
new/libnitrokey-v3.7/unittest/test_storage.py
--- old/libnitrokey-v3.6/unittest/test_storage.py       2020-09-19 
17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/unittest/test_storage.py       2022-04-27 
14:35:24.000000000 +0200
@@ -18,9 +18,14 @@
 
 SPDX-License-Identifier: LGPL-3.0
 """
-
+import dataclasses
 import pprint
+import secrets
+from datetime import datetime, timedelta
+from time import sleep
+
 import pytest
+from hypothesis import given, strategies as st, settings, Verbosity, assume
 
 from conftest import skip_if_device_version_lower_than
 from constants import DefaultPasswords, DeviceErrorCode
@@ -43,6 +48,15 @@
     return d
 
 
+def get_status_storage(C):
+    status_pointer = C.NK_get_status_storage_as_string()
+    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+    status_string = gs(status_pointer)
+    assert len(status_string) > 0
+    status_dict = get_dict_from_dissect(status_string.decode('ascii'))
+    # assert int(status_dict['AdminPwRetryCount']) == 
default_admin_password_retry_count
+    return status_dict, C.NK_get_last_command_status()
+
 @pytest.mark.other
 @pytest.mark.info
 def test_get_status_storage(C):
@@ -350,17 +364,14 @@
     wrong_password = b'aaaaaaaaaaa'
     assert C.NK_change_update_password(wrong_password, 
DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD
     assert C.NK_change_update_password(DefaultPasswords.UPDATE, 
DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK
+    assert C.NK_enable_firmware_update(DefaultPasswords.UPDATE) == 
DeviceErrorCode.WRONG_PASSWORD
     assert C.NK_change_update_password(DefaultPasswords.UPDATE_TEMP, 
DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
 
-
-# @pytest.mark.skip(reason='no reversing method added yet')
+@pytest.mark.skip_by_default
 @pytest.mark.update
 def test_enable_firmware_update(C):
     skip_if_device_version_lower_than({'S': 50})
-    wrong_password = b'aaaaaaaaaaa'
-    assert C.NK_enable_firmware_update(wrong_password) == 
DeviceErrorCode.WRONG_PASSWORD
-    # skip actual test - reason: no reversing method added yet
-    # assert C.NK_enable_firmware_update(DefaultPasswords.UPDATE) == 
DeviceErrorCode.STATUS_OK
+    assert C.NK_enable_firmware_update(DefaultPasswords.UPDATE) == 
DeviceErrorCode.STATUS_OK
 
 
 @pytest.mark.other
@@ -396,6 +407,8 @@
         )
     print(info)
 
+
+@pytest.mark.skip(reason='additional test')
 @pytest.mark.other
 @pytest.mark.firmware
 def test_export_firmware_extended_fedora29(C):
@@ -574,8 +587,97 @@
     assert checks_add == checks
 
 
-def skip_if_not_macos(message:str) -> None:
+def skip_if_not_macos(message: str) -> None:
     import platform
 
     if platform.system() != 'Darwin':
         pytest.skip(message)
+
+
+from typing import Callable, Optional
+
+
+def helper_busy_wait(func: Callable, timeout=10) -> DeviceErrorCode:
+    start = datetime.now()
+    res = func()
+    while res == DeviceErrorCode.BUSY:
+        sleep(.5)
+        status_dict, res = get_status_storage(C)
+        print(status_dict)
+        if datetime.now() - start > timedelta(seconds=timeout):
+            print('Timeout, stopping')
+            return res
+    return res
+
+
+def helper_create_hidden_volume(C, slot, begin, end, password_in: 
Optional[bytes], should_work=True):
+    """
+    :param password_in:  if None, generate randomly per call
+    """
+    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+    assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == 
DeviceErrorCode.STATUS_OK
+
+    password = password_in
+    if password_in is None:
+        password = secrets.token_hex(5)
+        password = password.encode('ascii')
+    assert not should_work ^ (helper_busy_wait(
+        lambda: C.NK_create_hidden_volume(slot, begin, end, password)) == 
DeviceErrorCode.STATUS_OK)
+
+    assert not should_work ^ (
+            helper_busy_wait(lambda: C.NK_unlock_hidden_volume(password)) == 
DeviceErrorCode.STATUS_OK)
+    if should_work:
+        assert helper_busy_wait(lambda: C.NK_lock_hidden_volume()) == 
DeviceErrorCode.STATUS_OK
+
+
+@dataclasses.dataclass
+class MalformedParams:
+    slot: int
+    begin: int
+    end: int
+    password: Optional[bytes] = None
+    should_work: bool = True
+
+    def as_list(self):
+        return [self.slot, self.begin, self.end, self.password, 
self.should_work]
+
+@pytest.mark.hidden
+@pytest.mark.parametrize("args", [
+    # correct calls
+    MalformedParams(slot=0, begin=1, end=2),
+    MalformedParams(slot=0, begin=0, end=100),
+    MalformedParams(slot=0, begin=99, end=100),
+    MalformedParams(slot=0, begin=0, end=1),
+    MalformedParams(slot=0, begin=0, end=1),
+
+    # password not empty
+    # password not longer than 20 characters
+    MalformedParams(slot=0, begin=1, end=2, password=b"", should_work=False),
+    MalformedParams(slot=0, begin=1, end=2, password=b"a" * 21, 
should_work=False),
+    MalformedParams(slot=0, begin=1, end=2, password=b"a" * 20),
+
+    # slot 0..3
+    MalformedParams(slot=5, begin=1, end=2, should_work=False),
+    MalformedParams(slot=55, begin=1, end=2, should_work=False),
+    # end 1..100
+    MalformedParams(slot=0, begin=0, end=101, should_work=False),
+    # begin 0..99
+    MalformedParams(slot=0, begin=100, end=100, should_work=False),
+    MalformedParams(slot=0, begin=50, end=49, should_work=False),
+    MalformedParams(slot=0, begin=50, end=50, should_work=False),
+], ids=str)
+def test_hidden_volume_malformed_parameters(C, args):
+    skip_if_device_version_lower_than({'S': 56})
+    helper_create_hidden_volume(C, *args.as_list())
+
+
+# password=st.text(min_size=10, max_size=20) # disabled to avoid failed tests 
due to unlocking previously correctly
+# configured HV with the same password used in failing example
+@given(slot=st.integers(min_value=0, max_value=3), 
begin=st.integers(min_value=0, max_value=99),
+       end=st.integers(min_value=1, max_value=100))
+@settings(max_examples=15, deadline=timedelta(seconds=10), 
verbosity=Verbosity.verbose)
+def test_hypo_hidden_volume_setup(C, slot, begin, end):
+    assume(begin < end)
+    # password = password.encode('utf-8')
+    password = None
+    helper_create_hidden_volume(C, slot, begin, end, password, 
should_work=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libnitrokey-v3.6/version.cc 
new/libnitrokey-v3.7/version.cc
--- old/libnitrokey-v3.6/version.cc     2020-09-19 17:11:29.000000000 +0200
+++ new/libnitrokey-v3.7/version.cc     2022-04-27 14:35:24.000000000 +0200
@@ -23,10 +23,16 @@
 
 namespace nitrokey {
     unsigned int get_major_library_version() {
+#ifdef LIBNK_VERSION_MAJOR
+        return LIBNK_VERSION_MAJOR;
+#endif
         return 3;
     }
 
     unsigned int get_minor_library_version() {
+#ifdef LIBNK_VERSION_MINOR
+        return LIBNK_VERSION_MINOR;
+#endif
         return 0;
     }
 

Reply via email to