# frozen_string_literal: true require "active_support/core_ext/hash/keys" module DjiMqttConnect module Utils # Sanitizes MQTT messages to remove any personally-identifiable information class MessageSanitizer DEFAULT_LATITUDE = "-33.865143" DEFAULT_LONGITUDE = "151.209900" SECRET_KEYS = %w[app_id app_key app_license callsign device_secret nonce] SERIAL_NUMBER_KEYS = %w[device_sn gateway sn] TOPIC_REGEX = /\A.+\/.+\/(.+)\/.+/ def initialize(latitude: DEFAULT_LATITUDE, longitude: DEFAULT_LONGITUDE, secret_keys: SECRET_KEYS, serial_number_keys: SERIAL_NUMBER_KEYS, serial_number_aliases: {}) @latitude = latitude @longitude = longitude @secret_keys = secret_keys @serial_number_keys = serial_number_keys serial_aliases = serial_number_aliases.stringify_keys @serial_number_generator = Hash.new do |h, k| return "" if k.length == 0 h[k] = serial_aliases.fetch(k) { sprintf("%s%02d", "SERIAL", h.length + 1) } end serial_aliases.keys.each { |k| serial_number_generator[k] } end def sanitize_topic(topic) topic.sub(TOPIC_REGEX) { |topic_match| topic_match.sub($1, serial_number_generator[$1]) } end def sanitize_message(message) sanitized_message = sanitize_object(message) strip_serial_numbers(sanitized_message) end private attr_reader :latitude, :longitude attr_reader :secret_keys, :serial_number_keys, :serial_number_generator def sanitize_object(object) case object when Hash serial_number_keys.each do |key| if object.key?(key) && object[key].length > 0 object[key] = serial_number_generator[object[key]] end end secret_keys.each do |key| object[key] = key.upcase if object.key?(key) end if object.key?("latitude") obj_latitude = object["latitude"] object["latitude"] = (obj_latitude.is_a?(Integer) && obj_latitude.zero?) ? 0 : latitude end if object.key?("longitude") obj_longitude = object["longitude"] object["longitude"] = (obj_longitude.is_a?(Integer) && obj_longitude.zero?) ? 0 : longitude end object.transform_values { |v| sanitize_object(v) } when Array object.map { |v| sanitize_object(v) } else object end end # Strips known serial numbers from an object def strip_serial_numbers(object) case object when Hash object.transform_values { |v| strip_serial_numbers(v) } when Array object.map { |v| strip_serial_numbers(v) } when String object.split("-").map { |s| serial_number_generator.fetch(s, s) }.join("-") else object end end end end end