Skip to content

Commit fa54aee

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

File tree

5 files changed

+96
-98
lines changed

5 files changed

+96
-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: 73 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,67 @@ 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 xdrlib is deprecated in Python 3.11, so we implement the following serialization classes
44+
# See https://datatracker.ietf.org/doc/html/rfc1014 for the XDR specification.
45+
46+
47+
class Int32Serializer(Serializer[int], Deserializer[int]):
48+
def serialize(self, i: int) -> bytes:
49+
return i.to_bytes(length=4, byteorder="big", signed=True)
50+
51+
def deserialize(self, payload: io.BytesIO) -> int:
52+
return int.from_bytes(payload.read(4), byteorder="big", signed=True)
53+
54+
55+
class UInt32Serializer(Serializer[int], Deserializer[int]):
56+
def serialize(self, i: int) -> bytes:
3757
return i.to_bytes(length=4, byteorder="big", signed=False)
3858

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

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

53106
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
107+
return OpaqueVarLengthSerializer().serialize(body)
60108

61109
def _write_string(self, s: str) -> bytes:
62-
return self._write_var_length_opaque(s.encode("ascii"))
110+
return StringSerializer().serialize(s)
63111

64112

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.
113+
class XdrDeserializer(Generic[Serializable], Deserializer[Serializable]):
76114
def _read_uint32(self, payload: io.BytesIO) -> int:
77-
return int.from_bytes(payload.read(4), byteorder="big", signed=False)
115+
return UInt32Serializer().deserialize(payload)
78116

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

82120
def _read_uint64(self, payload: io.BytesIO) -> int:
83121
return int.from_bytes(payload.read(8), byteorder="big", signed=False)
@@ -86,19 +124,15 @@ def _read_enum(self, payload: io.BytesIO, enum: EnumType) -> EnumType:
86124
value = self._read_int32(payload)
87125
return enum(value)
88126

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-
96127
def _read_var_length(self, payload: io.BytesIO, deserializer: Deserializer[ElementType]) -> list[ElementType]:
97128
length = self._read_uint32(payload)
98129
return [deserializer.deserialize(payload) for _ in range(length)]
99130

131+
def _read_var_length_opaque(self, payload: io.BytesIO) -> bytes:
132+
return OpaqueVarLengthSerializer().deserialize(payload)
133+
100134
def _read_string(self, payload: io.BytesIO) -> str:
101-
return self._read_var_length_opaque(payload).decode("ascii")
135+
return StringSerializer().deserialize(payload)
102136

103137
def _read_optional(self, payload: io.BytesIO, deserializer: Deserializer[ElementType]) -> ElementType | None:
104138
has_value = self._read_enum(payload, sunrpc.Bool)
@@ -107,42 +141,6 @@ def _read_optional(self, payload: io.BytesIO, deserializer: Deserializer[Element
107141
return deserializer.deserialize(payload)
108142

109143

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-
146144
class ReplyStat(Enum):
147145
MSG_ACCEPTED = 0
148146
MSG_DENIED = 1
@@ -155,7 +153,7 @@ class AuthFlavor(Enum):
155153
AUTH_DES = 3
156154

157155

158-
class AuthSerializer(Generic[AuthProtocol], Serializer[AuthProtocol], Deserializer[AuthProtocol]):
156+
class AuthSerializer(Generic[AuthProtocol], XdrSerializer[AuthProtocol], XdrDeserializer[AuthProtocol]):
159157
def serialize(self, protocol: AuthProtocol) -> bytes:
160158
flavor = self._flavor()
161159
result = self._write_int32(flavor)
@@ -219,13 +217,13 @@ def _read_body(self, payload: io.BytesIO) -> sunrpc.AuthUnix:
219217

220218
class MessageSerializer(
221219
Generic[ProcedureParams, ProcedureResults, Credentials, Verifier],
222-
Serializer[sunrpc.Message[ProcedureParams, ProcedureResults, Credentials, Verifier]],
223-
Deserializer[sunrpc.Message[ProcedureParams, ProcedureResults, Credentials, Verifier]],
220+
XdrSerializer[sunrpc.Message[ProcedureParams, ProcedureResults, Credentials, Verifier]],
221+
XdrDeserializer[sunrpc.Message[ProcedureParams, ProcedureResults, Credentials, Verifier]],
224222
):
225223
def __init__(
226224
self,
227-
paramsSerializer: Serializer[ProcedureParams],
228-
resultsDeserializer: Deserializer[ProcedureResults],
225+
paramsSerializer: XdrSerializer[ProcedureParams],
226+
resultsDeserializer: XdrDeserializer[ProcedureResults],
229227
credentialsSerializer: AuthSerializer[Credentials],
230228
verifierSerializer: AuthSerializer[Verifier],
231229
):
@@ -296,7 +294,7 @@ def _read_mismatch(self, payload: io.BytesIO) -> sunrpc.Mismatch:
296294
return sunrpc.Mismatch(low, high)
297295

298296

299-
class PortMappingSerializer(Serializer[sunrpc.PortMapping]):
297+
class PortMappingSerializer(XdrSerializer[sunrpc.PortMapping]):
300298
def serialize(self, port_mapping: sunrpc.PortMapping) -> bytes:
301299
result = self._write_uint32(port_mapping.program)
302300
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)