Skip to content

Commit fba07e5

Browse files
committed
refactor: move implementation of xdr primitives to serializer / deserializer subclass.
- Replace enums with intenums
1 parent 92ede66 commit fba07e5

File tree

5 files changed

+97
-98
lines changed

5 files changed

+97
-98
lines changed

dissect/target/helpers/nfs/nfs3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from enum import Enum, IntEnum
4+
from enum import IntEnum
55
from typing import ClassVar, NamedTuple
66

77
# See https://datatracker.ietf.org/doc/html/rfc1057
@@ -20,7 +20,7 @@ class ProcedureDescriptor(NamedTuple):
2020
ReadFileProc = ProcedureDescriptor(100003, 3, 6)
2121

2222

23-
class Nfs3Stat(Enum):
23+
class Nfs3Stat(IntEnum):
2424
OK = 0
2525
ERR_PERM = 1
2626
ERR_NOENT = 2

dissect/target/helpers/nfs/serializer.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@
2020
SpecData3,
2121
)
2222
from dissect.target.helpers.sunrpc.serializer import (
23-
Deserializer,
2423
Int32Serializer,
2524
OpaqueVarLengthSerializer,
26-
Serializer,
25+
XdrDeserializer,
26+
XdrSerializer,
2727
)
2828
from dissect.target.helpers.sunrpc.sunrpc import Bool
2929

3030

3131
# Used Union because 3.9 does not support '|' here even with future annotations
32-
class MountResultDeserializer(Deserializer[Union[MountOK, MountStat3]]):
32+
class MountResultDeserializer(XdrDeserializer[Union[MountOK, MountStat3]]):
3333
def deserialize(self, payload: io.BytesIO) -> MountOK | MountStat3:
3434
mount_stat = self._read_enum(payload, MountStat3)
3535
if mount_stat != MountStat3.OK:
@@ -40,7 +40,7 @@ def deserialize(self, payload: io.BytesIO) -> MountOK | MountStat3:
4040
return MountOK(FileHandle3(filehandle_bytes), auth_flavors)
4141

4242

43-
class ReadDirPlusParamsSerializer(Serializer[ReadDirPlusParams]):
43+
class ReadDirPlusParamsSerializer(XdrSerializer[ReadDirPlusParams]):
4444
def serialize(self, params: ReadDirPlusParams) -> bytes:
4545
result = self._write_var_length_opaque(params.dir.opaque)
4646
result += self._write_uint64(params.cookie)
@@ -51,23 +51,23 @@ def serialize(self, params: ReadDirPlusParams) -> bytes:
5151
return result
5252

5353

54-
class SpecDataSerializer(Deserializer[SpecData3]):
54+
class SpecDataSerializer(XdrDeserializer[SpecData3]):
5555
def deserialize(self, payload: io.BytesIO) -> bytes:
5656
specdata1 = self._read_uint32(payload)
5757
specdata2 = self._read_uint32(payload)
5858

5959
return SpecData3(specdata1, specdata2)
6060

6161

62-
class NfsTimeSerializer(Deserializer[NfsTime3]):
62+
class NfsTimeSerializer(XdrDeserializer[NfsTime3]):
6363
def deserialize(self, payload: io.BytesIO) -> bytes:
6464
seconds = self._read_uint32(payload)
6565
nseconds = self._read_uint32(payload)
6666

6767
return NfsTime3(seconds, nseconds)
6868

6969

70-
class FileAttributesSerializer(Deserializer[FileAttributes3]):
70+
class FileAttributesSerializer(XdrDeserializer[FileAttributes3]):
7171
def deserialize(self, payload: io.BytesIO) -> FileAttributes3:
7272
type = self._read_enum(payload, FileType3)
7373
mode = self._read_uint32(payload)
@@ -87,7 +87,7 @@ def deserialize(self, payload: io.BytesIO) -> FileAttributes3:
8787
return FileAttributes3(type, mode, nlink, uid, gid, size, used, rdev, fsid, fileid, atime, mtime, ctime)
8888

