licentivo-launch 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +35 -0
  4. data/ext/launch_native/extconf.rb +28 -0
  5. data/lib/licentivo_launch/vendor/licentivo.rb +262 -0
  6. data/lib/licentivo_launch.rb +352 -0
  7. data/vendor/LICENSE +21 -0
  8. data/vendor/NOTICE +17 -0
  9. data/vendor/VERSION +1 -0
  10. data/vendor/c/app.c +33 -0
  11. data/vendor/c/demo.c +77 -0
  12. data/vendor/c/feature_tasks.h +72 -0
  13. data/vendor/c/licentivo.c +382 -0
  14. data/vendor/c/licentivo.h +67 -0
  15. data/vendor/c/quickstart.c +51 -0
  16. data/vendor/c/test_vectors.c +4 -0
  17. data/vendor/c/vendor/Monocypher-LICENCE.md +173 -0
  18. data/vendor/c/vendor/cJSON-LICENSE +20 -0
  19. data/vendor/c/vendor/cJSON.c +3191 -0
  20. data/vendor/c/vendor/cJSON.h +306 -0
  21. data/vendor/c/vendor/monocypher-ed25519.c +500 -0
  22. data/vendor/c/vendor/monocypher-ed25519.h +140 -0
  23. data/vendor/c/vendor/monocypher.c +2956 -0
  24. data/vendor/c/vendor/monocypher.h +321 -0
  25. data/vendor/curl/COPYING.txt +22 -0
  26. data/vendor/curl/bin/libcurl-x64.dll +0 -0
  27. data/vendor/curl/include/brotli/decode.h +409 -0
  28. data/vendor/curl/include/brotli/encode.h +505 -0
  29. data/vendor/curl/include/brotli/port.h +302 -0
  30. data/vendor/curl/include/brotli/shared_dictionary.h +100 -0
  31. data/vendor/curl/include/brotli/types.h +83 -0
  32. data/vendor/curl/include/curl/curl.h +3374 -0
  33. data/vendor/curl/include/curl/curlver.h +77 -0
  34. data/vendor/curl/include/curl/easy.h +123 -0
  35. data/vendor/curl/include/curl/header.h +74 -0
  36. data/vendor/curl/include/curl/mprintf.h +85 -0
  37. data/vendor/curl/include/curl/multi.h +561 -0
  38. data/vendor/curl/include/curl/options.h +70 -0
  39. data/vendor/curl/include/curl/stdcheaders.h +35 -0
  40. data/vendor/curl/include/curl/system.h +399 -0
  41. data/vendor/curl/include/curl/typecheck-gcc.h +964 -0
  42. data/vendor/curl/include/curl/urlapi.h +156 -0
  43. data/vendor/curl/include/curl/websockets.h +98 -0
  44. data/vendor/curl/include/libpsl.h +207 -0
  45. data/vendor/curl/include/libssh2.h +1516 -0
  46. data/vendor/curl/include/libssh2_publickey.h +128 -0
  47. data/vendor/curl/include/libssh2_sftp.h +382 -0
  48. data/vendor/curl/include/nghttp2/nghttp2.h +6878 -0
  49. data/vendor/curl/include/nghttp2/nghttp2ver.h +42 -0
  50. data/vendor/curl/include/nghttp3/nghttp3.h +3704 -0
  51. data/vendor/curl/include/nghttp3/version.h +46 -0
  52. data/vendor/curl/include/ngtcp2/ngtcp2.h +7485 -0
  53. data/vendor/curl/include/ngtcp2/ngtcp2_crypto.h +1071 -0
  54. data/vendor/curl/include/ngtcp2/ngtcp2_crypto_quictls.h +147 -0
  55. data/vendor/curl/include/ngtcp2/version.h +51 -0
  56. data/vendor/curl/include/openssl/aes.h +120 -0
  57. data/vendor/curl/include/openssl/asn1.h +1121 -0
  58. data/vendor/curl/include/openssl/asn1t.h +996 -0
  59. data/vendor/curl/include/openssl/bio.h +716 -0
  60. data/vendor/curl/include/openssl/blowfish.h +106 -0
  61. data/vendor/curl/include/openssl/bn.h +480 -0
  62. data/vendor/curl/include/openssl/buffer.h +102 -0
  63. data/vendor/curl/include/openssl/camellia.h +121 -0
  64. data/vendor/curl/include/openssl/cast.h +99 -0
  65. data/vendor/curl/include/openssl/chacha.h +54 -0
  66. data/vendor/curl/include/openssl/cmac.h +81 -0
  67. data/vendor/curl/include/openssl/cms.h +541 -0
  68. data/vendor/curl/include/openssl/comp.h +7 -0
  69. data/vendor/curl/include/openssl/conf.h +189 -0
  70. data/vendor/curl/include/openssl/crypto.h +458 -0
  71. data/vendor/curl/include/openssl/ct.h +567 -0
  72. data/vendor/curl/include/openssl/curve25519.h +104 -0
  73. data/vendor/curl/include/openssl/des.h +215 -0
  74. data/vendor/curl/include/openssl/dh.h +245 -0
  75. data/vendor/curl/include/openssl/dsa.h +263 -0
  76. data/vendor/curl/include/openssl/dtls1.h +107 -0
  77. data/vendor/curl/include/openssl/ec.h +675 -0
  78. data/vendor/curl/include/openssl/ecdh.h +6 -0
  79. data/vendor/curl/include/openssl/ecdsa.h +6 -0
  80. data/vendor/curl/include/openssl/engine.h +215 -0
  81. data/vendor/curl/include/openssl/err.h +325 -0
  82. data/vendor/curl/include/openssl/evp.h +1288 -0
  83. data/vendor/curl/include/openssl/hkdf.h +65 -0
  84. data/vendor/curl/include/openssl/hmac.h +101 -0
  85. data/vendor/curl/include/openssl/idea.h +99 -0
  86. data/vendor/curl/include/openssl/kdf.h +137 -0
  87. data/vendor/curl/include/openssl/lhash.h +179 -0
  88. data/vendor/curl/include/openssl/md4.h +105 -0
  89. data/vendor/curl/include/openssl/md5.h +99 -0
  90. data/vendor/curl/include/openssl/mlkem.h +236 -0
  91. data/vendor/curl/include/openssl/modes.h +118 -0
  92. data/vendor/curl/include/openssl/obj_mac.h +4646 -0
  93. data/vendor/curl/include/openssl/objects.h +137 -0
  94. data/vendor/curl/include/openssl/ocsp.h +484 -0
  95. data/vendor/curl/include/openssl/opensslconf.h +16 -0
  96. data/vendor/curl/include/openssl/opensslfeatures.h +153 -0
  97. data/vendor/curl/include/openssl/opensslv.h +18 -0
  98. data/vendor/curl/include/openssl/ossl_typ.h +196 -0
  99. data/vendor/curl/include/openssl/pem.h +542 -0
  100. data/vendor/curl/include/openssl/pkcs12.h +284 -0
  101. data/vendor/curl/include/openssl/pkcs7.h +523 -0
  102. data/vendor/curl/include/openssl/poly1305.h +45 -0
  103. data/vendor/curl/include/openssl/posix_time.h +54 -0
  104. data/vendor/curl/include/openssl/rand.h +118 -0
  105. data/vendor/curl/include/openssl/rc2.h +101 -0
  106. data/vendor/curl/include/openssl/rc4.h +88 -0
  107. data/vendor/curl/include/openssl/ripemd.h +107 -0
  108. data/vendor/curl/include/openssl/rsa.h +603 -0
  109. data/vendor/curl/include/openssl/safestack.h +1739 -0
  110. data/vendor/curl/include/openssl/sha.h +190 -0
  111. data/vendor/curl/include/openssl/sm3.h +49 -0
  112. data/vendor/curl/include/openssl/sm4.h +47 -0
  113. data/vendor/curl/include/openssl/srtp.h +148 -0
  114. data/vendor/curl/include/openssl/ssl.h +2336 -0
  115. data/vendor/curl/include/openssl/ssl3.h +441 -0
  116. data/vendor/curl/include/openssl/stack.h +99 -0
  117. data/vendor/curl/include/openssl/tls1.h +764 -0
  118. data/vendor/curl/include/openssl/ts.h +660 -0
  119. data/vendor/curl/include/openssl/txt_db.h +112 -0
  120. data/vendor/curl/include/openssl/ui.h +397 -0
  121. data/vendor/curl/include/openssl/x509.h +1037 -0
  122. data/vendor/curl/include/openssl/x509_vfy.h +464 -0
  123. data/vendor/curl/include/openssl/x509v3.h +1039 -0
  124. data/vendor/curl/include/zconf.h +206 -0
  125. data/vendor/curl/include/zdict.h +481 -0
  126. data/vendor/curl/include/zlib.h +1859 -0
  127. data/vendor/curl/include/zlib_name_mangling.h +8 -0
  128. data/vendor/curl/include/zstd.h +3198 -0
  129. data/vendor/curl/include/zstd_errors.h +107 -0
  130. data/vendor/curl/lib/libcurl.dll.a +0 -0
  131. data/vendor/curl/licenses/brotli/LICENSE.txt +19 -0
  132. data/vendor/curl/licenses/certdata/LICENSE.url +2 -0
  133. data/vendor/curl/licenses/libpsl/COPYING.txt +20 -0
  134. data/vendor/curl/licenses/libressl/COPYING.txt +133 -0
  135. data/vendor/curl/licenses/libssh2/COPYING.txt +43 -0
  136. data/vendor/curl/licenses/nghttp2/COPYING.txt +23 -0
  137. data/vendor/curl/licenses/nghttp3/COPYING.txt +22 -0
  138. data/vendor/curl/licenses/ngtcp2/COPYING.txt +22 -0
  139. data/vendor/curl/licenses/psl/LICENSE.txt +373 -0
  140. data/vendor/curl/licenses/trurl/COPYING.txt +22 -0
  141. data/vendor/curl/licenses/zlibng/LICENSE.md +19 -0
  142. data/vendor/curl/licenses/zstd/LICENSE.txt +30 -0
  143. metadata +205 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fbb28b47d47a702a68c5a5c787b801d8bac0d2be249ff3e17e0f78202b4f9060
