1 // Copyright 2015 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 #ifndef PIEX_BINARY_PARSE_RANGE_CHECKED_BYTE_PTR_H_ 18 #define PIEX_BINARY_PARSE_RANGE_CHECKED_BYTE_PTR_H_ 19 20 #include <assert.h> 21 22 #include <cstddef> 23 #include <memory> 24 #include <string> 25 #include <vector> 26 27 namespace piex { 28 namespace binary_parse { 29 30 // Since NaCl does not comply to C++11 we can not just use stdint.h. 31 typedef unsigned short uint16; // NOLINT 32 typedef short int16; // NOLINT 33 typedef unsigned int uint32; 34 typedef int int32; 35 36 enum MemoryStatus { 37 RANGE_CHECKED_BYTE_SUCCESS = 0, 38 RANGE_CHECKED_BYTE_ERROR = 1, 39 RANGE_CHECKED_BYTE_ERROR_OVERFLOW = 2, 40 RANGE_CHECKED_BYTE_ERROR_UNDERFLOW = 3, 41 }; 42 43 // Interface that RangeCheckedBytePtr uses to access the underlying array of 44 // bytes. This allows RangeCheckedBytePtr to be used to access data as if it 45 // were stored contiguously in memory, even if the data is in fact split up 46 // into non-contiguous chunks and / or does not reside in memory. 47 // 48 // The only requirement is that the data can be read in pages of a fixed (but 49 // configurable) size. Notionally, the byte array (which contains length() 50 // bytes) is split up into non-overlapping pages of pageSize() bytes each. 51 // (The last page may be shorter if length() is not a multiple of pageSize().) 52 // There are therefore (length() - 1) / pageSize() + 1 such pages, with indexes 53 // 0 through (length() - 1) / pageSize(). Page i contains the bytes from offset 54 // i * pageSize() in the array up to and including the byte at offset 55 // (i + 1) * pageSize() - 1 (or, in the case of the last page, length() - 1). 56 // 57 // In essence, RangeCheckedBytePtr and PagedByteArray together provide a poor 58 // man's virtual-memory-and-memory-mapped-file work-alike in situations where 59 // virtual memory cannot be used or would consume too much virtual address 60 // space. 61 // 62 // Thread safety: In general, subclasses implementing this interface should 63 // ensure that the member functions are thread-safe. It will then be safe to 64 // access the same array from multiple threads. (Note that RangeCheckedBytePtr 65 // itself is not thread-safe in the sense that a single instance of 66 // RangeCheckedBytePtr cannot be used concurrently from multiple threads; it 67 // is, however, safe to use different RangeCheckedBytePtr instances in 68 // different threads to access the same PagedByteArray concurrently, assuming 69 // that the PagedByteArray implementation is thread-safe.) 70 class PagedByteArray { 71 public: 72 // Base class for pages in the byte array. Implementations of PagedByteArray 73 // can create a subclass of the Page class to manage the lifetime of buffers 74 // associated with a page returned by getPage(). For example, a 75 // PagedByteArray backed by a file might define a Page subclass like this: 76 // 77 // class FilePage : public Page { 78 // std::vector<unsigned char> bytes; 79 // }; 80 // 81 // The corresponding getPage() implementation could then look like this: 82 // 83 // void getPage(size_t page_index, const unsigned char** begin, 84 // const unsigned char** end, std::shared_ptr<Page>* page) 85 // { 86 // // Create a new page. 87 // std::shared_ptr<FilePage> file_page(new FilePage()); 88 // 89 // // Read contents of page from file into file_page->bytes. 90 // [...] 91 // 92 // // Set *begin and *end to point to beginning and end of 93 // // file_page->bytes vector. 94 // *begin = &file_page->bytes[0]; 95 // *end = *begin + file_page->bytes.size(); 96 // 97 // // Return page to caller 98 // *page = file_page; 99 // } 100 // 101 // In this way, the storage associated with the page (the FilePage::bytes 102 // vector) will be kept alive until the RangeCheckedBytePtr releases the 103 // shared pointer. 104 class Page {}; 105 106 typedef std::shared_ptr<Page> PagePtr; 107 108 virtual ~PagedByteArray(); 109 110 // Returns the length of the array in bytes. The value returned must remain 111 // the same on every call for the entire lifetime of the object. 112 virtual size_t length() const = 0; 113 114 // Returns the length of each page in bytes. (The last page may be shorter 115 // than pageSize() if length() is not a multiple of pageSize() -- see also 116 // the class-wide comment above.) The value returned must remain the same on 117 // every call for the entire lifetime of the object. 118 virtual size_t pageSize() const = 0; 119 120 // Returns a pointer to a memory buffer containing the data for the page 121 // with index "page_index". 122 // 123 // *begin is set to point to the first byte of the page; *end is set to point 124 // one byte beyond the last byte in the page. This implies that: 125 // - (*end - *begin) == pageSize() for every page except the last page 126 // - (*end - *begin) == length() - pageSize() * ((length() - 1) / pageSize()) 127 // for the last page 128 // 129 // *page will be set to a SharedPtr that the caller will hold on to until 130 // it no longer needs to access the memory buffer. The memory buffer will 131 // remain valid until the SharedPtr is released or the PagedByteArray object 132 // is destroyed. An implementation may choose to return a null SharedPtr; 133 // this indicates that the memory buffer will remain valid until the 134 // PagedByteArray object is destroyed, even if the caller does not hold on to 135 // the SharedPtr. (This is intended as an optimization that some 136 // implementations may choose to take advantage of, as a null SharedPtr is 137 // cheaper to copy.) 138 virtual void getPage(size_t page_index, const unsigned char **begin, 139 const unsigned char **end, PagePtr *page) const = 0; 140 }; 141 142 typedef std::shared_ptr<PagedByteArray> PagedByteArrayPtr; 143 144 // Smart pointer that has the same semantics as a "const unsigned char *" (plus 145 // some convenience functions) but provides range checking and the ability to 146 // access arrays that are not contiguous in memory or do not reside entirely in 147 // memory (through the PagedByteArray interface). 148 // 149 // In the following, we abbreviate RangeCheckedBytePtr as RCBP. 150 // 151 // The intent of this class is to allow easy security hardening of code that 152 // parses binary data structures using raw byte pointers. To do this, only the 153 // declarations of the pointers need to be changed; the code that uses the 154 // pointers can remain unchanged. 155 // 156 // If an illegal operation occurs on a pointer, an error flag is set, and all 157 // read operations from this point on return 0. This means that error checking 158 // need not be done after every access; it is sufficient to check the error flag 159 // (using errorOccurred()) once before the RCBP is destroyed. Again, this allows 160 // the majority of the parsing code to remain unchanged. (Note caveats below 161 // that apply if a copy of the pointer is created.) 162 // 163 // Legal operations are exactly the ones that would be legal on a raw C++ 164 // pointer. Read accesses are legal if they fall within the underlying array. A 165 // RCBP may point to any element in the underlying array or one element beyond 166 // the end of the array. 167 // 168 // For brevity, the documentation for individual member functions does not state 169 // explicitly that the error flag will be set on out-of-range operations. 170 // 171 // Note: 172 // 173 // - Just as for raw pointers, it is legal for a pointer to point one element 174 // beyond the end of the array, but it is illegal to use operator*() on such a 175 // pointer. 176 // 177 // - If a copy of an RCBP is created, then performing illegal operations on the 178 // copy affects the error flag of the copy, but not of the original pointer. 179 // Note that using operator+ and operator- also creates a copy of the pointer. 180 // For example: 181 // 182 // // Assume we have an RCBP called "p" and a size_t variable called 183 // // "offset". 184 // RangeCheckedBytePtr sub_data_structure = p + offset; 185 // 186 // If "offset" is large enough to cause an out-of-range access, then 187 // sub_data_structure.errorOccurred() will be true, but p.errorOccurred() will 188 // still be false. The error flag for sub_data_structure therefore needs to be 189 // checked before it is destroyed. 190 class RangeCheckedBytePtr { 191 private: 192 // This class maintains the following class invariants: 193 // - page_data_ always points to a buffer of at least current_page_len_ 194 // bytes. 195 // 196 // - The current position lies within the sub-array, i.e. 197 // sub_array_begin_ <= current_pos_ <= sub_array_end_ 198 // 199 // - The sub-array is entirely contained within the array, i.e. 200 // 0 <= sub_array_begin <= sub_array_end <= array_->length() 201 // 202 // - If the current page is non-empty, it lies completely within the 203 // sub-array, i.e. 204 // if _current_page_len_ >= 0, then 205 // sub_array_begin_ <= page_begin_offset_ 206 // and 207 // page_begin_offset_ + current_page_len_ <= sub_array_end_ 208 // (See also restrictPageToSubArray().) 209 // (If _current_page_len_ == 0, then page_begin_offset_ may lie outside 210 // the sub-array; this condition is harmless. Additional logic would be 211 // required to make page_begin_offset_ lie within the sub-array in this 212 // case, and it would serve no purpose other than to make the invariant 213 // slightly simpler.) 214 // 215 // Note that it is _not_ a class invariant that current_pos_ needs to lie 216 // within the current page. Making this an invariant would have two 217 // undesirable consequences: 218 // a) When operator[] is called with an index that lies beyond the end of 219 // the current page, it would need to temporarily load the page that 220 // contains this index, but it wouldn't be able to "retain" the page 221 // (i.e. make it the current page) because that would violate the 222 // proposed invariant. This would lead to inefficient behavior in the 223 // case where code accesses a large range of indices beyond the end of 224 // the page because a page would need to be loaded temporarily on each 225 // access. 226 // b) It would require more code: loadPageForOffset() would need to be 227 // called anywhere that current_pos_ changes (whereas, with the present 228 // approach, loadPageForOffset() is only called in operator[]). 229 230 // PagedByteArray that is accessed by this pointer. 231 PagedByteArrayPtr array_; 232 233 // Pointer to the current page. 234 mutable PagedByteArray::PagePtr page_; 235 236 // Pointer to the current page's data buffer. 237 mutable const unsigned char *page_data_; 238 239 // All of the following offsets are defined relative to the beginning of 240 // the array defined by the PagedByteArray array_. 241 242 // Array offset that the pointer points to. 243 size_t current_pos_; 244 245 // Start offset of the current sub-array. 246 size_t sub_array_begin_; 247 248 // End offset of the current sub-array. 249 size_t sub_array_end_; 250 251 // Array offset corresponding to the "page_data_" pointer. 252 mutable size_t page_begin_offset_; 253 254 // Length of the current page. 255 mutable size_t current_page_len_; 256 257 // Error flag. This is mutable because methods that don't affect the value 258 // of the pointer itself (such as operator[]) nevertheless need to be able to 259 // signal error conditions. 260 mutable MemoryStatus error_flag_; 261 262 RangeCheckedBytePtr(); 263 264 public: 265 // Creates a pointer that points to the first element of 'array', which has a 266 // length of 'len'. The caller must ensure that the array remains valid until 267 // this pointer and any pointers created from it have been destroyed. 268 // Note: 'len' may be zero, but 'array' must in this case still be a valid, 269 // non-null pointer. 270 explicit RangeCheckedBytePtr(const unsigned char *array, const size_t len); 271 272 // Creates a pointer that points to the first element of the given 273 // PagedByteArray. The caller must ensure that this PagedByteArray remains 274 // valid until this pointer and any pointers created from it have been 275 // destroyed. 276 explicit RangeCheckedBytePtr(PagedByteArray *array); 277 278 // Creates an invalid RangeCheckedBytePtr. Calling errorOccurred() on the 279 // result of invalidPointer() always returns true. 280 // Do not check a RangeCheckedBytePtr for validity by comparing against 281 // invalidPointer(); use errorOccurred() instead. 282 static RangeCheckedBytePtr invalidPointer(); 283 284 // Returns a RangeCheckedBytePtr that points to a sub-array of this pointer's 285 // underlying array. The sub-array starts at position 'pos' relative to this 286 // pointer and is 'length' bytes long. The sub-array must lie within this 287 // pointer's array, i.e. pos + length <= remainingLength() must hold. If this 288 // condition is violated, an invalid pointer is returned. 289 RangeCheckedBytePtr pointerToSubArray(size_t pos, size_t length) const; 290 291 // Returns the number of bytes remaining in the array from this pointer's 292 // present position. 293 inline size_t remainingLength() const; 294 295 // Returns the offset (or index) in the underlying array that this pointer 296 // points to. If this pointer was created using pointerToSubArray(), the 297 // offset is relative to the beginning of the sub-array (and not relative to 298 // the beginning of the original array). 299 size_t offsetInArray() const; 300 301 // Returns whether an out-of-bounds error has ever occurred on this pointer in 302 // the past. An error occurs if a caller attempts to read from a position 303 // outside the bounds of the array or to move the pointer outside the bounds 304 // of the array. 305 // 306 // The error flag is never reset. Once an error has occurred, 307 // all subsequent attempts to read from the pointer (even within the bounds of 308 // the array) return 0. 309 // 310 // Note that it is permissible for a pointer to point one element past the end 311 // of the array, but it is not permissible to read from this position. This is 312 // equivalent to the semantics of raw C++ pointers. 313 inline bool errorOccurred() const; 314 315 // Returns the substring of length 'length' located at position 'pos' relative 316 // to this pointer. 317 std::string substr(size_t pos, size_t length) const; 318 319 // Returns 'length' number of bytes from the array starting at position 'pos' 320 // relative to this pointer. 321 std::vector<unsigned char> extractBytes(size_t pos, size_t length) const; 322 323 // Equivalent to calling convert(0, output). 324 template <class T> 325 bool convert(T *output) const { 326 union { 327 T t; 328 unsigned char ch[sizeof(T)]; 329 } buffer; 330 for (size_t i = 0; i < sizeof(T); i++) { 331 buffer.ch[i] = (*this)[i]; 332 } 333 if (!errorOccurred()) { 334 *output = buffer.t; 335 } 336 return !errorOccurred(); 337 } 338 339 // Reinterprets this pointer as a pointer to an array of T, then returns the 340 // element at position 'index' in this array of T. (Note that this position 341 // corresponds to position index * sizeof(T) in the underlying byte array.) 342 // 343 // Returns true if successful; false if an out-of-range error occurred or if 344 // the error flag was already set on the pointer when calling convert(). 345 // 346 // The conversion from a sequence of sizeof(T) bytes to a T is performed in an 347 // implementation-defined fashion. This conversion is equivalent to the one 348 // obtained using the following union by filling the array 'ch' and then 349 // reading the member 't': 350 // 351 // union { 352 // T t; 353 // unsigned char ch[sizeof(T)]; 354 // }; 355 // 356 // Callers should note that, among other things, the conversion is not 357 // endian-agnostic with respect to the endianness of T. 358 template <class T> 359 bool convert(size_t index, T *output) const { 360 RangeCheckedBytePtr p = (*this) + index * sizeof(T); 361 bool valid = p.convert(output); 362 if (!valid) { 363 error_flag_ = p.error_flag_; 364 } 365 return valid; 366 } 367 368 // Operators. Unless otherwise noted, these operators have the same semantics 369 // as the same operators on an unsigned char pointer. 370 371 // If an out-of-range access is attempted, returns 0 (and sets the error 372 // flag). 373 inline unsigned char operator[](size_t i) const; 374 375 inline unsigned char operator*() const; 376 377 inline RangeCheckedBytePtr &operator++(); 378 379 inline RangeCheckedBytePtr operator++(int); 380 381 inline RangeCheckedBytePtr &operator--(); 382 383 inline RangeCheckedBytePtr operator--(int); 384 385 inline RangeCheckedBytePtr &operator+=(size_t x); 386 387 inline RangeCheckedBytePtr &operator-=(size_t x); 388 389 inline friend RangeCheckedBytePtr operator+(const RangeCheckedBytePtr &p, 390 size_t x); 391 392 inline friend RangeCheckedBytePtr operator-(const RangeCheckedBytePtr &p, 393 size_t x); 394 395 // Tests whether x and y point at the same position in the underlying array. 396 // Two pointers that point at the same position but have different 397 // sub-arrays still compare equal. It is not legal to compare two pointers 398 // that point into different paged byte arrays. 399 friend bool operator==(const RangeCheckedBytePtr &x, 400 const RangeCheckedBytePtr &y); 401 402 // Returns !(x == y). 403 friend bool operator!=(const RangeCheckedBytePtr &x, 404 const RangeCheckedBytePtr &y); 405 406 private: 407 void loadPageForOffset(size_t offset) const; 408 void restrictPageToSubArray() const; 409 }; 410 411 // Returns the result of calling std::memcmp() on the sequences of 'num' bytes 412 // pointed to by 'x' and 'y'. The result is undefined if either 413 // x.remainingLength() or y.remainingLength() is less than 'num'. 414 int memcmp(const RangeCheckedBytePtr &x, const RangeCheckedBytePtr &y, 415 size_t num); 416 417 // Returns the result of calling std::memcmp() (note: _not_ strcmp()) on the 418 // y.length() number of bytes pointed to by 'x' and the string 'y'. The result 419 // is undefined if x.remainingLength() is less than y.length(). 420 int strcmp(const RangeCheckedBytePtr &x, const std::string &y); 421 422 // Returns the length of the zero-terminated string starting at 'src' (not 423 // including the '\0' terminator). If no '\0' occurs before the end of the 424 // array, the result is undefined. 425 size_t strlen(const RangeCheckedBytePtr &src); 426 427 // Integer decoding functions. 428 // 429 // These functions read signed (Get16s, Get32s) or unsigned (Get16u, Get32u) 430 // integers from 'input'. The integer read from the input can be specified to be 431 // either big-endian (big_endian == true) or little-endian 432 // (little_endian == false). Signed integers are read in two's-complement 433 // representation. The integer read in the specified format is then converted to 434 // the implementation's native integer representation and returned. In other 435 // words, the semantics of these functions are independent of the 436 // implementation's endianness and signed integer representation. 437 // 438 // If an out-of-range error occurs, these functions do _not_ set the error flag 439 // on 'input'. Instead, they set 'status' to RANGE_CHECKED_BYTE_ERROR and return 440 // 0. 441 // 442 // Note: 443 // - If an error occurs and 'status' is already set to an error value (i.e. a 444 // value different from RANGE_CHECKED_BYTE_SUCCESS), the value of 'status' is 445 // left unchanged. 446 // - If the operation is successful, 'status' is left unchanged (i.e. it is not 447 // actively set to RANGE_CHECKED_BYTE_SUCCESS). 448 // 449 // Together, these two properties mean that these functions can be used to read 450 // a number of integers in succession with only a single error check, like this: 451 // 452 // MemoryStatus status = RANGE_CHECKED_BYTE_SUCCESS; 453 // int16 val1 = Get16s(input, false, &status); 454 // int32 val2 = Get32s(input + 2, false, &status); 455 // uint32 val3 = Get32u(input + 6, false, &status); 456 // if (status != RANGE_CHECKED_BYTE_SUCCESS) { 457 // // error handling 458 // } 459 int16 Get16s(const RangeCheckedBytePtr &input, const bool big_endian, 460 MemoryStatus *status); 461 uint16 Get16u(const RangeCheckedBytePtr &input, const bool big_endian, 462 MemoryStatus *status); 463 int32 Get32s(const RangeCheckedBytePtr &input, const bool big_endian, 464 MemoryStatus *status); 465 uint32 Get32u(const RangeCheckedBytePtr &input, const bool big_endian, 466 MemoryStatus *status); 467 468 size_t RangeCheckedBytePtr::remainingLength() const { 469 if (!errorOccurred()) { 470 // current_pos_ <= sub_array_end_ is a class invariant, but protect 471 // against violations of this invariant. 472 if (current_pos_ <= sub_array_end_) { 473 return sub_array_end_ - current_pos_; 474 } else { 475 assert(false); 476 return 0; 477 } 478 } else { 479 return 0; 480 } 481 } 482 483 bool RangeCheckedBytePtr::errorOccurred() const { 484 return error_flag_ != RANGE_CHECKED_BYTE_SUCCESS; 485 } 486 487 unsigned char RangeCheckedBytePtr::operator[](size_t i) const { 488 // Check that pointer doesn't have an error flag set. 489 if (!errorOccurred()) { 490 // Offset in array to read from. 491 const size_t read_offset = current_pos_ + i; 492 493 // Check for the common case first: The byte we want to read lies in the 494 // current page. For performance reasons, we don't check for the case 495 // "read_offset < page_begin_offset_" explicitly; if it occurs, it will 496 // lead to wraparound (which is well-defined for unsigned quantities), and 497 // this will cause the test "pos_in_page < current_page_len_" to fail. 498 size_t pos_in_page = read_offset - page_begin_offset_; 499 if (pos_in_page < current_page_len_) { 500 return page_data_[pos_in_page]; 501 } 502 503 // Check that the offset we're trying to read lies within the sub-array 504 // we're allowed to access. 505 if (read_offset >= sub_array_begin_ && read_offset < sub_array_end_) { 506 // Read the page that contains the offset "read_offset". 507 loadPageForOffset(read_offset); 508 509 // Compute the position within the new page from which we need to read. 510 pos_in_page = read_offset - page_begin_offset_; 511 512 // After the call to loadPageForOffset(), read_offset must lie within 513 // the current page, and therefore pos_in_page must be less than the 514 // length of the page. We nevertheless check for this to protect against 515 // potential bugs in loadPageForOffset(). 516 assert(pos_in_page < current_page_len_); 517 if (pos_in_page < current_page_len_) { 518 return page_data_[pos_in_page]; 519 } 520 } 521 } 522 523 // All error cases fall through to here. 524 #ifdef BREAK_IF_DEBUGGING_AND_OUT_OF_RANGE 525 assert(false); 526 #endif 527 error_flag_ = RANGE_CHECKED_BYTE_ERROR_OVERFLOW; 528 // return 0, which represents the invalid value 529 return static_cast<unsigned char>(0); 530 } 531 532 unsigned char RangeCheckedBytePtr::operator*() const { return (*this)[0]; } 533 534 RangeCheckedBytePtr &RangeCheckedBytePtr::operator++() { 535 if (current_pos_ < sub_array_end_) { 536 current_pos_++; 537 } else { 538 #ifdef BREAK_IF_DEBUGGING_AND_OUT_OF_RANGE 539 assert(false); 540 #endif 541 error_flag_ = RANGE_CHECKED_BYTE_ERROR_OVERFLOW; 542 } 543 return *this; 544 } 545 546 RangeCheckedBytePtr RangeCheckedBytePtr::operator++(int) { 547 RangeCheckedBytePtr result(*this); 548 ++(*this); 549 return result; 550 } 551 552 RangeCheckedBytePtr &RangeCheckedBytePtr::operator--() { 553 if (current_pos_ > sub_array_begin_) { 554 current_pos_--; 555 } else { 556 #ifdef BREAK_IF_DEBUGGING_AND_OUT_OF_RANGE 557 assert(false); 558 #endif 559 error_flag_ = RANGE_CHECKED_BYTE_ERROR_UNDERFLOW; 560 } 561 return *this; 562 } 563 564 RangeCheckedBytePtr RangeCheckedBytePtr::operator--(int) { 565 RangeCheckedBytePtr result(*this); 566 --(*this); 567 return result; 568 } 569 570 RangeCheckedBytePtr &RangeCheckedBytePtr::operator+=(size_t x) { 571 if (remainingLength() >= x) { 572 current_pos_ += x; 573 } else { 574 #ifdef BREAK_IF_DEBUGGING_AND_OUT_OF_RANGE 575 assert(false); 576 #endif 577 error_flag_ = RANGE_CHECKED_BYTE_ERROR_OVERFLOW; 578 } 579 return *this; 580 } 581 582 RangeCheckedBytePtr &RangeCheckedBytePtr::operator-=(size_t x) { 583 if (x <= current_pos_ - sub_array_begin_) { 584 current_pos_ -= x; 585 } else { 586 #ifdef BREAK_IF_DEBUGGING_AND_OUT_OF_RANGE 587 assert(false); 588 #endif 589 error_flag_ = RANGE_CHECKED_BYTE_ERROR_UNDERFLOW; 590 } 591 return *this; 592 } 593 594 RangeCheckedBytePtr operator+(const RangeCheckedBytePtr &p, size_t x) { 595 RangeCheckedBytePtr result(p); 596 result += x; 597 return result; 598 } 599 600 RangeCheckedBytePtr operator-(const RangeCheckedBytePtr &p, size_t x) { 601 RangeCheckedBytePtr result(p); 602 result -= x; 603 return result; 604 } 605 606 } // namespace binary_parse 607 } // namespace piex 608 609 #endif // PIEX_BINARY_PARSE_RANGE_CHECKED_BYTE_PTR_H_ 610