endpoint_security 0.1.0-universal-darwin

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +44 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +114 -0
  5. data/Rakefile +170 -0
  6. data/codegen/ast.rb +67 -0
  7. data/codegen/emit_c.rb +68 -0
  8. data/codegen/emit_ruby.rb +128 -0
  9. data/codegen/ir.rb +193 -0
  10. data/codegen/overlay/availability.yml +158 -0
  11. data/codegen/overlay/version_map.yml +92 -0
  12. data/codegen/run.rb +80 -0
  13. data/codegen/snapshots/26.5.json +5828 -0
  14. data/examples/eslogger_clone.rb +5 -0
  15. data/examples/execblock.rb +16 -0
  16. data/examples/filemon.rb +10 -0
  17. data/examples/procmon.rb +13 -0
  18. data/ext/endpoint_security/client.c +795 -0
  19. data/ext/endpoint_security/client.h +88 -0
  20. data/ext/endpoint_security/endpoint_security.c +17 -0
  21. data/ext/endpoint_security/extconf.rb +37 -0
  22. data/ext/endpoint_security/field.c +719 -0
  23. data/ext/endpoint_security/field.h +42 -0
  24. data/ext/endpoint_security/generated/es_schema.c +1233 -0
  25. data/ext/endpoint_security/message.c +426 -0
  26. data/ext/endpoint_security/message.h +13 -0
  27. data/ext/endpoint_security/mute.c +291 -0
  28. data/ext/endpoint_security/mute.h +8 -0
  29. data/ext/endpoint_security/queue.c +124 -0
  30. data/ext/endpoint_security/queue.h +46 -0
  31. data/ext/endpoint_security/watchdog.c +243 -0
  32. data/lib/endpoint_security/availability.rb +30 -0
  33. data/lib/endpoint_security/client.rb +280 -0
  34. data/lib/endpoint_security/diagnostics.rb +26 -0
  35. data/lib/endpoint_security/doctor.rb +35 -0
  36. data/lib/endpoint_security/errors.rb +42 -0
  37. data/lib/endpoint_security/generated/availability.rb +169 -0
  38. data/lib/endpoint_security/generated/enums.rb +409 -0
  39. data/lib/endpoint_security/generated/event_types.rb +520 -0
  40. data/lib/endpoint_security/message.rb +29 -0
  41. data/lib/endpoint_security/object_model.rb +170 -0
  42. data/lib/endpoint_security/recorder.rb +26 -0
  43. data/lib/endpoint_security/version.rb +6 -0
  44. data/lib/endpoint_security.rb +38 -0
  45. data/support/entitlements.plist +8 -0
  46. data/support/esmock/esmock.c +482 -0
  47. data/support/esmock/esmock.h +21 -0
  48. data/support/sign_ruby.sh +13 -0
  49. metadata +90 -0