8989

90-
class EntryPlusSerializer(Deserializer[EntryPlus3]):
90+
class EntryPlusSerializer(XdrDeserializer[EntryPlus3]):
9191
def deserialize(self, payload: io.BytesIO) -> EntryPlus3:
9292
fileid = self._read_uint64(payload)
9393
name = self._read_string(payload)
@@ -100,7 +100,7 @@ def deserialize(self, payload: io.BytesIO) -> EntryPlus3:
100100

101101

102102
# Used Union because 3.9 does not support '|' here even with future annotations
103-
class ReadDirPlusResultDeserializer(Deserializer[Union[ReadDirPlusResult3, Nfs3Stat]]):
103+
class ReadDirPlusResultDeserializer(XdrDeserializer[Union[ReadDirPlusResult3, Nfs3Stat]]):
104104
def deserialize(self, payload: io.BytesIO) -> ReadDirPlusResult3:
105105
stat = self._read_enum(payload, Nfs3Stat)
106106
if stat != Nfs3Stat.OK:
@@ -122,15 +122,15 @@ def deserialize(self, payload: io.BytesIO) -> ReadDirPlusResult3:
122122
return ReadDirPlusResult3(dir_attributes, CookieVerf3(cookieverf), entries, eof)
123123

124124

125-
class Read3ArgsSerializer(Serializer[ReadDirPlusParams]):
125+
class Read3ArgsSerializer(XdrSerializer[ReadDirPlusParams]):
126126
def serialize(self, args: Read3args) -> bytes:
127127
result = self._write_var_length_opaque(args.file.opaque)
128128
result += self._write_uint64(args.offset)
129129
result += self._write_uint32(args.count)
130130
return result
131131

132132

133-
class Read3ResultDeserializer(Deserializer[Read3resok]):
133+
class Read3ResultDeserializer(XdrDeserializer[Read3resok]):
134134
def deserialize(self, payload: io.BytesIO) -> Read3resok:
135135
stat = self._read_enum(payload, Nfs3Stat)
136136
if stat != Nfs3Stat.OK:

dissect/target/helpers/sunrpc/client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
AuthNullSerializer,
1414
AuthSerializer,
1515
AuthUnixSerializer,
16-
Deserializer,
1716
MessageSerializer,
18-
Serializer,
17+
XdrDeserializer,
18+
XdrSerializer,
1919
)
2020

2121
Credentials = TypeVar("Credentials")
@@ -102,8 +102,8 @@ def call(
102102
self,
103103
proc_desc: ProcedureDescriptor,
104104
params: Params,
105-
params_serializer: Serializer[Params],
106-
result_deserializer: Deserializer[Results],
105+
params_serializer: XdrSerializer[Params],
106+
result_deserializer: XdrDeserializer[Results],
107107
) -> Results:
108108
"""Synchronously call an RPC procedure and return the result"""
109109

dissect/target/helpers/sunrpc/serializer.py

Lines changed: 74 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,68 @@ class Serializer(ABC, Generic[Serializable]):
3030
def serialize(self, _: Serializable) -> bytes:
3131
pass
3232

