Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(276)

Side by Side Diff: net/spdy/hpack_header_table_test.cc

Issue 448433002: Update HPACK implementation to draft-09 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Modify HandleControlFrameHeadersComplete test. Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « net/spdy/hpack_header_table.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "net/spdy/hpack_header_table.h" 5 #include "net/spdy/hpack_header_table.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <set> 8 #include <set>
9 #include <string> 9 #include <string>
10 #include <vector> 10 #include <vector>
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after
124 HpackHeaderTable::EntryTable::iterator begin, end; 124 HpackHeaderTable::EntryTable::iterator begin, end;
125 125
126 table_.EvictionSet(it->name(), it->value(), &begin, &end); 126 table_.EvictionSet(it->name(), it->value(), &begin, &end);
127 EXPECT_EQ(0, distance(begin, end)); 127 EXPECT_EQ(0, distance(begin, end));
128 128
129 HpackEntry* entry = table_.TryAddEntry(it->name(), it->value()); 129 HpackEntry* entry = table_.TryAddEntry(it->name(), it->value());
130 EXPECT_NE(entry, static_cast<HpackEntry*>(NULL)); 130 EXPECT_NE(entry, static_cast<HpackEntry*>(NULL));
131 } 131 }
132 132
133 for (size_t i = 0; i != entries.size(); ++i) { 133 for (size_t i = 0; i != entries.size(); ++i) {
134 size_t index = entries.size() - i; 134 size_t index = 61 + entries.size() - i;
135 HpackEntry* entry = table_.GetByIndex(index); 135 HpackEntry* entry = table_.GetByIndex(index);
136 EXPECT_EQ(entries[i].name(), entry->name()); 136 EXPECT_EQ(entries[i].name(), entry->name());
137 EXPECT_EQ(entries[i].value(), entry->value()); 137 EXPECT_EQ(entries[i].value(), entry->value());
138 EXPECT_EQ(index, table_.IndexOf(entry)); 138 EXPECT_EQ(index, table_.IndexOf(entry));
139 } 139 }
140 } 140 }
141 141
142 HpackEntry StaticEntry() { 142 HpackEntry StaticEntry() {
143 peer_.AddStaticEntry(name_, value_); 143 peer_.AddStaticEntry(name_, value_);
144 return peer_.static_entries().back(); 144 return peer_.static_entries().back();
145 } 145 }
146 HpackEntry DynamicEntry() { 146 HpackEntry DynamicEntry() {
147 peer_.AddDynamicEntry(name_, value_); 147 peer_.AddDynamicEntry(name_, value_);
148 return peer_.dynamic_entries().back(); 148 return peer_.dynamic_entries().back();
149 } 149 }
150 150
151 HpackHeaderTable table_; 151 HpackHeaderTable table_;
152 test::HpackHeaderTablePeer peer_; 152 test::HpackHeaderTablePeer peer_;
153 string name_, value_; 153 string name_, value_;
154 }; 154 };
155 155
156 TEST_F(HpackHeaderTableTest, StaticTableInitialization) { 156 TEST_F(HpackHeaderTableTest, StaticTableInitialization) {
157 EXPECT_EQ(0u, table_.size()); 157 EXPECT_EQ(0u, table_.size());
158 EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.max_size()); 158 EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.max_size());
159 EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.settings_size_bound()); 159 EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.settings_size_bound());
160 160
161 EXPECT_EQ(0u, peer_.dynamic_entries_count()); 161 EXPECT_EQ(0u, peer_.dynamic_entries_count());
162 EXPECT_EQ(0u, table_.reference_set().size());
163 EXPECT_EQ(peer_.static_entries().size(), peer_.total_insertions()); 162 EXPECT_EQ(peer_.static_entries().size(), peer_.total_insertions());
164 163
165 // Static entries have been populated and inserted into the table & index. 164 // Static entries have been populated and inserted into the table & index.
166 EXPECT_NE(0u, peer_.static_entries().size()); 165 EXPECT_NE(0u, peer_.static_entries().size());
167 EXPECT_EQ(peer_.index().size(), peer_.static_entries().size()); 166 EXPECT_EQ(peer_.index().size(), peer_.static_entries().size());
168 for (size_t i = 0; i != peer_.static_entries().size(); ++i) { 167 for (size_t i = 0; i != peer_.static_entries().size(); ++i) {
169 const HpackEntry* entry = &peer_.static_entries()[i]; 168 const HpackEntry* entry = &peer_.static_entries()[i];
170 169
171 EXPECT_TRUE(entry->IsStatic()); 170 EXPECT_TRUE(entry->IsStatic());
172 EXPECT_EQ(entry, table_.GetByIndex(i + 1)); 171 EXPECT_EQ(entry, table_.GetByIndex(i + 1));
(...skipping 13 matching lines...) Expand all
186 EXPECT_FALSE(entry->IsStatic()); 185 EXPECT_FALSE(entry->IsStatic());
187 186
188 // Table counts were updated appropriately. 187 // Table counts were updated appropriately.
189 EXPECT_EQ(entry->Size(), table_.size()); 188 EXPECT_EQ(entry->Size(), table_.size());
190 EXPECT_EQ(1u, peer_.dynamic_entries_count()); 189 EXPECT_EQ(1u, peer_.dynamic_entries_count());
191 EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count()); 190 EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count());
192 EXPECT_EQ(static_count + 1, peer_.total_insertions()); 191 EXPECT_EQ(static_count + 1, peer_.total_insertions());
193 EXPECT_EQ(static_count + 1, peer_.index().size()); 192 EXPECT_EQ(static_count + 1, peer_.index().size());
194 193
195 // Index() of entries reflects the insertion. 194 // Index() of entries reflects the insertion.
196 EXPECT_EQ(1u, table_.IndexOf(entry)); 195 EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
197 EXPECT_EQ(2u, table_.IndexOf(first_static_entry)); 196 // Static table has 61 entries.
198 EXPECT_EQ(entry, table_.GetByIndex(1)); 197 EXPECT_EQ(62u, table_.IndexOf(entry));
199 EXPECT_EQ(first_static_entry, table_.GetByIndex(2)); 198 EXPECT_EQ(first_static_entry, table_.GetByIndex(1));
199 EXPECT_EQ(entry, table_.GetByIndex(62));
200 200
201 // Evict |entry|. Table counts are again updated appropriately. 201 // Evict |entry|. Table counts are again updated appropriately.
202 peer_.Evict(1); 202 peer_.Evict(1);
203 EXPECT_EQ(0u, table_.size()); 203 EXPECT_EQ(0u, table_.size());
204 EXPECT_EQ(0u, peer_.dynamic_entries_count()); 204 EXPECT_EQ(0u, peer_.dynamic_entries_count());
205 EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count()); 205 EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count());
206 EXPECT_EQ(static_count + 1, peer_.total_insertions()); 206 EXPECT_EQ(static_count + 1, peer_.total_insertions());
207 EXPECT_EQ(static_count, peer_.index().size()); 207 EXPECT_EQ(static_count, peer_.index().size());
208 208
209 // Index() of |first_static_entry| reflects the eviction. 209 // Index() of |first_static_entry| reflects the eviction.
(...skipping 15 matching lines...) Expand all
225 first_static_entry->value()); 225 first_static_entry->value());
226 HpackEntry* entry2 = table_.TryAddEntry(first_static_entry->name(), 226 HpackEntry* entry2 = table_.TryAddEntry(first_static_entry->name(),
227 "Value Four"); 227 "Value Four");
228 HpackEntry* entry3 = table_.TryAddEntry("key-1", "Value One"); 228 HpackEntry* entry3 = table_.TryAddEntry("key-1", "Value One");
229 HpackEntry* entry4 = table_.TryAddEntry("key-2", "Value Three"); 229 HpackEntry* entry4 = table_.TryAddEntry("key-2", "Value Three");
230 HpackEntry* entry5 = table_.TryAddEntry("key-1", "Value Two"); 230 HpackEntry* entry5 = table_.TryAddEntry("key-1", "Value Two");
231 HpackEntry* entry6 = table_.TryAddEntry("key-2", "Value Three"); 231 HpackEntry* entry6 = table_.TryAddEntry("key-2", "Value Three");
232 HpackEntry* entry7 = table_.TryAddEntry("key-2", "Value Four"); 232 HpackEntry* entry7 = table_.TryAddEntry("key-2", "Value Four");
233 233
234 // Entries are queryable under their current index. 234 // Entries are queryable under their current index.
235 EXPECT_EQ(entry7, table_.GetByIndex(1)); 235 EXPECT_EQ(entry7, table_.GetByIndex(62));
236 EXPECT_EQ(entry6, table_.GetByIndex(2)); 236 EXPECT_EQ(entry6, table_.GetByIndex(63));
237 EXPECT_EQ(entry5, table_.GetByIndex(3)); 237 EXPECT_EQ(entry5, table_.GetByIndex(64));
238 EXPECT_EQ(entry4, table_.GetByIndex(4)); 238 EXPECT_EQ(entry4, table_.GetByIndex(65));
239 EXPECT_EQ(entry3, table_.GetByIndex(5)); 239 EXPECT_EQ(entry3, table_.GetByIndex(66));
240 EXPECT_EQ(entry2, table_.GetByIndex(6)); 240 EXPECT_EQ(entry2, table_.GetByIndex(67));
241 EXPECT_EQ(entry1, table_.GetByIndex(7)); 241 EXPECT_EQ(entry1, table_.GetByIndex(68));
242 EXPECT_EQ(first_static_entry, table_.GetByIndex(8)); 242 EXPECT_EQ(first_static_entry, table_.GetByIndex(1));
243 243
244 // Querying by name returns the lowest-value matching entry. 244 // Querying by name returns the lowest-value matching entry.
245 EXPECT_EQ(entry3, table_.GetByName("key-1")); 245 EXPECT_EQ(entry3, table_.GetByName("key-1"));
246 EXPECT_EQ(entry7, table_.GetByName("key-2")); 246 EXPECT_EQ(entry7, table_.GetByName("key-2"));
247 EXPECT_EQ(entry2->name(), 247 EXPECT_EQ(entry2->name(),
248 table_.GetByName(first_static_entry->name())->name()); 248 table_.GetByName(first_static_entry->name())->name());
249 EXPECT_EQ(NULL, table_.GetByName("not-present")); 249 EXPECT_EQ(NULL, table_.GetByName("not-present"));
250 250
251 // Querying by name & value returns the lowest-index matching entry. 251 // Querying by name & value returns the lowest-index matching entry.
252 EXPECT_EQ(entry3, table_.GetByNameAndValue("key-1", "Value One")); 252 EXPECT_EQ(entry3, table_.GetByNameAndValue("key-1", "Value One"));
253 EXPECT_EQ(entry5, table_.GetByNameAndValue("key-1", "Value Two")); 253 EXPECT_EQ(entry5, table_.GetByNameAndValue("key-1", "Value Two"));
254 EXPECT_EQ(entry6, table_.GetByNameAndValue("key-2", "Value Three")); 254 EXPECT_EQ(entry6, table_.GetByNameAndValue("key-2", "Value Three"));
255 EXPECT_EQ(entry7, table_.GetByNameAndValue("key-2", "Value Four")); 255 EXPECT_EQ(entry7, table_.GetByNameAndValue("key-2", "Value Four"));
256 EXPECT_EQ(entry1, table_.GetByNameAndValue(first_static_entry->name(), 256 EXPECT_EQ(first_static_entry,
257 first_static_entry->value())); 257 table_.GetByNameAndValue(first_static_entry->name(),
258 first_static_entry->value()));
258 EXPECT_EQ(entry2, table_.GetByNameAndValue(first_static_entry->name(), 259 EXPECT_EQ(entry2, table_.GetByNameAndValue(first_static_entry->name(),
259 "Value Four")); 260 "Value Four"));
260 EXPECT_EQ(NULL, table_.GetByNameAndValue("key-1", "Not Present")); 261 EXPECT_EQ(NULL, table_.GetByNameAndValue("key-1", "Not Present"));
261 EXPECT_EQ(NULL, table_.GetByNameAndValue("not-present", "Value One")); 262 EXPECT_EQ(NULL, table_.GetByNameAndValue("not-present", "Value One"));
262 263
263 // Evict |entry1|. Queries for its name & value now return the static entry. 264 // Evict |entry1|. Queries for its name & value now return the static entry.
264 // |entry2| remains queryable. 265 // |entry2| remains queryable.
265 peer_.Evict(1); 266 peer_.Evict(1);
266 EXPECT_EQ(first_static_entry, 267 EXPECT_EQ(first_static_entry,
267 table_.GetByNameAndValue(first_static_entry->name(), 268 table_.GetByNameAndValue(first_static_entry->name(),
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
302 303
303 // SETTINGS_HEADER_TABLE_SIZE upper-bounds |table_.max_size()|, 304 // SETTINGS_HEADER_TABLE_SIZE upper-bounds |table_.max_size()|,
304 // and will force evictions. 305 // and will force evictions.
305 max_size = entry3->Size() - 1; 306 max_size = entry3->Size() - 1;
306 table_.SetSettingsHeaderTableSize(max_size); 307 table_.SetSettingsHeaderTableSize(max_size);
307 EXPECT_EQ(max_size, table_.max_size()); 308 EXPECT_EQ(max_size, table_.max_size());
308 EXPECT_EQ(max_size, table_.settings_size_bound()); 309 EXPECT_EQ(max_size, table_.settings_size_bound());
309 EXPECT_EQ(0u, peer_.dynamic_entries().size()); 310 EXPECT_EQ(0u, peer_.dynamic_entries().size());
310 } 311 }
311 312
312 TEST_F(HpackHeaderTableTest, ToggleReferenceSet) {
313 HpackEntry* entry1 = table_.TryAddEntry("key-1", "Value One");
314 HpackEntry* entry2 = table_.TryAddEntry("key-2", "Value Two");
315
316 // Entries must be explictly toggled after creation.
317 EXPECT_EQ(0u, table_.reference_set().size());
318
319 // Add |entry1|.
320 EXPECT_TRUE(table_.Toggle(entry1));
321 EXPECT_EQ(1u, table_.reference_set().size());
322 EXPECT_EQ(1u, table_.reference_set().count(entry1));
323 EXPECT_EQ(0u, table_.reference_set().count(entry2));
324
325 // Add |entry2|.
326 EXPECT_TRUE(table_.Toggle(entry2));
327 EXPECT_EQ(2u, table_.reference_set().size());
328 EXPECT_EQ(1u, table_.reference_set().count(entry1));
329 EXPECT_EQ(1u, table_.reference_set().count(entry2));
330
331 // Remove |entry2|.
332 EXPECT_FALSE(table_.Toggle(entry2));
333 EXPECT_EQ(1u, table_.reference_set().size());
334 EXPECT_EQ(0u, table_.reference_set().count(entry2));
335
336 // Evict |entry1|. Implicit removal from reference set.
337 peer_.Evict(1);
338 EXPECT_EQ(0u, table_.reference_set().size());
339 }
340
341 TEST_F(HpackHeaderTableTest, ClearReferenceSet) {
342 HpackEntry* entry1 = table_.TryAddEntry("key-1", "Value One");
343 EXPECT_TRUE(table_.Toggle(entry1));
344 entry1->set_state(123);
345
346 // |entry1| state is cleared, and removed from the reference set.
347 table_.ClearReferenceSet();
348 EXPECT_EQ(0u, entry1->state());
349 EXPECT_EQ(0u, table_.reference_set().size());
350 }
351
352 TEST_F(HpackHeaderTableTest, EvictionCountForEntry) { 313 TEST_F(HpackHeaderTableTest, EvictionCountForEntry) {
353 string key = "key", value = "value"; 314 string key = "key", value = "value";
354 HpackEntry* entry1 = table_.TryAddEntry(key, value); 315 HpackEntry* entry1 = table_.TryAddEntry(key, value);
355 HpackEntry* entry2 = table_.TryAddEntry(key, value); 316 HpackEntry* entry2 = table_.TryAddEntry(key, value);
356 size_t entry3_size = HpackEntry::Size(key, value); 317 size_t entry3_size = HpackEntry::Size(key, value);
357 318
358 // Just enough capacity for third entry. 319 // Just enough capacity for third entry.
359 table_.SetMaxSize(entry1->Size() + entry2->Size() + entry3_size); 320 table_.SetMaxSize(entry1->Size() + entry2->Size() + entry3_size);
360 EXPECT_EQ(0u, peer_.EvictionCountForEntry(key, value)); 321 EXPECT_EQ(0u, peer_.EvictionCountForEntry(key, value));
361 EXPECT_EQ(1u, peer_.EvictionCountForEntry(key, value + "x")); 322 EXPECT_EQ(1u, peer_.EvictionCountForEntry(key, value + "x"));
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
418 } 379 }
419 380
420 // Fill a header table with entries, and then add an entry just big 381 // Fill a header table with entries, and then add an entry just big
421 // enough to cause eviction of all but one entry. Make sure the 382 // enough to cause eviction of all but one entry. Make sure the
422 // eviction happens as expected and the long entry is inserted into 383 // eviction happens as expected and the long entry is inserted into
423 // the table. 384 // the table.
424 TEST_F(HpackHeaderTableTest, TryAddEntryEviction) { 385 TEST_F(HpackHeaderTableTest, TryAddEntryEviction) {
425 HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size()); 386 HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
426 AddEntriesExpectNoEviction(entries); 387 AddEntriesExpectNoEviction(entries);
427 388
428 HpackEntry* survivor_entry = table_.GetByIndex(1); 389 HpackEntry* survivor_entry = table_.GetByIndex(61 + 1);
429 HpackEntry long_entry = 390 HpackEntry long_entry =
430 MakeEntryOfSize(table_.max_size() - survivor_entry->Size()); 391 MakeEntryOfSize(table_.max_size() - survivor_entry->Size());
431 392
432 // All entries but the first are to be evicted. 393 // All dynamic entries but the first are to be evicted.
433 EXPECT_EQ(peer_.dynamic_entries().size() - 1, peer_.EvictionSet( 394 EXPECT_EQ(peer_.dynamic_entries().size() - 1, peer_.EvictionSet(
434 long_entry.name(), long_entry.value()).size()); 395 long_entry.name(), long_entry.value()).size());
435 396
436 HpackEntry* new_entry = table_.TryAddEntry(long_entry.name(), 397 HpackEntry* new_entry = table_.TryAddEntry(long_entry.name(),
437 long_entry.value()); 398 long_entry.value());
438 EXPECT_EQ(1u, table_.IndexOf(new_entry)); 399 EXPECT_EQ(62u, table_.IndexOf(new_entry));
439 EXPECT_EQ(2u, peer_.dynamic_entries().size()); 400 EXPECT_EQ(2u, peer_.dynamic_entries().size());
440 EXPECT_EQ(table_.GetByIndex(2), survivor_entry); 401 EXPECT_EQ(table_.GetByIndex(63), survivor_entry);
441 EXPECT_EQ(table_.GetByIndex(1), new_entry); 402 EXPECT_EQ(table_.GetByIndex(62), new_entry);
442 } 403 }
443 404
444 // Fill a header table with entries, and then add an entry bigger than 405 // Fill a header table with entries, and then add an entry bigger than
445 // the entire table. Make sure no entry remains in the table. 406 // the entire table. Make sure no entry remains in the table.
446 TEST_F(HpackHeaderTableTest, TryAddTooLargeEntry) { 407 TEST_F(HpackHeaderTableTest, TryAddTooLargeEntry) {
447 HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size()); 408 HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
448 AddEntriesExpectNoEviction(entries); 409 AddEntriesExpectNoEviction(entries);
449 410
450 HpackEntry long_entry = MakeEntryOfSize(table_.max_size() + 1); 411 HpackEntry long_entry = MakeEntryOfSize(table_.max_size() + 1);
451 412
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
487 EXPECT_TRUE(comparator(&entry1, &entry2)); 448 EXPECT_TRUE(comparator(&entry1, &entry2));
488 EXPECT_FALSE(comparator(&entry2, &entry1)); 449 EXPECT_FALSE(comparator(&entry2, &entry1));
489 450
490 HpackEntry entry3(DynamicEntry()); 451 HpackEntry entry3(DynamicEntry());
491 HpackEntry entry4(DynamicEntry()); 452 HpackEntry entry4(DynamicEntry());
492 453
493 // |entry4| has lower index than |entry3|. 454 // |entry4| has lower index than |entry3|.
494 EXPECT_TRUE(comparator(&entry4, &entry3)); 455 EXPECT_TRUE(comparator(&entry4, &entry3));
495 EXPECT_FALSE(comparator(&entry3, &entry4)); 456 EXPECT_FALSE(comparator(&entry3, &entry4));
496 457
497 // |entry3| has lower index than |entry1|. 458 // |entry1| has lower index than |entry3|.
498 EXPECT_TRUE(comparator(&entry3, &entry1)); 459 EXPECT_TRUE(comparator(&entry1, &entry3));
499 EXPECT_FALSE(comparator(&entry1, &entry3)); 460 EXPECT_FALSE(comparator(&entry3, &entry1));
500 461
501 // |entry1| & |entry2| ordering is preserved, though each Index() has changed. 462 // |entry1| & |entry2| ordering is preserved, though each Index() has changed.
502 EXPECT_TRUE(comparator(&entry1, &entry2)); 463 EXPECT_TRUE(comparator(&entry1, &entry2));
503 EXPECT_FALSE(comparator(&entry2, &entry1)); 464 EXPECT_FALSE(comparator(&entry2, &entry1));
504 } 465 }
505 466
506 TEST_F(HpackHeaderTableTest, ComparatorEqualityOrdering) { 467 TEST_F(HpackHeaderTableTest, ComparatorEqualityOrdering) {
507 HpackEntry entry1(StaticEntry()); 468 HpackEntry entry1(StaticEntry());
508 HpackEntry entry2(DynamicEntry()); 469 HpackEntry entry2(DynamicEntry());
509 470
510 HpackHeaderTable::EntryComparator comparator(&table_); 471 HpackHeaderTable::EntryComparator comparator(&table_);
511 EXPECT_FALSE(comparator(&entry1, &entry1)); 472 EXPECT_FALSE(comparator(&entry1, &entry1));
512 EXPECT_FALSE(comparator(&entry2, &entry2)); 473 EXPECT_FALSE(comparator(&entry2, &entry2));
513 } 474 }
514 475
515 } // namespace 476 } // namespace
516 477
517 } // namespace net 478 } // namespace net
OLDNEW
« no previous file with comments | « net/spdy/hpack_header_table.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698