data/codegen/ir.rb ADDED
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EndpointSecurity
4
+ module Codegen
5
+ Event = Data.define(:name, :symbol, :value, :minimum_os, :reserved)
6
+ EventTypes = Data.define(:events, :last)
7
+ Field = Data.define(:name, :type, :minimum_version)
8
+ Record = Data.define(:name, :fields)
9
+ EnumValue = Data.define(:name, :symbol, :value)
10
+ Enumeration = Data.define(:name, :values)
11
+
12
+ class IR
13
+ AVAILABILITY = /available beginning in macOS ([0-9]+(?:\.[0-9]+){1,2})/i
14
+ EVENT = /\b(ES_EVENT_TYPE_[A-Z0-9_]+)\b/
15
+ SCALAR_TYPES = [
16
+ "bool", "_Bool", "unsigned long long", "uint64_t", "long long", "int64_t", "unsigned long", "long",
17
+ "unsigned int", "uint32_t", "int", "int32_t", "unsigned short", "uint16_t", "short", "int16_t",
18
+ "unsigned char", "uint8_t", "signed char", "int8_t", "char", "es_string_token_t", "es_token_t",
19
+ "audit_token_t", "struct stat", "struct timespec", "struct timeval", "struct attrlist"
20
+ ].freeze
21
+ POINTER_TYPES = ["struct statfs *", "struct _acl *", "es_sha256_t *", "audit_token_t *"].freeze
22
+ SPECIAL_FIELDS = %w[
23
+ es_token_t.data es_string_token_t.data es_muted_path_t.events es_muted_process_t.events
24
+ es_muted_paths_t.paths es_muted_processes_t.processes es_event_su_t.argv es_event_su_t.env
25
+ es_event_authorization_petition_t.rights es_event_authorization_judgement_t.results
26
+ es_event_od_attribute_set_t.attribute_values es_message_t.opaque es_message_t.action es_result_t.result
27
+ es_fd_t.pipe es_event_rename_t.destination es_event_create_t.destination es_event_authentication_t.data
28
+ es_event_authentication_touchid_t.uid es_event_openssh_login_t.uid es_event_login_login_t.uid
29
+ es_event_su_t.to_uid es_event_sudo_t.from_uid es_event_sudo_t.to_uid
30
+ es_event_gatekeeper_user_override_t.file es_event_setacl_t.acl es_od_member_id_t.member_value
31
+ es_od_member_id_array_t.member_array
32
+ ].freeze
33
+
34
+ def self.event_types(ast:, source:)
35
+ availability = scan_availability(source)
36
+ value = -1
37
+ last = nil
38
+ entries = ast.enum_containing("ES_EVENT_TYPE_AUTH_EXEC").fetch("inner", []).filter_map do |node|
39
+ next unless node["kind"] == "EnumConstantDecl"
40
+
41
+ value = explicit_value(node) || (value + 1)
42
+ if node["name"] == "ES_EVENT_TYPE_LAST"
43
+ last = value
44
+ next
45
+ end
46
+
47
+ symbol = node["name"].delete_prefix("ES_EVENT_TYPE_").downcase.to_sym
48
+ Event.new(node["name"], symbol, value, availability.fetch(node["name"]), symbol.start_with?("reserved_"))
49
+ end
50
+
51
+ unless last == entries.length
52
+ raise CodegenError, "ES_EVENT_TYPE_LAST=#{last}, but #{entries.length} events were parsed"
53
+ end
54
+
55
+ EventTypes.new(entries.freeze, last)
56
+ end
57
+
58
+ def self.scan_availability(source)
59
+ first_event = source.index("ES_EVENT_TYPE_AUTH_EXEC")
60
+ raise CodegenError, "es_event_type_t was not found" unless first_event
61
+
62
+ source = source[source.rindex("typedef enum {", first_event)..]
63
+ current = nil
64
+
65
+ source.each_line.with_object({}) do |line, versions|
66
+ availability_match = line.match(AVAILABILITY)
67
+ current = availability_match[1] if availability_match
68
+ name = line[EVENT, 1]
69
+ versions[name] = current if name && name != "ES_EVENT_TYPE_LAST"
70
+ break versions if line.include?("} es_event_type_t;")
71
+ end
72
+ end
73
+
74
+ def self.explicit_value(node)
75
+ pending = node.fetch("inner", []).dup
76
+ until pending.empty?
77
+ child = pending.shift
78
+ return Integer(child["value"]) if child.key?("value")
79
+
80
+ pending.concat(child.fetch("inner", []))
81
+ end
82
+ nil
83
+ end
84
+
85
+ private_class_method :explicit_value
86
+
87
+ def self.records(ast:, version_sources:)
88
+ versions = scan_field_versions(version_sources)
89
+ ast.typedef_records.filter_map do |name, node|
90
+ direct = node.fetch("inner", []).select { |child| child["kind"] == "FieldDecl" && child["name"] }
91
+ indirect_fields = node.fetch("inner", []).select { |child| child["kind"] == "IndirectFieldDecl" }
92
+ indirect = indirect_fields.filter_map do |child|
93
+ recursive_field(node, child["name"])
94
+ end
95
+ fields = (direct + indirect).uniq { |field| field["name"] }.filter_map do |field|
96
+ next if field["name"].start_with?("reserved")
97
+
98
+ type = normalize_type(field.dig("type", "desugaredQualType") || field.dig("type", "qualType"))
99
+ minimum_version = versions.fetch([name, field["name"]], 1)
100
+ Field.new(field["name"], type, minimum_version)
101
+ end
102
+ Record.new(name, fields.freeze)
103
+ end.freeze
104
+ end
105
+
106
+ def self.enumerations(ast:)
107
+ ast.typedef_enums.map do |name, node|
108
+ value = -1
109
+ prefix = "#{name.delete_suffix("_t").upcase}_"
110
+ values = node.fetch("inner", []).filter_map do |entry|
111
+ next unless entry["kind"] == "EnumConstantDecl"
112
+
113
+ value = explicit_value(entry) || (value + 1)
114
+ symbol = entry.fetch("name").delete_prefix(prefix).delete_prefix("ES_").downcase.to_sym
115
+ EnumValue.new(entry.fetch("name"), symbol, value)
116
+ end
117
+ Enumeration.new(name, values.freeze) unless values.empty?
118
+ end.compact.freeze
119
+ end
120
+
121
+ def self.normalize_type(type)
122
+ type.gsub(/\((?:unnamed|anonymous) at .*?:\d+:\d+\)/, "(anonymous)")
123
+ end
124
+
125
+ def self.validate_field_types!(records:, enumerations:)
126
+ record_names = records.map(&:name)
127
+ enum_names = enumerations.map(&:name)
128
+ records.each do |record|
129
+ record.fields.each do |field|
130
+ next if supported_field_type?(record, field, record_names, enum_names)
131
+
132
+ raise CodegenError, "unsupported field type #{field.type} at #{record.name}.#{field.name}"
133
+ end
134
+ end
135
+ end
136
+
137
+ def self.scan_field_versions(sources)
138
+ sources.each_with_object({}) do |source, versions|
139
+ fields = nil
140
+ source.each_line do |line|
141
+ fields = {} if fields.nil? && line.match?(/\btypedef\s+struct(?:\s+[a-zA-Z_]\w*)?\s*\{/)
142
+ if fields && (match = line.match(
143
+ /\b([a-zA-Z_]\w*)\s*(?:\[[^\]]*\])?\s*;[^\n]*(?:message|msg)\s+versions?\s*>=\s*(\d+)/i
144
+ ))
145
+ fields[match[1]] = Integer(match[2])
146
+ end
147
+ next unless fields && (record = line[/^\s*}\s*(es_[a-zA-Z0-9_]+_t)\s*;/, 1])
148
+
149
+ fields.each { |name, version| versions[[record, name]] = version }
150
+ fields = nil
151
+ end
152
+ end
153
+ end
154
+
155
+ def self.recursive_field(node, name)
156
+ pending = node.fetch("inner", []).dup
157
+ until pending.empty?
158
+ child = pending.shift
159
+ return child if child["kind"] == "FieldDecl" && child["name"] == name
160
+
161
+ pending.concat(child.fetch("inner", []))
162
+ end
163
+ nil
164
+ end
165
+
166
+ private_class_method :recursive_field
167
+
168
+ def self.supported_field_type?(record, field, record_names, enum_names)
169
+ type = field.type
170
+ return true if SPECIAL_FIELDS.include?("#{record.name}.#{field.name}")
171
+ return true if SCALAR_TYPES.include?(type) || enum_names.include?(type) || record_names.include?(type)
172
+ return true if type == "uint8_t[20]" || POINTER_TYPES.include?(type)
173
+
174
+ pointee = type.delete_prefix("const ").delete_suffix(" *")
175
+ type.end_with?(" *") && record_names.include?(pointee)
176
+ end
177
+
178
+ private_class_method :supported_field_type?
179
+
180
+ def self.cacheable_events(source, events)
181
+ cacheable_structs = source.to_enum(:scan, /}\s*(es_event_[a-zA-Z0-9_]+_t);/).filter_map do
182
+ match = Regexp.last_match
183
+ comment_start = source.rindex("/**", match.begin(0))
184
+ match[1] if comment_start && source[comment_start...match.begin(0)].include?("Cache key for this event type")
185
+ end
186
+ suffixes = cacheable_structs.map { |name| name.delete_prefix("es_event_").delete_suffix("_t") }
187
+ events.events.select do |event|
188
+ event.symbol.start_with?("auth_") && suffixes.include?(event.symbol.to_s.delete_prefix("auth_"))
189
+ end.map(&:symbol)
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,158 @@
1
+ ---
2
+ auth_exec: '10.15'
3
+ auth_open: '10.15'
4
+ auth_kextload: '10.15'
5
+ auth_mmap: '10.15'
6
+ auth_mprotect: '10.15'
7
+ auth_mount: '10.15'
8
+ auth_rename: '10.15'
9
+ auth_signal: '10.15'
10
+ auth_unlink: '10.15'
11
+ notify_exec: '10.15'
12
+ notify_open: '10.15'
13
+ notify_fork: '10.15'
14
+ notify_close: '10.15'
15
+ notify_create: '10.15'
16
+ notify_exchangedata: '10.15'
17
+ notify_exit: '10.15'
18
+ notify_get_task: '10.15'
19
+ notify_kextload: '10.15'
20
+ notify_kextunload: '10.15'
21
+ notify_link: '10.15'
22
+ notify_mmap: '10.15'
23
+ notify_mprotect: '10.15'
24
+ notify_mount: '10.15'
25
+ notify_unmount: '10.15'
26
+ notify_iokit_open: '10.15'
27
+ notify_rename: '10.15'
28
+ notify_setattrlist: '10.15'
29
+ notify_setextattr: '10.15'
30
+ notify_setflags: '10.15'
31
+ notify_setmode: '10.15'
32
+ notify_setowner: '10.15'
33
+ notify_signal: '10.15'
34
+ notify_unlink: '10.15'
35
+ notify_write: '10.15'
36
+ auth_file_provider_materialize: '10.15'
37
+ notify_file_provider_materialize: '10.15'
38
+ auth_file_provider_update: '10.15'
39
+ notify_file_provider_update: '10.15'
40
+ auth_readlink: '10.15'
41
+ notify_readlink: '10.15'
42
+ auth_truncate: '10.15'
43
+ notify_truncate: '10.15'
44
+ auth_link: '10.15'
45
+ notify_lookup: '10.15'
46
+ auth_create: '10.15'
47
+ auth_setattrlist: '10.15'
48
+ auth_setextattr: '10.15'
49
+ auth_setflags: '10.15'
50
+ auth_setmode: '10.15'
51
+ auth_setowner: '10.15'
52
+ auth_chdir: 10.15.1
53
+ notify_chdir: 10.15.1
54
+ auth_getattrlist: 10.15.1
55
+ notify_getattrlist: 10.15.1
56
+ notify_stat: 10.15.1
57
+ notify_access: 10.15.1
58
+ auth_chroot: 10.15.1
59
+ notify_chroot: 10.15.1
60
+ auth_utimes: 10.15.1
61
+ notify_utimes: 10.15.1
62
+ auth_clone: 10.15.1
63
+ notify_clone: 10.15.1
64
+ notify_fcntl: 10.15.1
65
+ auth_getextattr: 10.15.1
66
+ notify_getextattr: 10.15.1
67
+ auth_listextattr: 10.15.1
68
+ notify_listextattr: 10.15.1
69
+ auth_readdir: 10.15.1
70
+ notify_readdir: 10.15.1
71
+ auth_deleteextattr: 10.15.1
72
+ notify_deleteextattr: 10.15.1
73
+ auth_fsgetpath: 10.15.1
74
+ notify_fsgetpath: 10.15.1
75
+ notify_dup: 10.15.1
76
+ auth_settime: 10.15.1
77
+ notify_settime: 10.15.1
78
+ notify_uipc_bind: 10.15.1
79
+ auth_uipc_bind: 10.15.1
80
+ notify_uipc_connect: 10.15.1
81
+ auth_uipc_connect: 10.15.1
82
+ auth_exchangedata: 10.15.1
83
+ auth_setacl: 10.15.1
84
+ notify_setacl: 10.15.1
85
+ notify_pty_grant: 10.15.4
86
+ notify_pty_close: 10.15.4
87
+ auth_proc_check: 10.15.4
88
+ notify_proc_check: 10.15.4
89
+ auth_get_task: 10.15.4
90
+ auth_searchfs: '11.0'
91
+ notify_searchfs: '11.0'
92
+ auth_fcntl: '11.0'
93
+ auth_iokit_open: '11.0'
94
+ auth_proc_suspend_resume: '11.0'
95
+ notify_proc_suspend_resume: '11.0'
96
+ notify_cs_invalidated: '11.0'
97
+ notify_get_task_name: '11.0'
98
+ notify_trace: '11.0'
99
+ notify_remote_thread_create: '11.0'
100
+ auth_remount: '11.0'
101
+ notify_remount: '11.0'
102
+ auth_get_task_read: '11.3'
103
+ notify_get_task_read: '11.3'
104
+ notify_get_task_inspect: '11.3'
105
+ notify_setuid: '12.0'
106
+ notify_setgid: '12.0'
107
+ notify_seteuid: '12.0'
108
+ notify_setegid: '12.0'
109
+ notify_setreuid: '12.0'
110
+ notify_setregid: '12.0'
111
+ auth_copyfile: '12.0'
112
+ notify_copyfile: '12.0'
113
+ notify_authentication: '13.0'
114
+ notify_xp_malware_detected: '13.0'
115
+ notify_xp_malware_remediated: '13.0'
116
+ notify_lw_session_login: '13.0'
117
+ notify_lw_session_logout: '13.0'
118
+ notify_lw_session_lock: '13.0'
119
+ notify_lw_session_unlock: '13.0'
120
+ notify_screensharing_attach: '13.0'
121
+ notify_screensharing_detach: '13.0'
122
+ notify_openssh_login: '13.0'
123
+ notify_openssh_logout: '13.0'
124
+ notify_login_login: '13.0'
125
+ notify_login_logout: '13.0'
126
+ notify_btm_launch_item_add: '13.0'
127
+ notify_btm_launch_item_remove: '13.0'
128
+ notify_profile_add: '14.0'
129
+ notify_profile_remove: '14.0'
130
+ notify_su: '14.0'
131
+ notify_authorization_petition: '14.0'
132
+ notify_authorization_judgement: '14.0'
133
+ notify_sudo: '14.0'
134
+ notify_od_group_add: '14.0'
135
+ notify_od_group_remove: '14.0'
136
+ notify_od_group_set: '14.0'
137
+ notify_od_modify_password: '14.0'
138
+ notify_od_disable_user: '14.0'
139
+ notify_od_enable_user: '14.0'
140
+ notify_od_attribute_value_add: '14.0'
141
+ notify_od_attribute_value_remove: '14.0'
142
+ notify_od_attribute_set: '14.0'
143
+ notify_od_create_user: '14.0'
144
+ notify_od_create_group: '14.0'
145
+ notify_od_delete_user: '14.0'
146
+ notify_od_delete_group: '14.0'
147
+ notify_xpc_connect: '14.0'
148
+ notify_gatekeeper_user_override: '15.0'
149
+ notify_tcc_modify: '15.4'
150
+ reserved_0: '26.3'
151
+ reserved_1: '26.3'
152
+ reserved_2: '26.3'
153
+ reserved_3: 26.4.0
154
+ reserved_4: 26.4.0
155
+ reserved_5: 26.4.0
156
+ reserved_6: 26.4.0
157
+ reserved_7: '26.5'
158
+ reserved_8: '26.5'
@@ -0,0 +1,92 @@
1
+ ---
2
+ es_process_t:
3
+ tty: 2
4
+ start_time: 3
5
+ responsible_audit_token: 4
6
+ parent_audit_token: 4
7
+ cs_validation_category: 10
8
+ es_event_exec_t:
9
+ dyld_exec_path: 7
10
+ script: 2
11
+ cwd: 3
12
+ last_fd: 4
13
+ image_cputype: 6
14
+ image_cpusubtype: 6
15
+ es_event_mount_t:
16
+ disposition: 8
17
+ es_event_remount_t:
18
+ remount_flags: 8
19
+ disposition: 8
20
+ es_event_signal_t:
21
+ instigator: 9
22
+ es_event_close_t:
23
+ was_mapped_writable: 6
24
+ es_event_create_t:
25
+ acl: 2
26
+ es_event_iokit_open_t:
27
+ parent_registry_id: 10
28
+ parent_path: 10
29
+ es_event_get_task_t:
30
+ type: 5
31
+ es_event_get_task_read_t:
32
+ type: 5
33
+ es_event_get_task_inspect_t:
34
+ type: 5
35
+ es_event_get_task_name_t:
36
+ type: 5
37
+ es_event_file_provider_materialize_t:
38
+ instigator_token: 8
39
+ es_event_authentication_od_t:
40
+ instigator_token: 8
41
+ es_event_authentication_touchid_t:
42
+ instigator_token: 8
43
+ es_event_authentication_token_t:
44
+ instigator_token: 8
45
+ es_event_xp_malware_detected_t:
46
+ detected_executable: 10
47
+ es_event_btm_launch_item_add_t:
48
+ instigator_token: 8
49
+ app_token: 8
50
+ es_event_btm_launch_item_remove_t:
51
+ instigator_token: 8
52
+ app_token: 8
53
+ es_event_profile_add_t:
54
+ instigator_token: 8
55
+ es_event_profile_remove_t:
56
+ instigator_token: 8
57
+ es_event_authorization_petition_t:
58
+ instigator_token: 8
59
+ petitioner_token: 8
60
+ es_event_authorization_judgement_t:
61
+ instigator_token: 8
62
+ petitioner_token: 8
63
+ es_event_od_group_add_t:
64
+ instigator_token: 8
65
+ es_event_od_group_remove_t:
66
+ instigator_token: 8
67
+ es_event_od_group_set_t:
68
+ instigator_token: 8
69
+ es_event_od_modify_password_t:
70
+ instigator_token: 8
71
+ es_event_od_disable_user_t:
72
+ instigator_token: 8
73
+ es_event_od_enable_user_t:
74
+ instigator_token: 8
75
+ es_event_od_attribute_value_add_t:
76
+ instigator_token: 8
77
+ es_event_od_attribute_value_remove_t:
78
+ instigator_token: 8
79
+ es_event_od_attribute_set_t:
80
+ instigator_token: 8
81
+ es_event_od_create_user_t:
82
+ instigator_token: 8
83
+ es_event_od_create_group_t:
84
+ instigator_token: 8
85
+ es_event_od_delete_user_t:
86
+ instigator_token: 8
87
+ es_event_od_delete_group_t:
88
+ instigator_token: 8
89
+ es_message_t:
90
+ seq_num: 2
91
+ thread: 4
92
+ global_seq_num: 4
data/codegen/run.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "open3"
6
+ require "yaml"
7
+ require_relative "../lib/endpoint_security/errors"
8
+ require_relative "ast"
9
+ require_relative "ir"
10
+ require_relative "emit_ruby"
11
+ require_relative "emit_c"
12
+
13
+ module EndpointSecurity
14
+ module Codegen
15
+ module Run
16
+ module_function
17
+
18
+ def call
19
+ sdk = capture("xcrun", "--show-sdk-path")
20
+ version = capture("xcrun", "--show-sdk-version")
21
+ header = File.join(sdk, "usr/include/EndpointSecurity/ESTypes.h")
22
+ source = File.read(header)
23
+ ir = IR.event_types(ast: AST.load(header: header, sdk: sdk), source: source)
24
+ umbrella = File.join(sdk, "usr/include/EndpointSecurity/EndpointSecurity.h")
25
+ schema_ast = AST.load(header: umbrella, sdk: sdk)
26
+ message_sources = %w[ESMessage.h ESMessageCore.h].map do |name|
27
+ File.read(File.join(sdk, "usr/include/EndpointSecurity", name))
28
+ end
29
+ records = IR.records(ast: schema_ast, version_sources: message_sources)
30
+ enumerations = IR.enumerations(ast: schema_ast)
31
+ IR.validate_field_types!(records: records, enumerations: enumerations)
32
+ emitter = EmitRuby.new(
33
+ ir, cacheable: IR.cacheable_events(message_sources.first, ir), enumerations: enumerations
34
+ )
35
+
36
+ write("lib/endpoint_security/generated/event_types.rb", emitter.event_types)
37
+ write("lib/endpoint_security/generated/enums.rb", emitter.enums)
38
+ write("lib/endpoint_security/generated/availability.rb", emitter.availability)
39
+ write("ext/endpoint_security/generated/es_schema.c", EmitC.new(records, ir).schema)
40
+ availability = ir.events.to_h { |event| [event.symbol.to_s, event.minimum_os] }
41
+ write("codegen/overlay/availability.yml", YAML.dump(availability))
42
+ write("codegen/overlay/version_map.yml", YAML.dump(version_map(records)))
43
+ snapshot_json = JSON.pretty_generate(snapshot(version, ir, records, enumerations)) << "\n"
44
+ write("codegen/snapshots/#{version}.json", snapshot_json)
45
+ end
46
+
47
+ def capture(*command)
48
+ output, status = Open3.capture2(*command)
49
+ raise CodegenError, "#{command.join(" ")} failed" unless status.success?
50
+
51
+ output.strip
52
+ end
53
+
54
+ def write(path, content)
55
+ FileUtils.mkdir_p(File.dirname(path))
56
+ File.write(path, content) unless File.exist?(path) && File.binread(path) == content
57
+ end
58
+
59
+ def version_map(records)
60
+ versions = records.to_h do |record|
61
+ fields = record.fields.select { |field| field.minimum_version > 1 }
62
+ [record.name, fields.to_h { |field| [field.name, field.minimum_version] }]
63
+ end
64
+ versions.reject { |_record, fields| fields.empty? }
65
+ end
66
+
67
+ def snapshot(version, event_types_ir, records, enumerations)
68
+ {
69
+ sdk_version: version,
70
+ last: event_types_ir.last,
71
+ events: event_types_ir.events.map(&:to_h),
72
+ records: records.to_h { |record| [record.name, record.fields.map(&:to_h)] },
73
+ enumerations: enumerations.to_h { |enumeration| [enumeration.name, enumeration.values.map(&:to_h)] }
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ EndpointSecurity::Codegen::Run.call if $PROGRAM_NAME == __FILE__