#!/bin/bash
#
# This is an sdm plugin for: gadgetmode
#
# The plugin is called three times: for Phase 0, Phase 1, and post-install.
#
# Based on: https://forums.raspberrypi.com/viewtopic.php?t=376578 ("simple" mode)
#      and: https://blog.hardill.me.uk/2023/12/23/pi5-usb-c-gadget ("shared" mode)
#

function loadparams() {
    source $SDMPT/etc/sdm/sdm-readparams
}

function makerandmac() {
    local v=$1
    local u m

    u=$(uuid -v4)
    u=${u//-/}
    m="$v:${u:4:2}:${u:8:2}:${u:12:2}"
    echo "$m"
}

function makehostmac() {
    # $1: a MAC addess
    #
    #
    local dmac=$1 hmac hmac1

    hmac=${dmac:0:16}
    # Derive host mac from device mac. Change lowest character
    [ "${dmac:16:1}" == "2" ] && hmac="${hmac}4" || hmac="${hmac}2"
    echo "$hmac"
    return
}

function makenmconn() {
    local usbno=$1 usbname cfile1 cfile2

    usbname="usb${usbno}"
    [ -v noipv6 ] && ipv6=disabled || ipv6=auto
    [ "$dhcp__timeout" == "" ] && dhcp__timeout=60
    [ "$autoconnect__retries" == "" ] && autoconnect__retries=5
    cfile1=/etc/NetworkManager/system-connections/$usbname-dhcp.nmconnection
    logtoboth "> Plugin $pfx: Create NetworkManager connection '$usbname-dhcp' with 'dhcp-timeout=$dhcp__timeout' and 'autoconnect-retries=$autoconnect__retries'"
    cat <<- EOF >$cfile1
[connection]
id=$usbname-dhcp
uuid=$(uuid -v4)
type=ethernet
interface-name=$usbname
autoconnect-priority=100
autoconnect-retries=$autoconnect__retries

[ethernet]

[ipv4]
dhcp-timeout=$dhcp__timeout
method=auto

[ipv6]
addr-gen-mode=default
method=$ipv6

[proxy]
EOF

    logtoboth "> Plugin $pfx: Create NetworkManager connection '$usbname-ll'"
    cfile2=/etc/NetworkManager/system-connections/$usbname-ll.nmconnection
    cat <<- EOF >$cfile2
[connection]
id=$usbname-ll
uuid=$(uuid -v4)
type=ethernet
interface-name=$usbname
autoconnect-priority=50

[ethernet]

[ipv4]
method=link-local

[ipv6]
addr-gen-mode=default
method=$ipv6

[proxy]
EOF
    # Set proper connection file protection
    chmod 600 $cfile1 $cfile2
}

function makeservice() {
    #
    # $1: gadget name
    # $2: static mac address
    local gname="$1" decmmac="$2" rndismac="$3"
    local decmmac drndismac hecmmac hrndismac

    logtoboth "> Plugin $pfx: Create and enable 'sdm-gadget-init' service"
    hecmmac="$(makehostmac $decmmac)"
    #hrndismac="$(makehostmac $rndismac)"
    cat >> /etc/systemd/system/sdm-gadget-init.service <<EOF
[Unit]
Description=sdm gadget initializer
Before=NetworkManager.service
After=dbus.service

[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/local/bin/sdm-gadget-init
TimeoutSec=30

[Install]
WantedBy=multi-user.target
EOF
    systemctl -q enable sdm-gadget-init
    [ "$gadget__name" == "" ] && gadget__name=mygadget
    logtoboth "> Plugin $pfx: Create /usr/local/bin/sdm-gadget-init"
    cat >> /usr/local/bin/sdm-gadget-init <<EOF
#!/bin/bash
#
# Load module libcomposite
#
modprobe libcomposite
#
# Configure the gadget
#
kd=/sys/kernel/config/usb_gadget
kdm=\$kd/$gadget__name
kdms=\$kdm/strings/0x409
kdmf=\$kdm/functions/ecm.usb0
kdmfr=\$kdm/functions/rndis.usb0
kdmc=\$kdm/configs/c.1
kdmcs=\$kdmc/strings/0x409
kdmc2=\$kdm/configs/c.2
kdmcs2=\$kdmc2/strings/0x409

mkdir -p \$kdm
echo 0x1d6b >| \$kdm/idVendor     # Linux Foundation
echo 0x0104 >| \$kdm/idProduct    # Multifunction Composite Gadget
echo 0x0103 >| \$kdm/bcdDevice    # V1.0.3  (0x0100 = v1.0.0)
echo 0x0320 >| \$kdm/bcdUSB       # USB2
echo 2      >| \$kdm/bDeviceClass #
mkdir -p \$kdms
echo "facefeed" >| \$kdms/serialnumber
echo "sdm" >| \$kdms/manufacturer
echo "Tether USB Device" >| \$kdms/product

mkdir -p \$kdmcs
echo "ECM network" >| \$kdcms/configuration
echo 250 >| \$kdmc/MaxPower
echo 0x80 >| \$kdmc/bmAttributes
mkdir -p \$kdmf
echo "$decmmac" >| \$kdmf/dev_addr
echo "$hecmmac" >| \$kdmf/host_addr
ln -s \$kdmf \$kdmc

# Configure RNDIS
mkdir -p \$kdmc2
echo 0x80 >| \$kdmc2/bmAttributes
echo 0x250 >| \$kdmc2/MaxPower
mkdir -p \$kdmcs2
echo "RNDIS network" >| \$kdmcs2/configuration
 
echo "1" >| \$kdm/os_desc/use
echo "0xcd" >| \$kdm/os_desc/b_vendor_code
echo "MSFT100" >| \$kdm/os_desc/qw_sign

mkdir -p \$kdmfr
echo "RNDIS" >|   \$kdmfr/os_desc/interface.rndis/compatible_id
echo "5162001" >| \$kdmfr/os_desc/interface.rndis/sub_compatible_id
echo "$decmmac" >| \$kdmfr/dev_addr
echo "$hecmmac" >| \$kdmfr/host_addr
 
ln -s \$kdmfr \$kdmc2
ln -s \$kdmc2 \$kdm/os_desc

#ls /sys/class/udc >| \$kdm/UDC
EOF
    chmod 755 /usr/local/bin/sdm-gadget-init
}

function setmanaged() {
    # Remove the rule setting gadget devices to be unmanaged
    logtoboth "> Plugin $pfx: Update udev rules settings for gadget mode"
    sed 's/^[^#]*gadget/#\ &/' /usr/lib/udev/rules.d/85-nm-unmanaged.rules >| /etc/udev/rules.d/85-nm-unmanaged.rules
    #sed 's/^[^#]*gadget/#\ &/' /usr/lib/udev/rules.d/85-nm-unmanaged.rules | sed '/"gadget"/a ENV{DEVTYPE}=="gadget", ENV{NM_UNMANAGED}="0"' >| /etc/udev/rules.d/85-nm-unmanaged.rules

}

function updateconfig() {
    logtoboth "> Plugin $pfx: Update config.txt with 'dtoverlay=dwc2'"
    [ "$(tail -1 /boot/firmware/config.txt)" != "[all]" ] && printf "\n[all]\n" >> /boot/firmware/config.txt
    printf "dtoverlay=dwc2\n" >> /boot/firmware/config.txt
}

function updatecmdline() {
    local dmac="$1" hmac smac

    if [ "$dmac" != "" ]
    then
	hmac="$(makehostmac $dmac)"
	smac=" g_ether.dev_addr=$dmac g_ether.host_addr=$hmac"
    fi
    [ -v eem ] && smac=" $smac g_ether.use_eem=0"
    logtoboth "> Plugin $pfx: Update cmdline.txt with 'modules-load=dwc2,g_ether${smac}'"
    sed -i "s/rootwait/rootwait modules-load=dwc2,g_ether${smac}/" /boot/firmware/cmdline.txt 
}

# $1 is the phase: "0", "1", or "post-install"
# $2 is the argument list: arg1=val1|arg2=val2|arg3=val3| ...
#
# Main code for the Plugin
#
phase=$1
pfx="$(basename $0)"     #For messages
args="$2"
loadparams
vldargs="|autoconnect-retries|dhcp-timeout|eem|gadget-mode|gadget-name|mac-vendor|static-mac|noipv6|"
rqdargs=""                   # |list|of|required|args|or|nullstring|
assetdir="$SDMPT/etc/sdm/assets/$pfx"

if [ "$phase" == "0" ]
then
    #
    # In Phase 0 all references to directories in the image must be preceded by $SDMPT
    #
    logtoboth "* Plugin $pfx: Start Phase 0"
    plugin_getargs $pfx "$args" "$vldargs" "$rqdargs" || exit
    plugin_printkeys
    mkdir -p $assetdir
    [ "$gadget__mode" == "" ] && gadget__mode="simple"
    [[ "simple|shared|host" =~ $gadget__mode ]] || logtobothex "? Plugin $pfx: Unrecognized gadget-mode setting '$gadget__mode'"
    [ "$gadget__mode" == "host" ] && logtobothex "? Plugin $pfx: Use the sdm 'hotspot' plugin to achieve gadget-mode 'host'"
    if [ "$static__mac" != "" ]
    then
	[[ "$static__mac" =~ ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ ]] || logtobothex "? Plugin $pfx: Static MAC '$static__mac' is invalid"
	# Check LBS=multicast of 2nd digit and disallow
	[ $((0x${static__mac:1:1} & 1)) -ne 0 ] && logtobothex "? Plugin $pfx: Static MAC '$static__mac' is a multicast MAC"
	[ $((0x${static__mac:1:1} & 2)) -ne 0 ] && logtoboth "% Plugin $pfx: Static MAC '$static__mac' is a Locally Administered MAC"
	[ "$mac__vendor" != "" ] && logtoboth "% Plugin $pfx: mac-vendor '$mac__vendor' not used with Static MAC '$static__mac'"
    fi
    if [ "$mac__vendor" != "" ]
    then
	[[ "$mac__vendor" =~ ^([a-fA-F0-9]{2}:){2}[a-fA-F0-9]{2}$ ]] || logtobothex "? Plugin $pfx: mac-vendor '$mac__vendor' is invalid"
	[ $((0x${mac__vendor:1:1} & 1)) -ne 0 ] && logtobothex "? Plugin $pfx: mac-vendor '$mac__vendor' has the multicast bit set"
	[ $((0x${mac__vendor:1:1} & 2)) -ne 0 ] && logtoboth "% Plugin $pfx: mac-vendor '$mac__vendor' is a Locally Administered MAC"
    fi

    logtoboth "* Plugin $pfx: Complete Phase 0"

elif [ "$phase" == "1" ]
then
    #
    # Phase 1 (in nspawn)
    #
    logtoboth "* Plugin $pfx: Start Phase 1"
    plugin_getargs $pfx "$args" "$vldargs" "$rqdargs"
    #logfreespace "at start of Plugin $pfx Phase 1"

    [ "$gadget__mode" == "" ] && gadget__mode="simple"
    [ "$mac__vendor" == "" ] && mac__vendor="dc:a6:32"
    [ -v static__mac ] && [ "$static__mac" == "" ] && static__mac=$(makerandmac "$mac__vendor")

    if [[ "shared|simple" =~ $gadget__mode ]]
    then
	logtoboth "> Plugin $pfx: Configure '$gadget__mode' gadget mode with MAC '$static__mac'"
	updateconfig
	updatecmdline "$static__mac"
	[ "$gadget__mode" == "shared" ] && makeservice "$gadget__name" "$static__mac"
	setmanaged
	makenmconn 0
	#makenmconn 1
    fi

    #logfreespace "at end of $pfx Phase 1"
    logtoboth "* Plugin $pfx: Complete Phase 1"
elif [ "$phase" == "post-install" ]
then
    #
    # Plugin Post-install edits
    #
    logtoboth "* Plugin $pfx: Start Phase post-install"
    plugin_getargs $pfx "$args" "$vldargs" "$rqdargs"
    #logfreespace "at start of Plugin $pfx Phase post-install"

    #logfreespace "at end of $pfx Custom Phase post-install"
    logtoboth "* Plugin $pfx: Complete Phase post-install"
fi
