Skip to main content

iggy_binary_protocol/requests/users/
change_password.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::WireError;
19use crate::WireIdentifier;
20use crate::codec::{WireDecode, WireEncode, read_str, read_u8};
21use bytes::{BufMut, BytesMut};
22
23/// `ChangePassword` request.
24///
25/// Wire format:
26/// `[user_id:WireIdentifier][current_password_len:u8][current_password:N]
27///  [new_password_len:u8][new_password:N]`
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct ChangePasswordRequest {
30    pub user_id: WireIdentifier,
31    pub current_password: String,
32    pub new_password: String,
33}
34
35impl WireEncode for ChangePasswordRequest {
36    fn encoded_size(&self) -> usize {
37        self.user_id.encoded_size() + 1 + self.current_password.len() + 1 + self.new_password.len()
38    }
39
40    fn encode(&self, buf: &mut BytesMut) {
41        self.user_id.encode(buf);
42        debug_assert!(
43            u8::try_from(self.current_password.len()).is_ok()
44                && u8::try_from(self.new_password.len()).is_ok(),
45            "password exceeds u8 length prefix; callers must validate before encoding"
46        );
47        #[allow(clippy::cast_possible_truncation)]
48        buf.put_u8(self.current_password.len() as u8);
49        buf.put_slice(self.current_password.as_bytes());
50        #[allow(clippy::cast_possible_truncation)]
51        buf.put_u8(self.new_password.len() as u8);
52        buf.put_slice(self.new_password.as_bytes());
53    }
54}
55
56impl WireDecode for ChangePasswordRequest {
57    fn decode(buf: &[u8]) -> Result<(Self, usize), WireError> {
58        let (user_id, mut pos) = WireIdentifier::decode(buf)?;
59
60        let current_len = read_u8(buf, pos)? as usize;
61        pos += 1;
62        let current_password = read_str(buf, pos, current_len)?;
63        pos += current_len;
64
65        let new_len = read_u8(buf, pos)? as usize;
66        pos += 1;
67        let new_password = read_str(buf, pos, new_len)?;
68        pos += new_len;
69
70        Ok((
71            Self {
72                user_id,
73                current_password,
74                new_password,
75            },
76            pos,
77        ))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn roundtrip() {
87        let req = ChangePasswordRequest {
88            user_id: WireIdentifier::numeric(1),
89            current_password: "old-pass".to_string(),
90            new_password: "new-pass-123".to_string(),
91        };
92        let bytes = req.to_bytes();
93        let (decoded, consumed) = ChangePasswordRequest::decode(&bytes).unwrap();
94        assert_eq!(consumed, bytes.len());
95        assert_eq!(decoded, req);
96    }
97
98    #[test]
99    fn roundtrip_named_user() {
100        let req = ChangePasswordRequest {
101            user_id: WireIdentifier::named("admin").unwrap(),
102            current_password: "root".to_string(),
103            new_password: "s3cure!".to_string(),
104        };
105        let bytes = req.to_bytes();
106        let (decoded, consumed) = ChangePasswordRequest::decode(&bytes).unwrap();
107        assert_eq!(consumed, bytes.len());
108        assert_eq!(decoded, req);
109    }
110
111    #[test]
112    fn encoded_size_matches_output() {
113        let req = ChangePasswordRequest {
114            user_id: WireIdentifier::numeric(42),
115            current_password: "abc".to_string(),
116            new_password: "xyz123".to_string(),
117        };
118        assert_eq!(req.encoded_size(), req.to_bytes().len());
119    }
120
121    #[test]
122    fn truncated_returns_error() {
123        let req = ChangePasswordRequest {
124            user_id: WireIdentifier::numeric(1),
125            current_password: "old".to_string(),
126            new_password: "new".to_string(),
127        };
128        let bytes = req.to_bytes();
129        for i in 0..bytes.len() {
130            assert!(
131                ChangePasswordRequest::decode(&bytes[..i]).is_err(),
132                "expected error for truncation at byte {i}"
133            );
134        }
135    }
136}