#!/usr/bin/python3
#
# Copyright (c) 2012-2015 Qualcomm Atheros, Inc.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import ctypes
import struct
import optparse
import time
import math
import logging
import sys
import os.path
import traceback
import binascii
import tempfile
import subprocess
import hashlib

DEFAULT_FW_API_VERSION = 4

ATH10K_SIGNATURE = b"QCA-ATH10K"
MAX_LEN = 2000000

ATH10K_FW_IE_FW_VERSION = 0
ATH10K_FW_IE_TIMESTAMP = 1
ATH10K_FW_IE_FEATURES = 2
ATH10K_FW_IE_FW_IMAGE = 3
ATH10K_FW_IE_OTP_IMAGE = 4
ATH10K_FW_IE_WMI_OP_VERSION = 5
ATH10K_FW_IE_HTT_OP_VERSION = 6
ATH10K_FW_IE_FW_CODE_SWAP_IMAGE = 7

# enum ath10k_fw_features from ath10k/core.h
ATH10K_FW_FEATURE_EXT_WMI_MGMT_RX = 0
ATH10K_FW_FEATURE_WMI_10X = 1
ATH10K_FW_FEATURE_HAS_WMI_MGMT_TX = 2
ATH10K_FW_FEATURE_NO_P2P = 3
ATH10K_FW_FEATURE_WMI_10_2 = 4
ATH10K_FW_FEATURE_MULTI_VIF_PS_SUPPORT = 5
ATH10K_FW_FEATURE_WOWLAN_SUPPORT = 6
ATH10K_FW_FEATURE_IGNORE_OTP_RESULT = 7
ATH10K_FW_FEATURE_NO_NWIFI_DECAP_4ADDR_PADDING = 8
ATH10K_FW_FEATURE_SUPPORTS_SKIP_CLOCK_INIT = 9
ATH10K_FW_FEATURE_RAW_MODE_SUPPORT = 10
ATH10K_FW_FEATURE_SUPPORTS_ADAPTIVE_CCA = 11
ATH10K_FW_FEATURE_MFP_SUPPORT = 12
ATH10K_FW_FEATURE_PEER_FLOW_CONTROL = 13
ATH10K_FW_FEATURE_BTCOEX_PARAM = 14
ATH10K_FW_FEATURE_SKIP_NULL_FUNC_WAR = 15
ATH10K_FW_FEATURE_ALLOWS_MESH_BCAST = 16
ATH10K_FW_FEATURE_NO_PS = 17
ATH10K_FW_FEATURE_MGMT_TX_BY_REF = 18
ATH10K_FW_FEATURE_NON_BMI = 19
ATH10K_FW_FEATURE_SINGLE_CHAN_INFO_PER_CHANNEL = 20
ATH10K_FW_FEATURE_PEER_FIXED_RATE = 21
ATH10K_FW_FEATURE_MAX = 22

feature_map = {
    'ext-wmi-mgmt-rx': ATH10K_FW_FEATURE_EXT_WMI_MGMT_RX,
    'wmi-10x': ATH10K_FW_FEATURE_WMI_10X,
    'wmi-mgmt-tx': ATH10K_FW_FEATURE_HAS_WMI_MGMT_TX,
    'no-p2p': ATH10K_FW_FEATURE_NO_P2P,
    'wmi-10-2': ATH10K_FW_FEATURE_WMI_10_2,
    'multi-vif-ps': ATH10K_FW_FEATURE_MULTI_VIF_PS_SUPPORT,
    'wowlan': ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
    'ignore-otp-result': ATH10K_FW_FEATURE_IGNORE_OTP_RESULT,
    'no-nwifi-decap-4addr-padding':
        ATH10K_FW_FEATURE_NO_NWIFI_DECAP_4ADDR_PADDING,
    'skip-clock-init': ATH10K_FW_FEATURE_SUPPORTS_SKIP_CLOCK_INIT,
    'raw-mode': ATH10K_FW_FEATURE_RAW_MODE_SUPPORT,
    'adaptive-cca': ATH10K_FW_FEATURE_SUPPORTS_ADAPTIVE_CCA,
    'mfp-support': ATH10K_FW_FEATURE_MFP_SUPPORT,
    'peer-flow-ctrl': ATH10K_FW_FEATURE_PEER_FLOW_CONTROL,
    'btcoex-param': ATH10K_FW_FEATURE_BTCOEX_PARAM,
    'skip-null-func-war': ATH10K_FW_FEATURE_SKIP_NULL_FUNC_WAR,
    'allows-mesh-bcast': ATH10K_FW_FEATURE_ALLOWS_MESH_BCAST,
    'no-ps': ATH10K_FW_FEATURE_NO_PS,
    'mgmt-tx-by-ref': ATH10K_FW_FEATURE_MGMT_TX_BY_REF,
    'non-bmi': ATH10K_FW_FEATURE_NON_BMI,
    'single-chan-info-per-channel': ATH10K_FW_FEATURE_SINGLE_CHAN_INFO_PER_CHANNEL,
    'peer-fixed-rate': ATH10K_FW_FEATURE_PEER_FIXED_RATE,
}