33-
# Unfortunately xdrlib is deprecated in Python 3.11, so we implement the following serialization methods
34-
# to be used by descendants of the Serializer class.
35-
# See https://datatracker.ietf.org/doc/html/rfc1014 for the XDR specification.
36-
def _write_uint32(self, i: int) -> bytes:
33+
34+
class Deserializer(ABC, Generic[Serializable]):
35+
def deserialize_from_bytes(self, payload: bytes) -> Serializable:
36+
return self.deserialize(io.BytesIO(payload))
37+
38+
@abstractmethod
39+
def deserialize(self, _: io.BytesIO) -> Serializable:
40+
pass
41+
42+
43+
# Unfortunately xd)rlib is deprecated in Python 3.11, so we implement the following serialization methods
44+
# to be used by descendants of the Serializer class.
45+
# See https://datatracker.ietf.org/doc/html/rfc1014 for the XDR specification.
46+
47+
48+
class Int32Serializer(Serializer[int], Deserializer[int]):
49+
def serialize(self, i: int) -> bytes:
50+
return i.to_bytes(length=4, byteorder="big", signed=True)
51+
52+
def deserialize(self, payload: io.BytesIO) -> int:
53+
return int.from_bytes(payload.read(4), byteorder="big", signed=True)
54+
55+
56+
class UInt32Serializer(Serializer[int], Deserializer[int]):
57+
def serialize(self, i: int) -> bytes:
3758
return i.to_bytes(length=4, byteorder="big", signed=False)
3859

60+
def deserialize(self, payload: io.BytesIO) -> int:
61+
return int.from_bytes(payload.read(4), byteorder="big", signed=False)
62+
63+
64+
class OpaqueVarLengthSerializer(Serializer[bytes], Deserializer[bytes]):
65+
def serialize(self, body: bytes) -> bytes:
66+
length = len(body)
67+
result = UInt32Serializer().serialize(length)
68+
result += body
69+
70+
padding_bytes = (ALIGNMENT - (length % ALIGNMENT)) % ALIGNMENT
71+
return result + b"\x00" * padding_bytes
72+
73+
def deserialize(self, payload: io.BytesIO) -> bytes:
74+
length = UInt32Serializer().deserialize(payload)
75+
result = payload.read(length)
76+
padding_bytes = (ALIGNMENT - (length % ALIGNMENT)) % ALIGNMENT
77+
payload.read(padding_bytes)
78+
return result
79+
80+
81+
class StringSerializer(Serializer[str], Deserializer[str]):
82+
def serialize(self, s: str) -> bytes:
83+
return OpaqueVarLengthSerializer().serialize(s.encode("ascii"))
84+
85+
def deserialize(self, payload: io.BytesIO) -> str:
86+
return OpaqueVarLengthSerializer().deserialize(payload).decode("ascii")
87+
88+
89+
class XdrSerializer(Generic[Serializable], Serializer[Serializable]):
90+
def _write_uint32(self, i: int) -> bytes:
91+
return UInt32Serializer().serialize(i)
92+
3993
def _write_int32(self, i: int) -> bytes:
40-
return i.to_bytes(length=4, byteorder="big", signed=True)
94+
return Int32Serializer().serialize(i)
4195

