// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// part of "common_patch.dart";

@patch
class SecureSocket {
  @patch
  factory SecureSocket._(RawSecureSocket rawSocket) =>
      new _SecureSocket(rawSocket);
}

@patch
class _SecureFilter {
  @patch
  factory _SecureFilter._() => new _SecureFilterImpl._();
}

@patch
@pragma("vm:entry-point")
class X509Certificate {
  @patch
  @pragma("vm:entry-point")
  factory X509Certificate._() => new _X509CertificateImpl._();
}

class _SecureSocket extends _Socket implements SecureSocket {
  _RawSecureSocket get _raw => super._raw as _RawSecureSocket;

  _SecureSocket(RawSecureSocket raw) : super(raw);

  void renegotiate(
      {bool useSessionCache: true,
      bool requestClientCertificate: false,
      bool requireClientCertificate: false}) {
    _raw.renegotiate(
        useSessionCache: useSessionCache,
        requestClientCertificate: requestClientCertificate,
        requireClientCertificate: requireClientCertificate);
  }

  X509Certificate get peerCertificate {
    if (_raw == null) {
      throw new StateError("peerCertificate called on destroyed SecureSocket");
    }
    return _raw.peerCertificate;
  }

  String get selectedProtocol {
    if (_raw == null) {
      throw new StateError("selectedProtocol called on destroyed SecureSocket");
    }
    return _raw.selectedProtocol;
  }
}

/**
 * _SecureFilterImpl wraps a filter that encrypts and decrypts data travelling
 * over an encrypted socket.  The filter also handles the handshaking
 * and certificate verification.
 *
 * The filter exposes its input and output buffers as Dart objects that
 * are backed by an external C array of bytes, so that both Dart code and
 * native code can access the same data.
 */
@pragma("vm:entry-point")
class _SecureFilterImpl extends NativeFieldWrapperClass1
    implements _SecureFilter {
  // Performance is improved if a full buffer of plaintext fits
  // in the encrypted buffer, when encrypted.
  // SIZE and ENCRYPTED_SIZE are referenced from C++.
  @pragma("vm:entry-point")
  static final int SIZE = 8 * 1024;
  @pragma("vm:entry-point")
  static final int ENCRYPTED_SIZE = 10 * 1024;

  _SecureFilterImpl._() {
    buffers = new List<_ExternalBuffer>(_RawSecureSocket.bufferCount);
    for (int i = 0; i < _RawSecureSocket.bufferCount; ++i) {
      buffers[i] = new _ExternalBuffer(
          _RawSecureSocket._isBufferEncrypted(i) ? ENCRYPTED_SIZE : SIZE);
    }
  }

  void connect(
      String hostName,
      SecurityContext context,
      bool is_server,
      bool requestClientCertificate,
      bool requireClientCertificate,
      Uint8List protocols) native "SecureSocket_Connect";

  void destroy() {
    buffers = null;
    _destroy();
  }

  void _destroy() native "SecureSocket_Destroy";

  void handshake() native "SecureSocket_Handshake";

  void rehandshake() => throw new UnimplementedError();

  int processBuffer(int bufferIndex) => throw new UnimplementedError();

  String selectedProtocol() native "SecureSocket_GetSelectedProtocol";

  void renegotiate(bool useSessionCache, bool requestClientCertificate,
      bool requireClientCertificate) native "SecureSocket_Renegotiate";

  void init() native "SecureSocket_Init";

  X509Certificate get peerCertificate native "SecureSocket_PeerCertificate";

  void registerBadCertificateCallback(Function callback)
      native "SecureSocket_RegisterBadCertificateCallback";

  void registerHandshakeCompleteCallback(Function handshakeCompleteHandler)
      native "SecureSocket_RegisterHandshakeCompleteCallback";

  // This is a security issue, as it exposes a raw pointer to Dart code.
  int _pointer() native "SecureSocket_FilterPointer";

  @pragma("vm:entry-point", "get")
  List<_ExternalBuffer> buffers;
}

