#!/usr/bin/perl
#
# Copyright (c) 2018-2020, AT&T Intellectual Property.
# All rights reserved.
# Copyright (c) 2015 by Brocade Communications Systems, Inc.
# All rights reserved.
#
# SPDX-License-Identifier: GPL-2.0-only
#

use strict;
use warnings;

use lib '/opt/vyatta/share/perl5/';
use Vyatta::Interface;
use Vyatta::Config;
use Vyatta::VPlaned;
use Vyatta::RestoreIPv6Addr;
use Vyatta::UnnumberedInterface;

use vyatta::proto::SpeedConfig;
use vyatta::proto::PauseConfig;

sub bring_up_dhcp {
    my $intf = shift;
    my $cfg  = new Vyatta::Config( $intf->path() );
    my $dhcp_found;

    foreach my $addr ( $cfg->returnValues('address') ) {
        if ( $addr =~ /^dhcp/ ) {
            $dhcp_found = 1;

            # IPv6 link-local address must be assigned before DHCPv6 attempted
            Vyatta::RestoreIPv6Addr::restore_address(
                { interfaces => [ $intf->name() ] } );

            system( "vyatta-address add " . $intf->name() . " $addr" ) == 0
              or die "Cannot bring up $addr client for " . $intf->name();
        }
    }
    return $dhcp_found;
}

sub bring_up_required_interfaces {
    while ( my $name = shift ) {

        my $intf = new Vyatta::Interface($name);
        $intf or die "Unknown interface name/type: $name\n";

        next unless -d "/sys/class/net/$name";

        next if $intf->up();
        next if not $intf->configured();
        next if $intf->disabled();
        my $cfg = new Vyatta::Config( $intf->path() );
        next if $cfg->exists('breakout-reserved-for');
        my @parent_intf = split( /\./, $name, 2 );
        if ( @parent_intf == 2 ) {
            my $parent_name = $parent_intf[0];
            my $pintf       = new Vyatta::Interface($parent_name);
            next if $pintf->disabled();
        }

        system("ip link set dev $name up") == 0
          or die "Cannot change interface $name to up";

        next if bring_up_dhcp($intf);

        Vyatta::RestoreIPv6Addr::restore_address(
            {
                interfaces      => [$name],
                no_wait_for_dad => 1
            }
        );
    }
}

sub update_unnumbered_donors {
    while ( my $name = shift ) {
        next unless -d "/sys/class/net/$name";
        unnumbered_update_donor($name);
    }
}

sub set_link_speed {
    my $ctrl = new Vyatta::VPlaned;

    my %port_speeds = (
        'auto' => 'auto',
        '10m'  => 10,
        '100m' => 100,
        '1g'   => 1000,
        '2.5g' => 2500,
        '10g'  => 10000,
        '25g'  => 25000,
        '40g'  => 40000,
        '100g' => 100000,
    );

    my %duplex_option = (
        'auto' => SpeedConfig::Duplex::AUTO,
        'half' => SpeedConfig::Duplex::HALF,
        'full' => SpeedConfig::Duplex::FULL,
    );

    while ( my $name = shift ) {
        my $intf       = new Vyatta::Interface($name);
        my $cfg        = new Vyatta::Config( $intf->path() );
        my $duplex     = $cfg->returnValue('duplex') if $cfg->exists('duplex');
        my $speed      = $cfg->returnValue('speed') if $cfg->exists('speed');
        my $old_duplex = $cfg->returnOrigValue('duplex');
        my $old_speed  = $cfg->returnOrigValue('speed');

        next unless -d "/sys/class/net/$name";
        next unless defined $speed;
        my $speed_key = $speed eq "auto" ? "auto" : "numspeed";

        # You can't set the duplex w/o also setting a speed.
        my $mbits = $port_speeds{$speed};
        if ( defined $mbits ) {
            $duplex = $duplex_option{$duplex};
            next unless defined $duplex;
            next
              if defined $old_speed
              and $old_speed eq $speed
              and defined $old_duplex
              and $duplex_option{$old_duplex} eq $duplex;
            my $speedProto = SpeedConfig->new(
                {
                    ifname        => $name,
                    duplex_option => $duplex,
                    $speed_key    => $mbits,
                }
            );

            $ctrl->store_pb(
                "speed set $name",
                $speedProto,
                "vyatta:speed",
                "$name",

            );
        } else {
            warn "unsupported interface speed $speed\n";
        }
    }
}

sub process_pause {
    my $ctrl = new Vyatta::VPlaned;
    my $action_type;

    my %pausevalue = (
        'none' => PauseConfig::PauseValue::NONE,
        'rx'   => PauseConfig::PauseValue::RX,
        'tx'   => PauseConfig::PauseValue::TX,
        'both' => PauseConfig::PauseValue::BOTH
    );

    while ( my $name = shift ) {
        my $intf = new Vyatta::Interface($name);

        next unless ( defined($intf) and $intf->configured );

        my $cfg = new Vyatta::Config( $intf->path() );

        my $old_pause_flow = $cfg->returnOrigValue('pause-frame');
        my $pause_flow     = $cfg->returnValue('pause-frame');

        next unless ( $pause_flow or $old_pause_flow );

        next
          if defined($pause_flow)
          and defined($old_pause_flow)
          and $pause_flow eq $old_pause_flow;

        #Delete case, we are setting the default value i.e "both"
        if ( defined($old_pause_flow) and not defined($pause_flow) ) {
            $pause_flow  = 'both';
            $action_type = 'DELETE';
        } else {
            $action_type = 'SET';
        }

        my $msg = PauseConfig->new(
            {
                pauseif => PauseConfig::PauseIfConfig->new(
                    {
                        ifname => $name,
                        value  => $pausevalue{$pause_flow},
                    }
                ),
            }
        );
        $ctrl->store_pb( "pause $name", $msg, "vyatta:pause", $name,
            $action_type );
    }
}

set_link_speed(@ARGV);
bring_up_required_interfaces(@ARGV);
update_unnumbered_donors(@ARGV);
process_pause(@ARGV);
