<commit_before>module LIFX
  class Message
    class InvalidFrame < ArgumentError; end
    class UnsupportedProtocolVersion < StandardError; end
    class NotAddressableFrame < StandardError; end
    class NoPayload < ArgumentError; end
    class UnmappedPayload < ArgumentError; end
    class InvalidFields < ArgumentError; end
    class PackError < ArgumentError; end

    PROTOCOL_VERSION = 1024

    class << self
      def unpack(data)
        raise InvalidFrame if data.length < 2

        header = Protocol::Header.read(data)
        raise UnsupportedProtocolVersion if header.protocol != PROTOCOL_VERSION
        raise NotAddressableFrame if header.addressable == 0

        message = Protocol::Message.read(data)
        payload_class = message_type_for_id(message.type.snapshot)
        if payload_class.nil?
          LOG.warn("Message.unpack: Unrecognised payload ID: #{message.type}")
          LOG.warn("Message.unpack: Message: #{message}")
          return nil
          raise UnmappedPayload.new("Unrecognised payload ID: #{message.type}")
        end
        begin
          payload = payload_class.read(message.payload)
        rescue => ex
          if message.raw_site == "\x00" * 6
            LOG.info("Message.unpack: Ignoring malformed message from virgin bulb")
          else
            LOG.error("Message.unpack: Exception while unpacking payload of type #{payload_class}: #{ex}")
            LOG.error("Message.unpack: Data: #{data.inspect}")
          end
        end
        new(message, payload)
      end

      def message_type_for_id(type_id)
        Protocol::TYPE_ID_TO_CLASS[type_id]
      end

      def type_id_for_message_class(klass)
        Protocol::CLASS_TO_TYPE_ID[klass]
      end

      def valid_fields
        @valid_fields ||= Protocol::Message.new.field_names.map(&:to_sym)
      end
    end

    LIFX::Protocol::Message.fields.each do |field|
      define_method(field.name) do
        @message.send(field.name).snapshot
      end

      define_method("#{field.name}=") do |value|
        @message.send("#{field.name}=", value)
      end
    end

    alias_method :tagged?, :tagged
    alias_method :addressable?, :addressable

    attr_accessor :payload
    def initialize(*args)
      if args.count == 2 
        @message = args.first
        @payload = args.last
      elsif (hash = args.first).is_a?(Hash)
        payload = hash.delete(:payload)
        site    = hash.delete(:site)
        target  = hash.delete(:target)

        check_valid_fields!(hash)
        @message = Protocol::Message.new(hash)
        self.payload = payload
        self.site = site if site
        self.target = target if target
      else
        @message = Protocol::Message.new
      end
      @message.msg_size = @message.num_bytes
    rescue => ex
      raise PackError.new("Unable to pack message with args: #{args.inspect}")
    end

    def payload=(payload)
      @payload = payload
      type_id = self.class.type_id_for_message_class(payload.class)
      if type_id.nil?
        raise UnmappedPayload.new("Unmapped payload class #{payload.class}")
      end
      @message.type = type_id
      @message.payload = payload.pack
    end

    def pack
      raise NoPayload if !payload
      @message.msg_size = @message.num_bytes
      @message.pack
    end

    def site
      raw_site.unpack('H*').join
    end

    def site=(value)
      self.raw_site = [value].pack('H*')
    end

    def target
      raw_target.unpack('H*').join
    end<commit_msg>Expose exception in PackError<commit_after>module LIFX
  class Message
    class InvalidFrame < ArgumentError; end
    class UnsupportedProtocolVersion < StandardError; end
    class NotAddressableFrame < StandardError; end
    class NoPayload < ArgumentError; end
    class UnmappedPayload < ArgumentError; end
    class InvalidFields < ArgumentError; end
    class PackError < ArgumentError; end

    PROTOCOL_VERSION = 1024

    class << self
      def unpack(data)
        raise InvalidFrame if data.length < 2

        header = Protocol::Header.read(data)
        raise UnsupportedProtocolVersion if header.protocol != PROTOCOL_VERSION
        raise NotAddressableFrame if header.addressable == 0

        message = Protocol::Message.read(data)
        payload_class = message_type_for_id(message.type.snapshot)
        if payload_class.nil?
          LOG.warn("Message.unpack: Unrecognised payload ID: #{message.type}")
          LOG.warn("Message.unpack: Message: #{message}")
          return nil
          raise UnmappedPayload.new("Unrecognised payload ID: #{message.type}")
        end
        begin
          payload = payload_class.read(message.payload)
        rescue => ex
          if message.raw_site == "\x00" * 6
            LOG.info("Message.unpack: Ignoring malformed message from virgin bulb")
          else
            LOG.error("Message.unpack: Exception while unpacking payload of type #{payload_class}: #{ex}")
            LOG.error("Message.unpack: Data: #{data.inspect}")
          end
        end
        new(message, payload)
      end

      def message_type_for_id(type_id)
        Protocol::TYPE_ID_TO_CLASS[type_id]
      end

      def type_id_for_message_class(klass)
        Protocol::CLASS_TO_TYPE_ID[klass]
      end

      def valid_fields
        @valid_fields ||= Protocol::Message.new.field_names.map(&:to_sym)
      end
    end

    LIFX::Protocol::Message.fields.each do |field|
      define_method(field.name) do
        @message.send(field.name).snapshot
      end

      define_method("#{field.name}=") do |value|
        @message.send("#{field.name}=", value)
      end
    end

    alias_method :tagged?, :tagged
    alias_method :addressable?, :addressable

    attr_accessor :payload
    def initialize(*args)
      if args.count == 2 
        @message = args.first
        @payload = args.last
      elsif (hash = args.first).is_a?(Hash)
        payload = hash.delete(:payload)
        site    = hash.delete(:site)
        target  = hash.delete(:target)

        check_valid_fields!(hash)
        @message = Protocol::Message.new(hash)
        self.payload = payload
        self.site = site if site
        self.target = target if target
      else
        @message = Protocol::Message.new
      end
      @message.msg_size = @message.num_bytes
    rescue => ex
      raise PackError.new("Unable to pack message with args: #{args.inspect} - #{ex}")
    end

    def payload=(payload)
      @payload = payload
      type_id = self.class.type_id_for_message_class(payload.class)
      if type_id.nil?
        raise UnmappedPayload.new("Unmapped payload class #{payload.class}")
      end
      @message.type = type_id
      @message.payload = payload.pack
    end

    def pack
      raise NoPayload if !payload
      @message.msg_size = @message.num_bytes
      @message.pack
    end

    def site
      raw_site.unpack('H*').join
    end

    def site=(value)
      self.raw_site = [value].pack('H*')
    end

    def target
      raw_target.unpack('H*').join
    end
{'fraction_non_alphanumeric': 0.09829059829059829, 'fraction_numerical': 0.002849002849002849, 'mean_word_length': 2.453516969995081, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6917076', 'n_tokens_mistral': 2060, 'n_tokens_neox': 2025, 'n_words': 477}