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 put_in_buffer_checked(buffer, string).map(|_| ())
241}
242
243pub(crate) fn put_in_buffer_checked(buffer: &mut Buffer, string: &str) -> AmxResult<bool> {
246 #[cfg(feature = "encoding")]
247 let (bytes, had_unmappable) = encoding::encode_checked(string);
248
249 #[cfg(not(feature = "encoding"))]
252 let (bytes, had_unmappable) = (std::borrow::Cow::from(string.as_bytes()), false);
253
254 let bytes = bytes.as_ref();
255
256 if bytes.len() >= buffer.len() {
257 return Err(crate::error::AmxError::General);
258 }
259
260 buffer.as_mut_slice()[..bytes.len()]
261 .iter_mut()
262 .zip(bytes)
263 .for_each(|(cell, &byte)| *cell = i32::from(byte));
264
265 buffer[bytes.len()] = 0;
266
267 Ok(had_unmappable)
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::cell::Ref;
274
275 fn make_buffer(data: &mut Vec<i32>) -> Buffer<'_> {
276 let len = data.len();
277 let r = unsafe { Ref::new(0, data.as_mut_ptr()) };
278 Buffer::new(r, len)
279 }
280
281 #[test]
284 fn new_empty_string() {
285 let mut data = vec![0i32; 4];
286 let buf = make_buffer(&mut data);
287 let s = unsafe { AmxString::new(buf, b"") };
288 assert!(s.is_empty());
289 assert_eq!(s.len(), 0);
290 assert_eq!(&*s, "");
291 assert_eq!(s.to_bytes(), b"");
292 }
293
294 #[test]
295 fn new_ascii_string() {
296 let mut data = vec![0i32; 16];
297 let buf = make_buffer(&mut data);
298 let s = unsafe { AmxString::new(buf, b"hello") };
299 assert_eq!(s.len(), 5);
300 assert_eq!(&*s, "hello");
301 assert_eq!(s.to_bytes(), b"hello");
302 assert!(!s.is_empty());
303 }
304
305 #[test]
306 fn deref_str_enables_string_methods() {
307 let mut data = vec![0i32; 32];
308 let buf = make_buffer(&mut data);
309 let s = unsafe { AmxString::new(buf, b"hello world") };
310 assert!(s.contains("world"));
312 assert!(s.starts_with("hello"));
313 assert!(s.ends_with("world"));
314 assert_eq!(s.to_uppercase(), "HELLO WORLD");
315 assert_eq!(s.split_once(' ').unwrap(), ("hello", "world"));
316 }
317
318 #[test]
319 fn deref_is_lazy_and_cached() {
320 let mut data = vec![0i32; 16];
321 let buf = make_buffer(&mut data);
322 let s = unsafe { AmxString::new(buf, b"world") };
323 assert!(s.decoded.get().is_none());
325 let _ = &*s;
327 assert!(s.decoded.get().is_some());
328 let a = s.decoded.get().unwrap().as_ptr();
330 let _ = &*s;
331 let b = s.decoded.get().unwrap().as_ptr();
332 assert_eq!(a, b);
333 }
334
335 #[test]
336 fn display_and_deref_are_consistent() {
337 let mut data = vec![0i32; 16];
338 let buf = make_buffer(&mut data);
339 let s = unsafe { AmxString::new(buf, b"world") };
340 assert_eq!(s.to_string(), "world");
341 assert_eq!(&*s, "world");
342 assert_eq!(format!("{s}"), "world");
343 }
344
345 #[test]
346 fn bytes_len_reflects_buffer_size() {
347 let mut data = vec![0i32; 8];
348 let buf = make_buffer(&mut data);
349 let s = unsafe { AmxString::new(buf, b"abc") };
350 assert_eq!(s.bytes_len(), 8);
351 assert_eq!(s.len(), 3);
352 }
353
354 #[test]
355 fn unpacked_to_bytes_ascii() {
356 let text = b"SA-MP Plugin";
357 let mut data: Vec<i32> = text
358 .iter()
359 .map(|&b| i32::from(b))
360 .chain(std::iter::once(0))
361 .collect();
362 let buf = make_buffer(&mut data);
363 let s = unsafe { AmxString::new(buf, text) };
364 assert_eq!(s.to_bytes(), text);
365 }
366
367 #[test]
368 fn unpacked_single_char() {
369 let mut data = vec![0x41i32, 0];
370 let buf = make_buffer(&mut data);
371 let s = unsafe { AmxString::new(buf, b"A") };
372 assert_eq!(s.len(), 1);
373 assert_eq!(&*s, "A");
374 }
375
376 #[test]
382 fn packed_four_chars_one_cell() {
383 let mut data = vec![0x4142_4344i32, 0x0000_0000i32];
384 let buf = make_buffer(&mut data);
385 let s = AmxString::from_buffer_parts(buf, 4);
386 assert_eq!(s.to_bytes(), b"ABCD");
387 assert_eq!(&*s, "ABCD");
388 }
389
390 #[test]
391 fn packed_five_chars_two_cells() {
392 let mut data = vec![0x4142_4344i32, 0x4500_0000i32, 0x0000_0000i32];
394 let buf = make_buffer(&mut data);
395 let s = AmxString::from_buffer_parts(buf, 5);
396 assert_eq!(s.to_bytes(), b"ABCDE");
397 assert_eq!(&*s, "ABCDE");
398 }
399
400 #[test]
401 fn packed_truncates_at_len() {
402 let mut data = vec![0x4142_4344i32, 0x0000_0000i32];
403 let buf = make_buffer(&mut data);
404 let s = AmxString::from_buffer_parts(buf, 2);
405 assert_eq!(s.to_bytes(), b"AB");
406 }
407
408 #[test]
409 fn packed_stops_at_null_byte() {
410 let mut data = vec![0x4142_0044i32, 0x0000_0000i32];
412 let buf = make_buffer(&mut data);
413 let s = AmxString::from_buffer_parts(buf, 4);
414 assert_eq!(s.to_bytes(), b"AB");
415 }
416
417 #[test]
420 fn as_str_returns_decoded() {
421 let mut data = vec![0i32; 16];
422 let buf = make_buffer(&mut data);
423 let s = unsafe { AmxString::new(buf, b"hello") };
424 assert_eq!(s.as_str(), "hello");
425 }
426
427 #[test]
428 fn as_str_and_deref_are_same_pointer() {
429 let mut data = vec![0i32; 16];
430 let buf = make_buffer(&mut data);
431 let s = unsafe { AmxString::new(buf, b"rust") };
432 let a: &str = s.as_str();
434 let b: &str = &s;
435 assert_eq!(a.as_ptr(), b.as_ptr());
436 }
437
438 #[test]
441 fn partial_eq_str_literal() {
442 let mut data = vec![0i32; 16];
443 let buf = make_buffer(&mut data);
444 let s = unsafe { AmxString::new(buf, b"Admin") };
445 assert!(s == "Admin");
446 assert!(s != "admin");
447 }
448
449 #[test]
450 fn partial_eq_ref_str() {
451 let mut data = vec![0i32; 16];
452 let buf = make_buffer(&mut data);
453 let s = unsafe { AmxString::new(buf, b"samp") };
454 let key: &str = "samp";
455 assert!(s == key);
456 }
457
458 #[test]
459 fn partial_eq_string() {
460 let mut data = vec![0i32; 16];
461 let buf = make_buffer(&mut data);
462 let s = unsafe { AmxString::new(buf, b"plugin") };
463 let owned_match: String = "plugin".to_string();
464 let owned_other: String = "other".to_string();
465 assert!(s == owned_match);
466 assert!(s != owned_other);
467 }
468
469 #[test]
470 fn partial_eq_empty() {
471 let mut data = vec![0i32; 4];
472 let buf = make_buffer(&mut data);
473 let s = unsafe { AmxString::new(buf, b"") };
474 assert!(s.is_empty());
475 assert!(s != "x");
476 }
477
478 #[test]
481 fn put_in_buffer_writes_correctly() {
482 let mut data = vec![0i32; 16];
483 let mut buf = make_buffer(&mut data);
484 put_in_buffer(&mut buf, "hello").unwrap();
485 assert_eq!(buf[0], i32::from(b'h'));
486 assert_eq!(buf[4], i32::from(b'o'));
487 assert_eq!(buf[5], 0);
488 }
489
490 #[test]
491 fn put_in_buffer_exact_fit_fails() {
492 let mut data = vec![0i32; 5];
493 let mut buf = make_buffer(&mut data);
494 assert!(put_in_buffer(&mut buf, "hello").is_err());
495 }
496
497 #[test]
498 fn put_in_buffer_empty_string() {
499 let mut data = vec![0i32; 4];
500 let mut buf = make_buffer(&mut data);
501 put_in_buffer(&mut buf, "").unwrap();
502 assert_eq!(buf[0], 0);
503 }
504
505 fn lcg(seed: &mut u64) -> u32 {
510 *seed = seed
511 .wrapping_mul(6_364_136_223_846_793_005)
512 .wrapping_add(1_442_695_040_888_963_407);
513 (*seed >> 33) as u32
514 }
515
516 #[test]
517 fn to_bytes_declared_len_larger_than_buffer_is_bounded() {
518 let mut data = vec![0x41i32, 0x42, 0x43]; let buf = make_buffer(&mut data);
522 let s = AmxString::from_buffer_parts(buf, 9999);
523 let bytes = s.to_bytes();
524 assert!(bytes.len() <= 3, "read past the backing buffer: {bytes:?}");
525 }
526
527 #[test]
528 fn to_bytes_non_utf8_decodes_lossy_without_panic() {
529 let mut data = vec![0xFFi32, 0xFE, 0x41, 0];
532 let buf = make_buffer(&mut data);
533 let s = AmxString::from_buffer_parts(buf, 3);
534 let _ = &*s; assert!(!s.is_empty());
536 }
537
538 #[test]
539 fn fuzz_decode_never_panics() {
540 let mut seed = 0x0BAD_F00D_DEAD_BEEFu64;
544 for _ in 0..4000 {
545 let cells = (lcg(&mut seed) % 12) as usize + 1; let mut data: Vec<i32> = (0..cells).map(|_| lcg(&mut seed) as i32).collect();
547 let declared = (lcg(&mut seed) % 64) as usize;
549 let buf = make_buffer(&mut data);
550 let s = AmxString::from_buffer_parts(buf, declared);
551
552 let bytes = s.to_bytes();
553 assert!(bytes.len() <= 1024 * 1024);
554 let decoded = &*s;
556 assert!(decoded.len() <= bytes.len().max(4 * bytes.len() + 4));
557 }
558 }
559}