Skip to content

Commit

Permalink
Implement packet package
Browse files Browse the repository at this point in the history
  • Loading branch information
Cirras committed Nov 6, 2023
1 parent 13b0a5f commit 519ae02
Show file tree
Hide file tree
Showing 6 changed files with 341 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/eolib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .data import *
from .encrypt import *
from .packet import *
2 changes: 2 additions & 0 deletions src/eolib/packet/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .sequence_start import *
from .packet_sequencer import *
45 changes: 45 additions & 0 deletions src/eolib/packet/packet_sequencer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from .sequence_start import SequenceStart


class PacketSequencer:
"""A class for generating packet sequences."""

_start: SequenceStart
_counter: int

def __init__(self, start: SequenceStart):
"""
Constructs a new PacketSequencer with the provided SequenceStart.
Args:
start (SequenceStart): The sequence start.
"""
self._start = start
self._counter = 0

def next_sequence(self) -> int:
"""
Returns the next sequence value, updating the sequence counter in the process.
Note:
This is not a monotonic operation. The sequence counter increases from 0 to 9 before
looping back around to 0.
Returns:
int: The next sequence value.
"""
result = self._start.value + self._counter
self._counter = (self._counter + 1) % 10
return result

def set_sequence_start(self, start: SequenceStart) -> None:
"""
Sets the sequence start, also known as the "starting counter ID".
Note:
This does not reset the sequence counter.
Args:
start (SequenceStart): The new sequence start.
"""
self._start = start
211 changes: 211 additions & 0 deletions src/eolib/packet/sequence_start.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import random
from abc import ABC, abstractproperty
from eolib.data.eo_numeric_limits import CHAR_MAX


class SequenceStart(ABC):
"""
A value sent by the server to update the client's sequence start, also known as the 'starting
counter ID'.
"""

@abstractproperty
def value(self) -> int:
"""
int: Gets the sequence start value.
"""
raise NotImplementedError()

@staticmethod
def zero() -> 'SequenceStart':
"""
Returns an instance of SequenceStart with a value of 0.
Returns:
SequenceStart: An instance of SequenceStart.
"""
return SimpleSequenceStart(0)


class SimpleSequenceStart(SequenceStart):
_value: int

def __init__(self, value: int):
self._value = value

@property
def value(self):
return self._value


class AccountReplySequenceStart(SimpleSequenceStart):
"""
A class representing the sequence start value sent with the `ACCOUNT_REPLY` server packet.
See Also:
- [`AccountReplyServerPacket`][eolib.protocol._generated.net.server.AccountReplyServerPacket]
"""

def __init__(self, value: int):
super().__init__(value)

@staticmethod
def from_value(value: int) -> 'AccountReplySequenceStart':
"""
Creates an instance of AccountReplySequenceStart from the value sent with the
`ACCOUNT_REPLY` server packet.
Args:
value (int): The sequence start value sent with the `ACCOUNT_REPLY` server packet.
Returns:
AccountReplySequenceStart: An instance of AccountReplySequenceStart.
"""
return AccountReplySequenceStart(value)

@staticmethod
def generate() -> 'AccountReplySequenceStart':
"""
Generates an instance of AccountReplySequenceStart with a random value in the range 0-240.
Returns:
AccountReplySequenceStart: An instance of AccountReplySequenceStart.
"""
return AccountReplySequenceStart(random.randrange(0, 240))


class InitSequenceStart(SimpleSequenceStart):
"""
A class representing the sequence start value sent with the `INIT_INIT` server packet.
See Also:
- [`InitInitServerPacket`][eolib.protocol._generated.net.server.InitInitServerPacket]
"""

_seq1: int
_seq2: int

def __init__(self, value: int, seq1: int, seq2: int):
super().__init__(value)
self._seq1 = seq1
self._seq2 = seq2

@property
def seq1(self) -> int:
"""
Returns the `seq1` byte value sent with the INIT_INIT server packet.
Returns:
int: The seq1 byte value.
"""
return self._seq1

@property
def seq2(self) -> int:
"""
Returns the `seq2` byte value sent with the INIT_INIT server packet.
Returns:
int: The seq2 byte value.
"""
return self._seq2

@staticmethod
def from_init_values(seq1: int, seq2: int) -> 'InitSequenceStart':
"""
Creates an instance of InitSequenceStart from the values sent with the `INIT_INIT` server
packet.
Args:
seq1 (int): The `seq1` byte value sent with the `INIT_INIT` server packet.
seq2 (int): The `seq2` byte value sent with the `INIT_INIT` server packet.
Returns:
InitSequenceStart: An instance of InitSequenceStart
"""
value = seq1 * 7 + seq2 - 13
return InitSequenceStart(value, seq1, seq2)

