@@ -31,34 +31,22 @@ local HttpServer = {}
3131HttpServer .__index = HttpServer
3232
3333---- ----------------------------------------------------------------------------
34- -- Logging Utilities
34+ -- Simplified Logging Utilities
3535---- ----------------------------------------------------------------------------
3636
37- --- Simple logging that outputs to both mGBA console and stdout
37+ --- Streamlined logging to mGBA console and stdout
3838--- @param level string
3939--- @param message string
4040local function log (level , message )
41- local logMessage = string.format (" [%s] %s: %s" , os.date (" %H:%M:%S" ), level , message )
42-
43- if level == " ERROR" then
44- console :error (logMessage )
45- else
46- console :log (logMessage )
47- end
48-
49- io.stdout :write (logMessage .. " \n " )
41+ local msg = string.format (" [%s] %s: %s" , os.date (" %H:%M:%S" ), level , message )
42+ console :log (msg )
43+ io.stdout :write (msg .. " \n " )
5044 io.stdout :flush ()
5145end
5246
53- --- @param message string
5447local function logInfo (message ) log (" INFO" , message ) end
55-
56- --- @param message string
5748local function logError (message ) log (" ERROR" , message ) end
5849
59- --- @param message string
60- local function logDebug (message ) log (" DEBUG" , message ) end
61-
6250---- ----------------------------------------------------------------------------
6351-- "Static" Methods
6452---- ----------------------------------------------------------------------------
@@ -195,7 +183,7 @@ function HttpServer.createWebSocketFrame(data)
195183 return frame .. data
196184end
197185
198- --- Parses WebSocket frame with essential frame type handling.
186+ --- Optimized WebSocket frame parsing with essential frame type handling
199187--- @param data string
200188--- @return string ?, number , string ?
201189function HttpServer .parseWebSocketFrame (data )
@@ -219,42 +207,34 @@ function HttpServer.parseWebSocketFrame(data)
219207 end
220208
221209 -- Handle masking
222- local mask = nil
223210 if masked then
224211 if # data < offset + 4 then return nil , 0 , " incomplete_frame" end
225- mask = {data :byte (offset + 1 , offset + 4 )}
212+ local mask = {data :byte (offset + 1 , offset + 4 )}
226213 offset = offset + 4
227- end
228-
229- -- Check if we have complete payload
230- if # data < offset + len then return nil , 0 , " incomplete_frame" end
231-
232- -- Extract and unmask payload if needed
233- local function getPayload ()
214+
215+ if # data < offset + len then return nil , 0 , " incomplete_frame" end
216+
217+ -- Extract and unmask payload
234218 local payload = data :sub (offset + 1 , offset + len )
235- if masked and mask then
236- local unmasked = {}
237- for i = 1 , # payload do
238- unmasked [i ] = string.char (payload :byte (i ) ~ mask [((i - 1 )% 4 )+ 1 ])
239- end
240- return table.concat (unmasked )
219+ local unmasked = {}
220+ for i = 1 , # payload do
221+ unmasked [i ] = string.char (payload :byte (i ) ~ mask [((i - 1 )% 4 )+ 1 ])
241222 end
242- return payload
243- end
244-
245- -- Handle frame types
246- if opcode == 0x1 then -- Text frame
247- return getPayload (), offset + len , " text"
248- elseif opcode == 0x2 then -- Binary frame
249- return getPayload (), offset + len , " binary"
250- elseif opcode == 0x8 then -- Close frame
251- return nil , - 1 , " close"
252- elseif opcode == 0x9 then -- Ping frame
253- return nil , offset + len , " ping"
254- elseif opcode == 0xA then -- Pong frame
255- return nil , offset + len , " pong"
223+ payload = table.concat (unmasked )
224+
225+ -- Handle frame types
226+ if opcode == 0x1 then return payload , offset + len , " text"
227+ elseif opcode == 0x8 then return nil , - 1 , " close"
228+ elseif opcode == 0x9 then return nil , offset + len , " ping"
229+ else return nil , offset + len , " unknown" end
256230 else
257- return nil , offset + len , " unknown"
231+ if # data < offset + len then return nil , 0 , " incomplete_frame" end
232+ local payload = data :sub (offset + 1 , offset + len )
233+
234+ if opcode == 0x1 then return payload , offset + len , " text"
235+ elseif opcode == 0x8 then return nil , - 1 , " close"
236+ elseif opcode == 0x9 then return nil , offset + len , " ping"
237+ else return nil , offset + len , " unknown" end
258238 end
259239end
260240
@@ -476,7 +456,7 @@ function HttpServer:_handle_websocket_upgrade(clientId, req)
476456 end
477457end
478458
479- --- Handles WebSocket frame data with essential frame handling.
459+ --- Streamlined WebSocket frame handling
480460--- @param clientId number
481461--- @private
482462function HttpServer :_handle_websocket_data (clientId )
@@ -495,16 +475,10 @@ function HttpServer:_handle_websocket_data(clientId)
495475 local message , consumed , frameType = HttpServer .parseWebSocketFrame (chunk )
496476 if consumed == - 1 then -- Close frame
497477 self :_cleanup_client (clientId )
498- return
499478 elseif frameType == " ping" then
500- -- Respond to ping with pong
501- local pongFrame = string.char (0x8A , 0x00 )
502- client :send (pongFrame )
503- elseif frameType == " text" or frameType == " binary" then
504- -- Process text and binary frames as messages
505- if message and # message > 0 and ws .onMessage then
506- ws .onMessage (message )
507- end
479+ client :send (string.char (0x8A , 0x00 )) -- Pong response
480+ elseif (frameType == " text" or frameType == " binary" ) and message and # message > 0 and ws .onMessage then
481+ ws .onMessage (message )
508482 end
509483end
510484
636610
637611local app = HttpServer :new ()
638612
639- -- Global middleware
613+ -- Simplified middleware for essential logging only
640614app :use (function (req , res )
641- logInfo (req .method .. " " .. req .path .. " - Headers: " .. HttpServer .jsonStringify (req .headers ))
615+ -- Only log non-favicon requests to reduce noise
616+ if not req .path :find (" favicon" ) then
617+ logInfo (req .method .. " " .. req .path )
618+ end
642619end )
643620
644621-- Routes
@@ -654,12 +631,11 @@ app:post("/echo", function(req, res)
654631 res :send (" 200 OK" , req .body , req .headers [' content-type' ])
655632end )
656633
657- -- Simple message parser for WebSocket messages
634+ -- Optimized message parser for WebSocket messages
658635local function parseWebSocketMessage (str )
659636 if not str or str == " " then return nil , " Empty message" end
660637
661638 str = str :gsub (" ^%s*(.-)%s*$" , " %1" ) -- trim whitespace
662-
663639 local lines = {}
664640 for line in str :gmatch (" ([^\r\n ]+)" ) do
665641 lines [# lines + 1 ] = line :gsub (" ^%s*(.-)%s*$" , " %1" )
@@ -670,8 +646,7 @@ local function parseWebSocketMessage(str)
670646 for i = 2 , # lines do
671647 local address , size = lines [i ]:match (" ^(%d+),(%d+)$" )
672648 if address and size then
673- local addr = tonumber (address )
674- local sz = tonumber (size )
649+ local addr , sz = tonumber (address ), tonumber (size )
675650 if addr and sz and addr >= 0 and sz > 0 and sz <= 0x10000 then
676651 regions [# regions + 1 ] = { address = addr , size = sz }
677652 end
@@ -683,13 +658,18 @@ local function parseWebSocketMessage(str)
683658 return nil , " Unsupported format"
684659end
685660
686- -- Connection tracking
661+ -- Enhanced connection tracking with rate limiting
687662local activeEvalConnections = 0
688663local activeWatchConnections = 0
689- local maxConcurrentEvals = 100
690- local maxConcurrentWatchers = 50
664+ local maxConcurrentEvals = 200 -- Increased capacity
665+ local maxConcurrentWatchers = 100 -- Increased capacity
691666
692- -- WebSocket route for Lua code evaluation
667+ -- Rate limiting per connection
668+ local connectionRateLimits = {}
669+ local RATE_LIMIT_WINDOW = 1000 -- 1 second
670+ local MAX_REQUESTS_PER_WINDOW = 10
671+
672+ -- Enhanced WebSocket route for Lua code evaluation with rate limiting
693673app :websocket (" /eval" , function (ws )
694674 if activeEvalConnections >= maxConcurrentEvals then
695675 ws :send (HttpServer .jsonStringify ({ error = " Server at capacity" }))
@@ -698,49 +678,66 @@ app:websocket("/eval", function(ws)
698678 end
699679
700680 activeEvalConnections = activeEvalConnections + 1
681+
682+ -- Initialize rate limiting for this connection
683+ connectionRateLimits [ws .id ] = {
684+ lastReset = os.time () * 1000 ,
685+ requestCount = 0
686+ }
701687
702688 ws .onMessage = function (message )
703689 if not message or type (message ) ~= " string" then return end
704690
691+ -- Check rate limit
692+ local now = os.time () * 1000
693+ local limit = connectionRateLimits [ws .id ]
694+ if not limit then
695+ limit = { lastReset = now , requestCount = 0 }
696+ connectionRateLimits [ws .id ] = limit
697+ end
698+
699+ if now - limit .lastReset > RATE_LIMIT_WINDOW then
700+ limit .lastReset = now
701+ limit .requestCount = 0
702+ end
703+
704+ limit .requestCount = limit .requestCount + 1
705+ if limit .requestCount > MAX_REQUESTS_PER_WINDOW then
706+ ws :send (HttpServer .jsonStringify ({error = " Rate limit exceeded" }))
707+ return
708+ end
709+
705710 local code = message :gsub (" ^%s*(.-)%s*$" , " %1" )
706711 if # code == 0 then return end
707712
708- -- Add return if needed
709- if not code :match (" ^%s*return%s" ) and not code :match (" ^%s*local%s" ) and
710- not code :match (" ^%s*function%s" ) and not code :match (" ^%s*for%s" ) and
711- not code :match (" ^%s*while%s" ) and not code :match (" ^%s*if%s" ) and
712- not code :match (" ^%s*do%s" ) and not code :match (" ^%s*repeat%s" ) then
713+ -- Smart code completion
714+ if not code :match (" ^%s*return%s" ) and not code :match (" ^%s*[%a_][%w_]*%s*[=%(]" ) then
713715 code = " return " .. code
714716 end
715717
716718 local fn , err = load (code , " websocket-eval" )
717- if not fn then
718- ws :send (HttpServer .jsonStringify ({error = err or " Invalid code" }))
719- return
720- end
721-
722- local ok , result = pcall (fn )
723- if ok then
724- ws :send (HttpServer .jsonStringify ({result = result }))
719+ if fn then
720+ local ok , result = pcall (fn )
721+ ws :send (HttpServer .jsonStringify (ok and {result = result } or {error = tostring (result )}))
725722 else
726- ws :send (HttpServer .jsonStringify ({error = tostring ( result ) }))
723+ ws :send (HttpServer .jsonStringify ({error = err or " Invalid code " }))
727724 end
728725 end
729726
730727 ws .onClose = function ()
731728 activeEvalConnections = math.max (0 , activeEvalConnections - 1 )
729+ connectionRateLimits [ws .id ] = nil
732730 end
733731
734- -- Send welcome message
735732 ws :send (" Welcome to WebSocket Eval! Send Lua code to execute." )
736733end )
737734
738735-- Memory watching state
739736local memoryWatchers = {}
740737
741- -- WebSocket route for memory watching
738+ -- Enhanced WebSocket route for memory watching
742739app :websocket (" /watch" , function (ws )
743- -- Check concurrent connection limit
740+ -- Connection limit check
744741 if activeWatchConnections >= maxConcurrentWatchers then
745742 ws :send (HttpServer .jsonStringify ({
746743 type = " error" ,
@@ -750,7 +747,7 @@ app:websocket("/watch", function(ws)
750747 return
751748 end
752749
753- -- Initialize memory watcher
750+ -- Initialize memory watcher state
754751 memoryWatchers [ws .id ] = {
755752 regions = {},
756753 lastData = {},
@@ -788,11 +785,9 @@ app:websocket("/watch", function(ws)
788785 watcher .lastData = {}
789786 watcher .errorCount = 0
790787
791- -- Initialize baseline data
788+ -- Initialize baseline data efficiently
792789 for i , region in ipairs (parsed .regions ) do
793- local ok , data = pcall (function ()
794- return emu :readRange (region .address , region .size )
795- end )
790+ local ok , data = pcall (emu .readRange , emu , region .address , region .size )
796791 watcher .lastData [i ] = ok and data or " "
797792 end
798793
@@ -809,30 +804,26 @@ app:websocket("/watch", function(ws)
809804 end
810805 end
811806
812- -- Send welcome message
807+ -- Send streamlined welcome message
813808 ws :send (HttpServer .jsonStringify ({
814809 type = " welcome" ,
815- message = " Welcome to WebSocket Memory Watching! Send JSON messages with 'type': 'watch' to monitor memory regions." ,
816- version = " 1.0" ,
817- limits = {
818- maxRegions = 50 ,
819- maxRegionSize = 65536
820- }
810+ message = " Memory Watching Ready! Send WATCH messages with regions." ,
811+ limits = { maxRegions = 50 , maxRegionSize = 65536 }
821812 }))
822813end )
823814
824- -- Memory change detection callback
815+ -- Optimized memory change detection callback
825816local frameCount = 0
826817
827818local function checkMemoryChanges ()
828819 frameCount = frameCount + 1
829820
830- -- Check every 10 frames (about 6fps at 60fps )
831- if frameCount % 10 ~= 0 then return end
821+ -- Check every 8 frames (improved from 10 frames for better responsiveness )
822+ if frameCount % 8 ~= 0 then return end
832823
833824 for wsId , watcher in pairs (memoryWatchers ) do
834825 local ws = app .websockets [wsId ]
835- if not ws or ws . readyState == 2 or ws . readyState == 3 then
826+ if not ws then
836827 memoryWatchers [wsId ] = nil
837828 activeWatchConnections = math.max (0 , activeWatchConnections - 1 )
838829 goto continue
@@ -845,9 +836,7 @@ local function checkMemoryChanges()
845836 local changedRegions = {}
846837
847838 for i , region in ipairs (watcher .regions ) do
848- local ok , currentData = pcall (function ()
849- return emu :readRange (region .address , region .size )
850- end )
839+ local ok , currentData = pcall (emu .readRange , emu , region .address , region .size )
851840
852841 if not ok then
853842 watcher .errorCount = watcher .errorCount + 1
@@ -874,13 +863,11 @@ local function checkMemoryChanges()
874863
875864 -- Send memory update if any regions changed
876865 if # changedRegions > 0 then
877- local ok = pcall (function ()
878- ws :send (HttpServer .jsonStringify ({
879- type = " memoryUpdate" ,
880- regions = changedRegions ,
881- timestamp = os.time ()
882- }))
883- end )
866+ local ok = pcall (ws .send , ws , HttpServer .jsonStringify ({
867+ type = " memoryUpdate" ,
868+ regions = changedRegions ,
869+ timestamp = os.time ()
870+ }))
884871
885872 if not ok then
886873 watcher .errorCount = watcher .errorCount + 1
@@ -895,13 +882,13 @@ local function checkMemoryChanges()
895882 end
896883end
897884
898- -- Setup memory monitoring
885+ -- Streamlined setup and monitoring initialization
899886local function setupMemoryMonitoring ()
900887 callbacks :add (" frame" , checkMemoryChanges )
901888 logInfo (" 🔍 Memory monitoring callback registered" )
902889end
903890
904- -- Setup monitoring when ROM is loaded
891+ -- Initialize monitoring based on ROM availability
905892if emu and emu .romSize and emu :romSize () > 0 then
906893 setupMemoryMonitoring ()
907894else
0 commit comments