001package com.github.theholywaffle.teamspeak3;
002
003/*
004 * #%L
005 * TeamSpeak 3 Java API
006 * %%
007 * Copyright (C) 2016 Bert De Geyter, Roger Baumgartner
008 * %%
009 * Permission is hereby granted, free of charge, to any person obtaining a copy
010 * of this software and associated documentation files (the "Software"), to deal
011 * in the Software without restriction, including without limitation the rights
012 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
013 * copies of the Software, and to permit persons to whom the Software is
014 * furnished to do so, subject to the following conditions:
015 * 
016 * The above copyright notice and this permission notice shall be included in
017 * all copies or substantial portions of the Software.
018 * 
019 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
020 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
021 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
022 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
023 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
024 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
025 * THE SOFTWARE.
026 * #L%
027 */
028
029import com.github.theholywaffle.teamspeak3.api.wrapper.FileTransferParameters;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import java.io.IOException;
034import java.io.InputStream;
035import java.io.OutputStream;
036import java.net.Socket;
037import java.util.concurrent.atomic.AtomicInteger;
038import java.util.zip.CRC32;
039
040public class FileTransferHelper {
041
042        private static final Logger log = LoggerFactory.getLogger(FileTransferHelper.class);
043        private static final int BUFFER_SIZE = 16_384; // 16 kB
044
045        // Can only be in the range 0 - 65535
046        private final AtomicInteger clientTransferId = new AtomicInteger(0);
047        private final String defaultHost;
048
049        FileTransferHelper(String host) {
050                defaultHost = host;
051        }
052
053        // FILES
054
055        public void downloadFile(OutputStream dataOut, FileTransferParameters params) throws IOException {
056                final String host = getHostFromResponse(params.getFileServerHost());
057                final int port = params.getFileServerPort();
058                final long dataLength = params.getFileSize();
059                final int downloadId = params.getClientTransferId() + 1;
060
061                log.info("[Download {}] Download started", downloadId);
062                try (Socket socket = new Socket(host, port)) {
063                        socket.setReceiveBufferSize(BUFFER_SIZE);
064                        int actualSize = socket.getReceiveBufferSize();
065
066                        OutputStream out = socket.getOutputStream();
067                        out.write(params.getFileTransferKey().getBytes("UTF-8"));
068                        out.flush();
069
070                        InputStream in = socket.getInputStream();
071                        byte[] buffer = new byte[actualSize];
072                        long total = 0;
073                        while (total < dataLength) {
074                                int read = in.read(buffer);
075                                if (read < 0) throw new IOException("Server response contained less data than specified");
076                                total += read;
077                                if (total > dataLength) throw new IOException("Server response contained more data than specified");
078                                dataOut.write(buffer, 0, read);
079                        }
080                        log.info("[Download {}] Download finished", downloadId);
081                } catch (IOException e) {
082                        // Log and re-throw
083                        log.warn("[Download {}] Download failed: {}", downloadId, e.getMessage());
084                        throw e;
085                }
086        }
087
088        public void uploadFile(InputStream dataIn, long dataLength, FileTransferParameters params) throws IOException {
089                final String host = getHostFromResponse(params.getFileServerHost());
090                final int port = params.getFileServerPort();
091                final int uploadId = params.getClientTransferId() + 1;
092
093                log.info("[Upload {}] Upload started", uploadId);
094                try (Socket socket = new Socket(host, port)) {
095                        socket.setSendBufferSize(BUFFER_SIZE);
096                        int actualSize = socket.getSendBufferSize();
097
098                        OutputStream out = socket.getOutputStream();
099                        out.write(params.getFileTransferKey().getBytes("UTF-8"));
100                        out.flush();
101
102                        byte[] buffer = new byte[actualSize];
103                        long total = 0;
104                        while (total < dataLength) {
105                                int toRead = (int) Math.min(actualSize, dataLength - total);
106                                int read = dataIn.read(buffer, 0, toRead);
107                                if (read < 0) throw new IOException("User stream did not contain enough data");
108                                total += read;
109                                out.write(buffer, 0, read);
110                        }
111                        log.info("[Upload {}] Upload finished", uploadId);
112                } catch (IOException e) {
113                        // Log and re-throw
114                        log.warn("[Upload {}] Upload failed: {}", uploadId, e.getMessage());
115                        throw e;
116                }
117        }
118
119        // ICONS
120
121        public long getIconId(byte[] data) {
122                final CRC32 crc32 = new CRC32();
123                crc32.update(data);
124                return crc32.getValue();
125        }
126
127        // UTIL
128
129        public byte[] readFully(InputStream dataIn, long dataLength) throws IOException {
130                if (dataLength > Integer.MAX_VALUE - 64) throw new IOException("File too large");
131
132                final int len = (int) dataLength;
133                byte[] data = new byte[len];
134                int total = 0;
135                while (total < len) {
136                        int read = dataIn.read(data, total, len - total);
137                        if (read < 0) throw new IOException("User stream did not contain enough data");
138                        total += read;
139                }
140                return data;
141        }
142
143        public int getClientTransferId() {
144                // AtomicInteger#getAndUpdate without Java 8
145                int prev, next;
146                do {
147                        prev = clientTransferId.get();
148                        next = (prev + 1) & 0xFFFF;
149                } while (!clientTransferId.compareAndSet(prev, next));
150                return prev;
151        }
152
153        private String getHostFromResponse(String raw) {
154                if (raw == null || raw.isEmpty()) return defaultHost;
155                if (raw.startsWith("0.0.0.0")) return defaultHost;
156                int firstComma = raw.indexOf(',');
157                if (firstComma <= 0) return defaultHost;
158                return raw.substring(0, firstComma);
159        }
160}