#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#




"""Socket Module.

This file is intended to provide the equivalent of
python/Modules/socketmodule.c rather than python/Lib/socket.py which amongst
other things adds a buffered file-like interface.
"""














import errno
import os
import re
import struct
import time
import weakref

from google.appengine.api import apiproxy_stub_map
from google.appengine.api.remote_socket import remote_socket_service_pb

from google.appengine.api.remote_socket._remote_socket_addr import *

from google.appengine.api.remote_socket._remote_socket_error import *
from google.appengine.runtime import apiproxy_errors

has_ipv6 = True

SOCK_STREAM = 1
SOCK_DGRAM = 2

SOMAXCONN = 128

MSG_PEEK = 2
MSG_WAITALL = 256

IPPROTO_IP = 0
IPPROTO_ICMP = 1
IPPROTO_TCP = 6
IPPROTO_UDP = 17

IPPORT_RESERVED = 1024
IPPORT_USERRESERVED = 5000

INADDR_ANY = 0x00000000
INADDR_BROADCAST = 0xffffffff
INADDR_LOOPBACK = 0x7f000001
INADDR_NONE = 0xffffffff

(AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST, AI_NUMERICSERV, AI_V4MAPPED, AI_ALL,
 AI_ADDRCONFIG) = map(lambda x: 1 << x, range(7))

RemoteSocketServiceError = remote_socket_service_pb.RemoteSocketServiceError


def _ImportSymbols(protobuf, symbols, prefix='SOCKET_'):
  """Import symbols defined in a protobuf into the global namespace."""
  for sym in symbols:
    globals()[sym] = getattr(protobuf, prefix + sym)


_ImportSymbols(remote_socket_service_pb.ResolveReply, (
    'EAI_ADDRFAMILY', 'EAI_AGAIN', 'EAI_BADFLAGS', 'EAI_FAIL', 'EAI_FAMILY',
    'EAI_MEMORY', 'EAI_NODATA', 'EAI_NONAME', 'EAI_SERVICE', 'EAI_SOCKTYPE',
    'EAI_SYSTEM', 'EAI_BADHINTS', 'EAI_PROTOCOL', 'EAI_OVERFLOW', 'EAI_MAX'))

_ImportSymbols(remote_socket_service_pb.ShutDownRequest, (
    'SHUT_RD', 'SHUT_WR', 'SHUT_RDWR'))

_ImportSymbols(remote_socket_service_pb.SocketOption, (

    'SOL_SOCKET', 'SOL_IP', 'SOL_TCP', 'SOL_UDP',

    'SO_DEBUG', 'SO_REUSEADDR', 'SO_TYPE', 'SO_ERROR', 'SO_DONTROUTE',
    'SO_BROADCAST', 'SO_SNDBUF', 'SO_RCVBUF', 'SO_KEEPALIVE',

    'IP_TOS', 'IP_TTL', 'IP_HDRINCL', 'IP_OPTIONS',

    'TCP_NODELAY', 'TCP_MAXSEG', 'TCP_CORK', 'TCP_KEEPIDLE', 'TCP_KEEPINTVL',
    'TCP_KEEPCNT', 'TCP_SYNCNT', 'TCP_LINGER2', 'TCP_DEFER_ACCEPT',
    'TCP_WINDOW_CLAMP', 'TCP_INFO', 'TCP_QUICKACK'))

_ImportSymbols(remote_socket_service_pb.PollEvent, (
    'POLLNONE', 'POLLIN', 'POLLPRI', 'POLLOUT', 'POLLERR', 'POLLHUP',
    'POLLNVAL', 'POLLRDNORM', 'POLLRDBAND', 'POLLWRNORM', 'POLLWRBAND',
    'POLLMSG', 'POLLREMOVE', 'POLLRDHUP'))


_GLOBAL_DEFAULT_TIMEOUT = object()
_GLOBAL_TIMEOUT_VALUE = -1.0

_GLOBAL_SOCKET_NEXT_FILENO = 2**32
_GLOBAL_SOCKET_MAP = weakref.WeakValueDictionary()

_SERVICES = {
    'ftp': [('tcp', 21), ('udp', 21)],
    'ftp-data': [('tcp', 20), ('udp', 20)],
    'http': [('tcp', 80), ('udp', 80)],
    'pop3': [('tcp', 110), ('udp', 110)],
    'pop3s': [('tcp', 995), ('udp', 995)],
    'smtp': [('tcp', 25), ('udp', 25)],
    'telnet': [('tcp', 23), ('udp', 23)],
    'www': [('tcp', 80), ('udp', 80)],
    'www-http': [('tcp', 80), ('udp', 80)],
}