@staticmethod
def generate() -> 'InitSequenceStart':
"""
Generates an instance of InitSequenceStart with a random value in the range 0-1757.
Returns:
InitSequenceStart: An instance of InitSequenceStart.
"""
value = random.randrange(0, 1757)
seq1_max = int((value + 13) / 7)
seq1_min = max(0, int((value - (CHAR_MAX - 1) + 13 + 6) / 7))

seq1 = random.randrange(0, seq1_max - seq1_min) + seq1_min
seq2 = value - seq1 * 7 + 13

return InitSequenceStart(value, seq1, seq2)


class PingSequenceStart(SimpleSequenceStart):
"""
A class representing the sequence start value sent with the `CONNECTION_PLAYER` server packet.
See Also:
- [`ConnectionPlayerServerPacket`][eolib.protocol._generated.net.server.ConnectionPlayerServerPacket]
"""

def __init__(self, value: int, seq1: int, seq2: int):
super().__init__(value)
self._seq1 = seq1
self._seq2 = seq2

@property
def seq1(self) -> int:
"""
Returns the seq1 short value sent with the `CONNECTION_PLAYER` server packet.
Returns:
int: The seq1 short value.
"""
return self._seq1

@property
def seq2(self) -> int:
"""
Returns the seq2 char value sent with the CONNECTION_PLAYER server packet.
Returns:
int: The seq2 char value.
"""
return self._seq2

@staticmethod
def from_ping_values(seq1: int, seq2: int) -> 'PingSequenceStart':
"""
Creates an instance of PingSequenceStart from the values sent with the `CONNECTION_PLAYER`
server packet.
Args:
seq1 (int): The `seq1` short value sent with the `CONNECTION_PLAYER` server packet.
seq2 (int): The `seq2` char value sent with the `CONNECTION_PLAYER` server packet.
Returns:
PingSequenceStart: An instance of PingSequenceStart.
"""
value = seq1 - seq2
return PingSequenceStart(value, seq1, seq2)

@staticmethod
def generate() -> 'PingSequenceStart':
"""
Generates an instance of PingSequenceStart with a random value in the range 0-1757.
Returns:
PingSequenceStart: An instance of PingSequenceStart.
"""
value = random.randrange(0, 1757)
seq1 = value + random.randrange(0, CHAR_MAX - 1)
seq2 = seq1 - value

return PingSequenceStart(value, seq1, seq2)


__all__ = ['SequenceStart', 'AccountReplySequenceStart', 'InitSequenceStart', 'PingSequenceStart']
23 changes: 23 additions & 0 deletions tests/packet/test_packet_sequencer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from eolib.packet.packet_sequencer import PacketSequencer
from eolib.packet.sequence_start import AccountReplySequenceStart


def test_next_sequence():
sequence_start = AccountReplySequenceStart.from_value(123)
sequencer = PacketSequencer(sequence_start)

for i in range(10):
assert sequencer.next_sequence() == 123 + i

assert sequencer.next_sequence() == 123


def test_sequence_start():
sequence_start = AccountReplySequenceStart.from_value(100)
sequencer = PacketSequencer(sequence_start)

assert sequencer.next_sequence() == 100

sequencer.set_sequence_start(AccountReplySequenceStart.from_value(200))

assert sequencer.next_sequence() == 201
59 changes: 59 additions & 0 deletions tests/packet/test_sequence_start.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import math
from eolib.packet.sequence_start import (
SequenceStart,
AccountReplySequenceStart,
InitSequenceStart,
PingSequenceStart,
)


def randrange_midpoint(a, b):
return math.ceil((a + b) / 2)


def test_zero():
assert SequenceStart.zero().value == 0


def test_account_reply_from_value():
sequence_start = AccountReplySequenceStart.from_value(22)
assert sequence_start.value == 22


def test_account_reply_generate(monkeypatch):
monkeypatch.setattr('random.randrange', randrange_midpoint)

sequence_start = AccountReplySequenceStart.generate()
assert sequence_start.value == 120


def test_init__from_init_values():
sequence_start = InitSequenceStart.from_init_values(110, 122)
assert sequence_start.value == 879
assert sequence_start.seq1 == 110
assert sequence_start.seq2 == 122


def test_init_generate(monkeypatch):
monkeypatch.setattr('random.randrange', randrange_midpoint)

sequence_start = InitSequenceStart.generate()
assert sequence_start.value == 879
assert sequence_start.seq1 == 110
assert sequence_start.seq2 == 122


def test_ping_from_ping_values():
sequence_start = PingSequenceStart.from_ping_values(1005, 126)
assert sequence_start.value == 879
assert sequence_start.seq1 == 1005
assert sequence_start.seq2 == 126


def test_ping_generate(monkeypatch):
monkeypatch.setattr('random.randrange', randrange_midpoint)

sequence_start = PingSequenceStart.generate()
assert sequence_start.value == 879
assert sequence_start.seq1 == 1005
assert sequence_start.seq2 == 126

0 comments on commit 519ae02

Please sign in to comment.