Download | Plain Text | No Line Numbers


  1. /*
  2.  * Copyright (c) 2010, Manuel Mausz. All rights reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * - Redistributions of source code must retain the above copyright
  9.  * notice, this list of conditions and the following disclaimer.
  10.  *
  11.  * - Redistributions in binary form must reproduce the above copyright
  12.  * notice, this list of conditions and the following disclaimer in the
  13.  * documentation and/or other materials provided with the distribution.
  14.  *
  15.  * - The names of the authors may not be used to endorse or promote products
  16.  * derived from this software without specific prior written permission.
  17.  *
  18.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  19.  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  20.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  21.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  22.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  23.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  24.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  25.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  26.  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  27.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  28.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29.  */
  30.  
  31. import static java.lang.System.err;
  32. import static java.lang.System.out;
  33.  
  34. import java.nio.ByteBuffer;
  35. import java.nio.channels.ServerSocketChannel;
  36. import java.nio.channels.SocketChannel;
  37. import java.nio.channels.DatagramChannel;
  38.  
  39. import java.util.concurrent.ScheduledExecutorService;
  40. import java.util.concurrent.ScheduledFuture;
  41. import java.util.concurrent.ExecutorService;
  42. import java.util.concurrent.Executors;
  43. import java.util.concurrent.TimeUnit;
  44. import java.util.Collections;
  45. import java.util.Properties;
  46. import java.util.ArrayList;
  47. import java.util.Calendar;
  48. import java.util.Iterator;
  49. import java.util.HashMap;
  50. import java.util.Arrays;
  51. import java.util.Map;
  52. import java.net.*;
  53. import java.io.*;
  54.  
  55. /*
  56.  * Proxy implementation for Lab#1 of DSLab WS10
  57.  * See angabe.pdf for details
  58.  *
  59.  * This code is not documented at all. This is volitional
  60.  *
  61.  * @author Manuel Mausz (0728348)
  62.  */
  63. public class Proxy
  64. {
  65. public class FSRecord
  66. implements Comparable<FSRecord>
  67. {
  68. public final String host;
  69. public final int port;
  70. public int usage;
  71. public boolean online;
  72. public long lastUpdate;
  73.  
  74. FSRecord(String host, int port)
  75. {
  76. this.host = host;
  77. this.port = port;
  78. usage = 0;
  79. online = true;
  80. lastUpdate = Calendar.getInstance().getTimeInMillis();
  81. }
  82.  
  83. public void ping()
  84. {
  85. online = true;
  86. lastUpdate = Calendar.getInstance().getTimeInMillis();
  87. }
  88.  
  89. public boolean equals(FSRecord o)
  90. {
  91. return online == o.online && usage == o.usage;
  92. }
  93.  
  94. public int compareTo(FSRecord o)
  95. {
  96. return usage - o.usage;
  97. }
  98. }
  99.  
  100. public class FSRecords
  101. extends HashMap<String, FSRecord>
  102. {}
  103.  
  104. /*==========================================================================*/
  105.  
  106. public class UserRecord
  107. {
  108. public final String name;
  109. public final String pass;
  110. public int credits;
  111. public ArrayList<String> loggedin;
  112.  
  113. UserRecord(String name, String pass)
  114. {
  115. this.name = name;
  116. this.pass = pass;
  117. credits = 0;
  118. loggedin = new ArrayList<String>();
  119. }
  120. }
  121.  
  122. public class UserRecords
  123. extends HashMap<String, UserRecord>
  124. {}
  125.  
  126. /*==========================================================================*/
  127.  
  128.  
  129. public class ProxyConnection
  130. implements Runnable
  131. {
  132. private final String host;
  133. private final int port;
  134. private FSRecords fileservers;
  135.  
  136. ProxyConnection(String host, int port, FSRecords fileservers)
  137. {
  138. this.host = host;
  139. this.port = port;
  140. this.fileservers = fileservers;
  141. }
  142.  
  143. /*------------------------------------------------------------------------*/
  144.  
  145. public void run()
  146. {
  147. String key = host + ":" + port;
  148. synchronized(fileservers)
  149. {
  150. FSRecord record = fileservers.get(key);
  151. if (record == null)
  152. {
  153. fileservers.put(key, new FSRecord(host, port));
  154. out.println("New fileserver registered: " + key);
  155. }
  156. else
  157. {
  158. if (!record.online)
  159. out.println("Fileserver is online again: " + key);
  160. record.ping();
  161. }
  162. }
  163. }
  164. }
  165.  
  166. /*==========================================================================*/
  167.  
  168. public class UDPSocketReader
  169. implements Runnable
  170. {
  171. private final DatagramChannel dchannel;
  172. private FSRecords fileservers;
  173. private final Object mainLock;
  174. private final ExecutorService pool;
  175.  
  176. UDPSocketReader(DatagramChannel dchannel, FSRecords fileservers,
  177. Object mainLock)
  178. {
  179. this.dchannel = dchannel;
  180. this.fileservers = fileservers;
  181. this.mainLock = mainLock;
  182. this.pool = Executors.newCachedThreadPool();
  183. }
  184.  
  185. /*------------------------------------------------------------------------*/
  186.  
  187. public void run()
  188. {
  189. try
  190. {
  191. ByteBuffer buffer = ByteBuffer.allocate(4);
  192. while(true)
  193. {
  194. buffer.clear();
  195. InetSocketAddress proxyaddr = (InetSocketAddress) dchannel.receive(buffer);
  196. try
  197. {
  198. pool.execute(new ProxyConnection(proxyaddr.getHostName(),
  199. buffer.getInt(0), fileservers));
  200. }
  201. catch(IndexOutOfBoundsException e)
  202. {
  203. /* simple ignore that packet */
  204. }
  205. }
  206. }
  207. catch(IOException e)
  208. {
  209. /* ignore that exception
  210.   * thread will shutdown and unlock the main thread
  211.   * which will shutdown the application
  212.   */
  213. }
  214.  
  215. pool.shutdown();
  216. try
  217. {
  218. if (!pool.awaitTermination(100, TimeUnit.MILLISECONDS))
  219. out.println("Trying to shutdown the UDP Proxy connections. This may take up to 15 seconds...");
  220. if (!pool.awaitTermination(5, TimeUnit.SECONDS))
  221. {
  222. pool.shutdownNow();
  223. if (!pool.awaitTermination(5, TimeUnit.SECONDS))
  224. err.println("Error: UDP Proxy connections did not terminate. You may have to kill that appplication.");
  225. }
  226. }
  227. catch(InterruptedException e)
  228. {
  229. pool.shutdownNow();
  230. }
  231.  
  232. synchronized(mainLock)
  233. {
  234. mainLock.notify();
  235. }
  236. }
  237. }
  238.  
  239. /*==========================================================================*/
  240.  
  241. public class ClientConnection
  242. extends CommandNetwork
  243. implements Runnable
  244. {
  245. private final SocketChannel sock;
  246. private final FSRecords fileservers;
  247. private final UserRecords users;
  248. private final ObjectInputStream clin;
  249. private final ObjectOutputStream clout;
  250. private UserRecord user = null;
  251. private final String clientaddr;
  252.  
  253. private CommandNetwork fscmd;
  254. private FSRecord fileserver = null;
  255. private SocketChannel fsschannel = null;
  256. private ObjectInputStream fsin = null;
  257. private ObjectOutputStream fsout = null;
  258.  
  259. ClientConnection(SocketChannel sock, FSRecords fileservers,
  260. UserRecords users)
  261. throws NoSuchMethodException, IOException
  262. {
  263. this.sock = sock;
  264. this.clout = new ObjectOutputStream(sock.socket().getOutputStream());
  265. this.clin = new ObjectInputStream(new BufferedInputStream(sock.socket().getInputStream()));
  266. this.fileservers = fileservers;
  267. this.users = users;
  268. this.clientaddr = "tcp:/" + sock.socket().getInetAddress() + ":" + sock.socket().getPort();
  269.  
  270. cmdHandler.register("!login", this, "cmdLogin");
  271. cmdHandler.register("!buy", this, "cmdBuy");
  272. cmdHandler.register("!credits", this, "cmdCredits");
  273. cmdHandler.register("!list", this, "cmdList");
  274. cmdHandler.register("!download", this, "cmdDownload");
  275. cmdHandler.register("unknown", this, "cmdUnknown");
  276.  
  277. fscmd = new CommandNetwork();
  278. fscmd.setOneCommandMode(true);
  279. fscmd.cmdHandler.register("!error", this, "cmdFSRelayOutput");
  280. fscmd.cmdHandler.register("!output", this, "cmdFSRelayOutput");
  281. fscmd.cmdHandler.register("!download", this, "cmdFSDownload");
  282. fscmd.cmdHandler.register("unknown", this, "cmdFSRelayOutput");
  283. }
  284.  
  285. /*------------------------------------------------------------------------*/
  286.  
  287. public boolean checkLogin()
  288. throws IOException
  289. {
  290. if (user == null)
  291. {
  292. Utils.sendError(clout, "Not logged in");
  293. return false;
  294. }
  295. return true;
  296. }
  297.  
  298. /*------------------------------------------------------------------------*/
  299.  
  300. public void cmdLogin(String cmd, String[] args)
  301. throws IOException
  302. {
  303. if (user != null)
  304. {
  305. Utils.sendError(clout, "Already logged in");
  306. return;
  307. }
  308.  
  309. if (args.length != 2)
  310. {
  311. Utils.sendError(clout, "Invalid Syntax: !login <username> <password>");
  312. return;
  313. }
  314.  
  315. synchronized(users)
  316. {
  317. UserRecord record = users.get(args[0]);
  318. if (record == null || !record.pass.equals(args[1]))
  319. {
  320. Utils.sendError(clout, "Invalid username or password");
  321. return;
  322. }
  323.  
  324. user = record;
  325. user.loggedin.add(clientaddr);
  326. }
  327.  
  328. Utils.sendOutput(clout, "Successfully logged in");
  329. }
  330.  
  331. /*------------------------------------------------------------------------*/
  332.  
  333. public void cmdBuy(String cmd, String[] args)
  334. throws IOException
  335. {
  336. if (!checkLogin())
  337. return;
  338. if (args.length != 1)
  339. {
  340. Utils.sendError(clout, "Invalid Syntax: !buy <credits>");
  341. return;
  342. }
  343.  
  344. int add = 0;
  345. try
  346. {
  347. add = Integer.parseInt(args[0]);
  348. if (add <= 0)
  349. throw new NumberFormatException("");
  350. }
  351. catch(NumberFormatException e)
  352. {
  353. Utils.sendError(clout, "Credits must be numberic and positive");
  354. return;
  355. }
  356.  
  357. synchronized(users)
  358. {
  359. if (user.credits > Integer.MAX_VALUE - add)
  360. Utils.sendError(clout, "You can't buy that much/more credits");
  361. else
  362. {
  363. user.credits += add;
  364. Utils.sendOutput(clout, "You now have " + user.credits + " credits");
  365. }
  366. }
  367. }
  368.  
  369. /*------------------------------------------------------------------------*/
  370.  
  371. public void cmdCredits(String cmd, String[] args)
  372. throws IOException
  373. {
  374. if (!checkLogin())
  375. return;
  376. if (args.length != 0)
  377. {
  378. Utils.sendError(clout, "Invalid Syntax: !credits");
  379. return;
  380. }
  381.  
  382. synchronized(users)
  383. {
  384. Utils.sendOutput(clout, "You have " + user.credits + " credits left");
  385. }
  386. }
  387.  
  388. /*------------------------------------------------------------------------*/
  389.  
  390. public FSRecord getFileserver()
  391. {
  392. synchronized(fileservers)
  393. {
  394. ArrayList<FSRecord> fslist = new ArrayList<FSRecord>();
  395. for(FSRecord record : fileservers.values())
  396. {
  397. if (!record.online)
  398. continue;
  399. fslist.add(record);
  400. }
  401. if (fslist.size() == 0)
  402. return null;
  403. Collections.sort(fslist);
  404. return fslist.get(0);
  405. }
  406. }
  407.  
  408. /*------------------------------------------------------------------------*/
  409.  
  410. public boolean connectFileserver()
  411. throws IOException
  412. {
  413. disconnectFileserver();
  414.  
  415. fileserver = getFileserver();
  416. if (fileserver == null)
  417. {
  418. Utils.sendError(clout, "Unable to execute command. No fileservers online");
  419. return false;
  420. }
  421.  
  422. try
  423. {
  424. fsschannel = SocketChannel.open(new InetSocketAddress(fileserver.host,
  425. fileserver.port));
  426. fsin = new ObjectInputStream(new BufferedInputStream(
  427. fsschannel.socket().getInputStream()));
  428. fsout = new ObjectOutputStream(fsschannel.socket().getOutputStream());
  429. }
  430. catch(IOException e)
  431. {
  432. err.println("Error: Unable to connect to fileserver: " + e.getMessage());
  433. Utils.sendError(clout, "Unable to connect to fileserver");
  434.  
  435. synchronized(fileservers)
  436. {
  437. fileserver.online = false;
  438. out.println("Fileserver marked as offline: " + fileserver.host + ":" + fileserver.port);
  439. }
  440.  
  441. disconnectFileserver();
  442. return false;
  443. }
  444.  
  445. return true;
  446. }
  447.  
  448. /*------------------------------------------------------------------------*/
  449.  
  450. public void disconnectFileserver()
  451. {
  452. try
  453. {
  454. if (fsin != null)
  455. fsout.flush();
  456. }
  457. catch(IOException e)
  458. {}
  459.  
  460. try
  461. {
  462. if (fsin != null)
  463. fsin.close();
  464. }
  465. catch(IOException e)
  466. {}
  467.  
  468. try
  469. {
  470. if (fsout != null)
  471. fsout.close();
  472. }
  473. catch(IOException e)
  474. {}
  475.  
  476. try
  477. {
  478. if (fsschannel != null)
  479. fsschannel.close();
  480. }
  481. catch(IOException e)
  482. {}
  483.  
  484. fsin = null;
  485. fsout = null;
  486. fileserver = null;
  487. }
  488.  
  489. /*------------------------------------------------------------------------*/
  490.  
  491. public void cmdList(String cmd, String[] args)
  492. throws IOException
  493. {
  494. if (!checkLogin())
  495. return;
  496. if (args.length != 0)
  497. {
  498. Utils.sendError(clout, "Invalid Syntax: !list");
  499. return;
  500. }
  501.  
  502. try
  503. {
  504. if (!connectFileserver())
  505. return;
  506. fsout.writeUTF(cmd + " " + Utils.join(Arrays.asList(args), " "));
  507. fsout.flush();
  508. fscmd.run(fsin);
  509. }
  510. catch(IOException e)
  511. {
  512. Utils.sendError(clout, "Connection to fileserver terminated unexpected");
  513. }
  514. clout.flush();
  515. disconnectFileserver();
  516. }
  517.  
  518. /*------------------------------------------------------------------------*/
  519.  
  520. public void cmdDownload(String cmd, String[] args)
  521. throws IOException
  522. {
  523. if (!checkLogin())
  524. return;
  525. if (args.length != 1)
  526. {
  527. Utils.sendError(clout, "Invalid Syntax: !download <filename>");
  528. return;
  529. }
  530.  
  531. try
  532. {
  533. if (!connectFileserver())
  534. return;
  535. synchronized(users)
  536. {
  537. fsout.writeUTF(cmd + " " + Utils.join(Arrays.asList(args), " ") + " " + user.credits);
  538. }
  539. fsout.flush();
  540. fscmd.run(fsin);
  541. }
  542. catch(IOException e)
  543. {
  544. Utils.sendError(clout, "Connection to fileserver terminated unexpected");
  545. }
  546. clout.flush();
  547. disconnectFileserver();
  548. }
  549.  
  550. /*------------------------------------------------------------------------*/
  551.  
  552. public void cmdUnknown(String cmd, String[] args)
  553. throws IOException
  554. {
  555. err.println("Error: Unknown data from client: " + cmd + " "
  556. + Utils.join(Arrays.asList(args), " "));
  557. Utils.sendError(clout, "Unknown command");
  558. }
  559.  
  560. /*------------------------------------------------------------------------*/
  561.  
  562. public void cmdFSRelayOutput(String cmd, String[] args)
  563. throws IOException
  564. {
  565. long num;
  566. if ((num = Utils.parseHeaderNum(args, 0)) < 0)
  567. return;
  568. String msg;
  569. clout.writeUTF(cmd + " " + Utils.join(Arrays.asList(args), " "));
  570. for (; num > 0 && (msg = fsin.readUTF()) != null; --num)
  571. clout.writeUTF(msg);
  572. }
  573.  
  574. /*------------------------------------------------------------------------*/
  575.  
  576. public void cmdFSDownload(String cmd, String[] args)
  577. throws IOException
  578. {
  579. if (args.length < 2 || args[1].length() <= 0)
  580. {
  581. err.println("Error: Invalid " + cmd + "-command paket from fileserver. Ignoring...");
  582. Utils.sendError(clout, "Internal Error: Invalid packet from fileserver");
  583. return;
  584. }
  585.  
  586. long filesize, filesizecpy;
  587. if ((filesize = Utils.parseHeaderNum(args, 1)) < 0)
  588. return;
  589. filesizecpy = filesize;
  590.  
  591. clout.writeUTF(cmd + " " + Utils.join(Arrays.asList(args), " "));
  592.  
  593. try
  594. {
  595. byte[] buffer = new byte[8 * 1024];
  596. int toread = buffer.length;
  597. while(filesize > 0)
  598. {
  599. if (filesize < toread)
  600. toread = (int) filesize;
  601. int count = fsin.read(buffer, 0, toread);
  602. if (count == -1)
  603. throw new IOException("Connection reset by peer");
  604. clout.write(buffer, 0, count);
  605. filesize -= count;
  606. }
  607.  
  608. synchronized(users)
  609. {
  610. user.credits -= filesizecpy;
  611. }
  612.  
  613. synchronized(fileservers)
  614. {
  615. fileserver.usage += filesizecpy;
  616. }
  617. }
  618. catch(IOException e)
  619. {
  620. err.println("Error during file transfer: " + e.getMessage() + ". Closing connection to client");
  621. stop();
  622.  
  623. synchronized(fileservers)
  624. {
  625. fileserver.usage += filesizecpy;
  626. fileserver.online = false;
  627. out.println("Fileserver marked as offline: " + fileserver.host + ":" + fileserver.port);
  628. }
  629. }
  630. }
  631.  
  632. /*------------------------------------------------------------------------*/
  633.  
  634.  
  635. public void shutdown()
  636. {
  637. disconnectFileserver();
  638.  
  639. try
  640. {
  641. clout.flush();
  642. }
  643. catch(IOException e)
  644. {}
  645.  
  646. try
  647. {
  648. clin.close();
  649. }
  650. catch(IOException e)
  651. {}
  652.  
  653. try
  654. {
  655. clout.close();
  656. }
  657. catch(IOException e)
  658. {}
  659.  
  660. try
  661. {
  662. if (sock.isOpen())
  663. sock.close();
  664. }
  665. catch(IOException e)
  666. {}
  667. }
  668.  
  669. /*------------------------------------------------------------------------*/
  670.  
  671. public void run()
  672. {
  673. try
  674. {
  675. out.println("[" + Thread.currentThread().getId() + "] New client connection from "
  676. + clientaddr);
  677. run(clin);
  678. clout.flush();
  679. }
  680. catch(CommandHandler.Exception e)
  681. {
  682. err.println("Internal Error: " + e.getMessage());
  683. e.printStackTrace();
  684. }
  685. catch(IOException e)
  686. {
  687. /* ignore that exception
  688.   * it's usually a closed connection from client so
  689.   * we can't do anything about it anyway
  690.   */
  691. }
  692.  
  693. if (user != null)
  694. {
  695. synchronized(users)
  696. {
  697. user.loggedin.remove(user.loggedin.indexOf(clientaddr));
  698. }
  699. }
  700.  
  701. out.println("[" + Thread.currentThread().getId() + "] Connection closed");
  702. shutdown();
  703. }
  704. }
  705.  
  706. /*==========================================================================*/
  707.  
  708. public class TCPSocketReader
  709. implements Runnable
  710. {
  711. private final ServerSocketChannel sschannel;
  712. private final FSRecords fileservers;
  713. private final UserRecords users;
  714. private final Object mainLock;
  715. private final ExecutorService pool;
  716.  
  717. TCPSocketReader(ServerSocketChannel sschannel, FSRecords fileservers,
  718. UserRecords users, Object mainLock)
  719. {
  720. this.sschannel = sschannel;
  721. this.fileservers = fileservers;
  722. this.users = users;
  723. this.mainLock = mainLock;
  724. this.pool = Executors.newCachedThreadPool();
  725. }
  726.  
  727. /*------------------------------------------------------------------------*/
  728.  
  729. public void run()
  730. {
  731. try
  732. {
  733. while(true)
  734. pool.execute(new ClientConnection(sschannel.accept(), fileservers, users));
  735. }
  736. catch(NoSuchMethodException e)
  737. {
  738. err.println("Error: Unable to setup remote command handler");
  739. }
  740. catch(IOException e)
  741. {
  742. /* ignore that exception
  743.   * thread will shutdown and unlock the main thread
  744.   * which will shutdown the application
  745.   */
  746. }
  747.  
  748. pool.shutdown();
  749. try
  750. {
  751. if (!pool.awaitTermination(100, TimeUnit.MILLISECONDS))
  752. out.println("Trying to shutdown the client connections. This may take up to 15 seconds...");
  753. if (!pool.awaitTermination(5, TimeUnit.SECONDS))
  754. {
  755. pool.shutdownNow();
  756. if (!pool.awaitTermination(5, TimeUnit.SECONDS))
  757. err.println("Error: Client connections did not terminate. You may have to kill that appplication.");
  758. }
  759. }
  760. catch(InterruptedException e)
  761. {
  762. pool.shutdownNow();
  763. }
  764.  
  765. synchronized(mainLock)
  766. {
  767. mainLock.notify();
  768. }
  769. }
  770. }
  771.  
  772. /*==========================================================================*/
  773.  
  774. public class Interactive
  775. extends CommandInteractive
  776. implements Runnable
  777. {
  778. private final InputStream sin;
  779. private final Object mainLock;
  780.  
  781. Interactive(InputStream sin, Object mainLock)
  782. throws NoSuchMethodException
  783. {
  784. this.sin = sin;
  785. this.mainLock = mainLock;
  786.  
  787. cmdHandler.register("unknown", this, "cmdUnknown");
  788. cmdHandler.register("!fileservers", this, "cmdFileservers");
  789. cmdHandler.register("!users", this, "cmdUsers");
  790. cmdHandler.register("!exit", this, "cmdExit");
  791. }
  792.  
  793. /*------------------------------------------------------------------------*/
  794.  
  795. public void cmdUnknown(String cmd, String[] args)
  796. {
  797. err.println("Unknown command: " + cmd + " "
  798. + Utils.join(Arrays.asList(args), " "));
  799. }
  800.  
  801. /*------------------------------------------------------------------------*/
  802.  
  803. public void cmdFileservers(String cmd, String[] args)
  804. {
  805. synchronized(fileservers)
  806. {
  807. if (fileservers.size() == 0)
  808. out.println("No fileservers registered");
  809. else
  810. {
  811. int line = 1;
  812. int pad = Integer.toString(fileservers.size()).length();
  813. for(Map.Entry<String, FSRecord> entry : fileservers.entrySet())
  814. {
  815. FSRecord record = entry.getValue();
  816. out.println(String.format("%0" + pad + "d. IP: %s, Port: %d, %s, Usage: %d",
  817. line, record.host, record.port,
  818. (record.online) ? "online" : "offline",
  819. record.usage));
  820. ++line;
  821. }
  822. }
  823. }
  824. }
  825.  
  826. /*------------------------------------------------------------------------*/
  827.  
  828. public void cmdUsers(String cmd, String[] args)
  829. {
  830. synchronized(users)
  831. {
  832. if (users.size() == 0)
  833. out.println("No users registered");
  834. else
  835. {
  836. int line = 1;
  837. int pad = Integer.toString(users.size()).length();
  838. for(Map.Entry<String, UserRecord> entry : users.entrySet())
  839. {
  840. UserRecord record = entry.getValue();
  841. out.println(String.format("%0" + pad + "d. User: %s, %s, Credits: %d",
  842. line, record.name,
  843. (record.loggedin.size() > 0) ? "online" : "offline",
  844. record.credits));
  845. for(String host : record.loggedin)
  846. out.println(String.format("%1$#" + (pad + 1) + "s Client: %2$s",
  847. " ", host));
  848. ++line;
  849. }
  850. }
  851. }
  852. }
  853.  
  854. /*------------------------------------------------------------------------*/
  855.  
  856. public void cmdExit(String cmd, String[] args)
  857. {
  858. stop();
  859. }
  860.  
  861. /*------------------------------------------------------------------------*/
  862.  
  863. public void printPrompt()
  864. {
  865. out.print(">: ");
  866. out.flush();
  867. }
  868.  
  869. /*------------------------------------------------------------------------*/
  870.  
  871. public void run()
  872. {
  873. try
  874. {
  875. run(sin);
  876. }
  877. catch(CommandHandler.Exception e)
  878. {
  879. err.println("Internal Error: " + e.getMessage());
  880. }
  881. catch (IOException e)
  882. {
  883. /* ignore that exception
  884.   * thread will shutdown and unlock the main thread
  885.   * which will shutdown the application
  886.   */
  887. }
  888.  
  889. synchronized(mainLock)
  890. {
  891. mainLock.notify();
  892. }
  893. }
  894. }
  895.  
  896. /*==========================================================================*/
  897.  
  898. public class CheckFSTask
  899. implements Runnable
  900. {
  901. private FSRecords fileservers;
  902. private final int fserverTimeout;
  903.  
  904. CheckFSTask(FSRecords fileservers, int fserverTimeout)
  905. {
  906. this.fileservers = fileservers;
  907. this.fserverTimeout = fserverTimeout;
  908. }
  909.  
  910. /*------------------------------------------------------------------------*/
  911.  
  912. public void run()
  913. {
  914. synchronized(fileservers)
  915. {
  916. long curTime = Calendar.getInstance().getTimeInMillis();
  917. for(Map.Entry<String, FSRecord> entry : fileservers.entrySet())
  918. {
  919. if (entry.getValue().online && entry.getValue().lastUpdate + fserverTimeout < curTime)
  920. {
  921. entry.getValue().online = false;
  922. out.println("Fileserver has gone offline: " + entry.getKey());
  923. }
  924. }
  925. }
  926. }
  927. }
  928.  
  929. /*==========================================================================*/
  930.  
  931. private static int tcpPort;
  932. private static int udpPort;
  933. private static int fserverTimeout;
  934. private static int checkPeriod;
  935. private FSRecords fileservers;
  936. private UserRecords users;
  937. private ScheduledExecutorService scheduler = null;
  938. private DatagramChannel dchannel = null;
  939. private Thread tUDPSocketReader = null;
  940. private ServerSocketChannel sschannel = null;
  941. private Thread tTCPSocketReader = null;
  942. private Thread tInteractive = null;
  943. private InputStream stdin = null;
  944. private final Object mainLock = new Object();
  945.  
  946. /*--------------------------------------------------------------------------*/
  947.  
  948. Proxy()
  949. {
  950. fileservers = new FSRecords();
  951. users = new UserRecords();
  952. }
  953.  
  954. /*--------------------------------------------------------------------------*/
  955.  
  956. public static void usage()
  957. throws Utils.Shutdown
  958. {
  959. out.println("Usage: Proxy tcpPort udpPort fserverTimeout checkPeriod\n");
  960. out.println("tcpPort\t\t...the port to be used for instantiating a ServerSocket");
  961. out.println("udpPort\t\t...the port to be used for instantiating a DatagramSocket");
  962. out.println("fserverTimeout\t...the period in milliseconds each fileserver has to send an isAlive packet");
  963. out.println("\t\t if no such packet is received within this time, the fileserver is assumed");
  964. out.println("\t\t to be offline and is no longer available for handling requests");
  965. out.println("checkPeriod\t...specifies that the test whether a fileserver has timed-out or not");
  966. out.println("\t\t (see fileserverTimeout). is repeated every checkPeriod milliseconds");
  967.  
  968. // Java is some piece of crap which doesn't allow me to set exitcode w/o
  969. // using System.exit. Maybe someday Java will be a fully functional
  970. // programming language, but I wouldn't bet my money
  971. //System.exit(1);
  972. throw new Utils.Shutdown("FUCK YOU JAVA");
  973. }
  974.  
  975. /*--------------------------------------------------------------------------*/
  976.  
  977. public void bailout(String error)
  978. throws Utils.Shutdown
  979. {
  980. err.println("Error: " + error);
  981. shutdown();
  982.  
  983. // Java is some piece of crap which doesn't allow me to set exitcode w/o
  984. // using System.exit. Maybe someday Java will be a fully functional
  985. // programming language, but I wouldn't bet my money
  986. //System.exit(2);
  987. throw new Utils.Shutdown("FUCK YOU JAVA");
  988. }
  989.  
  990. /*--------------------------------------------------------------------------*/
  991.  
  992. public void parseArgs(String[] args)
  993. {
  994. if (args.length != 4)
  995. usage();
  996.  
  997. try
  998. {
  999. tcpPort = Integer.parseInt(args[0]);
  1000. if (tcpPort <= 0 || tcpPort > 65536)
  1001. bailout("tcpPort must be a valid port number (1 - 65535)");
  1002. }
  1003. catch(NumberFormatException e)
  1004. {
  1005. bailout("tcpPort must be numeric");
  1006. }
  1007.  
  1008. try
  1009. {
  1010. udpPort = Integer.parseInt(args[1]);
  1011. if (udpPort <= 0 || udpPort > 65536)
  1012. bailout("udpPort must be a valid port number (1 - 65535)");
  1013. }
  1014. catch(NumberFormatException e)
  1015. {
  1016. bailout("udpPort must be numeric");
  1017. }
  1018.  
  1019. try
  1020. {
  1021. fserverTimeout = Integer.parseInt(args[2]);
  1022. if (fserverTimeout <= 0)
  1023. bailout("fserverTimeout must be positive");
  1024. }
  1025. catch(NumberFormatException e)
  1026. {
  1027. bailout("fserverTimeout must be numeric");
  1028. }
  1029.  
  1030. try
  1031. {
  1032. checkPeriod = Integer.parseInt(args[3]);
  1033. if (checkPeriod <= 0)
  1034. bailout("checkPeriod must be positive");
  1035. }
  1036. catch(NumberFormatException e)
  1037. {
  1038. bailout("checkPeriod must be numeric");
  1039. }
  1040. }
  1041.  
  1042. /*--------------------------------------------------------------------------*/
  1043.  
  1044. public void parseUsers(InputStream in)
  1045. throws IOException, IllegalArgumentException
  1046. {
  1047. if (in == null)
  1048. throw new IOException("Properties file doesn't exist");
  1049. Properties props = new Properties();
  1050. props.load(in);
  1051. for(String prop : props.stringPropertyNames())
  1052. {
  1053. String[] pieces = prop.split("\\.", 2);
  1054. String user = pieces[0];
  1055.  
  1056. if (pieces.length == 1)
  1057. users.put(user, new UserRecord(user, props.getProperty(prop)));
  1058. else if (pieces.length == 2)
  1059. {
  1060. UserRecord record = users.get(user);
  1061. if (record == null)
  1062. {
  1063. err.println("Can't load user properties for unknown user '" + user + "'. Skipping...");
  1064. continue;
  1065. }
  1066.  
  1067. if (pieces[1].equals("credits"))
  1068. {
  1069. try
  1070. {
  1071. int credits = Integer.parseInt(props.getProperty(prop));
  1072. if (credits < 0)
  1073. {
  1074. err.println("Property " + prop + " must be positive number. Skipping...");
  1075. continue;
  1076. }
  1077. record.credits = credits;
  1078. }
  1079. catch(NumberFormatException e)
  1080. {
  1081. err.println("Property " + prop + " must be numeric. Skipping...");
  1082. }
  1083. }
  1084. else
  1085. err.println("Property " + prop + " is unknown. Skipping...");
  1086. }
  1087. else
  1088. err.println("Property " + prop + " is unknown. Skipping...");
  1089. }
  1090. }
  1091.  
  1092. /*--------------------------------------------------------------------------*/
  1093.  
  1094. public void shutdown()
  1095. {
  1096. try
  1097. {
  1098. if (scheduler != null)
  1099. {
  1100. scheduler.shutdownNow();
  1101. scheduler.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
  1102. }
  1103. }
  1104. catch(InterruptedException e)
  1105. {}
  1106.  
  1107. try
  1108. {
  1109. if (dchannel != null)
  1110. dchannel.close();
  1111. }
  1112. catch(IOException e)
  1113. {}
  1114.  
  1115. try
  1116. {
  1117. if (tUDPSocketReader != null)
  1118. tUDPSocketReader.join();
  1119. }
  1120. catch(InterruptedException e)
  1121. {}
  1122.  
  1123. try
  1124. {
  1125. if (sschannel != null)
  1126. sschannel.close();
  1127. }
  1128. catch(IOException e)
  1129. {}
  1130.  
  1131. try
  1132. {
  1133. if (tTCPSocketReader != null)
  1134. tTCPSocketReader.join();
  1135. }
  1136. catch(InterruptedException e)
  1137. {}
  1138.  
  1139. try
  1140. {
  1141. if (tInteractive != null)
  1142. {
  1143. tInteractive.interrupt();
  1144. tInteractive.join();
  1145. }
  1146. }
  1147. catch(InterruptedException e)
  1148. {}
  1149.  
  1150. try
  1151. {
  1152. if (stdin != null)
  1153. stdin.close();
  1154. }
  1155. catch(IOException e)
  1156. {}
  1157. }
  1158.  
  1159. /*--------------------------------------------------------------------------*/
  1160.  
  1161. public void run(String[] args)
  1162. {
  1163. parseArgs(args);
  1164.  
  1165. synchronized(users)
  1166. {
  1167. try
  1168. {
  1169. InputStream in = ClassLoader.getSystemResourceAsStream("user.properties");
  1170. if (in == null)
  1171. bailout("Properties file doesn't exist or isn't readable");
  1172. parseUsers(in);
  1173. in.close();
  1174. out.println("Users loaded successfully");
  1175. }
  1176. catch(IOException e)
  1177. {
  1178. bailout("Unable to read from properties file: " + e.getMessage());
  1179. }
  1180. catch(IllegalArgumentException e)
  1181. {
  1182. bailout("Malformed properties file: " + e.getMessage());
  1183. }
  1184. }
  1185.  
  1186. synchronized(mainLock)
  1187. {
  1188. scheduler = Executors.newScheduledThreadPool(1);
  1189. ScheduledFuture<?> checkFSTimer = scheduler.scheduleAtFixedRate(
  1190. new CheckFSTask(fileservers, fserverTimeout),
  1191. 0, checkPeriod, TimeUnit.MILLISECONDS);
  1192.  
  1193. try
  1194. {
  1195. dchannel = DatagramChannel.open();
  1196. dchannel.socket().bind(new InetSocketAddress(udpPort));
  1197. tUDPSocketReader = new Thread(new UDPSocketReader(dchannel, fileservers,
  1198. mainLock));
  1199. tUDPSocketReader.start();
  1200. out.println("Listening on udp:/" + dchannel.socket().getLocalSocketAddress());
  1201. }
  1202. catch(IOException e)
  1203. {
  1204. bailout("Unable to create UDP Socket: " + e.getMessage());
  1205. }
  1206.  
  1207. try
  1208. {
  1209. sschannel = ServerSocketChannel.open();
  1210. sschannel.socket().bind(new InetSocketAddress(tcpPort));
  1211. tTCPSocketReader = new Thread(new TCPSocketReader(sschannel,
  1212. fileservers, users, mainLock));
  1213. tTCPSocketReader.start();
  1214. out.println("Listening on tcp:/" + sschannel.socket().getLocalSocketAddress());
  1215. }
  1216. catch(IOException e)
  1217. {
  1218. bailout("Unable to create TCP Socket: " + e.getMessage());
  1219. }
  1220.  
  1221. try
  1222. {
  1223. InputStream stdin = java.nio.channels.Channels.newInputStream(
  1224. new FileInputStream(FileDescriptor.in).getChannel());
  1225. tInteractive = new Thread(new Interactive(stdin, mainLock));
  1226. tInteractive.start();
  1227. }
  1228. catch(NoSuchMethodException e)
  1229. {
  1230. bailout("Unable to setup interactive command handler");
  1231. }
  1232.  
  1233. out.println("Proxy startup successful!");
  1234. try
  1235. {
  1236. mainLock.wait();
  1237. }
  1238. catch(InterruptedException e)
  1239. {
  1240. /* if we get interrupted -> ignore */
  1241. }
  1242.  
  1243. try
  1244. {
  1245. /* let the threads shutdown */
  1246. Thread.sleep(100);
  1247. }
  1248. catch(InterruptedException e)
  1249. {}
  1250. }
  1251.  
  1252. if (tUDPSocketReader != null && !tUDPSocketReader.isAlive())
  1253. bailout("Listening UDP socket closed unexpected. Terminating...");
  1254. if (tTCPSocketReader != null && !tTCPSocketReader.isAlive())
  1255. bailout("Listening TCP socket closed unexpected. Terminating...");
  1256.  
  1257. shutdown();
  1258. }
  1259.  
  1260. /*--------------------------------------------------------------------------*/
  1261.  
  1262. public static void main(String[] args)
  1263. {
  1264. try
  1265. {
  1266. Proxy proxy = new Proxy();
  1267. proxy.run(args);
  1268. }
  1269. catch(Utils.Shutdown e)
  1270. {}
  1271. }
  1272. }
  1273.