Class: OpenASN::Snapshot
- Inherits:
-
Object
- Object
- OpenASN::Snapshot
- Defined in:
- lib/openasn/snapshot.rb
Overview
An immutable, fully-loaded dataset: canonical artifacts (both address families) + org names + Tier B overlays, plus the metadata to explain where it all came from. Snapshots are built off the hot path and swapped in with a single ivar assignment (see Dataset) — readers never see partial state, and there are no locks anywhere near lookups.
Defined Under Namespace
Classes: Family
Constant Summary collapse
- SEED_DIR =
File.("data/seed", __dir__)
- EMPTY_OVERLAYS =
[].freeze
Instance Attribute Summary collapse
-
#build_id ⇒ Object
readonly
Returns the value of attribute build_id.
-
#build_ts ⇒ Object
readonly
Returns the value of attribute build_ts.
-
#loaded_at ⇒ Object
readonly
Returns the value of attribute loaded_at.
-
#orgs ⇒ Object
readonly
Returns the value of attribute orgs.
-
#origin ⇒ Object
readonly
Returns the value of attribute origin.
-
#overlays ⇒ Object
readonly
Returns the value of attribute overlays.
-
#record_counts ⇒ Object
readonly
Returns the value of attribute record_counts.
-
#tier_b_status ⇒ Object
readonly
Returns the value of attribute tier_b_status.
-
#v4 ⇒ Object
readonly
Returns the value of attribute v4.
-
#v6 ⇒ Object
readonly
Returns the value of attribute v6.
Class Method Summary collapse
-
.build(config) ⇒ Object
Build from the best available data: 1.
- .load_orgs(data_dir, config) ⇒ Object
- .parse_pair(v4_bytes, v6_bytes, mode) ⇒ Object
- .read_artifacts(data_dir) ⇒ Object
- .verify_manifest_hashes!(manifest, files) ⇒ Object
Instance Method Summary collapse
- #age_seconds ⇒ Object
- #family(fam) ⇒ Object
-
#initialize(v4_parsed, v6_parsed, orgs, overlays, manifest, origin, store) ⇒ Snapshot
constructor
A new instance of Snapshot.
- #org_name(asn) ⇒ Object
-
#overlays_for(fam, maps_to) ⇒ Object
Overlays for one family in a stable order (executor wrote them; order among same-precedence overlays doesn't affect verdicts, only which provider gets attribution on exotic multi-overlay hits).
Constructor Details
#initialize(v4_parsed, v6_parsed, orgs, overlays, manifest, origin, store) ⇒ Snapshot
Returns a new instance of Snapshot.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/openasn/snapshot.rb', line 106 def initialize(v4_parsed, v6_parsed, orgs, , manifest, origin, store) @v4 = Family.new(base: v4_parsed[:base], vpn: v4_parsed[:vpn], dc: v4_parsed[:dc], relay: v4_parsed[:relay]) @v6 = Family.new(base: v6_parsed[:base], vpn: v6_parsed[:vpn], dc: v6_parsed[:dc], relay: v6_parsed[:relay]) @orgs = orgs @overlays = .freeze @build_id = manifest["build_id"] @build_ts = v4_parsed[:build_ts] @loaded_at = Time.now.utc @origin = origin @record_counts = { base_ipv4: @v4.base.count, vpn_ipv4: @v4.vpn.count, dc_ipv4: @v4.dc.count, base_ipv6: @v6.base.count }.freeze @tier_b_status = build_tier_b_status(store) # Precomputed (family, maps_to) -> [[entry, layer], ...] index. The old # per-lookup filter_map was fine at 8 overlays but allocation-heavy at # 18+ (measured: 42us/lookup vs 15us overlay-less with 11 overlays - # most of the gap was these throwaway arrays, not the binary searches). # The snapshot is immutable, so build the index exactly once. @overlay_index = {} @overlays.each do |o| { ipv4: o.v4, ipv6: o.v6 }.each do |fam, layer| next unless layer (@overlay_index[[fam, o.maps_to]] ||= []) << [o, layer].freeze end end @overlay_index.each_value(&:freeze) @overlay_index.freeze freeze end |
Instance Attribute Details
#build_id ⇒ Object (readonly)
Returns the value of attribute build_id.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def build_id @build_id end |
#build_ts ⇒ Object (readonly)
Returns the value of attribute build_ts.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def build_ts @build_ts end |
#loaded_at ⇒ Object (readonly)
Returns the value of attribute loaded_at.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def loaded_at @loaded_at end |
#orgs ⇒ Object (readonly)
Returns the value of attribute orgs.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def orgs @orgs end |
#origin ⇒ Object (readonly)
Returns the value of attribute origin.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def origin @origin end |
#overlays ⇒ Object (readonly)
Returns the value of attribute overlays.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def @overlays end |
#record_counts ⇒ Object (readonly)
Returns the value of attribute record_counts.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def record_counts @record_counts end |
#tier_b_status ⇒ Object (readonly)
Returns the value of attribute tier_b_status.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def tier_b_status @tier_b_status end |
#v4 ⇒ Object (readonly)
Returns the value of attribute v4.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def v4 @v4 end |
#v6 ⇒ Object (readonly)
Returns the value of attribute v6.
17 18 19 |
# File 'lib/openasn/snapshot.rb', line 17 def v6 @v6 end |
Class Method Details
.build(config) ⇒ Object
Build from the best available data:
1. data_dir artifacts (downloaded by the updater) when present+valid
2. the gem's bundled seed otherwise (first boot, corrupted dir, …)
A corrupt downloaded artifact NEVER crashes boot — parsing happens INSIDE the fallback boundary (a truncated download that still reads fine must not take the app down); we log and fall back to the seed, and the next update cycle re-downloads. If the bundled seed itself fails to parse, the gem is broken and raising IS correct.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/openasn/snapshot.rb', line 28 def self.build(config) data_dir = config.data_dir mode = config.memory_mode origin = :data_dir begin v4_bytes, v6_bytes, manifest = read_artifacts(data_dir) raise Errno::ENOENT, "no artifacts in data_dir" unless v4_bytes v4_parsed, v6_parsed = parse_pair(v4_bytes, v6_bytes, mode) rescue StandardError => e unless e.is_a?(Errno::ENOENT) config.logger.warn("openasn: data_dir artifacts unusable (#{e.}); falling back to bundled seed") end origin = :seed v4_bytes = File.binread(File.join(SEED_DIR, "openasn-ipv4.bin")) v6_bytes = File.binread(File.join(SEED_DIR, "openasn-ipv6.bin")) manifest = JSON.parse(File.read(File.join(SEED_DIR, "manifest.json"))) v4_parsed, v6_parsed = parse_pair(v4_bytes, v6_bytes, mode) end orgs = load_orgs(data_dir, config) store = OverlayStore.new(data_dir) = store.load(config.enabled_tier_b_source_ids, mode) new(v4_parsed, v6_parsed, orgs, , manifest, origin, store) end |
.load_orgs(data_dir, config) ⇒ Object
96 97 98 99 100 101 102 103 104 |
# File 'lib/openasn/snapshot.rb', line 96 def self.load_orgs(data_dir, config) path = File.join(data_dir, "openasn-orgs.bin") return nil unless File.exist?(path) BinaryFormat::OrgIndex.load(path) rescue StandardError => e config.logger.warn("openasn: openasn-orgs.bin unusable (#{e.}); as_org will be nil until next update") nil end |
.parse_pair(v4_bytes, v6_bytes, mode) ⇒ Object
56 57 58 59 60 61 62 63 |
# File 'lib/openasn/snapshot.rb', line 56 def self.parse_pair(v4_bytes, v6_bytes, mode) v4_parsed = BinaryFormat.parse_artifact(v4_bytes, mode: mode) v6_parsed = BinaryFormat.parse_artifact(v6_bytes, mode: mode) raise FormatError, "openasn-ipv4.bin is not an IPv4 artifact" unless v4_parsed[:family] == :ipv4 raise FormatError, "openasn-ipv6.bin is not an IPv6 artifact" unless v6_parsed[:family] == :ipv6 [v4_parsed, v6_parsed] end |
.read_artifacts(data_dir) ⇒ Object
65 66 67 68 69 70 71 72 73 74 |
# File 'lib/openasn/snapshot.rb', line 65 def self.read_artifacts(data_dir) v4 = File.join(data_dir, "openasn-ipv4.bin") v6 = File.join(data_dir, "openasn-ipv6.bin") mf = File.join(data_dir, "manifest.json") return nil unless File.exist?(v4) && File.exist?(v6) manifest = File.exist?(mf) ? JSON.parse(File.read(mf)) : {} verify_manifest_hashes!(manifest, "openasn-ipv4.bin" => v4, "openasn-ipv6.bin" => v6) [File.binread(v4), File.binread(v6), manifest] end |
.verify_manifest_hashes!(manifest, files) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/openasn/snapshot.rb', line 76 def self.verify_manifest_hashes!(manifest, files) by_name = (manifest["files"] || []).to_h { |f| [f["name"], f] } return if by_name.empty? # legacy/manual installs without checksums still parse through FORMAT.md. files.each do |name, path| expected = by_name.dig(name, "sha256") raise IntegrityError, "manifest missing checksum for #{name}" if expected.to_s.empty? actual = Digest::SHA256.file(path).hexdigest next if actual == expected # Updater writes manifest.json last as the commit marker. If a # process dies during the final rename loop, a fresh boot can see # new artifact bytes next to the old manifest. Reject that mixed # set here and fall back to the bundled seed instead of serving a # cross-build IPv4/IPv6 pair. raise IntegrityError, "#{name} checksum does not match manifest" end end |
Instance Method Details
#age_seconds ⇒ Object
153 154 155 |
# File 'lib/openasn/snapshot.rb', line 153 def age_seconds Time.now.to_i - @build_ts end |
#family(fam) ⇒ Object
138 |
# File 'lib/openasn/snapshot.rb', line 138 def family(fam) = fam == :ipv4 ? @v4 : @v6 |
#org_name(asn) ⇒ Object
149 150 151 |
# File 'lib/openasn/snapshot.rb', line 149 def org_name(asn) asn && @orgs ? @orgs.name(asn) : nil end |
#overlays_for(fam, maps_to) ⇒ Object
Overlays for one family in a stable order (executor wrote them; order among same-precedence overlays doesn't affect verdicts, only which provider gets attribution on exotic multi-overlay hits).
145 146 147 |
# File 'lib/openasn/snapshot.rb', line 145 def (fam, maps_to) @overlay_index.fetch([fam, maps_to], EMPTY_OVERLAYS) end |