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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +35 -0
- data/ext/launch_native/extconf.rb +28 -0
- data/lib/licentivo_launch/vendor/licentivo.rb +262 -0
- data/lib/licentivo_launch.rb +352 -0
- data/vendor/LICENSE +21 -0
- data/vendor/NOTICE +17 -0
- data/vendor/VERSION +1 -0
- data/vendor/c/app.c +33 -0
- data/vendor/c/demo.c +77 -0
- data/vendor/c/feature_tasks.h +72 -0
- data/vendor/c/licentivo.c +382 -0
- data/vendor/c/licentivo.h +67 -0
- data/vendor/c/quickstart.c +51 -0
- data/vendor/c/test_vectors.c +4 -0
- data/vendor/c/vendor/Monocypher-LICENCE.md +173 -0
- data/vendor/c/vendor/cJSON-LICENSE +20 -0
- data/vendor/c/vendor/cJSON.c +3191 -0
- data/vendor/c/vendor/cJSON.h +306 -0
- data/vendor/c/vendor/monocypher-ed25519.c +500 -0
- data/vendor/c/vendor/monocypher-ed25519.h +140 -0
- data/vendor/c/vendor/monocypher.c +2956 -0
- data/vendor/c/vendor/monocypher.h +321 -0
- data/vendor/curl/COPYING.txt +22 -0
- data/vendor/curl/bin/libcurl-x64.dll +0 -0
- data/vendor/curl/include/brotli/decode.h +409 -0
- data/vendor/curl/include/brotli/encode.h +505 -0
- data/vendor/curl/include/brotli/port.h +302 -0
- data/vendor/curl/include/brotli/shared_dictionary.h +100 -0
- data/vendor/curl/include/brotli/types.h +83 -0
- data/vendor/curl/include/curl/curl.h +3374 -0
- data/vendor/curl/include/curl/curlver.h +77 -0
- data/vendor/curl/include/curl/easy.h +123 -0
- data/vendor/curl/include/curl/header.h +74 -0
- data/vendor/curl/include/curl/mprintf.h +85 -0
- data/vendor/curl/include/curl/multi.h +561 -0
- data/vendor/curl/include/curl/options.h +70 -0
- data/vendor/curl/include/curl/stdcheaders.h +35 -0
- data/vendor/curl/include/curl/system.h +399 -0
- data/vendor/curl/include/curl/typecheck-gcc.h +964 -0
- data/vendor/curl/include/curl/urlapi.h +156 -0
- data/vendor/curl/include/curl/websockets.h +98 -0
- data/vendor/curl/include/libpsl.h +207 -0
- data/vendor/curl/include/libssh2.h +1516 -0
- data/vendor/curl/include/libssh2_publickey.h +128 -0
- data/vendor/curl/include/libssh2_sftp.h +382 -0
- data/vendor/curl/include/nghttp2/nghttp2.h +6878 -0
- data/vendor/curl/include/nghttp2/nghttp2ver.h +42 -0
- data/vendor/curl/include/nghttp3/nghttp3.h +3704 -0
- data/vendor/curl/include/nghttp3/version.h +46 -0
- data/vendor/curl/include/ngtcp2/ngtcp2.h +7485 -0
- data/vendor/curl/include/ngtcp2/ngtcp2_crypto.h +1071 -0
- data/vendor/curl/include/ngtcp2/ngtcp2_crypto_quictls.h +147 -0
- data/vendor/curl/include/ngtcp2/version.h +51 -0
- data/vendor/curl/include/openssl/aes.h +120 -0
- data/vendor/curl/include/openssl/asn1.h +1121 -0
- data/vendor/curl/include/openssl/asn1t.h +996 -0
- data/vendor/curl/include/openssl/bio.h +716 -0
- data/vendor/curl/include/openssl/blowfish.h +106 -0
- data/vendor/curl/include/openssl/bn.h +480 -0
- data/vendor/curl/include/openssl/buffer.h +102 -0
- data/vendor/curl/include/openssl/camellia.h +121 -0
- data/vendor/curl/include/openssl/cast.h +99 -0
- data/vendor/curl/include/openssl/chacha.h +54 -0
- data/vendor/curl/include/openssl/cmac.h +81 -0
- data/vendor/curl/include/openssl/cms.h +541 -0
- data/vendor/curl/include/openssl/comp.h +7 -0
- data/vendor/curl/include/openssl/conf.h +189 -0
- data/vendor/curl/include/openssl/crypto.h +458 -0
- data/vendor/curl/include/openssl/ct.h +567 -0
- data/vendor/curl/include/openssl/curve25519.h +104 -0
- data/vendor/curl/include/openssl/des.h +215 -0
- data/vendor/curl/include/openssl/dh.h +245 -0
- data/vendor/curl/include/openssl/dsa.h +263 -0
- data/vendor/curl/include/openssl/dtls1.h +107 -0
- data/vendor/curl/include/openssl/ec.h +675 -0
- data/vendor/curl/include/openssl/ecdh.h +6 -0
- data/vendor/curl/include/openssl/ecdsa.h +6 -0
- data/vendor/curl/include/openssl/engine.h +215 -0
- data/vendor/curl/include/openssl/err.h +325 -0
- data/vendor/curl/include/openssl/evp.h +1288 -0
- data/vendor/curl/include/openssl/hkdf.h +65 -0
- data/vendor/curl/include/openssl/hmac.h +101 -0
- data/vendor/curl/include/openssl/idea.h +99 -0
- data/vendor/curl/include/openssl/kdf.h +137 -0
- data/vendor/curl/include/openssl/lhash.h +179 -0
- data/vendor/curl/include/openssl/md4.h +105 -0
- data/vendor/curl/include/openssl/md5.h +99 -0
- data/vendor/curl/include/openssl/mlkem.h +236 -0
- data/vendor/curl/include/openssl/modes.h +118 -0
- data/vendor/curl/include/openssl/obj_mac.h +4646 -0
- data/vendor/curl/include/openssl/objects.h +137 -0
- data/vendor/curl/include/openssl/ocsp.h +484 -0
- data/vendor/curl/include/openssl/opensslconf.h +16 -0
- data/vendor/curl/include/openssl/opensslfeatures.h +153 -0
- data/vendor/curl/include/openssl/opensslv.h +18 -0
- data/vendor/curl/include/openssl/ossl_typ.h +196 -0
- data/vendor/curl/include/openssl/pem.h +542 -0
- data/vendor/curl/include/openssl/pkcs12.h +284 -0
- data/vendor/curl/include/openssl/pkcs7.h +523 -0
- data/vendor/curl/include/openssl/poly1305.h +45 -0
- data/vendor/curl/include/openssl/posix_time.h +54 -0
- data/vendor/curl/include/openssl/rand.h +118 -0
- data/vendor/curl/include/openssl/rc2.h +101 -0
- data/vendor/curl/include/openssl/rc4.h +88 -0
- data/vendor/curl/include/openssl/ripemd.h +107 -0
- data/vendor/curl/include/openssl/rsa.h +603 -0
- data/vendor/curl/include/openssl/safestack.h +1739 -0
- data/vendor/curl/include/openssl/sha.h +190 -0
- data/vendor/curl/include/openssl/sm3.h +49 -0
- data/vendor/curl/include/openssl/sm4.h +47 -0
- data/vendor/curl/include/openssl/srtp.h +148 -0
- data/vendor/curl/include/openssl/ssl.h +2336 -0
- data/vendor/curl/include/openssl/ssl3.h +441 -0
- data/vendor/curl/include/openssl/stack.h +99 -0
- data/vendor/curl/include/openssl/tls1.h +764 -0
- data/vendor/curl/include/openssl/ts.h +660 -0
- data/vendor/curl/include/openssl/txt_db.h +112 -0
- data/vendor/curl/include/openssl/ui.h +397 -0
- data/vendor/curl/include/openssl/x509.h +1037 -0
- data/vendor/curl/include/openssl/x509_vfy.h +464 -0
- data/vendor/curl/include/openssl/x509v3.h +1039 -0
- data/vendor/curl/include/zconf.h +206 -0
- data/vendor/curl/include/zdict.h +481 -0
- data/vendor/curl/include/zlib.h +1859 -0
- data/vendor/curl/include/zlib_name_mangling.h +8 -0
- data/vendor/curl/include/zstd.h +3198 -0
- data/vendor/curl/include/zstd_errors.h +107 -0
- data/vendor/curl/lib/libcurl.dll.a +0 -0
- data/vendor/curl/licenses/brotli/LICENSE.txt +19 -0
- data/vendor/curl/licenses/certdata/LICENSE.url +2 -0
- data/vendor/curl/licenses/libpsl/COPYING.txt +20 -0
- data/vendor/curl/licenses/libressl/COPYING.txt +133 -0
- data/vendor/curl/licenses/libssh2/COPYING.txt +43 -0
- data/vendor/curl/licenses/nghttp2/COPYING.txt +23 -0
- data/vendor/curl/licenses/nghttp3/COPYING.txt +22 -0
- data/vendor/curl/licenses/ngtcp2/COPYING.txt +22 -0
- data/vendor/curl/licenses/psl/LICENSE.txt +373 -0
- data/vendor/curl/licenses/trurl/COPYING.txt +22 -0
- data/vendor/curl/licenses/zlibng/LICENSE.md +19 -0
- data/vendor/curl/licenses/zstd/LICENSE.txt +30 -0
- 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
|