History

address auto-review

GPT 5.4 committed 6 months ago

Commit 7429b151 parent e64e3b815 files · +170 −73Browse files

Unified · Split

15 files
  1. gix-bitmap/src/ewah.rs +15 −2
  2. gix-bitmap/tests/bitmap.rs +21 −0
  3. gix-features/src/decode.rs +1 −1
  4. gix-odb/src/store_impls/loose/find.rs +4 −4
  5. gix-pack/fuzz/fuzz_targets/data_file.rs +1 −1
  6. gix-pack/fuzz/fuzz_targets/multi_index_file.rs +1 −1
  7. gix-pack/src/data/delta.rs +5 −5
  8. gix-pack/src/data/entry/decode.rs +2 −2
  9. gix-pack/src/index/init.rs +4 −4
  10. gix-pack/src/index/write/mod.rs +1 −1
  11. gix-pack/src/multi_index/chunk.rs +3 −3
  12. gix-pack/src/multi_index/verify.rs +7 −1
  13. gix-pack/tests/pack/multi_index/mod.rs +1 −1
  14. gix-transport/src/client/blocking_io/http/redirect.rs +98 −40
  15. gix-transport/src/client/blocking_io/http/reqwest/remote.rs +6 −7
gix-bitmap/src/ewah.rs+15−2
@@ -62,12 +62,25 @@ mod access { · 61 unmodified lines
62 index += 1;62 index += 1;
63 }63 }
64 } else {64 } else {
65 index += usize::try_from(rlw_running_len_bits(word)).ok()?;65 let len = usize::try_from(rlw_running_len_bits(word)).ok()?;
66 let end = index.checked_add(len)?;
67 if end > num_bits {
68 return None;
69 }
70 index = end;
66 }71 }
67 72
68 for _ in 0..rlw_literal_words(word) {73 for _ in 0..rlw_literal_words(word) {
69 let word = iter.next()?;74 let word = iter.next()?;
70 for bit_index in 0..64 {75 let remaining = num_bits.checked_sub(index)?;
76 if remaining == 0 {
77 return None;
78 }
79 let bits_in_word = remaining.min(64);
80 if bits_in_word < 64 && (word >> bits_in_word) != 0 {
81 return None;
82 }
83 for bit_index in 0..bits_in_word {
71 if word & (1 << bit_index) != 0 {84 if word & (1 << bit_index) != 0 {
72 f(index)?;85 f(index)?;
73 }86 }
gix-bitmap/tests/bitmap.rs+21−0
@@ -22,6 +22,27 @@ mod fuzzed { · 21 unmodified lines
22 );22 );
23 }23 }
24 24
25 #[test]
26 fn non_zero_padding_bits_in_last_literal_word_are_rejected() {
27 let mut data = Vec::new();
28 // One logical bit, two compressed words, then RLW position 0.
29 data.extend_from_slice(&1u32.to_be_bytes());
30 data.extend_from_slice(&2u32.to_be_bytes());
31 // The only in-range bit is bit 0 and it is unset; bit 33 is set only in padding.
32 data.extend_from_slice(&(1u64 << 33).to_be_bytes());
33 data.extend_from_slice(&2u64.to_be_bytes());
34 data.extend_from_slice(&0u32.to_be_bytes());
35
36 let (bitmap, rest) = gix_bitmap::ewah::decode(&data).expect("fixture must decode");
37
38 assert!(rest.is_empty(), "fixture should be fully consumed");
39 assert_eq!(
40 bitmap.for_each_set_bit(|_| Some(())),
41 None,
42 "set bits outside the declared bit length must be rejected"
43 );
44 }
45
25 fn artifact_paths(target: &str) -> Vec<std::path::PathBuf> {46 fn artifact_paths(target: &str) -> Vec<std::path::PathBuf> {
26 std::fs::read_dir(47 std::fs::read_dir(
27 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))48 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
gix-features/src/decode.rs+1−1
@@ -16,7 +16,7 @@ pub fn leb64_from_read(mut r: impl Read) -> Result<(u64, usize), std::io::Error> · 15 unmodified lines
16 .checked_add(1)16 .checked_add(1)
17 .and_then(|value| value.checked_shl(7))17 .and_then(|value| value.checked_shl(7))
18 .and_then(|value| value.checked_add(u64::from(c) & 0x7f))18 .and_then(|value| value.checked_add(u64::from(c) & 0x7f))
19 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "ofs-delta distance overflowed"))?;19 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "LEB64 value overflowed"))?;
20 }20 }
21 Ok((value, i))21 Ok((value, i))
22}22}
gix-odb/src/store_impls/loose/find.rs+4−4
@@ -29,7 +29,7 @@ pub enum Error { · 28 unmodified lines
29 29
30/// Object lookup30/// Object lookup
31impl Store {31impl Store {
32 const MAP_ACTION: &'static str = "open";32 const OPEN_OR_MAP_ACTION: &'static str = "open or map";
33 33
34 /// Returns true if the given id is contained in our repository.34 /// Returns true if the given id is contained in our repository.
35 pub fn contains(&self, id: &gix_hash::oid) -> bool {35 pub fn contains(&self, id: &gix_hash::oid) -> bool {
@@ -200,7 +200,7 @@ impl Store { · 164 unmodified lines
200 zlib::stream::inflate::read(&mut input, &mut inflate.state, &mut out[decompressed_body_prefix_len..])200 zlib::stream::inflate::read(&mut input, &mut inflate.state, &mut out[decompressed_body_prefix_len..])
201 .map_err(|e| Error::Io {201 .map_err(|e| Error::Io {
202 source: e,202 source: e,
203 action: "deflate",203 action: "inflate",
204 path: path.to_owned(),204 path: path.to_owned(),
205 })?;205 })?;
206 206
@@ -232,7 +232,7 @@ impl Store { · 25 unmodified lines
232 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),232 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
233 Err(err) => {233 Err(err) => {
234 return Err(Error::Io {234 return Err(Error::Io {
235 action: Self::MAP_ACTION,235 action: Self::OPEN_OR_MAP_ACTION,
236 source: err,236 source: err,
237 path: path.to_owned(),237 path: path.to_owned(),
238 })238 })
@@ -242,7 +242,7 @@ impl Store { · 3 unmodified lines
242 if map.is_empty() {242 if map.is_empty() {
243 return Err(Error::Io {243 return Err(Error::Io {
244 source: io::Error::other("empty loose object file"),244 source: io::Error::other("empty loose object file"),
245 action: Self::MAP_ACTION,245 action: Self::OPEN_OR_MAP_ACTION,
246 path: path.to_owned(),246 path: path.to_owned(),
247 });247 });
248 }248 }
gix-pack/fuzz/fuzz_targets/data_file.rs+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
3use anyhow::Result;3use anyhow::Result;
4use gix_features::{progress, zlib};4use gix_features::{progress, zlib};
5use gix_pack::{cache, data};5use gix_pack::{cache, data};
6use gix_pack_fuzz::{virtual_path, interrupt_flag};6use gix_pack_fuzz::{interrupt_flag, virtual_path};
7use libfuzzer_sys::fuzz_target;7use libfuzzer_sys::fuzz_target;
8use std::hint::black_box;8use std::hint::black_box;
9 9
gix-pack/fuzz/fuzz_targets/multi_index_file.rs+1−1
@@ -4,7 +4,7 @@ use anyhow::Result; · 3 unmodified lines
4use gix_features::progress;4use gix_features::progress;
5use gix_hash::Prefix;5use gix_hash::Prefix;
6use gix_pack::multi_index;6use gix_pack::multi_index;
7use gix_pack_fuzz::{interrupt_flag, virtual_path, empty_candidates};7use gix_pack_fuzz::{empty_candidates, interrupt_flag, virtual_path};
8use libfuzzer_sys::fuzz_target;8use libfuzzer_sys::fuzz_target;
9use std::hint::black_box;9use std::hint::black_box;
10 10
gix-pack/src/data/delta.rs+5−5
@@ -110,11 +110,11 @@ pub(crate) fn apply(base: &[u8], mut target: &mut [u8], data: &[u8]) -> Result<( · 109 unmodified lines
110 }110 }
111 }111 }
112 }112 }
113 if i != data.len() {113 debug_assert_eq!(
114 return Err(apply::Error::Corrupt {114 i,
115 message: "delta instructions were not consumed completely",115 data.len(),
116 });116 "delta instructions were not consumed completely, should be impossible"
117 }117 );
118 if !target.is_empty() {118 if !target.is_empty() {
119 return Err(apply::Error::Corrupt {119 return Err(apply::Error::Corrupt {
120 message: "delta instructions produced fewer bytes than promised",120 message: "delta instructions produced fewer bytes than promised",
gix-pack/src/data/entry/decode.rs+2−2
@@ -11,7 +11,7 @@ use crate::data; · 10 unmodified lines
11pub enum Error {11pub enum Error {
12 #[error("Object type {type_id} is unsupported")]12 #[error("Object type {type_id} is unsupported")]
13 UnsupportedType { type_id: u8 },13 UnsupportedType { type_id: u8 },
14 #[error("Pack entry is truncated while reading {message}")]14 #[error("Pack entry is truncated: {message}")]
15 Corrupt { message: &'static str },15 Corrupt { message: &'static str },
16 #[error("Pack entry header value overflowed while decoding")]16 #[error("Pack entry header value overflowed while decoding")]
17 Overflow,17 Overflow,
@@ -142,7 +142,7 @@ fn parse_header_info(data: &[u8]) -> Result<(u8, u64, usize), Error> { · 124 unmodified lines
142 let mut shift = 4u32;142 let mut shift = 4u32;
143 while c & 0b1000_0000 != 0 {143 while c & 0b1000_0000 != 0 {
144 c = *data.get(i).ok_or(Error::Corrupt {144 c = *data.get(i).ok_or(Error::Corrupt {
145 message: "Pack entry header continuation byte is missing",145 message: "pack entry header continuation byte",
146 })?;146 })?;
147 i += 1;147 i += 1;
148 let component = u64::from(c & 0b0111_1111).checked_shl(shift).ok_or(Error::Overflow)?;148 let component = u64::from(c & 0b0111_1111).checked_shl(shift).ok_or(Error::Overflow)?;
gix-pack/src/index/init.rs+4−4
@@ -153,14 +153,15 @@ fn validate_size(data: &[u8], kind: Version, num_objects: u32, hash_len: usize) · 152 unmodified lines
153 ),153 ),
154 });154 });
155 }155 }
156 let large_offset_indices: Vec<_> = data[offset32_start..offset32_end]156 let (large_offsets, max_large_offset_index) = data[offset32_start..offset32_end]
157 .chunks_exact(N32_SIZE)157 .chunks_exact(N32_SIZE)
158 .filter_map(|offset| {158 .filter_map(|offset| {
159 let offset = crate::read_u32(offset);159 let offset = crate::read_u32(offset);
160 (offset & (1 << 31) != 0).then_some((offset ^ (1 << 31)) as usize)160 (offset & (1 << 31) != 0).then_some((offset ^ (1 << 31)) as usize)
161 })161 })
162 .collect();162 .fold((0usize, 0usize), |(count, max_index), index| {
163 let large_offsets = large_offset_indices.len();163 (count + 1, max_index.max(index))
164 });
164 v2_header_size165 v2_header_size
165 .checked_add(oid_bytes)166 .checked_add(oid_bytes)
166 .and_then(|size| size.checked_add(table_bytes))167 .and_then(|size| size.checked_add(table_bytes))
@@ -171,7 +172,6 @@ fn validate_size(data: &[u8], kind: Version, num_objects: u32, hash_len: usize) · 4 unmodified lines
171 message: "Pack index size overflowed while validating version 2 layout".into(),172 message: "Pack index size overflowed while validating version 2 layout".into(),
172 })173 })
173 .and_then(|expected_size| {174 .and_then(|expected_size| {
174 let max_large_offset_index = large_offset_indices.into_iter().max().unwrap_or(0);
175 if large_offsets > 0 && max_large_offset_index >= large_offsets {175 if large_offsets > 0 && max_large_offset_index >= large_offsets {
176 return Err(Error::Corrupt {176 return Err(Error::Corrupt {
177 message: format!(177 message: format!(
gix-pack/src/index/write/mod.rs+1−1
@@ -22,7 +22,7 @@ pub struct Outcome { · 21 unmodified lines
22 pub num_objects: u32,22 pub num_objects: u32,
23}23}
24 24
25/// The progress ids used in [`write_data_iter_from_stream()`][crate::index::write_data_iter_to_stream()].25/// The progress ids used in [`write_data_iter_to_stream()`][crate::index::write_data_iter_to_stream()].
26///26///
27/// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.27/// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.
28#[derive(Debug, Copy, Clone)]28#[derive(Debug, Copy, Clone)]
gix-pack/src/multi_index/chunk.rs+3−3
@@ -203,7 +203,7 @@ pub mod lookup { · 202 unmodified lines
203 203
204 /// Return true if the size of the `offset` range seems to match for a `hash` of the given kind and the amount of objects.204 /// Return true if the size of the `offset` range seems to match for a `hash` of the given kind and the amount of objects.
205 pub fn is_valid(offset: &Range<usize>, hash: gix_hash::Kind, num_objects: u32) -> bool {205 pub fn is_valid(offset: &Range<usize>, hash: gix_hash::Kind, num_objects: u32) -> bool {
206 (offset.end - offset.start) == num_objects as usize * hash.len_in_bytes()206 (offset.end - offset.start) == (num_objects as usize).saturating_mul(hash.len_in_bytes())
207 }207 }
208}208}
209 209
@@ -251,10 +251,10 @@ pub mod offsets { · 41 unmodified lines
251 Ok(())251 Ok(())
252 }252 }
253 253
254 /// Returns true if the `offset` range seems to match the size required for `num_objects`.254 /// Returns true if the `offset` range seems to match the size required for the untrusted `num_objects`.
255 pub fn is_valid(offset: &Range<usize>, num_objects: u32) -> bool {255 pub fn is_valid(offset: &Range<usize>, num_objects: u32) -> bool {
256 let entry_size = 4 /* pack-id */ + 4 /* pack-offset */;256 let entry_size = 4 /* pack-id */ + 4 /* pack-offset */;
257 (offset.end - offset.start) == num_objects as usize * entry_size257 (offset.end - offset.start) == (num_objects as usize).saturating_mul(entry_size)
258 }258 }
259}259}
260 260
gix-pack/src/multi_index/verify.rs+7−1
@@ -34,6 +34,8 @@ pub mod integrity { · 33 unmodified lines
34 Fan { index: usize },34 Fan { index: usize },
35 #[error("The multi-index claims to have no objects")]35 #[error("The multi-index claims to have no objects")]
36 Empty,36 Empty,
37 #[error("The multi-index path '{path}' has no parent directory")]
38 InvalidPath { path: std::path::PathBuf },
37 #[error("Interrupted")]39 #[error("Interrupted")]
38 Interrupted,40 Interrupted,
39 }41 }
@@ -142,7 +144,11 @@ where · 102 unmodified lines
142 C: crate::cache::DecodeEntry,144 C: crate::cache::DecodeEntry,
143 F: Fn() -> C + Send + Clone,145 F: Fn() -> C + Send + Clone,
144 {146 {
145 let parent = self.path.parent().unwrap_or_else(|| std::path::Path::new(""));147 let parent = self.path.parent().ok_or_else(|| {
148 index::traverse::Error::Processor(integrity::Error::InvalidPath {
149 path: self.path.clone(),
150 })
151 })?;
146 152
147 let actual_index_checksum = self153 let actual_index_checksum = self
148 .verify_checksum(154 .verify_checksum(
gix-pack/tests/pack/multi_index/mod.rs+1−1
@@ -6,7 +6,7 @@ fn multi_index() -> (File, PathBuf) { · 5 unmodified lines
6 let path = crate::scripted_fixture_read_only("make_pack_gen_repo_multi_index.sh")6 let path = crate::scripted_fixture_read_only("make_pack_gen_repo_multi_index.sh")
7 .expect("test fixture exists")7 .expect("test fixture exists")
8 .join(".git/objects/pack/multi-pack-index");8 .join(".git/objects/pack/multi-pack-index");
9 let file = gix_pack::multi_index::File::at(&path, None).unwrap();9 let file = gix_pack::multi_index::File::at(&path, None).expect("multi-index fixture opens");
10 (file, path)10 (file, path)
11}11}
12 12
gix-transport/src/client/blocking_io/http/redirect.rs+98−40
@@ -6,21 +6,36 @@ pub struct Error { · 5 unmodified lines
6 expected_url: String,6 expected_url: String,
7}7}
8 8
9fn shares_authority(lhs: &str, rhs: &str) -> bool {9pub(crate) fn shares_authority_or_upgrades_scheme(redirect_url: &str, original_url: &str) -> bool {
10 let Ok(lhs) = gix_url::parse(lhs.into()) else {10 let Ok(redirect_url) = gix_url::parse(redirect_url.into()) else {
11 return false;11 return false;
12 };12 };
13 let Ok(rhs) = gix_url::parse(rhs.into()) else {13 let Ok(original_url) = gix_url::parse(original_url.into()) else {
14 return false;14 return false;
15 };15 };
16 lhs.scheme == rhs.scheme && lhs.host == rhs.host && lhs.port_or_default() == rhs.port_or_default()16
17 if redirect_url.host != original_url.host {
18 return false;
19 }
20
21 if redirect_url.scheme == original_url.scheme {
22 return redirect_url.port_or_default() == original_url.port_or_default();
23 }
24
25 if original_url.scheme == gix_url::Scheme::Http && redirect_url.scheme == gix_url::Scheme::Https {
26 let original_port = original_url.port_or_default();
27 let redirect_port = redirect_url.port_or_default();
28 return original_port == redirect_port || matches!((original_port, redirect_port), (Some(80), Some(443)));
29 }
30
31 false
17}32}
18 33
19pub(crate) fn base_url(redirect_url: &str, base_url: &str, url: String) -> Result<String, Error> {34pub(crate) fn base_url(redirect_url: &str, base_url: &str, url: String) -> Result<String, Error> {
20 let tail = url35 let tail = url
21 .strip_prefix(base_url)36 .strip_prefix(base_url)
22 .expect("BUG: caller assures `base_url` is subset of `url`");37 .expect("BUG: caller assures `base_url` is subset of `url`");
23 if !shares_authority(redirect_url, base_url) {38 if !shares_authority_or_upgrades_scheme(redirect_url, base_url) {
24 return Err(Error {39 return Err(Error {
25 redirect_url: redirect_url.into(),40 redirect_url: redirect_url.into(),
26 expected_url: url,41 expected_url: url,
@@ -62,46 +77,89 @@ mod tests { · 35 unmodified lines
62 );77 );
63 }78 }
64 79
80 #[test]
81 fn base_url_allows_same_host_scheme_upgrade() {
82 assert_eq!(
83 base_url(
84 "https://original/b/info/refs?hi",
85 "http://original/a",
86 "http://original/a/info/refs?hi".into()
87 )
88 .unwrap(),
89 "https://original/b"
90 );
91 }
92
65 #[test]93 #[test]
66 fn base_url_rejects_authority_changes() {94 fn base_url_rejects_authority_changes() {
67 assert!(base_url(95 assert!(
68 "http://original/b/info/refs?hi",96 base_url(
69 "https://original/a",97 "http://original/b/info/refs?hi",
70 "https://original/a/info/refs?hi".into()98 "https://original/a",
71 )99 "https://original/a/info/refs?hi".into()
72 .is_err());100 )
73 assert!(base_url(101 .is_err(),
74 "https://redirected.org/b/info/refs?hi",102 "downgrading from https to http must be rejected"
75 "https://original/a",103 );
76 "https://original/a/info/refs?hi".into()104 assert!(
77 )105 base_url(
78 .is_err());106 "https://redirected.org/b/info/refs?hi",
79 assert!(base_url(107 "https://original/a",
80 "https://original:444/b/info/refs?hi",108 "https://original/a/info/refs?hi".into()
81 "https://original/a",109 )
82 "https://original/a/info/refs?hi".into()110 .is_err(),
83 )111 "changing the host must be rejected"
84 .is_err());112 );
113 assert!(
114 base_url(
115 "https://original:444/b/info/refs?hi",
116 "https://original/a",
117 "https://original/a/info/refs?hi".into()
118 )
119 .is_err(),
120 "changing the port on https must be rejected"
121 );
122 assert!(
123 base_url(
124 "https://original:444/b/info/refs?hi",
125 "http://original/a",
126 "http://original/a/info/refs?hi".into()
127 )
128 .is_err(),
129 "upgrading the scheme does not allow changing the port"
130 );
85 }131 }
86 132
87 #[test]133 #[test]
88 fn shares_authority_complete() {134 fn shares_authority_or_upgrades_scheme_complete() {
89 assert!(shares_authority(135 assert!(
90 "https://original/b/info/refs?hi",136 shares_authority_or_upgrades_scheme("https://original/b/info/refs?hi", "https://original/a"),
91 "https://original/a"137 "keeping the same https authority must be allowed"
92 ));138 );
93 assert!(!shares_authority(139 assert!(
94 "http://original/b/info/refs?hi",140 shares_authority_or_upgrades_scheme("https://original/b/info/refs?hi", "http://original/a"),
95 "https://original/a"141 "upgrading from http to https on the same host must be allowed"
96 ));142 );
97 assert!(!shares_authority(143 assert!(
98 "https://redirected.org/b/info/refs?hi",144 shares_authority_or_upgrades_scheme("https://original:8080/b/info/refs?hi", "http://original:8080/a"),
99 "https://original/a"145 "upgrading from http to https on the same host and port must be allowed"
100 ));146 );
101 assert!(!shares_authority(147 assert!(
102 "https://original:444/b/info/refs?hi",148 !shares_authority_or_upgrades_scheme("http://original/b/info/refs?hi", "https://original/a"),
103 "https://original/a"149 "downgrading from https to http must be rejected"
104 ));150 );
151 assert!(
152 !shares_authority_or_upgrades_scheme("https://redirected.org/b/info/refs?hi", "https://original/a"),
153 "changing the host must be rejected"
154 );
155 assert!(
156 !shares_authority_or_upgrades_scheme("https://original:444/b/info/refs?hi", "https://original/a"),
157 "changing the port on https must be rejected"
158 );
159 assert!(
160 !shares_authority_or_upgrades_scheme("https://original:444/b/info/refs?hi", "http://original/a"),
161 "upgrading the scheme does not allow changing the port"
162 );
105 }163 }
106 164
107 #[test]165 #[test]
gix-transport/src/client/blocking_io/http/reqwest/remote.rs+6−7
@@ -41,12 +41,6 @@ impl Default for Remote { · 40 unmodified lines
41 let (req_send, req_recv) = std::sync::mpsc::sync_channel(0);41 let (req_send, req_recv) = std::sync::mpsc::sync_channel(0);
42 let (res_send, res_recv) = std::sync::mpsc::sync_channel(0);42 let (res_send, res_recv) = std::sync::mpsc::sync_channel(0);
43 let handle = std::thread::spawn(move || -> Result<(), Error> {43 let handle = std::thread::spawn(move || -> Result<(), Error> {
44 fn shares_authority(lhs: &reqwest::Url, rhs: &reqwest::Url) -> bool {
45 lhs.scheme() == rhs.scheme()
46 && lhs.host_str() == rhs.host_str()
47 && lhs.port_or_known_default() == rhs.port_or_known_default()
48 }
49
50 let mut follow = None;44 let mut follow = None;
51 let mut redirected_base_url = None::<String>;45 let mut redirected_base_url = None::<String>;
52 let allow_redirects = Arc::new(atomic::AtomicBool::new(false));46 let allow_redirects = Arc::new(atomic::AtomicBool::new(false));
@@ -64,7 +58,12 @@ impl Default for Remote { · 11 unmodified lines
64 let prev_urls = attempt.previous();58 let prev_urls = attempt.previous();
65 59
66 match prev_urls.first() {60 match prev_urls.first() {
67 Some(prev_url) if !shares_authority(prev_url, curr_url) => {61 Some(prev_url)
62 if !redirect::shares_authority_or_upgrades_scheme(
63 curr_url.as_str(),
64 prev_url.as_str(),
65 ) =>
66 {
68 // git does not want to be redirected to a different host.67 // git does not want to be redirected to a different host.
69 attempt.stop()68 attempt.stop()
70 }69 }