_ERROR_MAP = {
    RemoteSocketServiceError.PERMISSION_DENIED: errno.EACCES,
    RemoteSocketServiceError.INVALID_REQUEST: errno.EINVAL,
    RemoteSocketServiceError.SOCKET_CLOSED: errno.EPIPE,
}

_SOCK_PROTO_MAP = {
    (SOCK_STREAM, IPPROTO_TCP): 'tcp',
    (SOCK_DGRAM, IPPROTO_UDP): 'udp',
}

_ADDRESS_FAMILY_MAP = {
    AF_INET: remote_socket_service_pb.CreateSocketRequest.IPv4,
    AF_INET6: remote_socket_service_pb.CreateSocketRequest.IPv6,
}

_ADDRESS_FAMILY_LENGTH_MAP = {
    4: AF_INET,
    16: AF_INET6,
}


class SocketApiNotImplementedError(NotImplementedError, error):
  pass


def _SystemExceptionFromAppError(e):
  app_error = e.application_error
  if app_error in (RemoteSocketServiceError.SYSTEM_ERROR,
                   RemoteSocketServiceError.GAI_ERROR):
    error_detail = RemoteSocketServiceError()
    try:
      error_detail.ParseASCII(e.error_detail)
    except NotImplementedError:


      m = re.match(
          r'system_error:\s*(-?\d+)\s*,?\s*error_detail:\s*"([^"]*)"\s*',
          e.error_detail)
      if m:
        error_detail.set_system_error(int(m.group(1)))
        error_detail.set_error_detail(m.group(2))
      else:
        error_detail.set_system_error(-1)
        error_detail.set_error_detail(e.error_detail)
    if app_error == RemoteSocketServiceError.SYSTEM_ERROR:
      return error(error_detail.system_error(),
                   (error_detail.error_detail() or
                    os.strerror(error_detail.system_error())))
    elif app_error == RemoteSocketServiceError.GAI_ERROR:
      return gaierror(error_detail.system_error(),
                      error_detail.error_detail())
  elif app_error in _ERROR_MAP:
    return error(_ERROR_MAP[app_error], os.strerror(_ERROR_MAP[app_error]))
  else:
    return e


def _IsAddr(family, addr):
  try:
    inet_pton(family, addr)
  except Exception:
    return False
  return True




def _Resolve(name, families, use_dns=True, canonical=False):
  for family in families:
    if _IsAddr(family, name):

      return (name, [], [name])


  if use_dns:
    canon, aliases, addresses = _ResolveName(name, families)
    if addresses:
      return (canon, aliases, addresses)

  raise gaierror(EAI_NONAME, 'nodename nor servname provided, or not known')




def _ResolveName(name, address_families=(AF_INET6, AF_INET)):
  request = remote_socket_service_pb.ResolveRequest()
  request.set_name(name)
  for af in address_families:
    request.add_address_families(_ADDRESS_FAMILY_MAP[af])

  reply = remote_socket_service_pb.ResolveReply()

  try:
    apiproxy_stub_map.MakeSyncCall('remote_socket', 'Resolve', request, reply)
  except apiproxy_errors.ApplicationError, e:
    raise _SystemExceptionFromAppError(e)

  canonical_name = reply.canonical_name()
  aliases = reply.aliases_list()
  addresses = [inet_ntop(_ADDRESS_FAMILY_LENGTH_MAP[len(a)], a)
               for a in reply.packed_address_list()]
  return canonical_name, aliases, addresses


def _ResolveService(servicename, protocolname, numeric_only=False):
  try:
    return (protocolname, int(servicename))
  except ValueError:
    pass

  if not numeric_only:
    for protocol, port in _SERVICES.get(servicename, []):
      if not protocolname or protocol == protocolname:
        return (protocol, port)

  raise gaierror(EAI_SERVICE, '')


def gethostbyname(host):
  """gethostbyname(host) -> address

  Return the IP address (a string of the form '255.255.255.255') for a host.
  """
  return _Resolve(host, [AF_INET])[2][0]


def gethostbyname_ex(host):
  """gethostbyname_ex(host) -> (name, aliaslist, addresslist)

  Return the true host name, a list of aliases, and a list of IP addresses,
  for a host.  The host argument is a string giving a host name or IP number.
  """
  return _Resolve(host, [AF_INET])


def gethostbyaddr(addr):

  raise SocketApiNotImplementedError()


