#region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Outfile=.\SSDPdiscover.exe
#AutoIt3Wrapper_Compression=4
#AutoIt3Wrapper_Res_Description=
#AutoIt3Wrapper_Res_Fileversion=0.0.0.1
#AutoIt3Wrapper_Res_LegalCopyright=(c) by Rainer Ullrich
#AutoIt3Wrapper_Res_Language=1031
#AutoIt3Wrapper_Res_Field=Dateiname|SSDPdiscover
#AutoIt3Wrapper_Res_Field=Email|rainer@rainerullrich.de
#AutoIt3Wrapper_Run_After=del ".\SSDPdiscover*.au3.tbl"
#AutoIt3Wrapper_Run_After=del ".\SSDPdiscover*_Obfuscated.au3"
#AutoIt3Wrapper_Run_Obfuscator=y
#AutoIt3Wrapper_AU3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
#Obfuscator_Parameters=/cs=1 /cn=1 /cf=1 /cv=1 /sf=1 /sv=1
#endregion ;**** Directives created by AutoIt3Wrapper_GUI ****

#include <Date.au3>
#include <Array.au3>

Opt("MustDeclareVars", 1)

; --- MainVar-Deklaration ---
#region MainVar-Deklaration
; Für SSDPdiscover
Global $MyCollectedResponses ; Array mit den gesammelten Responses
Global $MyCollectedIPs ; Array mit den gesammelten IPs
Global $MyZeitZumSuchen = 15 * 1000 ; Zeit zum Suchen in Ticks
Global $MySendenIntervall = 3 * 1000; Alle x Ticks wird der ssdp-discover gesendet
Global $MyResultDatei = ".\ssdp-discover-result.txt"
Global $MyTemp, $MyError, $MyReturn

; MsgBox
Global Const $Mnormal = 0 + 262144
Global Const $Merror = 16 + 262144
Global Const $Mquestion = 32 + 262144
Global Const $Mattention = 48 + 262144
Global Const $Mresult = 64 + 262144
#endregion MainVar-Deklaration


; --- Main ---
#region Main
; -- SSDP Discxover via UPnP --
$MyReturn = SSDPdiscover_V1($MyCollectedResponses, $MyCollectedIPs, $MyZeitZumSuchen, $MySendenIntervall) ; hier ändern, also ..._V1 oder ..._V2   <-------------
$MyError = @error
If $MyError <> 0 Then
	$MyTemp = "SSDPdiscover ist gescheitert!" & @CRLF & @CRLF
	Switch $MyError
		Case 0
			; nix
		Case 1
			$MyTemp &= "UDPOpen-Fehler"
		Case 2
			$MyTemp &= "UDPSend-Fehler"
		Case 3
			$MyTemp &= "UDPRecv-Fehler"
		Case 4
			$MyTemp &= "UDPBind-Fehler"
	EndSwitch
	MsgBox($Merror, "Fehler:", $MyTemp)
	Exit
EndIf


; -- Ergebnisausgabe --
; File löschen
If FileExists($MyResultDatei) Then
	If Not FileDelete($MyResultDatei) Then
		MsgBox($Merror, "Fehler:", 'Die Datei "' & $MyResultDatei & '" konnte nicht gelöscht werden!')
		Exit
	EndIf
EndIf

; Responses
If IsArray($MyCollectedResponses) Then
	;  _ArrayDisplay($MyCollectedResponses, "Gesammelte Responses")
	$MyTemp = ''
	For $e = 1 To $MyCollectedResponses[0]
		$MyTemp &= $MyCollectedResponses[$e] & @CRLF
	Next
	If FileWrite($MyResultDatei, $MyTemp) Then
		; Quickhack un die Datei zu öffnen
		Run(@ComSpec & ' /c start "" "' & $MyResultDatei & '"', @WorkingDir, @SW_HIDE)
		MsgBox($Mresult, "Ergebnis:", 'Das Ergebnis wurde in die Datei "' & $MyResultDatei & '" geschrieben!')
	Else
		MsgBox($Mresult, "Ergebnis:", 'Das Ergebnis konnte nicht in die Datei "' & $MyResultDatei & '" geschrieben werden!')
	EndIf
Else
	MsgBox(0, "Ergebnis:", "Es wurden via UPnP keine Responses geliefert!")
EndIf

; IP
If IsArray($MyCollectedIPs) Then
	_ArrayDisplay($MyCollectedIPs, "Gesammelte IPs")
Else
	MsgBox($Mresult, "Ergebnis:", "Es wurden via UPnP keine Boxen gefunden!")
