2019-03-17 19:45:51 +03:00
package nsusbloader.NET ;
import javafx.concurrent.Task ;
2019-03-19 03:10:00 +03:00
import nsusbloader.NSLDataTypes.EFileStatus ;
import nsusbloader.ModelControllers.LogPrinter ;
2019-03-19 07:01:30 +03:00
import nsusbloader.NSLDataTypes.EMsgType ;
2019-03-17 19:45:51 +03:00
import java.io.* ;
import java.net.* ;
2019-03-18 19:12:11 +03:00
import java.nio.ByteBuffer ;
import java.nio.charset.StandardCharsets ;
2019-03-19 07:01:30 +03:00
import java.util.* ;
2019-03-17 19:45:51 +03:00
2019-03-18 19:12:11 +03:00
public class NETCommunications extends Task < Void > { // todo: thows IOException?
2019-03-17 19:45:51 +03:00
2019-03-19 03:10:00 +03:00
private LogPrinter logPrinter ;
2019-03-17 19:45:51 +03:00
private String hostIP ;
2019-03-18 19:12:11 +03:00
private int hostPort ;
2019-03-19 07:01:30 +03:00
private String extras ;
2019-03-17 19:45:51 +03:00
private String switchIP ;
2019-03-18 19:12:11 +03:00
private HashMap < String , File > nspMap ;
2019-03-17 19:45:51 +03:00
private ServerSocket serverSocket ;
2019-03-19 07:01:30 +03:00
private boolean isValid ;
private boolean doNotServeRequests ;
2019-03-20 00:56:44 +03:00
private OutputStream currSockOS ;
private PrintWriter currSockPW ;
2019-03-19 07:01:30 +03:00
/ * *
* Simple constructor that everybody uses
* * /
2019-03-19 17:44:39 +03:00
public NETCommunications ( List < File > filesList , String switchIP , boolean doNotServeRequests , String hostIPaddr , String hostPortNum , String extras ) {
this . doNotServeRequests = doNotServeRequests ;
if ( doNotServeRequests )
this . extras = extras ;
else
this . extras = "" ;
2019-03-17 19:45:51 +03:00
this . switchIP = switchIP ;
2019-03-19 17:44:39 +03:00
this . logPrinter = new LogPrinter ( ) ;
2019-03-18 19:12:11 +03:00
this . nspMap = new HashMap < > ( ) ;
2019-03-19 07:01:30 +03:00
// Collect and encode NSP files list
2019-03-18 19:12:11 +03:00
try {
for ( File nspFile : filesList )
2019-03-26 03:30:55 +03:00
nspMap . put ( URLEncoder . encode ( nspFile . getName ( ) , "UTF-8" ) . replaceAll ( "\\+" , "%20" ) , nspFile ) ; // replace + to %20
2019-03-18 19:12:11 +03:00
}
catch ( UnsupportedEncodingException uee ) {
2019-03-19 07:01:30 +03:00
isValid = false ;
2019-08-14 05:37:58 +03:00
logPrinter . print ( "NET: Unsupported encoding for 'URLEncoder'. Internal issue you can't fix. Please report. Returned:\n\t" + uee . getMessage ( ) , EMsgType . FAIL ) ;
2019-03-19 07:01:30 +03:00
for ( File nspFile : filesList )
nspMap . put ( nspFile . getName ( ) , nspFile ) ;
close ( EFileStatus . FAILED ) ;
return ;
2019-03-18 19:12:11 +03:00
}
2019-03-19 07:01:30 +03:00
// Resolve IP
2019-03-19 17:44:39 +03:00
if ( hostIPaddr . isEmpty ( ) ) {
DatagramSocket socket ;
try { // todo: check other method if internet unavaliable
2019-03-19 07:01:30 +03:00
socket = new DatagramSocket ( ) ;
2019-03-19 17:44:39 +03:00
socket . connect ( InetAddress . getByName ( "8.8.8.8" ) , 10002 ) ; // Google
2019-03-19 07:01:30 +03:00
hostIP = socket . getLocalAddress ( ) . getHostAddress ( ) ;
socket . close ( ) ;
2019-03-26 03:30:55 +03:00
}
catch ( SocketException | UnknownHostException e ) {
2019-03-19 17:44:39 +03:00
logPrinter . print ( "NET: Can't get your computer IP using Google DNS server. Returned:\n\t" + e . getMessage ( ) , EMsgType . INFO ) ;
2019-03-19 07:01:30 +03:00
try {
socket = new DatagramSocket ( ) ;
2019-03-19 17:44:39 +03:00
socket . connect ( InetAddress . getByName ( "193.0.14.129" ) , 10002 ) ; // RIPE NCC
2019-03-19 07:01:30 +03:00
hostIP = socket . getLocalAddress ( ) . getHostAddress ( ) ;
socket . close ( ) ;
2019-03-26 03:30:55 +03:00
}
catch ( SocketException | UnknownHostException e1 ) {
2019-03-19 17:44:39 +03:00
logPrinter . print ( "NET: Can't get your computer IP using RIPE NCC root server. Returned:\n\t" + e1 . getMessage ( ) , EMsgType . INFO ) ;
2019-03-19 07:01:30 +03:00
try {
2019-03-19 17:44:39 +03:00
socket = new DatagramSocket ( ) ;
socket . connect ( InetAddress . getByName ( "people.com.cn" ) , 10002 ) ; // Renmin Ribao
hostIP = socket . getLocalAddress ( ) . getHostAddress ( ) ;
socket . close ( ) ;
2019-03-26 03:30:55 +03:00
}
catch ( SocketException | UnknownHostException e2 ) {
2019-03-19 17:44:39 +03:00
logPrinter . print ( "NET: Can't get your computer IP using Renmin Ribao server. Returned:\n\t" + e2 . getMessage ( ) , EMsgType . FAIL ) ;
logPrinter . print ( "Try using 'Expert mode' and set IP manually." , EMsgType . INFO ) ;
try {
Enumeration enumeration = NetworkInterface . getNetworkInterfaces ( ) ;
while ( enumeration . hasMoreElements ( ) ) {
NetworkInterface n = ( NetworkInterface ) enumeration . nextElement ( ) ;
Enumeration enumeration1 = n . getInetAddresses ( ) ;
while ( enumeration1 . hasMoreElements ( ) ) {
InetAddress i = ( InetAddress ) enumeration1 . nextElement ( ) ;
logPrinter . print ( "Check for: " + i . getHostAddress ( ) , EMsgType . INFO ) ;
}
2019-03-19 07:01:30 +03:00
}
2019-03-26 03:30:55 +03:00
}
catch ( SocketException socketException ) { // Good block.
2019-03-19 17:44:39 +03:00
logPrinter . print ( "Can't determine possible variants. Returned:\n\t" + socketException . getMessage ( ) , EMsgType . FAIL ) ;
2019-03-19 07:01:30 +03:00
}
2019-03-19 17:44:39 +03:00
isValid = false ;
close ( EFileStatus . FAILED ) ;
return ;
2019-03-19 07:01:30 +03:00
}
2019-03-19 17:44:39 +03:00
}
}
logPrinter . print ( "NET: Your IP detected as: " + hostIP , EMsgType . PASS ) ;
}
else {
this . hostIP = hostIPaddr ;
logPrinter . print ( "NET: Your IP defined as: " + hostIP , EMsgType . PASS ) ;
}
// Get port
2019-03-26 03:30:55 +03:00
if ( ! doNotServeRequests ) {
if ( hostPortNum . isEmpty ( ) ) {
Random portRandomizer = new Random ( ) ;
for ( int i = 0 ; i < 5 ; i + + ) {
try {
this . hostPort = portRandomizer . nextInt ( 999 ) + 6000 ;
serverSocket = new ServerSocket ( hostPort ) ; //System.out.println(serverSocket.getInetAddress()); 0.0.0.0
logPrinter . print ( "NET: Your port detected as: " + hostPort , EMsgType . PASS ) ;
break ;
}
catch ( IOException ioe ) {
if ( i = = 4 ) {
logPrinter . print ( "NET: Can't find good port" , EMsgType . FAIL ) ;
logPrinter . print ( "Try using 'Expert mode' and set port by yourself." , EMsgType . INFO ) ;
isValid = false ;
close ( EFileStatus . FAILED ) ;
return ;
} else
logPrinter . print ( "NET: Can't use port " + hostPort + "\nLooking for another one." , EMsgType . WARNING ) ;
}
}
} else {
2019-03-19 17:44:39 +03:00
try {
2019-03-26 03:30:55 +03:00
this . hostPort = Integer . parseInt ( hostPortNum ) ;
serverSocket = new ServerSocket ( hostPort ) ;
logPrinter . print ( "NET: Using defined port number: " + hostPort , EMsgType . PASS ) ;
}
catch ( NumberFormatException nfe ) { // Literally never happens.
logPrinter . print ( "NET: Can't use port defined in settings: " + hostPortNum + "\nIt's not a valid number!" , EMsgType . FAIL ) ;
isValid = false ;
close ( EFileStatus . FAILED ) ;
return ;
}
catch ( IOException ioex ) {
logPrinter . print ( "NET: Can't use port defined in settings: " + hostPortNum + "\n\t" + ioex . getMessage ( ) , EMsgType . FAIL ) ;
isValid = false ;
close ( EFileStatus . FAILED ) ;
return ;
2019-03-19 07:01:30 +03:00
}
}
2019-03-17 19:45:51 +03:00
}
2019-03-19 17:44:39 +03:00
else {
2019-03-26 03:30:55 +03:00
if ( hostPortNum . isEmpty ( ) ) {
logPrinter . print ( "NET: Port must be defined if 'Don't serve requests' option selected!" , EMsgType . FAIL ) ;
isValid = false ;
close ( EFileStatus . FAILED ) ;
return ;
}
2019-03-19 07:01:30 +03:00
try {
2019-03-26 03:30:55 +03:00
this . hostPort = Integer . parseInt ( hostPortNum ) ;
2019-03-19 07:01:30 +03:00
}
2019-03-26 03:30:55 +03:00
catch ( NumberFormatException fex ) {
2019-03-19 17:44:39 +03:00
logPrinter . print ( "NET: Can't use port defined in settings: " + hostPortNum + "\nIt's not a valid number!" , EMsgType . WARNING ) ;
2019-03-20 03:07:08 +03:00
isValid = false ;
2019-03-19 17:44:39 +03:00
close ( EFileStatus . FAILED ) ;
return ;
2019-03-19 07:01:30 +03:00
}
2019-03-17 19:45:51 +03:00
}
2019-03-19 07:01:30 +03:00
isValid = true ;
2019-03-18 19:12:11 +03:00
}
2019-03-19 07:01:30 +03:00
/ * *
2019-03-19 17:44:39 +03:00
* Override cancel block to close connection by ourselves
2019-03-19 07:01:30 +03:00
* * /
2019-03-19 17:44:39 +03:00
@Override
protected void cancelled ( ) {
this . close ( EFileStatus . UNKNOWN ) ;
super . cancelled ( ) ;
2019-03-19 07:01:30 +03:00
}
2019-03-20 03:07:08 +03:00
2019-03-17 19:45:51 +03:00
@Override
2019-03-18 19:12:11 +03:00
protected Void call ( ) {
2019-03-19 17:44:39 +03:00
if ( ! isValid | isCancelled ( ) )
2019-03-19 07:01:30 +03:00
return null ;
logPrinter . print ( "\tStart chain" , EMsgType . INFO ) ;
2019-03-19 17:44:39 +03:00
// Create string that we'll send to TF and which initiates chain
2019-03-18 19:12:11 +03:00
StringBuilder myStrBuilder ;
myStrBuilder = new StringBuilder ( ) ;
for ( String fileNameEncoded : nspMap . keySet ( ) ) {
myStrBuilder . append ( hostIP ) ;
myStrBuilder . append ( ':' ) ;
myStrBuilder . append ( hostPort ) ;
myStrBuilder . append ( '/' ) ;
2019-03-19 07:01:30 +03:00
myStrBuilder . append ( extras ) ;
2019-03-18 19:12:11 +03:00
myStrBuilder . append ( fileNameEncoded ) ;
myStrBuilder . append ( '\n' ) ;
}
2019-03-17 19:45:51 +03:00
2019-03-18 19:12:11 +03:00
byte [ ] nspListNames = myStrBuilder . toString ( ) . getBytes ( StandardCharsets . UTF_8 ) ; // Follow the
byte [ ] nspListSize = ByteBuffer . allocate ( Integer . BYTES ) . putInt ( nspListNames . length ) . array ( ) ; // defining order
2019-03-17 19:45:51 +03:00
2019-03-18 19:12:11 +03:00
try {
Socket handShakeSocket = new Socket ( InetAddress . getByName ( switchIP ) , 2000 ) ;
OutputStream os = handShakeSocket . getOutputStream ( ) ;
os . write ( nspListSize ) ;
os . write ( nspListNames ) ;
os . flush ( ) ;
handShakeSocket . close ( ) ;
}
catch ( IOException uhe ) {
2019-03-19 17:44:39 +03:00
logPrinter . print ( "NET: Unable to connect to NS and send files list. Returned:\n\t" + uhe . getMessage ( ) , EMsgType . FAIL ) ;
close ( EFileStatus . UNKNOWN ) ;
return null ;
}
// Check if we should serve requests
if ( this . doNotServeRequests ) {
logPrinter . print ( "NET: List of files transferred. Replies won't be served." , EMsgType . PASS ) ;
close ( EFileStatus . UNKNOWN ) ;
2019-03-18 19:12:11 +03:00
return null ;
}
2019-03-19 17:44:39 +03:00
logPrinter . print ( "NET: Initiation files list has been sent to NS." , EMsgType . PASS ) ;
2019-03-18 19:12:11 +03:00
// Go transfer
2019-03-20 00:56:44 +03:00
Socket clientSocket ;
work_routine :
while ( true ) {
try {
clientSocket = serverSocket . accept ( ) ;
BufferedReader br = new BufferedReader (
new InputStreamReader ( clientSocket . getInputStream ( ) )
) ;
2019-03-18 19:12:11 +03:00
2019-03-20 00:56:44 +03:00
currSockOS = clientSocket . getOutputStream ( ) ;
currSockPW = new PrintWriter ( new OutputStreamWriter ( currSockOS ) ) ;
2019-03-18 19:12:11 +03:00
2019-03-20 00:56:44 +03:00
String line ;
LinkedList < String > tcpPacket = new LinkedList < > ( ) ;
2019-03-18 19:12:11 +03:00
2019-03-20 00:56:44 +03:00
while ( ( line = br . readLine ( ) ) ! = null ) {
2019-03-20 03:07:08 +03:00
//System.out.println(line); // TODO: remove DBG
2019-03-20 00:56:44 +03:00
if ( line . trim ( ) . isEmpty ( ) ) { // If TCP packet is ended
if ( handleRequest ( tcpPacket ) ) // Proceed required things
break work_routine ;
tcpPacket . clear ( ) ; // Clear data and wait for next TCP packet
}
else
tcpPacket . add ( line ) ; // Otherwise collect data
2019-03-18 19:12:11 +03:00
}
2019-03-20 03:07:08 +03:00
// and reopen client sock
2019-03-20 00:56:44 +03:00
clientSocket . close ( ) ;
}
catch ( IOException ioe ) { // If server socket closed, then client socket also closed.
break ;
2019-03-17 19:45:51 +03:00
}
}
2019-03-19 17:44:39 +03:00
if ( ! isCancelled ( ) )
close ( EFileStatus . UNKNOWN ) ;
2019-03-17 19:45:51 +03:00
return null ;
}
2019-03-18 19:12:11 +03:00
2019-03-20 00:56:44 +03:00
// 200 206 400 (inv range) 404 416 (Range Not Satisfiable )
/ * *
* Handle requests
* @return true if failed
* * /
private boolean handleRequest ( LinkedList < String > packet ) {
//private boolean handleRequest(LinkedList<String> packet, OutputStreamWriter pw){
2019-03-19 03:10:00 +03:00
File requestedFile ;
2019-03-20 00:56:44 +03:00
requestedFile = nspMap . get ( packet . get ( 0 ) . replaceAll ( "(^[A-z\\s]+/)|(\\s+?.*$)" , "" ) ) ;
if ( ! requestedFile . exists ( ) | | requestedFile . length ( ) = = 0 ) { // well.. tell 404 if file exists with 0 length is against standard, but saves time
currSockPW . write ( NETPacket . getCode404 ( ) ) ;
currSockPW . flush ( ) ;
logPrinter . print ( "NET: File " + requestedFile . getName ( ) + " doesn't exists or have 0 size. Returning 404" , EMsgType . FAIL ) ;
logPrinter . update ( requestedFile , EFileStatus . FAILED ) ;
return true ;
}
2019-03-18 19:12:11 +03:00
if ( packet . get ( 0 ) . startsWith ( "HEAD" ) ) {
2019-03-20 00:56:44 +03:00
currSockPW . write ( NETPacket . getCode200 ( requestedFile . length ( ) ) ) ;
currSockPW . flush ( ) ;
logPrinter . print ( "NET: Replying for requested file: " + requestedFile . getName ( ) , EMsgType . INFO ) ;
return false ;
2019-03-19 03:10:00 +03:00
}
if ( packet . get ( 0 ) . startsWith ( "GET" ) ) {
2019-03-20 00:56:44 +03:00
for ( String line : packet ) {
if ( line . toLowerCase ( ) . startsWith ( "range" ) ) { //todo: fix
try {
2019-03-19 03:10:00 +03:00
String [ ] rangeStr = line . toLowerCase ( ) . replaceAll ( "^range:\\s+?bytes=" , "" ) . split ( "-" , 2 ) ;
2019-03-20 00:56:44 +03:00
if ( ! rangeStr [ 0 ] . isEmpty ( ) & & ! rangeStr [ 1 ] . isEmpty ( ) ) { // If both ranges defined: Read requested
if ( Long . parseLong ( rangeStr [ 0 ] ) > Long . parseLong ( rangeStr [ 1 ] ) ) { // If start bytes greater then end bytes
currSockPW . write ( NETPacket . getCode400 ( ) ) ;
currSockPW . flush ( ) ;
logPrinter . print ( "NET: Requested range for " + requestedFile . getName ( ) + " is incorrect. Returning 400" , EMsgType . FAIL ) ;
logPrinter . update ( requestedFile , EFileStatus . FAILED ) ;
return true ;
}
if ( writeToSocket ( requestedFile , Long . parseLong ( rangeStr [ 0 ] ) , Long . parseLong ( rangeStr [ 1 ] ) ) ) // DO WRITE
return true ;
2019-03-19 03:10:00 +03:00
}
2019-03-20 00:56:44 +03:00
else if ( ! rangeStr [ 0 ] . isEmpty ( ) ) { // If only START defined: Read all
if ( writeToSocket ( requestedFile , Long . parseLong ( rangeStr [ 0 ] ) , requestedFile . length ( ) ) ) // DO WRITE
return true ;
2019-03-19 03:10:00 +03:00
}
2019-03-20 00:56:44 +03:00
else if ( ! rangeStr [ 1 ] . isEmpty ( ) ) { // If only END defined: Try to read last 500 bytes
if ( requestedFile . length ( ) > 500 ) {
if ( writeToSocket ( requestedFile , requestedFile . length ( ) - 500 , requestedFile . length ( ) ) ) // DO WRITE
return true ;
}
else { // If file smaller than 500 bytes
currSockPW . write ( NETPacket . getCode416 ( ) ) ;
currSockPW . flush ( ) ;
logPrinter . print ( "NET: File size requested for " + requestedFile . getName ( ) + " while actual size of it: " + requestedFile . length ( ) + ". Returning 416" , EMsgType . FAIL ) ;
logPrinter . update ( requestedFile , EFileStatus . FAILED ) ;
return true ;
}
2019-03-19 03:10:00 +03:00
}
else {
2019-03-20 00:56:44 +03:00
currSockPW . write ( NETPacket . getCode400 ( ) ) ; // If Range not defined: like "Range: bytes=-"
currSockPW . flush ( ) ;
logPrinter . print ( "NET: Requested range for " + requestedFile . getName ( ) + " is incorrect (empty start & end). Returning 400" , EMsgType . FAIL ) ;
2019-03-19 03:10:00 +03:00
logPrinter . update ( requestedFile , EFileStatus . FAILED ) ;
2019-03-20 00:56:44 +03:00
return true ;
2019-03-19 03:10:00 +03:00
}
2019-03-20 00:56:44 +03:00
break ;
}
catch ( NumberFormatException nfe ) {
currSockPW . write ( NETPacket . getCode400 ( ) ) ;
currSockPW . flush ( ) ;
logPrinter . print ( "NET: Requested range for " + requestedFile . getName ( ) + " has incorrect format. Returning 400\n\t" + nfe . getMessage ( ) , EMsgType . FAIL ) ;
logPrinter . update ( requestedFile , EFileStatus . FAILED ) ;
return true ;
2019-03-19 03:10:00 +03:00
}
2019-03-20 00:56:44 +03:00
}
2019-03-19 03:10:00 +03:00
}
2019-03-18 19:12:11 +03:00
}
2019-03-20 00:56:44 +03:00
return false ;
}
/ * *
* Send files .
* * /
private boolean writeToSocket ( File file , long start , long end ) {
2019-03-26 03:30:55 +03:00
logPrinter . print ( "NET: Responding to requested range: " + start + "-" + end , EMsgType . INFO ) ;
2019-03-20 00:56:44 +03:00
currSockPW . write ( NETPacket . getCode206 ( file . length ( ) , start , end ) ) ;
currSockPW . flush ( ) ;
try {
2019-03-26 03:30:55 +03:00
long count = end - start + 1 ;
BufferedInputStream bis = new BufferedInputStream ( new FileInputStream ( file ) ) ;
int readPice = 8388608 ; // = 8Mb
byte [ ] byteBuf ;
if ( bis . skip ( start ) ! = start ) {
logPrinter . print ( "NET: Unable to skip requested range." , EMsgType . FAIL ) ;
logPrinter . update ( file , EFileStatus . FAILED ) ;
return true ;
}
long currentOffset = 0 ;
while ( currentOffset < count ) {
if ( isCancelled ( ) )
return true ;
if ( ( currentOffset + readPice ) > = count ) {
readPice = Math . toIntExact ( count - currentOffset ) ;
}
byteBuf = new byte [ readPice ] ;
2019-03-20 00:56:44 +03:00
2019-03-26 03:30:55 +03:00
if ( bis . read ( byteBuf ) ! = readPice ) {
logPrinter . print ( "NET: Reading of file stream suddenly ended." , EMsgType . FAIL ) ;
return true ;
}
currSockOS . write ( byteBuf ) ;
//-----------------------------------------/
try {
logPrinter . updateProgress ( ( currentOffset + readPice ) / ( count / 100.0 ) / 100.0 ) ;
} catch ( InterruptedException ie ) {
getException ( ) . printStackTrace ( ) ; // TODO: Do something with this
}
//-----------------------------------------/
currentOffset + = readPice ;
}
currSockOS . flush ( ) ; // TODO: check if this really needed.
bis . close ( ) ;
//-----------------------------------------/
try {
logPrinter . updateProgress ( 1.0 ) ;
}
catch ( InterruptedException ie ) {
getException ( ) . printStackTrace ( ) ; // TODO: Do something with this
}
//-----------------------------------------/
2019-03-20 00:56:44 +03:00
}
catch ( IOException ioe ) {
2019-03-20 17:22:48 +03:00
logPrinter . print ( "NET: File transmission failed. Returned:\n\t" + ioe . getMessage ( ) , EMsgType . FAIL ) ;
logPrinter . update ( file , EFileStatus . FAILED ) ;
2019-03-20 00:56:44 +03:00
return true ;
}
return false ;
2019-03-18 19:12:11 +03:00
}
2019-03-19 07:01:30 +03:00
/ * *
* Close when done
* * /
private void close ( EFileStatus status ) {
2019-03-20 00:56:44 +03:00
if ( isCancelled ( ) )
logPrinter . print ( "NET: Interrupted by user." , EMsgType . INFO ) ;
2019-03-19 07:01:30 +03:00
try {
2019-03-26 03:30:55 +03:00
if ( serverSocket ! = null ) {
serverSocket . close ( ) ;
logPrinter . print ( "NET: Closing server socket." , EMsgType . PASS ) ;
}
2019-03-19 07:01:30 +03:00
}
2019-03-19 17:44:39 +03:00
catch ( IOException | NullPointerException ioe ) {
2019-03-19 07:01:30 +03:00
logPrinter . print ( "NET: Closing server socket failed. Sometimes it's not an issue." , EMsgType . WARNING ) ;
}
if ( status ! = null ) {
logPrinter . update ( nspMap , status ) ;
}
logPrinter . print ( "\tEnd chain" , EMsgType . INFO ) ;
logPrinter . close ( ) ;
}
2019-03-20 00:56:44 +03:00
}