github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/server/meekBuffer.go (about)

     1  /*
     2   * Copyright (c) 2017, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package server
    21  
    22  import (
    23  	"errors"
    24  	"io"
    25  )
    26  
    27  // CachedResponse is a data structure that supports meek
    28  // protocol connection interruption resiliency: it stores
    29  // payload data from the most recent response so that it
    30  // may be resent if the client fails to receive it.
    31  //
    32  // The meek server maintains one CachedResponse for each
    33  // meek client. Psiphon's variant of meek streams response
    34  // data, so responses are not fixed size. To limit the memory
    35  // overhead of response caching, each CachedResponse has a
    36  // fixed-size buffer that operates as a ring buffer,
    37  // discarding older response bytes when the buffer fills.
    38  // A CachedResponse that has discarded data may still satisfy
    39  // a client retry where the client has already received part
    40  // of the response payload.
    41  //
    42  // A CachedResponse will also extend its capacity by
    43  // borrowing buffers from a CachedResponseBufferPool, if
    44  // available. When Reset is called, borrowed buffers are
    45  // released back to the pool.
    46  type CachedResponse struct {
    47  	buffers            [][]byte
    48  	readPosition       int
    49  	readAvailable      int
    50  	writeIndex         int
    51  	writeBufferIndex   int
    52  	overwriting        bool
    53  	extendedBufferPool *CachedResponseBufferPool
    54  }
    55  
    56  // NewCachedResponse creates a CachedResponse with a fixed buffer
    57  // of size bufferSize and borrowing buffers from extendedBufferPool.
    58  func NewCachedResponse(
    59  	bufferSize int,
    60  	extendedBufferPool *CachedResponseBufferPool) *CachedResponse {
    61  
    62  	return &CachedResponse{
    63  		buffers:            [][]byte{make([]byte, bufferSize)},
    64  		extendedBufferPool: extendedBufferPool,
    65  	}
    66  }
    67  
    68  // Reset reinitializes the CachedResponse state to have
    69  // no buffered response and releases all extended buffers
    70  // back to the pool.
    71  // Reset _must_ be called before discarding a CachedResponse
    72  // or extended buffers will not be released.
    73  func (response *CachedResponse) Reset() {
    74  	for i, buffer := range response.buffers {
    75  		if i > 0 {
    76  			response.extendedBufferPool.Put(buffer)
    77  		}
    78  	}
    79  	response.buffers = response.buffers[0:1]
    80  	response.readPosition = 0
    81  	response.readAvailable = 0
    82  	response.writeIndex = 0
    83  	response.writeBufferIndex = 0
    84  	response.overwriting = false
    85  }
    86  
    87  // Available returns the size of the buffered response data.
    88  func (response *CachedResponse) Available() int {
    89  	return response.readAvailable
    90  }
    91  
    92  // HasPosition checks if the CachedResponse has buffered
    93  // response data starting at or before the specified
    94  // position.
    95  func (response *CachedResponse) HasPosition(position int) bool {
    96  	return response.readAvailable > 0 && response.readPosition <= position
    97  }
    98  
    99  // CopyFromPosition writes the response data, starting at
   100  // the specified position, to writer. Any data before the
   101  // position is skipped. CopyFromPosition will return an error
   102  // if the specified position is not available.
   103  // CopyFromPosition will copy no data and return no error if
   104  // the position is at the end of its available data.
   105  // CopyFromPosition can be called repeatedly to read the
   106  // same data -- it does not advance or modify the CachedResponse.
   107  func (response *CachedResponse) CopyFromPosition(
   108  	position int, writer io.Writer) (int, error) {
   109  
   110  	if response.readAvailable > 0 && response.readPosition > position {
   111  		return 0, errors.New("position unavailable")
   112  	}
   113  
   114  	// Special case: position is end of available data
   115  	if position == response.readPosition+response.readAvailable {
   116  		return 0, nil
   117  	}
   118  
   119  	// Begin at the start of the response data, which may
   120  	// be midway through the buffer(s).
   121  
   122  	index := 0
   123  	bufferIndex := 0
   124  	if response.overwriting {
   125  		index = response.writeIndex
   126  		bufferIndex = response.writeBufferIndex
   127  		if index >= len(response.buffers[bufferIndex]) {
   128  			index = 0
   129  			bufferIndex = (bufferIndex + 1) % len(response.buffers)
   130  		}
   131  	}
   132  
   133  	// Iterate over all available data, skipping until at the
   134  	// requested position.
   135  
   136  	n := 0
   137  
   138  	skip := position - response.readPosition
   139  	available := response.readAvailable
   140  
   141  	for available > 0 {
   142  
   143  		buffer := response.buffers[bufferIndex]
   144  
   145  		toCopy := min(len(buffer)-index, available)
   146  
   147  		available -= toCopy
   148  
   149  		if skip > 0 {
   150  			if toCopy >= skip {
   151  				index += skip
   152  				toCopy -= skip
   153  				skip = 0
   154  			} else {
   155  				skip -= toCopy
   156  			}
   157  		}
   158  
   159  		if skip == 0 {
   160  			written, err := writer.Write(buffer[index : index+toCopy])
   161  			n += written
   162  			if err != nil {
   163  				return n, err
   164  			}
   165  		}
   166  
   167  		index = 0
   168  		bufferIndex = (bufferIndex + 1) % len(response.buffers)
   169  	}
   170  
   171  	return n, nil
   172  }
   173  
   174  // Write appends data to the CachedResponse. All writes will
   175  // succeed, but only the most recent bytes will be retained
   176  // once the fixed buffer is full and no extended buffers are
   177  // available.
   178  //
   179  // Write may be called multiple times to record a single
   180  // response; Reset should be called between responses.
   181  //
   182  // Write conforms to the io.Writer interface.
   183  func (response *CachedResponse) Write(data []byte) (int, error) {
   184  
   185  	dataIndex := 0
   186  
   187  	for dataIndex < len(data) {
   188  
   189  		// Write into available space in the current buffer
   190  
   191  		buffer := response.buffers[response.writeBufferIndex]
   192  		canWriteLen := len(buffer) - response.writeIndex
   193  		needWriteLen := len(data) - dataIndex
   194  		writeLen := min(canWriteLen, needWriteLen)
   195  
   196  		if writeLen > 0 {
   197  			copy(
   198  				buffer[response.writeIndex:response.writeIndex+writeLen],
   199  				data[dataIndex:dataIndex+writeLen])
   200  
   201  			response.writeIndex += writeLen
   202  
   203  			// readPosition tracks the earliest position in
   204  			// the response that remains in the cached response.
   205  			// Once the buffer is full (and cannot be extended),
   206  			// older data is overwritten and readPosition advances.
   207  			//
   208  			// readAvailable is the amount of data in the cached
   209  			// response, which may be less than the buffer capacity.
   210  
   211  			if response.overwriting {
   212  				response.readPosition += writeLen
   213  			} else {
   214  				response.readAvailable += writeLen
   215  			}
   216  
   217  			dataIndex += writeLen
   218  		}
   219  
   220  		if needWriteLen > canWriteLen {
   221  
   222  			// Add an extended buffer to increase capacity
   223  
   224  			// TODO: can extend whenever response.readIndex and response.readBufferIndex are 0?
   225  			if response.writeBufferIndex == len(response.buffers)-1 &&
   226  				!response.overwriting {
   227  
   228  				extendedBuffer := response.extendedBufferPool.Get()
   229  				if extendedBuffer != nil {
   230  					response.buffers = append(response.buffers, extendedBuffer)
   231  				}
   232  			}
   233  
   234  			// Move to the next buffer, which may wrap around
   235  
   236  			// This isn't a general ring buffer: Reset is called at
   237  			// start of each response, so the initial data is always
   238  			// at the beginning of the first buffer. It follows that
   239  			// data is overwritten once the buffer wraps around back
   240  			// to the beginning.
   241  
   242  			response.writeBufferIndex++
   243  			if response.writeBufferIndex >= len(response.buffers) {
   244  				response.writeBufferIndex = 0
   245  				response.overwriting = true
   246  			}
   247  			response.writeIndex = 0
   248  		}
   249  	}
   250  
   251  	return len(data), nil
   252  }
   253  
   254  // CachedResponseBufferPool is a fixed-size pool of
   255  // fixed-size buffers that are used to temporarily extend
   256  // the capacity of CachedResponses.
   257  type CachedResponseBufferPool struct {
   258  	bufferSize int
   259  	buffers    chan []byte
   260  }
   261  
   262  // NewCachedResponseBufferPool creates a new CachedResponseBufferPool
   263  // with the specified number of buffers. Buffers are allocated on
   264  // demand and once allocated remain allocated.
   265  func NewCachedResponseBufferPool(
   266  	bufferSize, bufferCount int) *CachedResponseBufferPool {
   267  
   268  	buffers := make(chan []byte, bufferCount)
   269  	for i := 0; i < bufferCount; i++ {
   270  		buffers <- make([]byte, 0)
   271  	}
   272  
   273  	return &CachedResponseBufferPool{
   274  		bufferSize: bufferSize,
   275  		buffers:    buffers,
   276  	}
   277  }
   278  
   279  // Get returns a buffer, if one is available, or returns nil
   280  // when no buffer is available. Get does not block. Call Put
   281  // to release the buffer back to the pool.
   282  //
   283  // Note: currently, Buffers are not zeroed between use by
   284  // different CachedResponses owned by different clients.
   285  // A bug resulting in cross-client data transfer exposes
   286  // only OSSH ciphertext in the case of meek's use of
   287  // CachedResponses.
   288  func (pool *CachedResponseBufferPool) Get() []byte {
   289  	select {
   290  	case buffer := <-pool.buffers:
   291  		if len(buffer) == 0 {
   292  			buffer = make([]byte, pool.bufferSize)
   293  		}
   294  		return buffer
   295  	default:
   296  		return nil
   297  	}
   298  }
   299  
   300  // Put releases a buffer back to the pool. The buffer must
   301  // have been obtained from Get.
   302  func (pool *CachedResponseBufferPool) Put(buffer []byte) {
   303  	pool.buffers <- buffer
   304  }