4
+ data.tar.gz: 100a00bf0c3a53c2b2e0562df7d0b9f66e9d101e7a0b3a44b5566038d9729862
5
+ SHA512:
6
+ metadata.gz: 805783c773368701c707906a6922be3e3c0d3afd19017c3dd7e5eaa206caea51bcaf81ac78f4463818748219029878d1985958fa31530a8f7d3efeb47bed4c07
7
+ data.tar.gz: e3230b5ed71e1134333cad7a5bda3e97dd29682fb1867de402ff27ad99727a55cd43ec7723d37d0667e19e7d4b62a51bdf93406bfe010ad34e55d93289541230
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Licentivo Launch contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # Launch Ruby 0.1.1
2
+
3
+ Ruby 3.1+. Install only the Launch gem matching the platform:
4
+
5
+ ```sh
6
+ gem install --local ./licentivo-launch-0.1.1-x64-mingw-ucrt.gem
7
+ ```
8
+
9
+ SDK 0.11.0 Ruby code, its private native core and Windows curl DLL are included. RubyGems resolves the ordinary base64 dependency. The generic source gem builds its own embedded core automatically and needs normal C11/curl/OpenSSL tools for the platform. No official SDK gem or external native-library override is needed.
10
+
11
+ ## Direct initialization and manual lifecycle
12
+
13
+ LicentivoLaunch::Application.connect(merchant_origin, launch_product_id, licentivo_product_id, trusted_keys, environment: 'live', **options) accepts public parameters directly. Launch provides the filled connect_launch function.
14
+
15
+ ```ruby
16
+ app = connect_launch
17
+ begin
18
+ app.login(email, password)
19
+ result = app.run_feature('export') { export_report }
20
+ ensure
21
+ app.close
22
+ end
23
+ ```
24
+
25
+ restore, logout and release_device must handle errors. CancellationToken is cooperative; cancellation cannot become offline authorization. EncryptedFileStore uses a caller-supplied OS-vault key. It does not store that key beside the ciphertext.
26
+
27
+ Launch automatically supplies the current merchant URL, existing product mapping and signing public keys when you select the software in its integration page. Copy its connection function, or use the values already shown there when writing code manually. Do not ask the integrator to type two internal product IDs. No launch-config.json or product.json file is required. The merchant URL is mandatory; the official licensing origin is fixed inside the package to https://www.licentivo.com.
28
+
29
+ The connection function initializes an application; it does not authorize business work. Login obtains only the signed-in customer's credential. Restore checks account expiry, identity, product and environment. Put every protected operation inside the feature guard. Do not substitute merchant order state or a locally saved boolean for the SDK's signed checks.
30
+
31
+ Normal close stops the runtime. Logout clears local account/cache state and follows this package's remote-session contract. Device release is a separate explicit operation, and failure must be reported. Sessions default to memory. Remembered login needs a protected store; never persist merchant passwords. Scope private SDK cache directories by origin, product, environment and account, and exclude them from installers and logs. Session encryption does not encrypt every official SDK device file.
32
+
33
+ Only transport failures or HTTP 5xx may attempt an existing cryptographically verified offline grant. Explicit 401/403/404, session/account mismatch, expiration or suspension deny access without offline fallback. Pending retries are finite and honor Retry-After; cancellation and rate limiting never authorize work. First activation and mandatory-online policies retain their network requirements.
34
+
35
+ Manual development is supported without AI. The combined source bundle includes docs/manual-integration.md with all nine language flows, secure storage, error handling and acceptance procedures. Local package/runtime checks are distinct from external payment and licensing acceptance. Check the current publication status at https://github.com/spf86/licentivo-launch-sdk/blob/main/docs/registry-status.json before using public registry installation commands.
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'mkmf'
3
+ require 'shellwords'
4
+ package = File.expand_path('../..', __dir__)
5
+ core = File.join(package, 'vendor/c')
6
+ abort 'Launch bundled SDK core is missing' unless File.file?(File.join(core, 'licentivo.c'))
7
+ $INCFLAGS << " -I#{Shellwords.escape(core)}"
8
+ $VPATH.concat([core, File.join(core, 'vendor')])
9
+ $srcs = %w[licentivo.c cJSON.c monocypher.c monocypher-ed25519.c]
10
+ $CFLAGS << ' -std=c11'
11
+ bundled_curl = File.join(package, 'vendor/curl')
12
+ windows = RbConfig::CONFIG['host_os'].match?(/mswin|mingw/)
13
+ curl = ENV['LICENTIVO_CURL_ROOT'] || (windows && File.directory?(bundled_curl) ? bundled_curl : nil)
14
+ dir_config('curl', File.join(curl, 'include'), File.join(curl, 'lib')) if curl
15
+ pkg_config('libcurl') unless curl
16
+ abort 'A libcurl development package is required by this source build' unless have_header('curl/curl.h') && have_library('curl', 'curl_easy_init')
17
+ if RbConfig::CONFIG['host_os'].match?(/mswin|mingw/)
18
+ $DLDFLAGS << ' -Wl,--export-all-symbols'
19
+ have_library('bcrypt'); have_library('advapi32')
20
+ else
21
+ pkg_config('openssl')
22
+ abort 'OpenSSL development files are required' unless have_header('openssl/evp.h') && have_library('crypto', 'EVP_sha256')
23
+ have_library('pthread'); have_library('m')
24
+ end
25
+ if curl && RbConfig::CONFIG['host_os'].match?(/mswin|mingw/)
26
+ $INSTALLFILES << ['../../vendor/curl/bin/*.dll', '$(RUBYARCHDIR)', '../../vendor/curl/bin']
27
+ end
28
+ create_makefile('licentivo_launch/vendor/licentivo/native/licentivo_native')
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+ require 'fiddle'
3
+ require 'json'
4
+ require 'monitor'
5
+
6
+ module Licentivo
7
+ VERSION = '0.11.0'
8
+
9
+ class Error < StandardError
10
+ attr_reader :code, :http_status, :request_id, :retryable, :retry_after_seconds
11
+ def initialize(message, code: 'SDK_ERROR', http_status: 0, request_id: '', retryable: false, retry_after_seconds: 0)
12
+ @code, @http_status, @request_id = code, http_status, request_id
13
+ @retryable, @retry_after_seconds = retryable, retry_after_seconds
14
+ super("#{code}: #{message}#{request_id.empty? ? '' : " (request #{request_id})"}")
15
+ end
16
+ end
17
+
18
+ Execution = Struct.new(:value, :executed, keyword_init: true) do
19
+ def replay? = !executed
20
+ end
21
+
22
+ # This library does not register Ruby callbacks on the native heartbeat thread.
23
+ # Local status reads can be marshalled by the application to its own UI thread.
24
+ module Native
25
+ module_function
26
+ def library
27
+ @library ||= begin
28
+ suffix = case RUBY_PLATFORM
29
+ when /mswin|mingw/ then 'dll'
30
+ when /darwin/ then 'dylib'
31
+ else 'so'
32
+ end
33
+ folder = File.expand_path('licentivo/native', __dir__)
34
+ folders = [folder]
35
+ spec = Gem.loaded_specs['licentivo-launch'] if defined?(Gem)
36
+ folders << File.join(spec.extension_dir, 'licentivo_launch/vendor/licentivo/native') if spec
37
+ path = folders.product([suffix, 'so', 'dll', 'dylib'].uniq).map { |dir, ext| File.join(dir, "licentivo_native.#{ext}") }.find { |file| File.file?(file) }
38
+ path ||= File.join(folder, "licentivo_native.#{suffix}")
39
+ unless File.file?(path)
40
+ raise LoadError, 'Launch private native core is missing. Install the Launch gem for your platform or build its source gem with the normal C11/curl development tools.'
41
+ end
42
+ if suffix == 'dll'
43
+ # Ruby's Windows loader does not search process PATH for dependent
44
+ # DLLs. Load the matching libcurl explicitly and keep its handle alive.
45
+ candidates = Dir[File.join(File.dirname(path), 'libcurl*.dll')]
46
+ if ENV['LICENTIVO_CURL_ROOT']
47
+ candidates.concat(Dir[File.join(File.expand_path(ENV['LICENTIVO_CURL_ROOT']), 'bin', 'libcurl*.dll')])
48
+ end
49
+ @curl_library = Fiddle.dlopen(candidates.first) unless candidates.empty?
50
+ end
51
+ Fiddle.dlopen(path)
52
+ end
53
+ end
54
+ def bind(name, arguments, result)
55
+ # Release the GVL while the core uses the network. Fiddle closures acquire
56
+ # it again before entering a Ruby block; native heartbeats never enter Ruby.
57
+ Fiddle::Function.new(library[name], arguments, result, need_gvl: false)
58
+ end
59
+ def functions
60
+ @functions ||= begin
61
+ p, i, q = Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_LONG_LONG
62
+ signatures = {
63
+ init: ['ln_init_file_with_license', [p,p], p],
64
+ init_json: ['ln_init', [p], p],
65
+ init_product: ['ln_init_product', [p,p,p], p],
66
+ start: ['ln_start', [p], i], destroy: ['ln_destroy', [p], Fiddle::TYPE_VOID],
67
+ last_error: ['ln_last_error', [p], p], error_info: ['ln_get_error_info', [p,p], i],
68
+ status: ['ln_status', [p], p], feature_status: ['ln_feature_status', [p,p], p],
69
+ run_feature: ['ln_run_feature', [p,p,p,p], i],
70
+ run_metered_feature: ['ln_run_metered_feature', [p,p,q,p,p,p], i],
71
+ resolve_metered_feature: ['ln_resolve_metered_feature', [p,p,i], i],
72
+ validate: ['ln_validate', [p], i], heartbeat: ['ln_heartbeat', [p], i],
73
+ refresh_online: ['ln_refresh_online', [p], i], deactivate: ['ln_deactivate', [p], i],
74
+ reserve: ['ln_reserve', [p,p,q,p], p], consume: ['ln_consume', [p,p,q,p], p],
75
+ commit: ['ln_commit', [p,p,p], p], cancel: ['ln_cancel', [p,p,p], p],
76
+ offline_request: ['ln_offline_request', [p,p], p], import_offline: ['ln_import_offline', [p,p,p], i],
77
+ free_string: ['ln_free_string', [p], Fiddle::TYPE_VOID],
78
+ json: ['ln_json', [p], p], delete_json: ['cJSON_Delete', [p], Fiddle::TYPE_VOID],
79
+ verify: ['ln_verify', [p,p,p,p,q], i]
80
+ }
81
+ signatures.transform_values { |name, args, result| bind(name, args, result) }
82
+ end
83
+ end
84
+ def call(name, *arguments) = functions.fetch(name).call(*arguments)
85
+ def input(value)
86
+ value = String(value)
87
+ raise ArgumentError, 'Input must not contain NUL bytes' if value.include?("\0")
88
+ value + "\0"
89
+ end
90
+ def text(pointer) = pointer.null? ? '' : pointer.to_s.force_encoding(Encoding::UTF_8)
91
+ def owned_json(pointer)
92
+ raise Error.new('Native core returned no result', code: 'INVALID_RESPONSE') if pointer.null?
93
+ begin
94
+ JSON.parse(pointer.to_s)
95
+ ensure
96
+ call(:free_string, pointer)
97
+ end
98
+ end
99
+ end
100
+
101
+ class Client
102
+ def self.open_config(config, &block)
103
+ raise ArgumentError, 'Provide an in-code configuration Hash' unless config.is_a?(Hash)
104
+ open_with_license(config, &block)
105
+ end
106
+ def self.open_product(product_id, trusted_keys, customer_key = '')
107
+ client = new(product_id, customer_key, trusted_keys: trusted_keys)
108
+ begin
109
+ client.start
110
+ rescue Exception
111
+ client.close
112
+ raise
113
+ end
114
+ return client unless block_given?
115
+ begin
116
+ yield client
117
+ ensure
118
+ client.close
119
+ end
120
+ end
121
+
122
+ def self.open_with_license(path, customer_key = '')
123
+ client = new(path, customer_key)
124
+ begin
125
+ client.start
126
+ rescue Exception
127
+ client.close
128
+ raise
129
+ end
130
+ return client unless block_given?
131
+ begin
132
+ yield client
133
+ ensure
134
+ client.close
135
+ end
136
+ end
137
+
138
+ def initialize(path, customer_key = '', trusted_keys: nil)
139
+ @lock, @in_callback = Monitor.new, false
140
+ @native = if path.is_a?(Hash)
141
+ Native.call(:init_json, Native.input(JSON.generate(path)))
142
+ elsif trusted_keys.nil?
143
+ Native.call(:init, Native.input(path), Native.input(customer_key))
144
+ else
145
+ Native.call(:init_product, Native.input(path), Native.input(JSON.generate(trusted_keys)), Native.input(customer_key.strip))
146
+ end
147
+ if @native.null?
148
+ @native = nil
149
+ raise Error.new('Check product ID, public keys and local storage', code: 'CONFIGURATION_ERROR')
150
+ end
151
+ end
152
+
153
+ def start = checked(:start)
154
+ def validate = checked(:validate)
155
+ def heartbeat = checked(:heartbeat)
156
+ def refresh_online = checked(:refresh_online)
157
+ def deactivate = checked(:deactivate)
158
+ def status = read_json(:status)
159
+ def feature_status(feature) = read_json(:feature_status, Native.input(feature))
160
+ def reserve(feature, quantity, job_id) = read_json(:reserve, Native.input(feature), integer(quantity), Native.input(job_id))
161
+ def consume(feature, quantity, job_id) = read_json(:consume, Native.input(feature), integer(quantity), Native.input(job_id))
162
+ def commit(reservation_id, job_id) = read_json(:commit, Native.input(reservation_id), Native.input(job_id))
163
+ def cancel(reservation_id, job_id) = read_json(:cancel, Native.input(reservation_id), Native.input(job_id))
164
+ def offline_request(operation = 'activate') = read_json(:offline_request, Native.input(operation))
165
+ def import_offline(request, response) = checked(:import_offline, Native.input(JSON.generate(request)), Native.input(JSON.generate(response)))
166
+ def resolve_metered_feature(job_id, business_succeeded)
167
+ unless business_succeeded == true || business_succeeded == false
168
+ raise ArgumentError, 'Determine the real business outcome; pass true or false'
169
+ end
170
+ checked(:resolve_metered_feature, Native.input(job_id), business_succeeded ? 1 : 0)
171
+ end
172
+
173
+ def run_feature(feature, &action)
174
+ execution = run_callback(:run_feature, Native.input(feature), &action)
175
+ execution.value
176
+ end
177
+ def run_metered_feature(feature, quantity, job_id, &action)
178
+ run_callback(:run_metered_feature, Native.input(feature), integer(quantity), Native.input(job_id), &action)
179
+ end
180
+
181
+ def close
182
+ @lock.synchronize do
183
+ raise Error.new('Do not close this client inside its business callback', code: 'WORKFLOW_BUSY') if @in_callback
184
+ if @native
185
+ Native.call(:destroy, @native)
186
+ @native = nil
187
+ end
188
+ end
189
+ nil
190
+ end
191
+
192
+ private
193
+ def integer(value)
194
+ raise ArgumentError, 'Quantity must be a positive signed 64-bit integer' unless value.is_a?(Integer) && value.positive? && value <= 0x7fff_ffff_ffff_ffff
195
+ value
196
+ end
197
+ def active
198
+ raise Error.new('Client is closed', code: 'CLIENT_CLOSED') unless @native
199
+ @native
200
+ end
201
+ def error
202
+ # ln_error_info: int, char[80], char[128], int, int; the shared ABI uses
203
+ # 32-bit ints on supported Windows/Linux/macOS targets.
204
+ info = Fiddle::Pointer.malloc(220, Fiddle::RUBY_FREE)
205
+ info[0,220] = "\0" * 220
206
+ Native.call(:error_info, @native, info)
207
+ Error.new('The licensing operation did not complete',
208
+ code: info[4,80].split("\0", 2).first,
209
+ http_status: info[0,4].unpack1('i'), request_id: info[84,128].split("\0", 2).first,
210
+ retryable: info[212,4].unpack1('i') != 0, retry_after_seconds: info[216,4].unpack1('i'))
211
+ end
212
+ def checked(name, *arguments)
213
+ @lock.synchronize { raise error if Native.call(name, active, *arguments) == 0 }
214
+ true
215
+ end
216
+ def read_json(name, *arguments)
217
+ @lock.synchronize do
218
+ pointer = Native.call(name, active, *arguments)
219
+ raise error if pointer.null?
220
+ Native.owned_json(pointer)
221
+ end
222
+ end
223
+ def run_callback(name, *arguments)
224
+ raise ArgumentError, 'Supply your business operation as a block' unless block_given?
225
+ @lock.synchronize do
226
+ raise Error.new('A business callback is already running', code: 'WORKFLOW_BUSY') if @in_callback
227
+ execution = Execution.new(executed: false)
228
+ business_error = nil
229
+ callback = Fiddle::Closure::BlockCaller.new(Fiddle::TYPE_INT, [Fiddle::TYPE_VOIDP]) do |_context|
230
+ @in_callback = true
231
+ begin
232
+ execution.executed = true
233
+ execution.value = yield
234
+ 1
235
+ rescue Exception => exception
236
+ # Exceptions never cross the C boundary. The native workflow settles
237
+ # cancellation (or journals a pending cancellation) before re-raising.
238
+ business_error = exception
239
+ 0
240
+ ensure
241
+ @in_callback = false
242
+ end
243
+ end
244
+ ok = Native.call(name, active, *arguments, callback, 0)
245
+ raise business_error if business_error
246
+ raise error if ok == 0
247
+ execution
248
+ end
249
+ end
250
+ end
251
+
252
+ def self.verify_lease(envelope, pins, product, device, now)
253
+ parsed_envelope = Native.call(:json, Native.input(JSON.generate(envelope)))
254
+ parsed_pins = Native.call(:json, Native.input(JSON.generate(pins)))
255
+ begin
256
+ Native.call(:verify, parsed_envelope, parsed_pins, Native.input(product), Native.input(device), Integer(now)) != 0
257
+ ensure
258
+ Native.call(:delete_json, parsed_envelope)
259
+ Native.call(:delete_json, parsed_pins)
260
+ end
261
+ end
262
+ end