max / shop
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01DwpiantpUgohzML4xr6KeQ
7 files changed,
+372 insertions,
-50 deletions
| @@ -2390,6 +2390,10 @@ | |||
| 2390 | 2390 | name = "synckit-config" | |
| 2391 | 2391 | version = "0.2.0" | |
| 2392 | 2392 | ||
| 2393 | + | [[patch.unused]] | |
| 2394 | + | name = "docengine" | |
| 2395 | + | version = "0.7.0" | |
| 2396 | + | ||
| 2393 | 2397 | [[patch.unused]] | |
| 2394 | 2398 | name = "quasi-axum" | |
| 2395 | 2399 | version = "0.81.0" | |
| @@ -2441,7 +2445,3 @@ | |||
| 2441 | 2445 | [[patch.unused]] | |
| 2442 | 2446 | name = "tagtree" | |
| 2443 | 2447 | version = "0.4.1" | |
| 2444 | - | ||
| 2445 | - | [[patch.unused]] | |
| 2446 | - | name = "docengine" | |
| 2447 | - | version = "0.7.0" |
| @@ -31,8 +31,11 @@ | |||
| 31 | 31 | //! second drain with no input in between reports nothing. | |
| 32 | 32 | //! 5. **Resize keeps all of the above**, including a resize back to the | |
| 33 | 33 | //! original size, which is where `rewrap_history` runs. | |
| 34 | - | //! 6. **The parser's buffers are bounded by the input** — an amplification | |
| 35 | - | //! ceiling, not a fix. See [`MAX_RETAINED_PER_INPUT_BYTE`]. | |
| 34 | + | //! 6. **The parser's buffers are bounded**, twice over: by the input, via | |
| 35 | + | //! [`MAX_RETAINED_PER_INPUT_BYTE`], and absolutely, via | |
| 36 | + | //! [`MAX_RETAINED_BYTES`]. The second is the one that catches an | |
| 37 | + | //! accumulator growing 1:1 with a stream nobody terminates, which the | |
| 38 | + | //! first cannot see. | |
| 36 | 39 | //! 7. **Ground is always reachable** — after any byte sequence, `ESC \` then | |
| 37 | 40 | //! `ESC [ 0 m` returns the parser to Ground from every state. Cheap, and it | |
| 38 | 41 | //! catches a transition-table edit that strands a stream. | |
| @@ -43,27 +46,40 @@ | |||
| 43 | 46 | /// Bytes the parser may hold per byte of input before the oracle calls it a | |
| 44 | 47 | /// finding. | |
| 45 | 48 | /// | |
| 46 | - | /// Not a claim that the current parser is frugal. Measured on 2026-08-29, an | |
| 47 | - | /// `ESC [` followed by four million separators retains 27.2 bytes of buffer | |
| 48 | - | /// capacity per input byte, because every `;` pushes a fresh `Vec<u16>` and | |
| 49 | - | /// nothing caps the count; counted as process RSS, with the allocator's own | |
| 50 | - | /// per-allocation overhead, the same input costs about twice that. An | |
| 51 | - | /// unterminated OSC or APC body accumulates 1:1 with no cap either. Those are a | |
| 52 | - | /// denial of service and are reported as one; they are deliberately NOT what | |
| 53 | - | /// this constant asserts, because a ceiling set below today's behaviour makes | |
| 54 | - | /// the target report the same known finding on every input forever and never | |
| 55 | - | /// reach anything new. | |
| 49 | + | /// This is now an assertion rather than a placeholder. It sat at 128 until | |
| 50 | + | /// 2026-08-30, deliberately above a parser that had no ceiling at all: `ESC [` | |
| 51 | + | /// followed by four million separators retained 27.2 bytes per input byte | |
| 52 | + | /// because every `;` pushed a fresh `Vec<u16>`, and an unterminated OSC or APC | |
| 53 | + | /// body accumulated 1:1 forever. `shop_vt::MAX_PARAMS`, | |
| 54 | + | /// `shop_vt::MAX_STRING_BYTES` and `shop_vt::MAX_OSC_PARAMS` closed all three. | |
| 56 | 55 | /// | |
| 57 | - | /// What it does catch is a regression that makes the amplification worse, or an | |
| 58 | - | /// accumulator that grows without the input growing. Lower it when the | |
| 59 | - | /// allocation finding is fixed; that is the point at which it starts asserting | |
| 60 | - | /// something the code does not already satisfy. | |
| 61 | - | pub const MAX_RETAINED_PER_INPUT_BYTE: usize = 128; | |
| 56 | + | /// 4 is set from the measured worst case, which is a body buffer caught just | |
| 57 | + | /// past a doubling: 2.0 bytes of capacity per byte pushed, plus the parameter | |
| 58 | + | /// list and the OSC index table on top. The same four million separators now | |
| 59 | + | /// retain 0.001 bytes per input byte. Anything that pushes this back over 4 is | |
| 60 | + | /// either a new accumulator or a cap that stopped being enforced. | |
| 61 | + | pub const MAX_RETAINED_PER_INPUT_BYTE: usize = 4; | |
| 62 | 62 | ||
| 63 | - | /// Slack for the buffers a fresh parser allocates up front (two 2 KiB bodies) | |
| 64 | - | /// plus the parameter list's own spine. | |
| 63 | + | /// Slack for the buffers a fresh parser allocates up front (two 2 KiB bodies), | |
| 64 | + | /// the parameter list's own spine, and the OSC field index table, which costs | |
| 65 | + | /// 16 bytes per `;` and so amplifies hard against a stream of nothing else. | |
| 65 | 66 | pub const RETAINED_BASE_BYTES: usize = 64 * 1024; | |
| 66 | 67 | ||
| 68 | + | /// Bytes the parser may hold after any input at all, however long. | |
| 69 | + | /// | |
| 70 | + | /// The ratio ceiling above cannot see the second half of the 2026-08-29 | |
| 71 | + | /// finding. An unterminated OSC or APC body accumulated 1:1, so its ratio was | |
| 72 | + | /// 1.0 and no per-input-byte limit above 1 would ever have fired, while the | |
| 73 | + | /// buffer grew for as long as the writer kept writing. What was wrong with it | |
| 74 | + | /// was that it had no absolute bound, and this is that bound. | |
| 75 | + | /// | |
| 76 | + | /// Set from the caps rather than guessed: one body buffer at | |
| 77 | + | /// `shop_vt::MAX_STRING_BYTES`, the other resting at its initial 2 KiB, the OSC | |
| 78 | + | /// index table at `shop_vt::MAX_OSC_PARAMS` doubled, and `shop_vt::MAX_PARAMS` | |
| 79 | + | /// slots. That is about 8.42 MiB; 12 MiB leaves room for allocator rounding | |
| 80 | + | /// without leaving room for an accumulator that does not stop. | |
| 81 | + | pub const MAX_RETAINED_BYTES: usize = 12 * 1024 * 1024; | |
| 82 | + | ||
| 67 | 83 | /// Panics if `grid` has broken anything `place_char`'s unchecked store rests on. | |
| 68 | 84 | /// | |
| 69 | 85 | /// # Panics | |
| @@ -251,6 +267,10 @@ | |||
| 251 | 267 | "parser holds {retained} bytes after {} bytes of input, over the {ceiling} ceiling", | |
| 252 | 268 | stream.len() | |
| 253 | 269 | ); | |
| 270 | + | assert!( | |
| 271 | + | retained <= MAX_RETAINED_BYTES, | |
| 272 | + | "parser holds {retained} bytes, over the {MAX_RETAINED_BYTES} absolute ceiling" | |
| 273 | + | ); | |
| 254 | 274 | ||
| 255 | 275 | // Ground is reachable from every state these two sequences can leave the | |
| 256 | 276 | // parser in: `ESC \` closes any string state, and a complete CSI closes the |
| @@ -75,3 +75,35 @@ | |||
| 75 | 75 | let input = b"\x63\x1dhello\x1b[31mworld\x1b[H\x1b[2J"; | |
| 76 | 76 | assert_eq!(shop_grid::oracle::check_bytes(input), input.len() - 2); | |
| 77 | 77 | } | |
| 78 | + | ||
| 79 | + | /// The two unbounded growths the parser had until 2026-08-30, replayed at a | |
| 80 | + | /// size the committed corpus cannot carry. | |
| 81 | + | /// | |
| 82 | + | /// `fuzz/regressions/` holds an 8 KiB witness for each, which is enough for the | |
| 83 | + | /// amplification ceiling to catch the CSI case. Neither string case amplifies — | |
| 84 | + | /// they accumulated 1:1 — so the only thing that fails on an uncapped parser is | |
| 85 | + | /// feeding it more than the cap, and eight megabytes is not a file to commit. | |
| 86 | + | #[test] | |
| 87 | + | fn unbounded_parser_growth_stays_capped() { | |
| 88 | + | // Four million separators, each of which used to push a fresh Vec<u16>. | |
| 89 | + | // The oracle reads the first two bytes as the grid size, not as stream. | |
| 90 | + | let mut csi = b"\x4f\x17\x1b[".to_vec(); | |
| 91 | + | csi.extend(std::iter::repeat_n(b';', 4_000_000)); | |
| 92 | + | shop_grid::oracle::check_bytes(&csi); | |
| 93 | + | ||
| 94 | + | // Bodies twice the cap, opened and never terminated. | |
| 95 | + | let over = shop_vt::MAX_STRING_BYTES * 2; | |
| 96 | + | for opener in [&b"\x4f\x17\x1b]"[..], &b"\x4f\x17\x1b_"[..]] { | |
| 97 | + | let mut body = opener.to_vec(); | |
| 98 | + | body.extend(std::iter::repeat_n(b'A', over)); | |
| 99 | + | shop_grid::oracle::check_bytes(&body); | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | // And the buffers come back down afterwards rather than holding the cap | |
| 103 | + | // for the life of the parser, which is what makes the ceiling hold for | |
| 104 | + | // every input that follows a big one. | |
| 105 | + | let mut big = b"\x4f\x17\x1b]0;".to_vec(); | |
| 106 | + | big.extend(std::iter::repeat_n(b'A', over)); | |
| 107 | + | big.extend_from_slice(b"\x07\x1b[0m"); | |
| 108 | + | shop_grid::oracle::check_bytes(&big); | |
| 109 | + | } |
| @@ -9,7 +9,13 @@ | |||
| 9 | 9 | //! Deliberately narrow scope: | |
| 10 | 10 | //! - Written from the spec (not vendored from vte). | |
| 11 | 11 | //! - Params live in `Vec<Vec<u16>>` for clarity; not the tightest packing but | |
| 12 | - | //! trivial to iterate and terminals do not push millions of CSIs per sec. | |
| 12 | + | //! trivial to iterate. This used to add "and terminals do not push millions | |
| 13 | + | //! of CSIs per sec", which was a statement about well-behaved writers and not | |
| 14 | + | //! about the ones the parser actually has to survive. See [`MAX_PARAMS`]. | |
| 15 | + | //! - State the parser holds on behalf of an incomplete sequence is bounded: | |
| 16 | + | //! [`MAX_PARAMS`], [`MAX_STRING_BYTES`] and [`MAX_OSC_PARAMS`]. Nothing here | |
| 17 | + | //! grows with what a writer sends, because the writer is whatever program | |
| 18 | + | //! holds the far end of the PTY. | |
| 13 | 19 | //! - No SIMD UTF-8 fast path yet — that's a `simdutf8` swap when we care. | |
| 14 | 20 | //! - No sync-update (BSU/ESU) hooks yet — added when someone starts using | |
| 15 | 21 | //! them and paint tearing shows up. | |
| @@ -69,6 +75,55 @@ | |||
| 69 | 75 | } | |
| 70 | 76 | } | |
| 71 | 77 | ||
| 78 | + | /// Parameter slots a single CSI or DCS sequence may hold, counting | |
| 79 | + | /// subparameters. | |
| 80 | + | /// | |
| 81 | + | /// Past this the sequence is marked ignored and the extra slots are dropped, | |
| 82 | + | /// which is what the reference DEC parser does with its own fixed parameter | |
| 83 | + | /// store and what this parser already does with a third intermediate. Without | |
| 84 | + | /// it every `;` pushes a fresh `Vec<u16>` for as long as a writer keeps | |
| 85 | + | /// sending them: measured 2026-08-29, `ESC [` and four million separators | |
| 86 | + | /// retained 27.2 bytes of buffer per input byte, so about 19 MB of hostile | |
| 87 | + | /// stdout retained a gigabyte. | |
| 88 | + | /// | |
| 89 | + | /// 32 is `vte`'s number, and this crate exists to be a drop-in for `vte` with | |
| 90 | + | /// an APC callback, so matching it means an application that renders under one | |
| 91 | + | /// renders under the other. xterm's NPARAM is 30, and the widest real sequence | |
| 92 | + | /// anyone sends is a 6-slot `38:2::R:G:B`. | |
| 93 | + | pub const MAX_PARAMS: usize = 32; | |
| 94 | + | ||
| 95 | + | /// Bytes an OSC or APC body may accumulate before the parser stops buffering | |
| 96 | + | /// and drops the sequence. | |
| 97 | + | /// | |
| 98 | + | /// Unterminated bodies accumulated 1:1 with no ceiling at all, so a writer that | |
| 99 | + | /// opens `ESC ]` and never terminates it grew the terminal by whatever it felt | |
| 100 | + | /// like sending. | |
| 101 | + | /// | |
| 102 | + | /// 8 MiB is far above anything either protocol asks for. The kitty graphics | |
| 103 | + | /// protocol requires payloads over 4096 bytes to be chunked, so an APC body is | |
| 104 | + | /// a few kilobytes; OSC 52 carries a base64 clipboard selection, which is the | |
| 105 | + | /// one field with any real size to it. A body over the limit is dropped rather | |
| 106 | + | /// than truncated: half a base64 clipboard write or half an image chunk is not | |
| 107 | + | /// a smaller version of the request, it is a different one. | |
| 108 | + | pub const MAX_STRING_BYTES: usize = 8 * 1024 * 1024; | |
| 109 | + | ||
| 110 | + | /// Fields (`;`-separated) an OSC body may hold before the sequence is dropped. | |
| 111 | + | /// | |
| 112 | + | /// Separated from [`MAX_STRING_BYTES`] because a separator costs 16 bytes of | |
| 113 | + | /// index pair and contributes no body byte, so a stream of nothing but `;` | |
| 114 | + | /// amplifies about 32x against the byte budget. 1024 is past any real OSC: | |
| 115 | + | /// the widest is a multi-colour `OSC 4`, and shop's own handler reads two | |
| 116 | + | /// fields. | |
| 117 | + | pub const MAX_OSC_PARAMS: usize = 1024; | |
| 118 | + | ||
| 119 | + | /// What the two body buffers are allocated with, and shrunk back to once a | |
| 120 | + | /// sequence ends. | |
| 121 | + | /// | |
| 122 | + | /// Shrinking is the half that makes the cap hold across sequences: capacity is | |
| 123 | + | /// a high-water mark, so without it one large body leaves the parser holding | |
| 124 | + | /// that much for the life of the terminal. | |
| 125 | + | const INITIAL_BODY_CAPACITY: usize = 2048; | |
| 126 | + | ||
| 72 | 127 | /// Iterable parameter list for CSI / DCS. Each iteration yields one | |
| 73 | 128 | /// parameter's subparameters (colon-separated, e.g. `38:2::R:G:B` gives one | |
| 74 | 129 | /// entry `[38, 2, 0, R, G, B]`; `38;2;R;G;B` gives five entries `[38] [2] | |
| @@ -76,6 +131,9 @@ | |||
| 76 | 131 | #[derive(Debug, Default, Clone)] | |
| 77 | 132 | pub struct Params { | |
| 78 | 133 | inner: Vec<Vec<u16>>, | |
| 134 | + | /// Slots used across every group, which is what [`MAX_PARAMS`] bounds. | |
| 135 | + | /// Held rather than summed because it is consulted per byte. | |
| 136 | + | slots: usize, | |
| 79 | 137 | } | |
| 80 | 138 | ||
| 81 | 139 | impl Params { | |
| @@ -93,20 +151,36 @@ | |||
| 93 | 151 | ||
| 94 | 152 | fn clear(&mut self) { | |
| 95 | 153 | self.inner.clear(); | |
| 154 | + | self.slots = 0; | |
| 96 | 155 | } | |
| 97 | 156 | ||
| 98 | - | fn push_new(&mut self) { | |
| 99 | - | self.inner.push(Vec::with_capacity(1)); | |
| 157 | + | fn is_full(&self) -> bool { | |
| 158 | + | self.slots >= MAX_PARAMS | |
| 100 | 159 | } | |
| 101 | 160 | ||
| 102 | - | fn ensure_open(&mut self) { | |
| 103 | - | if self.inner.is_empty() { | |
| 104 | - | self.push_new(); | |
| 161 | + | /// Opens a group, or reports that the sequence has run out of slots. | |
| 162 | + | fn push_new(&mut self) -> bool { | |
| 163 | + | if self.is_full() { | |
| 164 | + | return false; | |
| 105 | 165 | } | |
| 166 | + | self.inner.push(Vec::with_capacity(1)); | |
| 167 | + | self.slots += 1; | |
| 168 | + | true | |
| 106 | 169 | } | |
| 107 | 170 | ||
| 171 | + | fn ensure_open(&mut self) -> bool { | |
| 172 | + | if self.inner.is_empty() { | |
| 173 | + | return self.push_new(); | |
| 174 | + | } | |
| 175 | + | true | |
| 176 | + | } | |
| 177 | + | ||
| 178 | + | /// Digits only ever rewrite the slot already open, so this cannot grow the | |
| 179 | + | /// list and does not report an overflow of its own. | |
| 108 | 180 | fn append_digit(&mut self, digit: u16) { | |
| 109 | - | self.ensure_open(); | |
| 181 | + | if !self.ensure_open() { | |
| 182 | + | return; | |
| 183 | + | } | |
| 110 | 184 | let group = self.inner.last_mut().unwrap(); | |
| 111 | 185 | if group.is_empty() { | |
| 112 | 186 | group.push(digit); | |
| @@ -116,13 +190,20 @@ | |||
| 116 | 190 | } | |
| 117 | 191 | } | |
| 118 | 192 | ||
| 119 | - | fn new_subparam(&mut self) { | |
| 120 | - | self.ensure_open(); | |
| 193 | + | fn new_subparam(&mut self) -> bool { | |
| 194 | + | if !self.ensure_open() { | |
| 195 | + | return false; | |
| 196 | + | } | |
| 197 | + | if self.is_full() { | |
| 198 | + | return false; | |
| 199 | + | } | |
| 121 | 200 | self.inner.last_mut().unwrap().push(0); | |
| 201 | + | self.slots += 1; | |
| 202 | + | true | |
| 122 | 203 | } | |
| 123 | 204 | ||
| 124 | - | fn new_param(&mut self) { | |
| 125 | - | self.push_new(); | |
| 205 | + | fn new_param(&mut self) -> bool { | |
| 206 | + | self.push_new() | |
| 126 | 207 | } | |
| 127 | 208 | ||
| 128 | 209 | /// Heap bytes this parameter list is holding. | |
| @@ -172,6 +253,10 @@ | |||
| 172 | 253 | osc_buf: Vec<u8>, | |
| 173 | 254 | osc_params: Vec<(usize, usize)>, | |
| 174 | 255 | apc_buf: Vec<u8>, | |
| 256 | + | /// Set when the open OSC or APC body has passed [`MAX_STRING_BYTES`] or | |
| 257 | + | /// [`MAX_OSC_PARAMS`]. The parser keeps scanning for the terminator so the | |
| 258 | + | /// stream stays in sync, buffers nothing more, and dispatches nothing. | |
| 259 | + | string_overflow: bool, | |
| 175 | 260 | utf8_buf: [u8; 4], | |
| 176 | 261 | utf8_idx: usize, | |
| 177 | 262 | utf8_expected: usize, | |
| @@ -192,9 +277,10 @@ | |||
| 192 | 277 | intermediates_idx: 0, | |
| 193 | 278 | ignoring: false, | |
| 194 | 279 | params: Params::default(), | |
| 195 | - | osc_buf: Vec::with_capacity(2048), | |
| 280 | + | osc_buf: Vec::with_capacity(INITIAL_BODY_CAPACITY), | |
| 196 | 281 | osc_params: Vec::with_capacity(8), | |
| 197 | - | apc_buf: Vec::with_capacity(2048), | |
| 282 | + | apc_buf: Vec::with_capacity(INITIAL_BODY_CAPACITY), | |
| 283 | + | string_overflow: false, | |
| 198 | 284 | utf8_buf: [0; 4], | |
| 199 | 285 | utf8_idx: 0, | |
| 200 | 286 | utf8_expected: 0, | |
| @@ -302,7 +388,7 @@ | |||
| 302 | 388 | self.state = State::DcsEntry; | |
| 303 | 389 | } | |
| 304 | 390 | 0x58 | 0x5E => { | |
| 305 | - | self.apc_buf.clear(); | |
| 391 | + | self.apc_start(); | |
| 306 | 392 | self.state = State::ApcString; | |
| 307 | 393 | } | |
| 308 | 394 | 0x5B => { | |
| @@ -314,7 +400,7 @@ | |||
| 314 | 400 | self.state = State::OscString; | |
| 315 | 401 | } | |
| 316 | 402 | 0x5F => { | |
| 317 | - | self.apc_buf.clear(); | |
| 403 | + | self.apc_start(); | |
| 318 | 404 | self.state = State::ApcString; | |
| 319 | 405 | } | |
| 320 | 406 | 0x7F => {} // Ignore. | |
| @@ -356,11 +442,15 @@ | |||
| 356 | 442 | self.state = State::CsiParam; | |
| 357 | 443 | } | |
| 358 | 444 | 0x3A => { | |
| 359 | - | self.params.new_subparam(); | |
| 445 | + | if !self.params.new_subparam() { | |
| 446 | + | self.ignoring = true; | |
| 447 | + | } | |
| 360 | 448 | self.state = State::CsiParam; | |
| 361 | 449 | } | |
| 362 | 450 | 0x3B => { | |
| 363 | - | self.params.new_param(); | |
| 451 | + | if !self.params.new_param() { | |
| 452 | + | self.ignoring = true; | |
| 453 | + | } | |
| 364 | 454 | self.state = State::CsiParam; | |
| 365 | 455 | } | |
| 366 | 456 | 0x3C..=0x3F => { | |
| @@ -389,8 +479,16 @@ | |||
| 389 | 479 | self.state = State::CsiIntermediate; | |
| 390 | 480 | } | |
| 391 | 481 | 0x30..=0x39 => self.params.append_digit((b - b'0') as u16), | |
| 392 | - | 0x3A => self.params.new_subparam(), | |
| 393 | - | 0x3B => self.params.new_param(), | |
| 482 | + | 0x3A => { | |
| 483 | + | if !self.params.new_subparam() { | |
| 484 | + | self.ignoring = true; | |
| 485 | + | } | |
| 486 | + | } | |
| 487 | + | 0x3B => { | |
| 488 | + | if !self.params.new_param() { | |
| 489 | + | self.ignoring = true; | |
| 490 | + | } | |
| 491 | + | } | |
| 394 | 492 | 0x3C..=0x3F => self.state = State::CsiIgnore, | |
| 395 | 493 | 0x40..=0x7E => { | |
| 396 | 494 | perform.csi_dispatch( | |
| @@ -438,6 +536,7 @@ | |||
| 438 | 536 | 0x07 => { | |
| 439 | 537 | self.osc_close_current(); | |
| 440 | 538 | self.dispatch_osc(perform, true); | |
| 539 | + | self.osc_end(); | |
| 441 | 540 | self.state = State::Ground; | |
| 442 | 541 | } | |
| 443 | 542 | 0x1B => { | |
| @@ -445,28 +544,53 @@ | |||
| 445 | 544 | // following `\` gets consumed as a no-op esc_dispatch. | |
| 446 | 545 | self.osc_close_current(); | |
| 447 | 546 | self.dispatch_osc(perform, false); | |
| 547 | + | self.osc_end(); | |
| 448 | 548 | self.prev_string_state = State::OscString; | |
| 449 | 549 | self.state = State::Escape; | |
| 450 | 550 | self.clear(); | |
| 451 | 551 | } | |
| 452 | 552 | 0x3B => { | |
| 453 | 553 | // Parameter separator: close current field, open next. | |
| 554 | + | if self.osc_params.len() >= MAX_OSC_PARAMS { | |
| 555 | + | self.string_overflow = true; | |
| 556 | + | return; | |
| 557 | + | } | |
| 454 | 558 | self.osc_close_current(); | |
| 455 | 559 | let end = self.osc_buf.len(); | |
| 456 | 560 | self.osc_params.push((end, end)); | |
| 457 | 561 | } | |
| 458 | - | _ => self.osc_buf.push(b), | |
| 562 | + | _ => { | |
| 563 | + | if self.osc_buf.len() >= MAX_STRING_BYTES { | |
| 564 | + | self.string_overflow = true; | |
| 565 | + | return; | |
| 566 | + | } | |
| 567 | + | self.osc_buf.push(b); | |
| 568 | + | } | |
| 459 | 569 | } | |
| 460 | 570 | } | |
| 461 | 571 | ||
| 462 | 572 | fn osc_start(&mut self) { | |
| 463 | 573 | self.osc_buf.clear(); | |
| 464 | 574 | self.osc_params.clear(); | |
| 575 | + | self.string_overflow = false; | |
| 465 | 576 | // Open the first parameter with a placeholder end index that | |
| 466 | 577 | // `osc_close_current` finalizes on ; / BEL / ST. | |
| 467 | 578 | self.osc_params.push((0, 0)); | |
| 468 | 579 | } | |
| 469 | 580 | ||
| 581 | + | /// Returns the OSC buffers to their resting size once a body has ended. | |
| 582 | + | /// | |
| 583 | + | /// `clear` leaves capacity behind, so a single oversized body would keep | |
| 584 | + | /// the cap's worth of memory reserved for the life of the parser and the | |
| 585 | + | /// limit above would bound one sequence rather than the process. | |
| 586 | + | fn osc_end(&mut self) { | |
| 587 | + | self.osc_buf.clear(); | |
| 588 | + | self.osc_buf.shrink_to(INITIAL_BODY_CAPACITY); | |
| 589 | + | self.osc_params.clear(); | |
| 590 | + | self.osc_params.shrink_to(8); | |
| 591 | + | self.string_overflow = false; | |
| 592 | + | } | |
| 593 | + | ||
| 470 | 594 | fn osc_close_current(&mut self) { | |
| 471 | 595 | if let Some(last) = self.osc_params.last_mut() { | |
| 472 | 596 | last.1 = self.osc_buf.len(); | |
| @@ -474,6 +598,11 @@ | |||
| 474 | 598 | } | |
| 475 | 599 | ||
| 476 | 600 | fn dispatch_osc<P: Perform>(&self, perform: &mut P, bell_terminated: bool) { | |
| 601 | + | // An over-long body is dropped, not truncated: half a base64 clipboard | |
| 602 | + | // write is a different request, not a smaller one. | |
| 603 | + | if self.string_overflow { | |
| 604 | + | return; | |
| 605 | + | } | |
| 477 | 606 | let slices: Vec<&[u8]> = self | |
| 478 | 607 | .osc_params | |
| 479 | 608 | .iter() | |
| @@ -495,7 +624,9 @@ | |||
| 495 | 624 | } | |
| 496 | 625 | 0x3A => self.state = State::DcsIgnore, | |
| 497 | 626 | 0x3B => { | |
| 498 | - | self.params.new_param(); | |
| 627 | + | if !self.params.new_param() { | |
| 628 | + | self.ignoring = true; | |
| 629 | + | } | |
| 499 | 630 | self.state = State::DcsParam; | |
| 500 | 631 | } | |
| 501 | 632 | 0x3C..=0x3F => { | |
| @@ -525,7 +656,11 @@ | |||
| 525 | 656 | } | |
| 526 | 657 | 0x30..=0x39 => self.params.append_digit((b - b'0') as u16), | |
| 527 | 658 | 0x3A => self.state = State::DcsIgnore, | |
| 528 | - | 0x3B => self.params.new_param(), | |
| 659 | + | 0x3B => { | |
| 660 | + | if !self.params.new_param() { | |
| 661 | + | self.ignoring = true; | |
| 662 | + | } | |
| 663 | + | } | |
| 529 | 664 | 0x3C..=0x3F => self.state = State::DcsIgnore, | |
| 530 | 665 | 0x40..=0x7E => { | |
| 531 | 666 | perform.hook( | |
| @@ -589,21 +724,44 @@ | |||
| 589 | 724 | fn apc_string<P: Perform>(&mut self, perform: &mut P, b: u8) { | |
| 590 | 725 | match b { | |
| 591 | 726 | 0x07 => { | |
| 592 | - | perform.apc_dispatch(&self.apc_buf); | |
| 593 | - | self.apc_buf.clear(); | |
| 727 | + | if !self.string_overflow { | |
| 728 | + | perform.apc_dispatch(&self.apc_buf); | |
| 729 | + | } | |
| 730 | + | self.apc_end(); | |
| 594 | 731 | self.state = State::Ground; | |
| 595 | 732 | } | |
| 596 | 733 | 0x1B => { | |
| 597 | - | perform.apc_dispatch(&self.apc_buf); | |
| 598 | - | self.apc_buf.clear(); | |
| 734 | + | if !self.string_overflow { | |
| 735 | + | perform.apc_dispatch(&self.apc_buf); | |
| 736 | + | } | |
| 737 | + | self.apc_end(); | |
| 599 | 738 | self.prev_string_state = State::ApcString; | |
| 600 | 739 | self.state = State::Escape; | |
| 601 | 740 | self.clear(); | |
| 602 | 741 | } | |
| 603 | - | _ => self.apc_buf.push(b), | |
| 742 | + | _ => { | |
| 743 | + | if self.apc_buf.len() >= MAX_STRING_BYTES { | |
| 744 | + | self.string_overflow = true; | |
| 745 | + | return; | |
| 746 | + | } | |
| 747 | + | self.apc_buf.push(b); | |
| 748 | + | } | |
| 604 | 749 | } | |
| 605 | 750 | } | |
| 606 | 751 | ||
| 752 | + | /// Opens an APC body. Paired with [`Parser::apc_end`] for the same reason | |
| 753 | + | /// [`Parser::osc_start`] is paired with [`Parser::osc_end`]. | |
| 754 | + | fn apc_start(&mut self) { | |
| 755 | + | self.apc_buf.clear(); | |
| 756 | + | self.string_overflow = false; | |
| 757 | + | } | |
| 758 | + | ||
| 759 | + | fn apc_end(&mut self) { | |
| 760 | + | self.apc_buf.clear(); | |
| 761 | + | self.apc_buf.shrink_to(INITIAL_BODY_CAPACITY); | |
| 762 | + | self.string_overflow = false; | |
| 763 | + | } | |
| 764 | + | ||
| 607 | 765 | /// Is the parser between sequences, holding no partial state? | |
| 608 | 766 | /// | |
| 609 | 767 | /// Ground is the only state in which a stream can be cut without losing | |
| @@ -894,4 +1052,113 @@ | |||
| 894 | 1052 | "dispatch should carry ignore=true, got {events:?}" | |
| 895 | 1053 | ); | |
| 896 | 1054 | } | |
| 1055 | + | ||
| 1056 | + | // ---- Bounded state (the 2026-08-29 DoS finding) -------------------- | |
| 1057 | + | ||
| 1058 | + | /// A CSI with more parameters than the list holds dispatches with the | |
| 1059 | + | /// ignore flag set, exactly as a third intermediate already does, and stops | |
| 1060 | + | /// growing. | |
| 1061 | + | #[test] | |
| 1062 | + | fn csi_past_the_parameter_cap_is_ignored_not_buffered() { | |
| 1063 | + | let mut p = Parser::new(); | |
| 1064 | + | let mut r = Rec::default(); | |
| 1065 | + | let mut seq = b"\x1b[".to_vec(); | |
| 1066 | + | seq.extend(std::iter::repeat_n(b';', 100_000)); | |
| 1067 | + | seq.push(b'm'); | |
| 1068 | + | p.advance(&mut r, &seq); | |
| 1069 | + | ||
| 1070 | + | let event = r.0.last().expect("a dispatch").clone(); | |
| 1071 | + | assert!(event.contains("ignore=true"), "got {event}"); | |
| 1072 | + | assert!( | |
| 1073 | + | p.buffered_bytes() < 8 * 1024, | |
| 1074 | + | "parser kept {} bytes for 100k separators", | |
| 1075 | + | p.buffered_bytes() | |
| 1076 | + | ); | |
| 1077 | + | assert!(p.in_ground()); | |
| 1078 | + | } | |
| 1079 | + | ||
| 1080 | + | /// Parameters up to the cap still arrive, so the cap changes nothing for | |
| 1081 | + | /// anything anyone sends. | |
| 1082 | + | #[test] | |
| 1083 | + | fn csi_at_the_parameter_cap_still_dispatches_every_slot() { | |
| 1084 | + | let params: Vec<String> = (1..=MAX_PARAMS).map(|n| n.to_string()).collect(); | |
| 1085 | + | let events = run(format!("\x1b[{}m", params.join(";")).as_bytes()); | |
| 1086 | + | ||
| 1087 | + | let event = events.last().expect("a dispatch"); | |
| 1088 | + | assert!(event.contains("ignore=false"), "got {event}"); | |
| 1089 | + | assert!(event.contains(&format!("[{MAX_PARAMS}]")), "got {event}"); | |
| 1090 | + | } | |
| 1091 | + | ||
| 1092 | + | /// An over-long OSC body is dropped rather than truncated, and the buffer | |
| 1093 | + | /// it grew goes back down. | |
| 1094 | + | /// | |
| 1095 | + | /// Dropped because half a base64 clipboard write is a different request, | |
| 1096 | + | /// not a smaller one; shrunk because capacity is a high-water mark, and | |
| 1097 | + | /// without that the cap would bound one sequence rather than the process. | |
| 1098 | + | #[test] | |
| 1099 | + | fn an_oversized_osc_body_is_dropped_and_the_buffer_shrinks() { | |
| 1100 | + | let mut p = Parser::new(); | |
| 1101 | + | let mut r = Rec::default(); | |
| 1102 | + | let mut seq = b"\x1b]0;".to_vec(); | |
| 1103 | + | seq.extend(std::iter::repeat_n(b'A', MAX_STRING_BYTES + 1)); | |
| 1104 | + | seq.push(0x07); | |
| 1105 | + | p.advance(&mut r, &seq); | |
| 1106 | + | ||
| 1107 | + | assert!( | |
| 1108 | + | r.0.is_empty(), | |
| 1109 | + | "a body over the cap should dispatch nothing, got {:?}", | |
| 1110 | + | r.0 | |
| 1111 | + | ); | |
| 1112 | + | assert!( | |
| 1113 | + | p.buffered_bytes() < 8 * 1024, | |
| 1114 | + | "buffers stayed at {} bytes after the body ended", | |
| 1115 | + | p.buffered_bytes() | |
| 1116 | + | ); | |
| 1117 | + | assert!(p.in_ground()); | |
| 1118 | + | } | |
| 1119 | + | ||
| 1120 | + | /// A body under the cap is unaffected, including one large enough to have | |
| 1121 | + | /// grown the buffer well past its resting size. | |
| 1122 | + | #[test] | |
| 1123 | + | fn an_ordinary_osc_body_still_dispatches() { | |
| 1124 | + | let title = "t".repeat(100_000); | |
| 1125 | + | let events = run(format!("\x1b]0;{title}\x07").as_bytes()); | |
| 1126 | + | assert_eq!(events.len(), 1, "got {events:?}"); | |
| 1127 | + | assert!(events[0].starts_with("osc("), "got {events:?}"); | |
| 1128 | + | } | |
| 1129 | + | ||
| 1130 | + | /// The OSC field table is bounded on its own account, because a separator | |
| 1131 | + | /// costs an index pair and contributes no body byte to charge it against. | |
| 1132 | + | #[test] | |
| 1133 | + | fn osc_separators_alone_do_not_grow_the_field_table() { | |
| 1134 | + | let mut p = Parser::new(); | |
| 1135 | + | let mut r = Rec::default(); | |
| 1136 | + | let mut seq = b"\x1b]".to_vec(); | |
| 1137 | + | seq.extend(std::iter::repeat_n(b';', 100_000)); | |
| 1138 | + | seq.push(0x07); | |
| 1139 | + | p.advance(&mut r, &seq); | |
| 1140 | + | ||
| 1141 | + | assert!(r.0.is_empty(), "got {:?}", r.0); | |
| 1142 | + | assert!( | |
| 1143 | + | p.buffered_bytes() < 64 * 1024, | |
| 1144 | + | "parser kept {} bytes for 100k separators", | |
| 1145 | + | p.buffered_bytes() | |
| 1146 | + | ); | |
| 1147 | + | } | |
| 1148 | + | ||
| 1149 | + | /// The APC body has the same ceiling. This is the one the kitty graphics | |
| 1150 | + | /// protocol arrives through, and the protocol requires payloads over 4096 | |
| 1151 | + | /// bytes to be chunked, so nothing legitimate comes near it. | |
| 1152 | + | #[test] | |
| 1153 | + | fn an_oversized_apc_body_is_dropped() { | |
| 1154 | + | let mut p = Parser::new(); | |
| 1155 | + | let mut r = Rec::default(); | |
| 1156 | + | let mut seq = b"\x1b_G".to_vec(); | |
| 1157 | + | seq.extend(std::iter::repeat_n(b'A', MAX_STRING_BYTES + 1)); | |
| 1158 | + | seq.extend_from_slice(b"\x1b\\"); | |
| 1159 | + | p.advance(&mut r, &seq); | |
| 1160 | + | ||
| 1161 | + | assert!(!r.0.iter().any(|e| e.starts_with("apc(")), "got {:?}", r.0); | |
| 1162 | + | assert!(p.buffered_bytes() < 8 * 1024); | |
| 1163 | + | } | |
| 897 | 1164 | } |
| @@ -1,0 +1,1 @@ | |||
| 1 | + | O_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
| @@ -1,0 +1,1 @@ | |||
| 1 | + | O[;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
| @@ -1,0 +1,1 @@ | |||
| 1 | + | O]AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |