22from typing import Literal , cast
33from uuid import uuid4
44
5- from pydantic import BaseModel , Field
5+ from pydantic import BaseModel , Field , field_validator
6+
7+
8+ def _validate_engine_id_format (engine_id : str ) -> str :
9+ """
10+ Basic format check for SNMPv3 Engine ID.
11+ - Allows empty string
12+ - Must be valid hex string (after cleaning)
13+ - Length: 5–32 bytes (10–64 hex digits)
14+ - No RFC compliance guaranteed
15+ """
16+ if not engine_id :
17+ return engine_id # allowed for v1/v2c
18+
19+ cleaned = engine_id .replace (":" , "" ).replace (" " , "" ).lower ()
20+
21+ if len (cleaned ) % 2 != 0 :
22+ raise ValueError ("Engine ID must contain full bytes (even number of hex digits)" )
23+
24+ try :
25+ raw = bytes .fromhex (cleaned )
26+ except ValueError :
27+ raise ValueError ("Engine ID must be a valid hex string" )
28+
29+ if not (5 <= len (raw ) <= 32 ):
30+ raise ValueError ("Engine ID must be 5–32 bytes long" )
31+
32+ return cleaned
633
734
835# --- User Enum Classes ---
@@ -44,7 +71,7 @@ class SnmpVersion(int, Enum):
4471
4572
4673# --- Data Model Classes ---
47- class SnmpUser (BaseModel ):
74+ class SnmpUser (BaseModel , validate_assignment = True ):
4875 level : SecurityLevel = SecurityLevel .NO_AUTH_NO_PRIV
4976 name : str = "New User"
5077 authProtocol : AuthProtocol = AuthProtocol .MD5
@@ -53,32 +80,47 @@ class SnmpUser(BaseModel):
5380 privPassword : str = ""
5481 authPassword : str = ""
5582
83+ @field_validator ("engineId" )
84+ def validate_engine_id (cls , value : str ) -> str :
85+ """
86+ Validates the SNMPv3 Engine ID format.
87+ - Allows empty string
88+ - Must be valid hex string (after cleaning)
89+ - Length: 5–32 bytes (10–64 hex digits)
90+ """
91+ return _validate_engine_id_format (value )
92+
5693
57- class SnmpDescriptor (BaseModel ):
94+ class SnmpDescriptor (BaseModel , validate_assignment = True ):
5895 label : str = ""
5996 desc : str = ""
6097
6198
62- class SnmpSecurityEntry (BaseModel ):
99+ class SnmpSecurityEntry (BaseModel , validate_assignment = True ):
63100 user : str = "" # User ID from "Users" section. Must be a valid UUID of an existing user.
64101 community : str = "" # Value from "Protocol Settings => SNMP v1/v2c Security => Write / Read community"
65102
66103
67- class SnmpSecurity (BaseModel ):
104+ class SnmpSecurity (BaseModel , validate_assignment = True ):
68105 read : SnmpSecurityEntry = SnmpSecurityEntry (community = "public" )
69106 write : SnmpSecurityEntry = SnmpSecurityEntry (community = "private" )
70107
71108
72- class SnmpProtocolSettings (BaseModel ):
109+ class SnmpProtocolSettings (BaseModel , validate_assignment = True ):
73110 preferredVersion : SnmpVersion = SnmpVersion .V2C
74- retries : int = 1
75- maxRepetitions : int = 10
111+ retries : int = Field ( default = 1 , ge = 0 )
112+ maxRepetitions : int = Field ( default = 10 , ge = 0 )
76113 useGetBulk : bool = True
77- timeout : int = 5000
114+ timeout : int = Field ( default = 5000 , ge = 0 )
78115 localEngineId : str = ""
79116
117+ @field_validator ("localEngineId" )
118+ @classmethod
119+ def validate_engine_id (cls , value : str ) -> str :
120+ return _validate_engine_id_format (value )
121+
80122
81- class SnmpConfiguration (BaseModel ):
123+ class SnmpConfiguration (BaseModel , validate_assignment = True ):
82124 id : str = Field (alias = "_id" )
83125 descriptor : SnmpDescriptor = Field (default_factory = SnmpDescriptor )
84126 users : dict [str , SnmpUser ] = Field (default_factory = dict )
@@ -161,8 +203,6 @@ def retries(self) -> int:
161203 @retries .setter
162204 def retries (self , value : int ):
163205 """Sets the number of retries for SNMP requests."""
164- if not isinstance (value , int ) or value < 0 :
165- raise ValueError ("Retries must be a non-negative integer." )
166206 self .protocol .retries = value
167207
168208 @property
@@ -173,8 +213,6 @@ def timeout(self) -> int:
173213 @timeout .setter
174214 def timeout (self , value : int ):
175215 """Sets the timeout for SNMP requests in milliseconds."""
176- if not isinstance (value , int ) or value < 0 :
177- raise ValueError ("Timeout must be a non-negative integer." )
178216 self .protocol .timeout = value
179217
180218 @property
@@ -185,8 +223,6 @@ def local_engine_id(self) -> str:
185223 @local_engine_id .setter
186224 def local_engine_id (self , value : str ):
187225 """Sets the local engine ID for SNMP."""
188- if not isinstance (value , str ) or not value :
189- raise ValueError ("Local Engine ID must be a non-empty string." )
190226 self .protocol .localEngineId = value
191227
192228 @property
@@ -197,8 +233,6 @@ def use_get_bulk(self) -> bool:
197233 @use_get_bulk .setter
198234 def use_get_bulk (self , value : bool ):
199235 """Sets whether to use GetBulk for SNMP requests."""
200- if not isinstance (value , bool ):
201- raise ValueError ("Use GetBulk must be a boolean value." )
202236 self .protocol .useGetBulk = value
203237
204238 @property
@@ -209,8 +243,6 @@ def max_repetitions(self) -> int:
209243 @max_repetitions .setter
210244 def max_repetitions (self , value : int ):
211245 """Sets the maximum number of repetitions for SNMP requests."""
212- if not isinstance (value , int ) or value < 0 :
213- raise ValueError ("Max Repetitions must be a non-negative integer." )
214246 self .protocol .maxRepetitions = value
215247
216248 @property
@@ -221,8 +253,6 @@ def read_community(self) -> str:
221253 @read_community .setter
222254 def read_community (self , value : str ):
223255 """Sets the read community for SNMP."""
224- if not isinstance (value , str ) or not value :
225- raise ValueError ("Read Community must be a non-empty string." )
226256 self .security .read .community = value
227257
228258 @property
@@ -233,8 +263,6 @@ def write_community(self) -> str:
233263 @write_community .setter
234264 def write_community (self , value : str ):
235265 """Sets the write community for SNMP."""
236- if not isinstance (value , str ) or not value :
237- raise ValueError ("Write Community must be a non-empty string." )
238266 self .security .write .community = value
239267
240268 def list_usernames (self ) -> list [str ]:
0 commit comments