- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
273 lines
7.4 KiB
Python
273 lines
7.4 KiB
Python
"""
|
|
Transport exceptions and functional exceptions that, when raised,
|
|
trigger well-defined behaviour from the Shapeshifter UFTP
|
|
implementation.
|
|
|
|
Subclasses of TransportException return the appropriate HTTP Status
|
|
Code.
|
|
|
|
Subclasses of FunctionalException return a proper
|
|
PayloadResponseMessage with result = REJECTED and the appropriate
|
|
rejection_reason.
|
|
|
|
More information on these exceptions can be found in the Shapeshifter
|
|
Specification. The relevant parts are copied as docstrings for these
|
|
exceptions.
|
|
"""
|
|
from abc import ABC
|
|
|
|
|
|
class TransportException(Exception, ABC):
|
|
"""
|
|
Base TransportException class that is used by FastApi to return
|
|
the approprate status code.
|
|
"""
|
|
http_status_code: int
|
|
|
|
|
|
class MissingContentLengthException(TransportException):
|
|
"""
|
|
Thrown when the content-length is missing from the message headers.
|
|
"""
|
|
|
|
http_status_code = 411
|
|
|
|
|
|
class InvalidContentTypeException(TransportException):
|
|
"""
|
|
Raised when the Content-Type header is not set to text/xml or the
|
|
character set is not utf-8.
|
|
"""
|
|
|
|
http_status_code = 400
|
|
|
|
|
|
class TooManyRequestsException(TransportException):
|
|
"""
|
|
Raised when the originating IP address is making too many requests
|
|
to the service.
|
|
"""
|
|
|
|
http_status_code = 429
|
|
|
|
|
|
class SchemaException(TransportException):
|
|
"""
|
|
Raised when the XML Body cannot be parsed or does not comply to
|
|
the schema.
|
|
"""
|
|
|
|
http_status_code = 400
|
|
|
|
|
|
class AuthenticationTimeoutException(TransportException):
|
|
"""
|
|
Raised when the sender's public key could not be looked up in
|
|
DNS.
|
|
"""
|
|
|
|
http_status_code = 419
|
|
|
|
|
|
class InvalidSignatureException(TransportException):
|
|
"""
|
|
Raised when the signed message could not be unsealed because of an
|
|
invalid signature.
|
|
"""
|
|
|
|
http_status_code = 401
|
|
|
|
|
|
class FunctionalException(ABC, Exception):
|
|
"""
|
|
Base class for gunctional exceptions. When raised in a request
|
|
context, FastAPI will return the appropriate response message to
|
|
the other participant.
|
|
"""
|
|
rejection_reason: str
|
|
|
|
|
|
class InvalidMessageException(FunctionalException):
|
|
"""
|
|
Despite being schema-compliant, the syntax, type or semantics of
|
|
the message were unacceptable for the receiving implementation.
|
|
"""
|
|
def __init__(self, message):
|
|
super().__init__()
|
|
self.rejection_reason = f"Invalid Message: '{message.__class__.__name__}'"
|
|
|
|
|
|
class InvalidSenderException(FunctionalException):
|
|
"""
|
|
There is a mismatch between the SenderDomain/Role combination in
|
|
the message wrapper and the inner XML message.
|
|
"""
|
|
rejection_reason = "Invalid Sender"
|
|
|
|
|
|
class UnknownRecipientException(FunctionalException):
|
|
"""
|
|
The RecipientDomain and/or RecipientRole specified in the inner
|
|
XML message is not handled by this endpoint.
|
|
"""
|
|
rejection_reason = "Unknown Recipient"
|
|
|
|
|
|
class BarredSenderException(FunctionalException):
|
|
"""
|
|
This endpoint is explicitly blocking messages from this sender.
|
|
"""
|
|
rejection_reason = "Barred Sender"
|
|
|
|
|
|
class DuplicateIdentifierException(FunctionalException):
|
|
"""
|
|
The MessageID attribute of the inner XML message is not unique,
|
|
and has already been used for a message with different content.
|
|
This message has been rejected.
|
|
"""
|
|
rejection_reason = "Duplicate Identifier"
|
|
|
|
|
|
class AlreadySubmittedException(FunctionalException):
|
|
"""
|
|
The MessageID attribute of the inner XML message is not unique,
|
|
but since the message content is the same as that of a previously
|
|
accepted message, this copy can be considered to be successfully
|
|
submitted as well.
|
|
"""
|
|
rejection_reason = "Already Submitted"
|
|
|
|
|
|
class ISPDurationRejectedException(FunctionalException):
|
|
"""
|
|
The message specifies a ISP duration that is not the agreed-upon
|
|
common value for the market in which it is used.
|
|
"""
|
|
rejection_reason = "ISP Duration Rejected"
|
|
|
|
|
|
class TimeZoneRejectedException(FunctionalException):
|
|
"""
|
|
The message specifies a time zone that has a different UTC offset
|
|
than is the agreed-upon common value for the market.
|
|
"""
|
|
rejection_reason = "TimeZone Rejected"
|
|
|
|
|
|
class InvalidCongestionPointException(FunctionalException):
|
|
"""
|
|
Unknown congestion point or the recipient is not active at this
|
|
congestion point.
|
|
"""
|
|
rejection_reason = "Invalid Congestion Point"
|
|
|
|
|
|
class UnknownReferenceException(FunctionalException):
|
|
"""
|
|
The message with the sequence where is referred to is unknown. For
|
|
the concerning reference field name can be filled in (for example
|
|
FlexRequestSequence or PrognosisSequence).
|
|
"""
|
|
rejection_reason = "Unknown Reference"
|
|
|
|
|
|
class ReferencePeriodMismatchException(FunctionalException):
|
|
"""
|
|
The message(s) with the sequence where is referred to contains a
|
|
different Period.
|
|
"""
|
|
rejection_reason = "Reference Period Mismatch"
|
|
|
|
|
|
class ReferenceMessageExpiredException(FunctionalException):
|
|
"""
|
|
The message that is referred to is expired.
|
|
"""
|
|
rejection_reason = "Reference Message Expired"
|
|
|
|
|
|
class ReferenceMessageRevokedException(FunctionalException):
|
|
"""
|
|
The message that is referred to is revoked.
|
|
"""
|
|
rejection_reason = "Reference Message Revoked"
|
|
|
|
|
|
class ISPsOutOfBoundsException(FunctionalException):
|
|
"""
|
|
One or more ISPs are outside the tolerated boundaries: ISPs do not
|
|
exist.
|
|
"""
|
|
rejection_reason = "ISPs Out Of Bounds"
|
|
|
|
|
|
class ISPConflictException(FunctionalException):
|
|
"""
|
|
One or more ISPs are defined more than once, possibly because of
|
|
an incorrect duration.
|
|
"""
|
|
rejection_reason = "ISP Conflict"
|
|
|
|
|
|
class PeriodOutOfBoundsException(FunctionalException):
|
|
"""
|
|
Period of the message is inappropriate. For example: a FlexRequest
|
|
with a Period in the past or a settlement item in a
|
|
FlexSettlement with a Period outside the concerning settlement
|
|
period.
|
|
"""
|
|
rejection_reason = "Period Out Of Bounds"
|
|
|
|
|
|
class ExpirationDateTimeOutOfBoundsException(FunctionalException):
|
|
"""
|
|
ExpirationDateTime is in the past or exceeds the ISPs in the
|
|
message.
|
|
"""
|
|
rejection_reason = "Expiration DateTime Out Of Bounds"
|
|
|
|
|
|
class UnauthorizedException(FunctionalException):
|
|
"""
|
|
CRO is operating in closed mode and the DSO is not pre-registered
|
|
as an authorized participant
|
|
"""
|
|
rejection_reason = "Unauthorized"
|
|
|
|
|
|
class ConnectionConflictException(FunctionalException):
|
|
"""
|
|
A connection is transmitted before at another Congestion Point.
|
|
Return EntityAddress of the concerning Connection and Congestion
|
|
Point where it has been placed before.
|
|
"""
|
|
def __init__(self, connection_entity_address, congestion_point_entity_address):
|
|
super().__init__()
|
|
self.rejection_reason = (
|
|
f"Connection conflict: {connection_entity_address} at {congestion_point_entity_address}"
|
|
)
|
|
|
|
|
|
class SubordinateSequenceNumberException(FunctionalException):
|
|
"""
|
|
The message sequence is lower than that of a previously received
|
|
DSOPortfolioUpdate
|
|
"""
|
|
rejection_reason = "Subordinate Sequence Number"
|
|
|
|
|
|
class ServiceDiscoveryException(Exception):
|
|
"""
|
|
Raised when there is an error during service discovery.
|
|
"""
|
|
|
|
|
|
class ClientTransportException(Exception):
|
|
"""
|
|
Raised when the response to the client is not HTTP 200.
|
|
"""
|
|
def __init__(self, *args, response, **kwargs):
|
|
self.response = response
|
|
super().__init__(*args, **kwargs)
|