init: workspace opencode sync
This commit is contained in:
+135
@@ -0,0 +1,135 @@
|
||||
import socket, hashlib
|
||||
|
||||
class RouterOSApi:
|
||||
def __init__(self, host, port=8728):
|
||||
self.host = host; self.port = port; self.sock = None
|
||||
def connect(self, username, password):
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(10)
|
||||
self.sock.connect((self.host, self.port))
|
||||
self._write_sentence('/login', f'=name={username}', f'=password={password}')
|
||||
resp = self._read_response()
|
||||
if any(isinstance(r, tuple) and r[0] == '!trap' for r in resp):
|
||||
chal = None
|
||||
for item in resp:
|
||||
if isinstance(item, dict) and 'ret' in item: chal = item['ret']
|
||||
if chal and len(chal) == 32:
|
||||
response = '00' + hashlib.md5(b'\x00' + password.encode() + bytes.fromhex(chal)).hexdigest()
|
||||
self._write_sentence('/login', f'=name={username}', f'=response={response}')
|
||||
resp = self._read_response()
|
||||
return self
|
||||
def _encode_length(self, length):
|
||||
if length < 0x80: return bytes([length])
|
||||
elif length < 0x4000: length |= 0x8000; return bytes([(length>>8)&0xFF, length&0xFF])
|
||||
elif length < 0x200000: length |= 0xC00000; return bytes([(length>>16)&0xFF, (length>>8)&0xFF, length&0xFF])
|
||||
elif length < 0x10000000: length |= 0xE0000000; return bytes([(length>>24)&0xFF, (length>>16)&0xFF, (length>>8)&0xFF, length&0xFF])
|
||||
else: return bytes([0xF0, (length>>24)&0xFF, (length>>16)&0xFF, (length>>8)&0xFF, length&0xFF])
|
||||
def _write_word(self, word): self.sock.sendall(self._encode_length(len(word)) + word.encode('utf-8'))
|
||||
def _write_sentence(self, cmd, *words):
|
||||
self._write_word(cmd)
|
||||
for w in words: self._write_word(w)
|
||||
self.sock.sendall(b'\x00')
|
||||
def _read_byte(self): return self.sock.recv(1)
|
||||
def _read_word_raw(self):
|
||||
"""Read a word and return raw bytes"""
|
||||
first = self._read_byte()
|
||||
if first == b'\x00': return None
|
||||
lb = first[0]
|
||||
if lb & 0x80:
|
||||
if (lb & 0xC0) == 0x80: length = ((lb & 0x3F) << 8) + self._read_byte()[0]
|
||||
elif (lb & 0xE0) == 0xC0: length = ((lb & 0x1F) << 16) + (self._read_byte()[0] << 8) + self._read_byte()[0]
|
||||
elif (lb & 0xF0) == 0xE0: length = ((lb & 0x0F) << 24) + (self._read_byte()[0] << 16) + (self._read_byte()[0] << 8) + self._read_byte()[0]
|
||||
else: length = (self._read_byte()[0] << 24) + (self._read_byte()[0] << 16) + (self._read_byte()[0] << 8) + self._read_byte()[0]
|
||||
else: length = lb
|
||||
data = b''
|
||||
while len(data) < length:
|
||||
chunk = self.sock.recv(length - len(data))
|
||||
if not chunk: break
|
||||
data += chunk
|
||||
return data
|
||||
def _read_sentence(self):
|
||||
words = []
|
||||
while True:
|
||||
w = self._read_word_raw()
|
||||
if w is None: break
|
||||
words.append(w)
|
||||
return words
|
||||
def _read_response(self):
|
||||
responses = []
|
||||
while True:
|
||||
sentence = self._read_sentence()
|
||||
if not sentence: break
|
||||
reply = sentence[0].decode('utf-8', errors='replace')
|
||||
rest = sentence[1:]
|
||||
if reply == '!done': break
|
||||
elif reply == '!re':
|
||||
d = {}
|
||||
for w in rest:
|
||||
ws = w.decode('utf-8', errors='replace')
|
||||
if '=' in ws:
|
||||
parts = ws.split('=', 2)
|
||||
d[parts[1]] = parts[2] if len(parts) >= 3 else ''
|
||||
else: d[ws] = ''
|
||||
responses.append(d)
|
||||
elif reply == '!trap':
|
||||
responses.append(('!trap', [r.decode('utf-8', errors='replace') for r in rest]))
|
||||
return responses
|
||||
def _read_response_raw(self):
|
||||
"""Read and return raw sentences without decoding"""
|
||||
sentences = []
|
||||
while True:
|
||||
sentence = self._read_sentence()
|
||||
if not sentence: break
|
||||
reply = sentence[0]
|
||||
rest = sentence[1:]
|
||||
if reply == b'!done': break
|
||||
sentences.append((reply, rest))
|
||||
return sentences
|
||||
def cmd(self, command, **attrs):
|
||||
words = [command]
|
||||
for k, v in attrs.items():
|
||||
if k.startswith('?'): words.append(f'{k}={v}')
|
||||
else: words.append(f'={k}={v}')
|
||||
self._write_sentence(*words)
|
||||
return self._read_response()
|
||||
def close(self):
|
||||
if self.sock: self.sock.close(); self.sock = None
|
||||
|
||||
api = RouterOSApi('192.168.100.2')
|
||||
api.connect('mi4', 'm14')
|
||||
|
||||
# Read raw bytes of Bulanan2 on-event
|
||||
words = [command:= '/system/scheduler/print']
|
||||
words.append('?name=Bulanan2')
|
||||
api._write_sentence(*words)
|
||||
raw_resp = api._read_response_raw()
|
||||
|
||||
# Find the on-event field
|
||||
for reply, rest in raw_resp:
|
||||
if reply == b'!re':
|
||||
for w in rest:
|
||||
ws = w.decode('utf-8', errors='replace')
|
||||
if ws.startswith('=on-event='):
|
||||
# Get raw bytes after '=on-event='
|
||||
prefix = b'=on-event='
|
||||
idx = w.find(prefix)
|
||||
if idx >= 0:
|
||||
on_event_bytes = w[idx + len(prefix):]
|
||||
print(f"on-event raw bytes length: {len(on_event_bytes)}")
|
||||
|
||||
# Find all occurrences of :pic in raw bytes
|
||||
search = b':pic'
|
||||
pos = 0
|
||||
while True:
|
||||
pos = on_event_bytes.find(search, pos)
|
||||
if pos == -1: break
|
||||
context = on_event_bytes[max(0,pos-5):pos+20]
|
||||
print(f"\n ':pic' at byte {pos}")
|
||||
print(f" Hex: {' '.join(f'{b:02x}' for b in context)}")
|
||||
print(f" ASCII: {''.join(chr(b) if 32 <= b < 127 else '?' for b in context)}")
|
||||
# Show what follows each :pic
|
||||
after = on_event_bytes[pos:pos+10]
|
||||
print(f" Next 10 bytes: {' '.join(f'{b:02x}' for b in after)}")
|
||||
pos += 4
|
||||
|
||||
api.close()
|
||||
Reference in New Issue
Block a user