# from enum ath10k_fw_wmi_op_version in ath10k/hw.h
ATH10K_FW_WMI_OP_VERSION_UNSET = 0
ATH10K_FW_WMI_OP_VERSION_MAIN = 1
ATH10K_FW_WMI_OP_VERSION_10_1 = 2
ATH10K_FW_WMI_OP_VERSION_10_2 = 3
ATH10K_FW_WMI_OP_VERSION_TLV = 4
ATH10K_FW_WMI_OP_VERSION_10_2_4 = 5
ATH10K_FW_WMI_OP_VERSION_10_4 = 6

wmi_op_version_map = {
    'unset': ATH10K_FW_WMI_OP_VERSION_UNSET,
    'main': ATH10K_FW_WMI_OP_VERSION_MAIN,
    '10.1': ATH10K_FW_WMI_OP_VERSION_10_1,
    '10.2': ATH10K_FW_WMI_OP_VERSION_10_2,
    'tlv': ATH10K_FW_WMI_OP_VERSION_TLV,
    '10.2.4': ATH10K_FW_WMI_OP_VERSION_10_2_4,
    '10.4': ATH10K_FW_WMI_OP_VERSION_10_4,
}


# from enum ath10k_fw_wmi_op_version in ath10k/hw.h
ATH10K_FW_HTT_OP_VERSION_UNSET = 0
ATH10K_FW_HTT_OP_VERSION_MAIN = 1
ATH10K_FW_HTT_OP_VERSION_10_1 = 2
ATH10K_FW_HTT_OP_VERSION_TLV = 3
ATH10K_FW_HTT_OP_VERSION_10_4 = 4

htt_op_version_map = {
    'unset': ATH10K_FW_HTT_OP_VERSION_UNSET,
    'main': ATH10K_FW_HTT_OP_VERSION_MAIN,
    '10.1': ATH10K_FW_HTT_OP_VERSION_10_1,
    'tlv': ATH10K_FW_HTT_OP_VERSION_TLV,
    '10.4': ATH10K_FW_HTT_OP_VERSION_10_4,
}

ETHTOOL_FWVERS_LEN = 32

# global variables
logger = None


class FWEncoderError(Exception):
    pass


def get_output_name(fw_api=None):
    if fw_api is not None:
        api = fw_api
    else:
        api = DEFAULT_FW_API_VERSION

    return 'firmware-%s.bin' % api