EndIf
#endregion Main



; --- Funktionsdefinitionen ---
#region Funktionsdefinitionen
; Funktion, die via UPnP einen ssdp-discover durchführt und die Ergebnisse in ein Array schreibt.
; Außerdem werden die IP-Adressen der gefundenen Geräte in ein extra Array geschrieben
; V1: Receive-Socket = Send-Socket. Funktioniert unter XP immer und in Win 7 nur dann, wenn alle bis auf eine Netzwerkkarte im Gerätemanager deaktiviert ist
; Error:
;   0  =  kein Error
;   1  =  UDPOpen-Fehler
;   2  =  UDPSend-Fehler
;   3  =  UDPRecv-Fehler
;   4  =  UDPBind-Fehler
Func SSDPdiscover_V1(ByRef $ResponsesArray, ByRef $IPArray, $TicksToSearch = 10000, $SendintervallInTicks = 1000)

	; --- UPnP-Kommando ---
	Local Const $UPnPcmd = _
			'M-SEARCH * HTTP/1.1' & @CRLF & _
			'ST:upnp:rootdevice' & @CRLF & _
			'MX: 10' & @CRLF & _
			'MAN: "ssdp:discover"' & @CRLF & _
			'HOST: 239.255.255.250:1900' & @CRLF & _
			@CRLF
	Local $UPNPsendSocket, $UPNPreceiveSocket
	Local $SendCounter = 0, $ReceiveCounter = 0
	Local $ReceiveData, $NewIP
	Local $StartTimeoutTimer, $UsedTimeoutTicks ; Timeout-Timer
	Local $StartSendeTimer, $UsedSendeTicks ; Send-Timer
	Local $OldRestSekunden = -99, $RestSekunden
	Local $return = 1, $error = 0

	; Arrays "löschen"
	$ResponsesArray = ""
	$IPArray = ""

	; UPnP-Kommando ausgeben
	ConsoleWrite(@CRLF)
	ConsoleWrite("UPnP-Kommando:" & @CRLF)
	ConsoleWrite($UPnPcmd & @CRLF)

	; UDP starten
	UDPStartup()

	; - Sender -
	;    $array[1] contains the real socket, $array[2] contains the specified IP address and $array[3] contains the port
	$UPNPsendSocket = UDPOpen("239.255.255.250", 1900)
	ConsoleWrite("SendSocket: real socket: " & $UPNPsendSocket[1] & ", IP-address: " & $UPNPsendSocket[2] & ", port: " & $UPNPsendSocket[3] & @CRLF & @CRLF)
	; _ArrayDisplay($UPNPsendSocket)
	If $UPNPsendSocket[0] == -1 Or $UPNPsendSocket[0] == 0 Then ; Doku etwas schwammig
		; Error 1
		Return SetError(1, 0, 0)
	EndIf

	; - Empfänger -
	; Socket muß der gleiche sein, sonst geht es nicht. Allerdings funktioniert es nicht auf Win 7-Rechnern mit mehreren Netzwerkkarten :-(
	$UPNPreceiveSocket = $UPNPsendSocket

	; Timer setzen
	$StartTimeoutTimer = TimerInit() ; Timeout-Timer
	$StartSendeTimer = -99 ; Notlösung, kann man schöner programmieren

	While 1

		; SenderPause berechnen
		If $StartSendeTimer = -99 Then
			; Timer wurde bisher noch nicht gesetzt, daher UsedTicks setzen
			$UsedSendeTicks = $SendintervallInTicks + 10
		Else
			; Berechnen
			$UsedSendeTicks = TimerDiff($StartSendeTimer)
		EndIf

		; Senden
		If $UsedSendeTicks > $SendintervallInTicks Then
			$SendCounter += 1
			ConsoleWrite("UPnP Send Count Nr " & $SendCounter & @CRLF & @CRLF)
			UDPSend($UPNPsendSocket, $UPnPcmd)
			If @error <> 0 Then
				$error = 2
				$return = 0
				ExitLoop
			EndIf
			; Timer rücksetzen
			$StartSendeTimer = TimerInit()
		EndIf

		; kurze Pause (nach dem Senden)
		Sleep(100)

		; Empfangen
		$ReceiveData = UDPRecv($UPNPreceiveSocket, 1024)
		If @error <> 0 Then
			$error = 3
			$return = 0
			ExitLoop
		EndIf
		If $ReceiveData <> "" Then
			$ReceiveCounter += 1
			ConsoleWrite("-------------------- Received Response " & $ReceiveCounter & ":" & " --------------------" & @CRLF)
			ConsoleWrite($ReceiveData & @CRLF)
			; Wenn neue Responds, dann hinzugügen
			If AddItemToArray($ResponsesArray, $ReceiveData, 1) > 0 Then
				ConsoleWrite("-> hinzugefügt" & @CRLF & @CRLF)
			Else
				ConsoleWrite("-> bereits vorhanden" & @CRLF & @CRLF)
			EndIf
			; IP extrahieren und sammeln
			$NewIP = FetchIPfromUPNPdata($ReceiveData)
			If $NewIP <> "" Then AddItemToArray($IPArray, $NewIP, 1)
		EndIf

		; Verbrauchte Zeit ermitteln
		$UsedTimeoutTicks = TimerDiff($StartTimeoutTimer)
		; Wenn die Zeit verstrichen ist, dann raus
		If $UsedTimeoutTicks >= $TicksToSearch Then
			ExitLoop
		Else
			; Restsekunden berechnen und als TrayTip ausgeben
			$RestSekunden = Ceiling(($TicksToSearch - $UsedTimeoutTicks) / 1000)
			If $RestSekunden <> $OldRestSekunden Then
				TrayTip("SSPD-Discover", "Devices via UPnP suchen (noch " & $RestSekunden & " Sekunden)... ", 5, 1)
				$OldRestSekunden = $RestSekunden
			EndIf
		EndIf
	WEnd

	; TrayTip schliessen
	TrayTip("", "", 0)

	; Socket schliessen
	UDPCloseSocket($UPNPsendSocket)
	UDPCloseSocket($UPNPreceiveSocket)

	; UDP beenden
	UDPShutdown()

	; Sortieren
	If IsArray($IPArray) Then _ArraySort($IPArray, 0, 1)
	If IsArray($ResponsesArray) Then _ArraySort($ResponsesArray, 0, 1)

	; Wenn Fehler vorhanden, dann Fehler zurückliefern
	If $error > 0 Then
		; Fehler zurückliefern
		Return SetError($error, 0, $return)
	Else
		; Normaler Return
		Return $return
	EndIf

EndFunc   ;==>SSDPdiscover_V1



; Funktion, die via UPnP einen ssdp-discover durchführt und die Ergebnisse in ein Array schreibt.
; Außerdem werden die IP-Adressen der gefundenen Geräte in ein extra Array geschrieben
; V2: Receive-Socket und Send-Socket sind unterschiedlich. Ich hab mal verschiedene Dinge durchprobiert, keine funktioniert.
; Error:
;   0  =  kein Error
;   1  =  UDPOpen-Fehler
;   2  =  UDPSend-Fehler
;   3  =  UDPRecv-Fehler
;   4  =  UDPBind-Fehler
Func SSDPdiscover_V2(ByRef $ResponsesArray, ByRef $IPArray, $TicksToSearch = 10000, $SendintervallInTicks = 1000)

	; --- UPnP-Kommando ---
	Local Const $UPnPcmd = _
			'M-SEARCH * HTTP/1.1' & @CRLF & _
			'ST:upnp:rootdevice' & @CRLF & _
			'MX: 10' & @CRLF & _
			'MAN: "ssdp:discover"' & @CRLF & _
			'HOST: 239.255.255.250:1900' & @CRLF & _
			@CRLF
	Local $UPNPsendSocket, $UPNPreceiveSocket
	Local $SendCounter = 0, $ReceiveCounter = 0
	Local $ReceiveData, $NewIP
	Local $StartTimeoutTimer, $UsedTimeoutTicks ; Timeout-Timer
	Local $StartSendeTimer, $UsedSendeTicks ; Send-Timer
	Local $OldRestSekunden = -99, $RestSekunden
	Local $return = 1, $error = 0

	; Arrays "löschen"
	$ResponsesArray = ""
	$IPArray = ""

	; UPnP-Kommando ausgeben
	ConsoleWrite(@CRLF)
	ConsoleWrite("UPnP-Kommando:" & @CRLF)
	ConsoleWrite($UPnPcmd & @CRLF)

	; UDP starten
	UDPStartup()

	; - Sender -
	;    $array[1] contains the real socket, $array[2] contains the specified IP address and $array[3] contains the port
	$UPNPsendSocket = UDPOpen("239.255.255.250", 1900)
	ConsoleWrite("SendSocket: real socket: " & $UPNPsendSocket[1] & ", IP-address: " & $UPNPsendSocket[2] & ", port: " & $UPNPsendSocket[3] & @CRLF & @CRLF)
	; _ArrayDisplay($UPNPsendSocket)
	If $UPNPsendSocket[0] == -1 Or $UPNPsendSocket[0] == 0 Then ; Doku etwas schwammig
		; Error 1
		Return SetError(1, 0, 0)
	EndIf

	; - Empfänger -
	If @IPAddress1 <> "0.0.0.0" Then ConsoleWrite(@IPAddress1 & @CRLF)
	If @IPAddress2 <> "0.0.0.0" Then ConsoleWrite(@IPAddress2 & @CRLF)
	If @IPAddress3 <> "0.0.0.0" Then ConsoleWrite(@IPAddress3 & @CRLF)
	If @IPAddress4 <> "0.0.0.0" Then ConsoleWrite(@IPAddress4 & @CRLF)
	; $UPNPreceiveSocket = UDPBind("127.0.0.1", 1900)
	; $UPNPreceiveSocket = UDPBind(@IPAddress1, 1900)
	; $UPNPreceiveSocket = UDPBind("127.0.0.1", $UPNPsendSocket[1])
	; $UPNPreceiveSocket = UDPBind(@IPAddress1, $UPNPsendSocket[1])
	; $UPNPreceiveSocket = UDPBind("239.255.255.250", 1900)
	$UPNPreceiveSocket = UDPBind(@IPAddress1, 1900)
	ConsoleWrite("ReceiveSocket: real socket: " & $UPNPreceiveSocket[1] & ", IP-address: " & $UPNPreceiveSocket[2] & ", port: " & $UPNPreceiveSocket[3] & @CRLF & @CRLF)
	; _ArrayDisplay($UPNPsendSocket)
	If $UPNPreceiveSocket[0] == -1 Or $UPNPreceiveSocket[0] == -0 Then ; Doku etwas schwammig
		; Error 4
		Return SetError(4, 0, 0)
	EndIf

	; Timer setzen
	$StartTimeoutTimer = TimerInit() ; Timeout-Timer
	$StartSendeTimer = -99 ; Notlösung, kann man schöner programmieren

	While 1

		; SenderPause berechnen
		If $StartSendeTimer = -99 Then
			; Timer wurde bisher noch nicht gesetzt, daher UsedTicks setzen
			$UsedSendeTicks = $SendintervallInTicks + 10
		Else
			; Berechnen
			$UsedSendeTicks = TimerDiff($StartSendeTimer)
		EndIf

		; Senden
		If $UsedSendeTicks > $SendintervallInTicks Then
			$SendCounter += 1
			ConsoleWrite("UPnP Send Count Nr " & $SendCounter & @CRLF & @CRLF)
			UDPSend($UPNPsendSocket, $UPnPcmd)
			If @error <> 0 Then
				$error = 2
				$return = 0
				ExitLoop
			EndIf
			; Timer rücksetzen
			$StartSendeTimer = TimerInit()
		EndIf

		; kurze Pause (nach dem Senden)
		Sleep(100)

		; Empfangen
		$ReceiveData = UDPRecv($UPNPreceiveSocket, 1024)
		If @error <> 0 Then
			$error = 3
			$return = 0
			ExitLoop
		EndIf
		If $ReceiveData <> "" Then
			$ReceiveCounter += 1
			ConsoleWrite("-------------------- Received Response " & $ReceiveCounter & ":" & " --------------------" & @CRLF)
			ConsoleWrite($ReceiveData & @CRLF)
			; Wenn neue Responds, dann hinzugügen
			If AddItemToArray($ResponsesArray, $ReceiveData, 1) > 0 Then
				ConsoleWrite("-> hinzugefügt" & @CRLF & @CRLF)
			Else
				ConsoleWrite("-> bereits vorhanden" & @CRLF & @CRLF)
			EndIf
			; IP extrahieren und sammeln
			$NewIP = FetchIPfromUPNPdata($ReceiveData)
			If $NewIP <> "" Then AddItemToArray($IPArray, $NewIP, 1)
		EndIf

		; Verbrauchte Zeit ermitteln
		$UsedTimeoutTicks = TimerDiff($StartTimeoutTimer)
		; Wenn die Zeit verstrichen ist, dann raus
		If $UsedTimeoutTicks >= $TicksToSearch Then
			ExitLoop
		Else
			; Restsekunden berechnen und als TrayTip ausgeben
			$RestSekunden = Ceiling(($TicksToSearch - $UsedTimeoutTicks) / 1000)
			If $RestSekunden <> $OldRestSekunden Then
				TrayTip("SSPD-Discover", "Devices via UPnP suchen (noch " & $RestSekunden & " Sekunden)... ", 5, 1)
				$OldRestSekunden = $RestSekunden
			EndIf
		EndIf
	WEnd

	; TrayTip schliessen
	TrayTip("", "", 0)

	; Socket schliessen
	UDPCloseSocket($UPNPsendSocket)
	UDPCloseSocket($UPNPreceiveSocket)

	; UDP beenden
	UDPShutdown()

	; Sortieren
	If IsArray($IPArray) Then _ArraySort($IPArray, 0, 1)
	If IsArray($ResponsesArray) Then _ArraySort($ResponsesArray, 0, 1)

	; Wenn Fehler vorhanden, dann Fehler zurückliefern
	If $error > 0 Then
		; Fehler zurückliefern
		Return SetError($error, 0, $return)
	Else
		; Normaler Return
		Return $return
	EndIf

EndFunc   ;==>SSDPdiscover_V2



; Funktion, die aus den ReceivedPacket die IP-Adresse extrahiert
Func FetchIPfromUPNPdata($Data)

	#cs Beispiel:
		
		HTTP/1.1 200 OK
		LOCATION: http://192.168.5.1:49000/igddesc.xml
		SERVER: WLAN_VDSL_Ullrich UPnP/1.0 AVM FRITZ!Box Fon WLAN 7270 54.04.74
		CACHE-CONTROL: max-age=1800
		EXT:
		ST: upnp:rootdevice
		USN: uuid:75802409-bccb-40e7-8e6c-001F3F56E239::upnp:rootdevice
	#ce

	; --- Einfach mit RegExp die IP rausfiltern ---
	; Local $IP = StringRegExp($Data, "LOCATION: http://\d+\.\d+\.\d+\.\d+:49", 1)
	Local $IP = StringRegExp($Data, "\d+\.\d+\.\d+\.\d+", 1)
	; _ArrayDisplay($IP)

	; Scheun ob es ein Array ist, wenn nicht raus
	If Not IsArray($IP) Then Return ""

	; Ich bauche erstes Element aus dem RegExp-Ergebnis
	$IP = $IP[0]

	; Überprüfen auf Gültigkeit
	If Not _IsIPv4($IP) Then Return ""

	Return $IP

EndFunc   ;==>FetchIPfromUPNPdata



; Funktion, die überprüft, ob es eine gültige IP4-IP ist
Func _IsIPv4($S_IP)
	If StringRegExp($S_IP, "\A(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])\z") Then Return (1)
	Return (0)
EndFunc   ;==>_IsIPv4



; Funktion, die ein Item an das Array (mit Zelle) hinzufügt und dabei den Counter in Zelle 0 um eins erhöht
; Ist das Array noch leer, wird es angelegt. Es wird der Counter zurückgeliefert
; Wenn $OnlyIfNew = 1, dann wird vorher geschaut, ob es schon im Array vorhanden ist
Func AddItemToArray(ByRef $ArrayMitZelleNull, $value, $OnlyIfNew = 0) ; Fügt zum Array ein Item hinzu und erhöht den Wert in Zelle 0. Ist das Array leer, wird eines erzeugt

	; Schauen, ob es ein Array ist. Wenn nicht, dann wird es angelegt und der Wert hinzugefügt
	If IsArray($ArrayMitZelleNull) Then
		; es ist ein Array
		; ggf. schauen, ob es bereits enthalten ist
		If $OnlyIfNew == 1 Then
			; Raus, wenn es bereist enthalten ist, also größer als 0
			If _ArraySearch($ArrayMitZelleNull, $value, 1) > 0 Then
				Return 0
			EndIf
		EndIf

		; Element hinzufügen
		Local $ret = _ArrayAdd($ArrayMitZelleNull, $value)
		; wenn ungleich -1 dann den Counter erhöhen
		If $ret <> -1 Then
			; um 1 erhöhren in Zelle 0
			Local $Count = $ArrayMitZelleNull[0] + 1
			$ArrayMitZelleNull[0] = $Count
			; Index, also Count zurückliefern
			Return $Count
		Else
			Return -1
		EndIf
	Else
		; Es ist kein Array, daher erzeugen und Wert hinzufügen
		Dim $ArrayMitZelleNull[2]
		$ArrayMitZelleNull[0] = 1
		$ArrayMitZelleNull[1] = $value
		Return 1
	EndIf

EndFunc   ;==>AddItemToArray
#endregion Funktionsdefinitionen