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::{AmxError, AmxResult};
21
22const MAX_UNPACKED: u32 = 0x00FF_FFFF;
28
29const MAX_STRING_CELLS: usize = 1024 * 1024;
31
32fn is_packed(first: i32) -> bool {
33 first.cast_unsigned() > MAX_UNPACKED
34}
35
36fn bounded_strlen(cells: &[i32]) -> Option<usize> {
42 if is_packed(*cells.first()?) {
43 let at = find_cell(cells, has_zero_byte)?;
44 let lead = cells[at].to_be_bytes().iter().position(|&byte| byte == 0)?;
45 Some(at * 4 + lead)
46 } else {
47 find_cell(cells, |cell| cell == 0)
48 }
49}
50
51fn find_cell(cells: &[i32], ends: impl Fn(i32) -> bool + Copy) -> Option<usize> {
58 const BLOCK: usize = 16;
59 let block = cells
60 .chunks(BLOCK)
61 .position(|block| block.iter().fold(false, |found, &cell| found | ends(cell)))?;
62 let start = block * BLOCK;
63 Some(start + cells[start..].iter().position(|&cell| ends(cell))?)
64}
65
66fn has_zero_byte(cell: i32) -> bool {
69 let bits = cell.cast_unsigned();
70 bits.wrapping_sub(0x0101_0101) & !bits & 0x8080_8080 != 0
71}
72
73pub struct AmxString<'amx> {
97 inner: Buffer<'amx>,
98 len: usize,
99 decoded: OnceCell<String>,
100}
101
102impl<'amx> AmxString<'amx> {
103 #[must_use]
110 pub unsafe fn new(mut buffer: Buffer<'amx>, bytes: &[u8]) -> AmxString<'amx> {
111 buffer.as_mut_slice()[..bytes.len()]
112 .iter_mut()
113 .zip(bytes)
114 .for_each(|(cell, &byte)| *cell = i32::from(byte));
115 buffer[bytes.len()] = 0;
116
117 AmxString {
118 len: bytes.len(),
119 inner: buffer,
120 decoded: OnceCell::new(),
121 }
122 }
123
124 #[doc(hidden)]
127 #[must_use]
128 pub fn from_buffer_parts(inner: Buffer<'amx>, len: usize) -> AmxString<'amx> {
129 AmxString {
130 inner,
131 len,
132 decoded: OnceCell::new(),
133 }
134 }
135
136 pub fn to_bytes(&self) -> Vec<u8> {
142 const MAX_STRING_LEN: usize = 1024 * 1024;
143 if self.inner.is_empty() {
147 return Vec::new();
148 }
149 let len = self.len.min(MAX_STRING_LEN);
150 let cells = self.inner.as_slice();
151
152 if is_packed(cells[0]) {
153 let mut vec = Vec::with_capacity(len);
157 for &cell in cells {
158 let bytes = cell.to_be_bytes();
159 if !has_zero_byte(cell) && vec.len() + 4 <= len {
160 vec.extend_from_slice(&bytes);
161 continue;
162 }
163 let room = len - vec.len();
164 vec.extend(bytes.into_iter().take(room).take_while(|&byte| byte != 0));
165 break;
166 }
167 vec
168 } else {
169 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
171 cells[..len.min(cells.len())]
172 .iter()
173 .map(|&cell| cell as u8)
174 .collect()
175 }
176 }
177
178 pub fn len(&self) -> usize {
180 self.len
181 }
182
183 pub fn is_empty(&self) -> bool {
185 self.len == 0
186 }
187
188 pub fn bytes_len(&self) -> usize {
190 self.inner.len()
191 }
192
193 pub fn as_str(&self) -> &str {
198 self
199 }
200}
201
202fn decode_bytes(bytes: Vec<u8>) -> String {
209 #[cfg(feature = "encoding")]
212 if let std::borrow::Cow::Owned(text) = encoding::get().decode_without_bom_handling(&bytes).0 {
213 return text;
214 }
215 String::from_utf8(bytes)
217 .unwrap_or_else(|invalid| String::from_utf8_lossy(invalid.as_bytes()).into_owned())
218}
219
220impl<'amx> AmxCell<'amx> for AmxString<'amx> {
221 fn from_raw(amx: &'amx Amx, cell: i32) -> AmxResult<AmxString<'amx>> {
222 let buffer = UnsizedBuffer::from_raw(amx, cell)?;
223 let str_len = match buffer.max_cells() {
224 usize::MAX => amx.strlen(buffer.as_ptr())?,
226 max_cells => {
227 let cells = unsafe {
229 std::slice::from_raw_parts(buffer.as_ptr(), max_cells.min(MAX_STRING_CELLS))
230 };
231 bounded_strlen(cells).ok_or(AmxError::MemoryAccess)?
232 }
233 };
234 let buf_len = str_len + 1;
235
236 Ok(AmxString {
237 inner: buffer.into_sized_buffer(buf_len),
238 len: str_len,
239 decoded: OnceCell::new(),
240 })
241 }
242
243 fn as_cell(&self) -> i32 {
244 self.inner.as_cell()
245 }
246}
247
248impl Deref for AmxString<'_> {
249 type Target = str;
250
251 fn deref(&self) -> &str {
254 self.decoded.get_or_init(|| decode_bytes(self.to_bytes()))
255 }
256}
257
258impl fmt::Display for AmxString<'_> {
259 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
260 fmt.write_str(self)
261 }
262}
263
264impl PartialEq<str> for AmxString<'_> {
265 fn eq(&self, other: &str) -> bool {
267 &**self == other
268 }
269}
270
271impl PartialEq<&str> for AmxString<'_> {
272 fn eq(&self, other: &&str) -> bool {
273 &**self == *other
274 }
275}
276
277impl PartialEq<String> for AmxString<'_> {
278 fn eq(&self, other: &String) -> bool {
279 &**self == other.as_str()
280 }
281}
282
283pub(crate) fn put_in_buffer(buffer: &mut Buffer, string: &str) -> AmxResult<()> {
295 put_in_buffer_checked(buffer, string).map(|_| ())
296}
297
298pub(crate) fn put_in_buffer_checked(buffer: &mut Buffer, string: &str) -> AmxResult<bool> {
301 #[cfg(feature = "encoding")]
302 let (bytes, had_unmappable) = encoding::encode_checked(string);
303
304 #[cfg(not(feature = "encoding"))]
307 let (bytes, had_unmappable) = (std::borrow::Cow::from(string.as_bytes()), false);
308
309 let bytes = bytes.as_ref();
310
311 if bytes.len() >= buffer.len() {
312 return Err(crate::error::AmxError::General);
313 }
314
315 buffer.as_mut_slice()[..bytes.len()]
316 .iter_mut()
317 .zip(bytes)
318 .for_each(|(cell, &byte)| *cell = i32::from(byte));
319
320 buffer[bytes.len()] = 0;
321
322 Ok(had_unmappable)
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use crate::cell::Ref;
329
330 fn make_buffer(data: &mut Vec<i32>) -> Buffer<'_> {
331 let len = data.len();
332 let r = unsafe { Ref::new(0, data.as_mut_ptr()) };
333 Buffer::new(r, len)
334 }
335
336 #[test]
339 fn bounded_strlen_stops_at_the_terminator() {
340 assert_eq!(bounded_strlen(&[0x41, 0x42, 0, 0x43]), Some(2));
341 assert_eq!(bounded_strlen(&[0x4142_4300]), Some(3));
342 assert_eq!(bounded_strlen(&[0x4142_4344, 0x4500_0000]), Some(5));
343 assert_eq!(bounded_strlen(&[0]), Some(0));
344 let mut long = vec![0x41; 40];
346 long[37] = 0;
347 assert_eq!(bounded_strlen(&long), Some(37));
348 long[16] = 0;
349 assert_eq!(bounded_strlen(&long), Some(16));
350 let mut packed = vec![0x4142_4344; 40];
351 packed[20] = 0x4142_0044;
352 assert_eq!(bounded_strlen(&packed), Some(20 * 4 + 2));
353 }
354
355 #[test]
356 fn bounded_strlen_agrees_with_a_byte_by_byte_search() {
357 fn naive(cells: &[i32]) -> Option<usize> {
358 let first = *cells.first()?;
359 if first.cast_unsigned() > 0x00FF_FFFF {
360 let bytes = cells.iter().flat_map(|cell| cell.to_be_bytes());
361 bytes
362 .enumerate()
363 .find(|&(_, byte)| byte == 0)
364 .map(|(at, _)| at)
365 } else {
366 cells.iter().position(|&cell| cell == 0)
367 }
368 }
369 let mut state = 0x2545_f491_u32;
371 let mut next = move || {
372 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
373 state
374 };
375 for _ in 0..5000 {
376 let len = (next() % 70) as usize;
377 let packed = next() % 2 == 0;
378 let mut cells: Vec<i32> = (0..len)
379 .map(|_| {
380 let value = next() | 0x0101_0101;
381 if packed {
382 value.cast_signed()
383 } else {
384 (value & 0xFF).cast_signed()
385 }
386 })
387 .collect();
388 if len > 0 && next() % 4 != 0 {
389 let at = (next() as usize) % len;
390 if packed {
391 let byte = (next() % 4) as usize;
392 let mut bytes = cells[at].to_be_bytes();
393 bytes[byte] = 0;
394 cells[at] = i32::from_be_bytes(bytes);
395 } else {
396 cells[at] = 0;
397 }
398 }
399 assert_eq!(bounded_strlen(&cells), naive(&cells), "{cells:x?}");
400 }
401 }
402
403 #[test]
404 fn zero_bytes_are_found_in_any_position() {
405 for cell in [
406 0x0041_4243,
407 0x4100_4243,
408 0x4142_0043,
409 0x4142_4300,
410 0x0000_0000,
411 ] {
412 assert!(has_zero_byte(cell), "{cell:#x}");
413 }
414 for cell in [0x4142_4344, 0x0101_0101, 0x8080_8080_u32.cast_signed(), -1] {
415 assert!(!has_zero_byte(cell), "{cell:#x}");
416 }
417 }
418
419 #[test]
420 fn bounded_strlen_without_terminator_is_none() {
421 assert_eq!(bounded_strlen(&[0x41, 0x42]), None);
424 assert_eq!(bounded_strlen(&[0x4142_4344]), None);
425 assert_eq!(bounded_strlen(&[]), None);
426 }
427
428 #[cfg(feature = "encoding")]
429 #[test]
430 fn a_leading_bom_does_not_change_the_encoding() {
431 let _g = crate::encoding::tests_lock();
432 let mut data = vec![0xFF, 0xFE, 0x61, 0x62, 0];
435 let s = AmxString::from_buffer_parts(make_buffer(&mut data), 4);
436 assert_eq!(&*s, "\u{ff}\u{fe}ab");
437 }
438
439 #[test]
440 fn packed_with_a_high_first_byte_is_packed() {
441 let mut data = vec![0xE978_797Au32.cast_signed(), 0];
444 assert_eq!(bounded_strlen(&data), Some(4));
445 let s = AmxString::from_buffer_parts(make_buffer(&mut data), 4);
446 assert_eq!(s.to_bytes(), [0xE9, b'x', b'y', b'z']);
447 }
448
449 #[test]
452 fn new_empty_string() {
453 let mut data = vec![0i32; 4];
454 let buf = make_buffer(&mut data);
455 let s = unsafe { AmxString::new(buf, b"") };
456 assert!(s.is_empty());
457 assert_eq!(s.len(), 0);
458 assert_eq!(&*s, "");
459 assert_eq!(s.to_bytes(), b"");
460 }
461
462 #[test]
463 fn new_ascii_string() {
464 let mut data = vec![0i32; 16];
465 let buf = make_buffer(&mut data);
466 let s = unsafe { AmxString::new(buf, b"hello") };
467 assert_eq!(s.len(), 5);
468 assert_eq!(&*s, "hello");
469 assert_eq!(s.to_bytes(), b"hello");
470 assert!(!s.is_empty());
471 }
472
473 #[test]
474 fn deref_str_enables_string_methods() {
475 let mut data = vec![0i32; 32];
476 let buf = make_buffer(&mut data);
477 let s = unsafe { AmxString::new(buf, b"hello world") };
478 assert!(s.contains("world"));
480 assert!(s.starts_with("hello"));
481 assert!(s.ends_with("world"));
482 assert_eq!(s.to_uppercase(), "HELLO WORLD");
483 assert_eq!(s.split_once(' ').unwrap(), ("hello", "world"));
484 }
485
486 #[test]
487 fn deref_is_lazy_and_cached() {
488 let mut data = vec![0i32; 16];
489 let buf = make_buffer(&mut data);
490 let s = unsafe { AmxString::new(buf, b"world") };
491 assert!(s.decoded.get().is_none());
493 let _ = &*s;
495 assert!(s.decoded.get().is_some());
496 let a = s.decoded.get().unwrap().as_ptr();
498 let _ = &*s;
499 let b = s.decoded.get().unwrap().as_ptr();
500 assert_eq!(a, b);
501 }
502
503 #[test]
504 fn display_and_deref_are_consistent() {
505 let mut data = vec![0i32; 16];
506 let buf = make_buffer(&mut data);
507 let s = unsafe { AmxString::new(buf, b"world") };
508 assert_eq!(s.to_string(), "world");
509 assert_eq!(&*s, "world");
510 assert_eq!(format!("{s}"), "world");
511 }
512
513 #[test]
514 fn bytes_len_reflects_buffer_size() {
515 let mut data = vec![0i32; 8];
516 let buf = make_buffer(&mut data);
517 let s = unsafe { AmxString::new(buf, b"abc") };
518 assert_eq!(s.bytes_len(), 8);
519 assert_eq!(s.len(), 3);
520 }
521
522 #[test]
523 fn unpacked_to_bytes_ascii() {
524 let text = b"SA-MP Plugin";
525 let mut data: Vec<i32> = text
526 .iter()
527 .map(|&b| i32::from(b))
528 .chain(std::iter::once(0))
529 .collect();
530 let buf = make_buffer(&mut data);
531 let s = unsafe { AmxString::new(buf, text) };
532 assert_eq!(s.to_bytes(), text);
533 }
534
535 #[test]
536 fn unpacked_single_char() {
537 let mut data = vec![0x41i32, 0];
538 let buf = make_buffer(&mut data);
539 let s = unsafe { AmxString::new(buf, b"A") };
540 assert_eq!(s.len(), 1);
541 assert_eq!(&*s, "A");
542 }
543
544 #[test]
550 fn packed_four_chars_one_cell() {
551 let mut data = vec![0x4142_4344i32, 0x0000_0000i32];
552 let buf = make_buffer(&mut data);
553 let s = AmxString::from_buffer_parts(buf, 4);
554 assert_eq!(s.to_bytes(), b"ABCD");
555 assert_eq!(&*s, "ABCD");
556 }
557
558 #[test]
559 fn packed_five_chars_two_cells() {
560 let mut data = vec![0x4142_4344i32, 0x4500_0000i32, 0x0000_0000i32];
562 let buf = make_buffer(&mut data);
563 let s = AmxString::from_buffer_parts(buf, 5);
564 assert_eq!(s.to_bytes(), b"ABCDE");
565 assert_eq!(&*s, "ABCDE");
566 }
567
568 #[test]
569 fn packed_truncates_at_len() {
570 let mut data = vec![0x4142_4344i32, 0x0000_0000i32];
571 let buf = make_buffer(&mut data);
572 let s = AmxString::from_buffer_parts(buf, 2);
573 assert_eq!(s.to_bytes(), b"AB");
574 }
575
576 #[test]
577 fn packed_stops_at_null_byte() {
578 let mut data = vec![0x4142_0044i32, 0x0000_0000i32];
580 let buf = make_buffer(&mut data);
581 let s = AmxString::from_buffer_parts(buf, 4);
582 assert_eq!(s.to_bytes(), b"AB");
583 }
584
585 #[test]
588 fn as_str_returns_decoded() {
589 let mut data = vec![0i32; 16];
590 let buf = make_buffer(&mut data);
591 let s = unsafe { AmxString::new(buf, b"hello") };
592 assert_eq!(s.as_str(), "hello");
593 }
594
595 #[test]
596 fn as_str_and_deref_are_same_pointer() {
597 let mut data = vec![0i32; 16];
598 let buf = make_buffer(&mut data);
599 let s = unsafe { AmxString::new(buf, b"rust") };
600 let a: &str = s.as_str();
602 let b: &str = &s;
603 assert_eq!(a.as_ptr(), b.as_ptr());
604 }
605
606 #[test]
609 fn partial_eq_str_literal() {
610 let mut data = vec![0i32; 16];
611 let buf = make_buffer(&mut data);
612 let s = unsafe { AmxString::new(buf, b"Admin") };
613 assert!(s == "Admin");
614 assert!(s != "admin");
615 }
616
617 #[test]
618 fn partial_eq_ref_str() {
619 let mut data = vec![0i32; 16];
620 let buf = make_buffer(&mut data);
621 let s = unsafe { AmxString::new(buf, b"samp") };
622 let key: &str = "samp";
623 assert!(s == key);
624 }
625
626 #[test]
627 fn partial_eq_string() {
628 let mut data = vec![0i32; 16];
629 let buf = make_buffer(&mut data);
630 let s = unsafe { AmxString::new(buf, b"plugin") };
631 let owned_match: String = "plugin".to_string();
632 let owned_other: String = "other".to_string();
633 assert!(s == owned_match);
634 assert!(s != owned_other);
635 }
636
637 #[test]
638 fn partial_eq_empty() {
639 let mut data = vec![0i32; 4];
640 let buf = make_buffer(&mut data);
641 let s = unsafe { AmxString::new(buf, b"") };
642 assert!(s.is_empty());
643 assert!(s != "x");
644 }
645
646 #[test]
649 fn put_in_buffer_writes_correctly() {
650 let mut data = vec![0i32; 16];
651 let mut buf = make_buffer(&mut data);
652 put_in_buffer(&mut buf, "hello").unwrap();
653 assert_eq!(buf[0], i32::from(b'h'));
654 assert_eq!(buf[4], i32::from(b'o'));
655 assert_eq!(buf[5], 0);
656 }
657
658 #[test]
659 fn put_in_buffer_exact_fit_fails() {
660 let mut data = vec![0i32; 5];
661 let mut buf = make_buffer(&mut data);
662 assert!(put_in_buffer(&mut buf, "hello").is_err());
663 }
664
665 #[test]
666 fn put_in_buffer_empty_string() {
667 let mut data = vec![0i32; 4];
668 let mut buf = make_buffer(&mut data);
669 put_in_buffer(&mut buf, "").unwrap();
670 assert_eq!(buf[0], 0);
671 }
672
673 fn lcg(seed: &mut u64) -> u32 {
678 *seed = seed
679 .wrapping_mul(6_364_136_223_846_793_005)
680 .wrapping_add(1_442_695_040_888_963_407);
681 (*seed >> 33) as u32
682 }
683
684 #[test]
685 fn to_bytes_declared_len_larger_than_buffer_is_bounded() {
686 let mut data = vec![0x41i32, 0x42, 0x43]; let buf = make_buffer(&mut data);
690 let s = AmxString::from_buffer_parts(buf, 9999);
691 let bytes = s.to_bytes();
692 assert!(bytes.len() <= 3, "read past the backing buffer: {bytes:?}");
693 }
694
695 #[test]
696 fn to_bytes_non_utf8_decodes_lossy_without_panic() {
697 let mut data = vec![0xFFi32, 0xFE, 0x41, 0];
700 let buf = make_buffer(&mut data);
701 let s = AmxString::from_buffer_parts(buf, 3);
702 let _ = &*s; assert!(!s.is_empty());
704 }
705
706 #[test]
707 fn fuzz_decode_never_panics() {
708 let mut seed = 0x0BAD_F00D_DEAD_BEEFu64;
712 for _ in 0..4000 {
713 let cells = (lcg(&mut seed) % 12) as usize + 1; let mut data: Vec<i32> = (0..cells).map(|_| lcg(&mut seed) as i32).collect();
715 let declared = (lcg(&mut seed) % 64) as usize;
717 let buf = make_buffer(&mut data);
718 let s = AmxString::from_buffer_parts(buf, declared);
719
720 let bytes = s.to_bytes();
721 assert!(bytes.len() <= 1024 * 1024);
722 let decoded = &*s;
724 assert!(decoded.len() <= bytes.len().max(4 * bytes.len() + 4));
725 }
726 }
727}