class FirmwareContainer:

    def add_element(self, type_id, value):
        length = len(value)
        padding_len = padding_needed(length)
        length = length + padding_len

        padding = ctypes.create_string_buffer(padding_len)

        for i in range(padding_len):
            struct.pack_into('<B', padding, i, 0x77)

        logger.debug('adding id %d len(value) %d'
                     'padding_len %d' % (type_id,
                                         len(value),
                                         padding_len))
        fmt = '<ii%ds%ds' % (len(value), padding_len)
        struct.pack_into(fmt, self.buf, self.buf_len, type_id, len(value),
                         value, padding.raw)
        self.buf_len = self.buf_len + 4 + 4 + len(value) + padding_len

    def add_u32(self, type_id, value):
        if not type(value) is int:
            raise FWEncoderError('u32 IE %d is not int: %s' %
                                 (type_id, str(value)))

        buf = ctypes.create_string_buffer(4)
        struct.pack_into("<i", buf, 0, value)
        self.add_element(type_id, buf.raw)

    def read_u32(self, type_id):
        (val,) = struct.unpack_from("<i", self.elements[type_id])
        return val

    def add_bitmap(self, ie, enabled, maximum):

        if (max(enabled) >= maximum):
            logger.error("bitmap %d out of maximum (%d >= %d)" %
                         (ie, max(enabled), maximum))
            return

        bytes = [0] * maximum

        for i in range(maximum):
            if i not in enabled:
                continue

            max_set = i
            index = int(i / 8)
            bit = i % 8
            bytes[index] = bytes[index] | (1 << bit)

        # remove trailing null bits away, that changing only
        # maximum doesn't affect created binary size
        length = int(math.ceil((max_set + 1) / float(8)))
        bytes = bytes[:length]

        buf = ctypes.create_string_buffer(length)

        for index in range(length):
            struct.pack_into('<B', buf, index, bytes[index])

        self.add_element(ie, buf.raw)

    def read_bitmap(self, ie):
        buf = self.elements[ie]
        length = len(buf)

        bits = []

        for index in range(length):
            val = struct.unpack_from('<B', buf, index)[0]

            for bit in range(8):
                if val & 0x1:
                    bits.append(index * 8 + bit)

                val = val >> 1

        return bits

    def set_signature(self, signature):
        self.signature = signature
        self.signature_len = len(signature)

        # include the null byte
        length = len(signature) + 1

        padding_len = padding_needed(length)

        length = length + padding_len

        padding = ctypes.create_string_buffer(padding_len)

        for i in range(padding_len):
            struct.pack_into('<B', padding, i, 0x77)

        fmt = '<%dsb%ds' % (len(signature), padding_len)
        struct.pack_into(fmt, self.buf, 0, signature, 0, padding.raw)
        self.buf_len = length

    def write(self, name):
        f = open(name, 'wb')
        f.write(self.buf.raw[:self.buf_len])
        f.close()

        return self.buf_len

    def open(self, name):
        f = open(name, 'rb')
        self.buf = f.read()
        self.buf_len = len(self.buf)
        f.close()

        offset = 0

        fmt = '<%dsb' % (self.signature_len)
        (signature, null) = struct.unpack_from(fmt, self.buf, offset)
        offset = offset + self.signature_len + 1

        if signature != self.signature or null != 0:
            logger.error("Invalid signature!")
            return False

        offset = self.signature_len + 1
        offset = offset + padding_needed(offset)

        self.elements = {}

        while offset + 4 + 4 < self.buf_len:
            (type_id, length) = struct.unpack_from("<ii", self.buf, offset)
            offset = offset + 4 + 4

            if offset + length > self.buf_len:
                logger.error("Buffer too short")
                return

            fmt = '<%ds' % length
            (payload,) = struct.unpack_from(fmt, self.buf, offset)
            offset = offset + length
            offset = offset + padding_needed(offset)

            self.elements[type_id] = payload

        return True

    def __init__(self, signature):
        self.buf = ctypes.create_string_buffer(MAX_LEN)
        self.buf_len = 0

        self.set_signature(signature)


