diff --git a/README.md b/README.md index c6c7759..283ba8c 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Français by [Stephane Meden (JackFromNice)](https://github.com/JackFromNice) - [ ] XCI support - [ ] File order sort (non-critical) - [ ] More deep file analyze before uploading. -- [] Network mode support for TinFoil +- [x] Network mode support for TinFoil ## Thanks Appreciate assistance and support of both Vitaliy and Konstantin. Without you all this magic would not have happened. diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 5cba9af..cb34835 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -13,12 +13,16 @@ import javafx.stage.FileChooser; import nsusbloader.AppPreferences; import nsusbloader.MediatorControl; import nsusbloader.NET.NETCommunications; +import nsusbloader.NET.NETPacket; import nsusbloader.NSLMain; import nsusbloader.ServiceWindow; import nsusbloader.USB.UsbCommunications; import java.io.File; import java.net.URL; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; @@ -47,14 +51,14 @@ public class NSLMainController implements Initializable { @FXML public NSTableViewController tableFilesListController; // Accessible from Mediator @FXML - private NetTabController NetTabController; + private SettingsController SettingsTabController; @FXML private TextField nsIpTextField; @FXML private Label nsIpLbl; private UsbCommunications usbCommunications; - private Thread usbThread; + private Thread workThread; private String previouslyOpenedPath; @@ -177,7 +181,7 @@ public class NSLMainController implements Initializable { * It's button listener when no transmission executes * */ private void uploadBtnAction(){ - if ((usbThread == null || !usbThread.isAlive())){ + if ((workThread == null || !workThread.isAlive())){ if (choiceProtocol.getSelectionModel().getSelectedItem().equals("GoldLeaf") || ( choiceProtocol.getSelectionModel().getSelectedItem().equals("TinFoil") @@ -188,26 +192,38 @@ public class NSLMainController implements Initializable { if ((nspToUpload = tableFilesListController.getFilesForUpload()) == null) { logArea.setText(resourceBundle.getString("logsNoFolderFileSelected")); return; - }else { + } + else { logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n"); for (File item: nspToUpload) logArea.appendText(" "+item.getAbsolutePath()+"\n"); } usbCommunications = new UsbCommunications(nspToUpload, choiceProtocol.getSelectionModel().getSelectedItem()); - usbThread = new Thread(usbCommunications); - usbThread.setDaemon(true); - usbThread.start(); + workThread = new Thread(usbCommunications); + workThread.setDaemon(true); + workThread.start(); } else { // NET INSTALL OVER TINFOIL - if (NetTabController.isNsIpValidate() && !nsIpTextField.getText().trim().matches("^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$")) + if (SettingsTabController.isNsIpValidate() && !nsIpTextField.getText().trim().matches("^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$")) if (!ServiceWindow.getConfirmationWindow(resourceBundle.getString("windowTitleBadIp"),resourceBundle.getString("windowBodyBadIp"))) return; String nsIP = nsIpTextField.getText().trim(); - if (!NetTabController.getExpertModeSelected()) { - NETCommunications netCommunications = new NETCommunications(nsIP); - usbThread = new Thread(netCommunications); - usbThread.setDaemon(true); - usbThread.start(); + + List nspToUpload; + if (!SettingsTabController.getExpertModeSelected()) { + if ((nspToUpload = tableFilesListController.getFilesForUpload()) == null) { + logArea.setText(resourceBundle.getString("logsNoFolderFileSelected")); + return; + } + else { + logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n"); + for (File item: nspToUpload) + logArea.appendText(" "+item.getAbsolutePath()+"\n"); + } + NETCommunications netCommunications = new NETCommunications(nspToUpload, nsIP); // TODO: move somewhere + workThread = new Thread(netCommunications); + workThread.setDaemon(true); + workThread.start(); } else { // TODO; pass to another constructor @@ -219,8 +235,8 @@ public class NSLMainController implements Initializable { * It's button listener when transmission in progress * */ private void stopBtnAction(){ - if (usbThread != null && usbThread.isAlive()){ - usbCommunications.cancel(false); + if (workThread != null && workThread.isAlive()){ + usbCommunications.cancel(false); // TODO: add something abstract to use also for network } } /** @@ -303,7 +319,8 @@ public class NSLMainController implements Initializable { AppPreferences.getInstance().setRecent(previouslyOpenedPath); AppPreferences.getInstance().setNetUsb(choiceNetUsb.getSelectionModel().getSelectedItem()); AppPreferences.getInstance().setNsIp(nsIpTextField.getText().trim()); - AppPreferences.getInstance().setNsIpValidationNeeded(NetTabController.isNsIpValidate()); - AppPreferences.getInstance().setExpertMode(NetTabController.getExpertModeSelected()); + + AppPreferences.getInstance().setNsIpValidationNeeded(SettingsTabController.isNsIpValidate()); + AppPreferences.getInstance().setExpertMode(SettingsTabController.getExpertModeSelected()); } } diff --git a/src/main/java/nsusbloader/Controllers/NetTabController.java b/src/main/java/nsusbloader/Controllers/SettingsController.java similarity index 95% rename from src/main/java/nsusbloader/Controllers/NetTabController.java rename to src/main/java/nsusbloader/Controllers/SettingsController.java index 272ef87..243ab3d 100644 --- a/src/main/java/nsusbloader/Controllers/NetTabController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsController.java @@ -11,7 +11,7 @@ import nsusbloader.AppPreferences; import java.net.URL; import java.util.ResourceBundle; -public class NetTabController implements Initializable { +public class SettingsController implements Initializable { @FXML private CheckBox validateNSHostNameCb; @@ -25,6 +25,7 @@ public class NetTabController implements Initializable { @Override public void initialize(URL url, ResourceBundle resourceBundle) { validateNSHostNameCb.setSelected(AppPreferences.getInstance().getNsIpValidationNeeded()); + if (AppPreferences.getInstance().getExpertMode()) { expertModeCb.setSelected(true); hostIpLbl.setVisible(true); diff --git a/src/main/java/nsusbloader/NET/NETCommunications.java b/src/main/java/nsusbloader/NET/NETCommunications.java index 12aa4dd..fc24c5a 100644 --- a/src/main/java/nsusbloader/NET/NETCommunications.java +++ b/src/main/java/nsusbloader/NET/NETCommunications.java @@ -1,62 +1,153 @@ package nsusbloader.NET; import javafx.concurrent.Task; +import nsusbloader.NSLMain; import java.io.*; import java.net.*; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +/* +* Add option: don't serve replies +* */ -public class NETCommunications extends Task { // todo: thows IOException +public class NETCommunications extends Task { // todo: thows IOException? private String hostIP; + private int hostPort; private String switchIP; + private HashMap nspMap; + + private ServerSocket serverSocket; - public NETCommunications(String switchIP){ + public NETCommunications(List filesList, String switchIP){ this.switchIP = switchIP; + this.nspMap = new HashMap<>(); + try { + for (File nspFile : filesList) + nspMap.put(URLEncoder.encode(nspFile.getName(), "UTF-8"), nspFile); + } + catch (UnsupportedEncodingException uee){ + uee.printStackTrace(); + return; // TODO: FIX + } + try{ // todo: check other method if internet unavaliable DatagramSocket socket = new DatagramSocket(); socket.connect(InetAddress.getByName("8.8.8.8"), 10002); //193.0.14.129 RIPE NCC hostIP = socket.getLocalAddress().getHostAddress(); - System.out.println(hostIP); + //System.out.println(hostIP); socket.close(); } catch (SocketException | UnknownHostException e){ e.printStackTrace(); } + this.hostPort = 6000; // TODO: fix try { - serverSocket = new ServerSocket(6000); // TODO: randomize + + serverSocket = new ServerSocket(hostPort); // TODO: randomize //System.out.println(serverSocket.getInetAddress()); 0.0.0.0 } catch (IOException ioe){ ioe.printStackTrace(); System.out.println("unable to use socket"); } + } - +/* +Replace everything to ASCII (WEB representation) +calculate +write in first 4 bytes +* */ @Override - protected Void call() throws Exception { - Socket clientSocket = serverSocket.accept(); + protected Void call() { + // Get files list length + StringBuilder myStrBuilder; - InputStream is = clientSocket.getInputStream(); - InputStreamReader isr = new InputStreamReader(is); - BufferedReader br = new BufferedReader(isr); - - OutputStream os = clientSocket.getOutputStream(); - OutputStreamWriter osr = new OutputStreamWriter(os); - PrintWriter pw = new PrintWriter(osr); - - String line; - while ((line = br.readLine()) != null) { - if (line.equals("hello world")) { - pw.write("stop doing it!"); - pw.flush(); - break; - } - System.out.println(line); + myStrBuilder = new StringBuilder(); + for (String fileNameEncoded : nspMap.keySet()) { + myStrBuilder.append(hostIP); + myStrBuilder.append(':'); + myStrBuilder.append(hostPort); + myStrBuilder.append('/'); + myStrBuilder.append(fileNameEncoded); + myStrBuilder.append('\n'); } - serverSocket.close(); + + + byte[] nspListNames = myStrBuilder.toString().getBytes(StandardCharsets.UTF_8); // Follow the + byte[] nspListSize = ByteBuffer.allocate(Integer.BYTES).putInt(nspListNames.length).array(); // defining order + + 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){ + uhe.printStackTrace(); // TODO: FIX: could be [UnknownHostException] + return null; + } + + // Go transfer + try { + Socket clientSocket = serverSocket.accept(); + + InputStream is = clientSocket.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + + OutputStream os = clientSocket.getOutputStream(); + OutputStreamWriter osr = new OutputStreamWriter(os); + PrintWriter pw = new PrintWriter(osr); + + String line; + LinkedList tcpPackeet = new LinkedList<>(); + while ((line = br.readLine()) != null) { + System.out.println(line); // TODO: remove DBG + if (line.trim().isEmpty()) { // If TCP packet is ended + handleRequest(tcpPackeet, pw); // Proceed required things + tcpPackeet.clear(); // Clear data and wait for next TCP packet + } + else + tcpPackeet.add(line); // Otherwise collect data + } + System.out.println("\nDone!"); // reopen client sock + clientSocket.close(); + serverSocket.close(); + } + catch (IOException ioe){ + ioe.printStackTrace(); // TODO: fix + } + return null; } + + // 200 206 400 (inv range) 404 + private void handleRequest(LinkedList packet, PrintWriter pw){ + if (packet.get(0).startsWith("HEAD")){ + File requestedFile = nspMap.get(packet.get(0).replaceAll("(^[A-z\\s]+/)|(\\s+?.*$)", "")); + if (requestedFile == null || !requestedFile.exists()){ + return; //todo: send 404 + } + else { + pw.write(NETPacket.getCode200(requestedFile.length())); + pw.flush(); + System.out.println(requestedFile.getAbsolutePath()+"\n"+NETPacket.getCode200(requestedFile.length())); + } + } + } } diff --git a/src/main/java/nsusbloader/NET/NETPacket.java b/src/main/java/nsusbloader/NET/NETPacket.java new file mode 100644 index 0000000..c6348e8 --- /dev/null +++ b/src/main/java/nsusbloader/NET/NETPacket.java @@ -0,0 +1,36 @@ +package nsusbloader.NET; + +import nsusbloader.NSLMain; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +public class NETPacket { + private static final String CODE_200 = + "HTTP/1.0 200 OK\r\n" + + "Server: NS-USBloader-"+NSLMain.appVersion+"\r\n" + + "Date: %s\r\n" + + "Content-type: application/octet-stream\r\n" + + "Accept-Ranges: bytes\r\n" + + "Content-Range: bytes 0-%d/%d\r\n" + + "Content-Length: %d\r\n" + + "Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT\r\n\r\n"; + private static final String CODE_206 = + "HTTP/1.0 206 Partial Content\r\n"+ + "Server: NS-USBloader-"+NSLMain.appVersion+"\r\n" + + "Date: %s\r\n" + + "Content-type: application/octet-stream\r\n"+ + "Accept-Ranges: bytes\r\n"+ + "Content-Range: bytes %d-%d/%d\r\n"+ + "Content-Length: %d\r\n"+ + "Last-Modified: Mon, 18 Mar 2019 12:57:33 GMT\r\n\r\n"; + + public static String getCode200(long nspFileSize){ + return String.format(CODE_200, ZonedDateTime.now(ZoneId.of("GMT")).format(DateTimeFormatter.RFC_1123_DATE_TIME), nspFileSize-1, nspFileSize, nspFileSize); + } + public static String getCode206(long nspFileSize, long startPos, long endPos){ + return String.format(CODE_206, ZonedDateTime.now(ZoneId.of("GMT")).format(DateTimeFormatter.RFC_1123_DATE_TIME), startPos, endPos, nspFileSize, endPos-startPos+1); + } + +} diff --git a/src/main/resources/NSLMain.fxml b/src/main/resources/NSLMain.fxml index 82a76bd..8fd1b10 100644 --- a/src/main/resources/NSLMain.fxml +++ b/src/main/resources/NSLMain.fxml @@ -65,7 +65,7 @@ - + diff --git a/src/main/resources/NetTab.fxml b/src/main/resources/SettingsTab.fxml similarity index 97% rename from src/main/resources/NetTab.fxml rename to src/main/resources/SettingsTab.fxml index 0802863..962e445 100644 --- a/src/main/resources/NetTab.fxml +++ b/src/main/resources/SettingsTab.fxml @@ -6,7 +6,7 @@ - + diff --git a/src/main/resources/res/app_light.css b/src/main/resources/res/app_light.css index d561906..066a322 100644 --- a/src/main/resources/res/app_light.css +++ b/src/main/resources/res/app_light.css @@ -84,7 +84,7 @@ -fx-background-color: #fefefe; } .dialog-pane > .button-bar > .container{ - -fx-background-color: #2d2d2d; + -fx-background-color: #ebebeb; } .dialog-pane > .label{