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 if self.inner.is_empty() {
101 return Vec::new();
102 }
103 let len = self.len.min(MAX_STRING_LEN);
104 let mut vec = Vec::with_capacity(len);
105
106 if self.inner[0] > MAX_UNPACKED {
108 let cells = self.inner.as_slice();
109 let max_cells = cells.len();
110 let mut cell_idx = 0usize;
111 let mut mark = 3usize;
112 for _ in 0..len {
113 if cell_idx >= max_cells {
114 break;
115 }
116 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
118 let ch = (cells[cell_idx] >> (mark * 8)) as u8;
119 if ch == b'\0' {
120 break;
121 }
122 vec.push(ch);
123 mark = (mark + 3) % 4;
124 if mark == 3 {
125 cell_idx += 1;
126 }
127 }
128 } else {
129 for item in self.inner.iter().take(len) {
130 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
132 let byte = *item as u8;
133 vec.push(byte);
134 }
135 }
136
137 vec
138 }
139
140 pub fn len(&self) -> usize {
142 self.len
143 }
144
145 pub fn is_empty(&self) -> bool {
147 self.len == 0
148 }
149
150 pub fn bytes_len(&self) -> usize {
152 self.inner.len()
153 }
154
155 pub fn as_str(&self) -> &str {
160 self
161 }
162}
163
164fn decode_bytes(bytes: &[u8]) -> String {
167 #[cfg(feature = "encoding")]
168 return encoding::get().decode(bytes).0.into_owned();
169
170 #[cfg(not(feature = "encoding"))]
171 return String::from_utf8_lossy(bytes).into_owned();
172}
173
174impl<'amx> AmxCell<'amx> for AmxString<'amx> {
175 fn from_raw(amx: &'amx Amx, cell: i32) -> AmxResult<AmxString<'amx>> {
176 let buffer = UnsizedBuffer::from_raw(amx, cell)?;
177 let ptr = buffer.as_ptr();
178 let str_len = amx.strlen(ptr)?;
179 let buf_len = str_len + 1;
180
181 Ok(AmxString {
182 inner: buffer.into_sized_buffer(buf_len),
183 len: str_len,
184 decoded: OnceCell::new(),
185 })
186 }
187
188 fn as_cell(&self) -> i32 {
189 self.inner.as_cell()
190 }
191}
192
193impl Deref for AmxString<'_> {
194 type Target = str;
195
196 fn deref(&self) -> &str {
199 self.decoded.get_or_init(|| decode_bytes(&self.to_bytes()))
200 }
201}
202
203impl fmt::Display for AmxString<'_> {
204 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
205 fmt.write_str(self)
206 }
207}
208
209impl PartialEq<str> for AmxString<'_> {
210 fn eq(&self, other: &str) -> bool {
212 &**self == other
213 }
214}
215
216impl PartialEq<&str> for AmxString<'_> {
217 fn eq(&self, other: &&str) -> bool {
218 &**self == *other
219 }
220}
221
222impl PartialEq<String> for AmxString<'_> {
223 fn eq(&self, other: &String) -> bool {
224 &**self == other.as_str()
225 }
226}
227
228pub(crate) fn put_in_buffer(buffer: &mut Buffer, string: &str) -> AmxResult<()> {
240 #[cfg(feature = "encoding")]
241 let bytes = encoding::get().encode(string).0;
242
243 #[cfg(not(feature = "encoding"))]
244 let bytes = std::borrow::Cow::from(string.as_bytes());
245
246 let bytes = bytes.as_ref();
247
248 if bytes.len() >= buffer.len() {
249 return Err(crate::error::AmxError::General);
250 }
251
252 buffer.as_mut_slice()[..bytes.len()]
253 .iter_mut()
254 .zip(bytes)
255 .for_each(|(cell, &byte)| *cell = i32::from(byte));
256
257 buffer[bytes.len()] = 0;
258
259 Ok(())
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use crate::cell::Ref;
266
267 fn make_buffer(data: &mut Vec<i32>) -> Buffer<'_> {
268 let len = data.len();
269 let r = unsafe { Ref::new(0, data.as_mut_ptr()) };
270 Buffer::new(r, len)
271 }
272
273 #[test]
276 fn new_empty_string() {
277 let mut data = vec![0i32; 4];
278 let buf = make_buffer(&mut data);
279 let s = unsafe { AmxString::new(buf, b"") };
280 assert!(s.is_empty());
281 assert_eq!(s.len(), 0);
282 assert_eq!(&*s, "");
283 assert_eq!(s.to_bytes(), b"");
284 }
285
286 #[test]
287 fn new_ascii_string() {
288 let mut data = vec![0i32; 16];
289 let buf = make_buffer(&mut data);
290 let s = unsafe { AmxString::new(buf, b"hello") };
291 assert_eq!(s.len(), 5);
292 assert_eq!(&*s, "hello");
293 assert_eq!(s.to_bytes(), b"hello");
294 assert!(!s.is_empty());
295 }
296
297 #[test]
298 fn deref_str_enables_string_methods() {
299 let mut data = vec![0i32; 32];
300 let buf = make_buffer(&mut data);
301 let s = unsafe { AmxString::new(buf, b"hello world") };
302 assert!(s.contains("world"));
304 assert!(s.starts_with("hello"));
305 assert!(s.ends_with("world"));
306 assert_eq!(s.to_uppercase(), "HELLO WORLD");
307 assert_eq!(s.split_once(' ').unwrap(), ("hello", "world"));
308 }
309
310 #[test]
311 fn deref_is_lazy_and_cached() {
312 let mut data = vec![0i32; 16];
313 let buf = make_buffer(&mut data);
314 let s = unsafe { AmxString::new(buf, b"world") };
315 assert!(s.decoded.get().is_none());
317 let _ = &*s;
319 assert!(s.decoded.get().is_some());
320 let a = s.decoded.get().unwrap().as_ptr();
322 let _ = &*s;
323 let b = s.decoded.get().unwrap().as_ptr();
324 assert_eq!(a, b);
325 }
326
327 #[test]
328 fn display_and_deref_are_consistent() {
329 let mut data = vec![0i32; 16];
330 let buf = make_buffer(&mut data);
331 let s = unsafe { AmxString::new(buf, b"world") };
332 assert_eq!(s.to_string(), "world");
333 assert_eq!(&*s, "world");
334 assert_eq!(format!("{s}"), "world");
335 }
336
337 #[test]
338 fn bytes_len_reflects_buffer_size() {
339 let mut data = vec![0i32; 8];
340 let buf = make_buffer(&mut data);
341 let s = unsafe { AmxString::new(buf, b"abc") };
342 assert_eq!(s.bytes_len(), 8);
343 assert_eq!(s.len(), 3);
344 }
345
346 #[test]
347 fn unpacked_to_bytes_ascii() {
348 let text = b"SA-MP Plugin";
349 let mut data: Vec<i32> = text
350 .iter()
351 .map(|&b| i32::from(b))
352 .chain(std::iter::once(0))
353 .collect();
354 let buf = make_buffer(&mut data);
355 let s = unsafe { AmxString::new(buf, text) };
356 assert_eq!(s.to_bytes(), text);
357 }
358
359 #[test]
360 fn unpacked_single_char() {
361 let mut data = vec![0x41i32, 0];
362 let buf = make_buffer(&mut data);
363 let s = unsafe { AmxString::new(buf, b"A") };
364 assert_eq!(s.len(), 1);
365 assert_eq!(&*s, "A");
366 }
367
368 #[test]
374 fn packed_four_chars_one_cell() {
375 let mut data = vec![0x4142_4344i32, 0x0000_0000i32];
376 let buf = make_buffer(&mut data);
377 let s = AmxString::from_buffer_parts(buf, 4);
378 assert_eq!(s.to_bytes(), b"ABCD");
379 assert_eq!(&*s, "ABCD");
380 }
381
382 #[test]
383 fn packed_five_chars_two_cells() {
384 let mut data = vec![0x4142_4344i32, 0x4500_0000i32, 0x0000_0000i32];
386 let buf = make_buffer(&mut data);
387 let s = AmxString::from_buffer_parts(buf, 5);
388 assert_eq!(s.to_bytes(), b"ABCDE");
389 assert_eq!(&*s, "ABCDE");
390 }
391
392 #[test]
393 fn packed_truncates_at_len() {
394 let mut data = vec![0x4142_4344i32, 0x0000_0000i32];
395 let buf = make_buffer(&mut data);
396 let s = AmxString::from_buffer_parts(buf, 2);
397 assert_eq!(s.to_bytes(), b"AB");
398 }
399
400 #[test]
401 fn packed_stops_at_null_byte() {
402 let mut data = vec![0x4142_0044i32, 0x0000_0000i32];
404 let buf = make_buffer(&mut data);
405 let s = AmxString::from_buffer_parts(buf, 4);
406 assert_eq!(s.to_bytes(), b"AB");
407 }
408
409 #[test]
412 fn as_str_returns_decoded() {
413 let mut data = vec![0i32; 16];
414 let buf = make_buffer(&mut data);
415 let s = unsafe { AmxString::new(buf, b"hello") };
416 assert_eq!(s.as_str(), "hello");
417 }
418
419 #[test]
420 fn as_str_and_deref_are_same_pointer() {
421 let mut data = vec![0i32; 16];
422 let buf = make_buffer(&mut data);
423 let s = unsafe { AmxString::new(buf, b"rust") };
424 let a: &str = s.as_str();
426 let b: &str = &s;
427 assert_eq!(a.as_ptr(), b.as_ptr());
428 }
429
430 #[test]
433 fn partial_eq_str_literal() {
434 let mut data = vec![0i32; 16];
435 let buf = make_buffer(&mut data);
436 let s = unsafe { AmxString::new(buf, b"Admin") };
437 assert!(s == "Admin");
438 assert!(s != "admin");
439 }
440
441 #[test]
442 fn partial_eq_ref_str() {
443 let mut data = vec![0i32; 16];
444 let buf = make_buffer(&mut data);
445 let s = unsafe { AmxString::new(buf, b"samp") };
446 let key: &str = "samp";
447 assert!(s == key);
448 }
449
450 #[test]
451 fn partial_eq_string() {
452 let mut data = vec![0i32; 16];
453 let buf = make_buffer(&mut data);
454 let s = unsafe { AmxString::new(buf, b"plugin") };
455 let owned_match: String = "plugin".to_string();
456 let owned_other: String = "other".to_string();
457 assert!(s == owned_match);
458 assert!(s != owned_other);
459 }
460
461 #[test]
462 fn partial_eq_empty() {
463 let mut data = vec![0i32; 4];
464 let buf = make_buffer(&mut data);
465 let s = unsafe { AmxString::new(buf, b"") };
466 assert!(s.is_empty());
467 assert!(s != "x");
468 }
469
470 #[test]
473 fn put_in_buffer_writes_correctly() {
474 let mut data = vec![0i32; 16];
475 let mut buf = make_buffer(&mut data);
476 put_in_buffer(&mut buf, "hello").unwrap();
477 assert_eq!(buf[0], i32::from(b'h'));
478 assert_eq!(buf[4], i32::from(b'o'));
479 assert_eq!(buf[5], 0);
480 }
481
482 #[test]
483 fn put_in_buffer_exact_fit_fails() {
484 let mut data = vec![0i32; 5];
485 let mut buf = make_buffer(&mut data);
486 assert!(put_in_buffer(&mut buf, "hello").is_err());
487 }
488
489 #[test]
490 fn put_in_buffer_empty_string() {
491 let mut data = vec![0i32; 4];
492 let mut buf = make_buffer(&mut data);
493 put_in_buffer(&mut buf, "").unwrap();
494 assert_eq!(buf[0], 0);
495 }
496
497 fn lcg(seed: &mut u64) -> u32 {
502 *seed = seed
503 .wrapping_mul(6_364_136_223_846_793_005)
504 .wrapping_add(1_442_695_040_888_963_407);
505 (*seed >> 33) as u32
506 }
507
508 #[test]
509 fn to_bytes_declared_len_larger_than_buffer_is_bounded() {
510 let mut data = vec![0x41i32, 0x42, 0x43]; let buf = make_buffer(&mut data);
514 let s = AmxString::from_buffer_parts(buf, 9999);
515 let bytes = s.to_bytes();
516 assert!(bytes.len() <= 3, "read past the backing buffer: {bytes:?}");
517 }
518
519 #[test]
520 fn to_bytes_non_utf8_decodes_lossy_without_panic() {
521 let mut data = vec![0xFFi32, 0xFE, 0x41, 0];
524 let buf = make_buffer(&mut data);
525 let s = AmxString::from_buffer_parts(buf, 3);
526 let _ = &*s; assert!(!s.is_empty());
528 }
529
530 #[test]
531 fn fuzz_decode_never_panics() {
532 let mut seed = 0x0BAD_F00D_DEAD_BEEFu64;
536 for _ in 0..4000 {
537 let cells = (lcg(&mut seed) % 12) as usize + 1; let mut data: Vec<i32> = (0..cells).map(|_| lcg(&mut seed) as i32).collect();
539 let declared = (lcg(&mut seed) % 64) as usize;
541 let buf = make_buffer(&mut data);
542 let s = AmxString::from_buffer_parts(buf, declared);
543
544 let bytes = s.to_bytes();
545 assert!(bytes.len() <= 1024 * 1024);
546 let decoded = &*s;
548 assert!(decoded.len() <= bytes.len().max(4 * bytes.len() + 4));
549 }
550 }
551}