class Ath10kFirmwareContainer(object):
    def _load_file(self, name):
        if os.path.getsize(name) > MAX_LEN:
            raise FWEncoderError('file %s is too large, maximum size is %d' %
                                 (name, MAX_LEN))

        f = open(name, 'rb')
        buf = f.read()
        f.close()

        return buf

    def set_fw_code_swap_image(self, fw_code_swap_image_name):
        self.fw_code_swap_image = self._load_file(fw_code_swap_image_name)

    def get_fw_code_swap_image(self):
        return self.fw_code_swap_image

    def set_htt_op_version(self, htt_op_version):
        s = htt_op_version

        # convert the string to integer
        if s in htt_op_version_map:
            version = htt_op_version_map[s]
        elif is_int(s):
            version = s
        else:
            print('Error: Invalid HTT OP version: %s' % s)
            return 1

        self.htt_op_version = version

    def get_htt_op_version(self):
        version = self.htt_op_version

        # find value from the dict
        try:
            name = [key for key, value in htt_op_version_map.items()
                    if value == version][0]
        except IndexError:
            name = str(version)

        return name

    def set_wmi_op_version(self, wmi_op_version):
        s = wmi_op_version

        # convert the string to integer
        if s in wmi_op_version_map:
            version = wmi_op_version_map[s]
        elif is_int(s):
            version = s
        else:
            print('Error: Invalid WMI OP version: %s' % s)
            return 1

        self.wmi_op_version = version

    def get_wmi_op_version(self):
        version = self.wmi_op_version

        # find value from the dict
        try:
            name = [key for key, value in wmi_op_version_map.items()
                    if value == version][0]
        except IndexError:
            name = str(version)

        return name

    def set_features(self, features):
        self.features = features.split(',')

        enabled = []
        for capa in self.features:
            if capa not in feature_map:
                print("Error: '%s' not found from the feature map" % capa)
                return 1

            enabled.append(feature_map[capa])

        self.features_bitmap = enabled

    def get_features(self):
        s = ""

        if self.features_bitmap is None:
            return None

        for capa in self.features_bitmap:
            # find value from the dict
            try:
                name = [key for key, value in feature_map.items()
                        if value == capa][0]
            except IndexError:
                name = str(capa)

            s = s + name + ","

        # strip last comma
        if len(s) > 0:
            s = s[:-1]

        return s

    def set_fw_image(self, fw_image_name):
        self.fw_image = self._load_file(fw_image_name)

    def get_fw_image(self):
        return self.fw_image

    def set_otp_image(self, otp_image_name):
        self.otp_image = self._load_file(otp_image_name)

    def get_otp_image(self):
        return self.otp_image

    def set_timestamp(self, timestamp):
        self.timestamp = int(timestamp)

    def get_timestamp(self):
        return self.timestamp

    def get_timestamp_as_iso8601(self):
        if self.timestamp is None:
            return None

        return time.strftime('%Y-%m-%d %H:%M:%S',
                             time.gmtime(self.timestamp))

    # fw_version must be a string
    def set_fw_version(self, fw_version):
        self.fw_version = fw_version
        # reserve one byte for null
        if len(self.fw_version) > ETHTOOL_FWVERS_LEN - 1:
            print('Firmware version string too long: %d' % (len(self.fw_version)))
            return 1

    # returns a string
    def get_fw_version(self):
        return self.fw_version

    def get_summary(self):
        s = ''

        s = s + 'FileSize: %s\n' % (len(self.file))
        s = s + 'FileCRC32: %08x\n' % (_crc32(self.file))
        s = s + 'FileMD5: %s\n' % (hashlib.md5(self.file).hexdigest())

        if self.get_fw_version():
            s = s + 'FirmwareVersion: %s\n' % (self.get_fw_version())

        if self.get_timestamp():
            s = s + 'Timestamp: %s\n' % (self.get_timestamp_as_iso8601())

        if self.get_features():
            s = s + 'Features: %s\n' % (self.get_features())

        if self.get_fw_image():
            s = s + 'FirmwareImageSize: %s\n' % (len(self.get_fw_image()))
            s = s + 'FirmwareImageCRC32: %08x\n' % (_crc32(self.get_fw_image()))

        if self.get_otp_image():
            s = s + 'OTPImageSize: %s\n' % (len(self.get_otp_image()))
            s = s + 'OTPImageCRC32: %08x\n' % (_crc32(self.get_otp_image()))

        if self.get_wmi_op_version():
            s = s + 'WMIOpVersion: %s\n' % (self.get_wmi_op_version())

        if self.get_htt_op_version():
            s = s + 'HTTOpVersion: %s\n' % (self.get_htt_op_version())

        if self.get_fw_code_swap_image():
            s = s + 'FirmwareCodeSwapImageSize: %s\n' % (len(self.get_fw_code_swap_image()))
            s = s + 'FirmwareCodeSwapImageCRC32: %08x\n' % (_crc32(self.get_fw_code_swap_image()))

        return s.strip()

    def load(self, filename):
        c = FirmwareContainer(ATH10K_SIGNATURE)
        c.open(filename)

        self.file = c.buf

        for e in c.elements:
            if e == ATH10K_FW_IE_FW_VERSION:
                self.fw_version = c.elements[e].decode()
            elif e == ATH10K_FW_IE_TIMESTAMP:
                self.timestamp = c.read_u32(e)
            elif e == ATH10K_FW_IE_OTP_IMAGE:
                self.otp_image = c.elements[e]
            elif e == ATH10K_FW_IE_FW_IMAGE:
                self.fw_image = c.elements[e]
            elif e == ATH10K_FW_IE_FEATURES:
                self.features_bitmap = c.read_bitmap(ATH10K_FW_IE_FEATURES)
            elif e == ATH10K_FW_IE_WMI_OP_VERSION:
                self.wmi_op_version = c.read_u32(ATH10K_FW_IE_WMI_OP_VERSION)
            elif e == ATH10K_FW_IE_HTT_OP_VERSION:
                self.htt_op_version = c.read_u32(ATH10K_FW_IE_HTT_OP_VERSION)
            elif e == ATH10K_FW_IE_FW_CODE_SWAP_IMAGE:
                self.fw_code_swap_image = c.elements[e]
            else:
                print("Unknown IE: ", e)

    def save(self, filename):
        self.container = FirmwareContainer(ATH10K_SIGNATURE)

        if self.fw_version:
            self.container.add_element(ATH10K_FW_IE_FW_VERSION,
                                       self.fw_version.encode())
        if self.timestamp:
            self.container.add_u32(ATH10K_FW_IE_TIMESTAMP, self.timestamp)

        # FIXME: otp should be after fw_image but that breaks the
        # current tests
        if self.otp_image:
            self.container.add_element(ATH10K_FW_IE_OTP_IMAGE, self.otp_image)

        if self.fw_image:
            self.container.add_element(ATH10K_FW_IE_FW_IMAGE, self.fw_image)

        # FIXME: features should be before fw_image but that breaks
        # the current tests
        if self.features_bitmap:
            self.container.add_bitmap(ATH10K_FW_IE_FEATURES,
                                      self.features_bitmap,
                                      ATH10K_FW_FEATURE_MAX)

        if self.wmi_op_version:
            self.container.add_u32(ATH10K_FW_IE_WMI_OP_VERSION, self.wmi_op_version)

        if self.htt_op_version:
            self.container.add_u32(ATH10K_FW_IE_HTT_OP_VERSION,
                                   self.htt_op_version)

        if self.fw_code_swap_image:
            self.container.add_element(ATH10K_FW_IE_FW_CODE_SWAP_IMAGE,
                                       self.fw_code_swap_image)

        return self.container.write(filename)

    def __init__(self):
        self.fw_version = None
        self.timestamp = None
        self.features = None
        self.features_bitmap = None
        self.fw_image = None
        self.otp_image = None
        self.wmi_op_version = None
        self.htt_op_version = None
        self.fw_code_swap_image = None