def gethostname():
  """gethostname() -> string

  Return the current host name.
  """
  return os.environ.get('HTTP_HOST', 'www.appspot.com')


def getprotobyname(protocolname):
  raise SocketApiNotImplementedError()


def getservbyname(servicename, protocolname=None):
  """getservbyname(servicename[, protocolname]) -> integer

  Return a port number from a service name and protocol name.
  The optional protocol name, if given, should be 'tcp' or 'udp',
  otherwise any protocol will match.
  """
  return _ResolveService(servicename, protocolname)[1]


def getservbyport(portnumber, protocolname=0):
  raise SocketApiNotImplementedError()




def getaddrinfo(host, service, family=AF_UNSPEC, socktype=0, proto=0, flags=0):
  """getaddrinfo(host, port [, family, socktype, proto, flags])
      -> list of (family, socktype, proto, canonname, sockaddr)

  Resolve host and port into addrinfo struct.
  """
  if isinstance(host, unicode):
    host = host.encode('idna')
  if host == '*':
    host = ''
  if service == '*':
    service = ''
  if not host and not service:
    raise gaierror(EAI_NONAME, 'nodename nor servname provided, or not known')

  families = [f for f in _ADDRESS_FAMILY_MAP.keys()
              if family in (AF_UNSPEC, f)]
  if not families:
    raise gaierror(EAI_FAMILY, 'ai_family not supported')

  sock_proto = [sp for sp in _SOCK_PROTO_MAP.keys()
                if socktype in (0, sp[0]) and proto in (0, sp[1])]
  if not sock_proto:
    raise gaierror(EAI_BADHINTS, 'Bad hints')

  canon = ''
  sock_proto_port = []
  family_addresses = []


  if host:
    canon, _, addresses = _Resolve(
        host, families,
        use_dns=~(flags & AI_NUMERICHOST),
        canonical=(flags & AI_CANONNAME))
    family_addresses = [(f, a)
                        for f in families
                        for a in addresses if _IsAddr(f, a)]
  else:
    if flags & AI_PASSIVE:
      canon = 'anyaddr'
      if AF_INET6 in families:
        family_addresses.append((AF_INET6, '::'))
      if AF_INET in families:
        family_addresses.append((AF_INET, '0.0.0.0'))
    else:
      canon = 'localhost'
      if AF_INET6 in families:
        family_addresses.append((AF_INET6, '::1'))
      if AF_INET in families:
        family_addresses.append((AF_INET, '127.0.0.1'))


  if service:
    sock_proto_port = [
        sp + (_ResolveService(service, _SOCK_PROTO_MAP[sp],
                              flags & AI_NUMERICSERV)[1],)
        for sp in sock_proto]
  else:
    sock_proto_port = [sp + (0,) for sp in sock_proto]

  return [(fa[0], spp[0], spp[1], canon, (fa[1], spp[2]))
          for fa in family_addresses
          for spp in sock_proto_port]


def getnameinfo():

  raise SocketApiNotImplementedError()


def getdefaulttimeout():
  """getdefaulttimeout() -> timeout

  Returns the default timeout in floating seconds for new socket objects.
  A value of None indicates that new socket objects have no timeout.
  When the socket module is first imported, the default is None.
  """

  if _GLOBAL_TIMEOUT_VALUE < 0.0:
    return None
  return _GLOBAL_TIMEOUT_VALUE




def setdefaulttimeout(timeout):
  """setdefaulttimeout(timeout)

  Set the default timeout in floating seconds for new socket objects.
  A value of None indicates that new socket objects have no timeout.
  When the socket module is first imported, the default is None.
  """

  if timeout is None:
    timeout = -1.0
  else:
    try:
      timeout = 0.0 + timeout
    except TypeError:
      raise TypeError('a float is required')
    if timeout < 0.0:
      raise ValueError('Timeout value out of range')


  global _GLOBAL_TIMEOUT_VALUE
  _GLOBAL_TIMEOUT_VALUE = timeout


def _GetSocket(value):
  if isinstance(value, (int, long)):
    fileno = value
  else:
    try:
      fileno = value.fileno()
    except AttributeError:
      raise TypeError('argument must be an int, or have a fileno() method.')
  try:
    return _GLOBAL_SOCKET_MAP[fileno]
  except KeyError:
    raise ValueError('select only supported on socket objects.')






