1use std::cell::OnceCell;
13use std::fmt;
14use std::ops::Deref;
15
16use super::{AmxCell, Buffer, UnsizedBuffer};
17use crate::amx::Amx;
18#[cfg(feature = "encoding")]
19use crate::encoding;
20use crate::error::AmxResult;
21
22const MAX_UNPACKED: i32 = 0x00FF_FFFF;
26
27pub struct AmxString<'amx> {
51 inner: Buffer<'amx>,
52 len: usize,
53 decoded: OnceCell<String>,
54}
55
56impl<'amx> AmxString<'amx> {
57 #[must_use]
64 pub unsafe fn new(mut buffer: Buffer<'amx>, bytes: &[u8]) -> AmxString<'amx> {
65 buffer.as_mut_slice()[..bytes.len()]
66 .iter_mut()
67 .zip(bytes)
68 .for_each(|(cell, &byte)| *cell = i32::from(byte));
69 buffer[bytes.len()] = 0;
70
71 AmxString {
72 len: bytes.len(),
73 inner: buffer,
74 decoded: OnceCell::new(),
75 }
76 }
77
78 #[doc(hidden)]
81 #[must_use]
82 pub fn from_buffer_parts(inner: Buffer<'amx>, len: usize) -> AmxString<'amx> {
83 AmxString {
84 inner,
85 len,
86 decoded: OnceCell::new(),
87 }
88 }
89
90 pub fn to_bytes(&self) -> Vec<u8> {
96 const MAX_STRING_LEN: usize = 1024 * 1024;
97 let len = self.len.min(MAX_STRING_LEN);
98 let mut vec = Vec::with_capacity(len);
99
100 if self.inner[0] > MAX_UNPACKED {
102 let cells = self.inner.as_slice();
103 let max_cells = cells.len();
104 let mut cell_idx = 0usize;
105 let mut mark = 3usize;
106 for _ in 0..len {
107 if cell_idx >= max_cells {
108 break;
109 }
110 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
112 let ch = (cells[cell_idx] >> (mark * 8)) as u8;
113 if ch == b'\0' {
114 break;
115 }
116 vec.push(ch);
117 mark = (mark + 3) % 4;
118 if mark == 3 {
119 cell_idx += 1;
120 }
121 }
122 } else {
123 for item in self.inner.iter().take(len) {
124 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
126 let byte = *item as u8;
127 vec.push(byte);
128 }
129 }
130
131 vec
132 }
133
134 pub fn len(&self) -> usize {
136 self.len
137 }
138
139 pub fn is_empty(&self) -> bool {
141 self.len == 0
142 }
143
144 pub fn bytes_len(&self) -> usize {
146 self.inner.len()
147 }
148
149 pub fn as_str(&self) -> &str {
154 self
155 }
156}
157
158fn decode_bytes(bytes: &[u8]) -> String {
161 #[cfg(feature = "encoding")]
162 return encoding::get().decode(bytes).0.into_owned();
163
164 #[cfg(not(feature = "encoding"))]
165 return String::from_utf8_lossy(bytes).into_owned();
166}
167
168impl<'amx> AmxCell<'amx> for AmxString<'amx> {
169 fn from_raw(amx: &'amx Amx, cell: i32) -> AmxResult<AmxString<'amx>> {
170 let buffer = UnsizedBuffer::from_raw(amx, cell)?;
171 let ptr = buffer.as_ptr();
172 let str_len = amx.strlen(ptr)?;
173 let buf_len = str_len + 1;
174
175 Ok(AmxString {
176 inner: buffer.into_sized_buffer(buf_len),
177 len: str_len,
178 decoded: OnceCell::new(),
179 })
180 }
181
182 fn as_cell(&self) -> i32 {
183 self.inner.as_cell()
184 }
185}
186
187impl Deref for AmxString<'_> {
188 type Target = str;
189
190 fn deref(&self) -> &str {
193 self.decoded.get_or_init(|| decode_bytes(&self.to_bytes()))
194 }
195}
196
197impl fmt::Display for AmxString<'_> {
198 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
199 fmt.write_str(self)
200 }
201}
202
203impl PartialEq<str> for AmxString<'_> {
204 fn eq(&self, other: &str) -> bool {
206 &**self == other
207 }
208}
209
210impl PartialEq<&str> for AmxString<'_> {
211 fn eq(&self, other: &&str) -> bool {
212 &**self == *other
213 }
214}
215
216impl PartialEq<String> for AmxString<'_> {
217 fn eq(&self, other: &String) -> bool {
218 &**self == other.as_str()
219 }
220}
221
222pub(crate) fn put_in_buffer(buffer: &mut Buffer, string: &str) -> AmxResult<()> {
234 #[cfg(feature = "encoding")]
235 let bytes = encoding::get().encode(string).0;
236
237 #[cfg(not(feature = "encoding"))]
238 let bytes = std::borrow::Cow::from(string.as_bytes());
239
240 let bytes = bytes.as_ref();
241
242 if bytes.len() >= buffer.len() {
243 return Err(crate::error::AmxError::General);
244 }
245
246 buffer.as_mut_slice()[..bytes.len()]
247 .iter_mut()
248 .zip(bytes)
249 .for_each(|(cell, &byte)| *cell = i32::from(byte));
250
251 buffer[bytes.len()] = 0;
252
253 Ok(())
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use crate::cell::Ref;
260
261 fn make_buffer(data: &mut Vec<i32>) -> Buffer<'_> {
262 let len = data.len();
263 let r = unsafe { Ref::new(0, data.as_mut_ptr()) };
264 Buffer::new(r, len)
265 }
266
267 #[test]
270 fn new_empty_string() {
271 let mut data = vec![0i32; 4];
272 let buf = make_buffer(&mut data);
273 let s = unsafe { AmxString::new(buf, b"") };
274 assert!(s.is_empty());
275 assert_eq!(s.len(), 0);
276 assert_eq!(&*s, "");
277 assert_eq!(s.to_bytes(), b"");
278 }
279
280 #[test]
281 fn new_ascii_string() {
282 let mut data = vec![0i32; 16];
283 let buf = make_buffer(&mut data);
284 let s = unsafe { AmxString::new(buf, b"hello") };
285 assert_eq!(s.len(), 5);
286 assert_eq!(&*s, "hello");
287 assert_eq!(s.to_bytes(), b"hello");
288 assert!(!s.is_empty());
289 }
290
291 #[test]
292 fn deref_str_enables_string_methods() {
293 let mut data = vec![0i32; 32];
294 let buf = make_buffer(&mut data);
295 let s = unsafe { AmxString::new(buf, b"hello world") };
296 assert!(s.contains("world"));
298 assert!(s.starts_with("hello"));
299 assert!(s.ends_with("world"));
300 assert_eq!(s.to_uppercase(), "HELLO WORLD");
301 assert_eq!(s.split_once(' ').unwrap(), ("hello", "world"));
302 }
303
304 #[test]
305 fn deref_is_lazy_and_cached() {
306 let mut data = vec![0i32; 16];
307 let buf = make_buffer(&mut data);
308 let s = unsafe { AmxString::new(buf, b"world") };
309 assert!(s.decoded.get().is_none());
311 let _ = &*s;
313 assert!(s.decoded.get().is_some());
314 let a = s.decoded.get().unwrap().as_ptr();
316 let _ = &*s;
317 let b = s.decoded.get().unwrap().as_ptr();
318 assert_eq!(a, b);
319 }
320
321 #[test]
322 fn display_and_deref_are_consistent() {
323 let mut data = vec![0i32; 16];
324 let buf = make_buffer(&mut data);
325 let s = unsafe { AmxString::new(buf, b"world") };
326 assert_eq!(s.to_string(), "world");
327 assert_eq!(&*s, "world");
328 assert_eq!(format!("{s}"), "world");
329 }
330
331 #[test]
332 fn bytes_len_reflects_buffer_size() {
333 let mut data = vec![0i32; 8];
334 let buf = make_buffer(&mut data);
335 let s = unsafe { AmxString::new(buf, b"abc") };
336 assert_eq!(s.bytes_len(), 8);
337 assert_eq!(s.len(), 3);
338 }
339
340 #[test]
341 fn unpacked_to_bytes_ascii() {
342 let text = b"SA-MP Plugin";
343 let mut data: Vec<i32> = text
344 .iter()
345 .map(|&b| i32::from(b))
346 .chain(std::iter::once(0))
347 .collect();
348 let buf = make_buffer(&mut data);
349 let s = unsafe { AmxString::new(buf, text) };
350 assert_eq!(s.to_bytes(), text);
351 }
352
353 #[test]
354 fn unpacked_single_char() {
355 let mut data = vec![0x41i32, 0];
356 let buf = make_buffer(&mut data);
357 let s = unsafe { AmxString::new(buf, b"A") };
358 assert_eq!(s.len(), 1);
359 assert_eq!(&*s, "A");
360 }
361
362 #[test]
368 fn packed_four_chars_one_cell() {
369 let mut data = vec![0x4142_4344i32, 0x0000_0000i32];
370 let buf = make_buffer(&mut data);
371 let s = AmxString::from_buffer_parts(buf, 4);
372 assert_eq!(s.to_bytes(), b"ABCD");
373 assert_eq!(&*s, "ABCD");
374 }
375
376 #[test]
377 fn packed_five_chars_two_cells() {
378 let mut data = vec![0x4142_4344i32, 0x4500_0000i32, 0x0000_0000i32];
380 let buf = make_buffer(&mut data);
381 let s = AmxString::from_buffer_parts(buf, 5);
382 assert_eq!(s.to_bytes(), b"ABCDE");
383 assert_eq!(&*s, "ABCDE");
384 }
385
386 #[test]
387 fn packed_truncates_at_len() {
388 let mut data = vec![0x4142_4344i32, 0x0000_0000i32];
389 let buf = make_buffer(&mut data);
390 let s = AmxString::from_buffer_parts(buf, 2);
391 assert_eq!(s.to_bytes(), b"AB");
392 }
393
394 #[test]
395 fn packed_stops_at_null_byte() {
396 let mut data = vec![0x4142_0044i32, 0x0000_0000i32];
398 let buf = make_buffer(&mut data);
399 let s = AmxString::from_buffer_parts(buf, 4);
400 assert_eq!(s.to_bytes(), b"AB");
401 }
402
403 #[test]
406 fn as_str_returns_decoded() {
407 let mut data = vec![0i32; 16];
408 let buf = make_buffer(&mut data);
409 let s = unsafe { AmxString::new(buf, b"hello") };
410 assert_eq!(s.as_str(), "hello");
411 }
412
413 #[test]
414 fn as_str_and_deref_are_same_pointer() {
415 let mut data = vec![0i32; 16];
416 let buf = make_buffer(&mut data);
417 let s = unsafe { AmxString::new(buf, b"rust") };
418 let a: &str = s.as_str();
420 let b: &str = &s;
421 assert_eq!(a.as_ptr(), b.as_ptr());
422 }
423
424 #[test]
427 fn partial_eq_str_literal() {
428 let mut data = vec![0i32; 16];
429 let buf = make_buffer(&mut data);
430 let s = unsafe { AmxString::new(buf, b"Admin") };
431 assert!(s == "Admin");
432 assert!(s != "admin");
433 }
434
435 #[test]
436 fn partial_eq_ref_str() {
437 let mut data = vec![0i32; 16];
438 let buf = make_buffer(&mut data);
439 let s = unsafe { AmxString::new(buf, b"samp") };
440 let key: &str = "samp";
441 assert!(s == key);
442 }
443
444 #[test]
445 fn partial_eq_string() {
446 let mut data = vec![0i32; 16];
447 let buf = make_buffer(&mut data);
448 let s = unsafe { AmxString::new(buf, b"plugin") };
449 let owned_match: String = "plugin".to_string();
450 let owned_other: String = "other".to_string();
451 assert!(s == owned_match);
452 assert!(s != owned_other);
453 }
454
455 #[test]
456 fn partial_eq_empty() {
457 let mut data = vec![0i32; 4];
458 let buf = make_buffer(&mut data);
459 let s = unsafe { AmxString::new(buf, b"") };
460 assert!(s.is_empty());
461 assert!(s != "x");
462 }
463
464 #[test]
467 fn put_in_buffer_writes_correctly() {
468 let mut data = vec![0i32; 16];
469 let mut buf = make_buffer(&mut data);
470 put_in_buffer(&mut buf, "hello").unwrap();
471 assert_eq!(buf[0], i32::from(b'h'));
472 assert_eq!(buf[4], i32::from(b'o'));
473 assert_eq!(buf[5], 0);
474 }
475
476 #[test]
477 fn put_in_buffer_exact_fit_fails() {
478 let mut data = vec![0i32; 5];
479 let mut buf = make_buffer(&mut data);
480 assert!(put_in_buffer(&mut buf, "hello").is_err());
481 }
482
483 #[test]
484 fn put_in_buffer_empty_string() {
485 let mut data = vec![0i32; 4];
486 let mut buf = make_buffer(&mut data);
487 put_in_buffer(&mut buf, "").unwrap();
488 assert_eq!(buf[0], 0);
489 }
490}