# to workaround annoying python feature of returning negative hex values
def hex32(val):
    return val & 0xffffffff


# match with kernel crc32_le(0, buf, buf_len) implementation
def _crc32(buf):
    return hex32(~(hex32(binascii.crc32(buf, 0xffffffff))))


def padding_needed(length):
    if length % 4 != 0:
        return 4 - length % 4

    return 0


def is_int(val):
    try:
        int(val)
        return True
    except ValueError:
        return False


def write_file(filename, buf):
    f = open(filename, 'wb')
    f.write(buf)
    f.close


def info(options, args):

    if len(args) != 1:
        print('Filename missing')
        return 1

    filename = args[0]

    c = Ath10kFirmwareContainer()
    c.load(filename)

    print(c.get_summary())


def dump(options, args):

    if len(args) != 1:
        print('Filename missing')
        return 1

    filename = args[0]

    c = Ath10kFirmwareContainer()
    c.load(filename)

    print("ath10k-fwencoder --create \\")

    if c.get_fw_version():
        print("--firmware-version=%s \\" % c.get_fw_version())

    if c.get_timestamp() and options.show_timestamp:
        print("--timestamp=%u \\" % c.get_timestamp())

    if c.get_features():
        print("--features=%s \\" % c.get_features())

    if c.get_fw_image():
        name = "athwlan.bin"
        print("--firmware=%s \\" % name)

    if c.get_otp_image():
        name = "otp.bin"
        print("--otp=%s \\" % name)

    if c.get_wmi_op_version():
        print('--set-wmi-op-version=%s \\' % c.get_wmi_op_version())

    if c.get_htt_op_version():
        print('--set-htt-op-version=%s \\' % (c.get_htt_op_version()))

    if c.get_fw_code_swap_image():
        name = "athwlan.codeswap.bin"
        print("--firmware-codeswap=%s \\" % name)

    print('')