def select(rlist, wlist, xlist, timeout=None):
  """select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)

  Wait until one or more file descriptors are ready for some kind of I/O.
  The first three arguments are sequences of file descriptors to be waited for:
  rlist -- wait until ready for reading
  wlist -- wait until ready for writing
  xlist -- wait for an ``exceptional condition''
  If only one kind of condition is required, pass [] for the other lists.
  A file descriptor is either a socket or file object, or a small integer
  gotten from a fileno() method call on one of those.

  The optional 4th argument specifies a timeout in seconds; it may be
  a floating point number to specify fractions of seconds.  If it is absent
  or None, the call will never time out.

  The return value is a tuple of three lists corresponding to the first three
  arguments; each contains the subset of the corresponding file descriptors
  that are ready.
  """
  if not rlist and not wlist and not xlist:
    if timeout:
      time.sleep(timeout)
    return ([], [], [])

  state_map = {}
  rlist_out, wlist_out, xlist_out = [], [], []

  def _SetState(request, sock, event):
    socket_descriptor = sock._SocketDescriptor()
    state = state_map.setdefault(socket_descriptor, { 'observed_events': 0, })

    if ((event == POLLIN and sock._shutdown_read) or
        (event == POLLOUT and sock._shutdown_write)):
      state['observed_events'] |= event
      request.set_timeout_seconds(0.0)
      return

    poll_event = state.get('poll_event')
    if not poll_event:
      poll_event = request.add_events()
      poll_event.set_socket_descriptor(socket_descriptor)
      poll_event.set_observed_events(0)
      state['poll_event'] = poll_event
    poll_event.set_requested_events(poll_event.requested_events()|event)

  request = remote_socket_service_pb.PollRequest()
  if timeout is not None:
    request.set_timeout_seconds(timeout)

  for value in rlist:
    _SetState(request, _GetSocket(value), POLLIN)
  for value in wlist:
    _SetState(request, _GetSocket(value), POLLOUT)

  if request.events_size():
    reply = remote_socket_service_pb.PollReply()

    try:
      apiproxy_stub_map.MakeSyncCall('remote_socket', 'Poll', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    for event in reply.events_list():
      state_map[event.socket_descriptor()][
          'observed_events'] |= event.observed_events()

  for value in rlist:
    state = state_map[_GetSocket(value)._SocketDescriptor()]
    if state['observed_events'] & POLLIN:
      rlist_out.append(value)
  for value in wlist:
    state = state_map[_GetSocket(value)._SocketDescriptor()]
    if state['observed_events'] & POLLOUT:
      wlist_out.append(value)

  return (rlist_out, wlist_out, xlist_out)


class socket(object):
  """socket([family[, type[, proto]]]) -> socket object

  Open a socket of the given type.  The family argument specifies the
  address family; it defaults to AF_INET.  The type argument specifies
  whether this is a stream (SOCK_STREAM, this is the default)
  or datagram (SOCK_DGRAM) socket.  The protocol argument defaults to 0,
  specifying the default protocol.  Keyword arguments are accepted.

  A socket object represents one endpoint of a network connection.
  """

  def __del__(self):
    if not self._serialized:
      self.close()

  def __getstate__(self):
    self._serialized = True
    return self.__dict__



  def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, _create=False):
    if family not in (AF_INET, AF_INET6):
      raise error(errno.EAFNOSUPPORT, os.strerror(errno.EAFNOSUPPORT))

    if type not in (SOCK_STREAM, SOCK_DGRAM):
      raise error(errno.EPROTONOSUPPORT, os.strerror(errno.EPROTONOSUPPORT))

    if proto:
      if ((proto not in (IPPROTO_TCP, IPPROTO_UDP)) or
          (proto == IPPROTO_TCP and type != SOCK_STREAM) or
          (proto == IPPROTO_UDP and type != SOCK_DGRAM)):
        raise error(errno.EPROTONOSUPPORT, os.strerror(errno.EPROTONOSUPPORT))

    self.family = family
    self.type = type
    self.proto = proto
    self._created = False
    self._fileno = None
    self._serialized = False
    self.settimeout(getdefaulttimeout())
    self._Clear()

    if _create:
      self._CreateSocket()

  def _Clear(self):
    self._socket_descriptor = None
    self._bound = False
    self._listen = False
    self._connected = False
    self._connect_in_progress = False
    self._shutdown_read = False
    self._shutdown_write = False
    self._setsockopt = []
    self._stream_offset = 0

  def _CreateSocket(self, address=None, bind_address=None,
                    address_hostname_hint=None):
    assert not self._created
    self._created = True

    request = remote_socket_service_pb.CreateSocketRequest()

    if self.family == AF_INET:
      request.set_family(remote_socket_service_pb.CreateSocketRequest.IPv4)
    elif self.family == AF_INET6:
      request.set_family(remote_socket_service_pb.CreateSocketRequest.IPv6)

    if self.type == SOCK_STREAM:
      request.set_protocol(remote_socket_service_pb.CreateSocketRequest.TCP)
    elif self.type == SOCK_DGRAM:
      request.set_protocol(remote_socket_service_pb.CreateSocketRequest.UDP)

    if address:
      assert self.gettimeout() is None, (
          'Non-blocking connect not supported by CreateSocket')
      self._SetProtoFromAddr(request.mutable_remote_ip(), address,
                             address_hostname_hint)

    if bind_address:
      self._SetProtoFromAddr(request.mutable_proxy_external_ip(), bind_address)

    for level, option, value in self._setsockopt:
      o = request.add_socket_options()
      o.set_level(level)
      o.set_option(option)
      if isinstance(value, (int, long)):
        o.set_value(struct.pack('=L', value))
      else:
        o.set_value(value)
    self._setsockopt = []

    reply = remote_socket_service_pb.CreateSocketReply()

    try:
      apiproxy_stub_map.MakeSyncCall(
          'remote_socket', 'CreateSocket', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    self._socket_descriptor = reply.socket_descriptor()
    if bind_address:
      self._bound = True
    if address:
      self._bound = True
      self._connected = True

  def _GetPackedAddr(self, addr):
    if addr == '<broadcast>':
      if self.family == AF_INET6:
        return '\xff' * 16
      else:
        return '\xff' * 4
    for res in getaddrinfo(addr, '0',
                           self.family, self.type, self.proto,
                           AI_NUMERICSERV|AI_PASSIVE):
      return inet_pton(self.family, res[4][0])

  def _SetProtoFromAddr(self, proto, address, hostname_hint=None):
    address, port = address
    proto.set_packed_address(self._GetPackedAddr(address))
    proto.set_port(port)
    proto.set_hostname_hint(hostname_hint or address)

  def fileno(self):
    """fileno() -> integer

    Return the integer file descriptor of the socket.
    """
    global _GLOBAL_SOCKET_MAP
    global _GLOBAL_SOCKET_NEXT_FILENO
    if self._fileno is None:
      self._fileno = _GLOBAL_SOCKET_NEXT_FILENO
      _GLOBAL_SOCKET_NEXT_FILENO += 1
      _GLOBAL_SOCKET_MAP[self._fileno] = self
    assert _GLOBAL_SOCKET_MAP.get(self._fileno) == self, (
        "fileno mismatch in _GLOBAL_SOCKET_MAP")
    return self._fileno

  def bind(self, address):
    """bind(address)

    Bind the socket to a local address.  For IP sockets, the address is a
    pair (host, port); the host must refer to the local host. For raw packet
    sockets the address is a tuple (ifname, proto [,pkttype [,hatype]])
    """
    if not self._created:
      self._CreateSocket(bind_address=address)
      return
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))
    if self._bound:
      raise error(errno.EINVAL, os.strerror(errno.EINVAL))

    request = remote_socket_service_pb.BindRequest()
    request.set_socket_descriptor(self._socket_descriptor)
    self._SetProtoFromAddr(request.mutable_proxy_external_ip(), address)

    reply = remote_socket_service_pb.BindReply()

    try:
      apiproxy_stub_map.MakeSyncCall('remote_socket', 'Bind', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

  def listen(self, backlog):
    """listen(backlog)

    Enable a server to accept connections.  The backlog argument must be at
    least 1; it specifies the number of unaccepted connection that the system
    will allow before refusing new connections.
    """
    if not self._created:
      self._CreateSocket(bind_address=('', 0))
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))
    if self._connected:
      raise error(errno.EINVAL, os.strerror(errno.EINVAL))
    if self.type != SOCK_STREAM:
      raise error(errno.EOPNOTSUPP, os.strerror(errno.EOPNOTSUPP))
    self._bound = True
    self._listen = True

    request = remote_socket_service_pb.ListenRequest()
    request.set_socket_descriptor(self._socket_descriptor)
    request.set_backlog(backlog)

    reply = remote_socket_service_pb.ListenReply()

    try:
      apiproxy_stub_map.MakeSyncCall('remote_socket', 'Listen', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

  def accept(self):
    """accept() -> (socket object, address info)

    Wait for an incoming connection.  Return a new socket representing the
    connection, and the address of the client.  For IP sockets, the address
    info is a pair (hostaddr, port).
    """
    if not self._created:
      self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))
    if not self._listen:
      raise error(errno.EINVAL, os.strerror(errno.EINVAL))

    request = remote_socket_service_pb.AcceptRequest()
    request.set_socket_descriptor(self._socket_descriptor)
    if self.gettimeout() is not None:
      request.set_timeout_seconds(self.gettimeout())

    reply = remote_socket_service_pb.AcceptReply()

    try:
      apiproxy_stub_map.MakeSyncCall('remote_socket', 'Accept', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    ret = socket(self.family, self.type, self.proto)
    ret._socket_descriptor = reply.new_socket_descriptor()
    ret._created = True
    ret._bound = True
    ret._connected = True
    return ret




  def connect(self, address, _hostname_hint=None):
    """connect(address)

    Connect the socket to a remote address.  For IP sockets, the address
    is a pair (host, port).
    """
    if not self._created:
      if self.gettimeout() is None:
        self._CreateSocket(address=address,
                           address_hostname_hint=_hostname_hint)
        return
      else:




        self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))
    if self._connected:
      raise error(errno.EISCONN, os.strerror(errno.EISCONN))

    request = remote_socket_service_pb.ConnectRequest()
    request.set_socket_descriptor(self._socket_descriptor)
    self._SetProtoFromAddr(request.mutable_remote_ip(), address, _hostname_hint)
    if self.gettimeout() is not None:
      request.set_timeout_seconds(self.gettimeout())

    reply = remote_socket_service_pb.ConnectReply()

    try:
      apiproxy_stub_map.MakeSyncCall('remote_socket', 'Connect', request, reply)
    except apiproxy_errors.ApplicationError, e:
      translated_e = _SystemExceptionFromAppError(e)
      if translated_e.errno == errno.EISCONN:
        self._bound = True
        self._connected = True
      elif translated_e.errno == errno.EINPROGRESS:
        self._connect_in_progress = True
      raise translated_e

    self._bound = True
    self._connected = True

  def connect_ex(self, address):
    """connect_ex(address) -> errno

    This is like connect(address), but returns an error code (the errno value)
    instead of raising an exception when an error occurs.
    """
    try:
      self.connect(address)
    except error, e:
      return e.errno
    return 0

  def getpeername(self):
    """getpeername() -> address info

    Return the address of the remote endpoint.  For IP sockets, the address
    info is a pair (hostaddr, port).
    """
    if not self._created:
      self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))
    if not (self._connected or self._connect_in_progress):
      raise error(errno.ENOTCONN, os.strerror(errno.ENOTCONN))

    request = remote_socket_service_pb.GetPeerNameRequest()
    request.set_socket_descriptor(self._socket_descriptor)

    reply = remote_socket_service_pb.GetPeerNameReply()

    try:
      apiproxy_stub_map.MakeSyncCall(
          'remote_socket', 'GetPeerName', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    if self._connect_in_progress:
      self._connect_in_progress = False
      self._connected = True

    return (
        inet_ntop(self.family, reply.peer_ip().packed_address()),
        reply.peer_ip().port())

  def getsockname(self):
    """getsockname() -> address info

    Return the address of the local endpoint.  For IP sockets, the address
    info is a pair (hostaddr, port).
    """
    if not self._created:
      self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))

    request = remote_socket_service_pb.GetSocketNameRequest()
    request.set_socket_descriptor(self._socket_descriptor)

    reply = remote_socket_service_pb.GetSocketNameReply()

    try:
      apiproxy_stub_map.MakeSyncCall(
          'remote_socket', 'GetSocketName', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    return (
        inet_ntop(self.family, reply.proxy_external_ip().packed_address()),
        reply.proxy_external_ip().port())

  def recv(self, buffersize, flags=0):
    """recv(buffersize[, flags]) -> data

    Receive up to buffersize bytes from the socket.  For the optional flags
    argument, see the Unix manual.  When no data is available, block until
    at least one byte is available or until the remote end is closed.  When
    the remote end is closed and all data is read, return the empty string.
    """
    return self.recvfrom(buffersize, flags)[0]

  def recv_into(self, buf, nbytes=0, flags=0):
    """recv_into(buffer, [nbytes[, flags]]) -> nbytes_read

    A version of recv() that stores its data into a buffer rather than
    creating a new string.  Receive up to buffersize bytes from the socket.
    If buffersize is not specified (or 0), receive up to the size available
    in the given buffer.

    See recv() for documentation about the flags.
    """
    raise SocketApiNotImplementedError()

  def recvfrom(self, buffersize, flags=0):
    """recvfrom(buffersize[, flags]) -> (data, address info)

    Like recv(buffersize, flags) but also return the sender's address info.
    """
    if not self._created:
      self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))

    request = remote_socket_service_pb.ReceiveRequest()
    request.set_socket_descriptor(self._socket_descriptor)
    request.set_data_size(buffersize)
    request.set_flags(flags)
    if self.type == SOCK_STREAM:
      if not (self._connected or self._connect_in_progress):
        raise error(errno.ENOTCONN, os.strerror(errno.ENOTCONN))
    if self._shutdown_read:
      request.set_timeout_seconds(0.0)
    elif self.gettimeout() is not None:
      request.set_timeout_seconds(self.gettimeout())

    reply = remote_socket_service_pb.ReceiveReply()

    try:
      apiproxy_stub_map.MakeSyncCall('remote_socket', 'Receive', request, reply)
    except apiproxy_errors.ApplicationError, e:
      e = _SystemExceptionFromAppError(e)
      if not self._shutdown_read or e.errno != errno.EAGAIN:
        raise e

    if self._connect_in_progress:
      self._connect_in_progress = False
      self._connected = True

    address = None
    if reply.has_received_from():
      address = (
          inet_ntop(self.family, reply.received_from().packed_address()),
          reply.received_from().port())

    return reply.data(), address



  def recvfrom_into(self, buffer, nbytes=0, flags=0):
    """recvfrom_into(buffer[, nbytes[, flags]]) -> (nbytes, address info)

    Like recv_into(buffer[, nbytes[, flags]]) but also return the
    sender's address info.
    """

    raise SocketApiNotImplementedError()

  def send(self, data, flags=0):
    """send(data[, flags]) -> count

    Send a data string to the socket.  For the optional flags
    argument, see the Unix manual.  Return the number of bytes
    sent; this may be less than len(data) if the network is busy.
    """
    return self.sendto(data, flags, None)

  def sendall(self, data, flags=0):
    """sendall(data[, flags])

    Send a data string to the socket.  For the optional flags
    argument, see the Unix manual.  This calls send() repeatedly
    until all data is sent.  If an error occurs, it's impossible
    to tell how much data has been sent.
    """
    offset = 0
    while offset < len(data):
      offset += self.sendto(data[offset:], flags, None)

  def sendto(self, data, *args):
    """sendto(data[, flags], address) -> count

    Like send(data, flags) but allows specifying the destination address.
    For IP sockets, the address is a pair (hostaddr, port).
    """
    if len(args) == 1:
      flags, address = 0, args[0]
    elif len(args) == 2:
      flags, address = args

    if not self._created:
      self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))
    if self._shutdown_write:
      raise error(errno.EPIPE, os.strerror(errno.EPIPE))

    request = remote_socket_service_pb.SendRequest()
    request.set_socket_descriptor(self._socket_descriptor)

    if len(data) > 512*1024:
      request.set_data(data[:512*1024])
    else:
      request.set_data(data)
    request.set_flags(flags)
    request.set_stream_offset(self._stream_offset)

    if address:
      if self._connected:
        raise error(errno.EISCONN, os.strerror(errno.EISCONN))
      if self.type != SOCK_DGRAM:
        raise error(errno.ENOTCONN, os.strerror(errno.ENOTCONN))
      self._SetProtoFromAddr(request.mutable_send_to(), address)
    else:
      if not (self._connected or self._connect_in_progress):
        raise error(errno.ENOTCONN, os.strerror(errno.ENOTCONN))

    if self.gettimeout() is not None:
      request.set_timeout_seconds(self.gettimeout())

    reply = remote_socket_service_pb.SendReply()

    try:
      apiproxy_stub_map.MakeSyncCall('remote_socket', 'Send', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    if self._connect_in_progress:
      self._connect_in_progress = False
      self._connected = True

    nbytes = reply.data_sent()
    assert nbytes >= 0
    if self.type == SOCK_STREAM:
      self._stream_offset += nbytes
    return nbytes

  def setblocking(self, block):
    """setblocking(flag)

    Set the socket to blocking (flag is true) or non-blocking (false).
    setblocking(True) is equivalent to settimeout(None);
    setblocking(False) is equivalent to settimeout(0.0).
    """
    if block:
      self._timeout = -1.0
    else:
      self._timeout = 0.0

  def settimeout(self, timeout):
    """settimeout(timeout)

    Set a timeout on socket operations.  'timeout' can be a float,
    giving in seconds, or None.  Setting a timeout of None disables
    the timeout feature and is equivalent to setblocking(1).
    Setting a timeout of zero is the same as setblocking(0).
    """
    if timeout is None:
      self._timeout = -1.0
    else:
      try:
        self._timeout = 0.0 + timeout
      except:
        raise TypeError('a float is required')
      if self._timeout < 0.0:
        raise ValueError('Timeout value out of range')

  def gettimeout(self):
    """gettimeout() -> timeout

    Returns the timeout in floating seconds associated with socket
    operations. A timeout of None indicates that timeouts on socket
    operations are disabled.
    """
    if self._timeout < 0.0:
      return None
    return self._timeout

  def setsockopt(self, level, option, value):
    """setsockopt(level, option, value)

    Set a socket option.  See the Unix manual for level and option.
    The value argument can either be an integer or a string.
    """



    if not self._created:
      self._setsockopt.append((level, option, value))
      self._CreateSocket()
      return
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))

    request = remote_socket_service_pb.SetSocketOptionsRequest()
    request.set_socket_descriptor(self._socket_descriptor)

    o = request.add_options()
    o.set_level(level)
    o.set_option(option)
    if isinstance(value, (int, long)):
      o.set_value(struct.pack('=L', value))
    else:
      o.set_value(value)

    reply = remote_socket_service_pb.SetSocketOptionsReply()

    try:
      apiproxy_stub_map.MakeSyncCall(
          'remote_socket', 'SetSocketOptions', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

  def getsockopt(self, level, option, buffersize=0):
    """getsockopt(level, option[, buffersize]) -> value

    Get a socket option.  See the Unix manual for level and option.
    If a nonzero buffersize argument is given, the return value is a
    string of that length; otherwise it is an integer.
    """
    if not self._created:
      self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))

    request = remote_socket_service_pb.GetSocketOptionsRequest()
    request.set_socket_descriptor(self._socket_descriptor)
    o = request.add_options()
    o.set_level(level)
    o.set_option(option)
    o.set_value('')

    reply = remote_socket_service_pb.GetSocketOptionsReply()

    try:
      apiproxy_stub_map.MakeSyncCall(
          'remote_socket', 'GetSocketOptions', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    if not buffersize:
      return struct.unpack('=L', reply.options(0).value())[0]
    else:
      return reply.options(0).value()[:buffersize]

  def shutdown(self, flag):
    """shutdown(flag)

    Shut down the reading side of the socket (flag == SHUT_RD), the writing side
    of the socket (flag == SHUT_WR), or both ends (flag == SHUT_RDWR).
    """
    if not flag in (SHUT_RD, SHUT_WR, SHUT_RDWR):
      raise error(errno.EINVAL, os.strerror(errno.EINVAL))
    if not self._created:
      self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))
    if (not self._connected or
        (self._shutdown_read and flag in (SHUT_RD, SHUT_RDWR)) or
        (self._shutdown_write and flag in (SHUT_RD, SHUT_RDWR))):
      raise error(errno.ENOTCONN, os.strerror(errno.ENOTCONN))

    request = remote_socket_service_pb.ShutDownRequest()
    request.set_socket_descriptor(self._socket_descriptor)
    request.set_how(flag)
    request.set_send_offset(self._stream_offset)

    reply = remote_socket_service_pb.ShutDownReply()

    try:
      apiproxy_stub_map.MakeSyncCall(
          'remote_socket', 'ShutDown', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    if flag == SHUT_RD or flag == SHUT_RDWR:
      self._shutdown_read = True
    if flag == SHUT_WR or flag == SHUT_RDWR:
      self._shutdown_write = True

  def close(self):
    """close()

    Close the socket.  It cannot be used after this call.
    """
    self._created = True
    if not self._socket_descriptor:
      return

    request = remote_socket_service_pb.CloseRequest()
    request.set_socket_descriptor(self._socket_descriptor)

    reply = remote_socket_service_pb.CloseReply()

    try:
      apiproxy_stub_map.MakeSyncCall('remote_socket', 'Close', request, reply)
    except apiproxy_errors.ApplicationError, e:
      raise _SystemExceptionFromAppError(e)

    self._Clear()

  def _SocketDescriptor(self):
    if not self._created:
      self._CreateSocket()
    if not self._socket_descriptor:
      raise error(errno.EBADF, os.strerror(errno.EBADF))
    return self._socket_descriptor