@patch
class SecurityContext {
  @patch
  factory SecurityContext({bool withTrustedRoots: false}) {
    return new _SecurityContext(withTrustedRoots);
  }

  @patch
  static SecurityContext get defaultContext {
    return _SecurityContext.defaultContext;
  }

  @patch
  static bool get alpnSupported => true;
}

class _SecurityContext extends NativeFieldWrapperClass1
    implements SecurityContext {
  _SecurityContext(bool withTrustedRoots) {
    _createNativeContext();
    if (withTrustedRoots) {
      _trustBuiltinRoots();
    }
  }

  void _createNativeContext() native "SecurityContext_Allocate";

  static final SecurityContext defaultContext = new _SecurityContext(true);

  void usePrivateKey(String file, {String password}) {
    List<int> bytes = (new File(file)).readAsBytesSync();
    usePrivateKeyBytes(bytes, password: password);
  }

  void usePrivateKeyBytes(List<int> keyBytes, {String password})
      native "SecurityContext_UsePrivateKeyBytes";

  void setTrustedCertificates(String file, {String password}) {
    List<int> bytes = (new File(file)).readAsBytesSync();
    setTrustedCertificatesBytes(bytes, password: password);
  }

  void setTrustedCertificatesBytes(List<int> certBytes, {String password})
      native "SecurityContext_SetTrustedCertificatesBytes";

  void useCertificateChain(String file, {String password}) {
    List<int> bytes = (new File(file)).readAsBytesSync();
    useCertificateChainBytes(bytes, password: password);
  }

  void useCertificateChainBytes(List<int> chainBytes, {String password})
      native "SecurityContext_UseCertificateChainBytes";

  void setClientAuthorities(String file, {String password}) {
    List<int> bytes = (new File(file)).readAsBytesSync();
    setClientAuthoritiesBytes(bytes, password: password);
  }

  void setClientAuthoritiesBytes(List<int> authCertBytes, {String password})
      native "SecurityContext_SetClientAuthoritiesBytes";

  void setAlpnProtocols(List<String> protocols, bool isServer) {
    Uint8List encodedProtocols =
        SecurityContext._protocolsToLengthEncoding(protocols);
    _setAlpnProtocols(encodedProtocols, isServer);
  }

  void _setAlpnProtocols(Uint8List protocols, bool isServer)
      native "SecurityContext_SetAlpnProtocols";
  void _trustBuiltinRoots() native "SecurityContext_TrustBuiltinRoots";
}

/**
 * _X509CertificateImpl wraps an X509 certificate object held by the BoringSSL
 * library. It exposes the fields of the certificate object.
 */
class _X509CertificateImpl extends NativeFieldWrapperClass1
    implements X509Certificate {
  // The native field must be set manually on a new object, in native code.
  // This is done by WrappedX509 in secure_socket.cc.
  _X509CertificateImpl._();

  Uint8List _cachedDer;
  Uint8List get _der native "X509_Der";
  Uint8List get der {
    if (_cachedDer == null) {
      _cachedDer = _der;
    }
    return _cachedDer;
  }

  String _cachedPem;
  String get _pem native "X509_Pem";
  String get pem {
    if (_cachedPem == null) {
      _cachedPem = _pem;
    }
    return _cachedPem;
  }

  Uint8List _cachedSha1;
  Uint8List get _sha1 native "X509_Sha1";
  Uint8List get sha1 {
    if (_cachedSha1 == null) {
      _cachedSha1 = _sha1;
    }
    return _cachedSha1;
  }

  String get subject native "X509_Subject";
  String get issuer native "X509_Issuer";
  DateTime get startValidity {
    return new DateTime.fromMillisecondsSinceEpoch(_startValidity(),
        isUtc: true);
  }

  DateTime get endValidity {
    return new DateTime.fromMillisecondsSinceEpoch(_endValidity(), isUtc: true);
  }

  int _startValidity() native "X509_StartValidity";
  int _endValidity() native "X509_EndValidity";
}