def extract(options, args):

    if len(args) != 1:
        print('Filename missing')
        return 1

    filename = args[0]

    c = Ath10kFirmwareContainer()
    c.load(filename)

    if c.get_fw_image():
        name = "athwlan.bin"
        write_file(name, c.get_fw_image())
        print('%s extracted: %d B' % (name, len(c.get_fw_image())))

    if c.get_otp_image():
        name = "otp.bin"
        write_file(name, c.get_otp_image())
        print('%s extracted: %d B' % (name, len(c.get_otp_image())))

    if c.get_fw_code_swap_image():
        name = "athwlan.codeswap.bin"
        write_file(name, c.get_fw_code_swap_image())
        print('%s extracted: %d B' % (name, len(c.get_fw_code_swap_image())))

    print('')


def modify(options, args):
    if len(args) != 1:
        print('Filename missing')
        return 1

    filename = args[0]

    c = Ath10kFirmwareContainer()
    c.load(filename)

    if options.firmware_version:
        c.set_fw_version(options.firmware_version)

    if options.timestamp:
        stamp = str(int(options.timestamp))
    else:
        # if no timestamp provided use the current time so that the
        # timestamp shows the time of last modication
        stamp = int(time.time())

    c.set_timestamp(stamp)

    if options.otp:
        c.set_otp_image(options.otp)

    if options.fw:
        c.set_fw_image(options.fw)

    if options.features:
        c.set_features(options.features)

    if options.wmi_op_version:
        c.set_wmi_op_version(options.wmi_op_version)

    if options.htt_op_version:
        c.set_htt_op_version(options.htt_op_version)

    if options.fw_codeswap:
        c.set_fw_code_swap_image(options.fw_codeswap)

    file_len = c.save(filename)

    print('%s modified: %d B' % (filename, file_len))


def create(options):
    output = get_output_name(options.fw_api)

    if options.output:
        output = options.output

    c = Ath10kFirmwareContainer()

    if options.firmware_version:
        c.set_fw_version(options.firmware_version)

    # always add a timestamp
    if options.timestamp:
        stamp = int(options.timestamp)
    else:
        stamp = int(time.time())

    c.set_timestamp(stamp)

    if options.otp:
        c.set_otp_image(options.otp)

    if options.fw:
        c.set_fw_image(options.fw)

    if options.features:
        c.set_features(options.features)

    if options.wmi_op_version:
        c.set_wmi_op_version(options.wmi_op_version)

    if options.htt_op_version:
        c.set_htt_op_version(options.htt_op_version)

    if options.fw_codeswap:
        c.set_fw_code_swap_image(options.fw_codeswap)

    file_len = c.save(output)

    print('%s created: %d B' % (output, file_len))


def cmd_crc32(options, args):
    if len(args) != 1:
        print('Filename missing')
        return 1

    filename = args[0]

    f = open(filename, 'rb')
    buf = f.read()
    print('%08x' % (_crc32(buf)))
    f.close()


def cmd_diff(options, args):
    if len(args) != 2:
        print('Usage: ath10k-fwencoder --diff FILE FILE')
        return 1

    filename1 = args[0]
    filename2 = args[1]

    c1 = Ath10kFirmwareContainer()
    c1.load(filename1)
    (temp1_fd, temp1_pathname) = tempfile.mkstemp(text=True)

    # for some reason text=True is not working with mkstemp() so open
    # the file manually
    f = open(temp1_pathname, 'w')
    f.write(c1.get_summary())
    f.close()

    c2 = Ath10kFirmwareContainer()
    c2.load(filename2)
    (temp2_fd, temp2_pathname) = tempfile.mkstemp(text=True)

    # for some reason text=True is not working with mkstemp() so open
    # the file manually
    f = open(temp2_pathname, 'w')
    f.write(c2.get_summary())
    f.close()

    # '--less-mode' and '--auto-page' would be nice when running on
    # terminal but don't know how to get the control character
    # through. For terminal detection sys.stdout.isatty() can be used.
    cmd = ['wdiff', temp1_pathname, temp2_pathname]

    # wdiff is braindead and returns 1 in a succesfull case
    try:
        output = subprocess.check_output(cmd, universal_newlines=True)
    except subprocess.CalledProcessError as e:
        if e.returncode == 1:
            output = e.output
        else:
            logger.error('Failed to run wdiff: %d\n%s' % (e.returncode, e.output))
            return 1
    except OSError as e:
        logger.error('Failed to run wdiff: %s' % (e))
        return 1

    print(output)

    os.close(temp1_fd)
    os.close(temp2_fd)


