diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78d04e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.sw* +*.dump +*.beam +Mnesia.* +*.swo +games/Pong/build/**/* +*~ +games/Pong/Pong.xcodeproj/project.xcworkspace/**/* +games/Pong/Pong.xcodeproj/project.xcworkspace/xcuserdata/jeena.xcuserdatad/UserInterfaceState.xcuserstate +games/Pong/Pong.xcodeproj/xcuserdata/**/* diff --git a/.gitmodules b/.gitmodules index 2e9d3a7..ef2ff2b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "erlang_js"] - path = erlang_js - url = https://github.com/basho/erlang_js.git -[submodule "erlv8"] - path = erlv8 +[submodule "lib/erlv8"] + path = lib/erlv8 url = https://github.com/beamjs/erlv8.git +[submodule "lib/erlang_js"] + path = lib/erlang_js + url = https://github.com/jonte/erlang_js.git diff --git a/HOWTO b/HOWTO deleted file mode 100644 index 035800e..0000000 --- a/HOWTO +++ /dev/null @@ -1,18 +0,0 @@ -INSTALL -1. Cd into directory where you to have the project -2. git-clone git@github.com:jeena/GGS.git (remember to have a local key) -3. cd GGS/ -4. git submodule init -5. git submodule update -6. cd erlang_js -7. make -8. make test (If not all tests are passed then you are screwed!) -10. cd ../ -11. ./build -12. - -USAGE -1. start a second terminal -2. telnet localhost 7000 -3. back to first terminal -4. ./start diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..52fcf4b --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +ERLC=erlc +ERLCFLAGS=-o +SRCDIR=src +TESTDIR=tests +LIBDIR=lib +BEAMDIR=ebin + +all: compile erlv8 + +compile: + mkdir -p $(BEAMDIR) ; + $(ERLC) $(ERLCFLAGS) $(BEAMDIR) $(SRCDIR)/*.erl ; + +erlang_js: force_look + cd $(LIBDIR)/erlang_js ; $(MAKE) $(MFLAGS); + +erlv8: force_look + cd $(LIBDIR)/erlv8 ; $(MAKE) $(MFLAGS); + +test: + echo "==> test $(MOD)" ; + mkdir -p $(BEAMDIR) ; +ifeq ($(strip $(MOD)),) + $(ERLC) $(ERLCFLAGS) $(BEAMDIR) $(TESTDIR)/*.erl ; + cd $(BEAMDIR) ; erl -noinput -pa ../erlang_js/ebin -eval 'eunit:test({dir, "."}, [verbose]), init:stop()' ; +else + $(ERLC) $(ERLCFLAGS) $(BEAMDIR) $(TESTDIR)/$(MOD)_test.erl ; + cd $(BEAMDIR) ; erl -noinput -pa ../lib/erlv8/ebin -eval 'eunit:test($(MOD)_test, [verbose]), init:stop()' ; +endif + +clean: + rm -rf $(BEAMDIR)/*.beam ; + rm -rf $(SRCDIR)/*.beam ; + rm -rf erl_crush.dump ; + echo "==> clean ggs" ; + $(MAKE) -C $(LIBDIR)/erlang_js/ clean + $(MAKE) -C $(LIBDIR)/erlv8/ clean + +run: + erl \ + -sname ggs \ + -mnesia dir '"/tmp/ggs"' \ + -boot start_sasl \ + -pa $(LIBDIR)/erlv8/ebin/ \ + -pa $(LIBDIR)/eqc/ebin/ \ + -pa ebin \ + -pa src \ + -s start_ggs + +eqc_db: + $(ERLC) -pa $(BEAMDIR) -pa $(LIBDIR)/eqc/ebin -pa $(SRCDIR) -pa $(TESTDIR) $(TESTDIR)/ggs_db_eqc_test.erl + erl -mnesia dir '"/tmp/ggs"' \ + -pa $(LIBDIR)/eqc/ebin/ \ + -pa ebin/ \ + -pa src/ \ + -pa tests/ \ + -s ggs_db_eqc_test + +force_look: + true diff --git a/README b/README index 853cc89..9aa5357 100644 --- a/README +++ b/README @@ -1,3 +1,35 @@ GGS is a Generic Game Server -Check out http://ggs-kandidat.blogspot.com/ \ No newline at end of file +Check out http://ggs-kandidat.blogspot.com/ + +PREREQUISITES: +python version 2.x set to default. + +INSTALL +1. cd into directory where you to have the project +2. git clone git@github.com:jeena/GGS.git (remember to have a local key) +3. cd GGS/ +4. git submodule init +5. git submodule update + +USAGE +1. start a second terminal +2. in new terminal do command: ./python_client 9000 +3. back to first terminal +4. make run + +MAKE +To compile modules (even erlang_js): + make + +To run server: + make run + +To clean (even erlang_js): + make clean + +To compile and run all tests: + make test + +To compile and run one test: + make test MOD=ggs_modulename # (must have /tests/ggs_modulename_test.erl) diff --git a/build b/build deleted file mode 100755 index d468113..0000000 --- a/build +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -for i in `find src -name "*.erl"` -do - erlc -o ebin $i -done diff --git a/build_test b/build_test deleted file mode 100755 index 2f9d630..0000000 --- a/build_test +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -./build - -for i in `find tests -name "*.erl"` -do - erlc -o ebin_test $i -done diff --git a/ebin/ggs.app b/ebin/ggs.app index cafc9df..b3ea744 100644 --- a/ebin/ggs.app +++ b/ebin/ggs.app @@ -4,7 +4,7 @@ {modules, [ ggs_app, ggs_sup, - ggs_server + ggs_dispatcher ]}, {registered, [ggs_sup]}, {applications, [kernel, stdlib]}, diff --git a/echo_test b/echo_test deleted file mode 100755 index 1bbff1f..0000000 --- a/echo_test +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env ruby -wKU - -require 'socket' # Sockets are in standard library - -hostname = 'localhost' -port = 7000 - -print "Which port @ loclhost?" -port = gets - -s = TCPSocket.open(hostname, port.chop) - -s.print("__hello 0") - -while true - line = s.gets # Read lines from the socket - puts ">> " + line.chop # And print with platform line terminator - s.print(gets.chop) -end -s.close # Close the socket when done diff --git a/erlang_js b/erlang_js deleted file mode 160000 index 5350ed2..0000000 --- a/erlang_js +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5350ed21606606dbee5ecb07e974f2abb9106270 diff --git a/erlv8 b/erlv8 deleted file mode 160000 index 688e503..0000000 --- a/erlv8 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 688e5036864eed01f7aefb6ee8b3a4c22961012f diff --git a/games/GGS-Killtrolpanel/ggskpanel.glade b/games/GGS-Killtrolpanel/ggskpanel.glade new file mode 100644 index 0000000..3f85df9 --- /dev/null +++ b/games/GGS-Killtrolpanel/ggskpanel.glade @@ -0,0 +1,135 @@ + + + + + + 561 + 521 + + + True + + + True + + + True + <span size="x-large">GGS Killtrolpanel</span> + True + + + False + 0 + + + + + True + kill_process_icon.jpg + + + False + False + 1 + + + + + False + False + 0 + + + + + True + 3 + 2 + True + + + All players + True + True + True + + + + + + Coordinator + True + True + True + + + + 1 + 2 + + + + + Dispatcher + True + True + True + + + + 1 + 2 + + + + + Coordinator backup + True + True + True + + + + 1 + 2 + 1 + 2 + + + + + All tables + True + True + True + + + + 2 + 3 + + + + + All GameVMs + True + True + True + + + + 1 + 2 + 2 + 3 + + + + + 1 + + + + + + diff --git a/games/GGS-Killtrolpanel/kill_process_icon.jpg b/games/GGS-Killtrolpanel/kill_process_icon.jpg new file mode 100644 index 0000000..7768c3d Binary files /dev/null and b/games/GGS-Killtrolpanel/kill_process_icon.jpg differ diff --git a/games/GGS-Killtrolpanel/kpanel.py b/games/GGS-Killtrolpanel/kpanel.py new file mode 100644 index 0000000..3f8438a --- /dev/null +++ b/games/GGS-Killtrolpanel/kpanel.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +import sys, socket, thread, gobject, getpass, time, os +try: + import pygtk + pygtk.require("2.16") +except: + pass +try: + import gtk + import gtk.glade +except: + sys.exit(1) + +class GGSKPanel: + + def __init__(self): + #Set the Glade file + self.gladefile = "ggskpanel.glade" + self.wTree = gtk.glade.XML(self.gladefile, "window1") + + #Create our dictionay and connect it + dic = { "on_window1_destroy_event" : gtk.main_quit + ,"on_coordinatorButton_clicked" : lambda x: self.terminateProcess("ggs_coordinator") + ,"on_coordinatorBackupButton_clicked" :\ + lambda x: self.terminateProcess("ggs_coordinator_backup") + ,"on_dispatcherButton_clicked" : lambda x: self.terminateProcess("ggs_dispatcher") + } + + self.wTree.signal_autoconnect(dic) + + self.wTree.get_widget("window1").show() + + def terminateProcess(self, process): + os.system("echo \"exit(whereis(%s), 'Bye bye').\" | erl_call -sname ggs -e" % process) + + def setStatus(self, msg): + self.wTree.get_widget("statusbar").push(0, msg) + + +if __name__ == "__main__": + chat = GGSKPanel() + gobject.threads_init() + gtk.main() diff --git a/games/GGSCalc/calc.glade b/games/GGSCalc/calc.glade new file mode 100644 index 0000000..720b7b4 --- /dev/null +++ b/games/GGSCalc/calc.glade @@ -0,0 +1,317 @@ + + + + + + + + True + + + True + True + + + + False + 0 + + + + + True + 5 + 4 + + + True + True + True + + + + + / + True + True + True + + + + 1 + 2 + + + + + * + True + True + True + + + + 2 + 3 + + + + + - + True + True + True + + + + 3 + 4 + + + + + 7 + True + True + True + + + + 1 + 2 + + + + + 8 + True + True + True + + + + 1 + 2 + 1 + 2 + + + + + 9 + True + True + True + + + + 2 + 3 + 1 + 2 + + + + + + + True + True + True + + + + 3 + 4 + 1 + 2 + + + + + 4 + True + True + True + + + + 2 + 3 + + + + + 5 + True + True + True + + + + 1 + 2 + 2 + 3 + + + + + 6 + True + True + True + + + + 2 + 3 + 2 + 3 + + + + + + True + True + True + + + + 3 + 4 + 2 + 3 + + + + + 1 + True + True + True + + + + 3 + 4 + + + + + 2 + True + True + True + + + + 1 + 2 + 3 + 4 + + + + + 3 + True + True + True + + + + 2 + 3 + 3 + 4 + + + + + = + True + True + True + + + + 3 + 4 + 3 + 4 + + + + + 0 + True + True + True + + + + 4 + 5 + + + + + True + True + True + + + 1 + 2 + 4 + 5 + + + + + True + True + True + + + 2 + 3 + 4 + 5 + + + + + + True + True + True + + + + 3 + 4 + 4 + 5 + + + + + 1 + + + + + True + 2 + + + False + 2 + + + + + + diff --git a/games/GGSCalc/calc.py b/games/GGSCalc/calc.py new file mode 100644 index 0000000..8468ceb --- /dev/null +++ b/games/GGSCalc/calc.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +import sys, socket +try: + import pygtk + pygtk.require("2.16") +except: + pass +try: + import gtk + import gtk.glade +except: + sys.exit(1) + +class GGSCalc: + + def __init__(self): + #Set the Glade file + self.gladefile = "calc.glade" + self.wTree = gtk.glade.XML(self.gladefile, "window1") + + #Create our dictionay and connect it + dic = {"on_mainWindow_destroy" : gtk.main_quit + , "on_btn0_clicked" : lambda x: self.OnBtnClick(0) + , "on_btn1_clicked" : lambda x: self.OnBtnClick(1) + , "on_btn2_clicked" : lambda x: self.OnBtnClick(2) + , "on_btn3_clicked" : lambda x: self.OnBtnClick(3) + , "on_btn4_clicked" : lambda x: self.OnBtnClick(4) + , "on_btn5_clicked" : lambda x: self.OnBtnClick(5) + , "on_btn6_clicked" : lambda x: self.OnBtnClick(6) + , "on_btn7_clicked" : lambda x: self.OnBtnClick(7) + , "on_btn8_clicked" : lambda x: self.OnBtnClick(8) + , "on_btn9_clicked" : lambda x: self.OnBtnClick(9) + , "on_btnDiv_clicked" : lambda x: self.OnBtnClick("/") + , "on_btnMul_clicked" : lambda x: self.OnBtnClick("*") + , "on_btnMin_clicked" : lambda x: self.OnBtnClick("-") + , "on_btnPlus_clicked" : lambda x: self.OnBtnClick("+") + , "on_btnEq_clicked" : lambda x: self.calc() + , "on_btnDel_clicked" : lambda x: self.OnBtnClick("Del") + , "on_btnConnect_clicked" : lambda x: self.connect() + } + + for i in range(0,9): + dic + self.wTree.signal_autoconnect(dic) + + self.wTree.get_widget("window1").show() + self.setStatus("Not connected") + + def setStatus(self, msg): + self.wTree.get_widget("statusbar").push(0, msg) + + def calc(self): + exp = self.wTree.get_widget("txtCalc").get_text() + self.s.send("Server-Command: call\n"+ + "Token: %s\n" % self.token + + "Content-Type: text\n"+ + "Content-Length: %s\n" % len(exp)+ + "\n"+ + exp) + fs = self.s.makefile() + self.wTree.get_widget("txtCalc").set_text(fs.readline().split(" ")[1]) + + + def connect(self): + print "Connecting" + self.setStatus("Connecting") + HOST = 'localhost' # The remote host + PORT = 9000 # The same port as used by the server + self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.s.connect((HOST, PORT)) + self.s.send("Server-Command: hello\n"+ + "Content-Type: text\n"+ + "Content-Length: 0\n"+ + "\n") + fs = self.s.makefile() + self.token = fs.readline().split(" ")[0] + self.setStatus("Connected!") + + def OnBtnClick(self, btn): + calcTxt = self.wTree.get_widget("txtCalc") + t = calcTxt.get_text() + if btn == "+": + t += "+" + elif btn == "-": + t += "-" + elif btn == "/": + t += "/" + elif btn == "=": + t += "=" + elif btn == "*": + t += "*" + elif btn == "Del": + t = t[:-1] + else: + t += str("\""+str(btn)+"\"") + calcTxt.set_text(t) + +if __name__ == "__main__": + calc = GGSCalc() + gtk.main() diff --git a/games/GGSChat/chat.py b/games/GGSChat/chat.py new file mode 100644 index 0000000..aadf5d9 --- /dev/null +++ b/games/GGSChat/chat.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python + +import sys, socket, thread, gobject, getpass, time +try: + import pygtk + pygtk.require("2.16") +except: + pass +try: + import gtk + import gtk.glade +except: + sys.exit(1) + +class GGSChat: + + def __init__(self,host, port): + #Set the Glade file + self.nicksListStore = gtk.ListStore(str) + self.gladefile = "ggschat.glade" + self.wTree = gtk.glade.XML(self.gladefile, "window1") + + self.setStatus("Not connected") + self.connect(host, port) + thread.start_new_thread(self.listenChat, ()) + #thread.start_new_thread(self.luserCheck, ()) + #Create our dictionay and connect it + dic = {"on_window1_destroy_event" : gtk.main_quit + , "on_sendButton_clicked" : lambda x: self.chat() + , "on_entry_activate" : lambda x : self.chat() + , "on_nickBox_activate" : lambda x : self.changeNick() + , "on_chatBox_focus" : lambda x, y: self.wTree.get_widget("entry").grab_focus() + } + + self.wTree.signal_autoconnect(dic) + + self.wTree.get_widget("nickBox").set_text(getpass.getuser()) + self.wTree.get_widget("window1").show() + self.wTree.get_widget("entry").grab_focus() + nicksList = self.wTree.get_widget("nicksList") + #self.changeNick() + nicksList.set_model(self.nicksListStore) +# self.nicksListStore.append(["Test!"]) + + rendererText = gtk.CellRendererText() + column = gtk.TreeViewColumn("Participants", rendererText, text=0) + column.set_sort_column_id(0) + nicksList.append_column(column) + + def setStatus(self, msg): + self.wTree.get_widget("statusbar").push(0, msg) + + def changeNick(self): + params = self.wTree.get_widget("nickBox").get_text() + self.s.send("Game-Command: nick\n" + + "Token: %s\n" % self.token + + "Content-Type: text\n" + + "Content-Length: %s\n" % len(params)+ + "\n"+ + params) + + + def chat(self): + exp = self.wTree.get_widget("entry").get_text() + nick = self.wTree.get_widget("nickBox").get_text() + if exp[0] == "/": + cmdStr = exp[1:].split(" ") + cmd = cmdStr[0] + params = ' '.join(cmdStr[1:]) + self.s.send("Game-Command: %s\n" % cmd + + "Token: %s\n" % self.token + + "Content-Type: text\n" + + "Content-Length: %s\n" % len(params)+ + "\n"+ + params) + else: + self.s.send("Game-Command: chat\n"+ + "Token: %s\n" % self.token + + "Content-Type: text\n"+ + "Content-Length: %s\n" % (len(exp))+ + "\n"+ + exp+"\n") + self.wTree.get_widget("entry").set_text("") + #self.listenChat() + + + def connect(self, host,port): + print "Connecting" + self.setStatus("Connecting") + HOST = host # The remote host + PORT = port # The same port as used by the server + self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.s.connect((HOST, PORT)) + self.setStatus("Connected!") + + + def protocolHandler(self, msg): + if msg["Client-Command"] == "hello": + self.token = msg["DATA"] + elif msg["Client-Command"] == "chat": + gobject.idle_add(self.updateChatText, msg["DATA"]) + elif msg["Client-Command"] == "lusers": + print msg + gobject.idle_add(self.updateUsers, msg["DATA"]) + + + def listenChat(self): + msg = {} + print "listening" + fs = self.s.makefile() + while True: + line = fs.readline() + print "Received: '%s'" % line + if line != "\n": + key = line.split(":")[0] + value = line.split(":")[1] + msg[key] = value.strip() + else: + msg["DATA"] = fs.read(int("%s" % msg["Content-Size"])) + print "Got data:", msg + self.protocolHandler(msg) + #gobject.idle_add(self.updateChatText, msg["DATA"]) + +# if line.split(" ")[0] == "LUSERS": +# gobject.idle_add(self.updateUsers, line) +# else: +# print msg + + def updateChatText(self, text): + self.wTree.get_widget("chatBox").get_buffer().insert_at_cursor(text) + + def luserCheck(self): + while True: + if self.token == None: + print "Not sending lusers cmd.." + continue + print "Sending lusers cmd.." + self.s.send("Game-Command: lusers\n" + + "Token: %s\n" % self.token + + "Content-Type: text\n" + + "Content-Length: 0\n"+ + "\n") + #time.sleep(2) + + def updateUsers(self, text): + evalNicks = eval(text) + self.nicksListStore.clear() + for nick in evalNicks: + self.nicksListStore.append([nick]) + +if __name__ == "__main__": + host = "localhost" + port = 9000 + if len(sys.argv) >= 2: + host = sys.argv[1] + port = int(sys.argv[2]) + chat = GGSChat(host, port) + gobject.threads_init() + gtk.main() diff --git a/games/GGSChat/ggschat.glade b/games/GGSChat/ggschat.glade new file mode 100644 index 0000000..ed39607 --- /dev/null +++ b/games/GGSChat/ggschat.glade @@ -0,0 +1,186 @@ + + + + + + 500 + 500 + + + + True + + + True + True + + + True + _File + True + + + True + True + + + Connect to ... + True + True + False + + + True + gtk-missing-image + + + + + + + True + + + + + gtk-quit + True + True + True + + + + + + + + + True + _Help + True + + + True + True + + + gtk-about + True + True + True + + + + + + + + + False + 0 + + + + + True + True + + + True + True + automatic + automatic + + + 412 + True + True + False + + + + + + True + False + + + + + True + True + 1 + + + False + True + + + + + 1 + + + + + True + + + True + True + + 10 + Anonymous + + + + False + 0 + + + + + True + True + + + + + 1 + + + + + Chat! + True + True + True + + + + False + False + 2 + + + + + False + False + 2 + + + + + True + 2 + + + False + 3 + + + + + + diff --git a/games/JS-chat/chat.rb b/games/JS-chat/chat.rb new file mode 100755 index 0000000..aef27e5 --- /dev/null +++ b/games/JS-chat/chat.rb @@ -0,0 +1,196 @@ +#!/usr/bin/env ruby -wKU + +$: << "." + +require 'ggs-network.rb' +require 'ggs-delegate.rb' +require 'my-random.rb' +require 'readline' + +class Chat + include GGSDelegate + include MyRandom + + @@log_file_path = "/tmp/ggs-ping-log.csv" + @@bot_threads = [] + + def initialize(is_bot=false, table_token="") + @is_bot = is_bot + @log = nil + @ignore = false + + stty_save = `stty -g`.chomp + trap('INT') { system('stty', stty_save); exit } + + print "Table token (empty for new): " unless @is_bot + table_token = gets.chomp unless @is_bot + + @ggs_network = GGSNetwork.new(self, table_token) + @ggs_network.connect("192.168.0.2", 9000) + #@ggs_network.connect("localhost", 9000) + end + + def ggsNetworkReady(ggs_network, am_i_host) + unless am_i_host + say @ggs_network.table_token + source_code = File.open("chat_server.js", "rb").read + @ggs_network.define(source_code) + else + ggsNetworkDefined(ggs_network, true) + end + end + + def ggsNetworkDefined(ggs_network, defined) + if defined + @nick = "" + while @nick == "" + print "\rYour nickname: " unless @is_bot + unless @is_bot + @nick = gets.chomp + else + @nick = random_nick + end + end + + @ggs_network.sendCommand("/nick", @nick) + + t = Thread.new { + loop do + unless @is_bot + input + else + sleep(rand 2) # interfall for bot to do something + random_function + end + end + } + @@bot_threads << t if @is_bot + else + source_code = File.open("chat_server.js", "rb").read + @ggs_network.define(source_code) + end + end + + def ggsNetworkReceivedCommandWithArgs(ggs_network, command, args) + case command + when "message" then message(args) + when "notice" then notice(args) + when "pong" then pong(args) + end + end + + protected + + def start_bots(number) + number.times { |n| + say "" + @@bot_threads << Thread.new { + Chat.new(true, @ggs_network.table_token) + } + } + end + + def stop_bots + @@bot_threads.each do |bot| + bot.kill + end + @bot_threads = [] + say "" + end + + def message(message) + say message + end + + def notice(notice) + say "<#{notice}>" + end + + def input(message="") + message = Readline.readline('> ', true) unless @is_bot + + if message[0,6] == "/nick " + @nick = message[6..-1] + @ggs_network.sendCommand("/nick", @nick) + elsif message == "/ping" + ping() + elsif message[0,6] == "/bots " + + number = message[6..-1].to_i + say "" + start_bots(number) + + elsif message == "/bots" + stop_bots + elsif message == "/log" + toggle_log + elsif message == "/help" + help + elsif message == "/exit" + exit + elsif message == "/ignore" + @ignore = @ignore ? false : true + if @ignore + say "" + else + say "" + end + else + @ggs_network.sendCommand("message", message) unless message == "" + end + end + + def ping + @start_ping = Time.now + @ggs_network.sendCommand("ping", @ggs_network.player_token) + end + + def pong(server_log) + time = ((Time.now - @start_ping) * 1000).to_s + say "" + + File.open(@@log_file_path, 'a') {|f| f.write(time << ",#{server_log}\n") } unless @log.nil? + end + + def say(something) + unless @ignore or @is_bot + puts "\r#{something}" + print "> " + end + + end + + def toggle_log + if @log.nil? + say "" + @log = Thread.new { + loop { + sleep 1 + ping + } + } + else + @log.kill + @log = nil + say "" + end + end + + def help + puts "+-----------------------------------------------+" + puts "| something | normal message |" + puts "| /nick something | changing your nick |" + puts "| /bots n | start n bots |" + puts "| /bots | stop all bots |" + puts "| /log | toggle logging |" + puts "| /ignore | toggle ignoring everyone |" + puts "| /exit | exit chat |" + puts "| /help | show this help |" + puts "+-----------------------------------------------+" + end + +end + +if __FILE__ == $0 + Chat.new +end diff --git a/games/JS-chat/chat_server.js b/games/JS-chat/chat_server.js new file mode 100644 index 0000000..8224ba5 --- /dev/null +++ b/games/JS-chat/chat_server.js @@ -0,0 +1,25 @@ +function playerCommand(player_id, command, args) { + if(command == "/nick") { + changeNick(player_id, args); + } else if(command == "message") { + message(player_id, args); + } else if(command == "ping") { + GGS.sendCommand(player_id, "pong", GGS.serverInfo() + ""); + } +} + +function changeNick(player_id, nick) { + var old_nick = GGS.localStorage.getItem("nick_" + player_id); + GGS.localStorage.setItem("nick_" + player_id, nick); + + if (!old_nick) { + GGS.sendCommandToAll("notice", nick + " joined"); + } else { + GGS.sendCommandToAll("notice", old_nick + " is now called " + nick); + } +} + +function message(player_id, message) { + var nick = GGS.localStorage.getItem("nick_" + player_id); + GGS.sendCommandToAll('message', nick + "> " + message); +} diff --git a/games/JS-chat/ggs-delegate.rb b/games/JS-chat/ggs-delegate.rb new file mode 100644 index 0000000..be4cc47 --- /dev/null +++ b/games/JS-chat/ggs-delegate.rb @@ -0,0 +1,15 @@ +module GGSDelegate + + def ggsNetworkReady(ggs_network, ready) + raise "ggsNetworkReady(ggs_network, ready) must be overridden" + end + + def ggsNetworkDefined(ggs_network, defined) + raise "ggsNetworkDefined(ggs_network, defined) must be overridden" + end + + def ggsNetworkReceivedCommandWithArgs(ggs_network, command, args) + raise "ggsNetworkReceivedCommandWithArgs(ggs_network, command, args) must be overridden" + end + +end \ No newline at end of file diff --git a/games/JS-chat/ggs-network.rb b/games/JS-chat/ggs-network.rb new file mode 100644 index 0000000..7f17818 --- /dev/null +++ b/games/JS-chat/ggs-network.rb @@ -0,0 +1,95 @@ +require 'socket' + +class GGSNetwork + + SERVER = "Server" + CLIENT = "Game" + + public + + attr_accessor :delegate, :player_token, :table_token + + def initialize(delegate, table_token="") + @table_token = table_token + @delegate = delegate + @player_token = nil + end + + def define(source_code) + write( makeMessage(SERVER, "define", source_code) ) + end + + def sendCommand(command, args="") + write( makeMessage(CLIENT, command, args) ) + end + + def connect(host='localhost', port=9000) + @socket = TCPSocket.new(host, port) + write( makeMessage(SERVER, "hello", @table_token) ) + read + end + + protected + + def write(message) + #puts message + @socket.write(message) + end + + def read + loop do + headers = {} + size = 0 + args = "" + + select([@socket], nil, nil) + + while (line = @socket.gets) != "\n" + break if line.nil? + + key, value = line.split(": ") + headers[key] = value.strip + end + + if headers.has_key?("Content-Size") + args = @socket.read(headers["Content-Size"].to_i) + end + + receivedCommand(headers, args) + end + end + + def receivedCommand(headers, data) + if headers.has_key? "Client-Command" + command = headers["Client-Command"] + case command + when "hello" + parse_hello(data) + @delegate.ggsNetworkReady(self, @am_i_host) + when "defined" + @delegate.ggsNetworkDefined(self, true) + else + @delegate.ggsNetworkReceivedCommandWithArgs(self, command, data) + end + else + STDERR.print "ERR: " + [headers, data, @socket.inspect].inspect + "\n" + end + end + + def makeMessage(serverOrGame, command, args) + message = "" + message += "Token: #{@player_token}\n" unless @player_token.nil? + message += "#{serverOrGame}-Command: #{command}\n" + + "Content-Length: #{args.bytesize}\n\n" + + message += args if args.length > 0 + + message + end + + def parse_hello(message) + @player_token, shall_define, @table_token = message.split(",") + @am_i_host = shall_define == "true" + end + +end diff --git a/games/JS-chat/my-random.rb b/games/JS-chat/my-random.rb new file mode 100644 index 0000000..c6a87ba --- /dev/null +++ b/games/JS-chat/my-random.rb @@ -0,0 +1,27 @@ +module MyRandom + def random_function + funcs = [] + funcs << lambda { ping() } + funcs << lambda { input("/nick " + random_nick) } + 20.times { funcs << lambda { input(random_message) } } + + funcs[rnd(0,funcs.length)].call + end + + def random_message + random_string(rnd(1,30)) + end + + def random_nick + random_string(rnd(1,6)) + end + + def random_string(length) + o = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten; + (0..length).map{ o[rand(o.length)] }.join; + end + + def rnd(min, max) + ((rand * (max - min)) + min).to_i + end +end \ No newline at end of file diff --git a/games/Pong-bots/bots.sh b/games/Pong-bots/bots.sh new file mode 100755 index 0000000..0353c04 --- /dev/null +++ b/games/Pong-bots/bots.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +for (( i = 0; i < 2; i++ )); do + ruby pong-bot.rb & +done diff --git a/games/Pong-bots/ggs-delegate.rb b/games/Pong-bots/ggs-delegate.rb new file mode 100644 index 0000000..be4cc47 --- /dev/null +++ b/games/Pong-bots/ggs-delegate.rb @@ -0,0 +1,15 @@ +module GGSDelegate + + def ggsNetworkReady(ggs_network, ready) + raise "ggsNetworkReady(ggs_network, ready) must be overridden" + end + + def ggsNetworkDefined(ggs_network, defined) + raise "ggsNetworkDefined(ggs_network, defined) must be overridden" + end + + def ggsNetworkReceivedCommandWithArgs(ggs_network, command, args) + raise "ggsNetworkReceivedCommandWithArgs(ggs_network, command, args) must be overridden" + end + +end \ No newline at end of file diff --git a/games/Pong-bots/ggs-network.rb b/games/Pong-bots/ggs-network.rb new file mode 100644 index 0000000..2e831b8 --- /dev/null +++ b/games/Pong-bots/ggs-network.rb @@ -0,0 +1,86 @@ +require 'socket' + +class GGSNetwork + + SERVER = "Server" + CLIENT = "Game" + + public + + attr_accessor :delegate + + def initialize(delegate) + @delegate = delegate + end + + def define(source_code) + write( makeMessage(SERVER, "define", source_code) ) + end + + def sendCommand(command, args="") + write( makeMessage(CLIENT, command, args) ) + end + + def connect(host='localhost', port=9000) + @socket = TCPSocket.new(host, port) + sprintf(@socket) + read + end + + protected + + def write(message) + puts message.inspect + @socket.write(message) + end + + def read + loop do + headers = {} + size = 0 + args = "" + + select([@socket], nil, nil) + + while (line = @socket.gets) != "\n" + break if line.nil? + + key, value = line.split(": ") + headers[key] = value.strip + end + + if headers.has_key?("Content-Size") + args = @socket.read(headers["Content-Size"].to_i) + end + + receivedCommand(headers, args) + end + end + + def receivedCommand(headers, data) + puts [headers, data].inspect + if headers.has_key? "Client-Command" + command = headers["Client-Command"] + case command + when "hello" + @game_token = data + @delegate.ggsNetworkReady(self, true) + when "defined" + @delegate.ggsNetworkDefined(self, true) + else + @delegate.ggsNetworkReceivedCommandWithArgs(self, command, data) + end + end + end + + def makeMessage(serverOrGame, command, args) + message = "Token: #{@game_token}\n" + + "#{serverOrGame}-Command: #{command}\n" + + "Content-Length: #{args.length}\n\n" + + message += args if args.length > 0 + + message + end + +end diff --git a/games/Pong-bots/pong-bot.rb b/games/Pong-bots/pong-bot.rb new file mode 100755 index 0000000..0a95812 --- /dev/null +++ b/games/Pong-bots/pong-bot.rb @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby -wKU + +$: << "." + +require 'ggs-network.rb' +require 'ggs-delegate.rb' + +class PongBot + include GGSDelegate + + def initialize + @me = nil + @player1 = Pos.new + @player2 = Pos.new + @ball = Pos.new + @game_paused = true + @send_start = false + + @ggs_network = GGSNetwork.new(self) + @ggs_network.connect("192.168.0.1") + #@ggs_network.connect() + end + + def ggsNetworkReady(ggs_network, ready) + @ggs_network.sendCommand("ready") + end + + def ggsNetworkDefined(ggs_network, defined) + # do nothing + end + + def ggsNetworkReceivedCommandWithArgs(ggs_network, command, args) + case command + when "welcome" then welcome(args) + when "ball" then ball(args) + when "player1_y" then player1_y(args) + when "player2_y" then player2_y(args) + when "game" then game(args) + when "player1_points" then new_round() + when "player2_points" then new_round() + end + end + + protected + + def gameTick() + if @game_paused + unless @send_start + @ggs_network.sendCommand("start") + @send_start = true + end + else + if @ball.y < @me.y - 5 + @ggs_network.sendCommand("up") + elsif @ball.y > @me.y - 5 + @ggs_network.sendCommand("down") + end + end + end + + def welcome(who_am_i) + if who_am_i == 1 + @me = @player1 + else + @me = @player2 + end + + Thread.new { + loop do + gameTick() + sleep 0.3 + end + } + end + + def ball(pos_s) + x, y = pos_s.split(",") + @ball.x, @ball.y = x.to_i, y.to_i + end + + def player1_y(y) + @player1.y = y.to_i + end + + def player2_y(y) + @player2.y = y.to_i + end + + def game(wait_or_start) + if wait_or_start == "wait" + else + @game_paused = false + end + end + + def new_round + @game_paused = true + @send_start = false + end + + + class Pos + attr_accessor :x, :y + def initialize + @x = 0 + @y = 0 + end + end + +end + +if __FILE__ == $0 + PongBot.new +end diff --git a/games/Pong-bots/start.sh b/games/Pong-bots/start.sh new file mode 100644 index 0000000..1fed8a7 --- /dev/null +++ b/games/Pong-bots/start.sh @@ -0,0 +1,3 @@ +for ((i = 0; i<30; i++)); do + ruby pong-bot.rb & +done diff --git a/games/Pong/Classes/AsyncSocket.h b/games/Pong/Classes/AsyncSocket.h new file mode 100644 index 0000000..8382cd3 --- /dev/null +++ b/games/Pong/Classes/AsyncSocket.h @@ -0,0 +1,659 @@ +// +// AsyncSocket.h +// +// This class is in the public domain. +// Originally created by Dustin Voss on Wed Jan 29 2003. +// Updated and maintained by Deusty Designs and the Mac development community. +// +// http://code.google.com/p/cocoaasyncsocket/ +// + +#import + +@class AsyncSocket; +@class AsyncReadPacket; +@class AsyncWritePacket; + +extern NSString *const AsyncSocketException; +extern NSString *const AsyncSocketErrorDomain; + +enum AsyncSocketError +{ + AsyncSocketCFSocketError = kCFSocketError, // From CFSocketError enum. + AsyncSocketNoError = 0, // Never used. + AsyncSocketCanceledError, // onSocketWillConnect: returned NO. + AsyncSocketConnectTimeoutError, + AsyncSocketReadMaxedOutError, // Reached set maxLength without completing + AsyncSocketReadTimeoutError, + AsyncSocketWriteTimeoutError +}; +typedef enum AsyncSocketError AsyncSocketError; + +@protocol AsyncSocketDelegate +@optional + +/** + * In the event of an error, the socket is closed. + * You may call "unreadData" during this call-back to get the last bit of data off the socket. + * When connecting, this delegate method may be called + * before"onSocket:didAcceptNewSocket:" or "onSocket:didConnectToHost:". +**/ +- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; + +/** + * Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, + * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". + * + * If you call the disconnect method, and the socket wasn't already disconnected, + * this delegate method will be called before the disconnect method returns. +**/ +- (void)onSocketDidDisconnect:(AsyncSocket *)sock; + +/** + * Called when a socket accepts a connection. Another socket is spawned to handle it. The new socket will have + * the same delegate and will call "onSocket:didConnectToHost:port:". +**/ +- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket; + +/** + * Called when a new socket is spawned to handle a connection. This method should return the run-loop of the + * thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used. +**/ +- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket; + +/** + * Called when a socket is about to connect. This method should return YES to continue, or NO to abort. + * If aborted, will result in AsyncSocketCanceledError. + * + * If the connectToHost:onPort:error: method was called, the delegate will be able to access and configure the + * CFReadStream and CFWriteStream as desired prior to connection. + * + * If the connectToAddress:error: method was called, the delegate will be able to access and configure the + * CFSocket and CFSocketNativeHandle (BSD socket) as desired prior to connection. You will be able to access and + * configure the CFReadStream and CFWriteStream in the onSocket:didConnectToHost:port: method. +**/ +- (BOOL)onSocketWillConnect:(AsyncSocket *)sock; + +/** + * Called when a socket connects and is ready for reading and writing. + * The host parameter will be an IP address, not a DNS name. +**/ +- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; + +/** + * Called when a socket has completed reading the requested data into memory. + * Not called if there is an error. +**/ +- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; + +/** + * Called when a socket has read in data, but has not yet completed the read. + * This would occur if using readToData: or readToLength: methods. + * It may be used to for things such as updating progress bars. +**/ +- (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; + +/** + * Called when a socket has completed writing the requested data. Not called if there is an error. +**/ +- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag; + +/** + * Called when a socket has written some data, but has not yet completed the entire write. + * It may be used to for things such as updating progress bars. +**/ +- (void)onSocket:(AsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; + +/** + * Called if a read operation has reached its timeout without completing. + * This method allows you to optionally extend the timeout. + * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. + * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. + * + * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. + * The length parameter is the number of bytes that have been read so far for the read operation. + * + * Note that this method may be called multiple times for a single read if you return positive numbers. +**/ +- (NSTimeInterval)onSocket:(AsyncSocket *)sock + shouldTimeoutReadWithTag:(long)tag + elapsed:(NSTimeInterval)elapsed + bytesDone:(NSUInteger)length; + +/** + * Called if a write operation has reached its timeout without completing. + * This method allows you to optionally extend the timeout. + * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. + * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. + * + * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. + * The length parameter is the number of bytes that have been written so far for the write operation. + * + * Note that this method may be called multiple times for a single write if you return positive numbers. +**/ +- (NSTimeInterval)onSocket:(AsyncSocket *)sock + shouldTimeoutWriteWithTag:(long)tag + elapsed:(NSTimeInterval)elapsed + bytesDone:(NSUInteger)length; + +/** + * Called after the socket has successfully completed SSL/TLS negotiation. + * This method is not called unless you use the provided startTLS method. + * + * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, + * and the onSocket:willDisconnectWithError: delegate method will be called with the specific SSL error code. +**/ +- (void)onSocketDidSecure:(AsyncSocket *)sock; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface AsyncSocket : NSObject +{ + CFSocketNativeHandle theNativeSocket4; + CFSocketNativeHandle theNativeSocket6; + + CFSocketRef theSocket4; // IPv4 accept or connect socket + CFSocketRef theSocket6; // IPv6 accept or connect socket + + CFReadStreamRef theReadStream; + CFWriteStreamRef theWriteStream; + + CFRunLoopSourceRef theSource4; // For theSocket4 + CFRunLoopSourceRef theSource6; // For theSocket6 + CFRunLoopRef theRunLoop; + CFSocketContext theContext; + NSArray *theRunLoopModes; + + NSTimer *theConnectTimer; + + NSMutableArray *theReadQueue; + AsyncReadPacket *theCurrentRead; + NSTimer *theReadTimer; + NSMutableData *partialReadBuffer; + + NSMutableArray *theWriteQueue; + AsyncWritePacket *theCurrentWrite; + NSTimer *theWriteTimer; + + id theDelegate; + UInt16 theFlags; + + long theUserData; +} + +- (id)init; +- (id)initWithDelegate:(id)delegate; +- (id)initWithDelegate:(id)delegate userData:(long)userData; + +/* String representation is long but has no "\n". */ +- (NSString *)description; + +/** + * Use "canSafelySetDelegate" to see if there is any pending business (reads and writes) with the current delegate + * before changing it. It is, of course, safe to change the delegate before connecting or accepting connections. +**/ +- (id)delegate; +- (BOOL)canSafelySetDelegate; +- (void)setDelegate:(id)delegate; + +/* User data can be a long, or an id or void * cast to a long. */ +- (long)userData; +- (void)setUserData:(long)userData; + +/* Don't use these to read or write. And don't close them either! */ +- (CFSocketRef)getCFSocket; +- (CFReadStreamRef)getCFReadStream; +- (CFWriteStreamRef)getCFWriteStream; + +// Once one of the accept or connect methods are called, the AsyncSocket instance is locked in +// and the other accept/connect methods can't be called without disconnecting the socket first. +// If the attempt fails or times out, these methods either return NO or +// call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:". + +// When an incoming connection is accepted, AsyncSocket invokes several delegate methods. +// These methods are (in chronological order): +// 1. onSocket:didAcceptNewSocket: +// 2. onSocket:wantsRunLoopForNewSocket: +// 3. onSocketWillConnect: +// +// Your server code will need to retain the accepted socket (if you want to accept it). +// The best place to do this is probably in the onSocket:didAcceptNewSocket: method. +// +// After the read and write streams have been setup for the newly accepted socket, +// the onSocket:didConnectToHost:port: method will be called on the proper run loop. +// +// Multithreading Note: If you're going to be moving the newly accepted socket to another run +// loop by implementing onSocket:wantsRunLoopForNewSocket:, then you should wait until the +// onSocket:didConnectToHost:port: method before calling read, write, or startTLS methods. +// Otherwise read/write events are scheduled on the incorrect runloop, and chaos may ensue. + +/** + * Tells the socket to begin listening and accepting connections on the given port. + * When a connection comes in, the AsyncSocket instance will call the various delegate methods (see above). + * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) +**/ +- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr; + +/** + * This method is the same as acceptOnPort:error: with the additional option + * of specifying which interface to listen on. So, for example, if you were writing code for a server that + * has multiple IP addresses, you could specify which address you wanted to listen on. Or you could use it + * to specify that the socket should only accept connections over ethernet, and not other interfaces such as wifi. + * You may also use the special strings "localhost" or "loopback" to specify that + * the socket only accept connections from the local machine. + * + * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. +**/ +- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr; + +/** + * Connects to the given host and port. + * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2") +**/ +- (BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; + +/** + * This method is the same as connectToHost:onPort:error: with an additional timeout option. + * To not time out use a negative time interval, or simply use the connectToHost:onPort:error: method. +**/ +- (BOOL)connectToHost:(NSString *)hostname + onPort:(UInt16)port + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr; + +/** + * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. + * For example, a NSData object returned from NSNetservice's addresses method. + * + * If you have an existing struct sockaddr you can convert it to a NSData object like so: + * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; + * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; + +/** + * This method is the same as connectToAddress:error: with an additional timeout option. + * To not time out use a negative time interval, or simply use the connectToAddress:error: method. +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; + +- (BOOL)connectToAddress:(NSData *)remoteAddr + viaInterfaceAddress:(NSData *)interfaceAddr + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr; + +/** + * Disconnects immediately. Any pending reads or writes are dropped. + * If the socket is not already disconnected, the onSocketDidDisconnect delegate method + * will be called immediately, before this method returns. + * + * Please note the recommended way of releasing an AsyncSocket instance (e.g. in a dealloc method) + * [asyncSocket setDelegate:nil]; + * [asyncSocket disconnect]; + * [asyncSocket release]; +**/ +- (void)disconnect; + +/** + * Disconnects after all pending reads have completed. + * After calling this, the read and write methods will do nothing. + * The socket will disconnect even if there are still pending writes. +**/ +- (void)disconnectAfterReading; + +/** + * Disconnects after all pending writes have completed. + * After calling this, the read and write methods will do nothing. + * The socket will disconnect even if there are still pending reads. +**/ +- (void)disconnectAfterWriting; + +/** + * Disconnects after all pending reads and writes have completed. + * After calling this, the read and write methods will do nothing. +**/ +- (void)disconnectAfterReadingAndWriting; + +/* Returns YES if the socket and streams are open, connected, and ready for reading and writing. */ +- (BOOL)isConnected; + +/** + * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. + * The host will be an IP address. +**/ +- (NSString *)connectedHost; +- (UInt16)connectedPort; + +- (NSString *)localHost; +- (UInt16)localPort; + +/** + * Returns the local or remote address to which this socket is connected, + * specified as a sockaddr structure wrapped in a NSData object. + * + * See also the connectedHost, connectedPort, localHost and localPort methods. +**/ +- (NSData *)connectedAddress; +- (NSData *)localAddress; + +/** + * Returns whether the socket is IPv4 or IPv6. + * An accepting socket may be both. +**/ +- (BOOL)isIPv4; +- (BOOL)isIPv6; + +// The readData and writeData methods won't block (they are asynchronous). +// +// When a read is complete the onSocket:didReadData:withTag: delegate method is called. +// When a write is complete the onSocket:didWriteDataWithTag: delegate method is called. +// +// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) +// If a read/write opertion times out, the corresponding "onSocket:shouldTimeout..." delegate method +// is called to optionally allow you to extend the timeout. +// Upon a timeout, the "onSocket:willDisconnectWithError:" method is called, followed by "onSocketDidDisconnect". +// +// The tag is for your convenience. +// You can use it as an array index, step number, state id, pointer, etc. + +/** + * Reads the first available bytes that become available on the socket. + * + * If the timeout value is negative, the read operation will not use a timeout. +**/ +- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Reads the first available bytes that become available on the socket. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, the socket will create a buffer for you. + * + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing, and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. + * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer. +**/ +- (void)readDataWithTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag; + +/** + * Reads the first available bytes that become available on the socket. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * A maximum of length bytes will be read. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, a buffer will automatically be created for you. + * If maxLength is zero, no length restriction is enforced. + * + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing, and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. + * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer. +**/ +- (void)readDataWithTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + maxLength:(NSUInteger)length + tag:(long)tag; + +/** + * Reads the given number of bytes. + * + * If the timeout value is negative, the read operation will not use a timeout. + * + * If the length is 0, this method does nothing and the delegate is not called. +**/ +- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Reads the given number of bytes. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, a buffer will automatically be created for you. + * + * If the length is 0, this method does nothing and the delegate is not called. + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing, and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. + * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer. +**/ +- (void)readDataToLength:(NSUInteger)length + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag; + +/** + * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. + * + * If the timeout value is negative, the read operation will not use a timeout. + * + * If you pass nil or zero-length data as the "data" parameter, + * the method will do nothing, and the delegate will not be called. + * + * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. + * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for + * a character, the read will prematurely end. +**/ +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, a buffer will automatically be created for you. + * + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing, and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. + * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer. + * + * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. + * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for + * a character, the read will prematurely end. +**/ +- (void)readDataToData:(NSData *)data + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag; + +/** + * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. + * + * If the timeout value is negative, the read operation will not use a timeout. + * + * If maxLength is zero, no length restriction is enforced. + * Otherwise if maxLength bytes are read without completing the read, + * it is treated similarly to a timeout - the socket is closed with a AsyncSocketReadMaxedOutError. + * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. + * + * If you pass nil or zero-length data as the "data" parameter, + * the method will do nothing, and the delegate will not be called. + * If you pass a maxLength parameter that is less than the length of the data parameter, + * the method will do nothing, and the delegate will not be called. + * + * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. + * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for + * a character, the read will prematurely end. +**/ +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag; + +/** + * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * A maximum of length bytes will be read. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, a buffer will automatically be created for you. + * + * If maxLength is zero, no length restriction is enforced. + * Otherwise if maxLength bytes are read without completing the read, + * it is treated similarly to a timeout - the socket is closed with a AsyncSocketReadMaxedOutError. + * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. + * + * If you pass a maxLength parameter that is less than the length of the data parameter, + * the method will do nothing, and the delegate will not be called. + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing, and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. + * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer. + * + * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. + * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for + * a character, the read will prematurely end. +**/ +- (void)readDataToData:(NSData *)data + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + maxLength:(NSUInteger)length + tag:(long)tag; + +/** + * Writes data to the socket, and calls the delegate when finished. + * + * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. + * If the timeout value is negative, the write operation will not use a timeout. +**/ +- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Returns progress of current read or write, from 0.0 to 1.0, or NaN if no read/write (use isnan() to check). + * "tag", "done" and "total" will be filled in if they aren't NULL. +**/ +- (float)progressOfReadReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total; +- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total; + +/** + * Secures the connection using SSL/TLS. + * + * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes + * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing + * the upgrade to TLS at the same time, without having to wait for the write to finish. + * Any reads or writes scheduled after this method is called will occur over the secured connection. + * + * The possible keys and values for the TLS settings are well documented. + * Some possible keys are: + * - kCFStreamSSLLevel + * - kCFStreamSSLAllowsExpiredCertificates + * - kCFStreamSSLAllowsExpiredRoots + * - kCFStreamSSLAllowsAnyRoot + * - kCFStreamSSLValidatesCertificateChain + * - kCFStreamSSLPeerName + * - kCFStreamSSLCertificates + * - kCFStreamSSLIsServer + * + * Please refer to Apple's documentation for associated values, as well as other possible keys. + * + * If you pass in nil or an empty dictionary, the default settings will be used. + * + * The default settings will check to make sure the remote party's certificate is signed by a + * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. + * However it will not verify the name on the certificate unless you + * give it a name to verify against via the kCFStreamSSLPeerName key. + * The security implications of this are important to understand. + * Imagine you are attempting to create a secure connection to MySecureServer.com, + * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. + * If you simply use the default settings, and MaliciousServer.com has a valid certificate, + * the default settings will not detect any problems since the certificate is valid. + * To properly secure your connection in this particular scenario you + * should set the kCFStreamSSLPeerName property to "MySecureServer.com". + * If you do not know the peer name of the remote host in advance (for example, you're not sure + * if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the + * certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured. + * The X509Certificate class is part of the CocoaAsyncSocket open source project. +**/ +- (void)startTLS:(NSDictionary *)tlsSettings; + +/** + * For handling readDataToData requests, data is necessarily read from the socket in small increments. + * The performance can be much improved by allowing AsyncSocket to read larger chunks at a time and + * store any overflow in a small internal buffer. + * This is termed pre-buffering, as some data may be read for you before you ask for it. + * If you use readDataToData a lot, enabling pre-buffering will result in better performance, especially on the iPhone. + * + * The default pre-buffering state is controlled by the DEFAULT_PREBUFFERING definition. + * It is highly recommended one leave this set to YES. + * + * This method exists in case pre-buffering needs to be disabled by default for some unforeseen reason. + * In that case, this method exists to allow one to easily enable pre-buffering when ready. +**/ +- (void)enablePreBuffering; + +/** + * When you create an AsyncSocket, it is added to the runloop of the current thread. + * So for manually created sockets, it is easiest to simply create the socket on the thread you intend to use it. + * + * If a new socket is accepted, the delegate method onSocket:wantsRunLoopForNewSocket: is called to + * allow you to place the socket on a separate thread. This works best in conjunction with a thread pool design. + * + * If, however, you need to move the socket to a separate thread at a later time, this + * method may be used to accomplish the task. + * + * This method must be called from the thread/runloop the socket is currently running on. + * + * Note: After calling this method, all further method calls to this object should be done from the given runloop. + * Also, all delegate calls will be sent on the given runloop. +**/ +- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; + +/** + * Allows you to configure which run loop modes the socket uses. + * The default set of run loop modes is NSDefaultRunLoopMode. + * + * If you'd like your socket to continue operation during other modes, you may want to add modes such as + * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. + * + * Accepted sockets will automatically inherit the same run loop modes as the listening socket. + * + * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. +**/ +- (BOOL)setRunLoopModes:(NSArray *)runLoopModes; +- (BOOL)addRunLoopMode:(NSString *)runLoopMode; +- (BOOL)removeRunLoopMode:(NSString *)runLoopMode; + +/** + * Returns the current run loop modes the AsyncSocket instance is operating in. + * The default set of run loop modes is NSDefaultRunLoopMode. +**/ +- (NSArray *)runLoopModes; + +/** + * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read + * any data that's left on the socket. +**/ +- (NSData *)unreadData; + +/* A few common line separators, for use with the readDataToData:... methods. */ ++ (NSData *)CRLFData; // 0x0D0A ++ (NSData *)CRData; // 0x0D ++ (NSData *)LFData; // 0x0A ++ (NSData *)ZeroData; // 0x00 + +@end diff --git a/games/Pong/Classes/AsyncSocket.m b/games/Pong/Classes/AsyncSocket.m new file mode 100644 index 0000000..4f0ab94 --- /dev/null +++ b/games/Pong/Classes/AsyncSocket.m @@ -0,0 +1,4343 @@ +// +// AsyncSocket.m +// +// This class is in the public domain. +// Originally created by Dustin Voss on Wed Jan 29 2003. +// Updated and maintained by Deusty Designs and the Mac development community. +// +// http://code.google.com/p/cocoaasyncsocket/ +// + +#import "AsyncSocket.h" +#import +#import +#import +#import + +#if TARGET_OS_IPHONE +// Note: You may need to add the CFNetwork Framework to your project +#import +#endif + +#pragma mark Declarations + +#define DEFAULT_PREBUFFERING YES // Whether pre-buffering is enabled by default + +#define READQUEUE_CAPACITY 5 // Initial capacity +#define WRITEQUEUE_CAPACITY 5 // Initial capacity +#define READALL_CHUNKSIZE 256 // Incremental increase in buffer size +#define WRITE_CHUNKSIZE (1024 * 4) // Limit on size of each write pass + +// AsyncSocket is RunLoop based, and is thus not thread-safe. +// You must always access your AsyncSocket instance from the thread/runloop in which the instance is running. +// You can use methods such as performSelectorOnThread to accomplish this. +// Failure to comply with these thread-safety rules may result in errors. +// You can enable this option to help diagnose where you are incorrectly accessing your socket. +#define DEBUG_THREAD_SAFETY 0 +// +// If you constantly need to access your socket from multiple threads +// then you may consider using GCDAsyncSocket instead, which is thread-safe. + +NSString *const AsyncSocketException = @"AsyncSocketException"; +NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain"; + + +enum AsyncSocketFlags +{ + kEnablePreBuffering = 1 << 0, // If set, pre-buffering is enabled + kDidStartDelegate = 1 << 1, // If set, disconnection results in delegate call + kDidCompleteOpenForRead = 1 << 2, // If set, open callback has been called for read stream + kDidCompleteOpenForWrite = 1 << 3, // If set, open callback has been called for write stream + kStartingReadTLS = 1 << 4, // If set, we're waiting for TLS negotiation to complete + kStartingWriteTLS = 1 << 5, // If set, we're waiting for TLS negotiation to complete + kForbidReadsWrites = 1 << 6, // If set, no new reads or writes are allowed + kDisconnectAfterReads = 1 << 7, // If set, disconnect after no more reads are queued + kDisconnectAfterWrites = 1 << 8, // If set, disconnect after no more writes are queued + kClosingWithError = 1 << 9, // If set, the socket is being closed due to an error + kDequeueReadScheduled = 1 << 10, // If set, a maybeDequeueRead operation is already scheduled + kDequeueWriteScheduled = 1 << 11, // If set, a maybeDequeueWrite operation is already scheduled + kSocketCanAcceptBytes = 1 << 12, // If set, we know socket can accept bytes. If unset, it's unknown. + kSocketHasBytesAvailable = 1 << 13, // If set, we know socket has bytes available. If unset, it's unknown. +}; + +@interface AsyncSocket (Private) + +// Connecting +- (void)startConnectTimeout:(NSTimeInterval)timeout; +- (void)endConnectTimeout; +- (void)doConnectTimeout:(NSTimer *)timer; + +// Socket Implementation +- (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr; +- (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr; +- (BOOL)bindSocketToAddress:(NSData *)interfaceAddr error:(NSError **)errPtr; +- (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; +- (BOOL)configureSocketAndReturnError:(NSError **)errPtr; +- (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; +- (void)doAcceptWithSocket:(CFSocketNativeHandle)newSocket; +- (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)err; + +// Stream Implementation +- (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr; +- (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; +- (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; +- (BOOL)configureStreamsAndReturnError:(NSError **)errPtr; +- (BOOL)openStreamsAndReturnError:(NSError **)errPtr; +- (void)doStreamOpen; +- (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr; + +// Disconnect Implementation +- (void)closeWithError:(NSError *)err; +- (void)recoverUnreadData; +- (void)emptyQueues; +- (void)close; + +// Errors +- (NSError *)getErrnoError; +- (NSError *)getAbortError; +- (NSError *)getStreamError; +- (NSError *)getSocketError; +- (NSError *)getConnectTimeoutError; +- (NSError *)getReadMaxedOutError; +- (NSError *)getReadTimeoutError; +- (NSError *)getWriteTimeoutError; +- (NSError *)errorFromCFStreamError:(CFStreamError)err; + +// Diagnostics +- (BOOL)isDisconnected; +- (BOOL)areStreamsConnected; +- (NSString *)connectedHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; +- (NSString *)connectedHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; +- (NSString *)connectedHostFromCFSocket4:(CFSocketRef)socket; +- (NSString *)connectedHostFromCFSocket6:(CFSocketRef)socket; +- (UInt16)connectedPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; +- (UInt16)connectedPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; +- (UInt16)connectedPortFromCFSocket4:(CFSocketRef)socket; +- (UInt16)connectedPortFromCFSocket6:(CFSocketRef)socket; +- (NSString *)localHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; +- (NSString *)localHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; +- (NSString *)localHostFromCFSocket4:(CFSocketRef)socket; +- (NSString *)localHostFromCFSocket6:(CFSocketRef)socket; +- (UInt16)localPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; +- (UInt16)localPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; +- (UInt16)localPortFromCFSocket4:(CFSocketRef)socket; +- (UInt16)localPortFromCFSocket6:(CFSocketRef)socket; +- (NSString *)hostFromAddress4:(struct sockaddr_in *)pSockaddr4; +- (NSString *)hostFromAddress6:(struct sockaddr_in6 *)pSockaddr6; +- (UInt16)portFromAddress4:(struct sockaddr_in *)pSockaddr4; +- (UInt16)portFromAddress6:(struct sockaddr_in6 *)pSockaddr6; + +// Reading +- (void)doBytesAvailable; +- (void)completeCurrentRead; +- (void)endCurrentRead; +- (void)scheduleDequeueRead; +- (void)maybeDequeueRead; +- (void)doReadTimeout:(NSTimer *)timer; + +// Writing +- (void)doSendBytes; +- (void)completeCurrentWrite; +- (void)endCurrentWrite; +- (void)scheduleDequeueWrite; +- (void)maybeDequeueWrite; +- (void)maybeScheduleDisconnect; +- (void)doWriteTimeout:(NSTimer *)timer; + +// Run Loop +- (void)runLoopAddSource:(CFRunLoopSourceRef)source; +- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source; +- (void)runLoopAddTimer:(NSTimer *)timer; +- (void)runLoopRemoveTimer:(NSTimer *)timer; +- (void)runLoopUnscheduleReadStream; +- (void)runLoopUnscheduleWriteStream; + +// Security +- (void)maybeStartTLS; +- (void)onTLSHandshakeSuccessful; + +// Callbacks +- (void)doCFCallback:(CFSocketCallBackType)type + forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData; +- (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream; +- (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream; + +@end + +static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); +static void MyCFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo); +static void MyCFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The AsyncReadPacket encompasses the instructions for any given read. + * The content of a read packet allows the code to determine if we're: + * - reading to a certain length + * - reading to a certain separator + * - or simply reading the first chunk of available data +**/ +@interface AsyncReadPacket : NSObject +{ + @public + NSMutableData *buffer; + NSUInteger startOffset; + NSUInteger bytesDone; + NSUInteger maxLength; + NSTimeInterval timeout; + NSUInteger readLength; + NSData *term; + BOOL bufferOwner; + NSUInteger originalBufferLength; + long tag; +} +- (id)initWithData:(NSMutableData *)d + startOffset:(NSUInteger)s + maxLength:(NSUInteger)m + timeout:(NSTimeInterval)t + readLength:(NSUInteger)l + terminator:(NSData *)e + tag:(long)i; + +- (NSUInteger)readLengthForNonTerm; +- (NSUInteger)readLengthForTerm; +- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr; + +- (NSUInteger)prebufferReadLengthForTerm; +- (NSInteger)searchForTermAfterPreBuffering:(NSUInteger)numBytes; +@end + +@implementation AsyncReadPacket + +- (id)initWithData:(NSMutableData *)d + startOffset:(NSUInteger)s + maxLength:(NSUInteger)m + timeout:(NSTimeInterval)t + readLength:(NSUInteger)l + terminator:(NSData *)e + tag:(long)i +{ + if((self = [super init])) + { + if (d) + { + buffer = [d retain]; + startOffset = s; + bufferOwner = NO; + originalBufferLength = [d length]; + } + else + { + if (readLength > 0) + buffer = [[NSMutableData alloc] initWithLength:readLength]; + else + buffer = [[NSMutableData alloc] initWithLength:0]; + + startOffset = 0; + bufferOwner = YES; + originalBufferLength = 0; + } + + bytesDone = 0; + maxLength = m; + timeout = t; + readLength = l; + term = [e copy]; + tag = i; + } + return self; +} + +/** + * For read packets without a set terminator, returns the safe length of data that can be read + * without exceeding the maxLength, or forcing a resize of the buffer if at all possible. +**/ +- (NSUInteger)readLengthForNonTerm +{ + NSAssert(term == nil, @"This method does not apply to term reads"); + + if (readLength > 0) + { + // Read a specific length of data + + return readLength - bytesDone; + + // No need to avoid resizing the buffer. + // It should be resized if the buffer space is less than the requested read length. + } + else + { + // Read all available data + + NSUInteger result = READALL_CHUNKSIZE; + + if (maxLength > 0) + { + result = MIN(result, (maxLength - bytesDone)); + } + + if (!bufferOwner) + { + // We did NOT create the buffer. + // It is owned by the caller. + // Avoid resizing the buffer if at all possible. + + if ([buffer length] == originalBufferLength) + { + NSUInteger buffSize = [buffer length]; + NSUInteger buffSpace = buffSize - startOffset - bytesDone; + + if (buffSpace > 0) + { + result = MIN(result, buffSpace); + } + } + } + + return result; + } +} + +/** + * For read packets with a set terminator, returns the safe length of data that can be read + * without going over a terminator, or the maxLength, or forcing a resize of the buffer if at all possible. + * + * It is assumed the terminator has not already been read. +**/ +- (NSUInteger)readLengthForTerm +{ + NSAssert(term != nil, @"This method does not apply to non-term reads"); + + // What we're going to do is look for a partial sequence of the terminator at the end of the buffer. + // If a partial sequence occurs, then we must assume the next bytes to arrive will be the rest of the term, + // and we can only read that amount. + // Otherwise, we're safe to read the entire length of the term. + + NSUInteger termLength = [term length]; + + // Shortcuts + if (bytesDone == 0) return termLength; + if (termLength == 1) return termLength; + + // i = index within buffer at which to check data + // j = length of term to check against + + NSUInteger i, j; + if (bytesDone >= termLength) + { + i = bytesDone - termLength + 1; + j = termLength - 1; + } + else + { + i = 0; + j = bytesDone; + } + + NSUInteger result = termLength; + + void *buf = [buffer mutableBytes]; + const void *termBuf = [term bytes]; + + while (i < bytesDone) + { + void *subbuf = buf + startOffset + i; + + if (memcmp(subbuf, termBuf, j) == 0) + { + result = termLength - j; + break; + } + + i++; + j--; + } + + if (maxLength > 0) + { + result = MIN(result, (maxLength - bytesDone)); + } + + if (!bufferOwner) + { + // We did NOT create the buffer. + // It is owned by the caller. + // Avoid resizing the buffer if at all possible. + + if ([buffer length] == originalBufferLength) + { + NSUInteger buffSize = [buffer length]; + NSUInteger buffSpace = buffSize - startOffset - bytesDone; + + if (buffSpace > 0) + { + result = MIN(result, buffSpace); + } + } + } + + return result; +} + +/** + * For read packets with a set terminator, + * returns the safe length of data that can be read from the given preBuffer, + * without going over a terminator or the maxLength. + * + * It is assumed the terminator has not already been read. +**/ +- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr +{ + NSAssert(term != nil, @"This method does not apply to non-term reads"); + NSAssert([preBuffer length] > 0, @"Invoked with empty pre buffer!"); + + // We know that the terminator, as a whole, doesn't exist in our own buffer. + // But it is possible that a portion of it exists in our buffer. + // So we're going to look for the terminator starting with a portion of our own buffer. + // + // Example: + // + // term length = 3 bytes + // bytesDone = 5 bytes + // preBuffer length = 5 bytes + // + // If we append the preBuffer to our buffer, + // it would look like this: + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // --------------------- + // + // So we start our search here: + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // -------^-^-^--------- + // + // And move forwards... + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // ---------^-^-^------- + // + // Until we find the terminator or reach the end. + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // ---------------^-^-^- + + BOOL found = NO; + + NSUInteger termLength = [term length]; + NSUInteger preBufferLength = [preBuffer length]; + + if ((bytesDone + preBufferLength) < termLength) + { + // Not enough data for a full term sequence yet + return preBufferLength; + } + + NSUInteger maxPreBufferLength; + if (maxLength > 0) { + maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); + + // Note: maxLength >= termLength + } + else { + maxPreBufferLength = preBufferLength; + } + + Byte seq[termLength]; + const void *termBuf = [term bytes]; + + NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); + void *buf = [buffer mutableBytes] + startOffset + bytesDone - bufLen; + + NSUInteger preLen = termLength - bufLen; + void *pre = (void *)[preBuffer bytes]; + + NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. + + NSUInteger result = preBufferLength; + + NSUInteger i; + for (i = 0; i < loopCount; i++) + { + if (bufLen > 0) + { + // Combining bytes from buffer and preBuffer + + memcpy(seq, buf, bufLen); + memcpy(seq + bufLen, pre, preLen); + + if (memcmp(seq, termBuf, termLength) == 0) + { + result = preLen; + found = YES; + break; + } + + buf++; + bufLen--; + preLen++; + } + else + { + // Comparing directly from preBuffer + + if (memcmp(pre, termBuf, termLength) == 0) + { + NSUInteger preOffset = pre - [preBuffer bytes]; // pointer arithmetic + + result = preOffset + termLength; + found = YES; + break; + } + + pre++; + } + } + + // There is no need to avoid resizing the buffer in this particular situation. + + if (foundPtr) *foundPtr = found; + return result; +} + +/** + * Assuming pre-buffering is enabled, returns the amount of data that can be read + * without going over the maxLength. +**/ +- (NSUInteger)prebufferReadLengthForTerm +{ + NSAssert(term != nil, @"This method does not apply to non-term reads"); + + NSUInteger result = READALL_CHUNKSIZE; + + if (maxLength > 0) + { + result = MIN(result, (maxLength - bytesDone)); + } + + if (!bufferOwner) + { + // We did NOT create the buffer. + // It is owned by the caller. + // Avoid resizing the buffer if at all possible. + + if ([buffer length] == originalBufferLength) + { + NSUInteger buffSize = [buffer length]; + NSUInteger buffSpace = buffSize - startOffset - bytesDone; + + if (buffSpace > 0) + { + result = MIN(result, buffSpace); + } + } + } + + return result; +} + +/** + * For read packets with a set terminator, scans the packet buffer for the term. + * It is assumed the terminator had not been fully read prior to the new bytes. + * + * If the term is found, the number of excess bytes after the term are returned. + * If the term is not found, this method will return -1. + * + * Note: A return value of zero means the term was found at the very end. +**/ +- (NSInteger)searchForTermAfterPreBuffering:(NSUInteger)numBytes +{ + NSAssert(term != nil, @"This method does not apply to non-term reads"); + NSAssert(bytesDone >= numBytes, @"Invoked with invalid numBytes!"); + + // We try to start the search such that the first new byte read matches up with the last byte of the term. + // We continue searching forward after this until the term no longer fits into the buffer. + + NSUInteger termLength = [term length]; + const void *termBuffer = [term bytes]; + + // Remember: This method is called after the bytesDone variable has been updated. + + NSUInteger prevBytesDone = bytesDone - numBytes; + + NSUInteger i; + if (prevBytesDone >= termLength) + i = prevBytesDone - termLength + 1; + else + i = 0; + + while ((i + termLength) <= bytesDone) + { + void *subBuffer = [buffer mutableBytes] + startOffset + i; + + if(memcmp(subBuffer, termBuffer, termLength) == 0) + { + return bytesDone - (i + termLength); + } + + i++; + } + + return -1; +} + +- (void)dealloc +{ + [buffer release]; + [term release]; + [super dealloc]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The AsyncWritePacket encompasses the instructions for any given write. +**/ +@interface AsyncWritePacket : NSObject +{ + @public + NSData *buffer; + NSUInteger bytesDone; + long tag; + NSTimeInterval timeout; +} +- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; +@end + +@implementation AsyncWritePacket + +- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i +{ + if((self = [super init])) + { + buffer = [d retain]; + timeout = t; + tag = i; + bytesDone = 0; + } + return self; +} + +- (void)dealloc +{ + [buffer release]; + [super dealloc]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The AsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. + * This class my be altered to support more than just TLS in the future. +**/ +@interface AsyncSpecialPacket : NSObject +{ + @public + NSDictionary *tlsSettings; +} +- (id)initWithTLSSettings:(NSDictionary *)settings; +@end + +@implementation AsyncSpecialPacket + +- (id)initWithTLSSettings:(NSDictionary *)settings +{ + if((self = [super init])) + { + tlsSettings = [settings copy]; + } + return self; +} + +- (void)dealloc +{ + [tlsSettings release]; + [super dealloc]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation AsyncSocket + +- (id)init +{ + return [self initWithDelegate:nil userData:0]; +} + +- (id)initWithDelegate:(id)delegate +{ + return [self initWithDelegate:delegate userData:0]; +} + +// Designated initializer. +- (id)initWithDelegate:(id)delegate userData:(long)userData +{ + if((self = [super init])) + { + theFlags = DEFAULT_PREBUFFERING ? kEnablePreBuffering : 0; + theDelegate = delegate; + theUserData = userData; + + theNativeSocket4 = 0; + theNativeSocket6 = 0; + + theSocket4 = NULL; + theSource4 = NULL; + + theSocket6 = NULL; + theSource6 = NULL; + + theRunLoop = NULL; + theReadStream = NULL; + theWriteStream = NULL; + + theConnectTimer = nil; + + theReadQueue = [[NSMutableArray alloc] initWithCapacity:READQUEUE_CAPACITY]; + theCurrentRead = nil; + theReadTimer = nil; + + partialReadBuffer = [[NSMutableData alloc] initWithCapacity:READALL_CHUNKSIZE]; + + theWriteQueue = [[NSMutableArray alloc] initWithCapacity:WRITEQUEUE_CAPACITY]; + theCurrentWrite = nil; + theWriteTimer = nil; + + // Socket context + NSAssert(sizeof(CFSocketContext) == sizeof(CFStreamClientContext), @"CFSocketContext != CFStreamClientContext"); + theContext.version = 0; + theContext.info = self; + theContext.retain = nil; + theContext.release = nil; + theContext.copyDescription = nil; + + // Default run loop modes + theRunLoopModes = [[NSArray arrayWithObject:NSDefaultRunLoopMode] retain]; + } + return self; +} + +// The socket may been initialized in a connected state and auto-released, so this should close it down cleanly. +- (void)dealloc +{ + [self close]; + [theReadQueue release]; + [theWriteQueue release]; + [theRunLoopModes release]; + [partialReadBuffer release]; + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + [super dealloc]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Thread-Safety +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)checkForThreadSafety +{ + if (theRunLoop && (theRunLoop != CFRunLoopGetCurrent())) + { + // AsyncSocket is RunLoop based. + // It is designed to be run and accessed from a particular thread/runloop. + // As such, it is faster as it does not have the overhead of locks/synchronization. + // + // However, this places a minimal requirement on the developer to maintain thread-safety. + // If you are seeing errors or crashes in AsyncSocket, + // it is very likely that thread-safety has been broken. + // This method may be enabled via the DEBUG_THREAD_SAFETY macro, + // and will allow you to discover the place in your code where thread-safety is being broken. + + [NSException raise:AsyncSocketException + format:@"Attempting to access AsyncSocket instance from incorrect thread."]; + + // Note: + // + // If you find you constantly need to access your socket from various threads, + // you may prefer to use GCDAsyncSocket which is thread-safe. + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Accessors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (long)userData +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return theUserData; +} + +- (void)setUserData:(long)userData +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + theUserData = userData; +} + +- (id)delegate +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return theDelegate; +} + +- (void)setDelegate:(id)delegate +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + theDelegate = delegate; +} + +- (BOOL)canSafelySetDelegate +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil); +} + +- (CFSocketRef)getCFSocket +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if(theSocket4) + return theSocket4; + else + return theSocket6; +} + +- (CFReadStreamRef)getCFReadStream +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return theReadStream; +} + +- (CFWriteStreamRef)getCFWriteStream +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return theWriteStream; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Progress +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (float)progressOfReadReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + // Check to make sure we're actually reading something right now, + // and that the read packet isn't an AsyncSpecialPacket (upgrade to TLS). + if (!theCurrentRead || ![theCurrentRead isKindOfClass:[AsyncReadPacket class]]) + { + if (tag != NULL) *tag = 0; + if (done != NULL) *done = 0; + if (total != NULL) *total = 0; + + return NAN; + } + + // It's only possible to know the progress of our read if we're reading to a certain length. + // If we're reading to data, we of course have no idea when the data will arrive. + // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. + + NSUInteger d = theCurrentRead->bytesDone; + NSUInteger t = theCurrentRead->readLength; + + if (tag != NULL) *tag = theCurrentRead->tag; + if (done != NULL) *done = d; + if (total != NULL) *total = t; + + if (t > 0.0) + return (float)d / (float)t; + else + return 1.0F; +} + +- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + // Check to make sure we're actually writing something right now, + // and that the write packet isn't an AsyncSpecialPacket (upgrade to TLS). + if (!theCurrentWrite || ![theCurrentWrite isKindOfClass:[AsyncWritePacket class]]) + { + if (tag != NULL) *tag = 0; + if (done != NULL) *done = 0; + if (total != NULL) *total = 0; + + return NAN; + } + + NSUInteger d = theCurrentWrite->bytesDone; + NSUInteger t = [theCurrentWrite->buffer length]; + + if (tag != NULL) *tag = theCurrentWrite->tag; + if (done != NULL) *done = d; + if (total != NULL) *total = t; + + return (float)d / (float)t; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Run Loop +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)runLoopAddSource:(CFRunLoopSourceRef)source +{ + for (NSString *runLoopMode in theRunLoopModes) + { + CFRunLoopAddSource(theRunLoop, source, (CFStringRef)runLoopMode); + } +} + +- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source +{ + for (NSString *runLoopMode in theRunLoopModes) + { + CFRunLoopRemoveSource(theRunLoop, source, (CFStringRef)runLoopMode); + } +} + +- (void)runLoopAddSource:(CFRunLoopSourceRef)source mode:(NSString *)runLoopMode +{ + CFRunLoopAddSource(theRunLoop, source, (CFStringRef)runLoopMode); +} + +- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source mode:(NSString *)runLoopMode +{ + CFRunLoopRemoveSource(theRunLoop, source, (CFStringRef)runLoopMode); +} + +- (void)runLoopAddTimer:(NSTimer *)timer +{ + for (NSString *runLoopMode in theRunLoopModes) + { + CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, (CFStringRef)runLoopMode); + } +} + +- (void)runLoopRemoveTimer:(NSTimer *)timer +{ + for (NSString *runLoopMode in theRunLoopModes) + { + CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, (CFStringRef)runLoopMode); + } +} + +- (void)runLoopAddTimer:(NSTimer *)timer mode:(NSString *)runLoopMode +{ + CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, (CFStringRef)runLoopMode); +} + +- (void)runLoopRemoveTimer:(NSTimer *)timer mode:(NSString *)runLoopMode +{ + CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, (CFStringRef)runLoopMode); +} + +- (void)runLoopUnscheduleReadStream +{ + for (NSString *runLoopMode in theRunLoopModes) + { + CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, (CFStringRef)runLoopMode); + } + CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL); +} + +- (void)runLoopUnscheduleWriteStream +{ + for (NSString *runLoopMode in theRunLoopModes) + { + CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, (CFStringRef)runLoopMode); + } + CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL); +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * See the header file for a full explanation of pre-buffering. +**/ +- (void)enablePreBuffering +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + theFlags |= kEnablePreBuffering; +} + +/** + * See the header file for a full explanation of this method. +**/ +- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop +{ + NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), + @"moveToRunLoop must be called from within the current RunLoop!"); + + if(runLoop == nil) + { + return NO; + } + if(theRunLoop == [runLoop getCFRunLoop]) + { + return YES; + } + + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + theFlags &= ~kDequeueReadScheduled; + theFlags &= ~kDequeueWriteScheduled; + + if(theReadStream && theWriteStream) + { + [self runLoopUnscheduleReadStream]; + [self runLoopUnscheduleWriteStream]; + } + + if(theSource4) [self runLoopRemoveSource:theSource4]; + if(theSource6) [self runLoopRemoveSource:theSource6]; + + // We do not retain the timers - they get retained by the runloop when we add them as a source. + // Since we're about to remove them as a source, we retain now, and release again below. + [theReadTimer retain]; + [theWriteTimer retain]; + + if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; + if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; + + theRunLoop = [runLoop getCFRunLoop]; + + if(theReadTimer) [self runLoopAddTimer:theReadTimer]; + if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; + + // Release timers since we retained them above + [theReadTimer release]; + [theWriteTimer release]; + + if(theSource4) [self runLoopAddSource:theSource4]; + if(theSource6) [self runLoopAddSource:theSource6]; + + if(theReadStream && theWriteStream) + { + if(![self attachStreamsToRunLoop:runLoop error:nil]) + { + return NO; + } + } + + [runLoop performSelector:@selector(maybeDequeueRead) target:self argument:nil order:0 modes:theRunLoopModes]; + [runLoop performSelector:@selector(maybeDequeueWrite) target:self argument:nil order:0 modes:theRunLoopModes]; + [runLoop performSelector:@selector(maybeScheduleDisconnect) target:self argument:nil order:0 modes:theRunLoopModes]; + + return YES; +} + +/** + * See the header file for a full explanation of this method. +**/ +- (BOOL)setRunLoopModes:(NSArray *)runLoopModes +{ + NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), + @"setRunLoopModes must be called from within the current RunLoop!"); + + if([runLoopModes count] == 0) + { + return NO; + } + if([theRunLoopModes isEqualToArray:runLoopModes]) + { + return YES; + } + + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + theFlags &= ~kDequeueReadScheduled; + theFlags &= ~kDequeueWriteScheduled; + + if(theReadStream && theWriteStream) + { + [self runLoopUnscheduleReadStream]; + [self runLoopUnscheduleWriteStream]; + } + + if(theSource4) [self runLoopRemoveSource:theSource4]; + if(theSource6) [self runLoopRemoveSource:theSource6]; + + // We do not retain the timers - they get retained by the runloop when we add them as a source. + // Since we're about to remove them as a source, we retain now, and release again below. + [theReadTimer retain]; + [theWriteTimer retain]; + + if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; + if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; + + [theRunLoopModes release]; + theRunLoopModes = [runLoopModes copy]; + + if(theReadTimer) [self runLoopAddTimer:theReadTimer]; + if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; + + // Release timers since we retained them above + [theReadTimer release]; + [theWriteTimer release]; + + if(theSource4) [self runLoopAddSource:theSource4]; + if(theSource6) [self runLoopAddSource:theSource6]; + + if(theReadStream && theWriteStream) + { + // Note: theRunLoop variable is a CFRunLoop, and NSRunLoop is NOT toll-free bridged with CFRunLoop. + // So we cannot pass theRunLoop to the method below, which is expecting a NSRunLoop parameter. + // Instead we pass nil, which will result in the method properly using the current run loop. + + if(![self attachStreamsToRunLoop:nil error:nil]) + { + return NO; + } + } + + [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + + return YES; +} + +- (BOOL)addRunLoopMode:(NSString *)runLoopMode +{ + NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), + @"addRunLoopMode must be called from within the current RunLoop!"); + + if(runLoopMode == nil) + { + return NO; + } + if([theRunLoopModes containsObject:runLoopMode]) + { + return YES; + } + + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + theFlags &= ~kDequeueReadScheduled; + theFlags &= ~kDequeueWriteScheduled; + + NSArray *newRunLoopModes = [theRunLoopModes arrayByAddingObject:runLoopMode]; + [theRunLoopModes release]; + theRunLoopModes = [newRunLoopModes retain]; + + if(theReadTimer) [self runLoopAddTimer:theReadTimer mode:runLoopMode]; + if(theWriteTimer) [self runLoopAddTimer:theWriteTimer mode:runLoopMode]; + + if(theSource4) [self runLoopAddSource:theSource4 mode:runLoopMode]; + if(theSource6) [self runLoopAddSource:theSource6 mode:runLoopMode]; + + if(theReadStream && theWriteStream) + { + CFReadStreamScheduleWithRunLoop(theReadStream, CFRunLoopGetCurrent(), (CFStringRef)runLoopMode); + CFWriteStreamScheduleWithRunLoop(theWriteStream, CFRunLoopGetCurrent(), (CFStringRef)runLoopMode); + } + + [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + + return YES; +} + +- (BOOL)removeRunLoopMode:(NSString *)runLoopMode +{ + NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), + @"addRunLoopMode must be called from within the current RunLoop!"); + + if(runLoopMode == nil) + { + return NO; + } + if(![theRunLoopModes containsObject:runLoopMode]) + { + return YES; + } + + NSMutableArray *newRunLoopModes = [[theRunLoopModes mutableCopy] autorelease]; + [newRunLoopModes removeObject:runLoopMode]; + + if([newRunLoopModes count] == 0) + { + return NO; + } + + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + theFlags &= ~kDequeueReadScheduled; + theFlags &= ~kDequeueWriteScheduled; + + [theRunLoopModes release]; + theRunLoopModes = [newRunLoopModes copy]; + + if(theReadTimer) [self runLoopRemoveTimer:theReadTimer mode:runLoopMode]; + if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer mode:runLoopMode]; + + if(theSource4) [self runLoopRemoveSource:theSource4 mode:runLoopMode]; + if(theSource6) [self runLoopRemoveSource:theSource6 mode:runLoopMode]; + + if(theReadStream && theWriteStream) + { + CFReadStreamScheduleWithRunLoop(theReadStream, CFRunLoopGetCurrent(), (CFStringRef)runLoopMode); + CFWriteStreamScheduleWithRunLoop(theWriteStream, CFRunLoopGetCurrent(), (CFStringRef)runLoopMode); + } + + [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + + return YES; +} + +- (NSArray *)runLoopModes +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return [[theRunLoopModes retain] autorelease]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Accepting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr +{ + return [self acceptOnInterface:nil port:port error:errPtr]; +} + +/** + * To accept on a certain interface, pass the address to accept on. + * To accept on any interface, pass nil or an empty string. + * To accept only connections from localhost pass "localhost" or "loopback". +**/ +- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr +{ + if (theDelegate == NULL) + { + [NSException raise:AsyncSocketException + format:@"Attempting to accept without a delegate. Set a delegate first."]; + } + + if (![self isDisconnected]) + { + [NSException raise:AsyncSocketException + format:@"Attempting to accept while connected or accepting connections. Disconnect first."]; + } + + // Clear queues (spurious read/write requests post disconnect) + [self emptyQueues]; + + // Set up the listen sockaddr structs if needed. + + NSData *address4 = nil, *address6 = nil; + if(interface == nil || ([interface length] == 0)) + { + // Accept on ANY address + struct sockaddr_in nativeAddr4; + nativeAddr4.sin_len = sizeof(struct sockaddr_in); + nativeAddr4.sin_family = AF_INET; + nativeAddr4.sin_port = htons(port); + nativeAddr4.sin_addr.s_addr = htonl(INADDR_ANY); + memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); + + struct sockaddr_in6 nativeAddr6; + nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); + nativeAddr6.sin6_family = AF_INET6; + nativeAddr6.sin6_port = htons(port); + nativeAddr6.sin6_flowinfo = 0; + nativeAddr6.sin6_addr = in6addr_any; + nativeAddr6.sin6_scope_id = 0; + + // Wrap the native address structures for CFSocketSetAddress. + address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + else if([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) + { + // Accept only on LOOPBACK address + struct sockaddr_in nativeAddr4; + nativeAddr4.sin_len = sizeof(struct sockaddr_in); + nativeAddr4.sin_family = AF_INET; + nativeAddr4.sin_port = htons(port); + nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); + + struct sockaddr_in6 nativeAddr6; + nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); + nativeAddr6.sin6_family = AF_INET6; + nativeAddr6.sin6_port = htons(port); + nativeAddr6.sin6_flowinfo = 0; + nativeAddr6.sin6_addr = in6addr_loopback; + nativeAddr6.sin6_scope_id = 0; + + // Wrap the native address structures for CFSocketSetAddress. + address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + else + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + struct addrinfo hints, *res, *res0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + + int error = getaddrinfo([interface UTF8String], [portStr UTF8String], &hints, &res0); + + if (error) + { + if (errPtr) + { + NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; + } + } + else + { + for (res = res0; res; res = res->ai_next) + { + if (!address4 && (res->ai_family == AF_INET)) + { + // Found IPv4 address + // Wrap the native address structures for CFSocketSetAddress. + address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + } + else if (!address6 && (res->ai_family == AF_INET6)) + { + // Found IPv6 address + // Wrap the native address structures for CFSocketSetAddress. + address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + } + } + freeaddrinfo(res0); + } + + if(!address4 && !address6) return NO; + } + + // Create the sockets. + + if (address4) + { + theSocket4 = [self newAcceptSocketForAddress:address4 error:errPtr]; + if (theSocket4 == NULL) goto Failed; + } + + if (address6) + { + theSocket6 = [self newAcceptSocketForAddress:address6 error:errPtr]; + + // Note: The iPhone doesn't currently support IPv6 + +#if !TARGET_OS_IPHONE + if (theSocket6 == NULL) goto Failed; +#endif + } + + // Attach the sockets to the run loop so that callback methods work + + [self attachSocketsToRunLoop:nil error:nil]; + + // Set the SO_REUSEADDR flags. + + int reuseOn = 1; + if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + + // Set the local bindings which causes the sockets to start listening. + + CFSocketError err; + if (theSocket4) + { + err = CFSocketSetAddress(theSocket4, (CFDataRef)address4); + if (err != kCFSocketSuccess) goto Failed; + + //NSLog(@"theSocket4: %hu", [self localPortFromCFSocket4:theSocket4]); + } + + if(port == 0 && theSocket4 && theSocket6) + { + // The user has passed in port 0, which means he wants to allow the kernel to choose the port for them + // However, the kernel will choose a different port for both theSocket4 and theSocket6 + // So we grab the port the kernel choose for theSocket4, and set it as the port for theSocket6 + UInt16 chosenPort = [self localPortFromCFSocket4:theSocket4]; + + struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)[address6 bytes]; + pSockAddr6->sin6_port = htons(chosenPort); + } + + if (theSocket6) + { + err = CFSocketSetAddress(theSocket6, (CFDataRef)address6); + if (err != kCFSocketSuccess) goto Failed; + + //NSLog(@"theSocket6: %hu", [self localPortFromCFSocket6:theSocket6]); + } + + theFlags |= kDidStartDelegate; + return YES; + +Failed: + if(errPtr) *errPtr = [self getSocketError]; + if(theSocket4 != NULL) + { + CFSocketInvalidate(theSocket4); + CFRelease(theSocket4); + theSocket4 = NULL; + } + if(theSocket6 != NULL) + { + CFSocketInvalidate(theSocket6); + CFRelease(theSocket6); + theSocket6 = NULL; + } + return NO; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Connecting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)connectToHost:(NSString*)hostname onPort:(UInt16)port error:(NSError **)errPtr +{ + return [self connectToHost:hostname onPort:port withTimeout:-1 error:errPtr]; +} + +/** + * This method creates an initial CFReadStream and CFWriteStream to the given host on the given port. + * The connection is then opened, and the corresponding CFSocket will be extracted after the connection succeeds. + * + * Thus the delegate will have access to the CFReadStream and CFWriteStream prior to connection, + * specifically in the onSocketWillConnect: method. +**/ +- (BOOL)connectToHost:(NSString *)hostname + onPort:(UInt16)port + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr +{ + if (theDelegate == NULL) + { + [NSException raise:AsyncSocketException + format:@"Attempting to connect without a delegate. Set a delegate first."]; + } + + if (![self isDisconnected]) + { + [NSException raise:AsyncSocketException + format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; + } + + // Clear queues (spurious read/write requests post disconnect) + [self emptyQueues]; + + if(![self createStreamsToHost:hostname onPort:port error:errPtr]) goto Failed; + if(![self attachStreamsToRunLoop:nil error:errPtr]) goto Failed; + if(![self configureStreamsAndReturnError:errPtr]) goto Failed; + if(![self openStreamsAndReturnError:errPtr]) goto Failed; + + [self startConnectTimeout:timeout]; + theFlags |= kDidStartDelegate; + + return YES; + +Failed: + [self close]; + return NO; +} + +- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr +{ + return [self connectToAddress:remoteAddr viaInterfaceAddress:nil withTimeout:-1 error:errPtr]; +} + +/** + * This method creates an initial CFSocket to the given address. + * The connection is then opened, and the corresponding CFReadStream and CFWriteStream will be + * created from the low-level sockets after the connection succeeds. + * + * Thus the delegate will have access to the CFSocket and CFSocketNativeHandle (BSD socket) prior to connection, + * specifically in the onSocketWillConnect: method. + * + * Note: The NSData parameter is expected to be a sockaddr structure. For example, an NSData object returned from + * NSNetservice addresses method. + * If you have an existing struct sockaddr you can convert it to an NSData object like so: + * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; + * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr +{ + return [self connectToAddress:remoteAddr viaInterfaceAddress:nil withTimeout:timeout error:errPtr]; +} + +/** + * This method is similar to the one above, but allows you to specify which socket interface + * the connection should run over. E.g. ethernet, wifi, bluetooth, etc. +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr + viaInterfaceAddress:(NSData *)interfaceAddr + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr +{ + if (theDelegate == NULL) + { + [NSException raise:AsyncSocketException + format:@"Attempting to connect without a delegate. Set a delegate first."]; + } + + if (![self isDisconnected]) + { + [NSException raise:AsyncSocketException + format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; + } + + // Clear queues (spurious read/write requests post disconnect) + [self emptyQueues]; + + if(![self createSocketForAddress:remoteAddr error:errPtr]) goto Failed; + if(![self bindSocketToAddress:interfaceAddr error:errPtr]) goto Failed; + if(![self attachSocketsToRunLoop:nil error:errPtr]) goto Failed; + if(![self configureSocketAndReturnError:errPtr]) goto Failed; + if(![self connectSocketToAddress:remoteAddr error:errPtr]) goto Failed; + + [self startConnectTimeout:timeout]; + theFlags |= kDidStartDelegate; + + return YES; + +Failed: + [self close]; + return NO; +} + +- (void)startConnectTimeout:(NSTimeInterval)timeout +{ + if(timeout >= 0.0) + { + theConnectTimer = [NSTimer timerWithTimeInterval:timeout + target:self + selector:@selector(doConnectTimeout:) + userInfo:nil + repeats:NO]; + [self runLoopAddTimer:theConnectTimer]; + } +} + +- (void)endConnectTimeout +{ + [theConnectTimer invalidate]; + theConnectTimer = nil; +} + +- (void)doConnectTimeout:(NSTimer *)timer +{ + #pragma unused(timer) + + [self endConnectTimeout]; + [self closeWithError:[self getConnectTimeoutError]]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Socket Implementation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Creates the accept sockets. + * Returns true if either IPv4 or IPv6 is created. + * If either is missing, an error is returned (even though the method may return true). +**/ +- (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr +{ + struct sockaddr *pSockAddr = (struct sockaddr *)[addr bytes]; + int addressFamily = pSockAddr->sa_family; + + CFSocketRef theSocket = CFSocketCreate(kCFAllocatorDefault, + addressFamily, + SOCK_STREAM, + 0, + kCFSocketAcceptCallBack, // Callback flags + (CFSocketCallBack)&MyCFSocketCallback, // Callback method + &theContext); + + if(theSocket == NULL) + { + if(errPtr) *errPtr = [self getSocketError]; + } + + return theSocket; +} + +- (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr +{ + struct sockaddr *pSockAddr = (struct sockaddr *)[remoteAddr bytes]; + + if(pSockAddr->sa_family == AF_INET) + { + theSocket4 = CFSocketCreate(NULL, // Default allocator + PF_INET, // Protocol Family + SOCK_STREAM, // Socket Type + IPPROTO_TCP, // Protocol + kCFSocketConnectCallBack, // Callback flags + (CFSocketCallBack)&MyCFSocketCallback, // Callback method + &theContext); // Socket Context + + if(theSocket4 == NULL) + { + if (errPtr) *errPtr = [self getSocketError]; + return NO; + } + } + else if(pSockAddr->sa_family == AF_INET6) + { + theSocket6 = CFSocketCreate(NULL, // Default allocator + PF_INET6, // Protocol Family + SOCK_STREAM, // Socket Type + IPPROTO_TCP, // Protocol + kCFSocketConnectCallBack, // Callback flags + (CFSocketCallBack)&MyCFSocketCallback, // Callback method + &theContext); // Socket Context + + if(theSocket6 == NULL) + { + if (errPtr) *errPtr = [self getSocketError]; + return NO; + } + } + else + { + if (errPtr) + { + NSString *errMsg = @"Remote address is not IPv4 or IPv6"; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; + } + return NO; + } + + return YES; +} + +- (BOOL)bindSocketToAddress:(NSData *)interfaceAddr error:(NSError **)errPtr +{ + if (interfaceAddr == nil) return YES; + + struct sockaddr *pSockAddr = (struct sockaddr *)[interfaceAddr bytes]; + + CFSocketRef theSocket = (theSocket4 != NULL) ? theSocket4 : theSocket6; + NSAssert((theSocket != NULL), @"bindSocketToAddress called without valid socket"); + + CFSocketNativeHandle nativeSocket = CFSocketGetNative(theSocket); + + if (pSockAddr->sa_family == AF_INET || pSockAddr->sa_family == AF_INET6) + { + int result = bind(nativeSocket, pSockAddr, (socklen_t)[interfaceAddr length]); + if (result != 0) + { + if (errPtr) *errPtr = [self getErrnoError]; + return NO; + } + } + else + { + if (errPtr) + { + NSString *errMsg = @"Interface address is not IPv4 or IPv6"; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; + } + return NO; + } + + return YES; +} + +/** + * Adds the CFSocket's to the run-loop so that callbacks will work properly. +**/ +- (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr +{ + #pragma unused(errPtr) + + // Get the CFRunLoop to which the socket should be attached. + theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; + + if(theSocket4) + { + theSource4 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket4, 0); + [self runLoopAddSource:theSource4]; + } + + if(theSocket6) + { + theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0); + [self runLoopAddSource:theSource6]; + } + + return YES; +} + +/** + * Allows the delegate method to configure the CFSocket or CFNativeSocket as desired before we connect. + * Note that the CFReadStream and CFWriteStream will not be available until after the connection is opened. +**/ +- (BOOL)configureSocketAndReturnError:(NSError **)errPtr +{ + // Call the delegate method for further configuration. + if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) + { + if([theDelegate onSocketWillConnect:self] == NO) + { + if (errPtr) *errPtr = [self getAbortError]; + return NO; + } + } + return YES; +} + +- (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr +{ + // Start connecting to the given address in the background + // The MyCFSocketCallback method will be called when the connection succeeds or fails + if(theSocket4) + { + CFSocketError err = CFSocketConnectToAddress(theSocket4, (CFDataRef)remoteAddr, -1); + if(err != kCFSocketSuccess) + { + if (errPtr) *errPtr = [self getSocketError]; + return NO; + } + } + else if(theSocket6) + { + CFSocketError err = CFSocketConnectToAddress(theSocket6, (CFDataRef)remoteAddr, -1); + if(err != kCFSocketSuccess) + { + if (errPtr) *errPtr = [self getSocketError]; + return NO; + } + } + + return YES; +} + +/** + * Attempt to make the new socket. + * If an error occurs, ignore this event. +**/ +- (void)doAcceptFromSocket:(CFSocketRef)parentSocket withNewNativeSocket:(CFSocketNativeHandle)newNativeSocket +{ + if(newNativeSocket) + { + // New socket inherits same delegate and run loop modes. + // Note: We use [self class] to support subclassing AsyncSocket. + AsyncSocket *newSocket = [[[[self class] alloc] initWithDelegate:theDelegate] autorelease]; + [newSocket setRunLoopModes:theRunLoopModes]; + + if(![newSocket createStreamsFromNative:newNativeSocket error:nil]) + goto Failed; + + if (parentSocket == theSocket4) + newSocket->theNativeSocket4 = newNativeSocket; + else + newSocket->theNativeSocket6 = newNativeSocket; + + if ([theDelegate respondsToSelector:@selector(onSocket:didAcceptNewSocket:)]) + [theDelegate onSocket:self didAcceptNewSocket:newSocket]; + + newSocket->theFlags |= kDidStartDelegate; + + NSRunLoop *runLoop = nil; + if ([theDelegate respondsToSelector:@selector(onSocket:wantsRunLoopForNewSocket:)]) + { + runLoop = [theDelegate onSocket:self wantsRunLoopForNewSocket:newSocket]; + } + + if(![newSocket attachStreamsToRunLoop:runLoop error:nil]) goto Failed; + if(![newSocket configureStreamsAndReturnError:nil]) goto Failed; + if(![newSocket openStreamsAndReturnError:nil]) goto Failed; + + return; + + Failed: + [newSocket close]; + } +} + +/** + * This method is called as a result of connectToAddress:withTimeout:error:. + * At this point we have an open CFSocket from which we need to create our read and write stream. +**/ +- (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)socketError +{ + NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); + + if(socketError == kCFSocketTimeout || socketError == kCFSocketError) + { + [self closeWithError:[self getSocketError]]; + return; + } + + // Get the underlying native (BSD) socket + CFSocketNativeHandle nativeSocket = CFSocketGetNative(sock); + + // Store a reference to it + if (sock == theSocket4) + theNativeSocket4 = nativeSocket; + else + theNativeSocket6 = nativeSocket; + + // Setup the CFSocket so that invalidating it will not close the underlying native socket + CFSocketSetSocketFlags(sock, 0); + + // Invalidate and release the CFSocket - All we need from here on out is the nativeSocket. + // Note: If we don't invalidate the CFSocket (leaving the native socket open) + // then theReadStream and theWriteStream won't function properly. + // Specifically, their callbacks won't work, with the exception of kCFStreamEventOpenCompleted. + // + // This is likely due to the mixture of the CFSocketCreateWithNative method, + // along with the CFStreamCreatePairWithSocket method. + // The documentation for CFSocketCreateWithNative states: + // + // If a CFSocket object already exists for sock, + // the function returns the pre-existing object instead of creating a new object; + // the context, callout, and callBackTypes parameters are ignored in this case. + // + // So the CFStreamCreateWithNative method invokes the CFSocketCreateWithNative method, + // thinking that is creating a new underlying CFSocket for it's own purposes. + // When it does this, it uses the context/callout/callbackTypes parameters to setup everything appropriately. + // However, if a CFSocket already exists for the native socket, + // then it is returned (as per the documentation), which in turn screws up the CFStreams. + + CFSocketInvalidate(sock); + CFRelease(sock); + theSocket4 = NULL; + theSocket6 = NULL; + + NSError *err; + BOOL pass = YES; + + if(pass && ![self createStreamsFromNative:nativeSocket error:&err]) pass = NO; + if(pass && ![self attachStreamsToRunLoop:nil error:&err]) pass = NO; + if(pass && ![self openStreamsAndReturnError:&err]) pass = NO; + + if(!pass) + { + [self closeWithError:err]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Stream Implementation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Creates the CFReadStream and CFWriteStream from the given native socket. + * The CFSocket may be extracted from either stream after the streams have been opened. + * + * Note: The given native socket must already be connected! +**/ +- (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr +{ + // Create the socket & streams. + CFStreamCreatePairWithSocket(kCFAllocatorDefault, native, &theReadStream, &theWriteStream); + if (theReadStream == NULL || theWriteStream == NULL) + { + NSError *err = [self getStreamError]; + + NSLog(@"AsyncSocket %p couldn't create streams from accepted socket: %@", self, err); + + if (errPtr) *errPtr = err; + return NO; + } + + // Ensure the CF & BSD socket is closed when the streams are closed. + CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + + return YES; +} + +/** + * Creates the CFReadStream and CFWriteStream from the given hostname and port number. + * The CFSocket may be extracted from either stream after the streams have been opened. +**/ +- (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr +{ + // Create the socket & streams. + CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)hostname, port, &theReadStream, &theWriteStream); + if (theReadStream == NULL || theWriteStream == NULL) + { + if (errPtr) *errPtr = [self getStreamError]; + return NO; + } + + // Ensure the CF & BSD socket is closed when the streams are closed. + CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + + return YES; +} + +- (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr +{ + // Get the CFRunLoop to which the socket should be attached. + theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; + + // Setup read stream callbacks + + CFOptionFlags readStreamEvents = kCFStreamEventHasBytesAvailable | + kCFStreamEventErrorOccurred | + kCFStreamEventEndEncountered | + kCFStreamEventOpenCompleted; + + if (!CFReadStreamSetClient(theReadStream, + readStreamEvents, + (CFReadStreamClientCallBack)&MyCFReadStreamCallback, + (CFStreamClientContext *)(&theContext))) + { + NSError *err = [self getStreamError]; + + NSLog (@"AsyncSocket %p couldn't attach read stream to run-loop,", self); + NSLog (@"Error: %@", err); + + if (errPtr) *errPtr = err; + return NO; + } + + // Setup write stream callbacks + + CFOptionFlags writeStreamEvents = kCFStreamEventCanAcceptBytes | + kCFStreamEventErrorOccurred | + kCFStreamEventEndEncountered | + kCFStreamEventOpenCompleted; + + if (!CFWriteStreamSetClient (theWriteStream, + writeStreamEvents, + (CFWriteStreamClientCallBack)&MyCFWriteStreamCallback, + (CFStreamClientContext *)(&theContext))) + { + NSError *err = [self getStreamError]; + + NSLog (@"AsyncSocket %p couldn't attach write stream to run-loop,", self); + NSLog (@"Error: %@", err); + + if (errPtr) *errPtr = err; + return NO; + } + + // Add read and write streams to run loop + + for (NSString *runLoopMode in theRunLoopModes) + { + CFReadStreamScheduleWithRunLoop(theReadStream, theRunLoop, (CFStringRef)runLoopMode); + CFWriteStreamScheduleWithRunLoop(theWriteStream, theRunLoop, (CFStringRef)runLoopMode); + } + + return YES; +} + +/** + * Allows the delegate method to configure the CFReadStream and/or CFWriteStream as desired before we connect. + * + * If being called from a connect method, + * the CFSocket and CFNativeSocket will not be available until after the connection is opened. +**/ +- (BOOL)configureStreamsAndReturnError:(NSError **)errPtr +{ + // Call the delegate method for further configuration. + if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) + { + if([theDelegate onSocketWillConnect:self] == NO) + { + if (errPtr) *errPtr = [self getAbortError]; + return NO; + } + } + return YES; +} + +- (BOOL)openStreamsAndReturnError:(NSError **)errPtr +{ + BOOL pass = YES; + + if(pass && !CFReadStreamOpen(theReadStream)) + { + NSLog (@"AsyncSocket %p couldn't open read stream,", self); + pass = NO; + } + + if(pass && !CFWriteStreamOpen(theWriteStream)) + { + NSLog (@"AsyncSocket %p couldn't open write stream,", self); + pass = NO; + } + + if(!pass) + { + if (errPtr) *errPtr = [self getStreamError]; + } + + return pass; +} + +/** + * Called when read or write streams open. + * When the socket is connected and both streams are open, consider the AsyncSocket instance to be ready. +**/ +- (void)doStreamOpen +{ + if ((theFlags & kDidCompleteOpenForRead) && (theFlags & kDidCompleteOpenForWrite)) + { + NSError *err = nil; + + // Get the socket + if (![self setSocketFromStreamsAndReturnError: &err]) + { + NSLog (@"AsyncSocket %p couldn't get socket from streams, %@. Disconnecting.", self, err); + [self closeWithError:err]; + return; + } + + // Stop the connection attempt timeout timer + [self endConnectTimeout]; + + if ([theDelegate respondsToSelector:@selector(onSocket:didConnectToHost:port:)]) + { + [theDelegate onSocket:self didConnectToHost:[self connectedHost] port:[self connectedPort]]; + } + + // Immediately deal with any already-queued requests. + [self maybeDequeueRead]; + [self maybeDequeueWrite]; + } +} + +- (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr +{ + // Get the CFSocketNativeHandle from theReadStream + CFSocketNativeHandle native; + CFDataRef nativeProp = CFReadStreamCopyProperty(theReadStream, kCFStreamPropertySocketNativeHandle); + if(nativeProp == NULL) + { + if (errPtr) *errPtr = [self getStreamError]; + return NO; + } + + CFIndex nativePropLen = CFDataGetLength(nativeProp); + CFIndex nativeLen = (CFIndex)sizeof(native); + + CFIndex len = MIN(nativePropLen, nativeLen); + + CFDataGetBytes(nativeProp, CFRangeMake(0, len), (UInt8 *)&native); + CFRelease(nativeProp); + + CFSocketRef theSocket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL); + if(theSocket == NULL) + { + if (errPtr) *errPtr = [self getSocketError]; + return NO; + } + + // Determine whether the connection was IPv4 or IPv6. + // We may already know if this was an accepted socket, + // or if the connectToAddress method was used. + // In either of the above two cases, the native socket variable would already be set. + + if (theNativeSocket4 > 0) + { + theSocket4 = theSocket; + return YES; + } + if (theNativeSocket6 > 0) + { + theSocket6 = theSocket; + return YES; + } + + CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); + if(peeraddr == NULL) + { + NSLog(@"AsyncSocket couldn't determine IP version of socket"); + + CFRelease(theSocket); + + if (errPtr) *errPtr = [self getSocketError]; + return NO; + } + struct sockaddr *sa = (struct sockaddr *)CFDataGetBytePtr(peeraddr); + + if(sa->sa_family == AF_INET) + { + theSocket4 = theSocket; + theNativeSocket4 = native; + } + else + { + theSocket6 = theSocket; + theNativeSocket6 = native; + } + + CFRelease(peeraddr); + + return YES; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Disconnect Implementation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Sends error message and disconnects +- (void)closeWithError:(NSError *)err +{ + theFlags |= kClosingWithError; + + if (theFlags & kDidStartDelegate) + { + // Try to salvage what data we can. + [self recoverUnreadData]; + + // Let the delegate know, so it can try to recover if it likes. + if ([theDelegate respondsToSelector:@selector(onSocket:willDisconnectWithError:)]) + { + [theDelegate onSocket:self willDisconnectWithError:err]; + } + } + [self close]; +} + +// Prepare partially read data for recovery. +- (void)recoverUnreadData +{ + if(theCurrentRead != nil) + { + // We never finished the current read. + // Check to see if it's a normal read packet (not AsyncSpecialPacket) and if it had read anything yet. + + if(([theCurrentRead isKindOfClass:[AsyncReadPacket class]]) && (theCurrentRead->bytesDone > 0)) + { + // We need to move its data into the front of the partial read buffer. + + void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; + + [partialReadBuffer replaceBytesInRange:NSMakeRange(0, 0) + withBytes:buffer + length:theCurrentRead->bytesDone]; + } + } + + [self emptyQueues]; +} + +- (void)emptyQueues +{ + if (theCurrentRead != nil) [self endCurrentRead]; + if (theCurrentWrite != nil) [self endCurrentWrite]; + + [theReadQueue removeAllObjects]; + [theWriteQueue removeAllObjects]; + + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueRead) object:nil]; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueWrite) object:nil]; + + theFlags &= ~kDequeueReadScheduled; + theFlags &= ~kDequeueWriteScheduled; +} + +/** + * Disconnects. This is called for both error and clean disconnections. +**/ +- (void)close +{ + // Empty queues + [self emptyQueues]; + + // Clear partialReadBuffer (pre-buffer and also unreadData buffer in case of error) + [partialReadBuffer replaceBytesInRange:NSMakeRange(0, [partialReadBuffer length]) withBytes:NULL length:0]; + + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil]; + + // Stop the connection attempt timeout timer + if (theConnectTimer != nil) + { + [self endConnectTimeout]; + } + + // Close streams. + if (theReadStream != NULL) + { + [self runLoopUnscheduleReadStream]; + CFReadStreamClose(theReadStream); + CFRelease(theReadStream); + theReadStream = NULL; + } + if (theWriteStream != NULL) + { + [self runLoopUnscheduleWriteStream]; + CFWriteStreamClose(theWriteStream); + CFRelease(theWriteStream); + theWriteStream = NULL; + } + + // Close sockets. + if (theSocket4 != NULL) + { + CFSocketInvalidate (theSocket4); + CFRelease (theSocket4); + theSocket4 = NULL; + } + if (theSocket6 != NULL) + { + CFSocketInvalidate (theSocket6); + CFRelease (theSocket6); + theSocket6 = NULL; + } + + // Closing the streams or sockets resulted in closing the underlying native socket + theNativeSocket4 = 0; + theNativeSocket6 = 0; + + // Remove run loop sources + if (theSource4 != NULL) + { + [self runLoopRemoveSource:theSource4]; + CFRelease (theSource4); + theSource4 = NULL; + } + if (theSource6 != NULL) + { + [self runLoopRemoveSource:theSource6]; + CFRelease (theSource6); + theSource6 = NULL; + } + theRunLoop = NULL; + + // If the client has passed the connect/accept method, then the connection has at least begun. + // Notify delegate that it is now ending. + BOOL shouldCallDelegate = (theFlags & kDidStartDelegate); + + // Clear all flags (except the pre-buffering flag, which should remain as is) + theFlags &= kEnablePreBuffering; + + if (shouldCallDelegate) + { + if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)]) + { + [theDelegate onSocketDidDisconnect:self]; + } + } + + // Do not access any instance variables after calling onSocketDidDisconnect. + // This gives the delegate freedom to release us without returning here and crashing. +} + +/** + * Disconnects immediately. Any pending reads or writes are dropped. +**/ +- (void)disconnect +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + [self close]; +} + +/** + * Diconnects after all pending reads have completed. +**/ +- (void)disconnectAfterReading +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + theFlags |= (kForbidReadsWrites | kDisconnectAfterReads); + + [self maybeScheduleDisconnect]; +} + +/** + * Disconnects after all pending writes have completed. +**/ +- (void)disconnectAfterWriting +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + theFlags |= (kForbidReadsWrites | kDisconnectAfterWrites); + + [self maybeScheduleDisconnect]; +} + +/** + * Disconnects after all pending reads and writes have completed. +**/ +- (void)disconnectAfterReadingAndWriting +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + theFlags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); + + [self maybeScheduleDisconnect]; +} + +/** + * Schedules a call to disconnect if possible. + * That is, if all writes have completed, and we're set to disconnect after writing, + * or if all reads have completed, and we're set to disconnect after reading. +**/ +- (void)maybeScheduleDisconnect +{ + BOOL shouldDisconnect = NO; + + if(theFlags & kDisconnectAfterReads) + { + if(([theReadQueue count] == 0) && (theCurrentRead == nil)) + { + if(theFlags & kDisconnectAfterWrites) + { + if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) + { + shouldDisconnect = YES; + } + } + else + { + shouldDisconnect = YES; + } + } + } + else if(theFlags & kDisconnectAfterWrites) + { + if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) + { + shouldDisconnect = YES; + } + } + + if(shouldDisconnect) + { + [self performSelector:@selector(disconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + } +} + +/** + * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read + * any data that's left on the socket. +**/ +- (NSData *)unreadData +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + // Ensure this method will only return data in the event of an error + if (!(theFlags & kClosingWithError)) return nil; + + if (theReadStream == NULL) return nil; + + NSUInteger totalBytesRead = [partialReadBuffer length]; + + BOOL error = NO; + while (!error && CFReadStreamHasBytesAvailable(theReadStream)) + { + if (totalBytesRead == [partialReadBuffer length]) + { + [partialReadBuffer increaseLengthBy:READALL_CHUNKSIZE]; + } + + // Number of bytes to read is space left in packet buffer. + NSUInteger bytesToRead = [partialReadBuffer length] - totalBytesRead; + + // Read data into packet buffer + UInt8 *packetbuf = (UInt8 *)( [partialReadBuffer mutableBytes] + totalBytesRead ); + + CFIndex result = CFReadStreamRead(theReadStream, packetbuf, bytesToRead); + + // Check results + if (result < 0) + { + error = YES; + } + else + { + CFIndex bytesRead = result; + + totalBytesRead += bytesRead; + } + } + + [partialReadBuffer setLength:totalBytesRead]; + + return partialReadBuffer; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Errors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns a standard error object for the current errno value. + * Errno is used for low-level BSD socket errors. +**/ +- (NSError *)getErrnoError +{ + NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; +} + +/** + * Returns a standard error message for a CFSocket error. + * Unfortunately, CFSocket offers no feedback on its errors. +**/ +- (NSError *)getSocketError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCFSocketError", + @"AsyncSocket", [NSBundle mainBundle], + @"General CFSocket error", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; +} + +- (NSError *)getStreamError +{ + CFStreamError err; + if (theReadStream != NULL) + { + err = CFReadStreamGetError (theReadStream); + if (err.error != 0) return [self errorFromCFStreamError: err]; + } + + if (theWriteStream != NULL) + { + err = CFWriteStreamGetError (theWriteStream); + if (err.error != 0) return [self errorFromCFStreamError: err]; + } + + return nil; +} + +/** + * Returns a standard AsyncSocket abort error. +**/ +- (NSError *)getAbortError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCanceledError", + @"AsyncSocket", [NSBundle mainBundle], + @"Connection canceled", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCanceledError userInfo:info]; +} + +/** + * Returns a standard AsyncSocket connect timeout error. +**/ +- (NSError *)getConnectTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketConnectTimeoutError", + @"AsyncSocket", [NSBundle mainBundle], + @"Attempt to connect to host timed out", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketConnectTimeoutError userInfo:info]; +} + +/** + * Returns a standard AsyncSocket maxed out error. +**/ +- (NSError *)getReadMaxedOutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadMaxedOutError", + @"AsyncSocket", [NSBundle mainBundle], + @"Read operation reached set maximum length", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadMaxedOutError userInfo:info]; +} + +/** + * Returns a standard AsyncSocket read timeout error. +**/ +- (NSError *)getReadTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadTimeoutError", + @"AsyncSocket", [NSBundle mainBundle], + @"Read operation timed out", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadTimeoutError userInfo:info]; +} + +/** + * Returns a standard AsyncSocket write timeout error. +**/ +- (NSError *)getWriteTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketWriteTimeoutError", + @"AsyncSocket", [NSBundle mainBundle], + @"Write operation timed out", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketWriteTimeoutError userInfo:info]; +} + +- (NSError *)errorFromCFStreamError:(CFStreamError)err +{ + if (err.domain == 0 && err.error == 0) return nil; + + // Can't use switch; these constants aren't int literals. + NSString *domain = @"CFStreamError (unlisted domain)"; + NSString *message = nil; + + if(err.domain == kCFStreamErrorDomainPOSIX) { + domain = NSPOSIXErrorDomain; + } + else if(err.domain == kCFStreamErrorDomainMacOSStatus) { + domain = NSOSStatusErrorDomain; + } + else if(err.domain == kCFStreamErrorDomainMach) { + domain = NSMachErrorDomain; + } + else if(err.domain == kCFStreamErrorDomainNetDB) + { + domain = @"kCFStreamErrorDomainNetDB"; + message = [NSString stringWithCString:gai_strerror(err.error) encoding:NSASCIIStringEncoding]; + } + else if(err.domain == kCFStreamErrorDomainNetServices) { + domain = @"kCFStreamErrorDomainNetServices"; + } + else if(err.domain == kCFStreamErrorDomainSOCKS) { + domain = @"kCFStreamErrorDomainSOCKS"; + } + else if(err.domain == kCFStreamErrorDomainSystemConfiguration) { + domain = @"kCFStreamErrorDomainSystemConfiguration"; + } + else if(err.domain == kCFStreamErrorDomainSSL) { + domain = @"kCFStreamErrorDomainSSL"; + } + + NSDictionary *info = nil; + if(message != nil) + { + info = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey]; + } + return [NSError errorWithDomain:domain code:err.error userInfo:info]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Diagnostics +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isDisconnected +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if (theNativeSocket4 > 0) return NO; + if (theNativeSocket6 > 0) return NO; + + if (theSocket4) return NO; + if (theSocket6) return NO; + + if (theReadStream) return NO; + if (theWriteStream) return NO; + + return YES; +} + +- (BOOL)isConnected +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return [self areStreamsConnected]; +} + +- (NSString *)connectedHost +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if(theSocket4) + return [self connectedHostFromCFSocket4:theSocket4]; + if(theSocket6) + return [self connectedHostFromCFSocket6:theSocket6]; + + if(theNativeSocket4 > 0) + return [self connectedHostFromNativeSocket4:theNativeSocket4]; + if(theNativeSocket6 > 0) + return [self connectedHostFromNativeSocket6:theNativeSocket6]; + + return nil; +} + +- (UInt16)connectedPort +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if(theSocket4) + return [self connectedPortFromCFSocket4:theSocket4]; + if(theSocket6) + return [self connectedPortFromCFSocket6:theSocket6]; + + if(theNativeSocket4 > 0) + return [self connectedPortFromNativeSocket4:theNativeSocket4]; + if(theNativeSocket6 > 0) + return [self connectedPortFromNativeSocket6:theNativeSocket6]; + + return 0; +} + +- (NSString *)localHost +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if(theSocket4) + return [self localHostFromCFSocket4:theSocket4]; + if(theSocket6) + return [self localHostFromCFSocket6:theSocket6]; + + if(theNativeSocket4 > 0) + return [self localHostFromNativeSocket4:theNativeSocket4]; + if(theNativeSocket6 > 0) + return [self localHostFromNativeSocket6:theNativeSocket6]; + + return nil; +} + +- (UInt16)localPort +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if(theSocket4) + return [self localPortFromCFSocket4:theSocket4]; + if(theSocket6) + return [self localPortFromCFSocket6:theSocket6]; + + if(theNativeSocket4 > 0) + return [self localPortFromNativeSocket4:theNativeSocket4]; + if(theNativeSocket6 > 0) + return [self localPortFromNativeSocket6:theNativeSocket6]; + + return 0; +} + +- (NSString *)connectedHost4 +{ + if(theSocket4) + return [self connectedHostFromCFSocket4:theSocket4]; + if(theNativeSocket4 > 0) + return [self connectedHostFromNativeSocket4:theNativeSocket4]; + + return nil; +} + +- (NSString *)connectedHost6 +{ + if(theSocket6) + return [self connectedHostFromCFSocket6:theSocket6]; + if(theNativeSocket6 > 0) + return [self connectedHostFromNativeSocket6:theNativeSocket6]; + + return nil; +} + +- (UInt16)connectedPort4 +{ + if(theSocket4) + return [self connectedPortFromCFSocket4:theSocket4]; + if(theNativeSocket4 > 0) + return [self connectedPortFromNativeSocket4:theNativeSocket4]; + + return 0; +} + +- (UInt16)connectedPort6 +{ + if(theSocket6) + return [self connectedPortFromCFSocket6:theSocket6]; + if(theNativeSocket6 > 0) + return [self connectedPortFromNativeSocket6:theNativeSocket6]; + + return 0; +} + +- (NSString *)localHost4 +{ + if(theSocket4) + return [self localHostFromCFSocket4:theSocket4]; + if(theNativeSocket4 > 0) + return [self localHostFromNativeSocket4:theNativeSocket4]; + + return nil; +} + +- (NSString *)localHost6 +{ + if(theSocket6) + return [self localHostFromCFSocket6:theSocket6]; + if(theNativeSocket6 > 0) + return [self localHostFromNativeSocket6:theNativeSocket6]; + + return nil; +} + +- (UInt16)localPort4 +{ + if(theSocket4) + return [self localPortFromCFSocket4:theSocket4]; + if(theNativeSocket4 > 0) + return [self localPortFromNativeSocket4:theNativeSocket4]; + + return 0; +} + +- (UInt16)localPort6 +{ + if(theSocket6) + return [self localPortFromCFSocket6:theSocket6]; + if(theNativeSocket6 > 0) + return [self localPortFromNativeSocket6:theNativeSocket6]; + + return 0; +} + +- (NSString *)connectedHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket +{ + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return nil; + } + return [self hostFromAddress4:&sockaddr4]; +} + +- (NSString *)connectedHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket +{ + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return nil; + } + return [self hostFromAddress6:&sockaddr6]; +} + +- (NSString *)connectedHostFromCFSocket4:(CFSocketRef)theSocket +{ + CFDataRef peeraddr; + NSString *peerstr = nil; + + if((peeraddr = CFSocketCopyPeerAddress(theSocket))) + { + struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(peeraddr); + + peerstr = [self hostFromAddress4:pSockAddr]; + CFRelease (peeraddr); + } + + return peerstr; +} + +- (NSString *)connectedHostFromCFSocket6:(CFSocketRef)theSocket +{ + CFDataRef peeraddr; + NSString *peerstr = nil; + + if((peeraddr = CFSocketCopyPeerAddress(theSocket))) + { + struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(peeraddr); + + peerstr = [self hostFromAddress6:pSockAddr]; + CFRelease (peeraddr); + } + + return peerstr; +} + +- (UInt16)connectedPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket +{ + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return 0; + } + return [self portFromAddress4:&sockaddr4]; +} + +- (UInt16)connectedPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket +{ + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return 0; + } + return [self portFromAddress6:&sockaddr6]; +} + +- (UInt16)connectedPortFromCFSocket4:(CFSocketRef)theSocket +{ + CFDataRef peeraddr; + UInt16 peerport = 0; + + if((peeraddr = CFSocketCopyPeerAddress(theSocket))) + { + struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(peeraddr); + + peerport = [self portFromAddress4:pSockAddr]; + CFRelease (peeraddr); + } + + return peerport; +} + +- (UInt16)connectedPortFromCFSocket6:(CFSocketRef)theSocket +{ + CFDataRef peeraddr; + UInt16 peerport = 0; + + if((peeraddr = CFSocketCopyPeerAddress(theSocket))) + { + struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(peeraddr); + + peerport = [self portFromAddress6:pSockAddr]; + CFRelease (peeraddr); + } + + return peerport; +} + +- (NSString *)localHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket +{ + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return nil; + } + return [self hostFromAddress4:&sockaddr4]; +} + +- (NSString *)localHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket +{ + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return nil; + } + return [self hostFromAddress6:&sockaddr6]; +} + +- (NSString *)localHostFromCFSocket4:(CFSocketRef)theSocket +{ + CFDataRef selfaddr; + NSString *selfstr = nil; + + if((selfaddr = CFSocketCopyAddress(theSocket))) + { + struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(selfaddr); + + selfstr = [self hostFromAddress4:pSockAddr]; + CFRelease (selfaddr); + } + + return selfstr; +} + +- (NSString *)localHostFromCFSocket6:(CFSocketRef)theSocket +{ + CFDataRef selfaddr; + NSString *selfstr = nil; + + if((selfaddr = CFSocketCopyAddress(theSocket))) + { + struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(selfaddr); + + selfstr = [self hostFromAddress6:pSockAddr]; + CFRelease (selfaddr); + } + + return selfstr; +} + +- (UInt16)localPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket +{ + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return 0; + } + return [self portFromAddress4:&sockaddr4]; +} + +- (UInt16)localPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket +{ + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return 0; + } + return [self portFromAddress6:&sockaddr6]; +} + +- (UInt16)localPortFromCFSocket4:(CFSocketRef)theSocket +{ + CFDataRef selfaddr; + UInt16 selfport = 0; + + if ((selfaddr = CFSocketCopyAddress(theSocket))) + { + struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(selfaddr); + + selfport = [self portFromAddress4:pSockAddr]; + CFRelease (selfaddr); + } + + return selfport; +} + +- (UInt16)localPortFromCFSocket6:(CFSocketRef)theSocket +{ + CFDataRef selfaddr; + UInt16 selfport = 0; + + if ((selfaddr = CFSocketCopyAddress(theSocket))) + { + struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(selfaddr); + + selfport = [self portFromAddress6:pSockAddr]; + CFRelease (selfaddr); + } + + return selfport; +} + +- (NSString *)hostFromAddress4:(struct sockaddr_in *)pSockaddr4 +{ + char addrBuf[INET_ADDRSTRLEN]; + + if(inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) + { + [NSException raise:NSInternalInconsistencyException format:@"Cannot convert IPv4 address to string."]; + } + + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; +} + +- (NSString *)hostFromAddress6:(struct sockaddr_in6 *)pSockaddr6 +{ + char addrBuf[INET6_ADDRSTRLEN]; + + if(inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) + { + [NSException raise:NSInternalInconsistencyException format:@"Cannot convert IPv6 address to string."]; + } + + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; +} + +- (UInt16)portFromAddress4:(struct sockaddr_in *)pSockaddr4 +{ + return ntohs(pSockaddr4->sin_port); +} + +- (UInt16)portFromAddress6:(struct sockaddr_in6 *)pSockaddr6 +{ + return ntohs(pSockaddr6->sin6_port); +} + +- (NSData *)connectedAddress +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + // Extract address from CFSocket + + CFSocketRef theSocket; + + if (theSocket4) + theSocket = theSocket4; + else + theSocket = theSocket6; + + if (theSocket) + { + CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); + + if (peeraddr == NULL) return nil; + + return [(NSData *)NSMakeCollectable(peeraddr) autorelease]; + } + + // Extract address from CFSocketNativeHandle + + socklen_t sockaddrlen; + CFSocketNativeHandle theNativeSocket = 0; + + if (theNativeSocket4 > 0) + { + theNativeSocket = theNativeSocket4; + sockaddrlen = sizeof(struct sockaddr_in); + } + else + { + theNativeSocket = theNativeSocket6; + sockaddrlen = sizeof(struct sockaddr_in6); + } + + NSData *result = nil; + void *sockaddr = malloc(sockaddrlen); + + if(getpeername(theNativeSocket, (struct sockaddr *)sockaddr, &sockaddrlen) >= 0) + { + result = [NSData dataWithBytesNoCopy:sockaddr length:sockaddrlen freeWhenDone:YES]; + } + else + { + free(sockaddr); + } + + return result; +} + +- (NSData *)localAddress +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + // Extract address from CFSocket + + CFSocketRef theSocket; + + if (theSocket4) + theSocket = theSocket4; + else + theSocket = theSocket6; + + if (theSocket) + { + CFDataRef selfaddr = CFSocketCopyAddress(theSocket); + + if (selfaddr == NULL) return nil; + + return [(NSData *)NSMakeCollectable(selfaddr) autorelease]; + } + + // Extract address from CFSocketNativeHandle + + socklen_t sockaddrlen; + CFSocketNativeHandle theNativeSocket = 0; + + if (theNativeSocket4 > 0) + { + theNativeSocket = theNativeSocket4; + sockaddrlen = sizeof(struct sockaddr_in); + } + else + { + theNativeSocket = theNativeSocket6; + sockaddrlen = sizeof(struct sockaddr_in6); + } + + NSData *result = nil; + void *sockaddr = malloc(sockaddrlen); + + if(getsockname(theNativeSocket, (struct sockaddr *)sockaddr, &sockaddrlen) >= 0) + { + result = [NSData dataWithBytesNoCopy:sockaddr length:sockaddrlen freeWhenDone:YES]; + } + else + { + free(sockaddr); + } + + return result; +} + +- (BOOL)isIPv4 +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return (theNativeSocket4 > 0 || theSocket4 != NULL); +} + +- (BOOL)isIPv6 +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + return (theNativeSocket6 > 0 || theSocket6 != NULL); +} + +- (BOOL)areStreamsConnected +{ + CFStreamStatus s; + + if (theReadStream != NULL) + { + s = CFReadStreamGetStatus(theReadStream); + if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusReading || s == kCFStreamStatusError) ) + return NO; + } + else return NO; + + if (theWriteStream != NULL) + { + s = CFWriteStreamGetStatus(theWriteStream); + if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusWriting || s == kCFStreamStatusError) ) + return NO; + } + else return NO; + + return YES; +} + +- (NSString *)description +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + static const char *statstr[] = {"not open","opening","open","reading","writing","at end","closed","has error"}; + CFStreamStatus rs = (theReadStream != NULL) ? CFReadStreamGetStatus(theReadStream) : 0; + CFStreamStatus ws = (theWriteStream != NULL) ? CFWriteStreamGetStatus(theWriteStream) : 0; + + NSString *peerstr, *selfstr; + + BOOL is4 = [self isIPv4]; + BOOL is6 = [self isIPv6]; + + if (is4 || is6) + { + if (is4 && is6) + { + peerstr = [NSString stringWithFormat: @"%@/%@ %u", + [self connectedHost4], + [self connectedHost6], + [self connectedPort]]; + } + else if (is4) + { + peerstr = [NSString stringWithFormat: @"%@ %u", + [self connectedHost4], + [self connectedPort4]]; + } + else + { + peerstr = [NSString stringWithFormat: @"%@ %u", + [self connectedHost6], + [self connectedPort6]]; + } + } + else peerstr = @"nowhere"; + + if (is4 || is6) + { + if (is4 && is6) + { + selfstr = [NSString stringWithFormat: @"%@/%@ %u", + [self localHost4], + [self localHost6], + [self localPort]]; + } + else if (is4) + { + selfstr = [NSString stringWithFormat: @"%@ %u", + [self localHost4], + [self localPort4]]; + } + else + { + selfstr = [NSString stringWithFormat: @"%@ %u", + [self localHost6], + [self localPort6]]; + } + } + else selfstr = @"nowhere"; + + NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:150]; + + [ms appendString:[NSString stringWithFormat:@"readLength > 0) + percentDone = (float)theCurrentRead->bytesDone / (float)theCurrentRead->readLength * 100.0F; + else + percentDone = 100.0F; + + [ms appendString: [NSString stringWithFormat:@"currently read %u bytes (%d%% done), ", + (unsigned int)[theCurrentRead->buffer length], + theCurrentRead->bytesDone ? percentDone : 0]]; + } + + if (theCurrentWrite == nil) + [ms appendString: @"no current write, "]; + else + { + int percentDone = (float)theCurrentWrite->bytesDone / (float)[theCurrentWrite->buffer length] * 100.0F; + + [ms appendString: [NSString stringWithFormat:@"currently written %u (%d%%), ", + (unsigned int)[theCurrentWrite->buffer length], + theCurrentWrite->bytesDone ? percentDone : 0]]; + } + + [ms appendString:[NSString stringWithFormat:@"read stream %p %s, ", theReadStream, statstr[rs]]]; + [ms appendString:[NSString stringWithFormat:@"write stream %p %s", theWriteStream, statstr[ws]]]; + + if(theFlags & kDisconnectAfterReads) + { + if(theFlags & kDisconnectAfterWrites) + [ms appendString: @", will disconnect after reads & writes"]; + else + [ms appendString: @", will disconnect after reads"]; + } + else if(theFlags & kDisconnectAfterWrites) + { + [ms appendString: @", will disconnect after writes"]; + } + + if (![self isConnected]) [ms appendString: @", not connected"]; + + [ms appendString:@">"]; + + return [ms autorelease]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Reading +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; +} + +- (void)readDataWithTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag +{ + [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; +} + +- (void)readDataWithTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + maxLength:(NSUInteger)length + tag:(long)tag +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if (offset > [buffer length]) return; + if (theFlags & kForbidReadsWrites) return; + + AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:length + timeout:timeout + readLength:0 + terminator:nil + tag:tag]; + [theReadQueue addObject:packet]; + [self scheduleDequeueRead]; + + [packet release]; +} + +- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; +} + +- (void)readDataToLength:(NSUInteger)length + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if (length == 0) return; + if (offset > [buffer length]) return; + if (theFlags & kForbidReadsWrites) return; + + AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:0 + timeout:timeout + readLength:length + terminator:nil + tag:tag]; + [theReadQueue addObject:packet]; + [self scheduleDequeueRead]; + + [packet release]; +} + +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; +} + +- (void)readDataToData:(NSData *)data + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag +{ + [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; +} + +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag +{ + [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; +} + +- (void)readDataToData:(NSData *)data + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + maxLength:(NSUInteger)length + tag:(long)tag +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if (data == nil || [data length] == 0) return; + if (offset > [buffer length]) return; + if (length > 0 && length < [data length]) return; + if (theFlags & kForbidReadsWrites) return; + + AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:length + timeout:timeout + readLength:0 + terminator:data + tag:tag]; + [theReadQueue addObject:packet]; + [self scheduleDequeueRead]; + + [packet release]; +} + +/** + * Puts a maybeDequeueRead on the run loop. + * An assumption here is that selectors will be performed consecutively within their priority. +**/ +- (void)scheduleDequeueRead +{ + if((theFlags & kDequeueReadScheduled) == 0) + { + theFlags |= kDequeueReadScheduled; + [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + } +} + +/** + * This method starts a new read, if needed. + * It is called when a user requests a read, + * or when a stream opens that may have requested reads sitting in the queue, etc. +**/ +- (void)maybeDequeueRead +{ + // Unset the flag indicating a call to this method is scheduled + theFlags &= ~kDequeueReadScheduled; + + // If we're not currently processing a read AND we have an available read stream + if((theCurrentRead == nil) && (theReadStream != NULL)) + { + if([theReadQueue count] > 0) + { + // Dequeue the next object in the write queue + theCurrentRead = [[theReadQueue objectAtIndex:0] retain]; + [theReadQueue removeObjectAtIndex:0]; + + if([theCurrentRead isKindOfClass:[AsyncSpecialPacket class]]) + { + // Attempt to start TLS + theFlags |= kStartingReadTLS; + + // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set + [self maybeStartTLS]; + } + else + { + // Start time-out timer + if(theCurrentRead->timeout >= 0.0) + { + theReadTimer = [NSTimer timerWithTimeInterval:theCurrentRead->timeout + target:self + selector:@selector(doReadTimeout:) + userInfo:nil + repeats:NO]; + [self runLoopAddTimer:theReadTimer]; + } + + // Immediately read, if possible + [self doBytesAvailable]; + } + } + else if(theFlags & kDisconnectAfterReads) + { + if(theFlags & kDisconnectAfterWrites) + { + if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) + { + [self disconnect]; + } + } + else + { + [self disconnect]; + } + } + } +} + +/** + * Call this method in doBytesAvailable instead of CFReadStreamHasBytesAvailable(). + * This method supports pre-buffering properly as well as the kSocketHasBytesAvailable flag. +**/ +- (BOOL)hasBytesAvailable +{ + if ((theFlags & kSocketHasBytesAvailable) || ([partialReadBuffer length] > 0)) + { + return YES; + } + else + { + return CFReadStreamHasBytesAvailable(theReadStream); + } +} + +/** + * Call this method in doBytesAvailable instead of CFReadStreamRead(). + * This method support pre-buffering properly. +**/ +- (CFIndex)readIntoBuffer:(void *)buffer maxLength:(NSUInteger)length +{ + if([partialReadBuffer length] > 0) + { + // Determine the maximum amount of data to read + NSUInteger bytesToRead = MIN(length, [partialReadBuffer length]); + + // Copy the bytes from the partial read buffer + memcpy(buffer, [partialReadBuffer bytes], (size_t)bytesToRead); + + // Remove the copied bytes from the partial read buffer + [partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToRead) withBytes:NULL length:0]; + + return (CFIndex)bytesToRead; + } + else + { + // Unset the "has-bytes-available" flag + theFlags &= ~kSocketHasBytesAvailable; + + return CFReadStreamRead(theReadStream, (UInt8 *)buffer, length); + } +} + +/** + * This method is called when a new read is taken from the read queue or when new data becomes available on the stream. +**/ +- (void)doBytesAvailable +{ + // If data is available on the stream, but there is no read request, then we don't need to process the data yet. + // Also, if there is a read request but no read stream setup, we can't process any data yet. + if((theCurrentRead == nil) || (theReadStream == NULL)) + { + return; + } + + // Note: This method is not called if theCurrentRead is an AsyncSpecialPacket (startTLS packet) + + NSUInteger totalBytesRead = 0; + + BOOL done = NO; + BOOL socketError = NO; + BOOL maxoutError = NO; + + while(!done && !socketError && !maxoutError && [self hasBytesAvailable]) + { + BOOL didPreBuffer = NO; + BOOL didReadFromPreBuffer = NO; + + // There are 3 types of read packets: + // + // 1) Read all available data. + // 2) Read a specific length of data. + // 3) Read up to a particular terminator. + + NSUInteger bytesToRead; + + if (theCurrentRead->term != nil) + { + // Read type #3 - read up to a terminator + // + // If pre-buffering is enabled we'll read a chunk and search for the terminator. + // If the terminator is found, overflow data will be placed in the partialReadBuffer for the next read. + // + // If pre-buffering is disabled we'll be forced to read only a few bytes. + // Just enough to ensure we don't go past our term or over our max limit. + // + // If we already have data pre-buffered, we can read directly from it. + + if ([partialReadBuffer length] > 0) + { + didReadFromPreBuffer = YES; + bytesToRead = [theCurrentRead readLengthForTermWithPreBuffer:partialReadBuffer found:&done]; + } + else + { + if (theFlags & kEnablePreBuffering) + { + didPreBuffer = YES; + bytesToRead = [theCurrentRead prebufferReadLengthForTerm]; + } + else + { + bytesToRead = [theCurrentRead readLengthForTerm]; + } + } + } + else + { + // Read type #1 or #2 + + bytesToRead = [theCurrentRead readLengthForNonTerm]; + } + + // Make sure we have enough room in the buffer for our read + + NSUInteger buffSize = [theCurrentRead->buffer length]; + NSUInteger buffSpace = buffSize - theCurrentRead->startOffset - theCurrentRead->bytesDone; + + if (bytesToRead > buffSpace) + { + NSUInteger buffInc = bytesToRead - buffSpace; + + [theCurrentRead->buffer increaseLengthBy:buffInc]; + } + + // Read data into packet buffer + + void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; + void *subBuffer = buffer + theCurrentRead->bytesDone; + + CFIndex result = [self readIntoBuffer:subBuffer maxLength:bytesToRead]; + + // Check results + if (result < 0) + { + socketError = YES; + } + else + { + CFIndex bytesRead = result; + + // Update total amount read for the current read + theCurrentRead->bytesDone += bytesRead; + + // Update total amount read in this method invocation + totalBytesRead += bytesRead; + + + // Is packet done? + if (theCurrentRead->readLength > 0) + { + // Read type #2 - read a specific length of data + + done = (theCurrentRead->bytesDone == theCurrentRead->readLength); + } + else if (theCurrentRead->term != nil) + { + // Read type #3 - read up to a terminator + + if (didPreBuffer) + { + // Search for the terminating sequence within the big chunk we just read. + + NSInteger overflow = [theCurrentRead searchForTermAfterPreBuffering:result]; + + if (overflow > 0) + { + // Copy excess data into partialReadBuffer + void *overflowBuffer = buffer + theCurrentRead->bytesDone - overflow; + + [partialReadBuffer appendBytes:overflowBuffer length:overflow]; + + // Update the bytesDone variable. + theCurrentRead->bytesDone -= overflow; + + // Note: The completeCurrentRead method will trim the buffer for us. + } + + done = (overflow >= 0); + } + else if (didReadFromPreBuffer) + { + // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method + } + else + { + // Search for the terminating sequence at the end of the buffer + + NSUInteger termlen = [theCurrentRead->term length]; + + if(theCurrentRead->bytesDone >= termlen) + { + void *bufferEnd = buffer + (theCurrentRead->bytesDone - termlen); + + const void *seq = [theCurrentRead->term bytes]; + + done = (memcmp (bufferEnd, seq, termlen) == 0); + } + } + + if(!done && theCurrentRead->maxLength > 0) + { + // We're not done and there's a set maxLength. + // Have we reached that maxLength yet? + + if(theCurrentRead->bytesDone >= theCurrentRead->maxLength) + { + maxoutError = YES; + } + } + } + else + { + // Read type #1 - read all available data + // + // We're done when: + // - we reach maxLength (if there is a max) + // - all readable is read (see below) + + if (theCurrentRead->maxLength > 0) + { + done = (theCurrentRead->bytesDone >= theCurrentRead->maxLength); + } + } + } + } + + if (theCurrentRead->readLength <= 0 && theCurrentRead->term == nil) + { + // Read type #1 - read all available data + + if (theCurrentRead->bytesDone > 0) + { + // Ran out of bytes, so the "read-all-available-data" type packet is done + done = YES; + } + } + + if (done) + { + [self completeCurrentRead]; + if (!socketError) [self scheduleDequeueRead]; + } + else if (totalBytesRead > 0) + { + // We're not done with the readToLength or readToData yet, but we have read in some bytes + if ([theDelegate respondsToSelector:@selector(onSocket:didReadPartialDataOfLength:tag:)]) + { + [theDelegate onSocket:self didReadPartialDataOfLength:totalBytesRead tag:theCurrentRead->tag]; + } + } + + if(socketError) + { + CFStreamError err = CFReadStreamGetError(theReadStream); + [self closeWithError:[self errorFromCFStreamError:err]]; + return; + } + + if(maxoutError) + { + [self closeWithError:[self getReadMaxedOutError]]; + return; + } +} + +// Ends current read and calls delegate. +- (void)completeCurrentRead +{ + NSAssert(theCurrentRead, @"Trying to complete current read when there is no current read."); + + NSData *result; + + if (theCurrentRead->bufferOwner) + { + // We created the buffer on behalf of the user. + // Trim our buffer to be the proper size. + [theCurrentRead->buffer setLength:theCurrentRead->bytesDone]; + + result = theCurrentRead->buffer; + } + else + { + // We did NOT create the buffer. + // The buffer is owned by the caller. + // Only trim the buffer if we had to increase its size. + + if ([theCurrentRead->buffer length] > theCurrentRead->originalBufferLength) + { + NSUInteger readSize = theCurrentRead->startOffset + theCurrentRead->bytesDone; + NSUInteger origSize = theCurrentRead->originalBufferLength; + + NSUInteger buffSize = MAX(readSize, origSize); + + [theCurrentRead->buffer setLength:buffSize]; + } + + void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; + + result = [NSData dataWithBytesNoCopy:buffer length:theCurrentRead->bytesDone freeWhenDone:NO]; + } + + if([theDelegate respondsToSelector:@selector(onSocket:didReadData:withTag:)]) + { + [theDelegate onSocket:self didReadData:result withTag:theCurrentRead->tag]; + } + + // Caller may have disconnected in the above delegate method + if (theCurrentRead != nil) + { + [self endCurrentRead]; + } +} + +// Ends current read. +- (void)endCurrentRead +{ + NSAssert(theCurrentRead, @"Trying to end current read when there is no current read."); + + [theReadTimer invalidate]; + theReadTimer = nil; + + [theCurrentRead release]; + theCurrentRead = nil; +} + +- (void)doReadTimeout:(NSTimer *)timer +{ + #pragma unused(timer) + + NSTimeInterval timeoutExtension = 0.0; + + if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) + { + timeoutExtension = [theDelegate onSocket:self shouldTimeoutReadWithTag:theCurrentRead->tag + elapsed:theCurrentRead->timeout + bytesDone:theCurrentRead->bytesDone]; + } + + if(timeoutExtension > 0.0) + { + theCurrentRead->timeout += timeoutExtension; + + theReadTimer = [NSTimer timerWithTimeInterval:timeoutExtension + target:self + selector:@selector(doReadTimeout:) + userInfo:nil + repeats:NO]; + [self runLoopAddTimer:theReadTimer]; + } + else + { + // Do not call endCurrentRead here. + // We must allow the delegate access to any partial read in the unreadData method. + + [self closeWithError:[self getReadTimeoutError]]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Writing +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if (data == nil || [data length] == 0) return; + if (theFlags & kForbidReadsWrites) return; + + AsyncWritePacket *packet = [[AsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; + + [theWriteQueue addObject:packet]; + [self scheduleDequeueWrite]; + + [packet release]; +} + +- (void)scheduleDequeueWrite +{ + if((theFlags & kDequeueWriteScheduled) == 0) + { + theFlags |= kDequeueWriteScheduled; + [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; + } +} + +/** + * Conditionally starts a new write. + * + * IF there is not another write in process + * AND there is a write queued + * AND we have a write stream available + * + * This method also handles auto-disconnect post read/write completion. +**/ +- (void)maybeDequeueWrite +{ + // Unset the flag indicating a call to this method is scheduled + theFlags &= ~kDequeueWriteScheduled; + + // If we're not currently processing a write AND we have an available write stream + if((theCurrentWrite == nil) && (theWriteStream != NULL)) + { + if([theWriteQueue count] > 0) + { + // Dequeue the next object in the write queue + theCurrentWrite = [[theWriteQueue objectAtIndex:0] retain]; + [theWriteQueue removeObjectAtIndex:0]; + + if([theCurrentWrite isKindOfClass:[AsyncSpecialPacket class]]) + { + // Attempt to start TLS + theFlags |= kStartingWriteTLS; + + // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set + [self maybeStartTLS]; + } + else + { + // Start time-out timer + if(theCurrentWrite->timeout >= 0.0) + { + theWriteTimer = [NSTimer timerWithTimeInterval:theCurrentWrite->timeout + target:self + selector:@selector(doWriteTimeout:) + userInfo:nil + repeats:NO]; + [self runLoopAddTimer:theWriteTimer]; + } + + // Immediately write, if possible + [self doSendBytes]; + } + } + else if(theFlags & kDisconnectAfterWrites) + { + if(theFlags & kDisconnectAfterReads) + { + if(([theReadQueue count] == 0) && (theCurrentRead == nil)) + { + [self disconnect]; + } + } + else + { + [self disconnect]; + } + } + } +} + +/** + * Call this method in doSendBytes instead of CFWriteStreamCanAcceptBytes(). + * This method supports the kSocketCanAcceptBytes flag. +**/ +- (BOOL)canAcceptBytes +{ + if (theFlags & kSocketCanAcceptBytes) + { + return YES; + } + else + { + return CFWriteStreamCanAcceptBytes(theWriteStream); + } +} + +- (void)doSendBytes +{ + if ((theCurrentWrite == nil) || (theWriteStream == NULL)) + { + return; + } + + // Note: This method is not called if theCurrentWrite is an AsyncSpecialPacket (startTLS packet) + + NSUInteger totalBytesWritten = 0; + + BOOL done = NO; + BOOL error = NO; + + while (!done && !error && [self canAcceptBytes]) + { + // Figure out what to write + NSUInteger bytesRemaining = [theCurrentWrite->buffer length] - theCurrentWrite->bytesDone; + NSUInteger bytesToWrite = (bytesRemaining < WRITE_CHUNKSIZE) ? bytesRemaining : WRITE_CHUNKSIZE; + + UInt8 *writestart = (UInt8 *)([theCurrentWrite->buffer bytes] + theCurrentWrite->bytesDone); + + // Write + CFIndex result = CFWriteStreamWrite(theWriteStream, writestart, bytesToWrite); + + // Unset the "can accept bytes" flag + theFlags &= ~kSocketCanAcceptBytes; + + // Check results + if (result < 0) + { + error = YES; + } + else + { + CFIndex bytesWritten = result; + + // Update total amount read for the current write + theCurrentWrite->bytesDone += bytesWritten; + + // Update total amount written in this method invocation + totalBytesWritten += bytesWritten; + + // Is packet done? + done = ([theCurrentWrite->buffer length] == theCurrentWrite->bytesDone); + } + } + + if(done) + { + [self completeCurrentWrite]; + [self scheduleDequeueWrite]; + } + else if(error) + { + CFStreamError err = CFWriteStreamGetError(theWriteStream); + [self closeWithError:[self errorFromCFStreamError:err]]; + return; + } + else if (totalBytesWritten > 0) + { + // We're not done with the entire write, but we have written some bytes + if ([theDelegate respondsToSelector:@selector(onSocket:didWritePartialDataOfLength:tag:)]) + { + [theDelegate onSocket:self didWritePartialDataOfLength:totalBytesWritten tag:theCurrentWrite->tag]; + } + } +} + +// Ends current write and calls delegate. +- (void)completeCurrentWrite +{ + NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); + + if ([theDelegate respondsToSelector:@selector(onSocket:didWriteDataWithTag:)]) + { + [theDelegate onSocket:self didWriteDataWithTag:theCurrentWrite->tag]; + } + + if (theCurrentWrite != nil) [self endCurrentWrite]; // Caller may have disconnected. +} + +// Ends current write. +- (void)endCurrentWrite +{ + NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); + + [theWriteTimer invalidate]; + theWriteTimer = nil; + + [theCurrentWrite release]; + theCurrentWrite = nil; +} + +- (void)doWriteTimeout:(NSTimer *)timer +{ + #pragma unused(timer) + + NSTimeInterval timeoutExtension = 0.0; + + if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) + { + timeoutExtension = [theDelegate onSocket:self shouldTimeoutWriteWithTag:theCurrentWrite->tag + elapsed:theCurrentWrite->timeout + bytesDone:theCurrentWrite->bytesDone]; + } + + if(timeoutExtension > 0.0) + { + theCurrentWrite->timeout += timeoutExtension; + + theWriteTimer = [NSTimer timerWithTimeInterval:timeoutExtension + target:self + selector:@selector(doWriteTimeout:) + userInfo:nil + repeats:NO]; + [self runLoopAddTimer:theWriteTimer]; + } + else + { + [self closeWithError:[self getWriteTimeoutError]]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Security +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)startTLS:(NSDictionary *)tlsSettings +{ +#if DEBUG_THREAD_SAFETY + [self checkForThreadSafety]; +#endif + + if(tlsSettings == nil) + { + // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, + // but causes problems if we later try to fetch the remote host's certificate. + // + // To be exact, it causes the following to return NULL instead of the normal result: + // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) + // + // So we use an empty dictionary instead, which works perfectly. + + tlsSettings = [NSDictionary dictionary]; + } + + AsyncSpecialPacket *packet = [[AsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; + + [theReadQueue addObject:packet]; + [self scheduleDequeueRead]; + + [theWriteQueue addObject:packet]; + [self scheduleDequeueWrite]; + + [packet release]; +} + +- (void)maybeStartTLS +{ + // We can't start TLS until: + // - All queued reads prior to the user calling StartTLS are complete + // - All queued writes prior to the user calling StartTLS are complete + // + // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set + + if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) + { + AsyncSpecialPacket *tlsPacket = (AsyncSpecialPacket *)theCurrentRead; + + BOOL didStartOnReadStream = CFReadStreamSetProperty(theReadStream, kCFStreamPropertySSLSettings, + (CFDictionaryRef)tlsPacket->tlsSettings); + BOOL didStartOnWriteStream = CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertySSLSettings, + (CFDictionaryRef)tlsPacket->tlsSettings); + + if(!didStartOnReadStream || !didStartOnWriteStream) + { + [self closeWithError:[self getSocketError]]; + } + } +} + +- (void)onTLSHandshakeSuccessful +{ + if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) + { + theFlags &= ~kStartingReadTLS; + theFlags &= ~kStartingWriteTLS; + + if([theDelegate respondsToSelector:@selector(onSocketDidSecure:)]) + { + [theDelegate onSocketDidSecure:self]; + } + + [self endCurrentRead]; + [self endCurrentWrite]; + + [self scheduleDequeueRead]; + [self scheduleDequeueWrite]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark CF Callbacks +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)doCFSocketCallback:(CFSocketCallBackType)type + forSocket:(CFSocketRef)sock + withAddress:(NSData *)address + withData:(const void *)pData +{ + #pragma unused(address) + + NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); + + switch (type) + { + case kCFSocketConnectCallBack: + // The data argument is either NULL or a pointer to an SInt32 error code, if the connect failed. + if(pData) + [self doSocketOpen:sock withCFSocketError:kCFSocketError]; + else + [self doSocketOpen:sock withCFSocketError:kCFSocketSuccess]; + break; + case kCFSocketAcceptCallBack: + [self doAcceptFromSocket:sock withNewNativeSocket:*((CFSocketNativeHandle *)pData)]; + break; + default: + NSLog(@"AsyncSocket %p received unexpected CFSocketCallBackType %i", self, (int)type); + break; + } +} + +- (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream +{ + #pragma unused(stream) + + NSParameterAssert(theReadStream != NULL); + + CFStreamError err; + switch (type) + { + case kCFStreamEventOpenCompleted: + theFlags |= kDidCompleteOpenForRead; + [self doStreamOpen]; + break; + case kCFStreamEventHasBytesAvailable: + if(theFlags & kStartingReadTLS) { + [self onTLSHandshakeSuccessful]; + } + else { + theFlags |= kSocketHasBytesAvailable; + [self doBytesAvailable]; + } + break; + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + err = CFReadStreamGetError (theReadStream); + [self closeWithError: [self errorFromCFStreamError:err]]; + break; + default: + NSLog(@"AsyncSocket %p received unexpected CFReadStream callback, CFStreamEventType %i", self, (int)type); + } +} + +- (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream +{ + #pragma unused(stream) + + NSParameterAssert(theWriteStream != NULL); + + CFStreamError err; + switch (type) + { + case kCFStreamEventOpenCompleted: + theFlags |= kDidCompleteOpenForWrite; + [self doStreamOpen]; + break; + case kCFStreamEventCanAcceptBytes: + if(theFlags & kStartingWriteTLS) { + [self onTLSHandshakeSuccessful]; + } + else { + theFlags |= kSocketCanAcceptBytes; + [self doSendBytes]; + } + break; + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + err = CFWriteStreamGetError (theWriteStream); + [self closeWithError: [self errorFromCFStreamError:err]]; + break; + default: + NSLog(@"AsyncSocket %p received unexpected CFWriteStream callback, CFStreamEventType %i", self, (int)type); + } +} + +/** + * This is the callback we setup for CFSocket. + * This method does nothing but forward the call to it's Objective-C counterpart +**/ +static void MyCFSocketCallback (CFSocketRef sref, CFSocketCallBackType type, CFDataRef address, const void *pData, void *pInfo) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; + [theSocket doCFSocketCallback:type forSocket:sref withAddress:(NSData *)address withData:pData]; + + [pool release]; +} + +/** + * This is the callback we setup for CFReadStream. + * This method does nothing but forward the call to it's Objective-C counterpart +**/ +static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; + [theSocket doCFReadStreamCallback:type forStream:stream]; + + [pool release]; +} + +/** + * This is the callback we setup for CFWriteStream. + * This method does nothing but forward the call to it's Objective-C counterpart +**/ +static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; + [theSocket doCFWriteStreamCallback:type forStream:stream]; + + [pool release]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Class Methods +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Return line separators. ++ (NSData *)CRLFData +{ + return [NSData dataWithBytes:"\x0D\x0A" length:2]; +} + ++ (NSData *)CRData +{ + return [NSData dataWithBytes:"\x0D" length:1]; +} + ++ (NSData *)LFData +{ + return [NSData dataWithBytes:"\x0A" length:1]; +} + ++ (NSData *)ZeroData +{ + return [NSData dataWithBytes:"" length:1]; +} + +@end diff --git a/games/Pong/Classes/GGSDelegate.h b/games/Pong/Classes/GGSDelegate.h new file mode 100644 index 0000000..da7d71a --- /dev/null +++ b/games/Pong/Classes/GGSDelegate.h @@ -0,0 +1,21 @@ +// +// GGSDelegate.h +// Pong +// +// Created by Jeena on 27.02.11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import "GGSNetwork.h" + +@class GGSNetwork; + +@protocol GGSDelegate + +- (void)GGSNetwork:(GGSNetwork *)ggsNetwork ready:(BOOL)ready; +- (void)GGSNetwork:(GGSNetwork *)ggsNetwork defined:(BOOL)defined; +- (void)GGSNetwork:(GGSNetwork *)ggsNetwork receivedCommand:(NSString *)command withArgs:(NSString *)args; +- (void)GGSNetwork:(GGSNetwork *)ggsNetwork defined:(BOOL)defined; + +@end diff --git a/games/Pong/Classes/GGSNetwork.h b/games/Pong/Classes/GGSNetwork.h new file mode 100644 index 0000000..66c007a --- /dev/null +++ b/games/Pong/Classes/GGSNetwork.h @@ -0,0 +1,34 @@ +// +// Network.h +// Pong +// +// Created by Jeena on 27.02.11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import "AsyncSocket.h" +#import "GGSDelegate.h" + +@protocol GGSDelegate; + +@interface GGSNetwork : NSObject { + AsyncSocket *asyncSocket; + id delegate; + NSString *gameToken; + NSDictionary *currentHeaders; +} + +@property (nonatomic, retain) AsyncSocket *asyncSocket; +@property (nonatomic, retain) id delegate; +@property (nonatomic, retain) NSDictionary *currentHeaders; +@property (nonatomic, retain) NSString *gameToken; + +- (id)initWithDelegate:(id)delegate; +- (NSData *)makeMessageFor:(NSString *)serverOrGame withCommand:(NSString *)command andArgs:(NSString *)args; +- (void)parseAndSetHeader:(NSData *)headerData; + +- (void)define:(NSString *)sourceCode; +- (void)sendCommand:(NSString *)command withArgs:(NSString *)args; + +@end diff --git a/games/Pong/Classes/GGSNetwork.m b/games/Pong/Classes/GGSNetwork.m new file mode 100644 index 0000000..45e7719 --- /dev/null +++ b/games/Pong/Classes/GGSNetwork.m @@ -0,0 +1,140 @@ +// +// Network.m +// Pong +// +// Created by Jeena on 27.02.11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "GGSNetwork.h" + + +@implementation GGSNetwork + +#define GGS_HOST @"ggs.jeena.net" +#define GGS_PORT 9000 +#define NO_TIMEOUT -1 + +#define HEADER_DELIMITER [@"\n\n" dataUsingEncoding:NSUTF8StringEncoding] + +#define NO_TAG 7 +#define CONNECT_HEAD 8 +#define CONNECT_BODY 9 +#define HELLO_HEAD 10 +#define HELLO_BODY 11 +#define DEFINE_HEAD 12 +#define DEFINE_BODY 13 +#define COMMAND_HEAD 14 +#define COMMAND_BODY 15 +#define HEAD 16 +#define BODY 17 + +@synthesize asyncSocket, delegate, gameToken, currentHeaders; + +- (id)initWithDelegate:(id)_delegate { + if ((self = [super init])) { + delegate = _delegate; + asyncSocket = [[AsyncSocket alloc] initWithDelegate:self]; + + [asyncSocket connectToHost:GGS_HOST onPort:GGS_PORT error:nil]; + + [asyncSocket readDataToData:HEADER_DELIMITER withTimeout:NO_TIMEOUT tag:HEAD]; + + } + + return self; +} + +- (NSData *)makeMessageFor:(NSString *)serverOrGame withCommand:(NSString *)command andArgs:(NSString *)args { + return [[NSString stringWithFormat:@"Token: %@\n%@-Command: %@\nContent-Length: %i\n\n%@", + self.gameToken, + serverOrGame, + command, + [args length], + args] dataUsingEncoding:NSUTF8StringEncoding]; +} + +- (void)define:(NSString *)sourceCode { + [asyncSocket writeData:[self makeMessageFor:@"Server" withCommand:@"define" andArgs:sourceCode] withTimeout:NO_TIMEOUT tag:NO_TAG]; +} + +- (void)sendCommand:(NSString *)command withArgs:(NSString *)args { + [asyncSocket writeData:[self makeMessageFor:@"Game" withCommand:command andArgs:args] withTimeout:NO_TIMEOUT tag:NO_TAG]; +} + +- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port { + //[asyncSocket writeData:[self makeMessageFor:@"Server" withCommand:@"hello" andArgs:@""] withTimeout:NO_TIMEOUT tag:NO_TAG]; +} + +- (void)onSocket:(AsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag { + + if (tag == HEAD) { + [self parseAndSetHeader:data]; + + NSInteger size = [[self.currentHeaders objectForKey:@"Content-Size"] intValue]; + if (size > 0) { + [asyncSocket readDataToLength:size withTimeout:NO_TIMEOUT tag:BODY]; + } else { + [delegate GGSNetwork:self receivedCommand:[self.currentHeaders objectForKey:@"Client-Command"] withArgs:@""]; + [asyncSocket readDataToData:HEADER_DELIMITER withTimeout:NO_TIMEOUT tag:HEAD]; + } + + } else { + + NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + NSString *command = [self.currentHeaders objectForKey:@"Client-Command"]; + if ([command isEqualToString:@"defined"]) { + + if ([response isEqualToString:@"ok"]) { + [delegate GGSNetwork:self defined:YES]; + } else { + [delegate GGSNetwork:self defined:NO]; + } + + } else if ([command isEqualToString:@"hello"]) { + + self.gameToken = response; + + [delegate GGSNetwork:self ready:YES]; + NSLog(@"%@", self.gameToken); + + } else { + [delegate GGSNetwork:self receivedCommand:command withArgs:response]; + } + + [asyncSocket readDataToData:HEADER_DELIMITER withTimeout:NO_TIMEOUT tag:HEAD]; + } +} + +- (void)parseAndSetHeader:(NSData *)headerData { + NSString *headerString = [[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding]; + NSArray *headers = [headerString componentsSeparatedByString:@"\n"]; + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:[headers count]]; + + for (NSInteger i=0; i<[headers count]; i++) { + NSString *header = [headers objectAtIndex:i]; + + if ([header rangeOfString:@"Client-Command: "].location == 0) { + [dict setValue:[header substringFromIndex:16] forKey:@"Client-Command"]; + } else if ([header rangeOfString:@"Content-Size: "].location == 0) { + [dict setValue:[header substringFromIndex:14] forKey:@"Content-Size"]; + } + } + + self.currentHeaders = dict; + [headerString release]; + [dict release]; +} + +- (void)dealloc { + [asyncSocket release]; + + [gameToken release]; + [currentHeaders release]; + + [super dealloc]; +} + +@end diff --git a/games/Pong/Classes/PongAppDelegate.h b/games/Pong/Classes/PongAppDelegate.h new file mode 100644 index 0000000..69d453a --- /dev/null +++ b/games/Pong/Classes/PongAppDelegate.h @@ -0,0 +1,22 @@ +// +// PongAppDelegate.h +// Pong +// +// Created by Jeena on 26.01.11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import + +@class PongViewController; + +@interface PongAppDelegate : NSObject { + UIWindow *window; + PongViewController *viewController; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet PongViewController *viewController; + +@end + diff --git a/games/Pong/Classes/PongAppDelegate.m b/games/Pong/Classes/PongAppDelegate.m new file mode 100644 index 0000000..7ca5983 --- /dev/null +++ b/games/Pong/Classes/PongAppDelegate.m @@ -0,0 +1,89 @@ +// +// PongAppDelegate.m +// Pong +// +// Created by Jeena on 26.01.11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "PongAppDelegate.h" +#import "PongViewController.h" + +@implementation PongAppDelegate + +@synthesize window; +@synthesize viewController; + + +#pragma mark - +#pragma mark Application lifecycle + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + // Override point for customization after application launch. + + // Add the view controller's view to the window and display. + [self.window addSubview:viewController.view]; + [self.window makeKeyAndVisible]; + + return YES; +} + + +- (void)applicationWillResignActive:(UIApplication *)application { + /* + Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + */ +} + + +- (void)applicationDidEnterBackground:(UIApplication *)application { + /* + Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + If your application supports background execution, called instead of applicationWillTerminate: when the user quits. + */ +} + + +- (void)applicationWillEnterForeground:(UIApplication *)application { + /* + Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background. + */ +} + + +- (void)applicationDidBecomeActive:(UIApplication *)application { + /* + Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + */ + [(PongViewController *)viewController restart]; +} + + +- (void)applicationWillTerminate:(UIApplication *)application { + /* + Called when the application is about to terminate. + See also applicationDidEnterBackground:. + */ +} + + +#pragma mark - +#pragma mark Memory management + +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { + /* + Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later. + */ +} + + +- (void)dealloc { + [viewController release]; + [window release]; + [super dealloc]; +} + + +@end diff --git a/games/Pong/Classes/PongViewController.h b/games/Pong/Classes/PongViewController.h new file mode 100644 index 0000000..0f8dbfb --- /dev/null +++ b/games/Pong/Classes/PongViewController.h @@ -0,0 +1,58 @@ +// +// PongViewController.h +// Pong +// +// Created by Jeena on 26.01.11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import "GGSDelegate.h" +#import "GGSNetwork.h" +#import + +enum GameType { + kGameTypeSinglePlayer = 0, + kGameTypeMultiPlayer, + kGameTypeNetworkMultiPlayer +}; + +@interface PongViewController : UIViewController { + IBOutlet UIView *ballView; + IBOutlet UIView *player1View; + IBOutlet UIView *player2View; + IBOutlet UILabel *tapToBegin; + CGPoint ballVelocity; + BOOL gamePaused; + + IBOutlet UILabel *pointsP1; + IBOutlet UILabel *pointsP2; + + GGSNetwork *ggsNetwork; + + AVAudioPlayer *pingSound; + AVAudioPlayer *pongSound; + AVAudioPlayer *lostSound; + +} + +@property (nonatomic, retain) IBOutlet UIView *ballView; +@property (nonatomic, retain) IBOutlet UIView *player1View; +@property (nonatomic, retain) IBOutlet UIView *player2View; +@property (nonatomic, retain) IBOutlet UIView *tapToBegin; + +@property (nonatomic, retain) IBOutlet UILabel *pointsP1; +@property (nonatomic, retain) IBOutlet UILabel *pointsP2; + +@property (nonatomic, retain) GGSNetwork *ggsNetwork; + +- (void)restart; + +- (void)startPositions; +- (void)zeroPoints; + +- (void)moveBall; +- (void)positionPlayer:(CGPoint)point; + +@end + diff --git a/games/Pong/Classes/PongViewController.m b/games/Pong/Classes/PongViewController.m new file mode 100644 index 0000000..6616e57 --- /dev/null +++ b/games/Pong/Classes/PongViewController.m @@ -0,0 +1,345 @@ +// +// PongViewController.m +// Pong +// +// Created by Jeena on 26.01.11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "PongViewController.h" + +@implementation PongViewController + +#define PLAYER_SPEED 20 +#define BALL_SPEED_X 7 +#define BALL_SPEED_Y 5 +#define INTERVAL 0.05 +#define WIDTH 480 +#define HEIGHT 320 + +#define TOX(x) ( 4.8 * x ) +#define TOY(y) ( 3.2 * y ) + +@synthesize ballView, player1View, player2View, tapToBegin, pointsP1, pointsP2, ggsNetwork; + +/* +// The designated initializer. Override to perform setup that is required before the view is loaded. +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} +*/ + +/* +// Implement loadView to create a view hierarchy programmatically, without using a nib. +- (void)loadView { + [super loadView]; +} +*/ + +-(BOOL)canBecomeFirstResponder { + return YES; +} + +#pragma mark - +#pragma mark GGSNetwork Delegate + +- (void)GGSNetwork:(GGSNetwork *)_ggsNetwork ready:(BOOL)ready { + NSLog(@"ready"); + [ggsNetwork sendCommand:@"ready" withArgs:@""]; +} + +- (void)GGSNetwork:(GGSNetwork *)_ggsNetwork defined:(BOOL)defined { + // do nothing. +} + +- (void)GGSNetwork:(GGSNetwork *)_ggsNetwork receivedCommand:(NSString *)command withArgs:(NSString *)args { + + if ([command isEqualToString:@"ball"]) { + NSArray *ball = [args componentsSeparatedByString:@","]; + [UIView beginAnimations:NULL context:NULL]; + CGFloat x = [[ball objectAtIndex:0] floatValue]; + CGFloat y = [[ball objectAtIndex:1] floatValue]; + ballView.center = CGPointMake(TOX(x), TOY(y)); + [UIView commitAnimations]; + + } else if ([command isEqualToString:@"player1_y"]) { + + [UIView beginAnimations:NULL context:NULL]; + player1View.center = CGPointMake(25, TOY([args floatValue])); + [UIView commitAnimations]; + + } else if ([command isEqualToString:@"player2_y"]) { + + [UIView beginAnimations:NULL context:NULL]; + player2View.center = CGPointMake(WIDTH - 35, TOY([args floatValue])); + [UIView commitAnimations]; + + } else if ([command isEqualToString:@"player1_points"]) { + + pointsP1.text = args; + gamePaused = YES; + [lostSound play]; + + } else if ([command isEqualToString:@"player2_points"]) { + + pointsP2.text = args; + gamePaused = YES; + [lostSound play]; + + } else if ([command isEqualToString:@"game"]) { + + if ([args isEqualToString:@"wait"]) { + + NSLog(@"Other ready"); + + } else if ([args isEqualToString:@"start"]) { + + gamePaused = NO; + + } + } else if ([command isEqualToString:@"welcome"]) { + if ([args isEqualToString:@"1"]) { + player1View.backgroundColor = [UIColor redColor]; + } else { + player2View.backgroundColor = [UIColor redColor]; + } + + } else if ([command isEqualToString:@"sound"]) { + if ([args isEqualToString:@"ping"]) { + [pingSound play]; + } else { + [pongSound play]; + } + + } +} + +#pragma mark - +#pragma mark Input + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + if (gamePaused) { + [ggsNetwork sendCommand:@"start" withArgs:@""]; + tapToBegin.hidden = YES; + } else { + CGPoint point = [[[touches allObjects] objectAtIndex:0] locationInView:self.view]; + if (point.y > (HEIGHT / 2)) { + [ggsNetwork sendCommand:@"down" withArgs:@""]; + } else { + [ggsNetwork sendCommand:@"up" withArgs:@""]; + } + + } +} + + +#pragma mark - +#pragma mark View + +- (void)restart { + player1View.backgroundColor = [UIColor whiteColor]; + player2View.backgroundColor = [UIColor whiteColor]; + pointsP1.text = @"0"; + pointsP2.text = @"0"; + self.ggsNetwork = [[GGSNetwork alloc] initWithDelegate:self]; + gamePaused = YES; + tapToBegin.hidden = NO; +} + +// Implement viewDidLoad to do additional setup after loading the view, typically from a nib. +- (void)viewDidLoad { + [super viewDidLoad]; + + NSString *path = [[NSBundle mainBundle] pathForResource:@"ping" ofType:@"wav"]; + pingSound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL]; + [pingSound play]; + + path = [[NSBundle mainBundle] pathForResource:@"pong" ofType:@"wav"]; + pongSound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL]; + [pongSound play]; + + path = [[NSBundle mainBundle] pathForResource:@"lost" ofType:@"wav"]; + lostSound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL]; + [lostSound play]; + + + //ggsNetwork = [[GGSNetwork alloc] initWithDelegate:self]; + + gamePaused = YES; + //[self startPositions]; + //[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(moveBall) userInfo:nil repeats:YES]; +} + +-(void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [self becomeFirstResponder]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [self resignFirstResponder]; + [super viewWillDisappear:animated]; +} + + +// Override to allow orientations other than the default portrait orientation. +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight); +} + + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)viewDidUnload { + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +/* +# pragma mark - +# pragma mark Ball + +- (void)moveBall { + if (!gamePaused) { + + [UIView beginAnimations:NULL context:NULL]; + ballView.center = CGPointMake(ballView.center.x + ballVelocity.x, ballView.center.y + ballVelocity.y ); + [UIView commitAnimations]; + + if (ballView.center.y > HEIGHT || ballView.center.y < 0) { + ballVelocity.y = -ballVelocity.y; + } + + if (CGRectIntersectsRect(ballView.frame, player1View.frame) || CGRectIntersectsRect(ballView.frame, player2View.frame)) { + ballVelocity.x = - (ballVelocity.x + 1); + if (arc4random() % 2) { + ballVelocity.y = - (ballVelocity.y + 1); + } + } + + if (ballView.center.x > WIDTH || ballView.center.x < 0) { + + if (ballView.center.x < 0) { + pointsP1.text = [NSString stringWithFormat:@"%i", [pointsP1.text intValue] + 1]; + } else { + pointsP2.text = [NSString stringWithFormat:@"%i", [pointsP2.text intValue] + 1]; + } + + gamePaused = YES; + [self startPositions]; + } + + } else { + tapToBegin.hidden = NO; + } + +} + +# pragma mark - +# pragma mark Positioning + +- (void)startPositions { + int s1 = - (arc4random() % 5); + int s2 = - (arc4random() % 5); + int d1 = arc4random() % 2 ? -1 : 1; + int d2 = arc4random() % 2 ? -1 : 1; + ballVelocity = CGPointMake((BALL_SPEED_X + s1) * d1 , (BALL_SPEED_Y + s2) * d2); + ballView.center = CGPointMake(WIDTH/2, HEIGHT/2); + player1View.center = CGPointMake(30, HEIGHT/2); + player2View.center = CGPointMake(WIDTH-30, HEIGHT/2); +} + +- (void)positionPlayer:(CGPoint)point { + UIView *p; + NSInteger direction = 0; + + if (point.x < WIDTH/2) { + p = player1View; + } else { + p = player2View; + } + + if (point.y > HEIGHT/2 && p.frame.origin.y + p.frame.size.height < HEIGHT) { + direction = 1; + } else if (point.y < HEIGHT/2 && p.frame.origin.y > 0) { + direction = -1; + } else { + direction = 0; + } + + + CGRect f = p.frame; + f.origin.y = f.origin.y + (PLAYER_SPEED * direction); + [UIView beginAnimations:NULL context:NULL]; + p.frame = f; + [UIView commitAnimations]; +} + +#pragma mark - +#pragma mark Input + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + if (gamePaused) { + tapToBegin.hidden = YES; + gamePaused = NO; + } else { + switch ([touches count]) { + case 1: + [self positionPlayer:[[[touches allObjects] objectAtIndex:0] locationInView:self.view]]; + break; + default: + [self positionPlayer:[[[touches allObjects] objectAtIndex:0] locationInView:self.view]]; + [self positionPlayer:[[[touches allObjects] objectAtIndex:1] locationInView:self.view]]; + break; + } + } +} + + +# pragma mark - +# pragma mark Reset + +-(BOOL)canBecomeFirstResponder { + return YES; +} + +- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { + if (event.type == UIEventSubtypeMotionShake) { + [self zeroPoints]; + } +} + +- (void)zeroPoints { + pointsP1.text = @"0"; + pointsP2.text = @"0"; +} +*/ +# pragma mark - +# pragma mark Dealloc + +- (void)dealloc { + [ballView release]; + [player1View release]; + [player2View release]; + [tapToBegin release]; + [pointsP1 release]; + [pointsP2 release]; + [ggsNetwork release]; + + [pingSound release]; + [pongSound release]; + [lostSound release]; + + [super dealloc]; +} + +@end diff --git a/games/Pong/MainWindow.xib b/games/Pong/MainWindow.xib new file mode 100644 index 0000000..141dd7d --- /dev/null +++ b/games/Pong/MainWindow.xib @@ -0,0 +1,444 @@ + + + + 1024 + 10D571 + 786 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 112 + + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + + PongViewController + + + 1 + + IBCocoaTouchFramework + NO + + + + 292 + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + IBCocoaTouchFramework + YES + + + + + YES + + + delegate + + + + 4 + + + + viewController + + + + 11 + + + + window + + + + 14 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + 3 + + + Pong App Delegate + + + -2 + + + + + 10 + + + + + 12 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 10.CustomClassName + 10.IBEditorWindowLastContentRect + 10.IBPluginDependency + 12.IBEditorWindowLastContentRect + 12.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + + + YES + UIApplication + UIResponder + PongViewController + {{234, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{525, 346}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + PongAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 15 + + + + YES + + UIWindow + UIView + + IBUserSource + + + + + PongAppDelegate + NSObject + + YES + + YES + viewController + window + + + YES + PongViewController + UIWindow + + + + YES + + YES + viewController + window + + + YES + + viewController + PongViewController + + + window + UIWindow + + + + + IBProjectSource + Classes/PongAppDelegate.h + + + + PongAppDelegate + NSObject + + IBUserSource + + + + + PongViewController + UIViewController + + IBProjectSource + Classes/PongViewController.h + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIApplication + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIApplication.h + + + + UIResponder + NSObject + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UIPopoverController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UISplitViewController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + UIWindow + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIWindow.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + Pong.xcodeproj + 3 + 112 + + diff --git a/games/Pong/Pong-Info.plist b/games/Pong/Pong-Info.plist new file mode 100644 index 0000000..170ef1c --- /dev/null +++ b/games/Pong/Pong-Info.plist @@ -0,0 +1,37 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + net.jeena.apps.pong + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft + + + diff --git a/games/Pong/Pong.xcodeproj/jeena.mode1v3 b/games/Pong/Pong.xcodeproj/jeena.mode1v3 new file mode 100644 index 0000000..d4bc790 --- /dev/null +++ b/games/Pong/Pong.xcodeproj/jeena.mode1v3 @@ -0,0 +1,1400 @@ + + + + + ActivePerspectiveName + Project + AllowedModules + + + BundleLoadPath + + MaxInstances + n + Module + PBXSmartGroupTreeModule + Name + Groups and Files Outline View + + + BundleLoadPath + + MaxInstances + n + Module + PBXNavigatorGroup + Name + Editor + + + BundleLoadPath + + MaxInstances + n + Module + XCTaskListModule + Name + Task List + + + BundleLoadPath + + MaxInstances + n + Module + XCDetailModule + Name + File and Smart Group Detail Viewer + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXBuildResultsModule + Name + Detailed Build Results Viewer + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXProjectFindModule + Name + Project Batch Find Tool + + + BundleLoadPath + + MaxInstances + n + Module + XCProjectFormatConflictsModule + Name + Project Format Conflicts List + + + BundleLoadPath + + MaxInstances + n + Module + PBXBookmarksModule + Name + Bookmarks Tool + + + BundleLoadPath + + MaxInstances + n + Module + PBXClassBrowserModule + Name + Class Browser + + + BundleLoadPath + + MaxInstances + n + Module + PBXCVSModule + Name + Source Code Control Tool + + + BundleLoadPath + + MaxInstances + n + Module + PBXDebugBreakpointsModule + Name + Debug Breakpoints Tool + + + BundleLoadPath + + MaxInstances + n + Module + XCDockableInspector + Name + Inspector + + + BundleLoadPath + + MaxInstances + n + Module + PBXOpenQuicklyModule + Name + Open Quickly Tool + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXDebugSessionModule + Name + Debugger + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXDebugCLIModule + Name + Debug Console + + + BundleLoadPath + + MaxInstances + n + Module + XCSnapshotModule + Name + Snapshots Tool + + + BundlePath + /Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources + Description + DefaultDescriptionKey + DockingSystemVisible + + Extension + mode1v3 + FavBarConfig + + PBXProjectModuleGUID + 1FA056A312F0B540003F1373 + XCBarModuleItemNames + + XCBarModuleItems + + + FirstTimeWindowDisplayed + + Identifier + com.apple.perspectives.project.mode1v3 + MajorVersion + 33 + MinorVersion + 0 + Name + Default + Notifications + + OpenEditors + + PerspectiveWidths + + -1 + -1 + + Perspectives + + + ChosenToolbarItems + + active-combo-popup + action + NSToolbarFlexibleSpaceItem + build-and-go + com.apple.ide.PBXToolbarStopButton + get-info + NSToolbarFlexibleSpaceItem + com.apple.pbx.toolbar.searchfield + + ControllerClassBaseName + + IconName + WindowOfProjectWithEditor + Identifier + perspective.project + IsVertical + + Layout + + + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C37FBAC04509CD000000102 + 1C37FAAC04509CD000000102 + 1C37FABC05509CD000000102 + 1C37FABC05539CD112110102 + E2644B35053B69B200211256 + 1C37FABC04509CD000100104 + 1CC0EA4004350EF90044410B + 1CC0EA4004350EF90041110B + + PBXProjectModuleGUID + 1CE0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + yes + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 186 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 29B97314FDCFA39411CA2CEA + 080E96DDFE201D6D7F000001 + 29B97317FDCFA39411CA2CEA + 29B97323FDCFA39411CA2CEA + 1C37FABC05509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 11 + 1 + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 0}, {186, 1086}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + + XCSharingToken + com.apple.Xcode.GFSharingToken + + GeometryConfiguration + + Frame + {{0, 0}, {203, 1104}} + GroupTreeTableConfiguration + + MainColumn + 186 + + RubberWindowFrame + 112 246 1267 1145 0 0 2560 1418 + + Module + PBXSmartGroupTreeModule + Proportion + 203pt + + + Dock + + + BecomeActive + + ContentConfiguration + + PBXProjectModuleGUID + 1CE0B20306471E060097A5F4 + PBXProjectModuleLabel + GGSNetwork.m + PBXSplitModuleInNavigatorKey + + Split0 + + PBXProjectModuleGUID + 1CE0B20406471E060097A5F4 + PBXProjectModuleLabel + GGSNetwork.m + _historyCapacity + 0 + bookmark + 1F36A02B1323454A004E7A99 + history + + 1FBEBF151319F5C1006D5497 + 1FBEC001131AA71C006D5497 + 1FBEC002131AA71C006D5497 + 1FBEC05C131B085D006D5497 + 1F369ED81323101D004E7A99 + 1F369FE61323395B004E7A99 + 1F36A0201323417E004E7A99 + 1F36A0291323454A004E7A99 + 1F36A02A1323454A004E7A99 + 1F369F6C13232750004E7A99 + + + SplitCount + 1 + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {1059, 1099}} + RubberWindowFrame + 112 246 1267 1145 0 0 2560 1418 + + Module + PBXNavigatorGroup + Proportion + 1099pt + + + ContentConfiguration + + PBXProjectModuleGUID + 1CE0B20506471E060097A5F4 + PBXProjectModuleLabel + Detail + + GeometryConfiguration + + Frame + {{0, 1104}, {1059, 0}} + RubberWindowFrame + 112 246 1267 1145 0 0 2560 1418 + + Module + XCDetailModule + Proportion + 0pt + + + Proportion + 1059pt + + + Name + Project + ServiceClasses + + XCModuleDock + PBXSmartGroupTreeModule + XCModuleDock + PBXNavigatorGroup + XCDetailModule + + TableOfContents + + 1F369E7C1323081C004E7A99 + 1CE0B1FE06471DED0097A5F4 + 1F369E7D1323081C004E7A99 + 1CE0B20306471E060097A5F4 + 1CE0B20506471E060097A5F4 + + ToolbarConfigUserDefaultsMinorVersion + 2 + ToolbarConfiguration + xcode.toolbar.config.defaultV3 + + + ControllerClassBaseName + + IconName + WindowOfProject + Identifier + perspective.morph + IsVertical + 0 + Layout + + + BecomeActive + 1 + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C37FBAC04509CD000000102 + 1C37FAAC04509CD000000102 + 1C08E77C0454961000C914BD + 1C37FABC05509CD000000102 + 1C37FABC05539CD112110102 + E2644B35053B69B200211256 + 1C37FABC04509CD000100104 + 1CC0EA4004350EF90044410B + 1CC0EA4004350EF90041110B + + PBXProjectModuleGUID + 11E0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + yes + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 186 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 29B97314FDCFA39411CA2CEA + 1C37FABC05509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 0}, {186, 337}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + 1 + XCSharingToken + com.apple.Xcode.GFSharingToken + + GeometryConfiguration + + Frame + {{0, 0}, {203, 355}} + GroupTreeTableConfiguration + + MainColumn + 186 + + RubberWindowFrame + 373 269 690 397 0 0 1440 878 + + Module + PBXSmartGroupTreeModule + Proportion + 100% + + + Name + Morph + PreferredWidth + 300 + ServiceClasses + + XCModuleDock + PBXSmartGroupTreeModule + + TableOfContents + + 11E0B1FE06471DED0097A5F4 + + ToolbarConfiguration + xcode.toolbar.config.default.shortV3 + + + PerspectivesBarVisible + + ShelfIsVisible + + SourceDescription + file at '/Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources/XCPerspectivesSpecificationMode1.xcperspec' + StatusbarIsVisible + + TimeStamp + 0.0 + ToolbarConfigUserDefaultsMinorVersion + 2 + ToolbarDisplayMode + 1 + ToolbarIsVisible + + ToolbarSizeMode + 1 + Type + Perspectives + UpdateMessage + The Default Workspace in this version of Xcode now includes support to hide and show the detail view (what has been referred to as the "Metro-Morph" feature). You must discard your current Default Workspace settings and update to the latest Default Workspace in order to gain this feature. Do you wish to update to the latest Workspace defaults for project '%@'? + WindowJustification + 5 + WindowOrderList + + 1F369E8E13230953004E7A99 + 1F369E871323081C004E7A99 + 1CD10A99069EF8BA00B06720 + 1FA056A412F0B540003F1373 + 1C78EAAD065D492600B07095 + /Users/jeena/Student/GGS/games/Pong/Pong.xcodeproj + + WindowString + 112 246 1267 1145 0 0 2560 1418 + WindowToolsV3 + + + FirstTimeWindowDisplayed + + Identifier + windowTool.build + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1CD0528F0623707200166675 + PBXProjectModuleLabel + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {500, 218}} + RubberWindowFrame + 907 680 500 500 0 0 2560 1418 + + Module + PBXNavigatorGroup + Proportion + 218pt + + + ContentConfiguration + + PBXProjectModuleGUID + XCMainBuildResultsModuleGUID + PBXProjectModuleLabel + Build Results + XCBuildResultsTrigger_Collapse + 1021 + XCBuildResultsTrigger_Open + 1011 + + GeometryConfiguration + + Frame + {{0, 223}, {500, 236}} + RubberWindowFrame + 907 680 500 500 0 0 2560 1418 + + Module + PBXBuildResultsModule + Proportion + 236pt + + + Proportion + 459pt + + + Name + Build Results + ServiceClasses + + PBXBuildResultsModule + + StatusbarIsVisible + + TableOfContents + + 1FA056A412F0B540003F1373 + 1F369E7E1323081C004E7A99 + 1CD0528F0623707200166675 + XCMainBuildResultsModuleGUID + + ToolbarConfiguration + xcode.toolbar.config.buildV3 + WindowContentMinSize + 486 300 + WindowString + 907 680 500 500 0 0 2560 1418 + WindowToolGUID + 1FA056A412F0B540003F1373 + WindowToolIsVisible + + + + FirstTimeWindowDisplayed + + Identifier + windowTool.debugger + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + Debugger + + HorizontalSplitView + + _collapsingFrameDimension + 0.0 + _indexOfCollapsedView + 0 + _percentageOfCollapsedView + 0.0 + isCollapsed + yes + sizes + + {{0, 0}, {357, 449}} + {{357, 0}, {428, 449}} + + + VerticalSplitView + + _collapsingFrameDimension + 0.0 + _indexOfCollapsedView + 0 + _percentageOfCollapsedView + 0.0 + isCollapsed + yes + sizes + + {{0, 0}, {785, 449}} + {{0, 449}, {785, 403}} + + + + LauncherConfigVersion + 8 + PBXProjectModuleGUID + 1C162984064C10D400B95A72 + PBXProjectModuleLabel + Debug - GLUTExamples (Underwater) + + GeometryConfiguration + + DebugConsoleVisible + None + DebugConsoleWindowFrame + {{200, 200}, {500, 300}} + DebugSTDIOWindowFrame + {{200, 200}, {500, 300}} + Frame + {{0, 0}, {785, 852}} + PBXDebugSessionStackFrameViewKey + + DebugVariablesTableConfiguration + + Name + 120 + Value + 85 + Summary + 198 + + Frame + {{357, 0}, {428, 449}} + RubberWindowFrame + 108 506 785 893 0 0 2560 1418 + + RubberWindowFrame + 108 506 785 893 0 0 2560 1418 + + Module + PBXDebugSessionModule + Proportion + 852pt + + + Proportion + 852pt + + + Name + Debugger + ServiceClasses + + PBXDebugSessionModule + + StatusbarIsVisible + + TableOfContents + + 1CD10A99069EF8BA00B06720 + 1F369E7F1323081C004E7A99 + 1C162984064C10D400B95A72 + 1F369E801323081C004E7A99 + 1F369E811323081C004E7A99 + 1F369E821323081C004E7A99 + 1F369E831323081C004E7A99 + 1F369E841323081C004E7A99 + + ToolbarConfiguration + xcode.toolbar.config.debugV3 + WindowString + 108 506 785 893 0 0 2560 1418 + WindowToolGUID + 1CD10A99069EF8BA00B06720 + WindowToolIsVisible + + + + FirstTimeWindowDisplayed + + Identifier + windowTool.find + IsVertical + + Layout + + + Dock + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1CDD528C0622207200134675 + PBXProjectModuleLabel + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {781, 212}} + RubberWindowFrame + 1185 925 781 470 0 0 2560 1418 + + Module + PBXNavigatorGroup + Proportion + 781pt + + + Proportion + 212pt + + + BecomeActive + + ContentConfiguration + + PBXProjectModuleGUID + 1CD0528E0623707200166675 + PBXProjectModuleLabel + Project Find + + GeometryConfiguration + + Frame + {{0, 217}, {781, 212}} + RubberWindowFrame + 1185 925 781 470 0 0 2560 1418 + + Module + PBXProjectFindModule + Proportion + 212pt + + + Proportion + 429pt + + + Name + Project Find + ServiceClasses + + PBXProjectFindModule + + StatusbarIsVisible + + TableOfContents + + 1C530D57069F1CE1000CFCEE + 1FC1C556131C35AC00D1FF71 + 1FC1C557131C35AC00D1FF71 + 1CDD528C0622207200134675 + 1CD0528E0623707200166675 + + WindowString + 1185 925 781 470 0 0 2560 1418 + WindowToolGUID + 1C530D57069F1CE1000CFCEE + WindowToolIsVisible + + + + Identifier + MENUSEPARATOR + + + FirstTimeWindowDisplayed + + Identifier + windowTool.debuggerConsole + IsVertical + + Layout + + + Dock + + + BecomeActive + + ContentConfiguration + + PBXProjectModuleGUID + 1C78EAAC065D492600B07095 + PBXProjectModuleLabel + Debugger Console + + GeometryConfiguration + + Frame + {{0, 0}, {724, 358}} + RubberWindowFrame + 3598 801 724 399 2560 0 1920 1200 + + Module + PBXDebugCLIModule + Proportion + 358pt + + + Proportion + 358pt + + + Name + Debugger Console + ServiceClasses + + PBXDebugCLIModule + + StatusbarIsVisible + + TableOfContents + + 1C78EAAD065D492600B07095 + 1F369E851323081C004E7A99 + 1C78EAAC065D492600B07095 + + ToolbarConfiguration + xcode.toolbar.config.consoleV3 + WindowString + 3598 801 724 399 2560 0 1920 1200 + WindowToolGUID + 1C78EAAD065D492600B07095 + WindowToolIsVisible + + + + Identifier + windowTool.snapshots + Layout + + + Dock + + + Module + XCSnapshotModule + Proportion + 100% + + + Proportion + 100% + + + Name + Snapshots + ServiceClasses + + XCSnapshotModule + + StatusbarIsVisible + Yes + ToolbarConfiguration + xcode.toolbar.config.snapshots + WindowString + 315 824 300 550 0 0 1440 878 + WindowToolIsVisible + Yes + + + Identifier + windowTool.scm + Layout + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1C78EAB2065D492600B07095 + PBXProjectModuleLabel + <No Editor> + PBXSplitModuleInNavigatorKey + + Split0 + + PBXProjectModuleGUID + 1C78EAB3065D492600B07095 + + SplitCount + 1 + + StatusBarVisibility + 1 + + GeometryConfiguration + + Frame + {{0, 0}, {452, 0}} + RubberWindowFrame + 743 379 452 308 0 0 1280 1002 + + Module + PBXNavigatorGroup + Proportion + 0pt + + + BecomeActive + 1 + ContentConfiguration + + PBXProjectModuleGUID + 1CD052920623707200166675 + PBXProjectModuleLabel + SCM + + GeometryConfiguration + + ConsoleFrame + {{0, 259}, {452, 0}} + Frame + {{0, 7}, {452, 259}} + RubberWindowFrame + 743 379 452 308 0 0 1280 1002 + TableConfiguration + + Status + 30 + FileName + 199 + Path + 197.0950012207031 + + TableFrame + {{0, 0}, {452, 250}} + + Module + PBXCVSModule + Proportion + 262pt + + + Proportion + 266pt + + + Name + SCM + ServiceClasses + + PBXCVSModule + + StatusbarIsVisible + 1 + TableOfContents + + 1C78EAB4065D492600B07095 + 1C78EAB5065D492600B07095 + 1C78EAB2065D492600B07095 + 1CD052920623707200166675 + + ToolbarConfiguration + xcode.toolbar.config.scm + WindowString + 743 379 452 308 0 0 1280 1002 + + + Identifier + windowTool.breakpoints + IsVertical + 0 + Layout + + + Dock + + + BecomeActive + 1 + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C77FABC04509CD000000102 + + PBXProjectModuleGUID + 1CE0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + no + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 168 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 1C77FABC04509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 0}, {168, 350}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + 0 + + GeometryConfiguration + + Frame + {{0, 0}, {185, 368}} + GroupTreeTableConfiguration + + MainColumn + 168 + + RubberWindowFrame + 315 424 744 409 0 0 1440 878 + + Module + PBXSmartGroupTreeModule + Proportion + 185pt + + + ContentConfiguration + + PBXProjectModuleGUID + 1CA1AED706398EBD00589147 + PBXProjectModuleLabel + Detail + + GeometryConfiguration + + Frame + {{190, 0}, {554, 368}} + RubberWindowFrame + 315 424 744 409 0 0 1440 878 + + Module + XCDetailModule + Proportion + 554pt + + + Proportion + 368pt + + + MajorVersion + 3 + MinorVersion + 0 + Name + Breakpoints + ServiceClasses + + PBXSmartGroupTreeModule + XCDetailModule + + StatusbarIsVisible + 1 + TableOfContents + + 1CDDB66807F98D9800BB5817 + 1CDDB66907F98D9800BB5817 + 1CE0B1FE06471DED0097A5F4 + 1CA1AED706398EBD00589147 + + ToolbarConfiguration + xcode.toolbar.config.breakpointsV3 + WindowString + 315 424 744 409 0 0 1440 878 + WindowToolGUID + 1CDDB66807F98D9800BB5817 + WindowToolIsVisible + 1 + + + Identifier + windowTool.debugAnimator + Layout + + + Dock + + + Module + PBXNavigatorGroup + Proportion + 100% + + + Proportion + 100% + + + Name + Debug Visualizer + ServiceClasses + + PBXNavigatorGroup + + StatusbarIsVisible + 1 + ToolbarConfiguration + xcode.toolbar.config.debugAnimatorV3 + WindowString + 100 100 700 500 0 0 1280 1002 + + + Identifier + windowTool.bookmarks + Layout + + + Dock + + + Module + PBXBookmarksModule + Proportion + 100% + + + Proportion + 100% + + + Name + Bookmarks + ServiceClasses + + PBXBookmarksModule + + StatusbarIsVisible + 0 + WindowString + 538 42 401 187 0 0 1280 1002 + + + Identifier + windowTool.projectFormatConflicts + Layout + + + Dock + + + Module + XCProjectFormatConflictsModule + Proportion + 100% + + + Proportion + 100% + + + Name + Project Format Conflicts + ServiceClasses + + XCProjectFormatConflictsModule + + StatusbarIsVisible + 0 + WindowContentMinSize + 450 300 + WindowString + 50 850 472 307 0 0 1440 877 + + + Identifier + windowTool.classBrowser + Layout + + + Dock + + + BecomeActive + 1 + ContentConfiguration + + OptionsSetName + Hierarchy, all classes + PBXProjectModuleGUID + 1CA6456E063B45B4001379D8 + PBXProjectModuleLabel + Class Browser - NSObject + + GeometryConfiguration + + ClassesFrame + {{0, 0}, {374, 96}} + ClassesTreeTableConfiguration + + PBXClassNameColumnIdentifier + 208 + PBXClassBookColumnIdentifier + 22 + + Frame + {{0, 0}, {630, 331}} + MembersFrame + {{0, 105}, {374, 395}} + MembersTreeTableConfiguration + + PBXMemberTypeIconColumnIdentifier + 22 + PBXMemberNameColumnIdentifier + 216 + PBXMemberTypeColumnIdentifier + 97 + PBXMemberBookColumnIdentifier + 22 + + PBXModuleWindowStatusBarHidden2 + 1 + RubberWindowFrame + 385 179 630 352 0 0 1440 878 + + Module + PBXClassBrowserModule + Proportion + 332pt + + + Proportion + 332pt + + + Name + Class Browser + ServiceClasses + + PBXClassBrowserModule + + StatusbarIsVisible + 0 + TableOfContents + + 1C0AD2AF069F1E9B00FABCE6 + 1C0AD2B0069F1E9B00FABCE6 + 1CA6456E063B45B4001379D8 + + ToolbarConfiguration + xcode.toolbar.config.classbrowser + WindowString + 385 179 630 352 0 0 1440 878 + WindowToolGUID + 1C0AD2AF069F1E9B00FABCE6 + WindowToolIsVisible + 0 + + + Identifier + windowTool.refactoring + IncludeInToolsMenu + 0 + Layout + + + Dock + + + BecomeActive + 1 + GeometryConfiguration + + Frame + {0, 0}, {500, 335} + RubberWindowFrame + {0, 0}, {500, 335} + + Module + XCRefactoringModule + Proportion + 100% + + + Proportion + 100% + + + Name + Refactoring + ServiceClasses + + XCRefactoringModule + + WindowString + 200 200 500 356 0 0 1920 1200 + + + + diff --git a/games/Pong/Pong.xcodeproj/jeena.pbxuser b/games/Pong/Pong.xcodeproj/jeena.pbxuser new file mode 100644 index 0000000..48544a7 --- /dev/null +++ b/games/Pong/Pong.xcodeproj/jeena.pbxuser @@ -0,0 +1,1505 @@ +// !$*UTF8*$! +{ + 1D3623240D0F684500981E51 /* PongAppDelegate.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {900, 935}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRange = "{0, 455}"; + }; + }; + 1D3623250D0F684500981E51 /* PongAppDelegate.m */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {1965, 1350}}"; + sepNavSelRange = "{2181, 0}"; + sepNavVisRange = "{0, 2635}"; + }; + }; + 1D6058900D05DD3D006BFB54 /* Pong */ = { + activeExec = 0; + executables = ( + 1FA0569112F0B513003F1373 /* Pong */, + ); + }; + 1F369E761323081C004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEC030131AF83B006D5497 /* GGSDelegate.h */; + name = "GGSDelegate.h: 16"; + rLen = 0; + rLoc = 283; + rType = 0; + vrLen = 531; + vrLoc = 0; + }; + 1F369E771323081C004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */; + name = "GGSNetwork.h: 28"; + rLen = 0; + rLoc = 748; + rType = 0; + vrLen = 910; + vrLoc = 0; + }; + 1F369E781323081C004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 60"; + rLen = 0; + rLoc = 1532; + rType = 0; + vrLen = 2676; + vrLoc = 860; + }; + 1F369E791323081C004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF60DDB3853001CB0EB /* PongViewController.h */; + name = "PongViewController.h: 11"; + rLen = 0; + rLoc = 246; + rType = 0; + vrLen = 1144; + vrLoc = 0; + }; + 1F369E7A1323081C004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 24"; + rLen = 0; + rLoc = 480; + rType = 0; + vrLen = 1968; + vrLoc = 37; + }; + 1F369E7B1323081C004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 40"; + rLen = 0; + rLoc = 947; + rType = 0; + vrLen = 2275; + vrLoc = 799; + }; + 1F369E8A13230953004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 40"; + rLen = 0; + rLoc = 947; + rType = 0; + vrLen = 2223; + vrLoc = 0; + }; + 1F369E8B13230953004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */; + name = "GGSNetwork.h: 28"; + rLen = 0; + rLoc = 748; + rType = 0; + vrLen = 910; + vrLoc = 0; + }; + 1F369E8C13230953004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 207; + rType = 0; + vrLen = 2278; + vrLoc = 0; + }; + 1F369E9313230981004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 17"; + rLen = 0; + rLoc = 253; + rType = 0; + vrLen = 2268; + vrLoc = 26; + }; + 1F369E961323099B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 39"; + rLen = 0; + rLoc = 823; + rType = 0; + vrLen = 2358; + vrLoc = 29; + }; + 1F369E99132309E0004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 39"; + rLen = 0; + rLoc = 823; + rType = 0; + vrLen = 2358; + vrLoc = 29; + }; + 1F369E9A132309E0004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 40"; + rLen = 0; + rLoc = 947; + rType = 0; + vrLen = 2223; + vrLoc = 0; + }; + 1F369E9B132309E0004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 56"; + rLen = 0; + rLoc = 1256; + rType = 0; + vrLen = 2288; + vrLoc = 191; + }; + 1F369E9E13230A0F004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 52"; + rLen = 0; + rLoc = 1168; + rType = 0; + vrLen = 2288; + vrLoc = 191; + }; + 1F369E9F13230A0F004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */; + name = "GGSNetwork.h: 28"; + rLen = 0; + rLoc = 748; + rType = 0; + vrLen = 910; + vrLoc = 0; + }; + 1F369EA013230A0F004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 64"; + rLen = 0; + rLoc = 1750; + rType = 0; + vrLen = 2447; + vrLoc = 328; + }; + 1F369EA513230A35004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 64"; + rLen = 0; + rLoc = 1750; + rType = 0; + vrLen = 2447; + vrLoc = 328; + }; + 1F369EA613230A35004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 52"; + rLen = 0; + rLoc = 1168; + rType = 0; + vrLen = 2288; + vrLoc = 191; + }; + 1F369EA713230A35004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 85"; + rLen = 0; + rLoc = 4498; + rType = 0; + vrLen = 2296; + vrLoc = 431; + }; + 1F369EAA13230A56004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 86"; + rLen = 0; + rLoc = 4500; + rType = 0; + vrLen = 2280; + vrLoc = 431; + }; + 1F369EAB13230A56004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 64"; + rLen = 0; + rLoc = 1750; + rType = 0; + vrLen = 2447; + vrLoc = 328; + }; + 1F369EAC13230A56004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 39"; + rLen = 0; + rLoc = 794; + rType = 0; + vrLen = 2464; + vrLoc = 329; + }; + 1F369EAF13230A6B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 69"; + rLen = 0; + rLoc = 1840; + rType = 0; + vrLen = 2490; + vrLoc = 369; + }; + 1F369EB413230AAC004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 40"; + rLen = 0; + rLoc = 794; + rType = 0; + vrLen = 2021; + vrLoc = 3; + }; + 1F369EB513230ADE004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 41"; + rLen = 0; + rLoc = 823; + rType = 0; + vrLen = 2021; + vrLoc = 3; + }; + 1F369EB613230B1D004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 43"; + rLen = 0; + rLoc = 860; + rType = 0; + vrLen = 2028; + vrLoc = 3; + }; + 1F369EBB13230B53004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 43"; + rLen = 0; + rLoc = 860; + rType = 0; + vrLen = 2174; + vrLoc = 29; + }; + 1F369EBF13230B6E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 43"; + rLen = 0; + rLoc = 860; + rType = 0; + vrLen = 2205; + vrLoc = 122; + }; + 1F369EC013230BF3004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 43"; + rLen = 0; + rLoc = 860; + rType = 0; + vrLen = 2271; + vrLoc = 1750; + }; + 1F369EC113230BF3004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 106"; + rLen = 0; + rLoc = 4500; + rType = 0; + vrLen = 2255; + vrLoc = 952; + }; + 1F369EC413230DCA004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 93"; + rLen = 0; + rLoc = 3351; + rType = 0; + vrLen = 2412; + vrLoc = 1563; + }; + 1F369EC613230DE0004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 93"; + rLen = 0; + rLoc = 3351; + rType = 0; + vrLen = 2416; + vrLoc = 1563; + }; + 1F369ECB13230E71004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 93"; + rLen = 0; + rLoc = 3351; + rType = 0; + vrLen = 2259; + vrLoc = 1720; + }; + 1F369ECC13230E71004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */; + name = "GGSNetwork.h: 28"; + rLen = 0; + rLoc = 748; + rType = 0; + vrLen = 910; + vrLoc = 0; + }; + 1F369ECD13230E71004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 198; + rType = 0; + vrLen = 2036; + vrLoc = 0; + }; + 1F369ED013230E9E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 198; + rType = 0; + vrLen = 2133; + vrLoc = 3; + }; + 1F369ED313230EAD004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 198; + rType = 0; + vrLen = 2110; + vrLoc = 26; + }; + 1F369ED413230F2A004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 20"; + rLen = 0; + rLoc = 529; + rType = 0; + vrLen = 2271; + vrLoc = 1571; + }; + 1F369ED71323101D004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 20"; + rLen = 0; + rLoc = 529; + rType = 0; + vrLen = 2271; + vrLoc = 1571; + }; + 1F369ED81323101D004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1D3623250D0F684500981E51 /* PongAppDelegate.m */; + name = "PongAppDelegate.m: 60"; + rLen = 0; + rLoc = 2181; + rType = 0; + vrLen = 2635; + vrLoc = 0; + }; + 1F369ED91323101D004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF60DDB3853001CB0EB /* PongViewController.h */; + name = "PongViewController.h: 43"; + rLen = 0; + rLoc = 1173; + rType = 0; + vrLen = 1162; + vrLoc = 0; + }; + 1F369EDA1323101D004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 104"; + rLen = 0; + rLoc = 3589; + rType = 0; + vrLen = 2119; + vrLoc = 2266; + }; + 1F369EDD1323104E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 121"; + rLen = 1; + rLoc = 4771; + rType = 0; + vrLen = 2160; + vrLoc = 2266; + }; + 1F369EDE1323104E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 20"; + rLen = 0; + rLoc = 529; + rType = 0; + vrLen = 2273; + vrLoc = 1569; + }; + 1F369EDF1323104E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 35"; + rLen = 0; + rLoc = 860; + rType = 0; + vrLen = 2277; + vrLoc = 3; + }; + 1F369EE21323105F004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 33"; + rLen = 0; + rLoc = 857; + rType = 0; + vrLen = 2264; + vrLoc = 26; + }; + 1F369EE513231076004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 20"; + rLen = 0; + rLoc = 529; + rType = 0; + vrLen = 2312; + vrLoc = 29; + }; + 1F369EF413231106004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 34"; + rLen = 0; + rLoc = 860; + rType = 0; + vrLen = 2234; + vrLoc = 3; + }; + 1F369EF91323111F004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 198; + rType = 0; + vrLen = 2099; + vrLoc = 26; + }; + 1F369EFC1323113B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 198; + rType = 0; + vrLen = 2171; + vrLoc = 26; + }; + 1F369EFD1323113B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 105"; + rLen = 0; + rLoc = 3690; + rType = 0; + vrLen = 2161; + vrLoc = 2282; + }; + 1F369F011323115B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 105"; + rLen = 0; + rLoc = 3690; + rType = 0; + vrLen = 2317; + vrLoc = 2302; + }; + 1F369F0413231163004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 105"; + rLen = 0; + rLoc = 3690; + rType = 0; + vrLen = 2231; + vrLoc = 2388; + }; + 1F369F0B1323128E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF60DDB3853001CB0EB /* PongViewController.h */; + name = "PongViewController.h: 43"; + rLen = 0; + rLoc = 1173; + rType = 0; + vrLen = 1162; + vrLoc = 0; + }; + 1F369F0C1323128E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 105"; + rLen = 0; + rLoc = 3690; + rType = 0; + vrLen = 2167; + vrLoc = 2533; + }; + 1F369F0D1323128E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 75"; + rLen = 0; + rLoc = 2465; + rType = 0; + vrLen = 2212; + vrLoc = 861; + }; + 1F369F101323129A004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 75"; + rLen = 0; + rLoc = 2465; + rType = 0; + vrLen = 2239; + vrLoc = 952; + }; + 1F369F16132312C3004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 75"; + rLen = 0; + rLoc = 2465; + rType = 0; + vrLen = 2274; + vrLoc = 992; + }; + 1F369F1F1323136F004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 76"; + rLen = 0; + rLoc = 2503; + rType = 0; + vrLen = 2313; + vrLoc = 1029; + }; + 1F369F241323151F004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 103"; + rLen = 0; + rLoc = 2770; + rType = 0; + vrLen = 2121; + vrLoc = 971; + }; + 1F369F2913231563004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 61"; + rLen = 0; + rLoc = 1370; + rType = 0; + vrLen = 2209; + vrLoc = 524; + }; + 1F369F2D13231572004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 61"; + rLen = 0; + rLoc = 1370; + rType = 0; + vrLen = 2210; + vrLoc = 528; + }; + 1F369F2E1323158B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 77"; + rLen = 0; + rLoc = 2044; + rType = 0; + vrLen = 2210; + vrLoc = 528; + }; + 1F369F33132317A8004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */; + name = "GGSNetwork.h: 28"; + rLen = 0; + rLoc = 748; + rType = 0; + vrLen = 910; + vrLoc = 0; + }; + 1F369F34132317A8004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 198; + rType = 0; + vrLen = 2403; + vrLoc = 236; + }; + 1F369F35132317A8004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 45"; + rLen = 0; + rLoc = 1002; + rType = 0; + vrLen = 2210; + vrLoc = 528; + }; + 1F369F36132317A8004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 66"; + rLen = 0; + rLoc = 1619; + rType = 0; + vrLen = 2074; + vrLoc = 712; + }; + 1F369F631323270D004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 66"; + rLen = 0; + rLoc = 1619; + rType = 0; + vrLen = 2047; + vrLoc = 1504; + }; + 1F369F641323270D004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 198; + rType = 0; + vrLen = 2403; + vrLoc = 236; + }; + 1F369F651323270D004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 69"; + rLen = 0; + rLoc = 1840; + rType = 0; + vrLen = 2623; + vrLoc = 1240; + }; + 1F369F6C13232750004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 69"; + rLen = 0; + rLoc = 1840; + rType = 0; + vrLen = 2571; + vrLoc = 1292; + }; + 1F369F6D13232750004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 66"; + rLen = 0; + rLoc = 1619; + rType = 0; + vrLen = 2047; + vrLoc = 1504; + }; + 1F369F6E13232750004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 105"; + rLen = 0; + rLoc = 2728; + rType = 0; + vrLen = 2055; + vrLoc = 1660; + }; + 1F369F711323276C004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 105"; + rLen = 0; + rLoc = 2768; + rType = 0; + vrLen = 2002; + vrLoc = 1711; + }; + 1F369F76132327A3004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 105"; + rLen = 0; + rLoc = 2768; + rType = 0; + vrLen = 1962; + vrLoc = 1792; + }; + 1F369F78132327B7004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 105"; + rLen = 0; + rLoc = 2768; + rType = 0; + vrLen = 2031; + vrLoc = 1792; + }; + 1F369F8913232BEE004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 135"; + rLen = 0; + rLoc = 3582; + rType = 0; + vrLen = 1976; + vrLoc = 1846; + }; + 1F369F8C13232BF6004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 135"; + rLen = 0; + rLoc = 3582; + rType = 0; + vrLen = 2013; + vrLoc = 1847; + }; + 1F369F9013232C36004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 137"; + rLen = 0; + rLoc = 3569; + rType = 0; + vrLen = 1951; + vrLoc = 1955; + }; + 1F369F9213232C3B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 137"; + rLen = 0; + rLoc = 3569; + rType = 0; + vrLen = 2020; + vrLoc = 1955; + }; + 1F369F9713232C58004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 133"; + rLen = 0; + rLoc = 3431; + rType = 0; + vrLen = 1936; + vrLoc = 2041; + }; + 1F369F9813232C66004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 139"; + rLen = 0; + rLoc = 3690; + rType = 0; + vrLen = 1936; + vrLoc = 2041; + }; + 1F369FAB13233178004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 91"; + rLen = 0; + rLoc = 2356; + rType = 0; + vrLen = 2153; + vrLoc = 1097; + }; + 1F369FAE132331BB004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 91"; + rLen = 0; + rLoc = 2356; + rType = 0; + vrLen = 2073; + vrLoc = 1211; + }; + 1F369FBB1323344E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 2080; + vrLoc = 996; + }; + 1F369FBE132334E1004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 21"; + rLen = 0; + rLoc = 358; + rType = 0; + vrLen = 2202; + vrLoc = 136; + }; + 1F369FC313233530004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 2209; + vrLoc = 191; + }; + 1F369FCA132335A2004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 2234; + vrLoc = 252; + }; + 1F369FCD132335E1004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 2245; + vrLoc = 298; + }; + 1F369FD2132336DF004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 2216; + vrLoc = 357; + }; + 1F369FD313233721004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 22"; + rLen = 0; + rLoc = 386; + rType = 0; + vrLen = 2225; + vrLoc = 40; + }; + 1F369FDA13233780004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 2113; + vrLoc = 693; + }; + 1F369FE1132338AC004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 1981; + vrLoc = 836; + }; + 1F369FE3132338C0004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 1996; + vrLoc = 836; + }; + 1F369FE61323395B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEC030131AF83B006D5497 /* GGSDelegate.h */; + name = "GGSDelegate.h: 16"; + rLen = 0; + rLoc = 283; + rType = 0; + vrLen = 531; + vrLoc = 0; + }; + 1F369FE71323395B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 69"; + rLen = 0; + rLoc = 1698; + rType = 0; + vrLen = 1994; + vrLoc = 838; + }; + 1F369FE81323395B004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 60"; + rLen = 0; + rLoc = 1369; + rType = 0; + vrLen = 1964; + vrLoc = 838; + }; + 1F36A00A13233D92004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF60DDB3853001CB0EB /* PongViewController.h */; + name = "PongViewController.h: 35"; + rLen = 0; + rLoc = 744; + rType = 0; + vrLen = 1245; + vrLoc = 0; + }; + 1F36A00B13233D92004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 73"; + rLen = 0; + rLoc = 1894; + rType = 0; + vrLen = 2005; + vrLoc = 971; + }; + 1F36A00C13233D92004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 328"; + rLen = 0; + rLoc = 8674; + rType = 0; + vrLen = 2044; + vrLoc = 845; + }; + 1F36A00F13233EE7004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF60DDB3853001CB0EB /* PongViewController.h */; + name = "PongViewController.h: 12"; + rLen = 0; + rLoc = 246; + rType = 0; + vrLen = 1284; + vrLoc = 0; + }; + 1F36A01013233EE7004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 11"; + rLen = 0; + rLoc = 168; + rType = 0; + vrLen = 2123; + vrLoc = 0; + }; + 1F36A01113233EE7004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 2159; + vrLoc = 3; + }; + 1F36A01713233F1E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 2167; + vrLoc = 40; + }; + 1F36A0201323417E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF60DDB3853001CB0EB /* PongViewController.h */; + name = "PongViewController.h: 12"; + rLen = 0; + rLoc = 246; + rType = 0; + vrLen = 1284; + vrLoc = 0; + }; + 1F36A0211323417E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 2167; + vrLoc = 169; + }; + 1F36A0221323417E004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 166"; + rLen = 0; + rLoc = 4434; + rType = 0; + vrLen = 2239; + vrLoc = 2533; + }; + 1F36A0291323454A004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 166"; + rLen = 0; + rLoc = 4434; + rType = 0; + vrLen = 1802; + vrLoc = 0; + }; + 1F36A02A1323454A004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */; + name = "GGSNetwork.h: 28"; + rLen = 0; + rLoc = 748; + rType = 0; + vrLen = 910; + vrLoc = 0; + }; + 1F36A02B1323454A004E7A99 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 14"; + rLen = 0; + rLoc = 198; + rType = 0; + vrLen = 1862; + vrLoc = 0; + }; + 1FA0569112F0B513003F1373 /* Pong */ = { + isa = PBXExecutable; + activeArgIndices = ( + ); + argumentStrings = ( + ); + autoAttachOnCrash = 1; + breakpointsEnabled = 0; + configStateDict = { + }; + customDataFormattersEnabled = 1; + dataTipCustomDataFormattersEnabled = 1; + dataTipShowTypeColumn = 1; + dataTipSortType = 0; + debuggerPlugin = GDBDebugging; + disassemblyDisplayState = 0; + dylibVariantSuffix = ""; + enableDebugStr = 1; + environmentEntries = ( + ); + executableSystemSymbolLevel = 0; + executableUserSymbolLevel = 0; + libgmallocEnabled = 0; + name = Pong; + savedGlobals = { + }; + showTypeColumn = 0; + sourceDirectories = ( + ); + variableFormatDictionary = { + }; + }; + 1FA0569D12F0B528003F1373 /* Source Control */ = { + isa = PBXSourceControlManager; + fallbackIsa = XCSourceControlManager; + isSCMEnabled = 0; + scmConfiguration = { + repositoryNamesForRoots = { + "" = ""; + }; + }; + }; + 1FA0569E12F0B528003F1373 /* Code sense */ = { + isa = PBXCodeSenseManager; + indexTemplatePath = ""; + }; + 1FBEBF151319F5C1006D5497 /* PlistBookmark */ = { + isa = PlistBookmark; + fRef = 8D1107310486CEB800E47090 /* Pong-Info.plist */; + fallbackIsa = PBXBookmark; + isK = 0; + kPath = ( + ); + name = "/Users/jeena/Projects/Pong/Pong-Info.plist"; + rLen = 0; + rLoc = 9223372036854775808; + }; + 1FBEBF4B1319FCDE006D5497 /* AsyncSocket.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {900, 10170}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRange = "{0, 2324}"; + }; + }; + 1FBEBF4C1319FCDE006D5497 /* AsyncSocket.m */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {852, 64080}}"; + sepNavSelRange = "{69038, 0}"; + sepNavVisRange = "{20925, 796}"; + }; + }; + 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {998, 1042}}"; + sepNavSelRange = "{748, 0}"; + sepNavVisRange = "{0, 910}"; + }; + }; + 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {998, 2175}}"; + sepNavSelRange = "{198, 0}"; + sepNavVisRange = "{0, 1862}"; + }; + }; + 1FBEC001131AA71C006D5497 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBF4B1319FCDE006D5497 /* AsyncSocket.h */; + name = "AsyncSocket.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 2324; + vrLoc = 0; + }; + 1FBEC002131AA71C006D5497 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBF4C1319FCDE006D5497 /* AsyncSocket.m */; + name = "AsyncSocket.m: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 2997; + vrLoc = 0; + }; + 1FBEC030131AF83B006D5497 /* GGSDelegate.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {998, 1171}}"; + sepNavSelRange = "{283, 0}"; + sepNavVisRange = "{0, 531}"; + }; + }; + 1FBEC05C131B085D006D5497 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1D3623240D0F684500981E51 /* PongAppDelegate.h */; + name = "PongAppDelegate.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 455; + vrLoc = 0; + }; + 1FBEC05D131B085D006D5497 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1D3623250D0F684500981E51 /* PongAppDelegate.m */; + name = "PongAppDelegate.m: 27"; + rLen = 0; + rLoc = 666; + rType = 0; + vrLen = 2203; + vrLoc = 0; + }; + 1FBEC124131B2559006D5497 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF60DDB3853001CB0EB /* PongViewController.h */; + name = "PongViewController.h: 11"; + rLen = 0; + rLoc = 246; + rType = 0; + vrLen = 1144; + vrLoc = 0; + }; + 1FBEC12D131B2581006D5497 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEC030131AF83B006D5497 /* GGSDelegate.h */; + name = "GGSDelegate.h: 16"; + rLen = 0; + rLoc = 283; + rType = 0; + vrLen = 459; + vrLoc = 0; + }; + 1FC1C5D0131C488000D1FF71 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; + name = "GGSNetwork.m: 44"; + rLen = 108; + rLoc = 966; + rType = 0; + vrLen = 2551; + vrLoc = 369; + }; + 1FC1C5D6131C806900D1FF71 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */; + name = "GGSNetwork.h: 28"; + rLen = 0; + rLoc = 748; + rType = 0; + vrLen = 910; + vrLoc = 0; + }; + 1FC1C5D7131C806900D1FF71 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 48"; + rLen = 0; + rLoc = 1181; + rType = 0; + vrLen = 2007; + vrLoc = 0; + }; + 1FC1C615131D81E200D1FF71 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; + name = "PongViewController.m: 85"; + rLen = 0; + rLoc = 4848; + rType = 0; + vrLen = 2258; + vrLoc = 1218; + }; + 28D7ACF60DDB3853001CB0EB /* PongViewController.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {998, 1171}}"; + sepNavSelRange = "{246, 0}"; + sepNavVisRange = "{0, 1284}"; + }; + }; + 28D7ACF70DDB3853001CB0EB /* PongViewController.m */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {998, 5070}}"; + sepNavSelRange = "{4434, 0}"; + sepNavVisRange = "{0, 1802}"; + }; + }; + 29B97313FDCFA39411CA2CEA /* Project object */ = { + activeBuildConfigurationName = Debug; + activeExecutable = 1FA0569112F0B513003F1373 /* Pong */; + activeSDKPreference = iphonesimulator4.2; + activeTarget = 1D6058900D05DD3D006BFB54 /* Pong */; + addToTargets = ( + 1D6058900D05DD3D006BFB54 /* Pong */, + ); + codeSenseManager = 1FA0569E12F0B528003F1373 /* Code sense */; + executables = ( + 1FA0569112F0B513003F1373 /* Pong */, + ); + perUserDictionary = { + PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; + PBXFileTableDataSourceColumnWidthsKey = ( + 20, + 820, + 20, + 48, + 43, + 43, + 20, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXFileDataSource_FiletypeID, + PBXFileDataSource_Filename_ColumnID, + PBXFileDataSource_Built_ColumnID, + PBXFileDataSource_ObjectSize_ColumnID, + PBXFileDataSource_Errors_ColumnID, + PBXFileDataSource_Warnings_ColumnID, + PBXFileDataSource_Target_ColumnID, + ); + }; + PBXPerProjectTemplateStateSaveDate = 321043010; + PBXWorkspaceStateSaveDate = 321043010; + }; + perUserProjectItems = { + 1F369E761323081C004E7A99 /* PBXTextBookmark */ = 1F369E761323081C004E7A99 /* PBXTextBookmark */; + 1F369E771323081C004E7A99 /* PBXTextBookmark */ = 1F369E771323081C004E7A99 /* PBXTextBookmark */; + 1F369E781323081C004E7A99 /* PBXTextBookmark */ = 1F369E781323081C004E7A99 /* PBXTextBookmark */; + 1F369E791323081C004E7A99 /* PBXTextBookmark */ = 1F369E791323081C004E7A99 /* PBXTextBookmark */; + 1F369E7A1323081C004E7A99 /* PBXTextBookmark */ = 1F369E7A1323081C004E7A99 /* PBXTextBookmark */; + 1F369E7B1323081C004E7A99 /* PBXTextBookmark */ = 1F369E7B1323081C004E7A99 /* PBXTextBookmark */; + 1F369E8A13230953004E7A99 /* PBXTextBookmark */ = 1F369E8A13230953004E7A99 /* PBXTextBookmark */; + 1F369E8B13230953004E7A99 /* PBXTextBookmark */ = 1F369E8B13230953004E7A99 /* PBXTextBookmark */; + 1F369E8C13230953004E7A99 /* PBXTextBookmark */ = 1F369E8C13230953004E7A99 /* PBXTextBookmark */; + 1F369E9313230981004E7A99 /* PBXTextBookmark */ = 1F369E9313230981004E7A99 /* PBXTextBookmark */; + 1F369E961323099B004E7A99 /* PBXTextBookmark */ = 1F369E961323099B004E7A99 /* PBXTextBookmark */; + 1F369E99132309E0004E7A99 /* PBXTextBookmark */ = 1F369E99132309E0004E7A99 /* PBXTextBookmark */; + 1F369E9A132309E0004E7A99 /* PBXTextBookmark */ = 1F369E9A132309E0004E7A99 /* PBXTextBookmark */; + 1F369E9B132309E0004E7A99 /* PBXTextBookmark */ = 1F369E9B132309E0004E7A99 /* PBXTextBookmark */; + 1F369E9E13230A0F004E7A99 /* PBXTextBookmark */ = 1F369E9E13230A0F004E7A99 /* PBXTextBookmark */; + 1F369E9F13230A0F004E7A99 /* PBXTextBookmark */ = 1F369E9F13230A0F004E7A99 /* PBXTextBookmark */; + 1F369EA013230A0F004E7A99 /* PBXTextBookmark */ = 1F369EA013230A0F004E7A99 /* PBXTextBookmark */; + 1F369EA513230A35004E7A99 /* PBXTextBookmark */ = 1F369EA513230A35004E7A99 /* PBXTextBookmark */; + 1F369EA613230A35004E7A99 /* PBXTextBookmark */ = 1F369EA613230A35004E7A99 /* PBXTextBookmark */; + 1F369EA713230A35004E7A99 /* PBXTextBookmark */ = 1F369EA713230A35004E7A99 /* PBXTextBookmark */; + 1F369EAA13230A56004E7A99 /* PBXTextBookmark */ = 1F369EAA13230A56004E7A99 /* PBXTextBookmark */; + 1F369EAB13230A56004E7A99 /* PBXTextBookmark */ = 1F369EAB13230A56004E7A99 /* PBXTextBookmark */; + 1F369EAC13230A56004E7A99 /* PBXTextBookmark */ = 1F369EAC13230A56004E7A99 /* PBXTextBookmark */; + 1F369EAF13230A6B004E7A99 /* PBXTextBookmark */ = 1F369EAF13230A6B004E7A99 /* PBXTextBookmark */; + 1F369EB413230AAC004E7A99 /* PBXTextBookmark */ = 1F369EB413230AAC004E7A99 /* PBXTextBookmark */; + 1F369EB513230ADE004E7A99 /* PBXTextBookmark */ = 1F369EB513230ADE004E7A99 /* PBXTextBookmark */; + 1F369EB613230B1D004E7A99 /* PBXTextBookmark */ = 1F369EB613230B1D004E7A99 /* PBXTextBookmark */; + 1F369EBB13230B53004E7A99 /* PBXTextBookmark */ = 1F369EBB13230B53004E7A99 /* PBXTextBookmark */; + 1F369EBF13230B6E004E7A99 /* PBXTextBookmark */ = 1F369EBF13230B6E004E7A99 /* PBXTextBookmark */; + 1F369EC013230BF3004E7A99 /* PBXTextBookmark */ = 1F369EC013230BF3004E7A99 /* PBXTextBookmark */; + 1F369EC113230BF3004E7A99 /* PBXTextBookmark */ = 1F369EC113230BF3004E7A99 /* PBXTextBookmark */; + 1F369EC413230DCA004E7A99 /* PBXTextBookmark */ = 1F369EC413230DCA004E7A99 /* PBXTextBookmark */; + 1F369EC613230DE0004E7A99 /* PBXTextBookmark */ = 1F369EC613230DE0004E7A99 /* PBXTextBookmark */; + 1F369ECB13230E71004E7A99 /* PBXTextBookmark */ = 1F369ECB13230E71004E7A99 /* PBXTextBookmark */; + 1F369ECC13230E71004E7A99 /* PBXTextBookmark */ = 1F369ECC13230E71004E7A99 /* PBXTextBookmark */; + 1F369ECD13230E71004E7A99 /* PBXTextBookmark */ = 1F369ECD13230E71004E7A99 /* PBXTextBookmark */; + 1F369ED013230E9E004E7A99 /* PBXTextBookmark */ = 1F369ED013230E9E004E7A99 /* PBXTextBookmark */; + 1F369ED313230EAD004E7A99 /* PBXTextBookmark */ = 1F369ED313230EAD004E7A99 /* PBXTextBookmark */; + 1F369ED413230F2A004E7A99 /* PBXTextBookmark */ = 1F369ED413230F2A004E7A99 /* PBXTextBookmark */; + 1F369ED71323101D004E7A99 /* PBXTextBookmark */ = 1F369ED71323101D004E7A99 /* PBXTextBookmark */; + 1F369ED81323101D004E7A99 /* PBXTextBookmark */ = 1F369ED81323101D004E7A99 /* PBXTextBookmark */; + 1F369ED91323101D004E7A99 /* PBXTextBookmark */ = 1F369ED91323101D004E7A99 /* PBXTextBookmark */; + 1F369EDA1323101D004E7A99 /* PBXTextBookmark */ = 1F369EDA1323101D004E7A99 /* PBXTextBookmark */; + 1F369EDD1323104E004E7A99 /* PBXTextBookmark */ = 1F369EDD1323104E004E7A99 /* PBXTextBookmark */; + 1F369EDE1323104E004E7A99 /* PBXTextBookmark */ = 1F369EDE1323104E004E7A99 /* PBXTextBookmark */; + 1F369EDF1323104E004E7A99 /* PBXTextBookmark */ = 1F369EDF1323104E004E7A99 /* PBXTextBookmark */; + 1F369EE21323105F004E7A99 /* PBXTextBookmark */ = 1F369EE21323105F004E7A99 /* PBXTextBookmark */; + 1F369EE513231076004E7A99 /* PBXTextBookmark */ = 1F369EE513231076004E7A99 /* PBXTextBookmark */; + 1F369EF413231106004E7A99 /* PBXTextBookmark */ = 1F369EF413231106004E7A99 /* PBXTextBookmark */; + 1F369EF91323111F004E7A99 /* PBXTextBookmark */ = 1F369EF91323111F004E7A99 /* PBXTextBookmark */; + 1F369EFC1323113B004E7A99 /* PBXTextBookmark */ = 1F369EFC1323113B004E7A99 /* PBXTextBookmark */; + 1F369EFD1323113B004E7A99 /* PBXTextBookmark */ = 1F369EFD1323113B004E7A99 /* PBXTextBookmark */; + 1F369F011323115B004E7A99 /* PBXTextBookmark */ = 1F369F011323115B004E7A99 /* PBXTextBookmark */; + 1F369F0413231163004E7A99 /* PBXTextBookmark */ = 1F369F0413231163004E7A99 /* PBXTextBookmark */; + 1F369F0B1323128E004E7A99 /* PBXTextBookmark */ = 1F369F0B1323128E004E7A99 /* PBXTextBookmark */; + 1F369F0C1323128E004E7A99 /* PBXTextBookmark */ = 1F369F0C1323128E004E7A99 /* PBXTextBookmark */; + 1F369F0D1323128E004E7A99 /* PBXTextBookmark */ = 1F369F0D1323128E004E7A99 /* PBXTextBookmark */; + 1F369F101323129A004E7A99 /* PBXTextBookmark */ = 1F369F101323129A004E7A99 /* PBXTextBookmark */; + 1F369F16132312C3004E7A99 /* PBXTextBookmark */ = 1F369F16132312C3004E7A99 /* PBXTextBookmark */; + 1F369F1F1323136F004E7A99 /* PBXTextBookmark */ = 1F369F1F1323136F004E7A99 /* PBXTextBookmark */; + 1F369F241323151F004E7A99 /* PBXTextBookmark */ = 1F369F241323151F004E7A99 /* PBXTextBookmark */; + 1F369F2913231563004E7A99 /* PBXTextBookmark */ = 1F369F2913231563004E7A99 /* PBXTextBookmark */; + 1F369F2D13231572004E7A99 /* PBXTextBookmark */ = 1F369F2D13231572004E7A99 /* PBXTextBookmark */; + 1F369F2E1323158B004E7A99 /* PBXTextBookmark */ = 1F369F2E1323158B004E7A99 /* PBXTextBookmark */; + 1F369F33132317A8004E7A99 /* PBXTextBookmark */ = 1F369F33132317A8004E7A99 /* PBXTextBookmark */; + 1F369F34132317A8004E7A99 /* PBXTextBookmark */ = 1F369F34132317A8004E7A99 /* PBXTextBookmark */; + 1F369F35132317A8004E7A99 /* PBXTextBookmark */ = 1F369F35132317A8004E7A99 /* PBXTextBookmark */; + 1F369F36132317A8004E7A99 /* PBXTextBookmark */ = 1F369F36132317A8004E7A99 /* PBXTextBookmark */; + 1F369F631323270D004E7A99 /* PBXTextBookmark */ = 1F369F631323270D004E7A99 /* PBXTextBookmark */; + 1F369F641323270D004E7A99 /* PBXTextBookmark */ = 1F369F641323270D004E7A99 /* PBXTextBookmark */; + 1F369F651323270D004E7A99 /* PBXTextBookmark */ = 1F369F651323270D004E7A99 /* PBXTextBookmark */; + 1F369F6C13232750004E7A99 /* PBXTextBookmark */ = 1F369F6C13232750004E7A99 /* PBXTextBookmark */; + 1F369F6D13232750004E7A99 /* PBXTextBookmark */ = 1F369F6D13232750004E7A99 /* PBXTextBookmark */; + 1F369F6E13232750004E7A99 /* PBXTextBookmark */ = 1F369F6E13232750004E7A99 /* PBXTextBookmark */; + 1F369F711323276C004E7A99 /* PBXTextBookmark */ = 1F369F711323276C004E7A99 /* PBXTextBookmark */; + 1F369F76132327A3004E7A99 /* PBXTextBookmark */ = 1F369F76132327A3004E7A99 /* PBXTextBookmark */; + 1F369F78132327B7004E7A99 /* PBXTextBookmark */ = 1F369F78132327B7004E7A99 /* PBXTextBookmark */; + 1F369F8913232BEE004E7A99 /* PBXTextBookmark */ = 1F369F8913232BEE004E7A99 /* PBXTextBookmark */; + 1F369F8C13232BF6004E7A99 /* PBXTextBookmark */ = 1F369F8C13232BF6004E7A99 /* PBXTextBookmark */; + 1F369F9013232C36004E7A99 /* PBXTextBookmark */ = 1F369F9013232C36004E7A99 /* PBXTextBookmark */; + 1F369F9213232C3B004E7A99 /* PBXTextBookmark */ = 1F369F9213232C3B004E7A99 /* PBXTextBookmark */; + 1F369F9713232C58004E7A99 /* PBXTextBookmark */ = 1F369F9713232C58004E7A99 /* PBXTextBookmark */; + 1F369F9813232C66004E7A99 /* PBXTextBookmark */ = 1F369F9813232C66004E7A99 /* PBXTextBookmark */; + 1F369FAB13233178004E7A99 /* PBXTextBookmark */ = 1F369FAB13233178004E7A99 /* PBXTextBookmark */; + 1F369FAE132331BB004E7A99 /* PBXTextBookmark */ = 1F369FAE132331BB004E7A99 /* PBXTextBookmark */; + 1F369FBB1323344E004E7A99 /* PBXTextBookmark */ = 1F369FBB1323344E004E7A99 /* PBXTextBookmark */; + 1F369FBE132334E1004E7A99 /* PBXTextBookmark */ = 1F369FBE132334E1004E7A99 /* PBXTextBookmark */; + 1F369FC313233530004E7A99 /* PBXTextBookmark */ = 1F369FC313233530004E7A99 /* PBXTextBookmark */; + 1F369FCA132335A2004E7A99 /* PBXTextBookmark */ = 1F369FCA132335A2004E7A99 /* PBXTextBookmark */; + 1F369FCD132335E1004E7A99 /* PBXTextBookmark */ = 1F369FCD132335E1004E7A99 /* PBXTextBookmark */; + 1F369FD2132336DF004E7A99 /* PBXTextBookmark */ = 1F369FD2132336DF004E7A99 /* PBXTextBookmark */; + 1F369FD313233721004E7A99 /* PBXTextBookmark */ = 1F369FD313233721004E7A99 /* PBXTextBookmark */; + 1F369FDA13233780004E7A99 /* PBXTextBookmark */ = 1F369FDA13233780004E7A99 /* PBXTextBookmark */; + 1F369FE1132338AC004E7A99 /* PBXTextBookmark */ = 1F369FE1132338AC004E7A99 /* PBXTextBookmark */; + 1F369FE3132338C0004E7A99 /* PBXTextBookmark */ = 1F369FE3132338C0004E7A99 /* PBXTextBookmark */; + 1F369FE61323395B004E7A99 /* PBXTextBookmark */ = 1F369FE61323395B004E7A99 /* PBXTextBookmark */; + 1F369FE71323395B004E7A99 /* PBXTextBookmark */ = 1F369FE71323395B004E7A99 /* PBXTextBookmark */; + 1F369FE81323395B004E7A99 /* PBXTextBookmark */ = 1F369FE81323395B004E7A99 /* PBXTextBookmark */; + 1F36A00A13233D92004E7A99 /* PBXTextBookmark */ = 1F36A00A13233D92004E7A99 /* PBXTextBookmark */; + 1F36A00B13233D92004E7A99 /* PBXTextBookmark */ = 1F36A00B13233D92004E7A99 /* PBXTextBookmark */; + 1F36A00C13233D92004E7A99 /* PBXTextBookmark */ = 1F36A00C13233D92004E7A99 /* PBXTextBookmark */; + 1F36A00F13233EE7004E7A99 /* PBXTextBookmark */ = 1F36A00F13233EE7004E7A99 /* PBXTextBookmark */; + 1F36A01013233EE7004E7A99 /* PBXTextBookmark */ = 1F36A01013233EE7004E7A99 /* PBXTextBookmark */; + 1F36A01113233EE7004E7A99 /* PBXTextBookmark */ = 1F36A01113233EE7004E7A99 /* PBXTextBookmark */; + 1F36A01713233F1E004E7A99 /* PBXTextBookmark */ = 1F36A01713233F1E004E7A99 /* PBXTextBookmark */; + 1F36A0201323417E004E7A99 /* PBXTextBookmark */ = 1F36A0201323417E004E7A99 /* PBXTextBookmark */; + 1F36A0211323417E004E7A99 /* PBXTextBookmark */ = 1F36A0211323417E004E7A99 /* PBXTextBookmark */; + 1F36A0221323417E004E7A99 /* PBXTextBookmark */ = 1F36A0221323417E004E7A99 /* PBXTextBookmark */; + 1F36A0291323454A004E7A99 /* PBXTextBookmark */ = 1F36A0291323454A004E7A99 /* PBXTextBookmark */; + 1F36A02A1323454A004E7A99 /* PBXTextBookmark */ = 1F36A02A1323454A004E7A99 /* PBXTextBookmark */; + 1F36A02B1323454A004E7A99 /* PBXTextBookmark */ = 1F36A02B1323454A004E7A99 /* PBXTextBookmark */; + 1FBEBF151319F5C1006D5497 = 1FBEBF151319F5C1006D5497 /* PlistBookmark */; + 1FBEC001131AA71C006D5497 = 1FBEC001131AA71C006D5497 /* PBXTextBookmark */; + 1FBEC002131AA71C006D5497 = 1FBEC002131AA71C006D5497 /* PBXTextBookmark */; + 1FBEC05C131B085D006D5497 = 1FBEC05C131B085D006D5497 /* PBXTextBookmark */; + 1FBEC05D131B085D006D5497 = 1FBEC05D131B085D006D5497 /* PBXTextBookmark */; + 1FBEC124131B2559006D5497 = 1FBEC124131B2559006D5497 /* PBXTextBookmark */; + 1FBEC12D131B2581006D5497 = 1FBEC12D131B2581006D5497 /* PBXTextBookmark */; + 1FC1C5D0131C488000D1FF71 = 1FC1C5D0131C488000D1FF71 /* PBXTextBookmark */; + 1FC1C5D6131C806900D1FF71 = 1FC1C5D6131C806900D1FF71 /* PBXTextBookmark */; + 1FC1C5D7131C806900D1FF71 = 1FC1C5D7131C806900D1FF71 /* PBXTextBookmark */; + 1FC1C615131D81E200D1FF71 = 1FC1C615131D81E200D1FF71 /* PBXTextBookmark */; + }; + sourceControlManager = 1FA0569D12F0B528003F1373 /* Source Control */; + userBuildSettings = { + }; + }; +} diff --git a/games/Pong/Pong.xcodeproj/project.pbxproj b/games/Pong/Pong.xcodeproj/project.pbxproj new file mode 100755 index 0000000..2e77be5 --- /dev/null +++ b/games/Pong/Pong.xcodeproj/project.pbxproj @@ -0,0 +1,302 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D3623260D0F684500981E51 /* PongAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* PongAppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 1F369FF813233C1E004E7A99 /* lost.wav in Resources */ = {isa = PBXBuildFile; fileRef = 1F369FF513233C1E004E7A99 /* lost.wav */; }; + 1F369FF913233C1E004E7A99 /* ping.wav in Resources */ = {isa = PBXBuildFile; fileRef = 1F369FF613233C1E004E7A99 /* ping.wav */; }; + 1F369FFA13233C1E004E7A99 /* pong.wav in Resources */ = {isa = PBXBuildFile; fileRef = 1F369FF713233C1E004E7A99 /* pong.wav */; }; + 1F36A00713233CCC004E7A99 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F36A00613233CCC004E7A99 /* AVFoundation.framework */; }; + 1FBEBF481319FC56006D5497 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FBEBF471319FC56006D5497 /* CFNetwork.framework */; }; + 1FBEBF4D1319FCDE006D5497 /* AsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FBEBF4C1319FCDE006D5497 /* AsyncSocket.m */; }; + 1FBEBFEF131A97F8006D5497 /* GGSNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */; }; + 288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765A40DF7441C002DB57D /* CoreGraphics.framework */; }; + 2899E5220DE3E06400AC0155 /* PongViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2899E5210DE3E06400AC0155 /* PongViewController.xib */; }; + 28AD733F0D9D9553002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD733E0D9D9553002E5188 /* MainWindow.xib */; }; + 28D7ACF80DDB3853001CB0EB /* PongViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28D7ACF70DDB3853001CB0EB /* PongViewController.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* PongAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PongAppDelegate.h; sourceTree = ""; }; + 1D3623250D0F684500981E51 /* PongAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PongAppDelegate.m; sourceTree = ""; }; + 1D6058910D05DD3D006BFB54 /* Pong.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pong.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 1F369FF513233C1E004E7A99 /* lost.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = lost.wav; sourceTree = ""; }; + 1F369FF613233C1E004E7A99 /* ping.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = ping.wav; sourceTree = ""; }; + 1F369FF713233C1E004E7A99 /* pong.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = pong.wav; sourceTree = ""; }; + 1F36A00613233CCC004E7A99 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 1FBEBF471319FC56006D5497 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + 1FBEBF4B1319FCDE006D5497 /* AsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncSocket.h; sourceTree = ""; }; + 1FBEBF4C1319FCDE006D5497 /* AsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncSocket.m; sourceTree = ""; }; + 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GGSNetwork.h; sourceTree = ""; }; + 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GGSNetwork.m; sourceTree = ""; }; + 1FBEC030131AF83B006D5497 /* GGSDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GGSDelegate.h; sourceTree = ""; }; + 288765A40DF7441C002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 2899E5210DE3E06400AC0155 /* PongViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PongViewController.xib; sourceTree = ""; }; + 28AD733E0D9D9553002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + 28D7ACF60DDB3853001CB0EB /* PongViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PongViewController.h; sourceTree = ""; }; + 28D7ACF70DDB3853001CB0EB /* PongViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PongViewController.m; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 32CA4F630368D1EE00C91783 /* Pong_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pong_Prefix.pch; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* Pong-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Pong-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */, + 1FBEBF481319FC56006D5497 /* CFNetwork.framework in Frameworks */, + 1F36A00713233CCC004E7A99 /* AVFoundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 1FBEBF4B1319FCDE006D5497 /* AsyncSocket.h */, + 1FBEBF4C1319FCDE006D5497 /* AsyncSocket.m */, + 1FA0569C12F0B528003F1373 /* Views */, + 1D3623240D0F684500981E51 /* PongAppDelegate.h */, + 1D3623250D0F684500981E51 /* PongAppDelegate.m */, + 28D7ACF60DDB3853001CB0EB /* PongViewController.h */, + 28D7ACF70DDB3853001CB0EB /* PongViewController.m */, + 1FBEC030131AF83B006D5497 /* GGSDelegate.h */, + 1FBEBFED131A97F8006D5497 /* GGSNetwork.h */, + 1FBEBFEE131A97F8006D5497 /* GGSNetwork.m */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* Pong.app */, + ); + name = Products; + sourceTree = ""; + }; + 1FA0569C12F0B528003F1373 /* Views */ = { + isa = PBXGroup; + children = ( + ); + name = Views; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 32CA4F630368D1EE00C91783 /* Pong_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 2899E5210DE3E06400AC0155 /* PongViewController.xib */, + 28AD733E0D9D9553002E5188 /* MainWindow.xib */, + 1F369FF513233C1E004E7A99 /* lost.wav */, + 1F369FF613233C1E004E7A99 /* ping.wav */, + 1F369FF713233C1E004E7A99 /* pong.wav */, + 8D1107310486CEB800E47090 /* Pong-Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 288765A40DF7441C002DB57D /* CoreGraphics.framework */, + 1FBEBF471319FC56006D5497 /* CFNetwork.framework */, + 1F36A00613233CCC004E7A99 /* AVFoundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* Pong */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Pong" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Pong; + productName = Pong; + productReference = 1D6058910D05DD3D006BFB54 /* Pong.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Pong" */; + compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* Pong */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28AD733F0D9D9553002E5188 /* MainWindow.xib in Resources */, + 2899E5220DE3E06400AC0155 /* PongViewController.xib in Resources */, + 1F369FF813233C1E004E7A99 /* lost.wav in Resources */, + 1F369FF913233C1E004E7A99 /* ping.wav in Resources */, + 1F369FFA13233C1E004E7A99 /* pong.wav in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* PongAppDelegate.m in Sources */, + 28D7ACF80DDB3853001CB0EB /* PongViewController.m in Sources */, + 1FBEBF4D1319FCDE006D5497 /* AsyncSocket.m in Sources */, + 1FBEBFEF131A97F8006D5497 /* GGSNetwork.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Pong_Prefix.pch; + INFOPLIST_FILE = "Pong-Info.plist"; + PRODUCT_NAME = Pong; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Pong_Prefix.pch; + INFOPLIST_FILE = "Pong-Info.plist"; + PRODUCT_NAME = Pong; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Richard Pannek (G62Q88N36M)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 3.0; + PREBINDING = NO; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = "6A9A419F-E593-49FC-98DE-2B027A0982C3"; + SDKROOT = iphoneos; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + PREBINDING = NO; + SDKROOT = iphoneos; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Pong" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Pong" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/games/Pong/Pong.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/games/Pong/Pong.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..2c44ce8 --- /dev/null +++ b/games/Pong/Pong.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/games/Pong/Pong.xcodeproj/xcuserdata/jeena.xcuserdatad/xcschemes/Pong.xcscheme b/games/Pong/Pong.xcodeproj/xcuserdata/jeena.xcuserdatad/xcschemes/Pong.xcscheme new file mode 100644 index 0000000..5ff41ee --- /dev/null +++ b/games/Pong/Pong.xcodeproj/xcuserdata/jeena.xcuserdatad/xcschemes/Pong.xcscheme @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/games/Pong/Pong.xcodeproj/xcuserdata/jeena.xcuserdatad/xcschemes/xcschememanagement.plist b/games/Pong/Pong.xcodeproj/xcuserdata/jeena.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..5bd0d76 --- /dev/null +++ b/games/Pong/Pong.xcodeproj/xcuserdata/jeena.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + Pong.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 1D6058900D05DD3D006BFB54 + + primary + + + + + diff --git a/games/Pong/PongViewController.xib b/games/Pong/PongViewController.xib new file mode 100644 index 0000000..37e8d08 --- /dev/null +++ b/games/Pong/PongViewController.xib @@ -0,0 +1,635 @@ + + + + 1056 + 10J567 + 823 + 1038.35 + 462.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 132 + + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 301 + + YES + + + 260 + {{20, 99}, {20, 90}} + + + 3 + MQA + + 2 + + + IBCocoaTouchFramework + + + + 256 + {{221, 135}, {20, 20}} + + + 3 + MQA + + + IBCocoaTouchFramework + + + + 260 + {{440, 99}, {20, 90}} + + + 3 + MQA + + + IBCocoaTouchFramework + + + + 301 + {{129, 227}, {203, 43}} + + NO + YES + 7 + NO + IBCocoaTouchFramework + Tap to begin + + Helvetica + 36 + 16 + + + 1 + MCAwIDAAA + + + 3 + MQA + + + 3 + MC42NjY2NjY2NjY3AA + + {2, 1} + 1 + 10 + + + + 292 + {{86, 20}, {42, 21}} + + NO + YES + 7 + NO + IBCocoaTouchFramework + 0 + + + 1 + 10 + 2 + + + + 292 + {{340, 20}, {42, 21}} + + NO + YES + 7 + NO + IBCocoaTouchFramework + 0 + + + 1 + 10 + + + {480, 300} + + + 2 + MCAwLjg5NDExNzcxMyAwLjA2Mjc0NTEwMTc1AA + + NO + YES + + 3 + + IBCocoaTouchFramework + + + + + YES + + + view + + + + 29 + + + + tapToBegin + + + + 30 + + + + pointsP2 + + + + 31 + + + + pointsP1 + + + + 32 + + + + player2View + + + + 33 + + + + player1View + + + + 34 + + + + ballView + + + + 35 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 6 + + + YES + + + + + + + + + + + 8 + + + YES + + + Player1 + + + 9 + + + Player2 + + + 10 + + + Ball + + + 20 + + + + + 22 + + + + + 23 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 10.IBPluginDependency + 10.IBViewBoundsToFrameTransform + 20.IBPluginDependency + 20.IBViewBoundsToFrameTransform + 22.IBPluginDependency + 22.IBViewBoundsToFrameTransform + 23.IBPluginDependency + 23.IBViewBoundsToFrameTransform + 6.IBEditorWindowLastContentRect + 6.IBPluginDependency + 8.IBPluginDependency + 8.IBViewBoundsToFrameTransform + 9.IBPluginDependency + 9.IBViewBoundsToFrameTransform + + + YES + PongViewController + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + P4AAAL+AAABDCgAAw2wAAA + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + P4AAAL+AAABDAQAAwyQAAA + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + AUKsAABBoAAAA + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + P4AAAL+AAABCtgAAwjAAAA + + {{546, 448}, {480, 300}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + P4AAAL+AAABBQAAAw4iAAA + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + P4AAAL+AAABDOQAAw7GAAA + + + + + YES + + + YES + + + + + YES + + + YES + + + + 35 + + + + YES + + PongViewController + UIViewController + + YES + + YES + ballView + player1View + player2View + pointsP1 + pointsP2 + tapToBegin + + + YES + UIView + UIView + UIView + UILabel + UILabel + UIView + + + + YES + + YES + ballView + player1View + player2View + pointsP1 + pointsP2 + tapToBegin + + + YES + + ballView + UIView + + + player1View + UIView + + + player2View + UIView + + + pointsP1 + UILabel + + + pointsP2 + UILabel + + + tapToBegin + UIView + + + + + IBProjectSource + Classes/PongViewController.h + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UILabel + UIView + + IBFrameworkSource + UIKit.framework/Headers/UILabel.h + + + + UIResponder + NSObject + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIPrintFormatter.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UIPopoverController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UISplitViewController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + Pong.xcodeproj + 3 + 132 + + diff --git a/games/Pong/Pong_Prefix.pch b/games/Pong/Pong_Prefix.pch new file mode 100644 index 0000000..25b822d --- /dev/null +++ b/games/Pong/Pong_Prefix.pch @@ -0,0 +1,8 @@ +// +// Prefix header for all source files of the 'Pong' target in the 'Pong' project +// + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/games/Pong/lost.wav b/games/Pong/lost.wav new file mode 100644 index 0000000..0ca32c6 Binary files /dev/null and b/games/Pong/lost.wav differ diff --git a/games/Pong/main.m b/games/Pong/main.m new file mode 100644 index 0000000..0fd4b5f --- /dev/null +++ b/games/Pong/main.m @@ -0,0 +1,17 @@ +// +// main.m +// Pong +// +// Created by Jeena on 26.01.11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import + +int main(int argc, char *argv[]) { + + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} diff --git a/games/Pong/ping.wav b/games/Pong/ping.wav new file mode 100644 index 0000000..581c6a9 Binary files /dev/null and b/games/Pong/ping.wav differ diff --git a/games/Pong/pong.wav b/games/Pong/pong.wav new file mode 100644 index 0000000..00120c8 Binary files /dev/null and b/games/Pong/pong.wav differ diff --git a/games/pong_bot_e/current_counterexample.eqc b/games/pong_bot_e/current_counterexample.eqc new file mode 100644 index 0000000..b3e181a Binary files /dev/null and b/games/pong_bot_e/current_counterexample.eqc differ diff --git a/games/pong_bot_e/ggs_network.erl b/games/pong_bot_e/ggs_network.erl new file mode 100644 index 0000000..713bd27 --- /dev/null +++ b/games/pong_bot_e/ggs_network.erl @@ -0,0 +1,121 @@ +-module(ggs_network). +-export([connect/0,append_key_value_strings_to_dict/2,key_value_string_to_list/1]). +-export([read/2, send_command/3]). + +%For quickcheck +-export([receive_content/1,receive_data/3]). + +connect() -> + %{ok,Socket} = gen_tcp:connect("ggs.jeena.net", 9000,[{active, false}]), + {ok,Socket} = gen_tcp:connect("localhost", 9000,[{active, false}]). + +read(Socket, Ref) -> + Content = receive_content(Socket), + Headers = extract_headers(Content), + ContentSize = dict:fetch("Content-Size", Headers), + ContentSizeI = list_to_integer(lists:nth(1, ContentSize)), + Data = receive_data(Socket, ContentSizeI, ""), + %io:format("Headers: ~s~n", [Content]), + %io:format("Data: ~s~n", [Data]), + received_command(Headers, Data, Ref). + +receive_content(Socket) -> + receive_content_(0, "", Socket). + +receive_content_(Amount, Headers, Socket) -> + {ok, Char1} = gen_tcp:recv(Socket, 1), + case Char1 of + "\n" -> case Amount of + 1 -> Headers; + _ -> receive_content_(Amount + 1, + Headers ++ Char1, + Socket) + end; + _ -> receive_content_(0, Headers ++ Char1, Socket) + end. + +receive_data(_, 0, Headers) -> + Headers; +receive_data(Socket, ContentSize, Headers) -> + {ok, Char} = gen_tcp:recv(Socket, 1), + receive_data(Socket, ContentSize - 1, Headers ++ Char). + +received_command(Headers, Data, Ref) -> + {ok, CommandList} = dict:find("Client-Command", Headers), + Command = lists:nth(1, CommandList), + case Command of + "hello" -> + pong_bot:set_game_token(Data, Ref), + send_command("ready", "", Ref); + "defined" -> + ok; + Command -> + pong_bot:ggsNetworkReceivedCommandWithArgs(Command, Data, Ref) + end. + +make_message(ServerOrGame, Command, Args, Ref) -> + GameToken = pong_bot:get_game_token(Ref), + StrGameToken = string:concat("Token: ", GameToken), + StrGameTokenln = string:concat(StrGameToken, "\n"), + StrCommand = string:concat("-Command: ", Command), + StrCommandln = string:concat(StrCommand, "\n"), + StrFullCommand = string:concat(ServerOrGame, StrCommandln), + StrContentLength = string:concat("Content-Length: ", integer_to_list(length(Args))), + StrContentLengthln = string:concat(StrContentLength, "\n\n"), + StrTokenCommand = string:concat(StrGameTokenln, StrFullCommand), + Message = string:concat(StrTokenCommand, StrContentLengthln), + MessageWithArgs = string:concat(Message, list_concat(Args,[])), + MessageWithArgs. + +send_command(Command, Args, Ref) -> + write(make_message("Game", Command, Args, Ref), Ref). + +write(Message, Ref) -> + Socket = gen_server:call({global, {pong_bot, Ref}}, socket), + gen_tcp:send(Socket, Message). + +list_concat([],Ret) -> + Ret; +list_concat([E|ES],Ret) -> + NewRet = string:concat(Ret,E), + list_concat(ES,NewRet). + +%%%Packet parsing.%%% + + +extract_headers(Source) -> + HeaderList = string:tokens(Source, "\n"), + key_value_strings_to_dict(HeaderList). + +%%%Low-level internals.%%% + + +%%["K1: V1","K2: V2","KN: VN" ...] -> Dict +key_value_strings_to_dict(Strings) -> + Dict = dict:new(), + append_key_value_strings_to_dict(Strings,Dict). + +%%["K1: V1","K2: V2","KN: VN" ...], Dict -> NewDict +append_key_value_strings_to_dict([Str|Strings],Dict) -> + KeyValueList = key_value_string_to_list(Str), + case length(KeyValueList) of + 2 -> + NewDict = append_string_pair_to_dict(Dict,lists:nth(1,KeyValueList),lists:nth(2,KeyValueList)), + append_key_value_strings_to_dict(Strings,NewDict); + _ -> + append_key_value_strings_to_dict(Strings,Dict) + end; +append_key_value_strings_to_dict([],Dict) -> + Dict. + + +%%"Hello: "World!" -> ["Hello","World!"] +key_value_string_to_list(KeyValueString) -> + string:tokens(KeyValueString, ": "). + + +%%Append a key str1 and a value str2 to the dict Dict +append_string_pair_to_dict(Dict, Str1, Str2) -> + dict:append(Str1, Str2, Dict). + + diff --git a/games/pong_bot_e/ggs_network_eqc_test.erl b/games/pong_bot_e/ggs_network_eqc_test.erl new file mode 100644 index 0000000..42b8f5c --- /dev/null +++ b/games/pong_bot_e/ggs_network_eqc_test.erl @@ -0,0 +1,86 @@ +-module(ggs_network_eqc_test). + +-include_lib("../../lib/eqc/include/eqc.hrl"). + +-compile(export_all). + + +start() -> + {ok, ListenSocket} = listen(), + eqc:quickcheck(prop_connect()), + gen_tcp:close(ListenSocket), + + {ok, ListenSocket2} = listen(), + eqc:quickcheck(prop_receive_content(ListenSocket2)), + %gen_tcp:close(ListenSocket2), + %timer:sleep(100), + + %{ok, ListenSocket} = listen(), + eqc:quickcheck(prop_receive_data(ListenSocket2)). + + +prop_connect() -> + {Atom, Socket} = ggs_network:connect(), + gen_tcp:close(Socket), + eqc:equals(Atom, ok). + +%% ?String++\n\n -> ok +prop_receive_content(ListenSocket) -> + G = fun(N) -> String = integer_to_list(N) ++"\n\n", + accept_run_compare(String,ListenSocket, fun(X) -> ggs_network:receive_content(X) end, "\n") end, + ?FORALL(NaturalNumber, nat(), G(NaturalNumber)). + +%% old(String) == new(String) +prop_receive_data(ListenSocket) -> + G = fun(N) -> String = integer_to_list(N), + Length = length(String), + accept_run_compare(String,ListenSocket, fun(S) -> ggs_network:receive_data(S,Length,"") end, "") end, + ?FORALL(NaturalNumber, nat(), G(NaturalNumber)). + +%% Helpers +accept_run_compare(Arg, ListenSocket, Fun, Newline) -> + spawn(fun() -> {ok, AcceptSocket} = accept(ListenSocket), + gen_tcp:send(AcceptSocket, Arg), + gen_tcp:close(AcceptSocket) end), + + {ok, ConnectSocket} = ggs_network:connect(), + Pid = spawn(fun() -> C = Fun(ConnectSocket), + gen_tcp:close(ConnectSocket), + receive + {From, getcontent} -> From!{C} end end ), + Pid!{self(), getcontent}, + receive + {C} -> eqc:equals(C++Newline,Arg) end. + +listen() -> + listen(9000). + +listen(Port) -> + case gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]) of + {ok, LSock} -> + {ok, LSock}; + {error, Reason} -> + io:format("Creation of listen socket failed: ~s~n", [Reason]) + end. + +recv(AcceptSocket) -> + gen_tcp:recv(AcceptSocket, 0). + +accept(ListenSocket) -> + case gen_tcp:accept(ListenSocket) of + {ok, Socket} -> {ok, Socket}; + {error, Reason} -> io:format("Error accepting listen socket~s~n", [Reason]); + _ -> io:format("Something bad happened with accept/1~n") + end. + + +%% new(length) == old(length) or +%% new(length) == old(length) + 1 +prop_setItem() -> + ggs_db:init(), + F = (fun(T,N,K,V) -> ggs_db:setItem(T,N,K,V), ggs_db:length(T,N) end), + ?FORALL({T,N,K,V},{bitstring(),bitstring(),bitstring(),bitstring()}, + (ggs_db:length(T,N) + 1 == F(T,N,K,V)) or + (ggs_db:length(T,N) == F(T,N,K,V))). + + diff --git a/games/pong_bot_e/pong_bot.erl b/games/pong_bot_e/pong_bot.erl new file mode 100644 index 0000000..ccc2e15 --- /dev/null +++ b/games/pong_bot_e/pong_bot.erl @@ -0,0 +1,255 @@ +-module(pong_bot). +-behaviour(gen_server). +-export([start/1, start_link/0]). +-export([init/1, handle_call/3, handle_cast/2]). +-export([ggsNetworkReceivedCommandWithArgs/3,set_game_token/2,get_game_token/1]). +-export([view/1, peek_socket/1]). + +start(0) -> + ok; +start(N) -> + start_link(), + timer:sleep(50), + start(N - 1). + +start_link() -> + Ref = make_ref(), + gen_server:start_link({global, {pong_bot, Ref}}, pong_bot, [], []), + Socket = peek_socket(Ref), + spawn(fun() -> communication_loop(Socket, Ref) end), + spawn(fun() -> game_loop(Ref) end ). + +communication_loop(Socket, Ref) -> + ggs_network:read(Socket, Ref), + communication_loop(Socket, Ref). + + +peek_socket(Ref) -> + gen_server:call({global, {pong_bot, Ref}}, socket). + + +init(_Args) -> + Player1 = new_pos(), + Player2 = new_pos(), + Ball = new_pos(), + Paused = true, + Start = false, + {ok, Socket} = ggs_network:connect(), %Localhost is set internally inside ggs_network. + State1 = dict:new(), + State2 = dict:store(player1, Player1, State1), + State3 = dict:store(player2, Player2, State2), + State4 = dict:store(ball, Ball, State3), + State5 = dict:store(paused, Paused, State4), + State6 = dict:store(start, Start, State5), + State = dict:store(socket, Socket, State6), + {ok, State}. + +new_pos() -> + {0, 0}. + + +ggsNetworkReceivedCommandWithArgs(Command, Args, Ref) -> + case Command of + "welcome" -> + welcome(Args, Ref); + "ball" -> + ball(Args, Ref); + "player1_y" -> + player1_y(Args, Ref); + "player2_y" -> + player2_y(Args, Ref); + "game" -> + game(Args, Ref); + "player1_points" -> + %io:format("Player1 win~n"), + new_round(Ref); + "player2_points" -> + %io:format("Player2 win~n"), + new_round(Ref); + _ -> ok + end. + +welcome(Who_am_I, Ref) -> + gen_server:cast({global, {pong_bot, Ref}}, {me, Who_am_I}). + %case Who_am_I of + % "1" -> + % Me = gen_server:call({global, {pong_bot, Ref}}, player1), + % gen_server:cast({global, {pong_bot, Ref}}, {me, Me}); + % "2" -> + % Me = gen_server:call({global, {pong_bot, Ref}}, player2), + % gen_server:cast({global, {pong_bot, Ref}}, {me, Me}) + %end. + + + +game_loop(Ref) -> + timer:sleep(300), + gameTick(Ref), + game_loop(Ref). + +gameTick(Ref) -> + GamePaused = gen_server:call({global, {pong_bot, Ref}}, paused), + SendStart = gen_server:call({global, {pong_bot, Ref}}, start), + + case GamePaused of + true -> + case SendStart of + false -> + ggs_network:send_command("start", "", Ref), + gen_server:cast({global, {pong_bot, Ref}}, {start, true}); + true -> + ok + end; + false -> + Ball = gen_server:call({global, {pong_bot, Ref}}, ball), + {_, BallY} = Ball, + Me = gen_server:call({global, {pong_bot, Ref}}, me), + case Me of + "1" -> + PlayerMe = gen_server:call({global, {pong_bot, Ref}}, player1); + "2" -> + PlayerMe = gen_server:call({global, {pong_bot, Ref}}, player2) + end, + + {_, MeY} = PlayerMe, + + case ((BallY - MeY) < 0) of + true -> + ggs_network:send_command("up", "", Ref); + %io:format("Player down sent to server~n"), + %io:format("Ball: ~B~n", [BallY]), + %io:format("Player: ~B~n", [MeY]); + _ -> case ((BallY - MeY) > 0) of + true -> + ggs_network:send_command("down", "", Ref); + %io:format("Player up sent to server~n"), + %io:format("Ball: ~B~n", [BallY]), + %io:format("Player: ~B~n", [MeY]); + _ -> ok + end + end + end. + + +ball(Pos_s, Ref) -> + PosList = string:tokens(Pos_s, ","), + XStr = lists:nth(1,PosList), + YStr = lists:nth(2,PosList), + X = list_to_integer(XStr), + Y = list_to_integer(YStr), + Pos = {X, Y}, + gen_server:cast({global, {pong_bot, Ref}}, {ball, Pos}). + +player1_y(YStr, Ref) -> + Y = list_to_integer(YStr), + %io:format("Player1_y~n~n~n~n"), + gen_server:cast({global, {pong_bot, Ref}}, {player1_y, Y}). + +player2_y(YStr, Ref) -> + Y = list_to_integer(YStr), + %io:format("Player2_y~n~n~n~n"), + gen_server:cast({global, {pong_bot, Ref}}, {player2_y, Y}). + +game(WaitOrStart, Ref) -> + case WaitOrStart of + "wait" -> + ok; + _ -> + gen_server:cast({global, {pong_bot, Ref}}, {paused, false}) + end. + + +new_round(Ref) -> + Paused = true, + Start = false, + gen_server:cast({global, {pong_bot, Ref}}, {new_round, Paused, Start}). + + +set_game_token(GameToken, Ref) -> + gen_server:cast({global, {pong_bot, Ref}}, {game_token, GameToken}). + +get_game_token(Ref) -> + gen_server:call({global, {pong_bot, Ref}}, game_token). + +view(Ref) -> + gen_server:call({global, {pong_bot, Ref}}, game_token). + +handle_call(player1, _From, State) -> + Player1 = dict:fetch(player1, State), + {reply, Player1, State}; + +handle_call(player2, _From, State) -> + Player2 = dict:fetch(player2, State), + {reply, Player2, State}; + +handle_call(player1_y, _From, State) -> + {_,Y} = dict:fetch(player1, State), + {reply, Y, State}; + +handle_call(player2_y, _From, State) -> + {_,Y} = dict:fetch(player2, State), + {reply, Y, State}; + +handle_call(ball, _From, State) -> + Ball = dict:fetch(ball, State), + {reply, Ball, State}; + +handle_call(me, _From, State) -> + Me = dict:fetch(me, State), + {reply, Me, State}; + +handle_call(game_token, _From, State) -> + GameToken = dict:fetch(game_token, State), + {reply, GameToken, State}; + +handle_call(view, _From, State) -> + {reply, State, State}; + +handle_call(socket, _From, State) -> + Socket = dict:fetch(socket, State), + {reply, Socket, State}; + +handle_call(paused, _From, State) -> + Paused = dict:fetch(paused, State), + {reply, Paused, State}; + +handle_call(start, _From, State) -> + Start = dict:fetch(start, State), + {reply, Start, State}. + +handle_cast({game_token, GameToken}, State) -> + NewState = dict:store(game_token, GameToken, State), + {noreply, NewState}; + +handle_cast({me, Me}, State) -> + NewState = dict:store(me, Me, State), + {noreply, NewState}; + +handle_cast({ball, Pos}, State) -> + NewState = dict:store(ball, Pos, State), + {noreply, NewState}; + +handle_cast({player1_y, Y}, State) -> + {OldX, _} = dict:fetch(player1, State), + NewPlayer1 = {OldX, Y}, + NewState = dict:store(player1, NewPlayer1, State), + {noreply, NewState}; + +handle_cast({player2_y, Y}, State) -> + {OldX, _} = dict:fetch(player2, State), + NewPlayer2 = {OldX, Y}, + NewState = dict:store(player2, NewPlayer2, State), + {noreply, NewState}; + +handle_cast({paused, Paused}, State) -> + NewState = dict:store(paused, Paused, State), + {noreply, NewState}; + +handle_cast({new_round, Paused, Start}, State) -> + State1 = dict:store(paused, Paused, State), + NewState = dict:store(start, Start, State1), + {noreply, NewState}; + +handle_cast({start, Start}, State) -> + NewState = dict:store(start, Start, State), + {noreply, NewState}. diff --git a/games/pong_bot_e/test/ggs_network_eqc_test.erl b/games/pong_bot_e/test/ggs_network_eqc_test.erl new file mode 100644 index 0000000..ebe69e5 --- /dev/null +++ b/games/pong_bot_e/test/ggs_network_eqc_test.erl @@ -0,0 +1,62 @@ +-module(ggs_network_eqc_test). + +-include_lib("../../lib/eqc/include/eqc.hrl"). + +-compile(export_all). + + +start() -> + eqc:quickcheck(prop_connect()). + +prop_connect() -> + {Atom, Value} = ggs_network:connect(), + eqc:equals(Atom, ok). + + +%% new(length) == old(length) or +%% new(length) == old(length) + 1 +prop_setItem() -> + ggs_db:init(), + F = (fun(T,N,K,V) -> ggs_db:setItem(T,N,K,V), ggs_db:length(T,N) end), + ?FORALL({T,N,K,V},{bitstring(),bitstring(),bitstring(),bitstring()}, + (ggs_db:length(T,N) + 1 == F(T,N,K,V)) or + (ggs_db:length(T,N) == F(T,N,K,V))). + + +%% new(length) >= 0 and +%% old(length) == new(length) or +%% old(length) - 1 == new(length) +prop_removeItem() -> + ggs_db:init(), + F = (fun(T,N,K) -> ggs_db:removeItem(T,N,K), ggs_db:length(T,N) end), + G = (fun(A,B) -> ((A == B) or (A == B + 1)) and (B >= 0) end), + ?FORALL({T,N,K},{bitstring(),bitstring(),bitstring()}, + G(ggs_db:length(T,N), F(T,N,K))). + + +%% clear(X) -> (length(X,?) == 0) +prop_clear() -> + ggs_db:init(), + F = (fun(T,N) -> ggs_db:clear(T), ggs_db:length(T,N) end), + ?FORALL({T,N},{bitstring(),bitstring()}, + F(T,N) == 0). + +%% ? -> length(?,?) >= 0 +prop_length() -> + ggs_db:init(), + F = fun(T,N,K,V) -> ggs_db:setItem(T,N,K,V), ggs_db:length(T,N) end, + G = fun(T,N,K) -> ggs_db:removeItem(T,N,K), ggs_db:length(T,N) end, + ?FORALL({{T,N,K,V},{T2,N2,K2}}, + {{bitstring(),bitstring(),bitstring(),bitstring()}, + {bitstring(),bitstring(),bitstring()}}, + (((F(T,N,K,V) >= 0) and (G(T2,N2,K2) >= 0)))). + + +%% key(X,Y,length(X,Y)) -> Exists +prop_key() -> + ggs_db:init(), + F = fun(T,N) -> case ggs_db:length(T,N) of 0 -> true; X -> + case ggs_db:key(T,N,X) of _ -> true end end end, + ?FORALL({T,N},{bitstring(),bitstring()}, + F(T,N)). + diff --git a/games/pong_bot_e/v.01/ggs_network_eqc_test.erl b/games/pong_bot_e/v.01/ggs_network_eqc_test.erl new file mode 100644 index 0000000..5b830c0 --- /dev/null +++ b/games/pong_bot_e/v.01/ggs_network_eqc_test.erl @@ -0,0 +1,90 @@ +-module(ggs_network_eqc_test). + +-include_lib("../../lib/eqc/include/eqc.hrl"). + +-compile(export_all). + + +start() -> + {ok, ListenSocket} = listen(), + register(listen, ListenSocket), + Pid = spawn(fun() -> {ok, AcceptSocket} = accept(ListenSocket), + + receive {From, accept} -> From!{AcceptSocket} end end), + {ok, ConnectSocket} = ggs_network:connect(), + Pid!{self(), accept}, + receive + {AcceptSocket} -> + io:format("AcceptSocket created~n"), + %eqc:quickcheck(prop_connect()), + %eqc:quickcheck(prop_receive_content(AcceptSocket, ConnectSocket)) + prop_receive_content(AcceptSocket, ConnectSocket) + end. + +prop_connect() -> + {Atom, _} = ggs_network:connect(), + eqc:equals(Atom, ok). + +prop_receive_content(AcceptSocket, ConnectSocket) -> + %{ok, ConnectSocket} = ggs_network:connect(), + io:format("Connected~n"), + %spawn(fun() -> {ok, B} = gen_tcp:recv(ConnectSocket, 1), io:format("Data: ~s~n",[B]) end), + %timer:sleep(300), + Pid = spawn(fun() -> C = receive_content(ConnectSocket), + io:format("Content: ~s~n", [C]) end), + + io:format("Before send to AcceptSocket~n"), + gen_tcp:send(AcceptSocket, "Hello\n\n"), + io:format("After send to AcceptSocket~n"), + eqc:equals(ok,ok). + +receive_content(Socket) -> + receive_content_(0, "", Socket). + +receive_content_(Amount, Headers, Socket) -> + {ok, Char1} = gen_tcp:recv(Socket, 1), + case Char1 of + "\n" -> case Amount of + 1 -> Headers; + _ -> receive_content_(Amount + 1, + Headers ++ Char1, + Socket) + end; + _ -> receive_content_(0, Headers ++ Char1, Socket) + end. + + + +%% Helpers +listen() -> + listen(9000). + +listen(Port) -> + case gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]) of + {ok, LSock} -> + {ok, LSock}; + {error, Reason} -> + io:format("Creation of listen socket failed: ~s~n", [Reason]) + end. + +recv(AcceptSocket) -> + gen_tcp:recv(AcceptSocket, 0). + +accept(ListenSocket) -> + case gen_tcp:accept(ListenSocket) of + {ok, Socket} -> {ok, Socket}; + {error, Reason} -> io:format("Error accepting listen socket~s~n", [Reason]); + _ -> io:format("Something bad happened with accept/1~n") + end. + + +%% new(length) == old(length) or +%% new(length) == old(length) + 1 +prop_setItem() -> + ggs_db:init(), + F = (fun(T,N,K,V) -> ggs_db:setItem(T,N,K,V), ggs_db:length(T,N) end), + ?FORALL({T,N,K,V},{bitstring(),bitstring(),bitstring(),bitstring()}, + (ggs_db:length(T,N) + 1 == F(T,N,K,V)) or + (ggs_db:length(T,N) == F(T,N,K,V))). + + diff --git a/games/pong_bot_e/v0.2/ggs_network_eqc_test.erl b/games/pong_bot_e/v0.2/ggs_network_eqc_test.erl new file mode 100644 index 0000000..911c4e3 --- /dev/null +++ b/games/pong_bot_e/v0.2/ggs_network_eqc_test.erl @@ -0,0 +1,66 @@ +-module(ggs_network_eqc_test). + +-include_lib("../../lib/eqc/include/eqc.hrl"). + +-compile(export_all). + + +start() -> + {ok, ListenSocket} = listen(), + register(listen, ListenSocket), + Pid = spawn(fun() -> {ok, AcceptSocket} = accept(ListenSocket), + gen_tcp:send(AcceptSocket, "Hello\n\n"), + gen_tcp:close(AcceptSocket) end), + eqc:quickcheck(prop_receive_content()), + gen_tcp:close(ListenSocket). + + + +prop_connect() -> + {Atom, _} = ggs_network:connect(), + eqc:equals(Atom, ok). + +prop_receive_content() -> + {ok, ConnectSocket} = ggs_network:connect(), + Pid = spawn(fun() -> C = ggs_network:receive_content(ConnectSocket), + gen_tcp:close(ConnectSocket), + receive + {From, getcontent} -> From!{C} end end ), + Pid!{self(), getcontent}, + receive + {C} -> eqc:equals(C,"Hello\n") + end. + +%% Helpers +listen() -> + listen(9000). + +listen(Port) -> + case gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]) of + {ok, LSock} -> + {ok, LSock}; + {error, Reason} -> + io:format("Creation of listen socket failed: ~s~n", [Reason]) + end. + +recv(AcceptSocket) -> + gen_tcp:recv(AcceptSocket, 0). + +accept(ListenSocket) -> + case gen_tcp:accept(ListenSocket) of + {ok, Socket} -> {ok, Socket}; + {error, Reason} -> io:format("Error accepting listen socket~s~n", [Reason]); + _ -> io:format("Something bad happened with accept/1~n") + end. + + +%% new(length) == old(length) or +%% new(length) == old(length) + 1 +prop_setItem() -> + ggs_db:init(), + F = (fun(T,N,K,V) -> ggs_db:setItem(T,N,K,V), ggs_db:length(T,N) end), + ?FORALL({T,N,K,V},{bitstring(),bitstring(),bitstring(),bitstring()}, + (ggs_db:length(T,N) + 1 == F(T,N,K,V)) or + (ggs_db:length(T,N) == F(T,N,K,V))). + + diff --git a/games/pong_bot_e/v0.3/ggs_network_eqc_test.erl b/games/pong_bot_e/v0.3/ggs_network_eqc_test.erl new file mode 100644 index 0000000..ed9e3d5 --- /dev/null +++ b/games/pong_bot_e/v0.3/ggs_network_eqc_test.erl @@ -0,0 +1,72 @@ +-module(ggs_network_eqc_test). + +-include_lib("../../lib/eqc/include/eqc.hrl"). + +-compile(export_all). + + +start() -> + {ok, ListenSocket} = listen(), + eqc:quickcheck(prop_connect()), + gen_tcp:close(ListenSocket), + {ok, ListenSocket2} = listen(), + eqc:quickcheck(prop_receive_content(ListenSocket2)). + + + +prop_connect() -> + {Atom, Socket} = ggs_network:connect(), + gen_tcp:close(Socket), + eqc:equals(Atom, ok). + +%% ?String++\n\n -> ok +prop_receive_content(ListenSocket) -> + F = fun(X) -> Pid2 = spawn(fun() -> {ok, AcceptSocket} = accept(ListenSocket), + gen_tcp:send(AcceptSocket, X), + gen_tcp:close(AcceptSocket) end), + + {ok, ConnectSocket} = ggs_network:connect(), + Pid = spawn(fun() -> C = ggs_network:receive_content(ConnectSocket), + gen_tcp:close(ConnectSocket), + receive + {From, getcontent} -> From!{C} end end ), + Pid!{self(), getcontent}, + receive + {C} -> eqc:equals(C++"\n",X) end end, + G = fun(N) -> String = integer_to_list(N) ++"\n\n", + F(String) end, + ?FORALL(NaturalNumber, nat(), G(NaturalNumber)). + +%% Helpers +listen() -> + listen(9000). + +listen(Port) -> + case gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]) of + {ok, LSock} -> + {ok, LSock}; + {error, Reason} -> + io:format("Creation of listen socket failed: ~s~n", [Reason]) + end. + +recv(AcceptSocket) -> + gen_tcp:recv(AcceptSocket, 0). + +accept(ListenSocket) -> + case gen_tcp:accept(ListenSocket) of + {ok, Socket} -> {ok, Socket}; + {error, Reason} -> io:format("Error accepting listen socket~s~n", [Reason]); + _ -> io:format("Something bad happened with accept/1~n") + end. + + +%% new(length) == old(length) or +%% new(length) == old(length) + 1 +prop_setItem() -> + ggs_db:init(), + F = (fun(T,N,K,V) -> ggs_db:setItem(T,N,K,V), ggs_db:length(T,N) end), + ?FORALL({T,N,K,V},{bitstring(),bitstring(),bitstring(),bitstring()}, + (ggs_db:length(T,N) + 1 == F(T,N,K,V)) or + (ggs_db:length(T,N) == F(T,N,K,V))). + + diff --git a/games/tic-tac-toe-js/index.html b/games/tic-tac-toe-js/index.html index 97d3c4f..5bdb286 100644 --- a/games/tic-tac-toe-js/index.html +++ b/games/tic-tac-toe-js/index.html @@ -12,8 +12,16 @@ function init() { GameServer.addGame(game_name, main()); - GameServer.addClient(game_name, new TicTacToeClient(frames.player1.document.getElementById("p1"), GameServer)); - GameServer.addClient(game_name, new TicTacToeClient(frames.player2.document.getElementById("p2"), GameServer)); + GameServer.addClient( + game_name, + new TicTacToeClient(frames.player1.document.getElementById("p1"), + GameServer + )); + GameServer.addClient( + game_name, + new TicTacToeClient(frames.player2.document.getElementById("p2"), + GameServer + )); } diff --git a/games/tic-tac-toe-js/proxy.rb b/games/tic-tac-toe-js/proxy.rb new file mode 100644 index 0000000..e69de29 diff --git a/games/tic-tac-toe-python/server.js b/games/tic-tac-toe-python/server.js new file mode 100644 index 0000000..5c8d5dd --- /dev/null +++ b/games/tic-tac-toe-python/server.js @@ -0,0 +1,172 @@ +function playerCommand(player_id, command, args) { + var p1_id = GGS.localStorage.getItem("p1_id"); + var p2_id = GGS.localStorage.getItem("p2_id"); + + if (command == "hi") { + hi(player_id); + } else if (command == "set" && p1_id && p2_id) { + move(player_id, args); + } else if (command == "new" && p1_id && p2_id) { + newGame(); + } +} + +var ROWS = 3; + +function hi(player_id) { + var p1_id = GGS.localStorage.getItem("p1_id"); + var p2_id = GGS.localStorage.getItem("p2_id"); + if (!p1_id) { + GGS.localStorage.setItem("p1_id", player_id); + GGS.sendCommand(player_id, "welcome", "1"); + } else if (!p2_id) { + GGS.localStorage.setItem("p2_id", player_id); + GGS.sendCommand(player_id, "welcome", "2"); + newGame(); + } else { + GGS.sendCommand(player_id, "not_welcome", "Already have 2 players on this table"); + } +} + +function move(player_id, args) { + var nextPlayer = GGS.localStorage.getItem("next_player"); + var p1_id = GGS.localStorage.getItem("p1_id"); + var p2_id = GGS.localStorage.getItem("p2_id"); + var valid = false; + + if(nextPlayer == 1 && player_id == p1_id) { + valid = true; + } else if (nextPlayer == 2 && player_id == p2_id) { + valid = true; + } + + if (valid) { + var p = nextPlayer; + var props = JSON.parse(args); + var gameBoard = JSON.parse(GGS.localStorage.getItem("game_board")); + + if (gameBoard[props.x][props.y] == 0) { + + gameBoard[props.x][props.y] = p; + GGS.localStorage.setItem("game_board", JSON.stringify(gameBoard)); + GGS.sendCommandToAll("game_board", boardAsString(gameBoard)); + GGS.log(this.checkIfWon(gameBoard)) + if (this.checkIfWon(gameBoard)) { + if (p == 1) { + GGS.sendCommand(p1_id, "winner", "You win!"); + GGS.sendCommand(p2_id, "loser", "You lose!"); + } else { + GGS.sendCommand(p1_id, "loser", "You lose!"); + GGS.sendCommand(p2_id, "winner", "You win!"); + } + } else if(allFieldsFull(gameBoard)) { + GGS.sendCommandToAll("draw", "It was a dwaw!"); + }allFieldsFull(gameBoard) + + if (nextPlayer == 1) { + GGS.localStorage.setItem("next_player", 2); + GGS.sendCommand(p1_id, "yourturn", ""); + } else { + GGS.localStorage.setItem("next_player", 1); + GGS.sendCommand(p2_id, "yourturn", ""); + } + } else { + GGS.sendCommand(player_id, "warning", "Already set, chose something else."); + } + + } else { + GGS.sendCommand(player_id, "warning", "Not your turn!"); + } +} + +function checkIfWon(gameBoard) { + + //var gameBoard = JSON.parse(GGS.localStorage.getItem("game_board")); + + for (i = 0; i < ROWS; ++i) { + for (j = 0; j < ROWS; ++j) { + if (gameBoard[i][j] != 1) { + break; + } + } + if (j == ROWS) { + return true; + } + + for (j = 0; j < ROWS; ++j) { + if (gameBoard[j][i] != 1) { + break; + } + } + if (j == ROWS) { + return true; + } + } + + // Now check diagnols + for (i = 0; i < ROWS; ++i) { + if (gameBoard[i][i] != 1) { + break; + } + } + + if (i == ROWS) { + return true; + } + + for (i = 0; i < ROWS; ++i) { + if (gameBoard[i][ROWS - i - 1] != 1) { + break; + } + } + if (i == ROWS) { + return true; + } + + return false; +} + +function allFieldsFull(gameBoard) { + for (var i=0; i < ROWS; i++) { + for(var j=0; j < ROWS; j++) { + if (gameBoard[i][j] == 0) { + return false; + } + } + } + return true; +} + +function newGame() { + // Initiate game with empty rows and columns + var gameBoard = []; + for (var i=0; i < ROWS; i++) { + gameBoard[i] = [""]; + for (var j=0; j < ROWS; j++) { + gameBoard[i][j] = 0; + } + } + + GGS.localStorage.setItem("game_board", JSON.stringify(gameBoard)); + GGS.sendCommandToAll("new_game", ""); + GGS.sendCommandToAll("game_board", boardAsString(gameBoard)); + GGS.localStorage.setItem("next_player", 1); +} + +function boardAsString(gameBoard) { + var out = ""; + for (var i=0; i < ROWS; i++) { + for (var j=0; j < ROWS; j++) { + var p = gameBoard[i][j]; + if (p == 1) { + out += "X"; + } else if (p == 2) { + out +="O"; + } else { + out += " "; + } + } + } + + return out; +} \ No newline at end of file diff --git a/games/tic-tac-toe-python/ttt.glade b/games/tic-tac-toe-python/ttt.glade new file mode 100644 index 0000000..147b280 --- /dev/null +++ b/games/tic-tac-toe-python/ttt.glade @@ -0,0 +1,249 @@ + + + + + + 561 + 521 + False + + + True + False + + + True + False + + + True + False + 0.41999998688697815 + 7 + <span size="x-large">GGS Tic Tac Toe</span> + True + + + True + False + 0 + + + + + False + False + 0 + + + + + True + False + 3 + 3 + True + + + True + True + True + False + + + + + + True + True + True + False + + + + 1 + 2 + + + + + True + True + True + False + + + + 1 + 2 + + + + + True + True + True + False + + + + 1 + 2 + 1 + 2 + + + + + True + True + True + False + + + + 2 + 3 + + + + + True + True + True + False + + + + 1 + 2 + 2 + 3 + + + + + True + True + True + False + + + + 2 + 3 + + + + + True + True + True + False + + + + 2 + 3 + 1 + 2 + + + + + True + True + True + False + + + + 2 + 3 + 2 + 3 + + + + + True + True + 1 + + + + + True + False + + + True + True + + ggs.jeena.net:9000 + True + False + False + True + True + + + True + True + 0 + + + + + True + True + + False + False + True + True + + + True + True + 1 + + + + + + True + True + True + False + + + + True + True + 2 + + + + + False + True + 2 + + + + + True + False + 2 + + + False + True + 3 + + + + + + diff --git a/games/tic-tac-toe-python/ttt.py b/games/tic-tac-toe-python/ttt.py new file mode 100644 index 0000000..85472d7 --- /dev/null +++ b/games/tic-tac-toe-python/ttt.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python + +import sys, socket, thread, gobject, getpass, time, os, pango +try: + import pygtk + pygtk.require("2.16") +except: + pass +try: + import gtk + import gtk.glade +except: + sys.exit(1) + +class GGSTTT: + + def __init__(self): + #Set the Glade file + self.gladefile = "ttt.glade" + self.wTree = gtk.glade.XML(self.gladefile, "window1") + host = "localhost" + port = 9000 + + #Create our dictionay and connect it + dic = { "on_window1_destroy_event" : gtk.main_quit + ,"on_x0y0_clicked" : lambda x: self.sendMove("{\"x\":0,\"y\":0}") + ,"on_x0y1_clicked" : lambda x: self.sendMove("{\"x\":0,\"y\":1}") + ,"on_x0y2_clicked" : lambda x: self.sendMove("{\"x\":0,\"y\":2}") + ,"on_x1y0_clicked" : lambda x: self.sendMove("{\"x\":1,\"y\":0}") + ,"on_x1y1_clicked" : lambda x: self.sendMove("{\"x\":1,\"y\":1}") + ,"on_x1y2_clicked" : lambda x: self.sendMove("{\"x\":1,\"y\":2}") + ,"on_x2y0_clicked" : lambda x: self.sendMove("{\"x\":2,\"y\":0}") + ,"on_x2y1_clicked" : lambda x: self.sendMove("{\"x\":2,\"y\":1}") + ,"on_x2y2_clicked" : lambda x: self.sendMove("{\"x\":2,\"y\":2}") + ,"on_connectBtn_clicked" : lambda x: self.doConnect() + } + + self.wTree.signal_autoconnect(dic) + + self.wTree.get_widget("window1").show() + + + def doConnect(self): + self.setStatus("Not connected") + hostport = self.wTree.get_widget("adress").get_text() + host, port = hostport.split(":") + + self.connect(host, int(port)) + thread.start_new_thread(self.listen, ()) + + token = self.wTree.get_widget("token").get_text() + self.s.send("Server-Command: hello\n" + + "Content-Type: text\n" + + "Content-Length: %s\n" % len(token)+ + "\n"+ + token) + + def sendMove(self, move): + print "Sending move", move + cmd = "set" + self.s.send("Game-Command: %s\n" % cmd + + "Content-Type: text\n" + + "Content-Length: %s\n" % len(move)+ + "\n"+ + move) + + def setStatus(self, msg): + self.wTree.get_widget("statusbar").push(0, msg) + + def listen(self): + msg = {} + print "listening" + fs = self.s.makefile() + while True: + line = fs.readline() + print "Received: '%s" % line.strip() + if line != "\n": + key = line.split(":")[0] + value = line.split(":")[1] + msg[key] = value.strip() + else: + msg["DATA"] = fs.read(int("%s" % msg["Content-Size"])) + print "Got data:", msg + self.protocolHandler(msg) + + def protocolHandler(self, msg): + if msg["Client-Command"] == "hello": + data = msg["DATA"] + self.token, defined, table_token = data.split(",") + if defined == "false": + print "Defining game" + js = open("server.js").read() + self.wTree.get_widget("token").set_text(table_token) + self.s.send("Server-Command: define\n"+ + "Content-Type: text\n" + + "Content-Length: %s\n" % str(len(js))+ + "\n" + + js) + if defined == "true": + self.s.send("Game-Command: hi\n" + + "Content-Type: text\n" + + "Content-Length: 0\n"+ + "\n") + + elif msg["Client-Command"] == "welcome": + self.setStatus("You are player %s" % msg["DATA"]) + elif msg["Client-Command"] == "warning": + self.setStatus("Warning: %s" % msg["DATA"]) + elif msg["Client-Command"] == "not_welcome": + self.setStatus("You are not welcome: %s" % msg["DATA"]) + elif msg["Client-Command"] == "game_board": + self.wTree.get_widget("x0y0").set_label(msg["DATA"][0]) + self.wTree.get_widget("x0y1").set_label(msg["DATA"][1]) + self.wTree.get_widget("x0y2").set_label(msg["DATA"][2]) + self.wTree.get_widget("x1y0").set_label(msg["DATA"][3]) + self.wTree.get_widget("x1y1").set_label(msg["DATA"][4]) + self.wTree.get_widget("x1y2").set_label(msg["DATA"][5]) + self.wTree.get_widget("x2y0").set_label(msg["DATA"][6]) + self.wTree.get_widget("x2y1").set_label(msg["DATA"][7]) + self.wTree.get_widget("x2y2").set_label(msg["DATA"][8]) + self.wTree.get_widget("x0y0").get_child().modify_font(pango.FontDescription("sans 48")) + self.wTree.get_widget("x0y1").get_child().modify_font(pango.FontDescription("sans 48")) + self.wTree.get_widget("x0y2").get_child().modify_font(pango.FontDescription("sans 48")) + self.wTree.get_widget("x1y0").get_child().modify_font(pango.FontDescription("sans 48")) + self.wTree.get_widget("x1y1").get_child().modify_font(pango.FontDescription("sans 48")) + self.wTree.get_widget("x1y2").get_child().modify_font(pango.FontDescription("sans 48")) + self.wTree.get_widget("x2y0").get_child().modify_font(pango.FontDescription("sans 48")) + self.wTree.get_widget("x2y1").get_child().modify_font(pango.FontDescription("sans 48")) + self.wTree.get_widget("x2y2").get_child().modify_font(pango.FontDescription("sans 48")) + self.setStatus("") + elif msg["Client-Command"] == "defined": + self.s.send("Game-Command: hi\n" + + "Content-Type: text\n" + + "Content-Length: 0\n"+ + "\n") + self.setStatus("") + elif msg["Client-Command"] == "lusers": + print msg + gobject.idle_add(self.updateUsers, msg["DATA"]) + elif msg["Client-Command"] == "loser" or msg["Client-Command"] == "winner" or msg["Client-Command"] == "draw": +# md = gtk.MessageDialog(None, +# gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, +# gtk.BUTTONS_CLOSE, msg["DATA"]) +# md.run() +# md.destroy() + self.s.send("Game-Command: new\n" + + "Content-Type: text\n" + + "Content-Length: 0\n"+ + "\n") + self.setStatus(msg["DATA"]) + + + def connect(self, host,port): + print "Connecting" + self.setStatus("Connecting") + self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.s.connect((host, port)) + self.setStatus("Connected!") + + + + +if __name__ == "__main__": + ttt = GGSTTT() + gobject.threads_init() + gtk.main() diff --git a/games/tic-tac-toe/data.py b/games/tic-tac-toe/data.py new file mode 100644 index 0000000..b83c3ad --- /dev/null +++ b/games/tic-tac-toe/data.py @@ -0,0 +1,15 @@ +def greatest_sequence(match, pattern): + m = match + p = pattern + size = 0 + max_size = 0 + + for p in pattern: + if m == p: + size += 1 + else: + if size > max_size: + max_size = size + size = 0 + + return max_size diff --git a/games/tic-tac-toe/data.pyc b/games/tic-tac-toe/data.pyc new file mode 100644 index 0000000..682dfaa Binary files /dev/null and b/games/tic-tac-toe/data.pyc differ diff --git a/games/tic-tac-toe/player.py b/games/tic-tac-toe/player.py new file mode 100644 index 0000000..081112c --- /dev/null +++ b/games/tic-tac-toe/player.py @@ -0,0 +1,15 @@ +from pygame.mouse import get_pos +from point import Point + +class Player(object): + def __init__(self, id, shape, board): + self.shape = shape + self.board = board + self.id = id + + + def turn(self): + #Ask mouse for position + board.make_turn(Point(get_pos()[0],get_pos()[1])) + + diff --git a/games/tic-tac-toe/server.py b/games/tic-tac-toe/server.py new file mode 100644 index 0000000..91e32b5 --- /dev/null +++ b/games/tic-tac-toe/server.py @@ -0,0 +1,23 @@ +#server.py +import json +from socket import socket, AF_INET, SOCK_STREAM + + +class server(object): + def __init__(self, port=None): + self.port = port + self.world = GGS.init() + self.socket = socket(AF_INET, SOCK_STREAM) + self.socket.connect(("www.???.com", 80)) + + def turn(self, id, index): + rows = sqrt(board.nr_of_rectangles) + x = int(index / rows) + y = int(index % rows) + + json.dumps({"x": x, "y": y} + world.callCommand("tictactoe", "set", json.dumps({"x": x, "y": y})) + + sent = 0 + length = len( + while sent diff --git a/games/tic-tac-toe/test_data.py b/games/tic-tac-toe/test_data.py new file mode 100644 index 0000000..38282f0 --- /dev/null +++ b/games/tic-tac-toe/test_data.py @@ -0,0 +1,14 @@ +import unittest +import data + + +class TestData(unittest.TestCase): + def setUp(self): + array = [0,1,1,1,3,4,5,2,2,3,3,3,3,3,33,4,2,2] + + self.assertTrue(data.greatest_sequence(array, 3) == 5) + +if __name__ == '__main__': + unittest.main() + + diff --git a/lib/eqc/LicenceAgreement.html b/lib/eqc/LicenceAgreement.html new file mode 100644 index 0000000..6e2704a --- /dev/null +++ b/lib/eqc/LicenceAgreement.html @@ -0,0 +1,49 @@ + + + + + + + + + + + + + +

QuickCheck Mini

+

End User License Agreement

+


+

This end user license agreement ("eula") is a legal agreement between you and quviq. Read it carefully before using the software. It provides a license to use the software and contains warranty information and liability disclaimers. By installing, copying, or otherwise using the software, you agree to be bound by the terms of this agreement. If you do not agree to the terms of this agreement, do not install or use the software.

+


+


+

1 LICENSE GRANTS

+


+

• Quviq – Quviq AB grants you the non-exclusive right to use Quviq’s software program, QuickCheck Mini (the "SOFTWARE").

+


+

2 LICENSE RESTRICTIONS

+


+

• Distribution. You may freely distribute copies of the SOFTWARE to third parties or point them to the download location at www.quviq.com.

+

• Prohibition on Reverse Engineering, Decompilation, and Disassembly. You may not reverse engineer, decompile, or disassemble the SOFTWARE.

+

• Reservation of Rights. Quviq retains all rights not expressly granted.

+

 

+

3 DISCLAIMER OF WARRANTY AND LIABILITY

+


+

Limited warranty. The SOFTWARE is provided "as is", without warranty of any kind. To the maximum extent permitted by applicable law, Quviq hereby disclaim all warranties, either expressed or implied, including, but not limited to, the implied warranties of merchantability, fitness for a particular purpose, title, and non-infringement, with regard to the SOFTWARE, and the provision of or failure to provide support services. This limited warranty gives you specific legal rights. You may have others, which vary from state/jurisdiction to state/jurisdiction. 

+


+

Limitation of liability. To the maximum extent permitted by applicable law, in no event shall Quviq or its suppliers be liable for any special, incidental, indirect, or consequential damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or any other pecuniary loss) arising out of the use of or inability to use the SOFTWARE or the provision of or failure to provide support services, even if quviq has been advised of the possibility of such damages. In any case, Quviq’s entire liability under any provision of this eula shall be limited to the amount actually paid by you for the SOFTWARE.

+


+


+ + diff --git a/lib/eqc/LicenceAgreement.rtf b/lib/eqc/LicenceAgreement.rtf new file mode 100644 index 0000000..763c06e --- /dev/null +++ b/lib/eqc/LicenceAgreement.rtf @@ -0,0 +1 @@ +{\rtf1\adeflang1025\ansi\ansicpg10000\uc1\adeff31507\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\upr{\fonttbl{\f0\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f4\fbidi \fnil\fcharset0\fprq2{\*\panose 02000500000000000000}Times;} {\f23\fbidi \fnil\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;}{\f24\fbidi \fnil\fcharset0\fprq2{\*\panose 02000503060000020004}Optima;}{\f25\fbidi \fnil\fcharset0\fprq2 Lucida Grande;} {\flomajor\f31500\fbidi \fnil\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}{\fdbmajor\f31501\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} {\fhimajor\f31502\fbidi \fnil\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbimajor\f31503\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} {\flominor\f31504\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} {\fhiminor\f31506\fbidi \fnil\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;}{\fbiminor\f31507\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} }{\*\ud{\fonttbl{\f0\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f4\fbidi \fnil\fcharset0\fprq2{\*\panose 02000500000000000000}Times;}{\f23\fbidi \fnil\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;} {\f24\fbidi \fnil\fcharset0\fprq2{\*\panose 02000503060000020004}Optima;}{\f25\fbidi \fnil\fcharset0\fprq2 Lucida Grande;}{\flomajor\f31500\fbidi \fnil\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;} {\fdbmajor\f31501\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fnil\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} {\fbimajor\f31503\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} {\fdbminor\f31505\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fnil\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;} {\fbiminor\f31507\fbidi \fnil\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}}}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0; \red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red240\green145\blue0;} {\*\defchp \f31506\fs24 }{\*\defpap \ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs24\alang1033 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 Normal;}{\s1\ql \li0\ri0\widctlpar\tqr\tx9026\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs24\alang1033 \ltrch\fcs0 \b\f24\fs32\cf17\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon18 \snext0 \sautoupd \slink17 heading 1;}{\s2\ql \li0\ri0\sb200\sl276\slmult1 \keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af31503\afs26\alang1033 \ltrch\fcs0 \b\fs26\cf17\lang1033\langfe1033\loch\f24\hich\af24\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \sautoupd \slink16 heading 2;}{\s3\ql \li0\ri0\sb200\sl276\slmult1\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af31503\afs24\alang1033 \ltrch\fcs0 \b\fs24\cf17\lang1033\langfe1033\loch\f24\hich\af24\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \sautoupd \slink15 heading 3;}{\*\cs10 \additive \ssemihidden Default Paragraph Font;}{\* \ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs24\alang1033 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden Normal Table;}{\*\cs15 \additive \rtlch\fcs1 \ab\af31503 \ltrch\fcs0 \b\cf17\loch\f24\hich\af24\dbch\af31501 \sbasedon10 \slink3 \slocked Heading 3 Char;}{\*\cs16 \additive \rtlch\fcs1 \ab\af31503 \ltrch\fcs0 \b\fs26\cf17\loch\f24\hich\af24\dbch\af31501 \sbasedon10 \slink2 \slocked Heading 2 Char;}{\*\cs17 \additive \rtlch\fcs1 \af0\alang1033 \ltrch\fcs0 \b\f24\fs32\cf17\lang1033\langfe0\langnp1033 \sbasedon10 \slink1 \slocked Heading 1 Char;}{\s18\ql \li0\ri0\widctlpar \tqc\tx4320\tqr\tx8640\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs24\alang1033 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext18 \slink19 \ssemihidden header;}{\* \cs19 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \sbasedon10 \slink18 \slocked \ssemihidden Header Char;}{\s20\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\lisb1\lisa1 \rtlch\fcs1 \af0\afs20\alang1033 \ltrch\fcs0 \f4\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext20 \styrsid6106910 Normal (Web);}{\*\cs21 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \sbasedon10 \styrsid6106910 apple-converted-space;}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}} {\*\rsidtbl \rsid6106910}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef0\mlMargin0\mrMargin0\mwrapRight0\mintLim0\mnaryLim0}{\info{\author Thomas Arts}{\operator Thomas Arts}{\creatim\yr2010\mo6\dy8\hr14\min55} {\revtim\yr2010\mo6\dy8\hr15\min8}{\version1}{\edmins10}{\nofpages1}{\nofwords0}{\nofchars0}{\*\company Quviq AB}{\nofcharsws0}{\vern33033}{\*\saveprevpict}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} \paperw11900\paperh16840\margl1800\margr1800\margt1440\margb1440\gutter0\ltrsect \ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen \expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace360\dgvspace360\dghorigin1800\dgvorigin1440\dghshow0\dgvshow0 \jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct \asianbrkrule\rsidroot6106910\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\afelev\utinl\hwelev\notvatxbx \nouicompat \fet0{\*\wgrffmtfilter 013f}\nofeaturethrottle1\ilfomacatclnup0\stylesortmethod0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectdefaultcl\sectrsid6106910\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3 \pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} {\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar \s20\ql \li280\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin280\itap0\pararsid6106910 \rtlch\fcs1 \af0\afs20\alang1033 \ltrch\fcs0 \f4\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs34\insrsid6106910 QuickCheck Mini}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs34\insrsid6106910 \par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs34\insrsid6106910 End User License Agreement}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs34\insrsid6106910 \par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par }\pard \ltrpar\s20\qj \li280\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin280\itap0\pararsid6106910 {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 This end user license agreement ("eula") is a legal agreement between you and quviq. Read it carefully before using the software. It provides a license to use the software and contains warranty information and li ability disclaimers. By installing, copying, or otherwise using the software, you agree to be bound by the terms of this agreement. If you do not agree to the terms of this agreement, do not install or use the software.}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par }\pard \ltrpar\s20\ql \fi-280\li560\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin560\itap0\pararsid6106910 {\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par \par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 1\~LICENSE GRANTS}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par \par }\pard \ltrpar\s20\ql \fi-300\li860\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin860\itap0\pararsid6106910 {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 \bullet \~Quviq}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \endash Quviq AB grants you the non-exclusive right to use }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 Quviq\rquote s}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 software program, QuickCheck Mini (the } {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 "SOFTWARE"}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 ). \par }\pard \ltrpar\s20\ql \fi-280\li560\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin560\itap0\pararsid6106910 {\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 2\~LICENSE RESTRICTIONS}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par \par }\pard \ltrpar\s20\ql \fi-300\li860\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin860\itap0\pararsid6106910 {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 \bullet \~Distribution.}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 You may freely distribute copies of the }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 SOFTWARE}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 to third parties or point them to the download location at ww w.quviq.com. \par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 \bullet Prohibition on Reverse Engineering, Decompilation, and Disassembly.}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 You may not reverse engineer, decompile, or disassemble the }{ \rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 SOFTWARE. \par \bullet \~Reservation of Rights. Quviq }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 retains all rights not expressly granted.}{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910\charrsid6106910 \par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par }\pard \ltrpar\s20\ql \fi-280\li560\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin560\itap0\pararsid6106910 {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 3\~DISCLAIMER OF WARRANTY AND LIABILITY}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par \par }\pard \ltrpar\s20\qj \fi-280\li560\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin560\itap0\pararsid6106910 {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 Limited warranty.}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 The }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 SOFTWARE}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 is provided "as is", without warranty of any kind. To the maximum extent permitted by applicable law, }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 Quviq}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 hereby disclaim all warranties, either expressed or implied, including, but not limited to, the implied w arranties of merchantability, fitness for a particular purpose, title, and non-infringement, with regard to the }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 SOFTWARE}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 , and the provision of or failure to provide support services. This limited warranty gives you specific legal rights. You may have others, which vary from state/jurisdiction to state/jurisdiction.}{\rtlch\fcs1 \af0 \ltrch\fcs0 \cs21\f25\fs22\insrsid6106910 \~}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par \par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 Limitation of liability.}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 To the maximum extent permitted by applicable law, in no event shall }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 Quviq}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 or its suppliers be liable for any special, incidental, indirect, or consequential damages wh atsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or any other pecuniary loss) arising out of the use of or inability to use the }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 SOFTWARE}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 or the provision of or failure to provide support services, even if }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 quviq}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 has been advised of the possibility of such damages. In any case, }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 Quviq\rquote s}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 entire liability under any provision of this eula shall be limited to the amount actually paid by you for the }{\rtlch\fcs1 \af0 \ltrch\fcs0 \b\f25\fs22\insrsid6106910 SOFTWARE}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 . \par }\pard \ltrpar\s20\ql \fi-280\li560\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin560\itap0\pararsid6106910 {\rtlch\fcs1 \af0 \ltrch\fcs0 \f25\fs22\insrsid6106910 \par }\pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs24\alang1033 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid6106910 \par }{\*\themedata 504b0304140006000800000021001b898e1bfe0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cf6ac3300cc6ef83bd83f17524 4e77186324e961ed6efb73e81e40384a6296c8c6564bfbf653d216c62885c12e0659d2f7fd2495cbfd38a81dc6e43c557a91175a2159df38ea2afdb979c91eb5 4a0cd4c0e0092b7dc0a497f5ed4db939044c4aba2955ba670e4fc624dbe30829f7014932ad8f23b084b13301ec177468ee8be2c1584f8cc4194f1aba2e57d8c2 7660b5decbf79144dab57a3ed64d5695861006678105d44c595397ef021e5d83ea0322bfc128558605017fbe8bfcbad68e9a5fc0996f5b67b1f1763b0a663e4b defdc5f31548868dffe37c123b035c5856c4215dd9d685094fe7c8a573de68ea5d48670733dfb6fe060000ffff0300504b030414000600080000002100a5d6a7 e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4fc7 060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b63095120f 88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462a1 a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f7468656d 652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b4b 0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b47 57e8d3f729e245eb2b260a0238fd010000ffff0300504b0304140006000800000021009d5c8bbe10070000871d0000160000007468656d652f7468656d652f74 68656d65312e786d6cec594f6f1b4514bf23f11d467b6f13274e9a4475aad8b109b469a3d82dea71bc3bf64e33bbb39a1927f10db547242444411ca8c48d0302 2ab51297f269024550a47e05deccecae77e271e39400153487d63bfb7b6fdefbbd3ff367af5e3b4e183a2442529e3682dae5c5009134e4114d878de076af7369 2d4052e134c28ca7a4118c890cae6dbefbce55bca162921004f2a9dcc08d20562adb585890210c63799967248577032e12ace0510c1722818f406fc216961617 5717124cd300a53801b5b706031a12d4d32a83cd42799bc163aaa41e0899e86ad5c49130d8e8a0a611722c5b4ca043cc1a01cc13f1a31e39560162582a78d108 16cd5fb0b07975016fe4424ccd90adc875cc5f2e970b44074b664e31ec9793d63af5f52bdba57e03606a1ad76eb75bed5aa9cf00701882a7d696aace7a67add6 2c745640f6e7b4eed6e2ca62ddc557f42f4fd9bcde6c3657d6735bac5203b23feb53f8b5c5d5fad6928337208b5f99c2d79b5badd6aa8337208b5f9dc277aeac afd65dbc01c58ca60753681dd04e27d75e42069ced78e16b005f5bcce113146443995d7a8a014fd5ac5c4bf03d2e3a00d04086154d911a67648043c8e21666b4 2fa89e006f105c796387423935a4e742321434538de0830c43454cf4bd7cf6ddcb674fd0c9fda727f77f3c79f0e0e4fe0f569123b583d36155eac5379ffef1e8 23f4fb93af5f3cfcdc8f9755fc2fdf7ffcf34f9ff981503e13739e7ff1f8d7a78f9f7ff9c96fdf3ef4c0b704ee57e13d9a10896e9223b4cf1370ccb0e25a4efa e27c12bd18d3aac4563a9438c57a168ffeb68a1df4cd3166d8836b1297c13b02da870ff8dee89e637037162395c7dbf1ec7a9c38c05dce59930b2f0bd7f55c15 9a7ba374e89f5c8caab87d8c0f7d73b770eac4b73dcaa06f529fca564c1c33f7184e151e929428a4dff103423c7cdda5d4e1759786824b3e50e82e454d4cbd94 f468dfc9a689d00e4d202e639f81106f879bdd3ba8c999cfeb6d72e822a12a30f318df23cca1f13d3c5238f1a9ece1845509bf8155ec33b23b166115d7960a22 3d248ca37644a4f4c9dc12e06f25e8d7a175f8c3becbc6898b148a1ef874dec09c5791dbfca015e324f361bb348dabd8f7e501a428467b5cf9e0bbdcad10fd0c 71c0e9cc70dfa1c409f7d9dde0361d3a264d1244bf19091d4b68d54e074e68faaa769c4037ceddb9b8760c0df0f9578f3c99f5a636e22d20c157093ba7daef2c dce9a6dbe222a26f7ecfddc6a3748f409a4f2f3c6f5beedb961bfce75beeac7a9eb7d14e7a2bb45dbdbdb19b62b3454e66ee900794b1ae1a3372439a4db28475 22eac0a09633a743529e98b2187ee67dddc10d0536324870f521557137c6196cb06b81563294b9eaa144199770b033c35edd1a0f9b74658f852bfac060fb81c4 6a97477678590f17e782528d596d86e6f0594cb4ac15cc3bd9f2955c29b8fd3a93d5b45173cf5633a69956e7cc56ba0c319c760d064b3661038260db022cafc2 f95c4f0d0713cc48a479b76b6f11161385bf2744b9d7d6911847c486c819aeb05933b12b52c85c10404a7942773e364bd680b4b38d3069313b7fe624b9503021 5997dda96a6269b5b6588a8e1ac1facad24a80429c3582011c49e1679241d0a4deb26136847b9d50099bb567d6a229d289c7ebfeacaac12dc38c8271ca381352 6d6319db189a5779a858aa67b2f62fadd475b25d8c0336515fc38ae53548917fcd0a08b51b5a3218905055835d19d1dcd9c7bc13f29122a21b4747a8cf46621f 43f88153ed4f4425dc2c9882d60f700da6d936afdcde9a779aeae593c1d971ccb218e7dd525fa3141567e1a6de4a1bcc53c53cf0cd6bbb71eefcaee88abf2857 aa69fc3f73452f0770d05f8e740442b8851518e97a6d045ca8984317ca621a7604acfba67740b6c0552abc06f2e12ed8fc2fc8a1fedfd69cd561ca1ace6b6a9f 0e91a0b09ca85810b2076dc964df19ca6af9d26355b25c91c9a88ab932b366f7c921613ddd0357750f0e500ca96eba49de060cee74feb9cf7905f5877a8f52ad 37a787944ba7ad817f7ae3628b199c3ab597d0f95bf05f9ae859fdacbc112fd6c8aa23fac56497542faac259fcd6d7f3a95ed3847916e0ca5a6b3bd694c74b2b 857110c5698f61b0dccf64705d83f43fb0fe511132fb61412fa83dbe0fbd15c17702cb1f82acbea4bb1a64906e90f6571ff63d76d026935665a9cd773e9ab562 b1bee08d6a39ef29b2b565f3c4fb9c64979b28773aa7162f92ec9c61876b3b36936a88ece91285a141710e3181315fa4aa1f8d78ff1e047a1baee747cc7e4692 193c993ac8f684c9ae3e8fc6f94f26ed826bb34e9f613492a5fb648068745c9c3f4a266c09d94f19c516d9a0b5984eb45270d97768700573bc16b5ab6529bc74 b67029616686965d0a9b1b329f02f89095376e7db403bc6db2d66b5d5c05532cfd2b94cd61bc9f32efc9675ecaec41f195817a0dcad4f1ab29cb9902f2a6130f 3e450a0c4793aee9bfb0e8d84c3729bbf927000000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d652f746865 6d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d363f2451ec ed0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e3198720e 274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d9850528a2c6 cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d00140006000800000021001b898e1bfe0000001c02000013000000000000000000000000000000 00005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b000000000000000000000000002f 0100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000000000180200007468656d 652f7468656d652f7468656d654d616e616765722e786d6c504b01022d00140006000800000021009d5c8bbe10070000871d0000160000000000000000000000 0000d50200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b0100002700000000000000 000000000000190a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000140b00000000} {\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d 617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} {\*\latentstyles\lsdstimax276\lsdlockeddef0\lsdsemihiddendef1\lsdunhideuseddef1\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal; \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority0 \lsdlocked0 heading 1;\lsdqformat1 \lsdpriority0 \lsdlocked0 heading 2;\lsdqformat1 \lsdpriority0 \lsdlocked0 heading 3;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9; \lsdpriority39 \lsdlocked0 toc 1;\lsdpriority39 \lsdlocked0 toc 2;\lsdpriority39 \lsdlocked0 toc 3;\lsdpriority39 \lsdlocked0 toc 4;\lsdpriority39 \lsdlocked0 toc 5;\lsdpriority39 \lsdlocked0 toc 6;\lsdpriority39 \lsdlocked0 toc 7; \lsdpriority39 \lsdlocked0 toc 8;\lsdpriority39 \lsdlocked0 toc 9;\lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdpriority1 \lsdlocked0 Default Paragraph Font; \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis; \lsdsemihidden0 \lsdunhideused0 \lsdpriority59 \lsdlocked0 Table Grid;\lsdunhideused0 \lsdlocked0 Placeholder Text;\lsdunhideused0 \lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading; \lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1; \lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2; \lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3; \lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List; \lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 1; \lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1; \lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdunhideused0 \lsdlocked0 Revision;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph; \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 1; \lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1; \lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 1; \lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 2; \lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2; \lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2; \lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 2; \lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 2; \lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 3; \lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 3; \lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3; \lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 3; \lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 4; \lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4; \lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; \lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4; \lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 4; \lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 5; \lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5; \lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5; \lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 5; \lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 5; \lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 6; \lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 6; \lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6; \lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; \lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis; \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference; \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdpriority37 \lsdlocked0 Bibliography; \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;}}{\*\datastore 010001000200000010000000584d4c2e5341585265616465722e3500000000000000000000060000 d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffff5200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000500ffffffffffffffffffffffffec69d9888b8b3d4c859eaf6cd158be0f0000000000000000000000007982 49bc0b07cb01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000100010000000000}} \ No newline at end of file diff --git a/lib/eqc/README b/lib/eqc/README new file mode 100644 index 0000000..d3d4d75 --- /dev/null +++ b/lib/eqc/README @@ -0,0 +1,27 @@ +QuickCheck Mini + +This is a fully functional stripped down version of Quviq QuickCheck. + +QuickCheck Mini is free of charge and the latest version can be +downloaded from Quviq's homepage. The Mini version of QuickCheck is +intended to support individuals and open source projects in obtaining +a better code quality without having to invest in the full version of +QuickCheck. + +QuickCheck Mini is installed by copying the complete directory +eqc-.... into the Erlang library directory or by pointing to it by +using the code:add_patha/1 function in the Erlang distribution. + +Examples are provided to illustrate how it works. + +You can subscribe to quickcheck-questions@quviq.com by sending +"Subscribe" in the subject line. This is a community email list around +the use of QuickCheck. + +Please note that although QuickCheck Mini does not require a licence +check with the Quviq licence server, it does check for a new version +each time it is started, and will notify you if one is available. No +other information is included in the request, and QuickCheck Mini will +start regardless of whether or not the version check succeeds, but +(because there is a short time-out) will start slightly more quickly +when it succeeds. diff --git a/lib/eqc/doc/edoc-info b/lib/eqc/doc/edoc-info new file mode 100644 index 0000000..063aae8 --- /dev/null +++ b/lib/eqc/doc/edoc-info @@ -0,0 +1,2 @@ +{packages,[]}. +{modules,[eqc,eqc_gen,eqc_symbolic]}. diff --git a/lib/eqc/doc/eqc.html b/lib/eqc/doc/eqc.html new file mode 100644 index 0000000..4b01957 --- /dev/null +++ b/lib/eqc/doc/eqc.html @@ -0,0 +1,385 @@ + + + +Module eqc + + + + +
+ +

Module eqc

+This module defines functions for writing and testing QuickCheck properties. + +

Version: 1.0.1

+ +

Description

This module defines functions for writing and testing QuickCheck properties. + Much of the interface is provided via macros (defined in eqc.hrl). + These are documented below: +

?FORALL(X,Gen,Prop)

+ Property that holds if Prop holds for all values X that + can be generated by Gen. For example, +
+ prop_reverse() ->
+   ?FORALL(Xs,list(int()),
+      lists:reverse(lists:reverse(Xs)) == Xs).
+ 
+ Generators are defined using the module eqc_gen. +

?IMPLIES(Pre,Prop)

+ Property that holds if Prop holds whenever the precondition + Pre is true. The precondition must be a boolean, but Prop + can be any QuickCheck property. An implication is tested by discarding test + cases which do not satisfy the precondition. This can make testing slow, + since many more test cases may need to be generated to find 100 which + satisfy the precondition. In the worst case, QuickCheck may not be able + to find enough test cases that do satisfy the precondition, in which case + the number actually found is reported. Some preconditions may also skew + the test data badly--for example, a precondition that a list is sorted + skews the test data towards short lists, since random longer lists are + extremely unlikely to be sorted just by chance. ?IMPLIES works + well for preconditions which are true with a high probability, but if the + precondition is unlikely to hold, then it is better to write a custom + generator which generates test cases where the precondition is true. +

?WHENFAIL(Action,Prop)

+ Property that is equivalent to Prop, but performs Action + (for its side effects) when Prop fails. This can be used to +print additional information when a test case fails.

+ + + + +

Data Types

+ +

counterexample()

+

abstract datatype: counterexample()

+

A counter-example to a QuickCheck property, which can be obtained + using counterexample/0 or counterexample/1, and used to repeat a test, + or test a different property in the same case. Counterexamples are represented by the values + bound by ?FORALL--for the counterexample to make sense independently, it's important that + these were generated without side-effects.

+ +

print_method()

+

print_method() = (list(term())) -> any()

+

A function for + printing statistics, which is passed a list of samples and is + expected to print statistical information about them. Print methods + are used by collect/3 and aggregate/3.

+ +

property()

+

abstract datatype: property()

+

QuickCheck properties, which can either be boolean + expressions, or constructed using the functions in this module. + QuickCheck properties are tested using quickcheck/1.

+ +

Function Index

+ + + + + + + + + + + + + + + + + + + + + + + + + +
aggregate/2A property logically equivalent to Prop, but which collects a list of values in + each test, and displays the distribution of these values once + testing is complete.
aggregate/3Like aggregate/2, but allows the user to specify how + the collected values should be printed.
backtrace/0Displays a stack backtrace from the last exception QuickCheck caught.
check/2Tests the property in the case given.
classify/3Property which is logically equivalent to Prop, but also + classifies test cases and displays the distribution of test case classes + when testing is complete.
collect/2Equivalent to aggregate([S], Prop). +
collect/3Equivalent to aggregate(PrintMethod, [S], Prop). +
counterexample/0Returns the last counter-example found.
counterexample/1Tests the property in the same way as quickcheck/1, but if + a test fails, then the failing test case is returned as a counterexample.
counterexamples/0Returns a list of the counterexamples found by the last call + of eqc:module, paired with the name of the property that failed.
current_counterexample/0Returns the most recent + counterexample found by QuickCheck.
equals/2A property which holds if X and Y are equal...
fails/1A property which succeeds when its argument fails.
measure/3Collects the values of X while testing Prop, and if all tests + pass, displays statistics such as the minimum, average, and maximum + values, identified by the name Name.
module/1Tests all the properties exported from a module, given the module name.
numtests/2Property which is logically equivalent to Prop, but is + tested N times rather than 100.
on_output/2Supplies an output function to be used instead of io:format + when QuickCheck generates output.
on_test/2Attaches a function to a property which is called every time a + test passes or fails.
quickcheck/1Tests the property in 100 random cases, printing a counter-example + if one is found.
recheck/1Tests the property with the same random number seed as + the last failing call of quickcheck/1.
start/0Equivalent to start(true). +
start/1Starts the QuickCheck server.
stop/0Stops the QuickCheck server.
version/0
with_title/1A printing method for collected data, which displays a title + before + the percentages of each value in the data.
+ +

Function Details

+ +

aggregate/2

+
+

aggregate(L::list(term()), Prop::property()) -> property()

+

A property logically equivalent to Prop, but which collects a list of values in + each test, and displays the distribution of these values once + testing is complete. A typical use would be to aggregate the list of command names generated + by eqc_statem:commands/1, in order to see how often each individual + command appeared in generated tests: +

aggregate(command_names(Cmds), ...) 
+

+ See also aggregate/3. +

+ +

aggregate/3

+
+

aggregate(PrintMethod::(list(term())) -> any(), L::list(term()), Prop::property()) -> property()

+

Like aggregate/2, but allows the user to specify how + the collected values should be printed. The PrintMethod parameter + is called with a sorted list of the collected data as an argument, + and is expected to print some statistics. A predefined printing + methods is provided to add a title to the statistics: +

aggregate(with_title(T),L,Prop)
. This is useful when a property contains + several calls to aggregate or collect.

+ +

backtrace/0

+
+

backtrace() -> ok

+

Displays a stack backtrace from the last exception QuickCheck caught. Note that + this is only possible if the exception is raised in the process in which the test + case starts. If a test case fails because of an exception in another, linked, + process, then no backtrace is available. Calls to functions in the implementation + of QuickCheck itself are not included in the backtrace. +

If you really need to see a backtrace from a linked process, then you can do so by + catching + the exception yourself in that process, using erlang:get_stacktrace() to obtain the + backtrace, and printing it yourself.

+ +

check/2

+
+

check(P::property(), Values::counterexample()) -> bool()

+

Tests the property in the case given. Counterexamples are generated by testing a + property using counterexample/1 or counterexample/0, and contain a list + of the values bound by ?FORALL. A property tested by check should begin with the same + sequence of ?FORALL s as the property from which the counterexample was generated, otherwise + the results will be unpredictable. In particular, there is no check that the values + in the counterexample could actually have been generated by the ?FORALL s in the property under + test. +

check/2 can be used without a QuickCheck licence, allowing anyone to run + tests that a licenced user has generated.

+ +

classify/3

+
+

classify(B::bool(), S::term(), Prop::property()) -> property()

+

Property which is logically equivalent to Prop, but also + classifies test cases and displays the distribution of test case classes + when testing is complete. If the boolean is true then the current test case is + labelled with the term S, + and, after testing is complete, QuickCheck prints out the percentage of + test cases carrying each label. This can be used to check that the space + of possible test cases has been covered reasonably well. For example, + classifying test cases according to the length of a list enables one to + see whether unreasonably many lists were short. Classifying + test cases is a way to discover skewed distributions, such as can arise + from using ?IMPLIES. It is good practice to check the distribution + of test data using classify or collect/2, at least while + properties are being developed. +

+ Each test case can be labelled with any number of labels: QuickCheck then + displays the percentage of each label in the generated + test data. +

+

+ Calls of classify or collect can be nested, in which case each call + generates its own table of distributions. +

+ +

collect/2

+
+

collect(S::term(), Prop::property()) -> property()

+

Equivalent to aggregate([S], Prop).

+ + +

collect/3

+
+

collect(PrintMethod::(list(term())) -> any(), S::term(), Prop::property()) -> property()

+

Equivalent to aggregate(PrintMethod, [S], Prop).

+ + +

counterexample/0

+
+

counterexample() -> undefined | counterexample()

+

Returns the last counter-example found. See counterexample/1.

+ +

counterexample/1

+
+

counterexample(P::property()) -> true | counterexample()

+

Tests the property in the same way as quickcheck/1, but if + a test fails, then the failing test case is returned as a counterexample.

+ +

counterexamples/0

+
+

counterexamples() -> list({atom(), counterexample()})

+

Returns a list of the counterexamples found by the last call + of eqc:module, paired with the name of the property that failed.

+ +

current_counterexample/0

+
+

current_counterexample() -> counterexample()

+

Returns the most recent + counterexample found by QuickCheck. This can be used while + QuickCheck is shrinking a failed test case to follow progress, or if + shrinking must be interrupted, to recover the last failed test case + that QuickCheck had found. The counterexample is fetched from a file + in the current directory.

+ +

equals/2

+
+

equals(X::any(), Y::any()) -> property()

+

A property which holds if X and Y are equal... and displays + their values when a test fails.

+ +

fails/1

+
+

fails(P::property()) -> property()

+

A property which succeeds when its argument fails. + Sometimes it is useful to write down properties which do not hold + (even though one might expect them to). This can help prevent misconceptions. + fails(P) is tested in the same way as P, but + fails only if P succeeds 100 times. Thus + fails(P) declares that QuickCheck should be able to find + a counter-example to property P.

+ +

measure/3

+
+

measure(Name::atom() | string(), X::number() | list(number()), Prop::property()) -> property()

+

Collects the values of X while testing Prop, and if all tests + pass, displays statistics such as the minimum, average, and maximum + values, identified by the name Name. X can also be a list of values, + in which case all of them are included in the measurements.

+ +

module/1

+
+

module(Mod::atom()) -> list(atom())

+

Tests all the properties exported from a module, given the module name. + Any function with arity zero whose name begins with "prop_" is treated as a + property. The result is a list of the names of the properties that + failed. See also module/2.

+ +

numtests/2

+
+

numtests(N::int(), Prop::property()) -> property()

+

Property which is logically equivalent to Prop, but is + tested N times rather than 100. If numtests appears more than once + in a property, then the outermost use takes precedence.

+ +

on_output/2

+
+

on_output(Fun::(string(), list(term())) -> any(), Prop::property()) -> property()

+

Supplies an output function to be used instead of io:format + when QuickCheck generates output. All output generated by + QuickCheck is passed to Fun, in the form of a format + string and a list of terms--the same arguments expected by + io:format. By supplying a function which does nothing, + QuickCheck can be run silently. By supplying a function which + writes to a file, all QuickCheck output can be saved. +

Note that output generated by user code is not passed to + this output function. For example, calls to io:format in the + property, or in the code under test, will generate output in the + shell as usual. This applies even to calls inside a + ?WHENFAIL. If you want to redirect such output also, then + you need to modify your own code appropriately.

+

The reason that Fun is passed a format string and + arguments, rather than an already formatted string, is to make it + easier to extract information from the output without parsing + it. However, there is no guarantee that different versions of + QuickCheck will use the same format strings and term lists--you use + this information at your own risk, in other words.

+ +

on_test/2

+
+

on_test(Fun::(counterexample(), bool()) -> any(), Prop::property()) -> property()

+

Attaches a function to a property which is called every time a + test passes or fails. The arguments are the test case (a list of + values), and a boolean indicating whether or not the test + passed. Tests which are skipped (because of an + ?IMPLIES(false,...)) are not included.

+ +

quickcheck/1

+
+

quickcheck(P::property()) -> bool()

+

Tests the property in 100 random cases, printing a counter-example + if one is found. Initially small test cases are generated, then the + size increases as testing progresses (see eqc_gen, ?SIZED, + eqc_gen:resize/2 for the way size affects test data generation). + The result is true if all tests succeeded (or if one failed, + and failure was expected). On success, quickcheck analyses + the distribution of test case labels. On failure, quickcheck + tries to simplify the counter-example found as far as possible (see + shrinking, described in eqc_gen).

+ +

recheck/1

+
+

recheck(Prop::property()) -> bool()

+

Tests the property with the same random number seed as + the last failing call of quickcheck/1. If the property is + the same as in that last call, then the same test case will be + generated. Note that recheck repeats the test and its + shrinking. This can be used to adjust the shrinking strategy in + the property, then reshrink the same counterexample, perhaps to a + better result. If you just + want to repeat the shrunk test, then use +

eqc:check(Prop,eqc:counterexample())
instead. +

Note: the type and behaviour of recheck changed in version 1.19.

+ +

start/0

+
+

start() -> any()

+

Equivalent to start(true).

+ + +

start/1

+
+

start(Force::bool()) -> pid()

+

Starts the QuickCheck server. If it is already running on this +node, nothing is done.

+ + Each user can run only one instance of the QuickCheck server at a + time. If the server is already running on another Erlang node, it + will be terminated automatically if Force is + true. If another instance is running, and Force is + false, then the new instance will not start.

+ +

stop/0

+
+

stop() -> any()

+

Stops the QuickCheck server. + QuickCheck properties are tested in the QuickCheck server process, which is + spawned automatically when quickcheck is first called. Usually there is no + need to stop the QuickCheck server explicitly, but if a need does arise + then this function can be used. For example, if the shell process crashes + and is restarted, then the QuickCheck server should be stopped and restarted + too, since otherwise the server will crash when it attempts to write to the + console.

+ +

version/0

+
+

version() -> any()

+
+ +

with_title/1

+
+

with_title(Title::atom() | string()) -> print_method()

+

A printing method for collected data, which displays a title + before + the percentages of each value in the data. It is intended to be + passed to collect/3 or aggregate/3.

+
+ + +

Generated by EDoc, Jun 13 2010, 13:15:30.

+ + diff --git a/lib/eqc/doc/eqc_gen.html b/lib/eqc/doc/eqc_gen.html new file mode 100644 index 0000000..adaa713 --- /dev/null +++ b/lib/eqc/doc/eqc_gen.html @@ -0,0 +1,342 @@ + + + +Module eqc_gen + + + + +
+ +

Module eqc_gen

+ + This module implements QuickCheck generators. + +

Version: 1.0.1

+ +

Description

+ This module implements QuickCheck generators. + QuickCheck generators are used to generate random test data for + QuickCheck properties. A generator specifies three things at the same + time: +
  • A set of values that can be generated,
  • +
  • A probability distribution on that set,
  • +
  • A way of shrinking generated values to similar, + smaller values---used after a test fails, to enable + QuickCheck to search for a similar, but simpler failing case.
  • +
+ QuickCheck permits constants to be used as generators for their own value, + and also permits tuples, records, and lists containing generators to be + used as generators for values of the same form. For example, +
 {int(),bool()} 
+ is a generator that generates random pairs of integers and booleans. +

+ Many of the functions in this module are usually used via macros, defined + in eqc.hrl. These macros are listed here. +

?LET(Pat,G1,G2)

+ Generates a value from G1, + binds it to Pat, then generates a value from G2 + (which may refer to the variables bound in Pat). +

The + result is shrunk by first shrinking the value generated by + G1 while the test still fails, then shrinking the value + generated by G2. It is thus better to write + ?LET({X,Y},{G1,G2},G3) than + ?LET(X,G1,?LET(Y,G2,G3)) (provided G2 does + not depend on X), since in the first case shrinking can + shrink G1 a bit, shrink G2, then shrink + G1 some more, while in the second case G1 + cannot be shrunk further once shrinking G2 has begun.

+

?SIZED(Size,G)

+ Binds the variable Size to the current size parameter for + generation. G may use Size in any way to control the + size of generated data. However, as Size increases, + the set of possible values that G can generate should also + increase. Size is always a natural number, and increases during + QuickCheck testing from a small value up to about 40. See also + resize/2 and pick/2. +

?SUCHTHAT(X,G,P)

+ Generates values X from G such that the condition P is true. + Should only be used if the probability that P holds is reasonably high for values + generated by G--otherwise generation may be slow, and the + distribution of generated values may be skewed. For example, +
?SUCHTHAT(Xs,list(int()),lists:sort(Xs)==Xs)
+ generates predominantly very short lists, since the probability that a random longer list + will just happen to be sorted is very low. If no value is found within 100 attempts, + then ?SUCHTHAT exits. + + +

?LETSHRINK(Pat,G1,G2)

+ This behaves in the same way as ?LET(Pat,G1,G2), except + that G1 must generate a list of values, and each one of these + values is added as a possible shrinking of the result. This is intended for + use in generating tree-like structures. For example, +
+ ?LETSHRINK([L,R],[tree(),tree()],{branch,L,R})
+ generates a tree node {branch,L,R}, which can shrink to either + L or R. +

?LAZY(G)

+ A generator equivalent to its argument, but which is always cheap to construct. To be used, + for example, in recursive generators to avoid building a huge generator, only a small part + of which will be used. +

+

Data Types

+ +

box()

+

abstract datatype: box(A)

+

Boxes are not supported in this version of QuickCheck.

+ +

gen()

+

abstract datatype: gen(A)

+

A QuickCheck generator for values of type A. + QuickCheck generators are first-class + values, and can be used repeatedly to generate many different values.

+ +

proplist()

+

proplist() = list({atom(), term()})

+

A property list associating values with names. + See the standard module proplists.

+ +

Function Index

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
binary/0Generates a binary of random size.
binary/1Generates a binary of a given size in bytes.
bitstring/0Generates a list of bits in a bitstring.
bitstring/1Generates a bitstring of a given size in bits.
bool/0Generates a random boolean.
char/0Generates a random character.
choose/2Generates a number in the range M to N.
default/2Adds a default value to a generator, to be chosen half the time.
elements/1Generates an element of the list argument.
eval/1Evaluates terms of the form {call,Module,Function,Args} anywhere in its + argument, replacing them by the result of the corresponding function call.
eval/2Like eval/1, but also replaces symbolic variables, that is, + terms of the form {var,V}, by their corresponding values in the + property list.
frequency/1Makes a weighted choice between the generators in its argument, such that the + probability of choosing each generator is proportional to the weight paired with it.
function0/1Generates a function of no arguments with result generated by G.
function1/1Generates a function of one argument with result generated by G.
int/0Generates a small integer (with absolute value bounded by the generation size).
is_generator/1Returns true if the argument is a QuickCheck generator.
largeint/0Generates an integer from a large range.
list/1Generates a list of elements generated by its argument.
nat/0Generates a small natural number (bounded by the generation size).
non_empty/1Make sure that the generated value is not empty.
noshrink/1Generates the same values as G, but these values are never + shrunk.
oneof/1Generates a value using a randomly chosen element of the list of generators.
orderedlist/1Generates an ordered list of elements generated by G.
real/0Generates a real number.
resize/2Binds the generation size parameter to Size within G.
return/1Constructs a generator that always generates the value + X.
sample/1Prints 11 values randomly generated by G, for sizes ranging + from 10 to 20.
sampleshrink/1Prints a value generated by G, followed by one way of shrinking it.
shuffle/1Shuffles a list and shrinks to the unshuffled list.
vector/2Generates a list of the given length, with elements generated by G.
+ +

Function Details

+ +

binary/0

+
+

binary() -> gen(binary())

+

Generates a binary of random size. The binary shrinks both in + size as well as in content. If you consider the + binary as a representation of a number, then each shrinking step + will result in a smaller-or-equal number.

+ +

binary/1

+
+

binary(NrBytes::int()) -> gen(binary())

+

Generates a binary of a given size in bytes. When shrinking, + the size is unchanged, but content shrinks like binary/0.

+ +

bitstring/0

+
+

bitstring() -> gen(bitstring())

+

Generates a list of bits in a bitstring. For Erlang release R12B and + later. + The bitstring shrinks both in + size as well as in content. If you consider the + bitstring as a representation of a number, then each shrinking step + will result in a smaller-or-equal number.

+ +

bitstring/1

+
+

bitstring(NrBits::int()) -> gen(bitstring())

+

Generates a bitstring of a given size in bits. For Erlang + release R12B and later. When shrinking, + the size is unchanged, but content shrinks like bitstring/0.

+ +

bool/0

+
+

bool() -> gen(bool())

+

Generates a random boolean. Shrinks to false.

+ +

char/0

+
+

char() -> gen(char())

+

Generates a random character. Shrinks to a, b or c.

+ +

choose/2

+
+

choose(M, N::integer()) -> gen(integer())

+

Generates a number in the range M to N. + The result shrinks towards smaller absolute values.

+ +

default/2

+
+

default(Default::A, G::gen(A)) -> gen(A)

+

Adds a default value to a generator, to be chosen half the time. Any + other value shrinks to the default.

+ +

elements/1

+
+

elements(Xs::list(A)) -> gen(A)

+

Generates an element of the list argument. Shrinking chooses an earlier element.

+ +

eval/1

+
+

eval(Term::term()) -> term()

+

Evaluates terms of the form {call,Module,Function,Args} anywhere in its + argument, replacing them by the result of the corresponding function call. + This is useful when, for example, test data is of an abstract datatype, and + we want to know how it was generated, rather than its representation--it is + much clearer to see that a test failed for sets:new() (that is + {call,sets,new,[]}), for example, + than for its representation. + We write ?FORALL(X,TGen,...eval(X)...), where TGen + generates terms containing calls, so that test cases are displayed in this + form, but the actual test data is the result of evaluating the calls.

+ +

eval/2

+
+

eval(Env::proplist(), T::term()) -> term()

+

Like eval/1, but also replaces symbolic variables, that is, + terms of the form {var,V}, by their corresponding values in the + property list. This should be a list of pairs of atoms and values. For example, + eval([{x,3}],{var,x}) evaluates to 3.

+ +

frequency/1

+
+

frequency(FGs::list({integer(), gen(A)})) -> gen(A)

+

Makes a weighted choice between the generators in its argument, such that the + probability of choosing each generator is proportional to the weight paired with it. + The + weights should be non-negative integers and sum to a positive value. A generator + with a weight of zero will not be chosen.

+ +

function0/1

+
+

function0(G::gen(A)) -> gen(() -> A)

+

Generates a function of no arguments with result generated by G.

+ +

function1/1

+
+

function1(G::gen(A)) -> gen((term()) -> A)

+

Generates a function of one argument with result generated by G. + The generated function is pure--will always return the same result for the same argument-- + and the result depends randomly on the argument.

+ +

int/0

+
+

int() -> gen(integer())

+

Generates a small integer (with absolute value bounded by the generation size).

+ +

is_generator/1

+
+

is_generator(X::any()) -> bool()

+

Returns true if the argument is a QuickCheck generator.

+ +

largeint/0

+
+

largeint() -> any()

+

Generates an integer from a large range.

+ +

list/1

+
+

list(G::gen(A)) -> gen(list(A))

+

Generates a list of elements generated by its argument. Shrinking drops elements + from the list. The length of the list varies up to one third of the generation size parameter.

+ +

nat/0

+
+

nat() -> gen(integer())

+

Generates a small natural number (bounded by the generation size).

+ +

non_empty/1

+
+

non_empty(G::gen(A)) -> gen(A)

+

Make sure that the generated value is not empty. + For example when creating a list of integers, but the list should always + contain at least one element non_empty(list(int())).

+ +

noshrink/1

+
+

noshrink(G::gen(A)) -> gen(A)

+

Generates the same values as G, but these values are never + shrunk.

+ +

oneof/1

+
+

oneof(Gs::list(gen(A))) -> gen(A)

+

Generates a value using a randomly chosen element of the list of generators.

+ +

orderedlist/1

+
+

orderedlist(G::gen(A)) -> gen(list(A))

+

Generates an ordered list of elements generated by G.

+ +

real/0

+
+

real() -> gen(float())

+

Generates a real number.

+ +

resize/2

+
+

resize(Size::integer(), G::gen(A)) -> gen(A)

+

Binds the generation size parameter to Size within G. + Size should never be negative.

+ +

return/1

+
+

return(X::A) -> gen(A)

+

Constructs a generator that always generates the value + X. Most values can also be used as generators for + themselves, making return unnecessary, but + return(X) may be more efficient than using X as a + generator, since when return(X) is used then QuickCheck + does not traverse X searching for values to be intepreted + specially.

+ +

sample/1

+
+

sample(G::gen(A)) -> ok

+

Prints 11 values randomly generated by G, for sizes ranging + from 10 to 20.

+ +

sampleshrink/1

+
+

sampleshrink(G::gen(A)) -> ok

+

Prints a value generated by G, followed by one way of shrinking it. + Each following line displays a list of values that the first value on the + previous line can be shrunk to in one step. Thus the output traces the leftmost path + through the shrinking tree.

+ +

shuffle/1

+
+

shuffle(List::list(A)) -> gen(list(A))

+

Shuffles a list and shrinks to the unshuffled list.

+ +

vector/2

+
+

vector(K::integer(), G::gen(A)) -> gen(list(A))

+

Generates a list of the given length, with elements generated by G.

+
+ + +

Generated by EDoc, Jun 13 2010, 13:15:30.

+ + diff --git a/lib/eqc/doc/eqc_symbolic.html b/lib/eqc/doc/eqc_symbolic.html new file mode 100644 index 0000000..d53a39c --- /dev/null +++ b/lib/eqc/doc/eqc_symbolic.html @@ -0,0 +1,156 @@ + + + +Module eqc_symbolic + + + + +
+ +

Module eqc_symbolic

+ +This module implements QuickCheck generators and utility functions for +symbolic calls. + +

Version: 1.0.1

+ +

Description

+This module implements QuickCheck generators and utility functions for +symbolic calls.

+ +

In test case generation it is often an advantage to postpone calling +functions in the subject under test. In a test one is interested in +the actual function that is called as well as its evaluated result. +If one would evaluate the result already at generation time, then the actual +call is not visible in the QuickCheck counter example shown in a +failing test.

+ +

For example, when testing a data structure like the OTP library sets.erl, +one may need more information than just the value to detect what goes +wrong with the following property:

+ +
+ prop_sets() ->
+  ?FORALL({S1,S2},{set(),set()},
+          begin
+              L1 = sets:to_list(S1),
+              L2 = sets:to_list(S2),
+              sets:intersection(S1,S2) ==
+                  sets:from_list(L1--(L1--L2))
+          end).
+ 
+ + which will fail with for example the following counter example: +
+  Failed! After 132 tests.
+  Shrinking.......(7 times)
+  {{set,2,16,16,8,80,48,
+        {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
+        {{[],[],[],[],[],[],[],[],[],[],[],[-15,33],[],[],[],[]}}},
+   {set,3,16,16,8,80,48,
+        {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
+        {{[0],[],[],[],[],[],[],[],[],[],[],[33,-15],[],[],[],[]}}}}
+  false
+ 
+ + We would really need to understand the internal representation of sets in order to understand which + sets we have generated and even if we know that, we have no clue which operations were used to + create those sets. + This is were symbolic representations help a lot. We would create a recursive generator that + creates symbolic sets and use the following property instead: +
+  prop_sets() ->
+    ?FORALL({SymbS1,SymbS2},{set(),set()},
+            begin
+              S1 = eval(SymbS1),
+              S2 = eval(SymbS2),
+              L1 = sets:to_list(S1),
+              L2 = sets:to_list(S2),
+              sets:intersection(S1,S2) ==
+                 sets:from_list(L1--(L1--L2))
+            end).
+ 
+  This would then result in a more readable error message:
+  Shrinking..........(10 times)
+  {{call,sets,from_list,[[6,-10]]},{call,sets,from_list,[[0,-10,6]]}}
+  false
+ 
+ + Symbolic representation of function calls provides us with +
    +
  • Better readable counter examples
  • +
  • No need to break abstraction by using clean interface to code + under test
  • +
  • Better possibilities for shrinking when defining generators
  • +
+

Function Index

+ + + + + + +
defined/1Checks whether a term can be evaluated without raising an exception.
eval/1Evaluates terms of the form {call,Module,Function,Args} anywhere in its + argument, replacing them by the result of the corresponding function call.
eval/2Like eval/1, but also replaces symbolic variables, that is, + terms of the form {var,V}, by their corresponding values in the + property list.
pretty_print/1Pretty printing of symbolic terms.
pretty_print/2Pretty printing of symbolic terms within given environment.
well_defined/1Generates a well defined symbolic value.
+ +

Function Details

+ +

defined/1

+
+

defined(E::term()) -> bool()

+

Checks whether a term can be evaluated without raising an exception. + Some symbolic terms may raise an exception when evaluating, e.g., division by zero would + raise an exception, thus eval({call,erlang,'div',[1,0]}) raises an exception as well.

+ +

eval/1

+
+

eval(Term::term()) -> term()

+

Evaluates terms of the form {call,Module,Function,Args} anywhere in its + argument, replacing them by the result of the corresponding function call. + This is useful when, for example, test data is of an abstract datatype, and + we want to know how it was generated, rather than its representation--it is + much clearer to see that a test failed for sets:new() (that is + {call,sets,new,[]}), for example, + than for its representation. + We write ?FORALL(X,TGen,...eval(X)...), where TGen + generates terms containing calls, so that test cases are displayed in this + form, but the actual test data is the result of evaluating the calls.

+ +

eval/2

+
+

eval(Env::proplist(), T::term()) -> term()

+

Like eval/1, but also replaces symbolic variables, that is, + terms of the form {var,V}, by their corresponding values in the + property list. This should be a list of pairs of atoms and values. For example, + eval([{x,3}],{var,x}) evaluates to 3.

+ +

pretty_print/1

+
+

pretty_print(Symb::term()) -> string()

+

Pretty printing of symbolic terms. + A symbolic value like {call,sets,union,[{call,sets,new,[]},{call,sets,from_list,[[1,2]]}]} + is transformed to the string \"sets:union(sets:new(),from_list([1,2]))\".

+ +

pretty_print/2

+
+

pretty_print(Env::proplist(), Symb::term()) -> string()

+

Pretty printing of symbolic terms within given environment. + Like pretty_print/1, but also replaces symbolic variables, that is, + terms of the form {var,V}, by their corresponding values in the + property list. This should be a list of pairs of atoms and values. For example, + eval([{x,3}],{var,x}) is pretty printed to \"3\".

+ +

well_defined/1

+
+

well_defined(G::gen(A)) -> A

+

Generates a well defined symbolic value. + A value is well defined if evaluating it does not raise an exception.

+
+ + +

Generated by EDoc, Jun 13 2010, 13:15:30.

+ + diff --git a/lib/eqc/doc/erlang.png b/lib/eqc/doc/erlang.png new file mode 100644 index 0000000..987a618 Binary files /dev/null and b/lib/eqc/doc/erlang.png differ diff --git a/lib/eqc/doc/index.html b/lib/eqc/doc/index.html new file mode 100644 index 0000000..047b9dc --- /dev/null +++ b/lib/eqc/doc/index.html @@ -0,0 +1,17 @@ + + + +Overview + + + + + + +<h2>This page uses frames</h2> +<p>Your browser does not accept frames. +<br>You should go to the <a href="overview-summary.html">non-frame version</a> instead. +</p> + + + \ No newline at end of file diff --git a/lib/eqc/doc/modules-frame.html b/lib/eqc/doc/modules-frame.html new file mode 100644 index 0000000..f6cea3f --- /dev/null +++ b/lib/eqc/doc/modules-frame.html @@ -0,0 +1,14 @@ + + + +Overview + + + +

Modules

+ + + +
eqc
eqc_gen
eqc_symbolic
+ + \ No newline at end of file diff --git a/lib/eqc/doc/overview-summary.html b/lib/eqc/doc/overview-summary.html new file mode 100644 index 0000000..4d02c2e --- /dev/null +++ b/lib/eqc/doc/overview-summary.html @@ -0,0 +1,53 @@ + + + +QuickCheck Mini + + + + +

QuickCheck Mini

+

Copyright Quviq AB, 2006-2010.

+

Version: 1.0.1

+

+QuickCheck is a specification-based testing tool for Erlang. QuickCheck Mini is +a powerful, but restricted version of QuickCheck. It is released free of charge for +anyone interested in learning more about QuickCheck as well as for open source +developers that want to ship their code with simple QuickCheck properties.

+ +

In order to learn more about QuickCheck, please visit http://www.quviq.com/. +There is also a mailing list discussing QuickCheck issues, which you can subscribe to by sending an email to quickcheck-questions@quviq.com with subject "Subscribe".

+ + +

QuickCheck

+ +Programs +are tested by writing properties in the source code, such as +
+prop_reverse() ->
+  ?FORALL(Xs,list(int()),
+    lists:reverse(lists:reverse(Xs)) == Xs).
+
+Properties are tested by calling +
+eqc:quickcheck(prop_reverse())
+

+which generates 100 random test cases and checks that the property +returns true in every case.

+ +

+Functions for writing properties are found in module eqc, +while functions for writing test data generators (such as +list(int()) above) are found in module eqc_gen. Parts +of the interface are provided via macros, which are defined in +eqc.hrl--this file should be included in every module which +uses QuickCheck. This header file also imports much of the QuickCheck +API, so it can be used without explicit module names. +

+ + +
+ +

Generated by EDoc, Jun 13 2010, 13:15:30.

+ + diff --git a/lib/eqc/doc/overview.edoc b/lib/eqc/doc/overview.edoc new file mode 100644 index 0000000..109e9c7 --- /dev/null +++ b/lib/eqc/doc/overview.edoc @@ -0,0 +1,39 @@ +@title QuickCheck Mini +@version {@version} +@copyright Quviq AB, 2006-2010. +@doc +QuickCheck is a specification-based testing tool for Erlang. QuickCheck Mini is +a powerful, but restricted version of QuickCheck. It is released free of charge for +anyone interested in learning more about QuickCheck as well as for open source +developers that want to ship their code with simple QuickCheck properties. + +In order to learn more about QuickCheck, please visit http://www.quviq.com/. +There is also a mailing list discussing QuickCheck issues, which you can subscribe to by sending an email to quickcheck-questions@quviq.com with subject "Subscribe". + + +

QuickCheck

+ +Programs +are tested by writing properties in the source code, such as +
+prop_reverse() ->
+  ?FORALL(Xs,list(int()),
+    lists:reverse(lists:reverse(Xs)) == Xs).
+
+Properties are tested by calling +
+eqc:quickcheck(prop_reverse())
+
+which generates 100 random test cases and checks that the property +returns true in every case. + +

+Functions for writing properties are found in module {@link eqc}, +while functions for writing test data generators (such as +list(int()) above) are found in module {@link eqc_gen}. Parts +of the interface are provided via macros, which are defined in +eqc.hrl--this file should be included in every module which +uses QuickCheck. This header file also imports much of the QuickCheck +API, so it can be used without explicit module names. +

+ diff --git a/lib/eqc/doc/packages-frame.html b/lib/eqc/doc/packages-frame.html new file mode 100644 index 0000000..4193cbe --- /dev/null +++ b/lib/eqc/doc/packages-frame.html @@ -0,0 +1,11 @@ + + + +Overview + + + +

Packages

+
+ + \ No newline at end of file diff --git a/lib/eqc/doc/stylesheet.css b/lib/eqc/doc/stylesheet.css new file mode 100644 index 0000000..e426a90 --- /dev/null +++ b/lib/eqc/doc/stylesheet.css @@ -0,0 +1,55 @@ +/* standard EDoc style sheet */ +body { + font-family: Verdana, Arial, Helvetica, sans-serif; + margin-left: .25in; + margin-right: .2in; + margin-top: 0.2in; + margin-bottom: 0.2in; + color: #000000; + background-color: #ffffff; +} +h1,h2 { + margin-left: -0.2in; +} +div.navbar { + background-color: #add8e6; + padding: 0.2em; +} +h2.indextitle { + padding: 0.4em; + background-color: #add8e6; +} +h3.function,h3.typedecl { + background-color: #add8e6; + padding-left: 1em; +} +div.spec { + margin-left: 2em; + background-color: #eeeeee; +} +a.module,a.package { + text-decoration:none +} +a.module:hover,a.package:hover { + background-color: #eeeeee; +} +ul.definitions { + list-style-type: none; +} +ul.index { + list-style-type: none; + background-color: #eeeeee; +} + +/* + * Minor style tweaks + */ +ul { + list-style-type: square; +} +table { + border-collapse: collapse; +} +td { + padding: 3 +} diff --git a/lib/eqc/ebin/eqc.app b/lib/eqc/ebin/eqc.app new file mode 100644 index 0000000..a1c5aa5 --- /dev/null +++ b/lib/eqc/ebin/eqc.app @@ -0,0 +1,7 @@ +{application,eqc, + [{mod,{eqc,[]}}, + {description,"Quviq QuickCheck Mini"}, + {vsn,"1.0.1"}, + {modules,[eqc,eqc_gen,eqc_symbolic,eqc_warn]}, + {applications,[kernel,stdlib,inets]}]}. + diff --git a/lib/eqc/examples/generators_eqc.erl b/lib/eqc/examples/generators_eqc.erl new file mode 100644 index 0000000..07659b2 --- /dev/null +++ b/lib/eqc/examples/generators_eqc.erl @@ -0,0 +1,227 @@ +%% This file tests that generators produce the right kind of data. It +%% also illustrates how to write a QuickSpec specification of a +%% datatype (such as the generator datatype). + +-module(generators_eqc). +-include_lib("eqc/include/eqc.hrl"). + +-compile(export_all). + +%% Generate most combinations of generator functions, as a symbolic +%% generator--for example: +%% {call,oneof,[{{call,bitstring,14},{call,elements,[15]}}]} + +%% We control the size of the symbolic generator, by limiting the +%% nesting depth. + +generator() -> + ?SIZED(N,generator(N)). + +generator(0) -> + % base case: no nested generators. + oneof([constant(), + binary,{call,binary,nat()}, + bitstring,{call,bitstring,nat()}, + bool,char, + ?SUCHTHAT({call,choose,Lo,Hi}, + {call,choose,int(),int()}, + Lo= + % recursive case: reduce the size of nested compound generators. + N1 = N div 4, + ?LAZY(oneof([generator(0),compound_generator(N1)])). + +constant() -> + oneof([int(),atom()]). + +atom() -> + elements([a,b,c,d]). + +term() -> + ?LET(G,generator(),generated_by(G)). + +compound_generator(N) -> + Smaller = generator(N), + oneof([?LETSHRINK([Sm],[Smaller],?LET(Def,term(),{call,default,Sm,Def})), + ?LETSHRINK(Gs,non_empty_list(Smaller), + {call,frequency,[{positive(),G} || G <- Gs]}), + ?LETSHRINK([Sm],[Smaller],{call,list,Sm}), + ?LETSHRINK([Sm],[Smaller],{call,orderedlist,Sm}), + ?LETSHRINK([Sm],[?SUCHTHAT(Sm,Smaller,not often_empty(Sm))], + {call,non_empty,Sm}), + ?LETSHRINK([Sm],[Smaller],{call,noshrink,Sm}), + ?LETSHRINK(Gs,non_empty_list(Smaller), + {call,oneof,Gs}), + ?LETSHRINK([Sm],[Smaller],{call,resize,choose(0,N),Sm}), + ?LETSHRINK(L,list(constant()),{call,shuffle,L}), + ?LETSHRINK(Gs,short_list(Smaller), + list_to_tuple(Gs)), + ?LETSHRINK([Sm],[Smaller],{call,vector,choose(0,4),Sm})]). + +%% To keep the size of generators under control, it is not enough to +%% restrict nesting depth. We also want lists of arguments to oneof, +%% frequency etc to be reasonably short. + +short_list(G) -> + ?SIZED(Size, + resize(Size div 3 + 1, + list(resize(Size,G)))). + +positive() -> + ?LET(N,nat(),N+1). + +non_empty_list(G) -> + non_empty(short_list(G)). + +%% When we generate {call,non_empty,G}, we need to know that G is +%% reasonably likely to produce a non-empty value... otherwise we may +%% loop when we try to use this generator! + +often_empty([]) -> + true; +often_empty(<<>>) -> + true; +often_empty({call,vector,N,G}) -> + N==0; +often_empty({call,binary,N}) -> + N==0; +often_empty({call,bitstring,N}) -> + N==0; +often_empty({call,noshrink,G}) -> + often_empty(G); +often_empty({call,oneof,Gs}) -> + % conservative + lists:any(fun often_empty/1,Gs); +often_empty({call,frequency,WGs}) -> + % conservative + lists:any(fun often_empty/1, [G || {_,G} <- WGs]); +often_empty({call,default,G,V}) -> + often_empty(G) andalso often_empty(V); +often_empty({call,resize,N,G}) -> + N<4 orelse often_empty(G); +often_empty({call,shuffle,L}) -> + L==[]; +often_empty(_) -> + false. + +%% The values generated by a symbolic generator. + +generated_by(A) when is_atom(A) -> + case erlang:function_exported(eqc_gen,A,0) of + true -> + eqc_gen:A(); + false -> + A + end; +generated_by(T) when is_tuple(T), size(T)>0 -> + case tuple_to_list(T) of + [call,F|Args] -> + erlang:apply(eqc_gen,F,[generated_by(G) || G <- Args]); + Gs -> + list_to_tuple([generated_by(G) || G <- Gs]) + end; +generated_by([H|T]) -> + [generated_by(H)|generated_by(T)]; +generated_by(X) -> + X. + +%% Check that a generated value corresponds to its generator. + +is(binary,B) -> + is_binary(B); +is({call,binary,N},B) -> + is_binary(B) andalso size(B)==N; +is(bitstring,B) -> + is_bitstring(B); +is({call,bitstring,N},B) -> + is_bitstring(B) andalso size(B) == N div 8; +is(bool,B) -> + B==true orelse B==false; +is(char,C) -> + is_integer(C) andalso 0= + is_integer(N) andalso Lo =< N andalso N =< Hi; +is({call,default,G,D},X) -> + is(G,X) orelse X==D; +is({call,elements,L},X) -> + lists:member(X,L); +is({call,frequency,WGs},X) -> + lists:any(fun({_,G})->is(G,X) end,WGs); +is(int,N) -> + is_integer(N) andalso abs(N) =< 100; +is(largeint,N) -> + is_integer(N); +is(nat,N) -> + is_integer(N) andalso N>=0; +is(real,N) -> + is_float(N); +is({call,list,G},L) -> + is_list(L) andalso + lists:all(fun(X)->is(G,X) end,L); +is({call,orderedlist,G},L) -> + is_list(L) andalso + lists:all(fun(X)->is(G,X) end,L) andalso + lists:sort(L) == L; +is({call,non_empty,G},X) -> + is(G,X) andalso X/=[] andalso X/=<<>>; +is({call,noshrink,G},X) -> + is(G,X); +is({call,oneof,Gs},X) -> + lists:any(fun(G) -> is(G,X) end,Gs); +is({call,resize,_,G},X) -> + is(G,X); +is({call,shuffle,L},X) -> + is_list(X) andalso lists:sort(X) == lists:sort(L); +is({call,vector,N,G},V) -> + is_list(V) andalso length(V)==N andalso + lists:all(fun(X)->is(G,X) end,V); +is(GT,T) when is_tuple(GT) -> + is_tuple(T) andalso size(T)==size(GT) andalso + lists:all(fun({G,X})->is(G,X) end, + lists:zip(tuple_to_list(GT), + tuple_to_list(T))); +is(Const,X) when is_atom(Const); is_integer(Const) -> + Const == X. + +%% The properties. + +%% Generate symbolic generators, and report on the distribution of +%% generator functions used. +prop_generator() -> + ?FORALL(G,generator(), + aggregate(generator_types(G),true)). + +generator_types(G) when is_tuple(G) -> + case tuple_to_list(G) of + [call,frequency,WArgs] -> + [T || {_,G1} <- WArgs, T <- generator_types(G1)]; + [call,F|Args] -> + [F|[T || A <- Args, + T <- generator_types(A)]]; + L -> + [T || X <- L, + T <- generator_types(X)] + end; +generator_types(N) when is_integer(N) -> + [integer_constant]; +generator_types(X) when is_float(X) -> + [float_constant]; +generator_types(B) when is_binary(B) -> + [binary]; +generator_types(B) when is_bitstring(B) -> + [bitstring]; +generator_types(L) when is_list(L) -> + [list|[T || X <- L, T <- generator_types(X)]]; +generator_types(X) -> + [X]. + +%% For each kind of generator, use it to generate a value, and check +%% that the value matches the generator. This tests the generators +%% (and our generator-generators!) pretty thoroughly. + +prop_correct_types() -> + ?FORALL(G,generator(), + ?FORALL(X,generated_by(G),is(G,X))). + diff --git a/lib/eqc/examples/ip_checksum.erl b/lib/eqc/examples/ip_checksum.erl new file mode 100644 index 0000000..ab76940 --- /dev/null +++ b/lib/eqc/examples/ip_checksum.erl @@ -0,0 +1,40 @@ +%%% File : ip_checksum.erl +%%% Author : Ulf Norell , +%%% Thomas Arts +%%% Description : Implementation of IP checksums. +%%% Created : 7 Jun 2010 by Ulf Norell +-module(ip_checksum). + +-export([checksum/1, checksum/2, sum/2, pad/2, add/2, negate/1]). + +checksum(Bin) -> + checksum(Bin, 16). + +checksum(Bin, N) -> + negate(sum(pad(Bin, N), N)). + +% Sum a binary of N bit words in ones complement representation. +sum(Bin, N) -> + lists:foldl(fun(A, B) -> add(A, B) end, <<0:N>>, + [ <> || <> <= Bin ]). + +% Add two numbers in ones complement representation. +add(A, B) -> + N = bit_size(A), + <> = A, + <> = B, + Carry = (X + Y) div (1 bsl N), + <<(X + Y + Carry):N>>. + +%% invert all bits... as simple as that. +negate(BitString) -> + << <<(1-Bit):1>> || <> <= BitString >>. + +pad(Binary, Bits) -> + PaddingLength = + case bit_size(Binary) rem Bits of + 0 -> 0; + N -> Bits - N + end, + <>. + diff --git a/lib/eqc/examples/ip_checksum_eqc.erl b/lib/eqc/examples/ip_checksum_eqc.erl new file mode 100644 index 0000000..a600c02 --- /dev/null +++ b/lib/eqc/examples/ip_checksum_eqc.erl @@ -0,0 +1,218 @@ +%%% File : ip_checksum_eqc.erl +%%% Author : Ulf Norell , +%%% Thomas Arts +%%% Description : QuickCheck properties for ip_checksum.erl +%%% Created : 7 Jun 2010 by Ulf Norell +-module(ip_checksum_eqc). + +-include_lib("eqc/include/eqc.hrl"). + +-compile(export_all). + +% == Testing IP checksum implementations == + +% In RFC 1071 efficient algorithms are discussed for computing the internet +% checksum, also known as IP checksum. Whenever you implement efficient +% algorithms, an error may sneak through. + +% This article is meant to be used as test driven development specification for +% anyone that wants to implement one of the algorithms of RFC 1071 or even a new +% one to compute the IP checksum. The article can also be read as an example of +% specifying something without revealing its implementation details; a good +% example of using QuickCheck specifications. + +% Whether you write your code in Erlang, C or Java, we assume that you can build +% an interface to a module called ip_checksum.erl in which Erlang functions +% either are the functions under test or call the functions under test. + +% === IP Checksum === + +% The IP checksum is the 16 bit one's complement of the one's complement sum of +% all 16 bit words in the header. + +% Ones complement is a way of representing negative numbers (see +% [http://en.wikipedia.org/wiki/Signed_number_representations#Ones.27_complement +% WikiPedia] for more details). + +% The IP checksum uses 16 bit words. In 16 bits you can represent the numbers 0 +% to 65535. The idea with ones complement is to use half the numbers in this +% interval for representing negative numbers. Thus, 0 up to 32767 are the +% positive numbers and 65535 is -0, or an alternative representation of zero. +% The number 65534 is -1 etc. Until 32768 which is -32767. Hence the interval +% -32767 up to 32767 can be represented. + +% In the remainder of this article we will present properties for functions that +% you probably would like to test. The properties are always parametrized by the +% word size. + +% === Utility functions === + +% First we define some functions that will come in handy. + +% The maximum number that can be represented in ''N'' bits. In the ones +% complement interpretation this will be the negative zero. +max_int(N) -> + (1 bsl N) - 1. + +negative_zero(N) -> + max_int(N). + +% === Ones complement === + +% The first function we might want to check is the ones' complement of a word, +% which in ones' complement representation corresponds to the negation. We +% assume we have a function '''ip_checksum:negate/1''' implemented that takes a +% bit string as input and computes its ones' complement. + +% Looking at the specification of ones' complement representation above we can +% see that adding the ones' complement representation of a number and the +% representation of its negation results in the representation of -0. For +% instance, the representation of -2 is 65533 and the representation of 2 is 2. +% Adding these we get 65535 which is the representation of -0. We use this +% property to test the implementation of the '''negate/2''' function. + +prop_negate(N) -> + ?FORALL(I, choose(0, max_int(N)), + begin + <> = ip_checksum:negate(<>), + equals(negative_zero(N), I + CI) + end). + +% The property above is parameterized by the word size N. We'll want to test +% our properties for a range of different word sizes, so we define a general +% function to transform a parameterized property to a property choosing random +% word sizes. + +random_word_size(Prop) -> + ?FORALL(N, choose(1, 64), Prop(N)). + +prop_negate() -> + random_word_size(fun prop_negate/1). + +% === Padding === + +% It is not clear from the specification presented above, but if you need to +% compute the checksum of a list of bytes in base 16, then there should be an +% even number of bytes. Likewise, if we would like to do ones complement in 32 +% bits base, we would need to extend a sequence of bytes such that it is +% divisible by 4. + +% Extending a bit string such that it is divisible by the base is called padding. +% We assume that you implemented a padding function that added the necessary +% bits, given a bit string. We assume this function to be implemented as +% '''ip_checksum:pad/1''' taking a bit string as argument and returning a new +% bit string which is an extended version with as many zero bits as needed. + +prop_padding() -> + random_word_size(fun prop_padding/1). + +prop_padding(N) -> + ?FORALL(BitString, bitstring(), + begin + Bits = bit_size(BitString), + <> = ip_checksum:pad(BitString,N), + Zeros = bit_size(Padded), + % If this property fails we need to know what the pad function actually + % returned in order to understand what went wrong. This is what the + % ?WHENFAIL macro is for. + ?WHENFAIL(io:format("B = ~w\nPadded = ~w\nZeros = ~w\n", + [B, Padded, Zeros]), + ((Bits + Zeros) rem N) == 0 andalso % the new length is divisible by N + B == BitString andalso % we don't change the bit string + <<0:Zeros>> == Padded andalso % we pad with zeros + Zeros < N % we don't pad more than we have to + ) + end). + +% Confident that the padding function works we can write a generator for +% correctly padded bit strings. +padded_bitstring(N) -> + ?LET(Bits, bitstring(), ip_checksum:pad(Bits, N)). + +% An alternative definition of this generator would not use the padding +% function, but rather first generate the length of the bit string and then +% pass that to the bit string generator (see below). The advantage of the former +% definition is that it behaves better when shrinking. The version below will +% regenerate the bit string everytime the length is getting shorter. +padded_bitstring_2(N) -> + ?LET(Len, nat(), bitstring(N * Len)). + +% === Ones complement sum === + +% The ones complement sum is computed by adding a number of words in ones +% complement representation. We assume this function to be implemented as +% '''ip_checksum:sum/2''' which takes a bit string as first argument and a +% word size as second argument. We assume that padding is done outside the +% sum function and only test that the function works for bit strings of +% which the length is divisible by the given word size. + +% Because of our test driven development method, we have already tested the +% '''negate/1''' function and therefore trust this function in our property. +% Remember that adding the representations of a number and its negation yields +% -0. This is in fact also true if we use one's complement addition rather than +% simply adding the representations (which is not the same thing). So if we +% concatenate a bit string with the negation of its sum, the sum of the +% resulting bit string should be -0 if our implementation of '''sum/2''' is +% correct. By testing the sum function in this way we don't have to worry about +% specifying the intricacies of ones' complement arithmetic (except for the +% fact that X + (-X) = -0). + +prop_sum() -> + random_word_size(fun prop_sum/1). + +prop_sum(N) -> + ?FORALL(Bin, padded_bitstring(N), + begin + Sum = ip_checksum:sum(Bin, N), + CSum = ip_checksum:negate(Sum), + equals(ip_checksum:sum(<>, N), + <<(negative_zero(N)):N>>) + end). + +% === Checksum === + +% After computing ones' complement sum, one has to take the ones' complement of +% the result to compute the checksum. Of course, we have all ingredients in +% house to do so, but in case you implement both functions as one you would +% like to test the final result '''ip_checksum:checksum/2''' with a bit string +% and word size as arguments. + +% We first test that the '''checksum/2''' function takes care of padding, by +% checking that padding the bitstring before passing it to '''checksum/2''' +% doesn't change the result. + +prop_checksum_pad() -> + random_word_size(fun prop_checksum_pad/1). + +prop_checksum_pad(N) -> + ?FORALL(Bits, bitstring(), + equals(ip_checksum:checksum(Bits, N), + ip_checksum:checksum(ip_checksum:pad(Bits, N), N))). + +% We can test the '''checksum/2''' function in the same way as we tested +% '''sum/2''' above. Taking a bit string and prepending its checksum should +% result in a bit string whose checksum is zero. Here's why: +% +% % definition of checksum +% checksum(Bits) == -sum(Bits) +% +% checksum(checksum(Bits) ++ Bits) == {def. of checksum} +% -sum(-sum(Bits) ++ Bits) == {sum(Xs ++ Ys) == sum(Xs) + sum(Ys)} +% -(-sum(Bits) + sum(Bits)) == {adding -X and X} +% -(-0) == +% 0 +% +% Note that due to padding, the property doesn't hold if we append the checksum +% rather than prepending it. + +prop_checksum() -> + random_word_size(fun prop_checksum/1). + +prop_checksum(N) -> + ?FORALL(Bin, bitstring(), + begin + Sum = ip_checksum:checksum(Bin, N), + equals(ip_checksum:checksum(<>, N), + <<0:N>>) + end). + diff --git a/lib/eqc/examples/lists_eqc.erl b/lib/eqc/examples/lists_eqc.erl new file mode 100644 index 0000000..4203fa7 --- /dev/null +++ b/lib/eqc/examples/lists_eqc.erl @@ -0,0 +1,223 @@ +%%% File : lists_eqc.erl +%%% Author : Thomas Arts +%%% Ulf Norell +%%% Description : QuickCheck tests for some functions from the lists library. +%%% Created : 23 Mar 2010 by Thomas Arts + +-module(lists_eqc). + +-include_lib("eqc/include/eqc.hrl"). + +-compile(export_all). + +% === Testing lists:delete/2 === + +% The lists:delete/2 function removes an element from a list. Here's a property +% we might want for this function: after you've removed an element from a list +% it's not there anymore. The corresponding QuickCheck property is: + +prop_delete_0() -> + ?FORALL({X, Xs}, {int(), list(int())}, + not lists:member(X, lists:delete(X, Xs))). + +% Checking this property for 100 random values and lists it might actually +% pass. +test_delete_0() -> + quickcheck(prop_delete_0()). + +% However, rerunning the property a few more times will reveal a problem: +test_delete_0_more() -> + quickcheck(numtests(2000,prop_delete_0())). + +% We get output looking like this: +% 74> lists_eqc:test_delete_0_more(). +% ............................................................................ +% ............................................................................ +% ............................................................................ +% ............................................................................ +% ....................................................Failed! After 377 tests. +% {7,[-6,1,23,24,7,7]} +% Shrinking..(2 times) +% {7,[7,7]} +% false +% There is a problem with our specification. lists:delete/2 only removes the +% first occurrence of the element, something our specification fails to take +% into account. + +% Before fixing the problem in the specification, it's worth thinking about why +% we needed so many tests to find the bug. In order to find the bug we need to +% generate a value and a list such that the value appears twice in the list. +% What's the probability of that? To answer that question we can write a new +% property: + +prop_member_probability() -> + ?FORALL({X, Xs}, {int(), list(int())}, + collect(lists:member(X, Xs), true)). + +% This property always succeeds, but for every test case it records whether the +% generated value appears (even once) in the list. Running the property a large +% number of times reveals that the probability that a random value appears in a +% random list is around 8%. No wonder it's hard to find a test case where it +% appears at least twice! + +% To make it easier to find a failing case, we can change our property to only +% look at cases where the value appears at least once in the list. To do this +% we use the ?IMPLIES macro. + +prop_delete_1() -> + ?FORALL({X, Xs}, {int(), list(int())}, + ?IMPLIES(lists:member(X, Xs), + not lists:member(X, lists:delete(X, Xs)))). + +% Now the output will look something like this: +% 102> eqc:quickcheck(lists_eqc:prop_delete_1()). +% xxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx. +% .xxxxxxxxxxxxxx.xx.xxxxxxx.xxxxxxxxxxx.xxxxxxxxxxxxxxxxxxx.xxx.xxxxxxxxxxxxx +% xxxxxxxxx.xxxxxxxxxxx.xxxxx.xxxxxxxxxxxxxxxxxxxxxx.xx.xxxxxxxxxxxx.xxxxxxxxx +% xxxxxxxxxxxxxxxxxxxxxxxxx..xxxxx.xxxx.xxxxxxxxx.xxxxxFailed! After 22 tests. +% {1,[31,35,35,34,-21,-13,7,1,2,1,-35,2]} +% Shrinking.....(5 times) +% {1,[1,1]} +% false +% The 'x's represent test cases that were discarded because they didn't satisfy +% the condition of the ?IMPLIES. + +% Now that we have a property that fails reliably we can use it to document our +% misconception about the behaviour of lists:delete/2. The fails/1 function +% takes a property that is expected to fail and fails it doesn't. + +prop_delete_2() -> + fails( + ?FORALL({X, Xs}, {int(), list(int())}, + ?IMPLIES(lists:member(X, Xs), + not lists:member(X, lists:delete(X, Xs))))). + +% Let's fix the specification. One possibility would be to count the number of +% occurrences of the value we're removing before and after calling +% lists:delete/2, but we can write a more precise specification quite elegantly: + +prop_delete_3() -> + ?FORALL({Xs, X, Ys}, {list(int()), int(), list(int())}, + ?IMPLIES(not lists:member(X, Xs), + equals(lists:delete(X, Xs ++ [X] ++ Ys), + Xs ++ Ys))). + +% The equals function compares its arguments for equality and, if they're not +% equal, prints the arguments. This lets us see what lists:delete actually +% returned in the case we get a failing test case. Try removing the ?IMPLIES +% and run the property to see what it looks like. + +% === Testing lists:seq/3 === + +% The lists:seq/2 function has recently changed in Erlang. The new +% specification allows lists:seq(1,0) and returns [] in that case. Copying the +% specification from the manual still reveals an error +% eqc:quickcheck(lists_eqc:prop_seq0()) due to the fact that a special case is +% overlooked. The fix is in prop_seq(). + +% This is the property according to the documentation. This property fails with +% a badarith exception on the test case {0, 0, 0}. The problem is that the +% specification of the length is not correct for increment 0. +prop_seq0() -> + ?FORALL({From,To,Incr},{int(),int(),int()}, + case catch lists:seq(From,To,Incr) of + {'EXIT',_} -> + (To < From-Incr andalso Incr > 0) orelse + (To > From-Incr andalso Incr < 0) orelse + (Incr==0 andalso From /= To); + List -> + is_list(List) andalso + length(List) == (To-From+Incr) div Incr + end). + +% This is the property as it holds. +prop_seq() -> + ?FORALL({From,To,Incr},{int(),int(),int()}, + case catch lists:seq(From,To,Incr) of + {'EXIT',_} -> + (To < From-Incr andalso Incr > 0) orelse + (To > From-Incr andalso Incr < 0) orelse + (Incr==0 andalso From /= To); + List when Incr /= 0 -> + is_list(List) andalso + length(List) == (To-From+Incr) div Incr; + List when Incr == 0 -> + length(List) == 1 + end). + +% This is probably how one would like seq to behave. +prop_seq_wish(Seq) -> + ?FORALL({From,To,Incr},{int(),int(),int()}, + case catch Seq(From,To,Incr) of + [] -> Incr > 0 andalso From > To orelse + Incr < 0 andalso From < To; + [_] when Incr == 0 -> From == To; + List when Incr /= 0 andalso is_list(List) -> + length(List) == (To-From+Incr) div Incr; + {'EXIT', _} -> + Incr == 0 andalso From /= To; + _ -> + false + end). + +prop_seq_wish() -> + prop_seq_wish(fun lists:seq/3). + +% Here is a reference implementation satisfying prop_seq_wish. +seq(From, To, 0) when From /= To -> + exit("seq: increment 0"); +seq(From, From, _Incr) -> + [From]; +seq(From, To, Incr) when From > To andalso Incr > 0 -> + []; +seq(From, To, Incr) when From < To andalso Incr < 0 -> + []; +seq(From, To, Incr) -> + [From | seq(From + Incr, To, Incr)]. + +prop_seq_wish_granted() -> + prop_seq_wish(fun seq/3). + +% The previous properties only specifies the length of the result of +% lists:seq/3. We also want to make sure that it contains the right elements. +% In particular, if lists:seq(From, To, Incr) returns a non-empty list, the +% first element of the list should be From, and the difference between adjacent +% elements should be Incr. We've already tested that the list has the right +% number of elements so we don't have to worry about when the list ends. + +% First some helper functions: + +% We're only interested in non-empty results of seq/3. +is_cons([_|_]) -> true; +is_cons(_) -> false. + +% We want to look at the difference between adjacent elements. +diff_adjacent([X,Y|Xs]) -> + [Y - X|diff_adjacent([Y|Xs])]; +diff_adjacent(_) -> + []. + +% We use ?IMPLIES to ignore the cases when lists:seq does not return a +% non-empty list. To make sure we still get interesting test cases we collect +% the lengths of the results and the increments we've chosen. +prop_seq_elements() -> + ?FORALL({From, To, Incr}, {int(), int(), int()}, + begin + Seq = (catch lists:seq(From, To, Incr)), + ?IMPLIES(is_cons(Seq), + begin + Adj = diff_adjacent(Seq), + ?WHENFAIL(io:format("Seq = ~w\nAdj = ~w", [Seq, Adj]), + % When you have several collects in the same property you can give them + % names using collect/3 and with_title/1 to distinguish them. + % We divide the actual numbers by 5 to reduce the number of different + % values collected. + collect(with_title(lengths), length(Seq) div 5, + collect(with_title(incr), Incr div 5, + hd(Seq) == From andalso + lists:all(fun(D) -> D == Incr end, Adj) + )) + ) + end) + end). + diff --git a/lib/eqc/examples/sets_eqc.erl b/lib/eqc/examples/sets_eqc.erl new file mode 100644 index 0000000..48ee035 --- /dev/null +++ b/lib/eqc/examples/sets_eqc.erl @@ -0,0 +1,146 @@ +%%% File : sets_eqc.erl +%%% Author : Thomas Arts +%%% Description : QuickCheck properties for sets.erl +%%% Based on "Testing Data Structures with QuickCheck" +%%% Created : 24 Mar 2010 by Thomas Arts + +-module(sets_eqc). + +-include_lib("eqc/include/eqc.hrl"). + +-compile(export_all). + +%% Create a generator for the opaque type "set". The generator will generate +%% symbolic calls which when evaluated computes a set. Each symbolic call has +%% the form {call, Module, Function, Arguments} and are evaluated using the +%% function eval/1. + +%% To avoid generating infinite symbolic representations we pass the size +%% parameter to the generator and use it to make sure we stop eventually. + +set(G) -> + ?SIZED(Size,well_defined(set(Size,G))). + +set(0,G) -> + oneof([{call,sets,new,[]}, + {call,sets,from_list,[list(G)]}]); +set(N,G) -> + frequency( + [{5,set(0,G)}, + {3, ?LAZY(?LETSHRINK([Set],[set(N-1,G)], + {call,sets,add_element,[G, Set]}))}, + {1, ?LAZY(?LETSHRINK([Set],[set(N-1,G)], + {call,sets,del_element,[G, Set]}))}, + {1, ?LAZY(?LETSHRINK([Set1,Set2],[set(N div 2,G),set(N div 2,G)], + {call,sets,union,[Set1, Set2]}))}, + {1, ?LAZY(?LETSHRINK(Sets,list(set(N div 3,G)), + {call,sets,union,[Sets]}))}, + {1, ?LAZY(?LETSHRINK([Set1,Set2],[set(N div 2,G),set(N div 2,G)], + {call,sets,intersection,[Set1, Set2]}))}, + {1, ?LAZY(?LETSHRINK(Sets,?LET(L,nat(),vector(L+1,set(N div (L+1),G))), + {call,sets,intersection,[Sets]}))}, + {1, ?LAZY(?LETSHRINK([Set1,Set2],[set(N div 2,G),set(N div 2,G)], + {call,sets,subtract,[Set1, Set2]}))}, + {1, ?LAZY(?LETSHRINK([Set],[set(N div 2,G)], + {call,sets,filter,[function1(bool()), Set]}))}]). + +%% The next step is to define a model interpretation, i.e. a simplified, +%% obviously correct implementation of the data type. In this case we use +%% usorted lists. + +model(S) -> + lists:sort(sets:to_list(S)). + +%% Define the set operations on the model. + +madd_element(E,S) -> + lists:usort([E|S]). + +mdel_element(E,S) -> + S -- [E]. + +msize(S) -> + length(S). + +mis_element(E,S) -> + lists:member(E,S). + +munion(Ss) -> + lists:usort(lists:append(Ss)). + +mintersection(Sets) -> + [ E || E <- lists:usort(lists:append(Sets)), + lists:all(fun(Set) -> lists:member(E,Set) end, Sets)]. + +mis_disjoint(S1,S2) -> + mintersection([S1,S2]) == []. + +mfilter(Pred,S) -> + [ E || E <- S, Pred(E)]. + +%% Define one property for each operation. We parameterize the properties on +%% the generator for the elements. To make it easy to run the properties we +%% also define special versions that use integers. + +%% Each property have the same basic form: we check that a given operation +%% on sets has the same behaviour as the corresponding model operation. + +prop_create() -> prop_create(int()). +prop_create(G) -> + ?FORALL(Set,set(G), + sets:is_set(eval(Set))). + +prop_add_element() -> prop_add_element(int()). +prop_add_element(G) -> + ?FORALL({E,Set},{G,set(G)}, + model(sets:add_element(E,eval(Set))) == madd_element(E,model(eval(Set)))). + +prop_del_element() -> prop_del_element(int()). +prop_del_element(G) -> + ?FORALL({E,Set},{G,set(G)}, + model(sets:del_element(E,eval(Set))) == mdel_element(E,model(eval(Set)))). + + +prop_union2() -> prop_union2(int()). +prop_union2(G) -> + ?FORALL({Set1,Set2},{set(G),set(G)}, + model(sets:union(eval(Set1),eval(Set2))) == munion([model(eval(Set1)),model(eval(Set2))])). + +prop_union() -> prop_union(int()). +prop_union(G) -> + ?FORALL(Sets,list(set(G)), + model(sets:union(eval(Sets))) == munion([model(Set) || Set<-eval(Sets)])). + +prop_intersection2() -> prop_intersection2(int()). +prop_intersection2(G) -> + ?FORALL({Set1,Set2},{set(G),set(G)}, + model(sets:intersection(eval(Set1),eval(Set2))) == + mintersection([model(eval(Set1)),model(eval(Set2))])). + + +prop_intersection() -> prop_intersection(int()). +prop_intersection(G) -> + ?FORALL(Sets,eqc_gen:non_empty(list(set(G))), + model(sets:intersection(eval(Sets))) == mintersection([model(Set) || Set<-eval(Sets)])). + +prop_size() -> prop_size(int()). +prop_size(G) -> + ?FORALL(Set,set(G), + sets:size(eval(Set)) == msize(model(eval(Set)))). + +prop_is_element() -> prop_is_element(int()). +prop_is_element(G) -> + ?FORALL({E,Set},{G,set(G)}, + sets:is_element(E,eval(Set)) == mis_element(E,model(eval(Set)))). + +prop_is_disjoint() -> prop_is_disjoint(int()). +prop_is_disjoint(G) -> + ?FORALL({Set1,Set2},{set(G),set(G)}, + sets:is_disjoint(eval(Set1),eval(Set2)) == mis_disjoint(model(eval(Set1)),model(eval(Set2)))). + +prop_filter() -> prop_filter(int()). +prop_filter(G) -> + ?FORALL({Pred,Set},{function1(bool()),set(G)}, + model(sets:filter(Pred,eval(Set))) == mfilter(Pred,model(eval(Set)))). + + diff --git a/lib/eqc/include/eqc.hrl b/lib/eqc/include/eqc.hrl new file mode 100644 index 0000000..36c1bd0 --- /dev/null +++ b/lib/eqc/include/eqc.hrl @@ -0,0 +1,63 @@ +% Generated file--see release:make() +% eqc_macros.hrl +-define(DELAY(X),fun()->X end). +-define(FORCE(X),(X)()). +-define(LET(X,E,E2),eqc_gen:bind(E,fun(X)->E2 end)). +-define(SIZED(S,G),eqc_gen:sized(fun(S)->G end)). +-define(SUCHTHAT(X,G,P),eqc_gen:suchthat(G,fun(X)->P end)). +-define(SUCHTHATMAYBE(X,G,P),eqc_gen:suchthatmaybe(G,fun(X)->P end)). +-define(SHRINK(G,Gs),eqc_gen:shrinkwith(G,?DELAY(Gs))). +-define(LETSHRINK(Es,Gs,E), eqc_gen:letshrink(Gs,fun(Es) -> E end)). +-define(LAZY(G),eqc_gen:lazy(?DELAY(G))). +-define(IMPLIES(Pre,Prop),eqc:implies(Pre,??Pre,?DELAY(Prop))). +-define(FORALL(X,Gen,Prop),eqc:forall(Gen,fun(X)->Prop end)). +-define(WHENFAIL(Action,Prop),eqc:whenfail(fun(_) -> Action end,?LAZY(Prop))). +-define(TRAPEXIT(E),eqc:trapexit(?DELAY(E))). +-define(TIMEOUT(Limit,Prop),eqc:timeout(Limit,?LAZY(Prop))). +-define(ALWAYS(N,P),eqc:always(N,?DELAY(P))). +-define(SOMETIMES(N,P),eqc:sometimes(N,?DELAY(P))). +% eqc_imports.hrl +-import(eqc_gen, + [pick/1,pick/2, + includeif/2,return/1,applygen/2, + noshrink/1,shrinkings/1,shrinking_path/2, + timeout/2, + resize/2, + parameter/1, parameter/2, with_parameter/3, with_parameters/2, + choose/2, + shuffle/1, + sample/1, sampleshrink/1, + oneof/1, frequency/1, + non_empty/1, + elements/1, growingelements/1, list/1, shrink_list/1, vector/2, + function0/1, function1/1, function2/1, function3/1, function4/1, + bool/0, maybe/1, char/0, int/0, shrink_int/3, nat/0, largeint/0, + real/0, orderedlist/1, + binary/0, binary/1, bitstring/0, bitstring/1, + default/2, weighted_default/2, + seal/1,open/1,peek/1, + fault/2, fault_rate/3, more_faulty/2, less_faulty/2, no_faults/1, + prop_shrinks_without_duplicates/1, shrink_without_duplicates/1, + is_generator/1]). + +-import(eqc_symbolic, + [eval/1,eval/2,defined/1,well_defined/1,pretty_print/1,pretty_print/2]). + +-import(eqc,[equals/2, + fails/1, + conjunction/1, + collect/2,collect/3,classify/3,aggregate/2,aggregate/3,measure/3, + %distribution/0, + with_title/1, + %print_distribution/1, + numtests/2, + on_output/2, + on_test/2, + quickcheck/1, + counterexample/0,counterexample/1, + current_counterexample/0, + module/1, + check/2, + recheck/1]). + +-compile({parse_transform,eqc_warn}). diff --git a/lib/erlang_js b/lib/erlang_js new file mode 160000 index 0000000..709b568 --- /dev/null +++ b/lib/erlang_js @@ -0,0 +1 @@ +Subproject commit 709b568efbc99c954507d1593bc5633f900bc5dc diff --git a/lib/erlv8 b/lib/erlv8 new file mode 160000 index 0000000..7443cc8 --- /dev/null +++ b/lib/erlv8 @@ -0,0 +1 @@ +Subproject commit 7443cc8e2e06f7907a9e1e44c181255e188cfb97 diff --git a/mnesia/.gamedb.erl.swp b/mnesia/.gamedb.erl.swp deleted file mode 100644 index 469b1f8..0000000 Binary files a/mnesia/.gamedb.erl.swp and /dev/null differ diff --git a/mnesia/gamedb.erl b/mnesia/gamedb.erl deleted file mode 100644 index 751eb92..0000000 --- a/mnesia/gamedb.erl +++ /dev/null @@ -1,50 +0,0 @@ -%%%%---------------------------------------------------- -%%% @author Mattias Pettersson -%%% @copyright 2011 Mattias Pettersson -%%% @doc Database for runtime game variable storage. -%%% @end - - Test Mnesia --module(gamedb). --import(mnesia). --export([init/0,insert_player/1,example_player/0,read_player/1,test_player/0]). --include("gamedb.hrl"). - -%%----------------------------------------------------- -%% Creation -%%----------------------------------------------------- -init() -> - mnesia:create_table(player, [{attributes, record_info(fields, player)}]). - -%%----------------------------------------------------- -%% Test -%%----------------------------------------------------- -test_player() -> - insert_player(example_player()), - read_player(0001). - -example_player() -> - #player{id = 0001, - name = "Tux"}. - -%%----------------------------------------------------- -%% Insertions -%%----------------------------------------------------- -insert_player(Player) -> - Fun = fun() -> - mnesia:write(Player) - end, - mnesia:transaction(Fun). - - -%%----------------------------------------------------- -%% Querries -%%----------------------------------------------------- -read_player(Player_Key) -> - Fun = fun() -> - [P] = mnesia:read(player, Player_Key), - Name = P#player.name, - io:format("Player name: ~s~n",[Name]) - end, - mnesia:transaction(Fun). - diff --git a/mnesia/gamedb.hrl b/mnesia/gamedb.hrl deleted file mode 100644 index 1ae9c8f..0000000 --- a/mnesia/gamedb.hrl +++ /dev/null @@ -1,6 +0,0 @@ -%% gamedb.hrl - --record(player, {id, name}). - - - diff --git a/mnesia/gamedb_usage.txt b/mnesia/gamedb_usage.txt deleted file mode 100644 index b3a07a0..0000000 --- a/mnesia/gamedb_usage.txt +++ /dev/null @@ -1,12 +0,0 @@ -1. From terminal: erl -mnesia dir '"/home/user/dir/to/GGS/GameDB.Player"' -2. mnesia:create_schema([node()]). -3. mnesia:start(). -4. c(gamedb). -5. gamedb:init(). -6. mnesia:info(). -7. gamedb:test_player(). - -Last output should be: - Player name: Tux - {atomic,ok} - diff --git a/python_client b/python_client index 77574e6..6eb345f 100755 --- a/python_client +++ b/python_client @@ -1,22 +1,84 @@ #!/usr/bin/env python -import sys - -import socket +import sys, time, socket HOST = 'localhost' # The remote host PORT = int(sys.argv[1]) # The same port as used by the server s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) -s.send('__hello 0') -fs = s.makefile() -data = fs.readline() -token = data.split(" ")[0] -print "Token:", token -s.send(token+" __echo 0 msg") -data = s.recv(1024) -print data +# Define ourselves a function! +token = s.recv(1024) + +#print "Defining a function called myFun" +#s.send( +#"Token: %s\n\ +#Server-Command: define\n\ +#Content-Type: text\n\ +#Content-Length: 49\n\ +#\n\ +#function myFun() {return 'Hello World!' ;}" % token) +#fs = s.makefile() +#data = fs.readline() +#print "Token:", token +#print "Data: ", ' '.join(data.split(" ")[1:]) + +# Call that function! +fs = s.makefile() +print "Token: ", token +s.send( +"Token: %s\n\ +Game-Command: greet\n\ +Content-Type: text\n\ +Content-Length: 0\n\ +\n\ +" % token) +time.sleep(1) + +s.send( +"Token: %s\n\ +Game-Command: uname\n\ +Content-Type: text\n\ +Content-Length: 0\n\ +\n\ +" % token) +time.sleep(1) + +s.send( +"Token: %s\n\ +Game-Command: chat\n\ +Content-Type: text\n\ +Content-Length: 23\n\ +\n\ +Hello guys, what's up?\n" % token) +time.sleep(3) + + +while True: + data = fs.readline() + print "Data: ", data s.close() +time.sleep(2) + +HOST = 'localhost' # The remote host +PORT = int(sys.argv[1]) # The same port as used by the server +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect((HOST, PORT)) +# Call that function! + +print "Calling myFun" +s.send( +"Token: %s\n\ +Server-Command: call\n\ +Content-Type: text\n\ +Content-Length: 6\n\ +\n\ +myFun" % token) +fs = s.makefile() +data = fs.readline() +print "Token:", token +print "Data: ", ' '.join(data.split(" ")[1:]) + +s.close() diff --git a/python_client_reconnect b/python_client_reconnect new file mode 100755 index 0000000..2a41b70 --- /dev/null +++ b/python_client_reconnect @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +import sys, time, socket + +HOST = 'localhost' # The remote host +PORT = int(sys.argv[1]) # The same port as used by the server +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect((HOST, PORT)) +# Call that function! + +print "Calling myFun" +s.send( +"Token: %s\n\ +Server-Command: call\n\ +Content-Type: text\n\ +Content-Length: 6\n\ +\n\ +myFun" % raw_input("Token >> ")) +fs = s.makefile() +data = fs.readline() +print "Data: ", ' '.join(data.split(" ")[1:]) + +s.close() diff --git a/src/.ggs_server.erl.swo b/src/.ggs_server.erl.swo deleted file mode 100644 index 3048659..0000000 Binary files a/src/.ggs_server.erl.swo and /dev/null differ diff --git a/src/ggs_api.js b/src/ggs_api.js new file mode 100644 index 0000000..4a891df --- /dev/null +++ b/src/ggs_api.js @@ -0,0 +1,177 @@ +function Storage(type) { + if (type == "world" || type == "localStorage" || type == "players") { + this.type = type; + var self = this; + + return { + setItem: function(key, value) { + if(this.type != "players") + callErlang("ggs_db setItem " + escapeErlang([GGS.tableToken, self.type, key, value])); + else + throw "No such method setItem()"; + }, + getItem: function(key) { + return callErlang("ggs_db getItem " + escapeErlang([GGS.tableToken, self.type, key])); + }, + key: function(position) { + return callErlang("ggs_db key " + escapeErlang([GGS.tableToken, self.type, position])); + }, + length: { + get: function() { + return callErlang("ggs_db length " + escapeErlang([GGS.tableToken, self.type])); + } + }, + removeItem: function(key) { + if(this.type != "players") + callErlang("ggs_db removeItem " + escapeErlang([GGS.tableToken, self.type, key])); + else + throw "No such method removeItem()"; + }, + clear: function() { + if(this.type != "players") + callErlang("ggs_db clear " + escapeErlang([GGS.tableToken, self.type])); + else + throw "No such method clear()"; + } + } + } else throw "GGS: No such storage available " + type; +} + +function _GGS(tableToken) { + + this.tableToken = tableToken; + + var world = new Storage("world"); + this.__defineGetter__("world", function() { + return world; + }); + + + var localStorage = new Storage("localStorage"); + this.__defineGetter__("localStorage", function() { + return localStorage; + }); + + var players = new Storage("players"); + this.__defineGetter__("players", function() { + return players; + }); + + var tableToken = this.tableToken; + this.__defineGetter__("tableToken", function() { + return tableToken; + }); + +} + +_GGS.prototype.sendCommandToAll = function(command, args) { + var message = "{" + command + "," + args + "}"; + callErlang("ggs_table send_command_to_all " + escapeErlang([this.tableToken, message])); +} + +_GGS.prototype.serverLog = function(message) { + callErlang("'error_logger info_msg " + escapeErlang([message]) + "'"); +} + +function escapeErlang(args) { + var str = JSON.stringify(args); + str = str.replace("'", "\\\'"); + return str; +} + +function Player(token) { + + var playerToken = token; + this.__defineGetter__("id", function() { + return playerToken; + }); + + return { + sendCommand: function(command, args) { + ejsLog("/tmp/ggs-test.txt", "'ggs_table send_command " + escapeErlang([GGS.tableToken+ "", playerToken, command, args])+"'"); + //callErlang("'ggs_table send_command " + escapeErlang([GGS.tableToken+ "", playerToken, command, args]) + "'"); + ejsLog("/tmp/ggs-test.txt", "done"); + } + } +} + + + +/* +// ------------ Player stuff ------------- +// TODO: remove this later on + +function playerCommand(player, command, args) { + switch(command) { + case "greet": + player.sendCommand("notification", "Welcome on our server!"); + var new_nick = args; + if(validNick(new_nick)) { + newNick(new_nick); + GGS.sendCommandToAll("joined", new_nick); + } + break; + case "chat": + GGS.sendCommandToAll("chat", args); + break; + case "uname": + player.sendCommand("notice", callErlang("os cmd [\"uname -a\"]")) + break; + case "lplayers": + listUsers(player); + break; + case "nick": + if(validNick(new_nick)) { + newNick(new_nick); + GGS.sendCommandToAll("nickchange", old_nick + "," + nicks[player.id]); + } + break; + default: + player.sendCommand("error", "Command not found"); + break; + } +} + +function validNick(new_nick) { + if(new_nick.lastIndexOf(",") != -1) { + player.sendCommand("error", "Mallformed nick " + new_nick); + return false; + } + + var nicks_s = GGS.localStorage("nicks"); + var nicks = {}; + if(nicks_s != "") { // if not the first player + nicks = JSON.parse(nicks_s); + } + for (var id in nicks) { + if (nicks[id] == new_nick) { + player.sendCommand("error", "Nick " + new_nick + " is already taken"); + return false; + } + } + + return true; +} + + +function newNick(new_nick) { + var nicks_s = GGS.localStorage("nicks"); + var nicks = {}; + if(nicks_s != "") { // if not the first player + nicks = JSON.parse(nicks_s); + } + + nicks[player.id] = new_nick; + old_nick = nicks[player.id]; + GGS.localStorage.setItem("nicks", JSON.stringify(nicks)); +} + +function listUsers(player) { + var nicks = JSON.parse(GGS.localStorage.getItem("nicks")); + var nicks_a = []; + for(var id in nicks) { + nicks_a.push(nicks[id]) + } + player.sendCommand("nicklist", nicks_a.join(",")); +} +*/ \ No newline at end of file diff --git a/src/ggs_coordinator.erl b/src/ggs_coordinator.erl new file mode 100644 index 0000000..99de9b7 --- /dev/null +++ b/src/ggs_coordinator.erl @@ -0,0 +1,184 @@ +-module(ggs_coordinator). + +%% API Exports +-export([ start_link/0, + stop/1, + join_table/1, + create_table/0, + join_lobby/0, + respawn_player/2, + respawn_table/1, + remove_player/2, + get_all_players/0, + table_token_to_pid/1, + table_pid_to_token/1, + player_pid_to_token/1, + player_token_to_pid/1]). + +%% gen_server callback exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). +-define(SERVER, ?MODULE). + +-record(co_state, + {players = [], % List of all player processes + player_table_map = [], % Players <-> Table map + table_state_map = [], + tables = []}). % Table <-> Table state map + +%% @doc This module act as "the man in the middle". +%% Creates the starting connection between table and players. + +%% @doc Starts the coordinator process. +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%% @doc Terminates the coordinator process. +stop(Reason) -> + gen_server:cast(ggs_coordinator, {stop, Reason}). + +%% @doc Joins table with specified token, returns {error, no_such_table} +%% if the specified table token does not exist +join_table(Token) -> + gen_server:call(ggs_coordinator, {join_table, Token}). + +%% @doc Create a new table, return {error, Reason} or {ok, TableToken} +create_table() -> + gen_server:call(ggs_coordinator, create_table). + +%% @doc This is the first function run by a newly created players. +%% Generates a unique token that we use to identify the player. +join_lobby() -> + gen_server:call(ggs_coordinator, join_lobby). + +%% @doc Act as a supervisor to player and respawns player when it gets bad data. +respawn_player(_Player, _Socket) -> + ggs_logger:not_implemented(). + +%% @doc Act as a supervisor to table and respawns table when it gets bad data. +respawn_table(_Token) -> + ggs_logger:not_implemented(). + +%% @doc Removes a player from coordinator. +remove_player(_From, _Player) -> + %gen_server:cast(ggs_coordinator, {remove_player, Player}). + ggs_logger:not_implemented(). + +get_all_players() -> + gen_server:call(?SERVER, get_all_players). + + +%% Conversion tools + +table_token_to_pid(Token) -> + gen_server:call(?SERVER, {table_token_to_pid, Token}). + +table_pid_to_token(Pid) -> + gen_server:call(?SERVER, {table_pid_to_token, Pid}). + +player_pid_to_token(Pid) -> + gen_server:call(?SERVER, {player_pid_to_token, Pid}). + +player_token_to_pid(Token) -> + gen_server:call(?SERVER, {player_token_to_pid, Token}). + +%% Just to shorten the name +back_up(State) -> + ggs_coordinator_backup:back_up(State), + State. + +%% gen_server callbacks + +init([]) -> + % Restore old state from backup if there is old state stored there + case ggs_coordinator_backup:retrieve() of + no_state_stored -> + io:format("No old state stored.. Creating new!~n"), + {ok, #co_state{}}; + State -> + {ok, State} + end. + +handle_call(join_lobby, From, State) -> + Token = helpers:get_new_token(), + Players = State#co_state.players, + {Pid, _Sock} = From, + NewState = State#co_state{players = [{Pid, Token} | Players]}, + back_up(NewState), + {reply, {ok, Token}, NewState}; + +handle_call({join_table, Table}, From, State) -> + {FromPlayer, _Ref} = From, + case lists:keyfind(Table, 1, State#co_state.tables) of + {TableID, TablePID} -> + % TODO check if table full + ggs_table:add_player(TablePID, FromPlayer), + {reply, {ok, TableID, TablePID}, State}; + false -> + back_up(State), + {reply, {error, no_such_table}, State} + end; + +handle_call(create_table, From, State) -> + TableToken = getNewToken(), + TableIDMap = State#co_state.player_table_map, + Tables = State#co_state.tables, + TablePid = ggs_table:start(TableToken), % With start_link, the table dies with the coordinator + NewState = State#co_state{ + player_table_map = [{From, TableToken} | TableIDMap], + tables = [{TableToken, TablePid} | Tables] + }, + back_up(NewState), + {reply, {ok, TableToken, TablePid}, NewState}; + + +handle_call(get_all_players, _From, State) -> + {reply, State#co_state.players, State}; + +%% Conversion tools +handle_call({table_token_to_pid, Token}, _From, State) -> + Tables = State#co_state.tables, + %erlang:display("Pre-keyfind"), + {_, Pid} = lists:keyfind(Token, 1, Tables), + {reply, Pid, State}; + +handle_call({table_pid_to_token, Pid}, _From, State) -> + Tables = State#co_state.tables, + {Token, _} = lists:keyfind(Pid, 2, Tables), + {reply, Token, State}; + +handle_call({player_pid_to_token, Pid}, _From, State) -> + Players = State#co_state.players, + {Pid, Token} = lists:keyfind(Pid, 1, Players), + {reply, Token, State}; + +handle_call({player_token_to_pid, Token}, _From, State) -> + Players = State#co_state.players, + {Pid, Token} = lists:keyfind(Token, 2, Players), + {reply, Pid, State}; + +handle_call(_Message, _From, State) -> + {noreply, State}. + +handle_cast({stop, _Reason}, State) -> + {stop, normal, State}; + +%% @TODO: Implement me +%handle_cast({remove_player, Player}) -> +% {noreply, State#co_state{ + +handle_cast(_Message, State) -> + {noreply, State}. + +handle_info(_Message, State) -> + {noreply, State}. + +terminate(normal, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +% helper +getNewToken() -> + string:strip(os:cmd("uuidgen"), right, $\n ). \ No newline at end of file diff --git a/src/ggs_coordinator_backup.erl b/src/ggs_coordinator_backup.erl new file mode 100644 index 0000000..e42fe48 --- /dev/null +++ b/src/ggs_coordinator_backup.erl @@ -0,0 +1,65 @@ +-module(ggs_coordinator_backup). + +-behaviour(gen_server). + +%% API Exports +-export([start_link/0, stop/1]). +-export([back_up/1, retrieve/0]). + +%% gen_server callback exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-define(SERVER, ?MODULE). + +%% @doc This module is repsponsible for keeping a backup of the coodinator +%% at all times. At any point in time a backup can be restored from this +%% module. +%% This module is started by the root supervisor, and is restarted when it +%% crashes. Upon a crash, the backup state is lost in this module, and must +%% be filled in from the ggs_coordinator. + +%% @doc Start a new ggs_coordinator backup instance, and register it under +%% this name. This means that there can only be one instance of this module +%% running. +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%% @doc Stops the server with the specified reason. +%% @spec stop(Reason) -> ok. +%% Reason = String +stop(_Reason) -> ggs_logger:not_implemented(). + +%% API +back_up(State) -> + gen_server:cast(?SERVER, State). + +%% @doc Retrieve the state stored in this server. If there is a state stored +%% here, it is returned to the caller. If the backup server does not have a +%% state stored, it will return the no_state_stored atom. +retrieve() -> + gen_server:call(?SERVER, retrieve). + +%% gen_server callbacks + +%% @doc Initiate the server. This is called from gen_server +init([]) -> + {ok, no_state_stored}. + +handle_call(retrieve, _From, State) -> + {reply, State, State}. + +handle_cast(NewState, _State) -> + {noreply, NewState}. + +handle_info(Msg, State) -> + io:format("Received out of bounds message! "), + erlang:display(Msg), + io:format("~n"), + {noreply, State}. + +terminate(normal, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/ggs_db.erl b/src/ggs_db.erl new file mode 100644 index 0000000..87e4e4c --- /dev/null +++ b/src/ggs_db.erl @@ -0,0 +1,83 @@ +-module(ggs_db). +-export([init/0,stop/0,setItem/4,getItem/3,removeItem/3,key/3,clear/2,clear/1,length/2]). +%-include("ggs_db.hrl"). +-record(data, {key, value}). + +%%----------------------------------------------------- +%% Creation +%%----------------------------------------------------- +init() -> +% mnesia:create_schema([node()]), + mnesia:start(), + mnesia:create_table(data, [{attributes, record_info(fields, data)}]). + +stop() -> + mnesia:stop(). + + +%%----------------------------------------------------- +%% Insertions +%%----------------------------------------------------- +setItem(GameToken,Ns,Key,Value) -> + Fun = fun() -> + Data = #data{key = {GameToken,Ns,Key}, value = Value}, + mnesia:write(Data) + end, + mnesia:transaction(Fun). + + +%%----------------------------------------------------- +%% Deletions +%%----------------------------------------------------- +removeItem(GameToken,Ns,Key) -> + Fun = fun() -> + mnesia:delete({data,{GameToken,Ns,Key}}) + end, + mnesia:transaction(Fun). + +clear(GameToken,Ns) -> + Fun = fun() -> + Keys = mnesia:all_keys(data), + Rest = lists:filter(fun({A,B,_}) -> ((A==GameToken) and (B==Ns)) end, Keys), + lists:map(fun({A,B,C}) -> removeItem(A,B,C) end, Rest) + end, + {atomic, Ret} = mnesia:transaction(Fun), + Ret. + +clear(GameToken) -> + Fun = fun() -> + Keys = mnesia:all_keys(data), + Rest = lists:filter(fun({A,_,_}) -> (A==GameToken) end, Keys), + lists:map(fun({A,B,C}) -> removeItem(A,B,C) end, Rest) + end, + {atomic, Ret} = mnesia:transaction(Fun), + Ret. + +%%----------------------------------------------------- +%% Queries +%%----------------------------------------------------- +getItem(GameToken,Ns,Key) -> + Fun = fun() -> + mnesia:read(data, {GameToken,Ns,Key}) + end, + case mnesia:transaction(Fun) of + {atomic, []} -> {error}; + {atomic, [Ret]} -> Ret#data.value +end. + +length(GameToken,Ns) -> + Fun = fun() -> + Keys = mnesia:all_keys(data), + length(lists:filter(fun({A,B,_}) -> ((A==GameToken) and (B==Ns)) end, Keys)) + end, + {atomic, Ret} = mnesia:transaction(Fun), + Ret. + +key(GameToken,Ns,Position) -> + Fun = fun() -> + Keys = mnesia:all_keys(data), + Rest = lists:filter(fun({A,B,_}) -> ((A==GameToken) and (B==Ns)) end, Keys), + lists:nth(Position, Rest) + end, + {atomic, Ret} = mnesia:transaction(Fun), + Ret. diff --git a/src/ggs_dispatcher.erl b/src/ggs_dispatcher.erl new file mode 100644 index 0000000..ff4f9e7 --- /dev/null +++ b/src/ggs_dispatcher.erl @@ -0,0 +1,66 @@ +-module(ggs_dispatcher). + +-behaviour(gen_server). + +%% API Exports +-export([start_link/1, stop/1]). + +%% gen_server callback exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3, accept_loop/1]). + +-define(SERVER, ?MODULE). + + +%% @doc This module is the entry-point for clients connecting to GGS. This is +%% the module responsible for: +%% * Greeting a connecting client, and associating a socket for it +%% * Spawning a ggs_player for the connecting client, passing the socket + +%% @doc Starts a new dispatcher with the specified port. Registers this +%% dispatcher under the name "ggs_dispatcher". The pid of the dispatcher +%% is returned. +%% @spec start_link(Port) -> Pid +%% Port = Integer +%% Pid = # +start_link(Port) -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []). + +%% @doc Stops the dispatcher with the specified reason. +%% @spec stop(Reason) -> ok. +%% Reason = String +stop(_Reason) -> ggs_logger:not_implemented(). + +%% gen_server callbacks + +%% @doc Initiate the dispatcher. This is called from gen_server +init([Port]) -> + case gen_tcp:listen(Port, [{active, true}, {reuseaddr, true}]) of + {ok, LSock} -> + {ok, accept(LSock), 0}; + {error, Reason} -> + {stop, Reason} + end. + +handle_cast({accepted, _Pid}, State) -> + {noreply, accept(State)}. + +accept_loop({Server, LSocket}) -> + {ok, Socket} = gen_tcp:accept(LSocket), + % Let the server spawn a new process and replace this loop + % with the echo loop, to avoid blocking + gen_server:cast(Server, {accepted, self()}), + {ok, Player} = ggs_player:start(), + gen_tcp:controlling_process(Socket, Player), + ggs_player:save_socket(Player, Socket). + +% To be more robust we should be using spawn_link and trapping exits +accept(LSocket) -> + proc_lib:spawn(?MODULE, accept_loop, [{self(), LSocket}]), + LSocket. + +% These are just here to suppress warnings. +handle_call(_Msg, _Caller, State) -> {noreply, State}. +handle_info(_Msg, Library) -> {noreply, Library}. +terminate(_Reason, _Library) -> ok. +code_change(_OldVersion, Library, _Extra) -> {ok, Library}. \ No newline at end of file diff --git a/src/ggs_dispatcher_backup.erl b/src/ggs_dispatcher_backup.erl new file mode 100644 index 0000000..63a0f53 --- /dev/null +++ b/src/ggs_dispatcher_backup.erl @@ -0,0 +1,64 @@ +-module(ggs_dispatcher_backup). + +-behaviour(gen_server). + +%% API Exports +-export([start_link/0, stop/1]). +-export([back_up/1, retrieve/0]). + +%% gen_server callback exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-define(SERVER, ?MODULE). +%% @doc This module is responsible for keeping a backup of the coodinator +%% at all times. At any point in time a backup can be restored from this +%% module. +%% This module is started by the root supervisor, and is restarted when it +%% crashes. Upon a crash, the backup state is lost in this module, and must +%% be filled in from the ggs_coordinator. + +%% @doc Start a new ggs_coordinator backup instance, and register it under +%% this name. This means that there can only be one instance of this module +%% running. +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%% @doc Stops the server with the specified reason. +%% @spec stop(Reason) -> ok. +%% Reason = String +stop(_Reason) -> ggs_logger:not_implemented(). + +%% API +back_up(State) -> + gen_server:cast(?SERVER, State). + +%% @doc Retrieve the state stored in this server. If there is a state stored +%% here, it is returned to the caller. If the backup server does not have a +%% state stored, it will return the no_state_stored atom. +retrieve() -> + gen_server:call(?SERVER, retrieve). + +%% gen_server callbacks + +%% @doc Initiate the server. This is called from gen_server +init([]) -> + {ok, no_state_stored}. + +handle_call(retrieve, _From, State) -> + {reply, State, State}. + +handle_cast(NewState, _State) -> + {noreply, NewState}. + +handle_info(Msg, State) -> + io:format("Received out of bounds message! "), + erlang:display(Msg), + io:format("~n"), + {noreply, State}. + +terminate(normal, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/ggs_gamevm.erl b/src/ggs_gamevm.erl new file mode 100644 index 0000000..ea06fba --- /dev/null +++ b/src/ggs_gamevm.erl @@ -0,0 +1,149 @@ +%% @doc This module is responsible for running the game VM:s. You can issue +%% commands to a vm using this module. + +-module(ggs_gamevm). +-behaviour(gen_server). + +-include_lib("lib/erlv8/include/erlv8.hrl"). + +%% API +-export([start/0,start_link/1,stop/1]). +-export([define/2, player_command/4, call_js/2]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2]). +-export([code_change/3, handle_info/2, terminate/2]). + +-record(state, { vm, global, table } ). + +%% Macros +-define(LOCALSTORAGE, "localstorage"). +-define(WORLD, "world"). +-define(PLAYER, "player"). + +%% ---------------------------------------------------------------------- +% API implementation + +start() -> + Table = "table", + start_link(Table). + +%% @doc Create a new VM process. The process ID is returned and can be used +%% with for example the define method of this module. +start_link(Table) -> + {ok, GameVM} = gen_server:start_link(?MODULE, [Table], []), + GameVM. + +%% @doc Define some new code on the specified VM, returns the atom ok. +define(GameVM, SourceCode) -> + gen_server:cast(GameVM, {define, SourceCode}). + +%% @doc Execute a player command on the specified VM. This function is +%% asynchronous, and returns ok. +%% @spec player_command(GameVM, User, Command, Args) -> ok +%% GameVM = process IS of VM +%% Player = the player running the command +%% Command = a game command to run +%% Args = arguments for the Command parameter +player_command(GameVm, Player, Command, Args) -> + gen_server:cast(GameVm, {player_command, Player, Command, Args}). + +%% @private +% only for tests +call_js(GameVM, SourceCode) -> + gen_server:call(GameVM, {eval, SourceCode}). + +% @doc stops the gamevm process +stop(GameVM) -> + gen_server:cast(GameVM, stop). + + +init([Table]) -> + process_flag(trap_exit, true), + application:start(erlv8), % Start erlv8 FIXME: don't use a new VM every time, only a context + {ok, VM} = erlv8_vm:start(), % Create a JavaScript vm + Global = erlv8_vm:global(VM), % Retrieve JS global + ggs_db:init(), % Initialize the database + NewGlobal = expose(Global, Table), + {ok, #state { vm = VM, global = NewGlobal, table = Table }}. + +% @doc Exposes some GGS functions to JavaScript +expose(Global, Table) -> + Global:set_value("GGS", erlv8_object:new([ + {"localStorage", erlv8_object:new([ + {"setItem", fun(#erlv8_fun_invocation{}, [Key, Val])-> ggs_db:setItem(Table, "localStorage", Key, Val) end}, + {"removeItem", fun(#erlv8_fun_invocation{}, [Key])-> ggs_db:removeItem(Table, "localStorage", Key) end}, + {"clear", fun(#erlv8_fun_invocation{}, [])-> ggs_db:clear(Table, "localStorage") end}, + {"getItem", fun(#erlv8_fun_invocation{}, [Key])-> ggs_db:getItem(Table, "localStorage", Key) end}, + {"length", fun(#erlv8_fun_invocation{}, [])-> ggs_db:length(Table, "localStorage") end}, + {"key", fun(#erlv8_fun_invocation{}, [Position])-> ggs_db:key(Table, "localStorage", Position) end} + ])}, + {"world", erlv8_object:new([ + {"setItem", fun(#erlv8_fun_invocation{}, [Key, Val])-> ggs_db:setItem(Table, "world", Key, Val) end}, + {"removeItem", fun(#erlv8_fun_invocation{}, [Key])-> ggs_db:removeItem(Table, "world", Key) end}, + {"clear", fun(#erlv8_fun_invocation{}, [])-> ggs_db:clear(Table, "world") end}, + {"getItem", fun(#erlv8_fun_invocation{}, [Key])-> ggs_db:getItem(Table, "world", Key) end}, + {"length", fun(#erlv8_fun_invocation{}, [])-> ggs_db:length(Table, "world") end}, + {"key", fun(#erlv8_fun_invocation{}, [Position])-> ggs_db:key(Table, "world", Position) end} + ])}, + {"sendCommand", fun(#erlv8_fun_invocation{}, [Player, Command, Args])-> + ggs_table:send_command(Table, Player, {Command, Args}) + end}, + {"sendCommandToAll", fun(#erlv8_fun_invocation{}, [Command, Args])-> + ggs_table:notify_all_players(Table, {Command, Args}) + end}, + {"serverInfo", fun(#erlv8_fun_invocation{}, []) -> + PlayerList = ggs_coordinator:get_all_players(), + integer_to_list(length(PlayerList)) + end}, + {"log", fun(#erlv8_fun_invocation{}, [Arg]) -> erlang:display(Arg) end } + %{"setTimeout", fund(#erlv8_fun_invocation{}, [Time, FunctionName])-> setTimeout(Time, FunctionName) end} + ])). + +%% Retrieve a JavaScript file from hard drive and return it +%read_js_file(Filename) -> +% {ok, JSApp} = file:read_file(Filename), +% erlang:binary_to_list(JSApp). + +%% private +% only needed for the tests +handle_call({eval, SourceCode}, _From, #state { vm = VM } = State) -> + {ok, Ret} = erlv8_vm:run(VM, SourceCode), + {reply, Ret, State}. + +handle_cast({define, SourceCode}, #state { table = Table, vm = VM } = State) -> + {ok, _Ret} = erlv8_vm:run(VM, SourceCode), + ggs_table:notify_all_players(Table, {"defined", "ok"}), + {noreply, State}; + +handle_cast({player_command, Player, Command, Args}, #state { vm = VM } = State) -> + Js = "playerCommand('" ++ Player ++ "', '" ++ js_escape(Command) ++ "', '" ++ js_escape(Args) ++ "');", + {ok, _Ret} = erlv8_vm:run(VM, Js), + {noreply, State}; + +handle_cast(stop, #state { vm = VM } = State) -> + erlv8_vm:stop(VM), + {stop, normal, State}; + +handle_cast(Msg, S) -> + error_logger:error_report([unknown_msg, Msg]), + {noreply, S}. + +%% @private +code_change(_, State, _) -> + {ok, State}. + +%% @private +handle_info(Msg, S) -> + error_logger:error_report([unknown_msg, Msg]), + {noreply, S}. + + +%% @private +terminate(_, _) -> + ok. + +%% @private +%% This function is probably important. Do not touch! +js_escape(S) -> + lists:flatmap(fun($\') -> [$\\, $\']; (X) -> [X] end, S). diff --git a/src/ggs_gamevm_e.erl b/src/ggs_gamevm_e.erl new file mode 100644 index 0000000..1ac9fc0 --- /dev/null +++ b/src/ggs_gamevm_e.erl @@ -0,0 +1,69 @@ +-module(ggs_gamevm_e). +-export([start_link/1, define/2, player_command/4]). +%% @doc This module is responsible for running the game VM:s. You can issue +%% commands to a vm using this module. + +%% @doc Create a new VM process. The process ID is returned and can be used +%% with for example the define method of this module. +start_link(Table) -> + PortPid = spawn( fun() -> + loop(Table) + end ), + PortPid. + +%% @doc Define some new code on the specified VM, returns the atom ok. +define(GameVM, SourceCode) -> + GameVM ! {define,SourceCode}, + ok. + +%% @doc Execute a player command on the specified VM. This function is +%% asynchronous, and returns ok. +%% @spec player_command(GameVM, User, Command, Args) -> ok +%% GameVM = process IS of VM +%% Player = the player running the command +%% Command = a game command to run +%% Args = arguments for the Command parameter +player_command(GameVM, Player, Command, Args) -> + Ref = make_ref(), + GameVM ! {player_command, Player, Command, Args, self(), Ref}, + ok. + +%% Helper functions + +loop(Table) -> + receive + {define, _SourceCode} -> + io:format("GameVM_e can't define functions, sorry!~n"), + loop(Table); + {player_command, Player, Command, Args, _From, _Ref} -> + erlang:display(Command), + do_stuff(Command, Args, Player, Table), + loop(Table) + end. + +do_stuff(Command, Args, PlayerToken, Table) -> + case Command of + "greet" -> + ggs_table:notify_player(Table, PlayerToken, server, {"chat", "Hello there!\n"}); + "chat" -> + case ggs_db:getItem(Table, nicks, PlayerToken) of + {error} -> + Nick = "Noname"; + Other -> + Nick = Other + end, + ggs_table:notify_all_players(Table, {"chat", "<"++Nick++"> "++ Args ++ "\n"}); + "uname" -> + Uname = os:cmd("uname -a"), + ggs_table:notify_player(Table, PlayerToken, server, {"chat", Uname}); + "lusers" -> + {ok, Players} = ggs_table:get_player_list(Table), + Nicks = lists:map(fun (P) -> ggs_db:getItem(Table, nicks, P) end, Players), + ggs_table:notify_player(Table, PlayerToken, server, + {"lusers", io_lib:format("~p\n",[Nicks])}); + "nick" -> + ggs_db:setItem(Table,nicks,PlayerToken,Args), + io:format("Changing nickname of ~p to ~p.", [PlayerToken, Args]); + _Other -> + ggs_table:notify_player(Table, PlayerToken, server, "I don't know that command..\n") + end. diff --git a/src/ggs_gamevm_p.erl b/src/ggs_gamevm_p.erl new file mode 100644 index 0000000..7fcad8e --- /dev/null +++ b/src/ggs_gamevm_p.erl @@ -0,0 +1,312 @@ +%% @doc This module is responsible for running the game VM:s. You can issue +%% commands to a vm using this module. + +-module(ggs_gamevm_p). +-behaviour(gen_server). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, { port, table } ). + +%% API +-export([start_link/1, define/2, player_command/4, stop/1, call_js/2]). + + +%% ---------------------------------------------------------------------- +% API implementation + +%% @doc Create a new VM process. The process ID is returned and can be used +%% with for example the define method of this module. +start_link(Table) -> + erlang_js:start(), %% @TODO: should only be done once + {ok, Pid} = gen_server:start_link(?MODULE, [Table], []), + Pid. + +%% @doc Define some new code on the specified VM, returns the atom ok. +define(GameVM, SourceCode) -> + gen_server:cast(GameVM, {define, SourceCode}). + +%% @doc Execute a player command on the specified VM. This function is +%% asynchronous, and returns ok. +%% @spec player_command(GameVM, User, Command, Args) -> ok +%% GameVM = process IS of VM +%% Player = the player running the command +%% Command = a game command to run +%% Args = arguments for the Command parameter +player_command(GameVM, Player, Command, Args) -> + gen_server:cast(GameVM, {player_command, Player, Command, Args}). + +%% @private +% only for tests +call_js(GameVM, SourceCode) -> + gen_server:call(GameVM, {eval, SourceCode}). + +% @doc stops the gamevm process +stop(GameVM) -> + gen_server:cast(GameVM, stop). + + +%% ---------------------------------------------------------------------- + +%% @private +init([Table]) -> + process_flag(trap_exit, true), + {ok, Port} = js_driver:new(), + {ok, JSAPISourceCode} = file:read_file("src/ggs_api.js"), + ok = js:define(Port, JSAPISourceCode), + InitGGSJSString = "var GGS = new _GGS(" ++ Table ++ ");", + ok = js:define(Port, list_to_binary(InitGGSJSString)), + {ok, #state { port = Port, table = Table }}. + +%% private +% only needed for the tests +handle_call({eval, SourceCode}, _From, #state { port = Port } = State) -> + {ok, Ret} = js:eval(Port, list_to_binary(SourceCode)), + {reply, Ret, State}. + +%% @private +handle_cast({define, SourceCode}, #state { port = Port, table = Table } = State) -> + Ret = js:define(Port, list_to_binary(SourceCode)), + case Ret of + ok -> + ggs_table:notify_all_players(Table, {"defined", "ok"}), + {noreply, State}; + Other -> + ggs_table:notify_all_players(Table, {"defined", "error " ++ Other}), + {noreply, State} + end; +handle_cast({player_command, Player, Command, Args}, #state { port = _Port, table = Table } = State) -> + intern_player_command(Table, Player, Command, Args), + {noreply, State}; +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(Msg, S) -> + error_logger:error_report([unknown_msg, Msg]), + {noreply, S}. + +%% @private +handle_info(Msg, S) -> + error_logger:error_report([unknown_msg, Msg]), + {noreply, S}. + +%% @private +terminate(_Reason, _State) -> + ok. + +%% @private +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%js_escape(S) -> +% lists:flatmap(fun($\') -> [$\\, $\']; (X) -> [X] end, S). + + +intern_player_command(Table, Player, Command, _Args) -> + case Command of + "ready" -> + intern_add_player(Table, Player); + "up" -> + intern_up(Table, Player); + "down" -> + intern_down(Table, Player); + "start" -> + intern_start(Table, Player) + end. + +intern_add_player(Table, Player) -> + {ok, PlayerList} = ggs_table:get_player_list(Table), + case length(PlayerList) of + 1 -> ok; + %erlang:display("A player joined"); + 2 -> + %erlang:display("Player 2 joined"), + [P1,P2] = PlayerList, + %erlang:display(PlayerList), + %erlang:display("P1: joining"), + ggs_db:setItem(Table, local_storage, P1, player1), + %erlang:display(ggs_db:getItem(Table, local_storage, P1)), + ggs_db:setItem(Table, local_storage, player1_y, 50), + ggs_table:send_command(Table, P1, {"welcome", int2str(1)}), + ggs_table:notify_all_players(Table, {"player1_y", int2str(50)}), + %erlang:display("P2: joining"), + ggs_db:setItem(Table, local_storage, P2, player2), + %erlang:display(ggs_db:getItem(Table, local_storage, P2)), + ggs_db:setItem(Table, local_storage, player2_y, 50), + ggs_table:send_command(Table, P2, {"welcome", int2str(2)}), + ggs_table:send_command(Table, P2, {"player1_y", int2str(50)}), + ggs_table:notify_all_players(Table, {"player2_y", int2str(50)}); + _Other -> + ggs_table:send_command(Table, Player, {"not_welcome", ""}) + end. + + +intern_up(Table, Player) -> + case ggs_db:getItem(Table, local_storage, Player) of + player1 -> + %erlang:display("P1: command up"), + Y = ggs_db:getItem(Table, local_storage, player1_y), + NewY = Y - 10, + case NewY >= 0 of + true -> + ggs_db:setItem(Table, local_storage, player1_y, NewY), + ggs_table:notify_all_players(Table, {"player1_y", int2str(NewY)}); + _Other -> + ggs_table:send_command(Table, Player, {"notice", "Already on top"}) + end; + player2 -> + %erlang:display("P2: command up"), + Y = ggs_db:getItem(Table, local_storage, player2_y), + NewY = Y - 10, + case NewY >= 0 of + true -> + ggs_db:setItem(Table, local_storage, player2_y, NewY), + ggs_table:notify_all_players(Table, {"player2_y", int2str(NewY)}); + _Other -> + ggs_table:send_command(Table, Player, {"notice", "Already on top"}) + end + end. + +intern_down(Table, Player) -> + case ggs_db:getItem(Table, local_storage, Player) of + player1 -> + %erlang:display("P1: command down"), + Y = ggs_db:getItem(Table, local_storage, player1_y), + NewY = Y + 10, + case NewY =< 100 of + true -> + ggs_db:setItem(Table, local_storage, player1_y, NewY), + ggs_table:notify_all_players(Table, {"player1_y", int2str(NewY)}); + _Other -> + ggs_table:send_command(Table, Player, {"notice", "Already on bottom"}) + end; + player2 -> + %erlang:display("P2: command down"), + Y = ggs_db:getItem(Table, local_storage, player2_y), + NewY = Y + 10, + case NewY =< 100 of + true -> + ggs_db:setItem(Table, local_storage, player2_y, NewY), + ggs_table:notify_all_players(Table, {"player2_y", int2str(NewY)}); + _Other -> + ggs_table:send_command(Table, Player, {"notice", "Already on bottom"}) + end + end. + +intern_start(Table, Player) -> + %erlang:display(Player), + %erlang:display(ggs_db:getItem(Table, local_storage, Player)), + case ggs_db:getItem(Table, local_storage, Player) of + player1 -> + ggs_db:setItem(Table, local_storage, player1_ready, true), + ggs_db:setItem(Table, local_storage, player1_points, 0), + case ggs_db:getItem(Table, local_storage, player2_ready) of + true -> + %erlang:display("P1 ready, start game."), + ggs_table:notify_all_players(Table, {"game", "start"}), + ggs_db:setItem(Table, local_storage, ball, {50,50,1,1}), + Pid = spawn(fun() -> game_loop([Table]) end), + Pid ! tick; + _Other -> + %erlang:display("P1 ready, waiting."), + ggs_table:send_command(Table, Player, {"game", "wait"}) + end; + player2 -> + ggs_db:setItem(Table, local_storage, player2_ready, true), + ggs_db:setItem(Table, local_storage, player2_points, 0), + case ggs_db:getItem(Table, local_storage, player1_ready) of + true -> + %erlang:display("P2 ready, start game."), + ggs_table:notify_all_players(Table, {"game", "start"}), + ggs_db:setItem(Table, local_storage, ball, {50,50,-1,-1}), + GameLoop = spawn(fun() -> game_loop([Table]) end), + GameLoop ! tick; + _Other -> + %erlang:display("P2 ready, waiting."), + ggs_table:send_command(Table, Player, {"game", "wait"}) + end; + _Other -> + ok + %erlang:display(Other) + end. + +game_loop([Table]) -> + receive + tick -> + {BX, BY, SX, SY} = step_ball(ggs_db:getItem(Table, local_storage, ball)), + Ball = {BX, BY, SX, SY}, + ggs_db:setItem(Table, local_storage, ball, Ball), + ggs_table:notify_all_players(Table, {"ball", int2str(BX) ++ "," ++ int2str(BY)}), + check_ball(Table, Ball), + timer:send_after(50, tick), + game_loop([Table]); + 'EXIT' -> + exit(normal) + end. + +int2str(Int) -> + lists:flatten(io_lib:format("~p", [Int])). + +step_ball({BX, BY, SX, SY}) -> + Ball = {BX + SX, BY + SY, SX, SY}, + Ball. + +check_ball(Table, {BX, BY, SX, SY}) -> + % check up and down bounds + case (BY > 90) or (BY < 0) of + true -> + NewSY = -SY; + false -> + NewSY = SY + end, + + % check intersection with a player + P1Y = ggs_db:getItem(Table, local_storage, player1_y), + P2Y = ggs_db:getItem(Table, local_storage, player2_y), + case (check_intersect({0, P1Y, 10, 30}, {BX, BY, 10, 10}) or check_intersect({99, P2Y, 10, 30}, {BX, BY, 10, 10})) of + true -> + NewSX = -SX, + case NewSX of + -1 -> + ggs_table:notify_all_players(Table, {"sound", "ping"}); + 1 -> + ggs_table:notify_all_players(Table, {"sound", "pong"}) + end; + false -> + NewSX = SX + end, + ggs_db:setItem(Table, local_storage, ball, {BX, BY , NewSX, NewSY}), + + + % check for point player1 + case BX > 100 of + true -> + Player1Points = ggs_db:getItem(Table, local_storage, player1_points), + NewPlayer1Points = Player1Points + 1, + ggs_db:setItem(Table, local_storage, player1_points, NewPlayer1Points), + ggs_table:notify_all_players(Table, {"player1_points", int2str(NewPlayer1Points)}), + exit(normal); + false -> + ok + end, + + % check for point player2 + case BX < 0 of + true -> + Player2Points = ggs_db:getItem(Table, local_storage, player2_points), + NewPlayer2Points = Player2Points + 1, + ggs_db:setItem(Table, local_storage, player2_points, NewPlayer2Points), + ggs_table:notify_all_players(Table, {"player2_points", int2str(NewPlayer2Points)}), + exit(normal); + false -> + ok + end. + + +check_intersect({AX1, AY1, AW, AH}, {BX1, BY1, BW, BH}) -> + AX2 = AX1 + AW, + AY2 = AY1 + AH, + BX2 = BX1 + BW, + BY2 = BY1 + BH, + (AX1 < BX2) and (AX2 > BX1) and (AY1 < BY2) and (AY2 > BY1). diff --git a/src/ggs_logger.erl b/src/ggs_logger.erl new file mode 100644 index 0000000..49f5abc --- /dev/null +++ b/src/ggs_logger.erl @@ -0,0 +1,8 @@ +-module(ggs_logger). +-export([not_implemented/0, log/2]). + +not_implemented() -> + exit(not_implemented). + +log(Format, Args) -> + error_logger:info_msg(Format, Args). diff --git a/src/ggs_player.erl b/src/ggs_player.erl new file mode 100644 index 0000000..a7b9b36 --- /dev/null +++ b/src/ggs_player.erl @@ -0,0 +1,132 @@ +%% @doc This module handles communication between a player and GGS. This module is +%% responsible for: +%% * The storage of the player socket, player token and a table token. +%% * Ability to fetch a player token. +%% * Forwarding messages from players to the game +%% * Remove a player from GGS + +-module(ggs_player). +-behaviour(gen_server). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-export([start/0, stop/1, notify/3, notify_game/2, get_token/1, save_socket/2]). + +-vsn(1.0). + +-record(state, { + token, + socket, + table, + protocol }). + +%% @doc Spawns a process representing the player in GGS. Takes the player socket as +%% an argument for storage and later usage. Creates a unique player token +%% identifying the player. +%% @spec start_link(Socket::socket()) -> {ok, Pid} | {error, Reason} +start() -> + gen_server:start(?MODULE, [], []). + +init([]) -> + {ok, Protocol} = ggs_protocol:start_link(), + {ok, Token} = ggs_coordinator:join_lobby(), + + State = #state{ + token = Token, + protocol = Protocol + }, + {ok, State}. + +save_socket(Player, Socket) -> + gen_server:cast(Player, {save_socket, Socket}). + +%% @doc Handles incoming messages from the GGS and forwards them through the player +%% socket to the player. +%% @spec notify(Player::Pid(), From::Pid(), +%% {Command::String(), Message::string()}) -> ok +notify(Player, _From, Message) -> + gen_server:cast(Player, {notify, Message}). + +%% @doc Handles incomming messages form a client and forwards them +%% through to the game_vm +notify_game(Player, Message) -> + gen_server:cast(Player, Message). + +%% @doc Get the player token uniquely representing the player. +%% @spec get_token() -> string() +get_token(_Player) -> + ggs_logger:not_implemented(). + +%% @doc Properly terminates the player process. The player token will be lost +%% together with the table token. It should also close the player socket and the +%% process should return in the end. +%% @spec stop(Table::pid()) -> Reason::string() +stop(Player) -> + gen_server:cast(Player, stop). + +%% Internals +handle_call(_Request, _From, St) -> {stop, unimplemented, St}. + +handle_cast({save_socket, Socket}, State) -> + {noreply, State#state { socket = Socket } }; +handle_cast({tcp, _Socket, Data}, #state { protocol = Protocol } = State) -> + ggs_protocol:parse(Protocol, Data), + {noreply, State}; +handle_cast({notify, Message}, #state { socket = Socket } = State) -> + gen_tcp:send(Socket, ggs_protocol:create_message(Message)), + {noreply, State}; +handle_cast({srv_cmd, "hello", _Headers, TableToken}, State) -> + Table = case TableToken of + "" -> + case ggs_coordinator:create_table() of + {ok, NewTableToken, TablePid} -> + ggs_coordinator:join_table(NewTableToken), + {ok, NewTableToken, TablePid}; + E -> + E + end; + _ -> + ggs_coordinator:join_table(TableToken) + end, + erlang:display(Table), + case Table of + {error, Error} -> + ggs_player:notify(self(), self(), {"error", atom_to_list(Error)}), + {noreply, State}; + {ok, TT, TPid} -> + ShallDefine = case ggs_table:already_defined(TPid) of + true -> "true"; + false -> "false" + end, + ggs_player:notify(self(), self(), {"hello", State#state.token ++ "," ++ ShallDefine ++ "," ++ TT}), + {noreply, State#state{ table = TPid } } + end; +handle_cast({srv_cmd, "define", _Headers, Data}, #state { table = Table } = State) -> + ggs_table:notify(Table, self(), {server, define, Data}), + {noreply, State}; +handle_cast({game_cmd, Command, _Headers, Data}, #state { table = Table } = State) -> + ggs_table:notify(Table, self(), {game, Command, Data}), + {noreply, State}; +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(_Request, St) -> + {stop, unimplemented1, St}. + +handle_info({tcp, _Socket, Data}, #state { protocol = Protocol } = State) -> + ggs_protocol:parse(Protocol, Data), + {noreply, State}; +handle_info({tcp_closed, _Socket}, State) -> + erlang:display("Client disconnected, but THIS IS NOT SUPPORTED YET!~n"), + gen_server:cast(self(), stop), + {noreply, State}. + +terminate(Reason, State) -> + erlang:display(Reason), + ggs_protocol:stop(State#state.protocol), + %ggs_table:remove_player(State#state.table, self()), + gen_tcp:close(State#state.socket), + % ggs_coordinator:remove_player(self(), self()), % not implemented yet + ok. + +code_change(_OldVsn, St, _Extra) -> + {ok, St}. diff --git a/src/ggs_protocol.erl b/src/ggs_protocol.erl index c470beb..437a404 100644 --- a/src/ggs_protocol.erl +++ b/src/ggs_protocol.erl @@ -1,36 +1,138 @@ -%% This is a parser for JSON implemented using mochijson2 -%% Don't feed it anything other than valid JSON. +%% Parse a string formatted with the GGS protocol using +%% an FSM. Each char is put into the FSM, which incrementally +%% builds a list of strings which represent the complete +%% message. -module(ggs_protocol). --export([parse/1]). +-export([parse/2, getToken/1, create_message/4, create_message/1, + expect_headers/2, expect_data_section/2, + expect_headers/3, expect_data_section/3]). -parse(Data) -> - Message = string:tokens(Data, " "), - io:format(Message), - case Message of - ["__get_vms"] -> - {vms}; - [RefID, "__error", Size, Message ] -> - {ok, you_said_error}; - [_, "__boot", _ ] -> - {ok, you_said_boot}; - [RefID, "__stop", _] -> - {ok, you_said_stop}; - [RefID, "__start", _] -> - {ok, you_said_start}; - ["__hello", _] -> - {hello}; - [RefID, "__define",_, JavaScript ] -> - {ok, you_said_define}; - [RefID, "__echo", Length, Msg ] -> - {Ref, _} = string:to_integer(RefID), - {echo, Ref, Length, Msg}; - [RefID, Command, _, Parameter ] -> - {cmd, Command, Parameter}; - %% Debugging tools, not for production use - ["__crash"] -> - {crash, 0}; - %% End debugging tools - Other -> - {out_of_bounds, Other} +%% tests +-export([to_dictionary/2]). + +% gen_fsm callbacks.. +-export([init/1, handle_info/2, handle_event/3, terminate/2, code_change/3, start_link/0, stop/1]). + + +%% API Functions +parse(Protocol, Data) -> + lists:foreach(fun(X) -> gen_fsm:sync_send_event(Protocol, {char, X}) end, Data). + + +start_link() -> + gen_fsm:start_link(?MODULE, [], []). + +stop(Protocol) -> + gen_fsm:send_all_state_event(Protocol, stop). + + +% Start state: {[""],0}, meaning: +% - Start with no strings parsed +% - Start with a data-section-lengths of 0 +init([]) -> + {ok, expect_headers, {[""], 0}}. + +getToken(Parsed) -> + case lists:keyfind(token, 1, Parsed) of + {_, Value} -> + Value; + false -> + false end. + +create_message({Command, Data}) -> + create_message(Command, "text", "text", Data). + +%% Assemble a message which can b +%e used as a reply to a client +create_message(Cmd, Enc, Acc, Data) -> + ggs_stats:message(server), + Length = integer_to_list(string:len(Data)), + Msg = "Client-Command: " ++ Cmd ++ "\n" ++ + "Client-Encoding: " ++ Enc ++ "\n" ++ + "Content-Size: " ++ Length ++ "\n" ++ + "GGS-Version: 1.0\n" ++ + "Accept: " ++ Acc ++ "\n" ++ + "\n" ++ + Data, + Msg. + +%%% Transitions +expect_headers(_Event, State) -> + {next_state, expect_headers, State}. +expect_data_section(_Event, State) -> + {next_state, expect_data_section, State}. + + +%%% End transitions +expect_headers({char, $\n}, {Pid,_}, {Strings, Remains}) -> + [LastMessage|_] = Strings, + case LastMessage of + "" -> % We have a data section.. Last line should thus be the content length. + % FIXME: the Content-Length doesn't have to be the last Header line + [LastMessage, SecondLast | Rest] = Strings, + case re:split(SecondLast, ": ", [{return, list}]) of + ["Content-Length", X] -> + {Int,_} = string:to_integer(X), + case Int of + 0 -> ggs_player:notify_game(Pid, prettify(to_dictionary([SecondLast|Rest], []), [])), + {reply, ok, expect_headers, {[""], 0}}; + _ -> {reply, ok, expect_data_section, {[""|Strings], Int -1}} + end; + _Other -> ok + end; + _Other -> + {reply,ok,expect_headers, {[""|Strings], Remains}} + end; + +expect_headers({char, Char}, _From, {[Current|Rest], Remains}) -> + NewCurrent = Current ++ [Char], + {reply, ok, expect_headers, {[NewCurrent|Rest], Remains}}. + + +expect_data_section({char, Char}, From, {Strings, Remains}) -> + case Remains of + 0 -> + [LastMsg,_|Rest] = Strings, + {Pid,_} = From, + ggs_player:notify_game(Pid, prettify(to_dictionary(Rest, []), LastMsg ++ [Char])), + {reply, ok, expect_headers, {[""], 0}}; + _Other -> [LastMsg|Rest] = Strings, + NewMsg = LastMsg ++ [Char], + {reply, ok, expect_data_section, {[NewMsg|Rest], Remains-1}} + end. + +%handle_call(_Msg, _From, State) -> +% {noreply, State}. + +handle_event(stop, _StateName, StateData) -> + {stop, normal, StateData}. +handle_info(_Msg, State) -> + {noreply, State}. +terminate(_Reason, _State) -> + ok. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +prettify(Args, Data) -> + ggs_stats:message(client), + case lists:keyfind("Server-Command", 1, Args) of + {_, Value} -> + {srv_cmd, Value, Args, Data}; + _Other -> + case lists:keyfind("Game-Command", 1, Args) of + {_, Value} -> + {game_cmd, Value, Args, Data}; + _Other -> + ok + end + end. + +to_dictionary([], Dict) -> + Dict; +to_dictionary([S|Strings], Dict) -> + [First, Snd] = re:split(S, ": ", [{return, list}]), + to_dictionary(Strings, [{First, Snd}|Dict]). + diff --git a/src/ggs_server.erl b/src/ggs_server.erl index 584dfdf..a8195ea 100644 --- a/src/ggs_server.erl +++ b/src/ggs_server.erl @@ -1,24 +1,15 @@ -%%%---------------------------------------------------- -%%% @author Jonatan Pålsson -%%% @copyright 2010 Jonatan Pålsson -%%% @end -%%%---------------------------------------------------- - -module(ggs_server). -behaviour(gen_server). -% import --import(ggs_connection). - %% API -export([start_link/1, start_link/0, - get_count/0, stop/0 ]). %% gen_server callbacks --export([terminate/2, code_change/3]). +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). @@ -35,25 +26,11 @@ %% @end %%----------------------------------------------------- start_link(Port) -> - process_flag(trap_exit, true), gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []). start_link() -> start_link(?DEFAULT_PORT). -%%----------------------------------------------------- -%% @doc Fetches the number of requests made to this server -%% @spec get_count() -> {ok, Count} -%% where -%% Count = integer() -%% @end -%%----------------------------------------------------- -get_count() -> gen_server:call(?SERVER, get_count). -_crash() -> gen_server:call(?SERVER, _crash). -_vms() -> gen_server:call(?SERVER, _vms). -_hello() -> gen_server:call(?SERVER, _hello). -_echo() -> gen_server:call(?SERVER, {_echo, RefID, _, MSG}). - %%----------------------------------------------------- %% @doc Stops the server. %% @spec stop() -> ok @@ -67,45 +44,36 @@ stop() -> %%----------------------------------------------------- init([Port]) -> - {ok, LSock} = gen_tcp:listen(Port, [{active, true}, - {reuseaddr, true}]), - {ok, #state{port = Port, lsock = LSock}, 0}. + case gen_server:call(ggs_backup, get_backup) of + {backup_state, not_initialized} -> + {ok, LSock} = gen_tcp:listen(Port, [{active, true}, + {reuseaddr, true}]), + {ok, #state{port = Port, lsock = LSock}, 0}; + {backup_state, State} -> + {ok, LSock} = gen_tcp:listen(Port, [{active, true}, + {reuseaddr, true}]), + {ok, State#state{lsock = LSock}, 0} + end. -handle_call(get_count, _From, State) -> - {reply, {ok, State#state.client_vm_map}, State}; +handle_call({backup_state, OldState}, _From, _State) -> + io:format("Received old state from backup~n"), + {noreply, OldState}. -handle_call(_crash, _From, State) -> - Zero/10. - {reply, sdas , State}; - -%handle_call(_hello, _From, State) -> -% Client = getRef(); -% send(Socket, Client, "_ok_hello"), -% {Client, JVSM) -%{reply, Client, State}; - -%handle_call(_vms, _From, State) -> -% send(Socket, "RefID", State) -%{reply, , State}; - -%handle_call(_echo, RefID, _, MSG) -> -%{reply, ,State}; - -handle_call(_Message, _From, State) - {reply, error, State}. - -handle_cast(stop, State) -> - {stop, normal, State}. handle_info({tcp, Socket, RawData}, State) -> - NewState = do_JSCall(Socket, RawData, State), - OldMap = State#state.client_vm_map, - io:format("Old map: ~p NewState: ~p~n", [OldMap, NewState]), - {noreply, State#state{client_vm_map = OldMap ++ [NewState]}}; + ggs_protocol:parse(RawData), + {noreply, State#state{lsock = Socket}}; + +handle_info({tcp_closed, Socket}, State) -> + gen_tcp:close(Socket), + {stop, "Client closed socket", State}; handle_info(timeout, #state{lsock = LSock} = State) -> {ok, _Sock} = gen_tcp:accept(LSock), - {noreply, State}. + {noreply, State}; + +handle_info(Other, _State) -> + erlang:display(Other). terminate(_Reason, _State) -> ok. @@ -116,46 +84,59 @@ code_change(_OldVsn, State, _Extra) -> %%----------------------------------------------------- %% Internal functions %%----------------------------------------------------- -do_JSCall(Socket, Data, State) -> - JSVM = js_runner:boot(), - js_runner:define(JSVM, "function userCommand(cmd, par) {return cmd+' '+ par}"), - Parsed = ggs_protocol:parse(Data), - NewState = case Parsed of - {cmd, Command, Parameter} -> - % Set the new state to [] - Ret = js_runner:call(JSVM, "userCommand", - [list_to_binary(Command), - list_to_binary(Parameter)]), - connection:send(Socket, "RefID", "JS says: ", Ret), - []; - % Set the new state to the reference generated, and JSVM associated - {hello} -> - Client = getRef(), - connection:send(Socket, Client, "__ok_hello"), - {Client, JSVM}; - {echo, RefID, _, MSG} -> - connection:send(Socket, RefID, "Your VM is ", getJSVM(RefID, State)), - []; - {crash, Zero} -> - 10/Zero; - {vms} -> - connection:send(Socket, "RefID", State); - % Set the new state to [] - Other -> - ggs_connection:send(Socket, "RefID", "__error"), - [] - end, - % Return the new state - NewState. +handle_cast(stop, State) -> + {stop, normal, State}; + +% Handle javascript defines +handle_cast({srv_cmd, "define", Headers, Data}, State) -> + Token = ggs_protocol:getToken(Headers), + GameVM = getJSVM(Token, State), + ggs_vm_runner:define(GameVM, Data), + send(State#state.lsock, "Token", "Okay, defined that for you!"), + {noreply, State}; + +% Handle javascript calls +handle_cast({srv_cmd, "call", Headers, Data}, State) -> + Token = ggs_protocol:getToken(Headers), + io:format("Got call request: ~p~n", [Data]), + GameVM = getJSVM(Token, State), + Ret = ggs_vm_runner:user_command(GameVM, "User", Data, []), + send(State#state.lsock, Token, "JS says:", binary_to_list(Ret)), + {noreply, State}; + +% Set the new state to the reference generated, and JSVM associated +%handle_cast({server, hello, Headers}, State) -> +handle_cast({srv_cmd, "hello", Headers, _Data}, State) -> + GameToken = case proplist:get_value(game_token, Headers) of + undefined -> getNewToken(); + GT -> GT + end, + ClientToken = getNewToken(), + OldMap = State#state.client_vm_map, + GameVM = getJSVM(ClientToken, State), + NewState = State#state{client_vm_map = OldMap ++ [{ClientToken, GameVM, GameToken}]}, + gen_server:cast(ggs_backup, {set_backup, NewState}), + {noreply, NewState}. + + %%----------------------------------------------------- %% Helpers %%----------------------------------------------------- -getRef() -> - {A1,A2,A3} = now(), - random:seed(A1, A2, A3), - random:uniform(1000). +getNewToken() -> + string:strip(os:cmd("uuidgen"), right, $\n ). -getJSVM(RefID, State) -> +getJSVM(ClientToken, State) -> VMs = State#state.client_vm_map, - {value, {_,VM}} = lists:keysearch(RefID, 1, VMs), + {value, {_,VM}} = lists:keysearch(ClientToken, 1, VMs), VM. + +%getGameVMByGameToken(GameToken, State) -> +% VMs = State#state.client_vm_map, +% {value, {_,VM}} = lists:keysearch(GameToken, 3, VMs), +% VM. + +send(Socket, RefID, String) -> + gen_tcp:send(Socket, string:join([RefID,String,"\n"], " ")). + +send(Socket, RefID, String1, String2) -> + gen_tcp:send(Socket, string:join([RefID,String1, String2,"\n"], " ")). diff --git a/src/ggs_stats.erl b/src/ggs_stats.erl new file mode 100644 index 0000000..02d61d6 --- /dev/null +++ b/src/ggs_stats.erl @@ -0,0 +1,66 @@ +-module(ggs_stats). +-export([start_link/0, message/1, print/0, tick/0]). +-behaviour(gen_server). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). +-vsn(0). + +-record(ate, { + server_messages = 0, + client_messages = 0, + stats = [] + }). +-define(SERVER, ?MODULE). + +message(Type) -> + gen_server:cast(ggs_stats, {add_one, Type}). + +print() -> + gen_server:cast(ggs_stats, print). + +tick() -> + print(), + gen_server:cast(ggs_stats, tick), + timer:apply_after(1000, ggs_stats, tick, []). + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + + +init(_Args) -> + St = #ate{ + server_messages = 0, + client_messages = 0, + stats = [] + }, + {ok, St}. + +handle_cast({add_one, Type}, St) -> + case Type of + server -> NewSt = St#ate { server_messages = St#ate.server_messages + 1 }; + client -> NewSt = St#ate { client_messages = St#ate.client_messages + 1 } + end, + {noreply, NewSt}; + +handle_cast(print, St) -> + CS = length(ggs_coordinator:get_all_players()), + S = lists:concat([CS,";",St#ate.server_messages,";",St#ate.client_messages]), + log("/tmp/ggs-mps-log.csv", S), + io:fwrite("CS:~w | CM:~w | SM:~w |~n", [CS, St#ate.server_messages, St#ate.client_messages]), + {noreply, St}; + +handle_cast(tick, St) -> + NewSt = #ate { server_messages = 0, + client_messages = 0, + stats = St#ate.stats }, + {noreply, NewSt}. + + + +handle_call(_Request, _From, St) -> {stop, unimplemented, St}. +handle_info(_Info, St) -> {stop, unimplemented, St}. +terminate(_Reason, _St) -> ok. +code_change(_OldVsn, St, _Extra) -> {ok, St}. + +log(FileName, Data) -> + file:write_file(FileName, Data ++ "\n", [append]). diff --git a/src/ggs_sup.erl b/src/ggs_sup.erl index 0cc8335..2e5c2ef 100644 --- a/src/ggs_sup.erl +++ b/src/ggs_sup.erl @@ -2,29 +2,38 @@ -behaviour(supervisor). %% API --export([start/1, start_link/1]). +-export([start_link/1]). %% Supervisor callbacks -export([init/1]). -define(SERVER, ?MODULE). -start(Port) -> - [FirstArg] = Port, - {IntPort, _} = string:to_integer(FirstArg), - start_link(IntPort). - start_link(Port) -> supervisor:start_link({local, ?SERVER}, ?MODULE, [Port]). init([Port]) -> - Server = {ggs_server, - {ggs_server, start_link, [Port]}, + Dispatcher = {ggs_dispatcher, + {ggs_dispatcher, start_link, [Port]}, permanent, 2000, worker, - [ggs_server] + [ggs_dispatcher] }, - Children = [Server], + Coordinator = {ggs_coordinator, + {ggs_coordinator, start_link, []}, + permanent, + 2000, + worker, + [ggs_coordinator] + }, + Coordinator_backup = {ggs_coordinator_backup, + {ggs_coordinator_backup, start_link, []}, + permanent, + 2000, + worker, + [ggs_coordinator_backup] + }, + Children = [Dispatcher, Coordinator_backup, Coordinator], RestartStrategy = { one_for_one, % Restart only crashing child 10, % Allow ten crashes per.. diff --git a/src/ggs_table.erl b/src/ggs_table.erl new file mode 100644 index 0000000..988a6ed --- /dev/null +++ b/src/ggs_table.erl @@ -0,0 +1,157 @@ +%% @doc This module represents a table with players + +-module(ggs_table). +-behaviour(gen_server). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, { players, game_vm, already_defined } ). + +%% API +-export([start/1, + add_player/2, + already_defined/1, + remove_player/2, + stop/1, + notify/3, + notify_all_players/2, + notify_game/3, + get_player_list/1, + send_command/3]). + + +%% ---------------------------------------------------------------------- +% API implementation + +% @doc returns a new table +start(Token) -> + {ok, Pid} = gen_server:start(?MODULE, [Token], []), + Pid. + +%% @private +call(Pid, Msg) -> + gen_server:call(Pid, Msg, infinity). + +% @doc adds a player to a table +add_player(Table, Player) -> + gen_server:cast(Table, {add_player, Player}). + +% @doc removes player form a table +remove_player(Table, Player) -> + call(Table, {remove_player, Player}). + +%% @doc Get a list of all player processes attached to this table +get_player_list(TableToken) -> + TablePid = ggs_coordinator:table_token_to_pid(TableToken), + gen_server:call(TablePid, get_player_list). + +% @doc stops the table process +stop(Table) -> + gen_server:cast(Table, stop). + +% @doc notifies the table with a message from a player +notify(TablePid, Player, Message) -> + %TablePid = ggs_coordinator:table_token_to_pid(TableToken), + gen_server:cast(TablePid, {notify, Player, Message}). + +notify_all_players(TableToken, Message) -> + TablePid = ggs_coordinator:table_token_to_pid(TableToken), + gen_server:cast(TablePid, {notify_all_players, Message}). + +notify_game(TablePid, From, Message) -> + TableToken = ggs_coordinator:table_pid_to_token(TablePid), + gen_server:cast(TableToken, {notify_game, Message, From}). + +%% @doc Notify a player sitting at this table with the message supplied. +%% Player, Table and From are in token form. +send_command(TableToken, PlayerToken, Message) -> + TablePid = ggs_coordinator:table_token_to_pid(TableToken), + gen_server:cast(TablePid, {notify_player, PlayerToken, self(), Message}). + +already_defined(TablePid) -> + gen_server:call(TablePid, already_defined). + +%% ---------------------------------------------------------------------- + +%% @private +init([TableToken]) -> + process_flag(trap_exit, true), + GameVM = ggs_gamevm:start_link(TableToken), + {ok, #state { + game_vm = GameVM, + players = [], + already_defined = false }}. + +%% @private + +handle_call({remove_player, Player}, _From, #state { players = Players } = State) -> + {reply, ok, State#state { players = Players -- [Player] }}; + +handle_call(already_defined, _From, #state { already_defined = AlreadyDefined} = State) -> + {reply, AlreadyDefined, State}; + +handle_call(get_player_list, _From, #state { players = Players } = State) -> + TokenPlayers = lists:map( + fun (Pid) -> ggs_coordinator:player_pid_to_token(Pid) end, Players), + {reply, {ok, TokenPlayers}, State}; + +handle_call(get_player_list_raw, _From, #state { players = Players } = State) -> + {reply, {ok, Players}, State}; + +handle_call(Msg, _From, State) -> + error_logger:error_report([unknown_msg, Msg]), + {reply, ok, State}. + +%% @private +handle_cast({notify, Player, Message}, #state { game_vm = GameVM } = State) -> + PlayerToken = ggs_coordinator:player_pid_to_token(Player), + case Message of + {server, define, Args} -> + ggs_gamevm:define(GameVM, Args), + NewState = State#state{ already_defined = true }; + {game, Command, Args} -> + ggs_gamevm:player_command(GameVM, PlayerToken, Command, Args), + NewState = State + end, + {noreply, NewState}; + +handle_cast({add_player, Player}, #state { players = Players } = State) -> + {noreply, State#state { players = [Player | Players] }}; + +handle_cast({notify_game, Message, From}, #state { game_vm = GameVM } = State) -> + ggs_gamevm:player_command(GameVM, From, Message, ""), + {noreply, State}; + +handle_cast({notify_all_players, Message}, #state{players = Players} = State) -> + lists:foreach( + fun(P) -> ggs_player:notify(P, "Server", Message) end, + Players + ), + {noreply, State}; + +handle_cast({notify_player, PlayerToken, From, Message}, State) -> + PlayerPid = ggs_coordinator:player_token_to_pid(PlayerToken), + ggs_player:notify(PlayerPid, From, Message), + {noreply, State}; + +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(Msg, S) -> + error_logger:error_report([unknown_msg, Msg]), + {noreply, S}. + +%% @private +handle_info(Msg, S) -> + error_logger:error_report([unknown_msg, Msg]), + {noreply, S}. + +%% @private +terminate(_Reason, _State) -> + ok. + +%% @private +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + diff --git a/src/ggs_vm_runner.erl b/src/ggs_vm_runner.erl new file mode 100644 index 0000000..e149c51 --- /dev/null +++ b/src/ggs_vm_runner.erl @@ -0,0 +1,42 @@ +-module(ggs_vm_runner). +-export([start_link/0, define/2, user_command/4]). + +%Mattias +start_link() -> + erlang_js:start(), + PortPid = spawn_link( fun() -> + process_flag(trap_exit, true), + {ok, Port} = js_driver:new(), + js:define(Port, <<"function userCommand(user, command, args){return 'Hello world';}">>), + loop(Port) + end ), + PortPid. + + +loop(Port) -> + receive + {define, SourceCode} -> + ok = js:define(Port, list_to_binary(SourceCode)), + loop(Port); + {user_command, User, Command, Args, From, Ref} -> + {ok, Ret} = js:call(Port, <<"userCommand">>, + [ list_to_binary(User), + list_to_binary(Command), + list_to_binary(Args) + ]), + From ! {Ref, Ret}, + loop(Port) + end. + + +define(GameVM, SourceCode) -> + GameVM ! {define,SourceCode}. + +user_command(GameVM, User, Command, Args) -> + Ref = make_ref(), + GameVM ! {user_command, User, Command, Args, self(), Ref}, + receive + {Ref, RetVal} -> + RetVal; + Other -> Other + end. diff --git a/src/helpers.erl b/src/helpers.erl new file mode 100644 index 0000000..3a42fbf --- /dev/null +++ b/src/helpers.erl @@ -0,0 +1,8 @@ +-module(helpers). +-export([not_implemented/0, get_new_token/0]). + +not_implemented() -> + exit("Not implemented"). + +get_new_token() -> + string:strip(os:cmd("uuidgen"), right, $\n ). diff --git a/src/js_runner.erl b/src/js_runner.erl deleted file mode 100644 index ca866c4..0000000 --- a/src/js_runner.erl +++ /dev/null @@ -1,13 +0,0 @@ --module(js_runner). --export([define/2,call/3, boot/0]). - -boot() -> - erlang_js:start(), - {ok, Port} = js_driver:new(), - Port. - -define(Port, Data) -> - ok = js:define(Port, list_to_binary(Data)). - -call(Port, Func, Params) -> - js:call(Port, list_to_binary(Func), Params). diff --git a/src/start_ggs.erl b/src/start_ggs.erl index eabdbc5..161d20e 100644 --- a/src/start_ggs.erl +++ b/src/start_ggs.erl @@ -3,4 +3,7 @@ start() -> application:start(inets), + application:start(erlang_js), + ggs_stats:start_link(), + ggs_db:init(), application:start(ggs). diff --git a/start b/start deleted file mode 100755 index f3720a5..0000000 --- a/start +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -erl -boot start_sasl -pa erlang_js/ebin/ -pa ebin -pa src -s start_ggs diff --git a/start_test b/start_test deleted file mode 100755 index 5920151..0000000 --- a/start_test +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -erl -boot start_sasl -pa ebin_test -pa erlang_js/ebin/ -pa erlv8/ebin -pa ebin -pa src -eval 'ggs_protocol_test:test_parse().' diff --git a/start_test_shell b/start_test_shell deleted file mode 100755 index f2135c4..0000000 --- a/start_test_shell +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -erl -boot start_sasl -pa ebin_test -pa erlang_js/ebin/ -pa erlv8/ebin -pa ebin -pa src diff --git a/tests/current_counterexample.eqc b/tests/current_counterexample.eqc new file mode 100644 index 0000000..fdb1c3c Binary files /dev/null and b/tests/current_counterexample.eqc differ diff --git a/tests/ggs_coordinator_test.erl b/tests/ggs_coordinator_test.erl new file mode 100644 index 0000000..5fd4056 --- /dev/null +++ b/tests/ggs_coordinator_test.erl @@ -0,0 +1,53 @@ +-module(ggs_coordinator_test). +-include_lib("eunit/include/eunit.hrl"). + +coordinator_test_() -> + {foreach, + fun() -> + {ok, _Coord} = ggs_coordinator:start_link(), + timer:sleep(100) + end, + fun(_X) -> + ggs_coordinator:stop("End of test"), + timer:sleep(100) + end, + [ + fun test_start_link/0, + fun test_stop/0, + fun test_join_bad_table/0, + fun test_join_lobby/0 + ] + }. + +test_start_link() -> + % Check process info + PInfo = whereis(ggs_coordinator), + ?assert((PInfo /= undefined)). % Did the server start? + +test_stop() -> + ok = ggs_coordinator:stop(""), % Extra cleaning + timer:sleep(100), + % Did it stop? + ?assert((whereis(ggs_coordinator)) == undefined). + +test_join_bad_table() -> + Response = ggs_coordinator:join_table("Nonexistant table"), + ?assert(Response == {error, no_such_table}). + +test_join_lobby() -> + {Response, _} = ggs_coordinator:join_lobby(), + ?assert(Response /= error). + +%% 'Manual' tests + +create_table_test() -> + {ok, _Coord} = ggs_coordinator:start_link(), + timer:sleep(100), + % Forcibly create a table. This functionality should be disabled + % in the production system, but is pretty nice for testing. + Response = ggs_coordinator:create_table({force, "1337"}), + ?assert(Response == {ok, "1337"}). + +join_good_table_test() -> + Response = ggs_coordinator:join_table("1337"), + ?assert(Response == {ok, "1337"}). diff --git a/tests/ggs_db_eqc_test.erl b/tests/ggs_db_eqc_test.erl new file mode 100644 index 0000000..a29a795 --- /dev/null +++ b/tests/ggs_db_eqc_test.erl @@ -0,0 +1,66 @@ +-module(ggs_db_eqc_test). + +-include_lib("lib/eqc/include/eqc.hrl"). + +-compile(export_all). + + +start() -> + io:format("prop_set_Item: "), + eqc:quickcheck(ggs_db_eqc_test:prop_setItem()), + io:format("prop_remove_Item: "), + eqc:quickcheck(ggs_db_eqc_test:prop_removeItem()), + io:format("prop_clear: "), + eqc:quickcheck(ggs_db_eqc_test:prop_clear()), + io:format("prop_length: "), + eqc:quickcheck(ggs_db_eqc_test:prop_length()), + io:format("prop_key: "), + eqc:quickcheck(ggs_db_eqc_test:prop_key()). + +%% new(length) == old(length) or +%% new(length) == old(length) + 1 +prop_setItem() -> + ggs_db:init(), + F = (fun(T,N,K,V) -> ggs_db:setItem(T,N,K,V), ggs_db:length(T,N) end), + ?FORALL({T,N,K,V},{bitstring(),bitstring(),bitstring(),bitstring()}, + (ggs_db:length(T,N) + 1 == F(T,N,K,V)) or + (ggs_db:length(T,N) == F(T,N,K,V))). + + +%% new(length) >= 0 and +%% old(length) == new(length) or +%% old(length) - 1 == new(length) +prop_removeItem() -> + ggs_db:init(), + F = (fun(T,N,K) -> ggs_db:removeItem(T,N,K), ggs_db:length(T,N) end), + G = (fun(A,B) -> ((A == B) or (A == B + 1)) and (B >= 0) end), + ?FORALL({T,N,K},{bitstring(),bitstring(),bitstring()}, + G(ggs_db:length(T,N), F(T,N,K))). + + +%% clear(X) -> (length(X,?) == 0) +prop_clear() -> + ggs_db:init(), + F = (fun(T,N) -> ggs_db:clear(T), ggs_db:length(T,N) end), + ?FORALL({T,N},{bitstring(),bitstring()}, + F(T,N) == 0). + +%% ? -> length(?,?) >= 0 +prop_length() -> + ggs_db:init(), + F = fun(T,N,K,V) -> ggs_db:setItem(T,N,K,V), ggs_db:length(T,N) end, + G = fun(T,N,K) -> ggs_db:removeItem(T,N,K), ggs_db:length(T,N) end, + ?FORALL({{T,N,K,V},{T2,N2,K2}}, + {{bitstring(),bitstring(),bitstring(),bitstring()}, + {bitstring(),bitstring(),bitstring()}}, + (((F(T,N,K,V) >= 0) and (G(T2,N2,K2) >= 0)))). + + +%% key(X,Y,length(X,Y)) -> Exists +prop_key() -> + ggs_db:init(), + F = fun(T,N) -> case ggs_db:length(T,N) of 0 -> true; X -> + case ggs_db:key(T,N,X) of _ -> true end end end, + ?FORALL({T,N},{bitstring(),bitstring()}, + F(T,N)). + diff --git a/tests/ggs_db_quickcheck_test.erl b/tests/ggs_db_quickcheck_test.erl new file mode 100644 index 0000000..e10789a --- /dev/null +++ b/tests/ggs_db_quickcheck_test.erl @@ -0,0 +1,22 @@ +-module(ggs_db_quickcheck_test). + +-include_lib("eqc/include/eqc.hrl"). + +-compile(export_all). + + +%prop_delete() -> +% ?FORALL({X, Xs}, {int(), list(int())}, +% not lists:member(X, lists:delete(X, Xs))). + +%test_delete() -> +% quickcheck(prop_delete()). + + +prop_getitem -> + ggs_db:init(), + ?FORALL({}). + + + + diff --git a/tests/ggs_db_test.erl b/tests/ggs_db_test.erl new file mode 100644 index 0000000..f2608f1 --- /dev/null +++ b/tests/ggs_db_test.erl @@ -0,0 +1,47 @@ +-module(ggs_db_test). +%-compile({no_auto_import,[get/1,set/2]}). +-include_lib("eunit/include/eunit.hrl"). + +%ggs_db_test_() -> +% {spawn, +% {setup, fun setup/0, fun cleanup/1,[ fun ggs_db_test/0 ]} +% }. + + +%Key should be a tuple of two elements +getItem_setItem_test() -> + ggs_db:init(), + ggs_db:setItem("dbname","nsname","keyname1","Hello"), + ggs_db:setItem("dbname","nsname","keyname2","Hello2"), + ggs_db:setItem("dbname2","nsname","keyname1","Hello3"), + ggs_db:setItem("dbname2","nsname","keyname1","Hello4"), + ggs_db:setItem("dbname3","nsname","keyname1","Hello5"), + "Hello" = ggs_db:getItem("dbname","nsname","keyname1"). + +%Test the length function of our database +length_test() -> + ggs_db:setItem(1,1,2,"112"), + ggs_db:setItem(1,2,2,"122"), + ggs_db:setItem(1,1,3,"113"), + ggs_db:setItem(1,1,4,"114"), + ?assertEqual(ggs_db:length(1,1), 3). + +%Test if we can remove correctly from the database +removeItem_test() -> + ggs_db:removeItem(1,1,4), + ?assertNot(ggs_db:getItem(1,1,4) =:= "114"). + +%Test the key function +key_test() -> + ?assert(ggs_db:key(1,1,2) =:= {1,1,2}). + +%Test the clear function(for gametoken and ns) +clear_test() -> + ggs_db:clear(1,1), + ?assert(ggs_db:length(1,1) =:= 0). + +%Test the clear function(gametoken) +clear_GameToken_test() -> + ggs_db:clear(1), + ?assert((ggs_db:length(1,1) + ggs_db:length(1,2)) =:= 0), + ggs_db:stop(). diff --git a/tests/ggs_gamevm_test.erl b/tests/ggs_gamevm_test.erl new file mode 100644 index 0000000..3560fb9 --- /dev/null +++ b/tests/ggs_gamevm_test.erl @@ -0,0 +1,31 @@ +-module(ggs_gamevm_test). +-include_lib("eunit/include/eunit.hrl"). + +start_link_test() -> + erlang_js:start(), %% @TODO: should only be done once + GameVM = ggs_gamevm:start_link(test_table), + ?assertNot(GameVM =:= undefined). + +define_test() -> + GameVM = ggs_gamevm:start_link(test_table), + ggs_gamevm:define(GameVM, "function hello(test) { return test; }"), + ?assertMatch(<<"jeena">>, gen_server:call(GameVM, {eval, "hello('jeena')"})). + +stop_test() -> + GameVM = ggs_gamevm:start_link(test_table), + ok = ggs_gamevm:stop(GameVM). + +user_command_test() -> + GameVM = ggs_gamevm:start_link(test_table), + ggs_gamevm:define(GameVM, "var t = '';\nfunction userCommand(user, command, args) { t = user + command + args; }\n"), + ggs_gamevm:user_command(GameVM, "'jeena", "thecommand", "theargs'"), + ?assertMatch(<<"'jeenathecommandtheargs'">>, gen_server:call(GameVM, {eval, "t;"})). + +js_erlang_test() -> + GameVM = ggs_gamevm:start_link(test_table), + ggs_gamevm:define(GameVM, "var t = '';\nfunction userCommand(user, command, args) { t = callErlang('erlang time') + ''; }\n"), + ggs_gamevm:user_command(GameVM, "", "", ""), + {A, B, C} = erlang:time(), + T = "{" ++ integer_to_list(A) ++ ", " ++ integer_to_list(B) ++ ", " ++ integer_to_list(C) ++ "}", + ?assertMatch(T, binary_to_list(gen_server:call(GameVM, {eval, "t;"}))). + diff --git a/tests/ggs_player_test.erl b/tests/ggs_player_test.erl new file mode 100644 index 0000000..1782842 --- /dev/null +++ b/tests/ggs_player_test.erl @@ -0,0 +1,27 @@ +-module(ggs_player_test). +-include_lib("eunit/include/eunit.hrl"). + +%% @doc start_link should always return ok for any valid socket. A valid socket +%% should always return {ok, Pid} and {error, Reason} otherwise. +start_link_test() -> + {ok, Player} = ggs_player:start_link(Sock). + +%% @doc Given that start_link returned {ok, Player}. Notify shall always return ok and +%% deliver a specified message through the socket. +notify_test() -> + Player = ggs_player:start_link("bad arg"), + Message = {"something", ""}, + Ret = ggs_player:notify(Player, self(), Message), + ?assertNot(ok =:= Ret). + +%% @doc Given that start_link returned {ok, Player}. get_token shall always return a valid +%% player token. a valid token should be unique. +get_token_test() -> + ggs_logger:not_implemented(). + +%% @doc Given that start_link returned {ok, Pid}. There shouldn't be possible to +%% execute this function with the same Player and Table arguments twice. +stop_test() -> + Player = ggs_player:start_link(something), + Table = test, + ok = ggs_player:stop(Player, Table). diff --git a/tests/ggs_protocol_test.erl b/tests/ggs_protocol_test.erl deleted file mode 100644 index 2230cbf..0000000 --- a/tests/ggs_protocol_test.erl +++ /dev/null @@ -1,6 +0,0 @@ --module(ggs_protocol_test). --export([test_parse/0]). - -test_parse() -> - Ret = ggs_protocol:parse("<> __define JavaScript"), - io:format("~p~n", [Ret]). diff --git a/tests/ggs_table_test.erl b/tests/ggs_table_test.erl new file mode 100644 index 0000000..8dbe11e --- /dev/null +++ b/tests/ggs_table_test.erl @@ -0,0 +1,38 @@ +-module(ggs_table_test). +-include_lib("eunit/include/eunit.hrl"). + +start_link_test() -> + Table = ggs_table:start_link(), + ?assertNot(Table =:= undefined). + +add_player_test() -> + Table = ggs_table:start_link(), + Player = test_player, + ggs_table:add_player(Table, Player), + {ok, [Player]} = gen_server:call(Table, get_player_list). + +remove_player_test() -> + Table = ggs_table:start_link(), + Player = test_player, + Player2 = test_player2, + ggs_table:add_player(Table, Player), + {ok, [Player]} = gen_server:call(Table, get_player_list), + ggs_table:add_player(Table, Player2), + {ok, [Player2, Player]} = gen_server:call(Table, get_player_list), + ggs_table:remove_player(Table, Player), + {ok, [Player2]} = gen_server:call(Table, get_player_list), + ggs_table:remove_player(Table, Player2), + {ok, []} = gen_server:call(Table, get_player_list). + +stop_test() -> + Table = ggs_table:start_link(), + ok = ggs_table:stop(Table). + +notify_test() -> + Table = ggs_table:start_link(), + Player = test_player, + Message = {server, define, "function helloWorld(x) { }"}, + ok = ggs_table:notify(Table, Player, Message), + Message2 = {game, "helloWorld", "test"}, + ok = ggs_table:notify(Table, Player, Message2). + diff --git a/tests/tick.erl b/tests/tick.erl new file mode 100644 index 0000000..0d21220 --- /dev/null +++ b/tests/tick.erl @@ -0,0 +1,26 @@ +-module(tick). +-export([start/0]). + +start() -> + spawn(fun() -> loop() end). + +loop() -> + receive + tick -> + erlang:display("tick!"), + timer:send_after(500, tick), + loop(); + 'EXIT' -> + exit(normal) + end. + +% 1> c(tick). +% {ok,tick} +% 2> Pid = tick:start(). +% <0.38.0> +% % Nothing happens :-( + +% When I send it myself then it responds +% 3> Pid ! tick. +% tick +% 5>