4296
def _write_uint64(self, i: int) -> bytes:
4397
return i.to_bytes(length=8, byteorder="big", signed=False)
@@ -51,33 +105,18 @@ def _write_var_length(self, elements: list[ElementType], serializer: Serializer[
51105
return result + b"".join(payload)
52106

53107
def _write_var_length_opaque(self, body: bytes) -> bytes:
54-
length = len(body)
55-
result = self._write_uint32(length)
56-
result += body
57-
58-
padding_bytes = (ALIGNMENT - (length % ALIGNMENT)) % ALIGNMENT
59-
return result + b"\x00" * padding_bytes
108+
return OpaqueVarLengthSerializer().serialize(body)
60109

61110
def _write_string(self, s: str) -> bytes:
62-
return self._write_var_length_opaque(s.encode("ascii"))
111+
return StringSerializer().serialize(s)
63112

64113

65-
class Deserializer(ABC, Generic[Serializable]):
66-
def deserialize_from_bytes(self, payload: bytes) -> Serializable:
67-
return self.deserialize(io.BytesIO(payload))
68-
69-
@abstractmethod
70-
def deserialize(self, _: io.BytesIO) -> Serializable:
71-
pass
72-
73-
# Unfortunately xdrlib is deprecated in Python 3.11, so we implement the following serialization methods
74-
# to be used by descendants of the Serializer class.
75-
# See https://datatracker.ietf.org/doc/html/rfc1014 for the XDR specification.
114+
class XdrDeserializer(Generic[Serializable], Deserializer[Serializable]):
76115
def _read_uint32(self, payload: io.BytesIO) -> int:
77-
return int.from_bytes(payload.read(4), byteorder="big", signed=False)
116+
return UInt32Serializer().deserialize(payload)
78117

79118
def _read_int32(self, payload: io.BytesIO) -> int:
80-
return int.from_bytes(payload.read(4), byteorder="big", signed=True)
119+
return Int32Serializer().deserialize(payload)
81120

82121
def _read_uint64(self, payload: io.BytesIO) -> int:
83122
return int.from_bytes(payload.read(8), byteorder="big", signed=False)
@@ -86,19 +125,15 @@ def _read_enum(self, payload: io.BytesIO, enum: EnumType) -> EnumType:
86125
value = self._read_int32(payload)
87126
return enum(value)
88127

89-
def _read_var_length_opaque(self, payload: io.BytesIO) -> bytes:
90-
length = self._read_uint32(payload)
91-
result = payload.read(length)
92-
padding_bytes = (ALIGNMENT - (length % ALIGNMENT)) % ALIGNMENT
93-
payload.read(padding_bytes)
94-
return result
95-
96128
def _read_var_length(self, payload: io.BytesIO, deserializer: Deserializer[ElementType]) -> list[ElementType]:
97129
length = self._read_uint32(payload)
98130
return [deserializer.deserialize(payload) for _ in range(length)]
99131

132+
def _read_var_length_opaque(self, payload: io.BytesIO) -> bytes:
133+
return OpaqueVarLengthSerializer().deserialize(payload)
134+
100135
def _read_string(self, payload: io.BytesIO) -> str:
101-
return self._read_var_length_opaque(payload).decode("ascii")
136+
return StringSerializer().deserialize(payload)
102137

103138
def _read_optional(self, payload: io.BytesIO, deserializer: Deserializer[ElementType]) -> ElementType | None:
104139
has_value = self._read_enum(payload, sunrpc.Bool)
@@ -107,42 +142,6 @@ def _read_optional(self, payload: io.BytesIO, deserializer: Deserializer[Element
107142
return deserializer.deserialize(payload)
108143

109144

110-
# RdJ: A bit clunky having to lift the primitives inside the Serializer/Deserializer class
111-
# to enable composition.
112-
# Possible design mistake, Alternatively, make serializers functions, since no state is kept.
113-
# But most of our stuff is OOP, so it would be inconsistent.
114-
class Int32Serializer(Serializer[int], Deserializer[int]):
115-
def serialize(self, i: int) -> bytes:
116-
return self._write_int32(i)
117-
118-
def deserialize(self, payload: io.BytesIO) -> int:
119-
return self._read_int32(payload)
120-
121-
122-
class UInt32Serializer(Serializer[int], Deserializer[int]):
123-
def serialize(self, i: int) -> bytes:
124-
return self._write_uint32(i)
125-
126-
def deserialize(self, payload: io.BytesIO) -> int:
127-
return self._read_uint32(payload)
128-
129-
130-
class StringSerializer(Serializer[str], Deserializer[str]):
131-
def serialize(self, s: str) -> bytes:
132-
return self._write_string(s)
133-
134-
def deserialize(self, payload: io.BytesIO) -> str:
135-
return self._read_string(payload)
136-
137-
138-
class OpaqueVarLengthSerializer(Serializer[bytes], Deserializer[bytes]):
139-
def serialize(self, body: bytes) -> bytes:
140-
return self._write_var_length_opaque(body)
141-
142-
def deserialize(self, payload: io.BytesIO) -> bytes:
143-
return self._read_var_length_opaque(payload)
144-
145-
146145
class ReplyStat(Enum):
147146
MSG_ACCEPTED = 0
148147
MSG_DENIED = 1
@@ -155,7 +154,7 @@ class AuthFlavor(Enum):
155154
AUTH_DES = 3
156155

157156

158-
class AuthSerializer(Generic[AuthProtocol], Serializer[AuthProtocol], Deserializer[AuthProtocol]):
157+
class AuthSerializer(Generic[AuthProtocol], XdrSerializer[AuthProtocol], XdrDeserializer[AuthProtocol]):
159158
def serialize(self, protocol: AuthProtocol) -> bytes:
160159
flavor = self._flavor()
161160
result = self._write_int32(flavor)
@@ -219,13 +218,13 @@ def _read_body(self, payload: io.BytesIO) -> sunrpc.AuthUnix:
219218

220219
class MessageSerializer(
221220
Generic[ProcedureParams, ProcedureResults, Credentials, Verifier],
222-
Serializer[sunrpc.Message[ProcedureParams, ProcedureResults, Credentials, Verifier]],
223-
Deserializer[sunrpc.Message[ProcedureParams, ProcedureResults, Credentials, Verifier]],
221+
XdrSerializer[sunrpc.Message[ProcedureParams, ProcedureResults, Credentials, Verifier]],
222+
XdrDeserializer[sunrpc.Message[ProcedureParams, ProcedureResults, Credentials, Verifier]],
224223
):
225224
def __init__(
226225
self,
227-
paramsSerializer: Serializer[ProcedureParams],
228-
resultsDeserializer: Deserializer[ProcedureResults],
226+
paramsSerializer: XdrSerializer[ProcedureParams],
227+
resultsDeserializer: XdrDeserializer[ProcedureResults],
229228
credentialsSerializer: AuthSerializer[Credentials],
230229
verifierSerializer: AuthSerializer[Verifier],
231230
):
@@ -296,7 +295,7 @@ def _read_mismatch(self, payload: io.BytesIO) -> sunrpc.Mismatch:
296295
return sunrpc.Mismatch(low, high)
297296

298297

299-
class PortMappingSerializer(Serializer[sunrpc.PortMapping]):
298+
class PortMappingSerializer(XdrSerializer[sunrpc.PortMapping]):
300299
def serialize(self, port_mapping: sunrpc.PortMapping) -> bytes:
301300
result = self._write_uint32(port_mapping.program)
302301
result += self._write_uint32(port_mapping.version)

dissect/target/helpers/sunrpc/sunrpc.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from enum import Enum
4+
from enum import IntEnum
55
from typing import Generic, TypeVar
66

77

8-
class Bool(Enum):
8+
class Bool(IntEnum):
99
FALSE = 0
1010
TRUE = 1
1111

1212

13-
class AcceptStat(Enum):
13+
class AcceptStat(IntEnum):
1414
SUCCESS = 0 # RPC executed successfully
1515
PROG_UNAVAIL = 1 # remote hasn't exported program
1616
PROG_MISMATCH = 2 # remote can't support version #
1717
PROC_UNAVAIL = 3 # program can't support procedure
1818
GARBAGE_ARGS = 4 # procedure can't decode params
1919

2020

21-
class RejectStat(Enum):
21+
class RejectStat(IntEnum):
2222
RPC_MISMATCH = 0
2323
AUTH_ERROR = 1
2424

2525

26-
class AuthStat(Enum):
26+
class AuthStat(IntEnum):
2727
AUTH_BADCRED = 1 # bad credentials (seal broken)
2828
AUTH_REJECTEDCRED = 2 # client must begin new session
2929
AUTH_BADVERF = 3 # bad verifier (seal broken)
@@ -94,7 +94,7 @@ class Message(Generic[ProcedureParams, ProcedureResults, Credentials, Verifier])
9494
body: CallBody[ProcedureParams, Credentials, Verifier] | AcceptedReply[ProcedureResults, Verifier] | RejectedReply
9595

9696

97-
class Protocol(Enum):
97+
class Protocol(IntEnum):
9898
TCP = 6
9999
UDP = 17
100100

0 commit comments

Comments
 (0)