def main():
    global logger

    logger = logging.getLogger('ath10k-fwencoder')
    logging.basicConfig(format='%(levelname)s: %(message)s')

    parser = optparse.OptionParser()

    # actions
    parser.add_option("-c", "--create", action="store_true", dest="create",
                      help='Create container file '
                           'for ath10k (%s)' % get_output_name())
    parser.add_option("-D", "--dump-cmdline", action="store_true", dest="dump",
                      help='Show the cmdline used to create '
                           'this container file')
    parser.add_option('--dump', action='store_true', dest='dump',
                      help='Same as --dump-cmdline '
                      '(for backwards compatibility)')
    parser.add_option("-e", "--extract", action="store_true", dest="extract",
                      help='Extract binary files from the container file '
                           'and dump cmdline as well')
    parser.add_option("--info", action="store_true", dest="info",
                      help='Show information about the container file')
    parser.add_option("--modify", action="store_true", dest="modify",
                      help='Modify the container file')
    parser.add_option('--crc32', action='store_true', dest='crc32',
                      help='Count crc32 checksum for a file')
    parser.add_option('--diff', action='store_true', dest='diff',
                      help='Show differences between two firmware files')

    # parameters
    parser.add_option("-o", "--output", action="store", type="string",
                      dest="output", help='Name of output file')

    # FW IEs, only use long style of option names!
    parser.add_option("--otp", action="store", type="string",
                      dest="otp",
                      help='Name of otp.bin file')
    parser.add_option("--firmware", action="store", type="string",
                      dest="fw",
                      help='Name of athwlan.bin file')
    parser.add_option("--firmware-version", action="store",
                      type="string", dest="firmware_version",
                      help='Firmware version string to be used')
    parser.add_option("--timestamp", action="store",
                      type="string", dest="timestamp",
                      help='Timestamp to be used (seconds)')

    parser.add_option("--features", action="store",
                      type="string", dest="features",
                      help='feature bits to be enabled: %s' %
                           feature_map.keys())
    parser.add_option("--set-wmi-op-version", action="store",
                      type="string", dest="wmi_op_version",
                      help='WMI op interface version: %s' %
                           wmi_op_version_map.keys())
    parser.add_option("--set-fw-api", action="store",
                      type="string", dest="fw_api",
                      help='Set firmware API used in creating the name for '
                           'output file (Default: %s)' %
                           DEFAULT_FW_API_VERSION)
    parser.add_option("--set-htt-op-version", action="store",
                      type="string", dest="htt_op_version",
                      help='HTT op interface version: %s' %
                           htt_op_version_map.keys())
    parser.add_option("--firmware-codeswap", action="store", type="string",
                      dest="fw_codeswap",
                      help='Name of athwlan.codeswap.bin file')

    # debug etc
    parser.add_option('--show-timestamp', action='store_true',
                      dest='show_timestamp',
                      help='Show timestamp in --dump-cmdline action. '
                      'It is not shown by default so that the timestamp would be correct')
    parser.add_option('-d', '--debug', action='store_true', dest='debug',
                      help='Enable debug messages')

    (options, args) = parser.parse_args()

    if options.debug:
        logger.setLevel(logging.DEBUG)

    if options.create:
        try:
            return create(options)
        except FWEncoderError as e:
            print('Create failed: %s' % e)
            sys.exit(2)
        except Exception as e:
            print('Create failed: %s' % e)
            traceback.print_exc()
            sys.exit(3)
    elif options.dump:
        return dump(options, args)
    elif options.extract:
        return extract(options, args)
    elif options.info:
        return info(options, args)
    elif options.modify:
        return modify(options, args)
    elif options.crc32:
        return cmd_crc32(options, args)
    elif options.diff:
        return cmd_diff(options, args)
    else:
        print('Action command missing')
        return 1

if __name__ == "__main__":
    main()
