1tbfree commited on
Commit
95682b3
·
verified ·
1 Parent(s): b93f3d5

Create cvm.py

Browse files
Files changed (1) hide show
  1. cvm.py +162 -0
cvm.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import websockets
3
+ import json
4
+
5
+ # Guacamole protocol message formatting functions
6
+ def encode_guac_message(opcode, *params):
7
+ parts = [f"{len(p)}.{p}" for p in params]
8
+ return f"{opcode},{','.join(parts)};"
9
+
10
+ def decode_guac_message(message):
11
+ parts = message.strip(';').split(',')
12
+ opcode = parts[0]
13
+ params = []
14
+
15
+ current_pos = 1
16
+ while current_pos < len(parts):
17
+ length_str = parts[current_pos].split('.')[0]
18
+ length = int(length_str)
19
+ # Note: A real implementation would handle string content more carefully
20
+ param = parts[current_pos][len(length_str) + 1:]
21
+ params.append(param)
22
+ current_pos += 1
23
+
24
+ return opcode, params
25
+
26
+ # Simplified VM and User classes for state management
27
+ class VM:
28
+ def __init__(self, vm_id, display_name):
29
+ self.vm_id = vm_id
30
+ self.display_name = display_name
31
+ self.users = {} # {username: WebSocket connection}
32
+ self.turn_queue = []
33
+ self.chat_history = []
34
+ self.screen_size = (800, 600)
35
+
36
+ class User:
37
+ def __init__(self, username, rank=0):
38
+ self.username = username
39
+ self.rank = rank
40
+
41
+ # Server state
42
+ vms = {
43
+ "vm-001": VM("vm-001", "Linux Desktop"),
44
+ "vm-002": VM("vm-002", "Windows 98")
45
+ }
46
+
47
+ # Main server logic
48
+ async def handle_client(websocket, path):
49
+ # Enforce guacamole subprotocol
50
+ if "guacamole" not in websocket.subprotocol:
51
+ await websocket.close()
52
+ print("Connection rejected: Incorrect subprotocol.")
53
+ return
54
+
55
+ # Handshake phase
56
+ print("New connection established. Starting handshake...")
57
+ user = User(username=None)
58
+ vm_id = None
59
+
60
+ # Send NOPs periodically (this is a simplified example)
61
+ async def nop_keepalive():
62
+ while True:
63
+ await asyncio.sleep(10) # 10-second interval
64
+ if websocket.open:
65
+ await websocket.send(encode_guac_message("nop"))
66
+ else:
67
+ break
68
+
69
+ asyncio.ensure_future(nop_keepalive())
70
+
71
+ try:
72
+ async for message in websocket:
73
+ opcode, params = decode_guac_message(message)
74
+ print(f"Received opcode: {opcode} with params: {params}")
75
+
76
+ # Handshake opcodes
77
+ if opcode == "list":
78
+ list_message = "list,"
79
+ for v in vms.values():
80
+ # For a real server, thumbnail would be base64-encoded image data
81
+ list_message += f"{len(v.vm_id)}.{v.vm_id},"
82
+ list_message += f"{len(v.display_name)}.{v.display_name},"
83
+ list_message += f"{4}.THMB;" # Mock thumbnail
84
+ await websocket.send(list_message)
85
+
86
+ elif opcode == "rename":
87
+ requested_name = params[0] if params else "guest"
88
+ user.username = requested_name # Simplified, server would check for availability
89
+
90
+ # Send back a Client Renamed event
91
+ response = encode_guac_message("rename", "0", "0", user.username)
92
+ await websocket.send(response)
93
+
94
+ elif opcode == "connect":
95
+ requested_vm_id = params[0]
96
+ if requested_vm_id in vms:
97
+ vm = vms[requested_vm_id]
98
+ vm_id = requested_vm_id
99
+ vm.users[user.username] = websocket
100
+ print(f"User {user.username} connected to VM {vm_id}")
101
+
102
+ # Connection success message
103
+ await websocket.send(encode_guac_message("connect", "1"))
104
+
105
+ # Announce new user to others in the VM
106
+ for other_user_ws in vm.users.values():
107
+ if other_user_ws != websocket:
108
+ await other_user_ws.send(encode_guac_message("adduser", user.username, "0"))
109
+
110
+ # Announce VM screen size
111
+ width, height = vm.screen_size
112
+ await websocket.send(encode_guac_message("size", "0", str(width), str(height)))
113
+
114
+ # Send mock framebuffer update (png)
115
+ # A real implementation would send a base64 encoded image
116
+ await websocket.send(encode_guac_message("png", "0", "0", "0", "0", "FAKE_BASE64_IMAGE_DATA"))
117
+
118
+ else:
119
+ await websocket.send(encode_guac_message("connect", "0"))
120
+
121
+ # VM interaction opcodes (only after connection)
122
+ elif vm_id:
123
+ vm = vms[vm_id]
124
+ if opcode == "chat":
125
+ message = params[0]
126
+ chat_msg = f"{user.username}: {message}"
127
+ vm.chat_history.append(chat_msg)
128
+ for other_user_ws in vm.users.values():
129
+ await other_user_ws.send(encode_guac_message("chat", user.username, message))
130
+
131
+ # Add more opcode handlers here (e.g., mouse, key, turn)
132
+
133
+ # Client has sent NOP in response to server
134
+ elif opcode == "nop":
135
+ # Do nothing, NOP is a keepalive
136
+ pass
137
+
138
+ except websockets.exceptions.ConnectionClosed as e:
139
+ print(f"Connection closed with code {e.code}: {e.reason}")
140
+ finally:
141
+ # Cleanup on disconnect
142
+ if user.username and vm_id:
143
+ vm = vms[vm_id]
144
+ if user.username in vm.users:
145
+ del vm.users[user.username]
146
+ print(f"User {user.username} disconnected from VM {vm_id}")
147
+ # Announce removed user
148
+ for other_user_ws in vm.users.values():
149
+ await other_user_ws.send(encode_guac_message("remuser", "1", user.username))
150
+
151
+ # Main function to start the server
152
+ async def main():
153
+ server = await websockets.serve(
154
+ handle_client,
155
+ "0.0.0.0", 8765,
156
+ subprotocols=["guacamole"]
157
+ )
158
+ print("CollabVM server started on ws://localhost:8765")
159
+ await server.wait_closed()
160
+
161
+ if __name__ == "__main__":
162
+ asyncio.run(main())