#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2016             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# tails. You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.


# 1.3.6.1.4.1.9.9.68.1.2.2.1.1 --> vmVlanType: static(1), dynamic(2), multiVlan(3)
# 1.3.6.1.4.1.9.9.68.1.2.2.1.2 --> vmVlan: id of the vlan the port is asssigned to
#                                  if type = 1 or 2.
#                                  it's 0 if the port is not assigned to a vlan
# 1.3.6.1.4.1.9.9.68.1.2.2.1.4 --> vmVlans: the vlans the port is assigned to
#                                  if the type = 3

# "The VLAN(s) the port is assigned to when the
# port's vmVlanType is set to multiVlan.
# This object is not instantiated if not applicable.

# The port is always assigned to one or more VLANs
# and the object may not be set so that there are
# no vlans assigned.

# Each octet within the value of this object specifies a
# set of eight VLANs, with the first octet specifying
# VLAN id 1 through 8, the second octet specifying VLAN
# ids 9 through 16, etc.   Within each octet, the most
# significant bit represents the lowest numbered
# VLAN id, and the least significant bit represents the
# highest numbered VLAN id.  Thus, each VLAN of the
# port is represented by a single bit within the
# value of this object.  If that bit has a value of
# '1' then that VLAN is included in the set of
# VLANs; the VLAN is not included if its bit has a
# value of '0'."


# This function compresses a list of vlans, eg [1,3,4,7,8,9,...,1024],
# into a readable format: "1, 3-4, 7-1024" in case of multi-vlans
def parse_multi_vlan(vlan_multi):

    def concatenate_vlans(vlan, subinfo):
        if vlan not in subinfo:
            subinfo.append(vlan)
        return "-".join(map(str, subinfo))

    vlans = []
    for k, hex in enumerate(vlan_multi):
        for l, bit in enumerate(bin(ord(hex))[2:]):
            if bit == '1':
                vlans.append(k*8 + l+1)

    if not vlans:
        return ""

    infotexts = []
    subinfo   = vlans[:1]
    last_vlan = vlans[0]

    for vlan in vlans[1:]:
        if vlan - last_vlan > 1:
            infotexts.append(concatenate_vlans(last_vlan, subinfo))
            subinfo = [vlan]

        if vlan == vlans[-1]:
            infotexts.append(concatenate_vlans(vlan, subinfo))

        last_vlan = vlan

    return ", ".join(infotexts)


def inv_cisco_vlans(info, params):
    node = inv_tree("networking.interfaces:")
    map_vlans = {
        '1' : 'static',
        '2' : 'dynamic',
        '3' : 'multi-VLAN',
    }

    for if_id, vlan_type, vlan_single, vlan_multi in info:
        vlan_readable = map_vlans.get(vlan_type, "")
        vlans         = None
        if vlan_single != '0' and vlan_type in ['1', '2']:
            vlans = vlan_single
        elif vlan_type == '3':
            vlans = parse_multi_vlan(vlan_multi)

        if vlans:
            for if_info in node:
                if if_info["index"] == int(if_id):
                    if_info["vlans"]    = vlans
                    if_info["vlantype"] = vlan_readable
                    break


inv_info['inv_cisco_vlans'] = {
    "inv_function"      : inv_cisco_vlans,
    'snmp_info'         : (".1.3.6.1.4.1.9.9.68.1.2.2.1", [
                                OID_END,
                                "1",     # vmVlanType
                                "2",     # vmVlan
                                "4",     # vmVlans
                          ]),
    'snmp_scan_function': lambda oid: "cisco" in oid(".1.3.6.1.2.1.1.1.0").lower(),
}
